@@ -44,6 +44,25 @@ const ceilLog2 = (x, y = 1) => {
44
44
return n ;
45
45
} ;
46
46
47
+ // returns [m, e, m * 10^e] where (m-1) * 10^e < v <= m * 10^e, m < 100 and m mod 10 != 0.
48
+ // therefore `${m}e${e}` is an upper bound approximation with ~2 significant digits.
49
+ const approximateWithTwoSigDigits = v => {
50
+ if ( v <= 0 ) return [ 0 , 0 , 0 ] ; // special case
51
+ let exp = 0 ;
52
+ let tens = 1 ;
53
+ while ( v >= tens * 100 ) {
54
+ ++ exp ;
55
+ tens *= 10 ;
56
+ }
57
+ let mant = Math . ceil ( v / tens ) ;
58
+ if ( mant % 10 === 0 ) { // 60e6 -> 6e7
59
+ mant /= 10 ;
60
+ ++ exp ;
61
+ tens *= 10 ;
62
+ }
63
+ return [ mant , exp , mant * tens ] ;
64
+ } ;
65
+
47
66
// Node.js 14 doesn't have a global performance object.
48
67
const getPerformanceObject = async ( ) => {
49
68
return globalThis . performance || ( await import ( 'perf_hooks' ) ) . performance ;
@@ -587,7 +606,14 @@ const countBytesPerContext = options => (options.modelMaxCount < 128 ? 1 : optio
587
606
588
607
const contextBitsFromMaxMemory = options => {
589
608
const bytesPerContext = predictionBytesPerContext ( options ) + countBytesPerContext ( options ) ;
590
- return floorLog2 ( options . maxMemoryMB * 1048576 , options . sparseSelectors . length * bytesPerContext ) ;
609
+ let contextBits = floorLog2 ( options . maxMemoryMB * 1048576 , options . sparseSelectors . length * bytesPerContext ) ;
610
+
611
+ // the decoder slightly overallocates the memory (~1%) so a naive calculation can exceed the memory limit;
612
+ // recalculate the actual memory usage and decrease contextBits in that case.
613
+ const [ , , actualNumContexts ] = approximateWithTwoSigDigits ( options . sparseSelectors . length << contextBits ) ;
614
+ if ( actualNumContexts * bytesPerContext > options . maxMemoryMB * 1048576 ) -- contextBits ;
615
+
616
+ return contextBits ;
591
617
} ;
592
618
593
619
// String.fromCharCode(...array) is short but doesn't work when array.length is "long enough".
@@ -650,8 +676,9 @@ export class Packer {
650
676
651
677
get memoryUsageMB ( ) {
652
678
const contextBits = this . options . contextBits || contextBitsFromMaxMemory ( this . options ) ;
679
+ const [ , , numContexts ] = approximateWithTwoSigDigits ( this . options . sparseSelectors . length << contextBits ) ;
653
680
const bytesPerContext = predictionBytesPerContext ( this . options ) + countBytesPerContext ( this . options ) ;
654
- return this . options . sparseSelectors . length * bytesPerContext * ( 1 << contextBits ) / 1048576 ;
681
+ return numContexts * bytesPerContext / 1048576 ;
655
682
}
656
683
657
684
static prepareText ( inputs ) {
@@ -959,16 +986,9 @@ export class Packer {
959
986
return 'θ' ;
960
987
} ;
961
988
962
- let contextSize ;
963
- {
964
- let v = numModels , shift = contextBits ;
965
- while ( v % 2 == 0 ) {
966
- v >>= 1 ;
967
- ++ shift ;
968
- }
969
- contextSize = `${ v } <<${ shift } ` ;
970
- // we can also make use of θ, but that wouldn't work in the argument position
971
- }
989
+ // only keep two significant digits, rounding up
990
+ const [ contextMant , contextExp ] = approximateWithTwoSigDigits ( numModels << contextBits ) ;
991
+ const contextSize = `${ contextMant } e${ contextExp } ` ;
972
992
973
993
// 0. first line
974
994
// ι: compressed data, where lowest 6 bits are used and higher bits are chosen to avoid escape sequences.
0 commit comments