Четыре способа посчитать текст
Число видимых знаков, Unicode code points, UTF-16 code units и UTF-8 bytes может различаться. В JavaScript .length считает UTF-16 units. Итератор строки проходит code points; Intl.Segmenter умеет выделять grapheme clusters, приблизительно соответствующие воспринимаемым символам. Это ещё не токены языковой модели. L01.
У строки «Я🙂» два code points, три UTF-16 units и шесть UTF-8 bytes. Буква занимает два байта, emoji четыре. Знак «é» можно записать одним U+00E9 или двумя code points: e и combining acute accent. Внешне строки похожи, байты различны.
Эксперимент. Вставьте кириллицу, emoji, пробел, перевод строки и составной знак. Сопоставьте code point U+…, UTF-8 bytes и grapheme count. Нельзя подменять отсутствие Intl.Segmenter приблизительным числом и объявлять его точным.
Нормализация NFC может сделать эквивалентные записи одинаковыми, но это изменение представления. В нашем обратимом byte-tokenizer она не выполняется, регистр и пробелы сохраняются. Изолированные UTF-16 surrogates отклоняются: стандартное UTF-8 кодирование заменило бы их U+FFFD, нарушив обещание восстановления исходной строки.
Связь с LLM. Контекстное окно обычно измеряется токенами, не буквами или байтами. Для одной фразы разные tokenizers могут выдавать разное число IDs. До сравнения размеров контекста нужно знать именно tokenizer модели.