Токенизатор и стоимость инференса: как срезать 77% расходов

Токенизатор и стоимость инференса: как срезать 77% расходов

Команда Dharma-AI взяла работающий production-сервис на Qwen3-4B, не тронула веса, не меняла датасет и не переписывала рецепт обучения. Они поменяли только токенизатор. Стоимость инференса упала на 77%, ответ модели сократился с 350 токенов до 101, а качество выросло: bench score с 0.703 до 0.810.

Это редкий случай, когда узкое место нашлось не в модели и не в железе, а в слое, который почти никто не трогает. Токенизатор наследуют от базовой модели как константу, и на этом разговор обычно заканчивается. Для доменной модели это может оказаться самым крупным рычагом экономии, причём рычагом, который доступен до первого шага обучения.

Что такое специализация словаря

Специализация словаря (vocabulary adaptation) это перестройка токенизатора под один язык, домен или формат ответа при сохранении базовой модели. Вместо универсального словаря на 151 669 записей модель получает компактный набор, где частые в домене последовательности склеены в один токен, а неиспользуемые записи удалены.

Речь не о том, чтобы «улучшить» модель. Речь о том, чтобы она перестала платить за текст, который никогда не пишет.

Две статьи расходов, которые вы не видите

В сервисе Dharma-AI грейдер проверяет школьные сочинения на португальском. На вход приходят текст сочинения, задание и опорные материалы, на выходе JSON с оценкой и комментарием по каждой компетенции. Модель уже была маленькой и уже была дообучена под задачу. Токенизатор при этом остался родной, от Qwen3-4B, собранный под десятки языков, код, юридические тексты и китайский.

Отсюда два вида потерь одновременно.

Первая, фрагментация. Токенизатор дробит на куски именно то, что домен повторяет чаще всего. В грейдере каждый ответ начинается с одного и того же JSON-конверта, тех же названий компетенций, тех же формулировок оценки. В договорах это шаблонные пункты, в коде идентификаторы. Каждая такая последовательность стоит несколько токенов там, где хватило бы одного, и модель платит за это на каждом запросе. Не один раз при разработке, а постоянно.

Вторая, избыточность. Модель, которая оценивает школьные сочинения на португальском, почти никогда не будет писать по-китайски. Но на каждом сгенерированном токене она считает вероятность для всех 151 669 записей словаря, включая те, что в этой задаче не встречаются вообще.

Почему чтение дешевле генерации

Логичная реакция: считать токены и сокращать их количество. Проблема в том, что количество токенов не равно счёту.

Инференс состоит из двух фаз с разной физикой. В prefill модель прогоняет весь входной контекст одним проходом по сети: каждое прочитанное из памяти GPU значение весов переиспользуется для всех входных токенов, ограничение это вычисления. В decode модель выдаёт ответ по одному токену, и каждый токен требует полного прохода. Вес переиспользуется всего несколько раз, GPU большую часть времени ждёт данные из памяти, и ограничением становится пропускная способность памяти.

Эффект сильнее всего там, где такие модели и живут: небольшие модели при умеренных батчах. Батчинг размазывает чтение весов по нескольким запросам и смягчает проблему, но не убирает её. К этому добавляется KV-кеш, память внимания по уже обработанному контексту, которая растёт вместе с длиной диалога и читается на каждом шаге.

В замере Dharma-AI сгенерированный токен обошёлся в 7.4 раза дороже прочитанного. Соотношение зависит от железа, батча, длины контекста и ещё десятка факторов, и может быть больше. Направление при этом устойчивое: писать дороже, чем читать.

Аналогия из статьи простая и точная. Логистическая компания берёт деньги за коробку, но за исходящие коробки в семь раз больше, чем за входящие. Сокращать общее число коробок полезно. Сокращать исходящие полезнее в семь раз. Метод, который сжимает вход и выход одинаково, теряет большую часть возможной экономии.

Третья статья: сам словарь

Есть и третья составляющая счёта, о которой вспоминают реже. Последний слой модели, LM head, превращает внутреннее состояние в оценку для каждого возможного токена. Для этого он держит по строке параметров на каждую запись словаря. У модели размера Qwen3-4B эта матрица занимает почти 10% всех параметров. Большая модель такую долю не замечает, маленькая замечает хорошо.

И платит она не один раз. Входной слой ищет только строку текущего токена, а LM head читает и умножает всю матрицу на каждом сгенерированном токене. Поскольку decode уже упирается в память, каждая лишняя строка это дополнительный трафик на каждом шаге. Запись, которую домен никогда не использует, стоит ровно столько же, сколько самая частая.

Отсюда контринтуитивный вывод: можно добавить токенов, снизить их количество в ответе и при этом увеличить счёт. VocabX эту составляющую в критерий отбора не включает, но размер словаря контролирует двумя шагами: субсловное слияние заменяет базовый словарь на меньший доменный, а макро-токены входят в фиксированный бюджет. У грейдера выходной слой сократился примерно со 152 000 строк до 65 000.

Почему готовые методы не подошли

Идея адаптации словаря не новая. В литературе есть fast vocabulary transfer, multi-word tokenization и расширение токенизатора на месте от Liquid AI (in-place BPE), которое продолжает обучение BPE с оригинального словаря. Все три уменьшают фрагментацию, и на практике выяснились две проблемы.

Первая: они выбирают токены по частоте, а не по стоимости. Выражение, которое часто встречается во входе, для них ценнее выражения, которое реже встречается в ответе, даже если второе дороже в обслуживании. Для сервиса, где ответ весит в 7.4 раза больше входа, такая логика ошибочна по построению.

Вторая: они работают на одном уровне. Либо меняют то, как слова режутся на куски, либо склеивают целые фразы в один токен. Ни один из трёх не делает и то и другое по одному критерию. А главные потери грейдера сидели именно в длинных повторяющихся отрезках вроде JSON-конверта, до которых субсловный метод добирается только последовательными слияниями, часть за частью.

Как устроен VocabX

Три изменения по сравнению с референсными методами.

Первое: другой вопрос. Вместо «сколько токенов сэкономит кандидат» спрашивают «сколько денег и времени он сэкономит». Каждый устранённый токен умножается на цену поля, в котором он находится: системный промпт, сообщение пользователя или ответ модели. В измеренном случае токен, сэкономленный в ответе, стоит в 7.4 раза дороже сэкономленного во входе, и словарь строится исходя из этого.

Второе: два уровня одновременно. Если продолжать логистику, коробки можно сократить, аккуратнее упаковывая каждый предмет, а можно заметить, что один и тот же набор из двенадцати предметов уезжает каждую неделю, и отправлять набор одной коробкой. VocabX делает и первое, и второе. На субсловном уровне перестраивает разбиение доменных слов. На многословном ищет повторяющиеся целые отрезки, от коротких выражений до полных строк и JSON-конвертов, и делает из каждого один токен, который авторы называют макро-токеном. Оба уровня отбираются по одному критерию стоимости, у каждого свой бюджет.

Искать отрезки помогают пять майнеров, автоматических поисков повторов по корпусу. Каждый специализирован на своём типе повторения: длинные слова, целые строки, выражения, законченные предложения и структуры с пунктуацией вроде JSON и тегов. В среднем по восьми корпусам последний дал больше всех, а на коде он один принёс 17.5 процентных пункта. Без всех пяти сразу сокращение токенов на грейдере упало бы на 55.6 пункта, то есть основной вклад даёт именно комбинация.

Проблема, которую видно только на выборке

Третья сложность неочевидна. Чтобы выбирать токены по стоимости, нужно понимать, сколько кандидат сэкономит после того, как уже выбраны предыдущие. Точный расчёт требует пересчитывать весь корпус после каждого выбора, десятки тысяч раз. На масштабе это делают на выборке документов, и тогда кандидат, которого в выборке нет, получает нулевую выгоду и выбывает.

Масштаб потерь виден по числам. Выражение, которое встречается 37 раз на 50 000 документов, попадёт в выборку из 400 документов примерно в 26% случаев. В корпусе договоров в выборку из 400 документов попадает лишь 16.6% кандидатов, остальные не оцениваются вообще.

Решение в том, чтобы разделить выгоду на две части. Первая, сколько кандидат экономит по всему корпусу, считается точно и без выборки. Вторая, какая доля этой экономии ещё не покрыта уже выбранными токенами, и только её нужно оценивать. Для кандидатов, которых выборка не видит, берут статистическую оценку по поведению видимых, а не ноль. Редкий в выборке, но частый в корпусе кандидат возвращается в игру.

Одно это изменение при той же выборке и том же бюджете подняло сокращение токенов на корпусе договоров с 44.7% до 52.8%.

Что показал замер

Чтобы измерить эффект по-честному, обучили семь версий грейдера. Одна базовая модель, одни данные, один рецепт, одно зерно случайности. Различие только в токенизаторе. Все версии прогнали одни и те же 500 запросов на одной машине с RTX 5090 и сервером vLLM, стоимость считали по измеренному времени работы.

Результат: средний ответ сократился с 350 токенов до 101. Стоимость упала на 77.0%. Качество, измеренное bench score, который объединяет точность выставления оценки и точность отсева посторонних или нарушающих правила сочинений, выросло с 0.703 до 0.810.

Провал, который оказался важнее успеха

К этим цифрам пришли через ошибку, и она стоит отдельного разбора. Первая версия словаря склеивала оценку по компетенции вместе с окружающим JSON в один макро-токен. Получалось компактнее всего. Качество просело: bench score 0.689, ниже production-версии с её 0.703.

Причина понятна задним числом. Когда оценка спрятана внутрь макро-токена, каждая комбинация оценки и структуры становится отдельным вариантом одного и того же токена, и модель выбирает и то и другое одним шагом. Решение, ради которого сервис существует, оказывается склеенным с оформлением.

Отдельный токен для оценки стоил на два токена больше на ответ, поднял bench score до 0.810 и в замере оказался даже дешевле: 77.0% против 73.3% у склеенного варианта. Урок шире грейдера: сжатие имеет цену, когда сжатый отрезок несёт само решение, которое модель принимает. Цель не в самом дешёвом словаре, а в самом дешёвом из тех, что сохраняют значимые токены.

Проверка на пятнадцати комбинациях

Отдельный вопрос: не является ли выигрыш особенностью одного португальского сервиса. VocabX сравнили с тремя референсными методами на пяти публичных доменах (договоры, медицинские статьи, научные статьи, новости, код на Python) и трёх семействах моделей: Qwen3, Llama-3.1 и Gemma-3. У всех методов одни и те же документы для поиска и тестирования, один и тот же потолок размера словаря.

VocabX сжал больше сильнейшего референса во всех пятнадцати комбинациях, с разрывом от 1.2 до 9.3 пункта. По оценённой стоимости инференса урезанная версия тоже лидировала везде, со средними преимуществами 10.2 пункта на Qwen3, 7.5 на Llama-3.1 и 13.6 на Gemma-3. На договорах она дошла до 62.6% сокращения токенов при 113 426 записях словаря, тогда как лучшая конфигурация in-place BPE даёт 61.8% при 207 803 записях.

Разница в размере словаря здесь не менее важна, чем разница в токенах. Меньше строк в LM head означает меньше трафика на каждом шаге генерации, а именно трафик и определяет скорость decode.

Где этот рычаг не сработает

Стоит обозначить границы, иначе метод выглядит волшебством.

Если ваша модель обслуживает открытый домен, где пользователи пишут на десяти языках и просят всё подряд, специализация словаря принесёт мало. Экономить не на чем: разнообразие текста и есть нагрузка.

Если у вас огромная модель с большими батчами, decode перестаёт упираться в память так сильно, и доля выигрыша падает. Те же 7.4 раза относятся к конкретной конфигурации: RTX 5090, умеренный батч, ответ в 350 токенов при длинном входе.

Если задача держится на свободном формате ответа, макро-токены для структур дадут меньше. Основной выигрыш приходит от повторяющихся оболочек: JSON, шаблонных фраз, названий полей, а их в свободном тексте нет.

И последнее: словарь нельзя менять без переобучения. Токенизатор задаёт входной слой и LM head, так что адаптация словаря это всегда шаг перед обучением или повод переобучить модель. Ноль-шот замена здесь не работает, и именно поэтому метод стоит воспринимать как часть подготовки к дообучению, а не как оптимизацию на лету.

Часто задаваемые вопросы

Правда ли, что качество выросло от сжатия словаря?

Да, в этом замере bench score поднялся с 0.703 до 0.810 при сокращении стоимости на 77%. Причина не в магии сжатия: более короткий ответ и меньше вариативности в оформлении снижают вероятность ошибок в JSON-структуре. Плюс отдельный токен для оценки оказался точнее склеенного.

Можно ли применить это к модели, которая уже работает в продакшене?

Формально да, но потребуется переобучение: словарь определяет входной слой и выходную матрицу. Практический путь выглядит так: собрать логи реальных запросов и ответов, построить доменный словарь по их стоимости, дообучить модель и сравнить на одинаковом наборе запросов, как это сделали авторы с семью версиями.

Насколько это применимо к русскоязычным сервисам?

Аргумент про фрагментацию для русского даже сильнее, чем для португальского. Универсальные токенизаторы тратят на кириллицу больше токенов, чем на латиницу: одно слово нередко распадается на два-три куска. Доменный словарь под конкретную задачу, например под разбор договоров или поддержку, снимает эту надбавку.

Итог

Специализация обычно начинается с данных и весов. История VocabX показывает, что она может начаться на слой раньше, в словаре, и что этот слой десятилетиями оставался без внимания именно потому, что считается частью базовой модели.

Если у вас есть узкий домен, повторяющийся формат ответа и маленькая модель в продакшене, токенизатор и стоимость инференса связаны напрямую. Посчитайте, сколько токенов уходит на оболочку ответа, сколько строк в вашем LM head и какая доля словаря вообще не встречается в ваших логах. Эти три числа дадут грубую оценку, стоит ли перестраивать словарь. Если оболочка занимает больше половины ответа, начинать почти наверняка есть с чего.

← Все записи