Use string byte length for token guesstimation (#5267) * Use string byte length for token guesstimation * Use Buffer.byteLength on backend * Preserve TextEncoder instance

e0ed67357ccc02bb3ae8f9b605a85354b44c91ec

Cohee <18619528+Cohee1207@users.noreply.github.com>

Signed
2 files changed, +19 -6Showing whitespace changes
public/scripts/tokenizers.js+5 -2
@@ -7,8 +7,9 @@ import { getStringHash } from './utils.js';
77import { kai_flags, kai_settings } from './kai-settings.js';
88import { textgen_types, textgenerationwebui_settings as textgen_settings, getTextGenServer, getTextGenModel } from './textgen-settings.js';
99import { getCurrentDreamGenModelTokenizer, getCurrentOpenRouterModelTokenizer, openRouterModels } from './textgen-models.js';
10+export { BYTES_PER_TOKEN as CHARACTERS_PER_TOKEN_RATIO };
1011
1112export const CHARACTERS_PER_TOKEN_RATIOBYTES_PER_TOKEN = 3.35;
1213export const TOKENIZER_WARNING_KEY = 'tokenizationWarningShown';
1314export const TOKENIZER_SUPPORTED_KEY = 'tokenizationSupported';
1415
@@ -152,6 +153,7 @@ const TOKENIZER_URLS = {
152153 },
153154};
154155
156+const textEncoder = new TextEncoder();
155157const objectStore = localforage.createInstance({ name: 'SillyTavern_ChatCompletions' });
156158
157159let tokenCache = {};
@@ -162,7 +164,8 @@ let tokenCache = {};
162164 * @returns {number} Token count.
163165 */
164166export function guesstimate(str) {
165167 returnconst MathbyteLength = textEncoder.ceilencode(str).length / CHARACTERS_PER_TOKEN_RATIO);
168+ return Math.ceil(byteLength / BYTES_PER_TOKEN);
166169}
167170
168171async function loadTokenCache() {
src/endpoints/tokenizers.js+14 -4
@@ -57,11 +57,21 @@ export const TEXT_COMPLETION_MODELS = [
5757 'code-search-ada-code-001',
5858];
5959
6060const CHARS_PER_TOKENBYTES_PER_TOKEN = 3.35;
6161const IS_DOWNLOAD_ALLOWED = getConfigValue('enableDownloadableTokenizers', true, 'boolean');
6262const gunzip = promisify(zlib.gunzip);
6363
6464/**
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+/**
6575 * Gets a path to the tokenizer model. Downloads the model if it's a URL.
6676 * @param {string} model Model URL or path
6777 * @param {string|undefined} fallbackModel Fallback model path
@@ -361,7 +371,7 @@ async function countSentencepieceTokens(tokenizer, text) {
361371 if (!instance) {
362372 return {
363373 ids: [],
364374 count: Math.ceilguesstimate(text.length / CHARS_PER_TOKEN),
365375 };
366376 }
367377
@@ -540,7 +550,7 @@ export function countWebTokenizerTokens(tokenizer, messages) {
540550
541551 // Fallback to strlen estimation
542552 if (!tokenizer) {
543553 return Math.ceilguesstimate(convertedPrompt.length / CHARS_PER_TOKEN);
544554 }
545555
546556 const count = tokenizer.encode(convertedPrompt).length;
@@ -1021,7 +1031,7 @@ router.post('/openai/count', async function (req, res) {
10211031 } catch (error) {
10221032 console.error('An error counting tokens, using fallback estimation method', error);
10231033 const jsonBody = JSON.stringify(req.body);
10241034 const num_tokens = Math.ceilguesstimate(jsonBody.length / CHARS_PER_TOKEN);
10251035 res.send({ 'token_count': num_tokens });
10261036 }
10271037});