gpt-tokenizer 2.3.0 → 2.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. package/README.md +13 -1
  2. package/cjs/BytePairEncodingCore.d.ts +30 -17
  3. package/cjs/BytePairEncodingCore.js +202 -134
  4. package/cjs/BytePairEncodingCore.js.map +1 -1
  5. package/cjs/GptEncoding.d.ts +6 -4
  6. package/cjs/GptEncoding.js +48 -14
  7. package/cjs/GptEncoding.js.map +1 -1
  8. package/cjs/encoding/cl100k_base.d.ts +1 -1
  9. package/cjs/encoding/o200k_base.d.ts +1 -1
  10. package/cjs/encoding/p50k_base.d.ts +1 -1
  11. package/cjs/encoding/p50k_edit.d.ts +1 -1
  12. package/cjs/encoding/r50k_base.d.ts +1 -1
  13. package/cjs/model/gpt-3.5-turbo-0125.d.ts +1 -1
  14. package/cjs/model/gpt-3.5-turbo-0301.d.ts +1 -1
  15. package/cjs/model/gpt-3.5-turbo-0613.d.ts +1 -1
  16. package/cjs/model/gpt-3.5-turbo-1106.d.ts +1 -1
  17. package/cjs/model/gpt-3.5-turbo-16k-0613.d.ts +1 -1
  18. package/cjs/model/gpt-3.5-turbo-16k.d.ts +1 -1
  19. package/cjs/model/gpt-3.5-turbo-instruct-0914.d.ts +1 -1
  20. package/cjs/model/gpt-3.5-turbo-instruct.d.ts +1 -1
  21. package/cjs/model/gpt-3.5-turbo.d.ts +1 -1
  22. package/cjs/model/gpt-4-0125-preview.d.ts +1 -1
  23. package/cjs/model/gpt-4-0314.d.ts +1 -1
  24. package/cjs/model/gpt-4-0613.d.ts +1 -1
  25. package/cjs/model/gpt-4-1106-preview.d.ts +1 -1
  26. package/cjs/model/gpt-4-32k-0314.d.ts +1 -1
  27. package/cjs/model/gpt-4-32k-0613.d.ts +1 -1
  28. package/cjs/model/gpt-4-32k.d.ts +1 -1
  29. package/cjs/model/gpt-4-turbo-2024-04-09.d.ts +1 -1
  30. package/cjs/model/gpt-4-turbo-preview.d.ts +1 -1
  31. package/cjs/model/gpt-4-turbo.d.ts +1 -1
  32. package/cjs/model/gpt-4-vision-preview.d.ts +1 -1
  33. package/cjs/model/gpt-4.d.ts +1 -1
  34. package/cjs/model/gpt-4o-2024-05-13.d.ts +1 -1
  35. package/cjs/model/gpt-4o-2024-08-06.d.ts +1 -1
  36. package/cjs/model/gpt-4o-mini-2024-07-18.d.ts +1 -1
  37. package/cjs/model/gpt-4o-mini.d.ts +1 -1
  38. package/cjs/model/gpt-4o.d.ts +1 -1
  39. package/dist/cl100k_base.js +1 -1
  40. package/dist/cl100k_base.js.map +1 -1
  41. package/dist/o200k_base.js +1 -1
  42. package/dist/o200k_base.js.map +1 -1
  43. package/dist/p50k_base.js +1 -1
  44. package/dist/p50k_base.js.map +1 -1
  45. package/dist/p50k_edit.js +1 -1
  46. package/dist/p50k_edit.js.map +1 -1
  47. package/dist/r50k_base.js +1 -1
  48. package/dist/r50k_base.js.map +1 -1
  49. package/esm/BytePairEncodingCore.d.ts +30 -17
  50. package/esm/BytePairEncodingCore.js +201 -133
  51. package/esm/BytePairEncodingCore.js.map +1 -1
  52. package/esm/GptEncoding.d.ts +6 -4
  53. package/esm/GptEncoding.js +49 -15
  54. package/esm/GptEncoding.js.map +1 -1
  55. package/esm/encoding/cl100k_base.d.ts +1 -1
  56. package/esm/encoding/o200k_base.d.ts +1 -1
  57. package/esm/encoding/p50k_base.d.ts +1 -1
  58. package/esm/encoding/p50k_edit.d.ts +1 -1
  59. package/esm/encoding/r50k_base.d.ts +1 -1
  60. package/esm/model/gpt-3.5-turbo-0125.d.ts +1 -1
  61. package/esm/model/gpt-3.5-turbo-0301.d.ts +1 -1
  62. package/esm/model/gpt-3.5-turbo-0613.d.ts +1 -1
  63. package/esm/model/gpt-3.5-turbo-1106.d.ts +1 -1
  64. package/esm/model/gpt-3.5-turbo-16k-0613.d.ts +1 -1
  65. package/esm/model/gpt-3.5-turbo-16k.d.ts +1 -1
  66. package/esm/model/gpt-3.5-turbo-instruct-0914.d.ts +1 -1
  67. package/esm/model/gpt-3.5-turbo-instruct.d.ts +1 -1
  68. package/esm/model/gpt-3.5-turbo.d.ts +1 -1
  69. package/esm/model/gpt-4-0125-preview.d.ts +1 -1
  70. package/esm/model/gpt-4-0314.d.ts +1 -1
  71. package/esm/model/gpt-4-0613.d.ts +1 -1
  72. package/esm/model/gpt-4-1106-preview.d.ts +1 -1
  73. package/esm/model/gpt-4-32k-0314.d.ts +1 -1
  74. package/esm/model/gpt-4-32k-0613.d.ts +1 -1
  75. package/esm/model/gpt-4-32k.d.ts +1 -1
  76. package/esm/model/gpt-4-turbo-2024-04-09.d.ts +1 -1
  77. package/esm/model/gpt-4-turbo-preview.d.ts +1 -1
  78. package/esm/model/gpt-4-turbo.d.ts +1 -1
  79. package/esm/model/gpt-4-vision-preview.d.ts +1 -1
  80. package/esm/model/gpt-4.d.ts +1 -1
  81. package/esm/model/gpt-4o-2024-05-13.d.ts +1 -1
  82. package/esm/model/gpt-4o-2024-08-06.d.ts +1 -1
  83. package/esm/model/gpt-4o-mini-2024-07-18.d.ts +1 -1
  84. package/esm/model/gpt-4o-mini.d.ts +1 -1
  85. package/esm/model/gpt-4o.d.ts +1 -1
  86. package/package.json +1 -1
  87. package/src/BytePairEncodingCore.ts +260 -173
  88. package/src/GptEncoding.ts +82 -20
@@ -11,16 +11,30 @@ export interface BytePairEncodingConfig {
11
11
  tokenSplitRegex: RegExp
12
12
  }
13
13
 
14
+ const emptyBuffer = new Uint8Array(0)
15
+
16
+ export const decoder = new TextDecoder('utf8')
17
+
14
18
  export class BytePairEncodingCore {
15
- readonly bytePairEncoderSize: number
16
- private bytePairEncoder: RawBytePairRanks
17
- private bytePairEncoderSortedLookup: readonly [Uint8Array, number][]
18
- private bytePairRanksDecoder = new Map<number, Uint8Array>()
19
+ readonly mergeableBytePairRankCount: number
20
+ /**
21
+ * an array where the index is the BPE rank,
22
+ * and the value is the string or the array of bytes that it decodes to
23
+ * it may contain holes if token is unused
24
+ */
25
+ private bytePairRankDecoder: RawBytePairRanks
26
+ private bytePairNonUtfRankDecoder = new Map<number, Uint8Array>()
27
+ private bytePairNonUtfSortedEncoder: readonly [Uint8Array, number][]
28
+ /**
29
+ * a reverse map of the bytePairRankDecoder,
30
+ * where the key is the string and the value is the rank
31
+ * values that cannot be represented as a string are present in `bytePairNonUtfSortedEncoder`
32
+ */
33
+ private bytePairStringRankEncoder: Map<string, number>
19
34
  private tokenSplitRegex: RegExp
20
35
  private specialTokensEncoder: Map<string, number>
21
36
  private specialTokensDecoder: Map<number, string>
22
37
  private specialTokenPatternRegex: RegExp
23
- private stringDecoder: Map<string, number>
24
38
  private textEncoder = new TextEncoder()
25
39
 
26
40
  constructor({
@@ -28,23 +42,23 @@ export class BytePairEncodingCore {
28
42
  specialTokenMapping: specialTokenEncoder,
29
43
  tokenSplitRegex,
30
44
  }: BytePairEncodingConfig) {
31
- this.bytePairEncoder = bytePairEncoder
32
- this.stringDecoder = new Map<string, number>()
45
+ this.bytePairRankDecoder = bytePairEncoder
46
+ this.bytePairStringRankEncoder = new Map<string, number>()
33
47
 
34
48
  // size without array holes (which may be present in the encoder)
35
- this.bytePairEncoderSize = Object.keys(bytePairEncoder).length
49
+ this.mergeableBytePairRankCount = Object.keys(bytePairEncoder).length
36
50
  const binaryLookup: [Uint8Array, number][] = []
37
51
  // forEach skips array holes:
38
52
  bytePairEncoder.forEach((value, rank) => {
39
53
  if (typeof value === 'string') {
40
- this.stringDecoder.set(value, rank)
54
+ this.bytePairStringRankEncoder.set(value, rank)
41
55
  return
42
56
  }
43
57
  const byteArray = new Uint8Array(value)
44
58
  binaryLookup.push([byteArray, rank])
45
- this.bytePairRanksDecoder.set(rank, byteArray)
59
+ this.bytePairNonUtfRankDecoder.set(rank, byteArray)
46
60
  })
47
- this.bytePairEncoderSortedLookup = binaryLookup.sort((a, b) =>
61
+ this.bytePairNonUtfSortedEncoder = binaryLookup.sort((a, b) =>
48
62
  compareUint8Arrays(a[0], b[0]),
49
63
  )
50
64
  this.specialTokensEncoder = specialTokenEncoder ?? new Map<string, number>()
@@ -53,85 +67,18 @@ export class BytePairEncodingCore {
53
67
  : new Map<number, string>()
54
68
  this.tokenSplitRegex = tokenSplitRegex
55
69
 
56
- const parts = [...this.specialTokensEncoder.keys()].map(escapeRegExp)
57
- const joinedParts = parts.join('|')
70
+ const escapedSpecialTokens = [...this.specialTokensEncoder.keys()].map(
71
+ escapeRegExp,
72
+ )
73
+ const allSpecialTokensRegex = escapedSpecialTokens.join('|')
58
74
  try {
59
- this.specialTokenPatternRegex = new RegExp(joinedParts)
75
+ this.specialTokenPatternRegex = new RegExp(allSpecialTokensRegex)
60
76
  } catch {
61
77
  throw new Error('Invalid regular expression pattern.')
62
78
  }
63
79
  }
64
80
 
65
- getBpeRankFromString(key: string): number | undefined {
66
- return this.stringDecoder.get(key)
67
- }
68
-
69
- getBpeRankFromStringOrThrow(key: string): number {
70
- const value = this.getBpeRankFromString(key)
71
- if (value === undefined) {
72
- throw new Error(
73
- `The byte-pair encoding does not contain a value for: ${key}`,
74
- )
75
- }
76
- return value
77
- }
78
-
79
- getBpeRankFromBytes(key: Uint8Array): number | undefined {
80
- const keyAsString = tryConvertToString(key)
81
-
82
- if (keyAsString !== undefined) {
83
- return this.getBpeRankFromString(keyAsString)
84
- }
85
-
86
- // Perform binary search on the binary keys
87
- const index = this.binarySearch(key)
88
- if (index !== -1) {
89
- return this.bytePairEncoderSortedLookup[index]![1]
90
- }
91
-
92
- return undefined
93
- }
94
-
95
- getBpeRankFromBytesOrThrow(key: Uint8Array): number {
96
- const value = this.getBpeRankFromBytes(key)
97
- if (value === undefined) {
98
- throw new Error(
99
- `The byte-pair encoding does not contain a value for: ${key.toString()}`,
100
- )
101
- }
102
- return value
103
- }
104
-
105
- // Binary search on the binary keys
106
- binarySearch(key: Uint8Array): number {
107
- let low = 0
108
- let high = this.bytePairEncoderSortedLookup.length - 1
109
-
110
- while (low <= high) {
111
- // eslint-disable-next-line no-bitwise
112
- const mid = (low + high) >>> 1
113
- const midKey = this.bytePairEncoderSortedLookup[mid]![0]
114
- let cmp = 0
115
- for (let i = 0; i < Math.min(midKey.length, key.length); i++) {
116
- cmp = midKey[i]! - key[i]!
117
- if (cmp !== 0) break
118
- }
119
- if (cmp === 0) {
120
- cmp = midKey.length - key.length
121
- }
122
- if (cmp === 0) {
123
- return mid
124
- }
125
- if (cmp < 0) {
126
- low = mid + 1
127
- } else {
128
- high = mid - 1
129
- }
130
- }
131
- return -1
132
- }
133
-
134
- *encodeNative(
81
+ *encodeNativeGenerator(
135
82
  text: string,
136
83
  allowedSpecial?: Set<string>,
137
84
  ): Generator<number[], number, undefined> {
@@ -143,7 +90,6 @@ export class BytePairEncodingCore {
143
90
  text,
144
91
  allowedSpecial,
145
92
  startIndex,
146
- this.specialTokenPatternRegex,
147
93
  )
148
94
 
149
95
  const endIndex =
@@ -186,35 +132,56 @@ export class BytePairEncodingCore {
186
132
  return lastTokenLength
187
133
  }
188
134
 
189
- findNextSpecialStartIndex(
190
- text: string,
191
- allowedSpecial: Set<string> | undefined,
192
- startIndex: number,
193
- specialRegex: RegExp,
194
- ): number | undefined {
195
- let searchIndex = startIndex
135
+ encodeNative(text: string, allowedSpecial?: Set<string>): number[] {
136
+ let startIndex = 0
137
+ const tokensArray: number[] = [] // Flat list to collect the tokens
196
138
 
197
139
  // eslint-disable-next-line no-constant-condition
198
140
  while (true) {
199
- const nextSpecialMatch = specialRegex.exec(
200
- text.slice(Math.max(0, searchIndex)),
141
+ const nextSpecialStartIndex = this.findNextSpecialStartIndex(
142
+ text,
143
+ allowedSpecial,
144
+ startIndex,
201
145
  )
202
146
 
203
- if (!nextSpecialMatch) {
204
- return undefined
205
- }
147
+ const endIndex =
148
+ nextSpecialStartIndex !== undefined
149
+ ? nextSpecialStartIndex
150
+ : text.length
206
151
 
207
- const [specialToken] = nextSpecialMatch
152
+ const textSegment = text.slice(startIndex, endIndex - startIndex)
208
153
 
209
- if (allowedSpecial?.has(specialToken)) {
210
- return nextSpecialMatch.index + searchIndex
154
+ for (const [match] of textSegment.matchAll(this.tokenSplitRegex)) {
155
+ const token = this.getBpeRankFromString(match)
156
+ if (token !== undefined) {
157
+ tokensArray.push(token)
158
+
159
+ continue
160
+ }
161
+
162
+ const tokens = this.bytePairEncode(match)
163
+ tokensArray.push(...tokens)
211
164
  }
212
165
 
213
- searchIndex = nextSpecialMatch.index + searchIndex + 1
166
+ if (nextSpecialStartIndex !== undefined) {
167
+ const specialToken = text.slice(Math.max(0, nextSpecialStartIndex))
168
+ const specialTokenValue = this.specialTokensEncoder.get(specialToken)
169
+ if (specialTokenValue === undefined) {
170
+ throw new Error(
171
+ `Special token "${specialToken}" is not in the special token encoder.`,
172
+ )
173
+ }
174
+ tokensArray.push(specialTokenValue)
175
+ startIndex = nextSpecialStartIndex + specialToken.length
176
+ } else {
177
+ break
178
+ }
214
179
  }
180
+
181
+ return tokensArray
215
182
  }
216
183
 
217
- *decodeNative(
184
+ *decodeNativeGenerator(
218
185
  tokens: Iterable<number>,
219
186
  ): Generator<Uint8Array | string, void, void> {
220
187
  for (const token of tokens) {
@@ -225,7 +192,36 @@ export class BytePairEncodingCore {
225
192
  }
226
193
  }
227
194
 
228
- async *decodeNativeAsync(
195
+ decodeNative(tokens: Iterable<number>): string {
196
+ let decoded = ''
197
+ let intBuffer = emptyBuffer
198
+
199
+ for (const token of tokens) {
200
+ const tokenBytes = this.tryDecodeToken(token)
201
+ if (tokenBytes === undefined) {
202
+ throw new Error(`Token ${token} is not in the byte pair encoder.`)
203
+ }
204
+ if (typeof tokenBytes === 'string') {
205
+ if (intBuffer !== emptyBuffer) {
206
+ decoded += decoder.decode(intBuffer, { stream: true })
207
+ intBuffer = emptyBuffer
208
+ }
209
+ decoded += tokenBytes
210
+ } else {
211
+ const newBuffer = new Uint8Array(intBuffer.length + tokenBytes.length)
212
+ newBuffer.set(intBuffer)
213
+ newBuffer.set(tokenBytes, intBuffer.length)
214
+ intBuffer = newBuffer
215
+ }
216
+ }
217
+
218
+ if (intBuffer !== emptyBuffer) {
219
+ decoded += decoder.decode(intBuffer, { stream: true })
220
+ }
221
+ return decoded
222
+ }
223
+
224
+ async *decodeNativeAsyncIterable(
229
225
  tokens: AsyncIterable<number>,
230
226
  ): AsyncGenerator<Uint8Array | string> {
231
227
  for await (const token of tokens) {
@@ -236,13 +232,110 @@ export class BytePairEncodingCore {
236
232
  }
237
233
  }
238
234
 
239
- tryDecodeToken(tokenRank: number): Uint8Array | string | undefined {
240
- const value = this.bytePairEncoder[tokenRank]
235
+ private getBpeRankFromString(key: string): number | undefined {
236
+ return this.bytePairStringRankEncoder.get(key)
237
+ }
238
+
239
+ private getBpeRankFromStringOrThrow(key: string): number {
240
+ const value = this.getBpeRankFromString(key)
241
+ if (value === undefined) {
242
+ throw new Error(
243
+ `The byte-pair encoding does not contain a value for: ${key}`,
244
+ )
245
+ }
246
+ return value
247
+ }
248
+
249
+ private getBpeRankFromBytes(key: Uint8Array): number | undefined {
250
+ const keyAsString = tryConvertToString(key)
251
+
252
+ if (keyAsString !== undefined) {
253
+ return this.getBpeRankFromString(keyAsString)
254
+ }
255
+
256
+ // Perform binary search on the binary keys
257
+ const index = this.binarySearch(key)
258
+ if (index !== -1) {
259
+ return this.bytePairNonUtfSortedEncoder[index]![1]
260
+ }
261
+
262
+ return undefined
263
+ }
264
+
265
+ private getBpeRankFromBytesOrThrow(key: Uint8Array): number {
266
+ const value = this.getBpeRankFromBytes(key)
267
+ if (value === undefined) {
268
+ throw new Error(
269
+ `The byte-pair encoding does not contain a value for: ${key.toString()}`,
270
+ )
271
+ }
272
+ return value
273
+ }
274
+
275
+ // Binary search on the binary keys
276
+ private binarySearch(key: Uint8Array): number {
277
+ let low = 0
278
+ let high = this.bytePairNonUtfSortedEncoder.length - 1
279
+
280
+ while (low <= high) {
281
+ // eslint-disable-next-line no-bitwise
282
+ const mid = (low + high) >>> 1
283
+ const midKey = this.bytePairNonUtfSortedEncoder[mid]![0]
284
+ let cmp = 0
285
+ const maxLength = Math.min(midKey.length, key.length)
286
+ for (let i = 0; i < maxLength; i++) {
287
+ cmp = midKey[i]! - key[i]!
288
+ if (cmp !== 0) break
289
+ }
290
+ if (cmp === 0) {
291
+ cmp = midKey.length - key.length
292
+ }
293
+ if (cmp === 0) {
294
+ return mid
295
+ }
296
+ if (cmp < 0) {
297
+ low = mid + 1
298
+ } else {
299
+ high = mid - 1
300
+ }
301
+ }
302
+ return -1
303
+ }
304
+
305
+ private findNextSpecialStartIndex(
306
+ text: string,
307
+ allowedSpecial: Set<string> | undefined,
308
+ startIndex: number,
309
+ ): number | undefined {
310
+ let searchIndex = startIndex
311
+
312
+ // eslint-disable-next-line no-constant-condition
313
+ while (true) {
314
+ const nextSpecialMatch = this.specialTokenPatternRegex.exec(
315
+ text.slice(Math.max(0, searchIndex)),
316
+ )
317
+
318
+ if (!nextSpecialMatch) {
319
+ return undefined
320
+ }
321
+
322
+ const specialToken = nextSpecialMatch[0]
323
+
324
+ if (allowedSpecial?.has(specialToken)) {
325
+ return nextSpecialMatch.index + searchIndex
326
+ }
327
+
328
+ searchIndex = nextSpecialMatch.index + searchIndex + 1
329
+ }
330
+ }
331
+
332
+ private tryDecodeToken(tokenRank: number): Uint8Array | string | undefined {
333
+ const value = this.bytePairRankDecoder[tokenRank]
241
334
  if (typeof value === 'string') {
242
335
  return value
243
336
  }
244
337
  if (typeof value === 'object') {
245
- const fromBinary = this.bytePairRanksDecoder.get(tokenRank)
338
+ const fromBinary = this.bytePairNonUtfRankDecoder.get(tokenRank)
246
339
  if (fromBinary) {
247
340
  return fromBinary
248
341
  }
@@ -250,108 +343,102 @@ export class BytePairEncodingCore {
250
343
  return this.specialTokensDecoder.get(tokenRank)
251
344
  }
252
345
 
253
- bytePairEncode(input: string): number[] {
346
+ private bytePairEncode(input: string): number[] {
254
347
  if (input.length === 1 && isAscii(input.codePointAt(0)!)) {
255
348
  return [this.getBpeRankFromStringOrThrow(input)]
256
349
  }
257
350
 
258
351
  const inputBytes = this.textEncoder.encode(input)
259
352
 
260
- return this.bytePairMerge(inputBytes, (start, end) => {
261
- const key = inputBytes.subarray(start, end)
262
- return this.getBpeRankFromBytesOrThrow(key)
263
- })
353
+ return this.bytePairMerge(inputBytes)
264
354
  }
265
355
 
266
- bytePairMerge(
356
+ private bytePairMerge(
267
357
  // Input array of bytes to process
268
358
  piece: Uint8Array,
269
- // Function to apply to each final segment after merging
270
- getByteForRange: (start: number, end: number) => number,
271
359
  ): number[] {
272
- // Create an array of partition objects. Each partition tracks the start index in 'piece'
273
- // and a rank value for adjacent pairs (initially set to positive infinity).
274
- const partitions = Array.from({ length: piece.length + 1 }, (_, i) => ({
275
- start: i,
276
- rank: Number.POSITIVE_INFINITY, // Rank starts at infinity (unmerged)
277
- }))
278
-
279
- // Helper function to get the rank of a byte pair starting at 'startIndex'.
280
- // 'skip' determines how far we look ahead (usually 0, for consecutive pairs).
281
- const getRank = (startIndex: number, skip: number): number | undefined => {
282
- if (startIndex + skip + 2 >= partitions.length) {
283
- // Avoid out-of-bounds errors, return undefined when no valid pair exists
284
- return undefined
360
+ // 'starts' holds the start indices of each partition
361
+ const starts: number[] = []
362
+ // 'ranks' holds the BPE ranks of each partition pair
363
+ const ranks: number[] = []
364
+
365
+ // Helper function to get the rank of a byte pair starting at 'startIndex'
366
+ const getRank = (
367
+ startIndex: number,
368
+ pairStart = starts[startIndex],
369
+ pairEnd = starts[startIndex + 2],
370
+ ): number => {
371
+ if (pairEnd === undefined) {
372
+ // No valid pair exists
373
+ return Number.POSITIVE_INFINITY
285
374
  }
286
375
 
287
- // Get the byte pair by extracting a subarray starting at 'startIndex' and ending at
288
- // the start of the partition after 'skip + 2'.
289
- const key = piece.subarray(
290
- partitions[startIndex]!.start,
291
- partitions[startIndex + skip + 2]!.start,
292
- )
376
+ // Extract the byte pair
377
+ const key = piece.subarray(pairStart, pairEnd)
293
378
 
294
- // Retrieve the rank of this byte pair from the BPE rank function
295
- return this.getBpeRankFromBytes(key)
379
+ // Retrieve the BPE rank of this byte pair (if it exists)
380
+ const rank = this.getBpeRankFromBytes(key)
381
+ return rank ?? Number.POSITIVE_INFINITY
296
382
  }
297
383
 
298
- // Initialize the ranks for all adjacent pairs in the array
299
- for (let i = 0; i < partitions.length - 2; i++) {
300
- // Get the rank for the pair starting at index 'i'
301
- const rank = getRank(i, 0)
302
- if (rank !== undefined) {
303
- // Assign the rank to the partition at index 'i'
304
- partitions[i]!.rank = rank
384
+ // Initialize the 'starts' array with all possible start indices
385
+ for (let i = 0; i <= piece.length; i++) {
386
+ starts.push(i)
387
+ if (i < piece.length - 1) {
388
+ // Initialize the BPE values for all adjacent pairs
389
+ ranks.push(getRank(i, i, i + 2))
390
+ } else {
391
+ // Initialize BPE values to infinity for the last pair
392
+ ranks.push(Number.POSITIVE_INFINITY)
305
393
  }
306
394
  }
307
395
 
308
396
  // Iteratively merge byte pairs until no more useful merges can be done
309
- while (partitions.length > 1) {
310
- let minRank = Number.POSITIVE_INFINITY
311
- let minRankIdx = 0
312
-
313
- // Find the partition with the minimum rank, i.e., the most important pair to merge next
314
- let i = 0
315
- for (const partition of partitions) {
316
- if (partition.rank < minRank) {
317
- minRank = partition.rank
318
- minRankIdx = i
397
+ while (starts.length > 1) {
398
+ let lowestRank = Number.POSITIVE_INFINITY
399
+ let lowestPartitionIndex = -1
400
+
401
+ // Find the partition with the minimum rank
402
+ for (let i = 0; i < ranks.length - 1; i++) {
403
+ const rank = ranks[i]!
404
+ if (rank < lowestRank) {
405
+ lowestRank = rank
406
+ lowestPartitionIndex = i
319
407
  }
320
- i++
321
408
  }
322
409
 
323
410
  // If no valid pair is left to merge, exit the loop
324
- if (minRank === Number.POSITIVE_INFINITY) {
411
+ if (
412
+ lowestRank === Number.POSITIVE_INFINITY ||
413
+ lowestPartitionIndex === -1
414
+ ) {
325
415
  break
326
416
  }
327
417
 
328
- // Update the rank of the partition after the merged one
329
- partitions[minRankIdx]!.rank =
330
- getRank(minRankIdx, 1) ?? Number.POSITIVE_INFINITY
418
+ // Merge the pair at 'lowestPartitionIndex' by removing the next start index
419
+ starts.splice(lowestPartitionIndex + 1, 1)
420
+ // Remove the BPE value of the merged pair
421
+ ranks.splice(lowestPartitionIndex, 1)
331
422
 
332
- // Update the rank of the partition before the merged one (if exists)
333
- if (minRankIdx > 0) {
334
- partitions[minRankIdx - 1]!.rank =
335
- getRank(minRankIdx - 1, 1) ?? Number.POSITIVE_INFINITY
336
- }
423
+ // Update the current merged pair's rank
424
+ ranks[lowestPartitionIndex] = getRank(lowestPartitionIndex)
337
425
 
338
- // Merge by removing the partition after the one we just merged
339
- partitions.splice(minRankIdx + 1, 1)
426
+ // Update the rank of the previous pair, if it exists
427
+ if (lowestPartitionIndex > 0) {
428
+ ranks[lowestPartitionIndex - 1] = getRank(lowestPartitionIndex - 1)
429
+ }
340
430
  }
341
431
 
342
432
  // Create the final output by applying the transform function to each partitioned range
343
433
  const output: number[] = []
344
- for (let i = 0; i < partitions.length - 1; i++) {
345
- output.push(
346
- getByteForRange(
347
- // start index
348
- partitions[i]!.start,
349
- // end index
350
- partitions[i + 1]!.start,
351
- ),
434
+ for (let i = 0; i < starts.length - 1; i++) {
435
+ const pairStart = starts[i]
436
+ const pairEnd = starts[i + 1]
437
+ const bpeValue = this.getBpeRankFromBytesOrThrow(
438
+ piece.subarray(pairStart, pairEnd),
352
439
  )
440
+ output.push(bpeValue)
353
441
  }
354
-
355
442
  return output
356
443
  }
357
444
  }