Use string byte length for token guesstimation (#5267) * Use string byte length for token guesstimation * Use Buffer.byteLength on backend * Preserve TextEncoder instance
Signed| @@ -7,8 +7,9 @@ import { getStringHash } from './utils.js'; | ||
| 7 | 7 | import { kai_flags, kai_settings } from './kai-settings.js'; |
| 8 | 8 | import { textgen_types, textgenerationwebui_settings as textgen_settings, getTextGenServer, getTextGenModel } from './textgen-settings.js'; |
| 9 | 9 | import { getCurrentDreamGenModelTokenizer, getCurrentOpenRouterModelTokenizer, openRouterModels } from './textgen-models.js'; |
| 10 | +export { BYTES_PER_TOKEN as CHARACTERS_PER_TOKEN_RATIO }; | |
| 10 | 11 | |
| 11 | 12 | export const CHARACTERS_PER_TOKEN_RATIOBYTES_PER_TOKEN = 3.35; |
| 12 | 13 | export const TOKENIZER_WARNING_KEY = 'tokenizationWarningShown'; |
| 13 | 14 | export const TOKENIZER_SUPPORTED_KEY = 'tokenizationSupported'; |
| 14 | 15 | |
| @@ -152,6 +153,7 @@ const TOKENIZER_URLS = { | ||
| 152 | 153 | }, |
| 153 | 154 | }; |
| 154 | 155 | |
| 156 | +const textEncoder = new TextEncoder(); | |
| 155 | 157 | const objectStore = localforage.createInstance({ name: 'SillyTavern_ChatCompletions' }); |
| 156 | 158 | |
| 157 | 159 | let tokenCache = {}; |
| @@ -162,7 +164,8 @@ let tokenCache = {}; | ||
| 162 | 164 | * @returns {number} Token count. |
| 163 | 165 | */ |
| 164 | 166 | export function guesstimate(str) { |
| 165 | 167 | returnconst MathbyteLength = textEncoder.ceilencode(str).length / CHARACTERS_PER_TOKEN_RATIO); |
| 168 | + return Math.ceil(byteLength / BYTES_PER_TOKEN); | |
| 166 | 169 | } |
| 167 | 170 | |
| 168 | 171 | async function loadTokenCache() { |
| @@ -57,11 +57,21 @@ export const TEXT_COMPLETION_MODELS = [ | ||
| 57 | 57 | 'code-search-ada-code-001', |
| 58 | 58 | ]; |
| 59 | 59 | |
| 60 | 60 | const CHARS_PER_TOKENBYTES_PER_TOKEN = 3.35; |
| 61 | 61 | const IS_DOWNLOAD_ALLOWED = getConfigValue('enableDownloadableTokenizers', true, 'boolean'); |
| 62 | 62 | const gunzip = promisify(zlib.gunzip); |
| 63 | 63 | |
| 64 | 64 | /** |
| 65 | + * Guesstimates the token count for a string. | |
| 66 | + * @param {string} str String to tokenize. | |
| 67 | + * @returns {number} Token count. | |
| 68 | + */ | |
| 69 | +function guesstimate(str) { | |
| 70 | + const byteLength = Buffer.byteLength(str, 'utf8'); | |
| 71 | + return Math.ceil(byteLength / BYTES_PER_TOKEN); | |
| 72 | +} | |
| 73 | + | |
| 74 | +/** | |
| 65 | 75 | * Gets a path to the tokenizer model. Downloads the model if it's a URL. |
| 66 | 76 | * @param {string} model Model URL or path |
| 67 | 77 | * @param {string|undefined} fallbackModel Fallback model path |
| @@ -361,7 +371,7 @@ async function countSentencepieceTokens(tokenizer, text) { | ||
| 361 | 371 | if (!instance) { |
| 362 | 372 | return { |
| 363 | 373 | ids: [], |
| 364 | 374 | count: Math.ceilguesstimate(text.length / CHARS_PER_TOKEN), |
| 365 | 375 | }; |
| 366 | 376 | } |
| 367 | 377 | |
| @@ -540,7 +550,7 @@ export function countWebTokenizerTokens(tokenizer, messages) { | ||
| 540 | 550 | |
| 541 | 551 | // Fallback to strlen estimation |
| 542 | 552 | if (!tokenizer) { |
| 543 | 553 | return Math.ceilguesstimate(convertedPrompt.length / CHARS_PER_TOKEN); |
| 544 | 554 | } |
| 545 | 555 | |
| 546 | 556 | const count = tokenizer.encode(convertedPrompt).length; |
| @@ -1021,7 +1031,7 @@ router.post('/openai/count', async function (req, res) { | ||
| 1021 | 1031 | } catch (error) { |
| 1022 | 1032 | console.error('An error counting tokens, using fallback estimation method', error); |
| 1023 | 1033 | const jsonBody = JSON.stringify(req.body); |
| 1024 | 1034 | const num_tokens = Math.ceilguesstimate(jsonBody.length / CHARS_PER_TOKEN); |
| 1025 | 1035 | res.send({ 'token_count': num_tokens }); |
| 1026 | 1036 | } |
| 1027 | 1037 | }); |