Заголовок этой статьи, по сути, — наглядная демонстрация того, как его видит языковая модель. Куски слов, нарезанные по логике, которая к русскому языку не имеет вообще никакого отношения.
И из этой нарезки растет длинный список вещей, которые иногда списывают на «глупую нейронку» (особенно если речь идет о небольших локальных моделях): модель не может посчитать буквы в слове, путается в арифметике, коверкает редкие фамилии, а русский текст обходится вдвое дороже английского при том же смысле.
А еще оттуда же растут вещи, которые с токенами вообще вроде бы не связаны. Например: почему «давай рассуждать по шагам» реально работает, и почему лишний пробел в конце промпта портит ответ.
Виноват во всем этом компонент стека, который стоит первым в пайплайне, никогда не обучается вместе с моделью и которому обычно посвящают полтора абзаца в начале туториала. Что ж, давайте по порядку.
Модель вообще не видит букв
Начнем с самого начала, с немного тривиальных, но важных моментов. Когда вы пишете модели «привет», кажется, что она получает слово «привет». Ну вот не получает, вместо этого она получает число. Например, 15 043.
Внутри трансформера есть матрица эмбеддингов (вместо слов). Это огромная таблица, где каждой строке соответствует один элемент словаря. Токен — это и есть номер строки в этой таблице. Ваш текст на входе превращается в список номеров, каждый номер меняется на вектор из соответствующей строки, и дальше вся математика происходит только с векторами.
Токенизатор — это переводчик между двумя мирами. Слева текст, справа числа. У него есть словарь (какие кусочки существуют) и правила (как резать текст на эти кусочки). Больше он ничего не умеет и ничего не решает.
Аналогия, которую я давно где-то прочел (настолько, что уже не вспомню, где именно), довольно точно работает: представьте, что вам надо пересказать любую фразу набором готовых магнитиков на холодильник. Магнитиков конечное число, они разного размера — на некоторых написаны целые слова, на некоторых по две буквы, на некоторых один символ. Ваша задача выложить фразу так, чтобы магнитиков ушло поменьше. Вот это и есть токенизация. А набор магнитиков кто-то составил за вас заранее.
Теперь понятно, откуда берется главная проблема: набор конечный. Обычно 32 000–256 000 элементов. И в него надо запихнуть все языки мира, весь код, всю разметку, эмодзи, математику и химические формулы.
Очевидное решение (на первый взгляд) — сделать магнитики из отдельных букв. Тогда влезет что угодно, набор-то крошечный. Но фразы становятся чудовищно длинными: одна страница текста вместит тысячи элементов, а стоимость внимания в трансформере растет квадратично длине.
Другая крайность (которая приходит на ум обычно после первого решения) — магнитик на каждое слово. Коротко и красиво. Но любое слово, которого не оказалось в наборе, превращается в <unk> — «неизвестно что». Модель его не прочитает и никогда не напишет. Для русского с его десятками словоформ на каждое слово — это приговор сразу.
Значит, нужно что-то посередине. Кусочки слов. Вопрос, а какие именно кусочки?
BPE: архиватор 1994 года, который случайно устроился лингвистом
Byte Pair Encoding (BPE) придумал Филип Гейдж в 1994 году, как алгоритм сжатия данных. Про языковые модели тогда, мягко говоря, и не думали.
Алгоритм примитивен до неприличия:
- разбейте текст на отдельные символы;
- найдите самую частую пару соседних элементов;
- склейте ее в один новый элемент, запишите в словарь;
- повторите, пока словарь не наберет нужный размер.
Вот и все. В 2015 это перетащили в машинный перевод, и с тех пор так работает почти все, что вы используете. Обучение токенизатора — это буквально прогон архиватора по гигантскому корпусу слов.
Прогоним руками
Проще один раз посмотреть. Возьмем микроскопический корпус из четырех слов с частотами:
низ × 4
низкий × 3
низко × 2
нить × 3
Сначала все разбивается на отдельные буквы:
н и з × 4
н и з к и й × 3
н и з к о × 2
н и т ь × 3
Теперь считаем все соседние пары. Пара н+и встречается во всех четырех словах: 4+3+2+3 = 12 раз. Больше, чем любая другая. Склеиваем:
[ни] з × 4
[ни] з к и й × 3
[ни] з к о × 2
[ни] т ь × 3
Пересчитываем. Теперь чемпион — ни+з: 4+3+2 = 9 раз. Склеиваем:
[низ] × 4
[низ] к и й × 3
[низ] к о × 2
[ни] т ь × 3
Пока все выглядит осмысленно: алгоритм нашел корень «низ», прямо как на уроке русского. Считаем дальше. Следующая по частоте пара — низ+к: 3 + 2 = 5 раз. Склеиваем:
[низ] × 4
[низк] и й × 3
[низк] о × 2
[ни] т ь × 3
И вот тут остановитесь на секунду и посмотрите, что произошло.
Алгоритм только что склеил корень «низ» с суффиксом «-к-» в один неделимый кусок «низк». Ни один лингвист в здравом уме так не сделает. Но с точки зрения статистики это абсолютно правильный ход: пара частая, склеить выгодно, экономия налицо.

Нам понадобилось три шага, а в реальном токенизаторе таких шагов 128 000. Результатом обучения являются два файла, которые лежат в открытом доступе у открытых моделек.
- vocab.json — какие кусочки существуют и под какими номерами.
- merges.txt — список слияний строго в том порядке, в котором они были выучены. Порядок важен, потому что применяются они потом ровно так же, по очереди.
А как это работает на новом тексте
Здесь частая путаница, поэтому расскажу отдельно. Все, что описано выше, происходит один раз, при создании токенизатора. Когда вы отправляете модели запрос, никто ничего не пересчитывает.
Ваш текст просто режется на буквы, а потом к нему применяются заранее выученные слияния — в том самом порядке из merges.txt.
Именно поэтому слово, которого не было в обучающем корпусе токенизатора, собирается из того, что подвернулось. Скормите нашему игрушечному токенизатору слово «низкоуровневый» — он найдет знакомое «низк», а все остальное рассыплет на буквы, потому что склеивать их он не учился.
С реальными токенизаторами то же самое, просто менее заметно. Внутренние термины, редкие фамилии, артикулы — все это модель видит как крошку из обломков чужих слов.
Теперь важный момент. На каждом шаге алгоритм выбирает самую частую пару, но не самую осмысленную.
У него нет ни малейшего представления о морфемах. Он не знает, что «пере-» — приставка, а «-ть» — окончание инфинитива. Он знает только частоты.
И вот тут английскому просто повезло. Английские суффиксы короткие и очень частотные, поэтому BPE на английском выглядит почти как обычный морфологический разбор. walking → walk + ing. Красиво же! И это просто совпадение, такая вот особенность английской морфологии.
С русским так не работает. У нас приставок, суффиксов и окончаний в разы больше, каждое конкретное сочетание встречается реже, а доля русского в корпусе токенизатора составляет обычно проценты.
В итоге граница токена проходит там, где так сложилась статистика. «Переписал» может распасться на «перепис» + «ал», а «переписывать» — остаться одним куском целиком. Логики, которую можно объяснить школьнику, здесь нет.
Байты, а не символы
Современные токенизаторы работают не с символами, а с байтами UTF-8. Сделано это из хороших побуждений: гарантия, что представим вообще любой ввод. Не будет <unk> ни на эмодзи, ни на что-либо еще. Стартовый алфавит — 256 байтов, дальше склеивай что хочешь.
Но у UTF-8 есть маленькая деталь. Латинская буква — 1 байт. Кириллическая — 2. Иероглиф — 3. Эмодзи — 4.
То есть русское слово стартует с двойной стоимости по количеству элементов, которые надо склеить. А теперь вспомним, что слияния распределяются пропорционально частоте в корпусе, и что английского в корпусе большинство.
Вывод такой: один и тот же смысл на русском обычно занимает в 2–3 раза больше токенов, чем на английском.
И это уже можно посчитать в рублях:
- счет за API в 2–3 раза выше при том же содержании;
- реальное контекстное окно в 2–3 раза меньше заявленного в маркетинге;
- ответ упирается в max_tokens там, где английский спокойно пролезает;
- инференс медленнее — токенов больше.
Кстати, для языков поменьше все гораздо хуже. Для некоторых африканских и юго-восточноазиатских языков разница с английским достигает десятков раз. Получается своеобразный налог на язык, зашитый в архитектуру: чем меньше ваш язык представлен в интернете, тем дороже вам обходится каждое предложение.
Токен — это квант мышления
Модель генерирует текст по одному токену за раз. На каждый токен ровно один проход через всю сеть. Фиксированное количество умножений матриц.
Вдумайтесь, количество вычислений, которое модель может потратить, жестко привязано к количеству токенов, которые она произвела.
Модель не может остановиться и поразмышлять подольше над трудным местом. У нее нет такой опции. Токен — это неделимый квант вычислений, и другого способа выделить себе больше ресурса, кроме как сгенерировать больше токенов, у нее нет.
И вот отсюда выпадает половина того, что мы эмпирически знаем про промпты:
Почему работает «давай подумаем по шагам»? Вы буквально разрешаете модели потратить на задачу в 20 раз больше вычислений. Она пишет промежуточные шаги, и каждый шаг — это еще один полный проход по сети, еще одна порция матричных умножений.
Почему «ответь одним словом» ломает сложные задачи? Вы своими руками урезали вычислительный бюджет до одного forward pass. Модель обязана выдать ответ немедленно, без черновика. Человек в такой ситуации тоже отвечает хуже, но у человека хотя бы есть внутренний монолог. У модели внутренний монолог — это и есть выходные токены, другого у нее нет.

Почему reasoning-модели такие дорогие? Никакой новой архитектуры там нет. Есть модель, которую натренировали писать очень длинный черновик перед ответом.
Почему модели иногда помогают бессмысленные символы? Есть работы, показывающие, что в некоторых задачах модель улучшает результат, даже если заставить ее генерировать бессодержательные мусорные токены перед ответом. Просто дополнительные проходы по сети, в которых что-то полезное успевает досчитаться. Чистая вычислительная пауза.
Почему модель не может передумать? Токен, который уже сгенерирован, попадает обратно в контекст и становится фактом. Стереть его нельзя. Если модель ляпнула неудачное начало — она дальше вынуждена его обосновывать, потому что каждый следующий токен предсказывается с учетом предыдущих.
Словарь как археология
Еще одно неочевидное следствие. Словарь токенизатора — это слепок текстов, на которых его обучали. И его можно читать как археологический слой.
Случай первый — глитч-токены. В начале 2023 исследователи нашли в словаре GPT-2/GPT-3 странные строки вроде SolidGoldMagikarp и petertodd. Когда модель просили их произнести, она вела себя неадекватно: уходила в оскорбления или отказывалась отвечать.
Механика оказалась поучительной. Токенизатор обучали на одном корпусе, где эти строки были частотными — это никнеймы с сабреддита, где люди годами по очереди писали числа по порядку. А саму модель обучали на другом, почищенном корпусе, где этих строк практически не было. Токен в словаре есть, а его эмбеддинг так и остался болтаться примерно там, где его инициализировали случайными числами при старте обучения. Модель встречает его и оказывается в области пространства, где она никогда не была. Предсказать поведение невозможно.
Случай второй. В 2024, когда вышел GPT-4o с новым словарем на 200 000 токенов, исследователи из Принстона вытащили сотню самых длинных китайских токенов. Больше 90 из них оказались фразами со спам-сайтов. Только три из ста были словами, которые нормальный человек употребляет в обычной жизни.
То есть корпус для обучения токенизатора почистили для английского и не почистили для китайского. И теперь в словаре навсегда зафиксирована реклама с помоек китайского интернета.
Мораль простая: токенизатор — это отдельный инструмент со своей биографией. И эта биография может расходиться с биографией модели.
Мелкие странности, которые теперь объясняются сами
Дальше просто список того, что перестает быть загадкой, если помнить про токены.
«Токенов в секунду» — нечестная метрика. Любимая цифра всех бенчмарков и презентаций. Проблема в том, что токены у разных моделей разного размера. 50 токенов в секунду на модели с богатым словарем — это заметно больше текста, чем 50 токенов в секунду на модели со словарем в 32k, особенно на русском. Сравнивать скорость честно можно только в символах или в словах, но так почему-то никто не делает.
Пробел приклеен слева. В большинстве байтовых токенизаторов пробел — не отдельный элемент, он приклеивается к следующему слову. привет и _привет — разные токены с разными эмбеддингами. Отсюда классический баг: промпт, заканчивающийся пробелом, дает заметно худший результат, потому что модель ждала токен с ведущим пробелом, а вы этот пробел уже потратили.
Арифметика. Если 1234 режется на 123 + 4, а 5678 — на 567 + 8, то модель складывает не числа, а пары непрозрачных идентификаторов. Разрядов она не видит. Поэтому арифметика работает на маленьких числах, которые попали в словарь целиком, и разваливается на больших. Именно поэтому в новых моделях числа принудительно режут на группы по 1–3 цифры, чтобы разряды хотя бы выравнивались между слагаемыми.
Та самая strawberry. Слово приходит модели как два-три токена, и внутри токена букв нет — есть номер строки в таблице. Спрашивать модель о буквах внутри слова — это примерно как спрашивать вас о значении отдельных битов в JPEG, который вы разглядываете. Информация формально присутствует, но не на том уровне абстракции, на котором вы работаете.
Рифмы, анаграммы, шифр Цезаря. Все то же самое. Модель не слышит фонем и не видит букв, поэтому рифмует по статистике («эти слова часто стоят рядом в конце строк»), а не по звучанию. Отсюда странные проколы в стихах, где все хорошо, а рифмы нет.
Код. Токенизаторы, обученные с большой долей кода, специально держат в словаре токены на 4, 8, 12 пробелов подряд. Те, что обучены без этого, тратят по токену на каждый пробел — и файл на Python внезапно обходится вдвое дороже, чем на JavaScript.
Почему это до сих пор не починили
Логичный вопрос: возьмите морфологический анализатор и режьте по-человечески. Пробовали, конечно. Проблемы вытекают следующие:
- морфологический разбор языкозависим, а модель должна съедать вообще все, включая код, JSON и разметку;
- он неоднозначен («стекло» — существительное или глагол?), а токенизация обязана быть детерминированной и обратимой;
- он проигрывает по сжатию: последовательности становятся длиннее, обучение и инференс — дороже.
Есть более радикальный путь — выкинуть токенизатор совсем и кормить модель байтами. Все проблемы выше исчезают разом: нет швов, нет глитч-токенов, нет налога на язык, модель видит буквы. Взамен приходит длина последовательности, выросшая в разы, и соответствующий счет за вычисления. Пока что в проде побеждает компромисс.