Токены и токенизаторы
Модели никогда не видят ни слов, ни букв. Они видят ID токенов — целые числа, индексирующие фиксированный словарь кусочков-подслов, построенный с помощью кодирования пар байтов (BPE) или близкого варианта. Токенизация — пограничный переход для всего: вашего промпта, ответа, окна контекста, счётчика токенов в секунду, счетов за API. Как только вы начинаете думать токенами, половина фольклора о поведении LLM рассыпается.
Как строится словарь
BPE стартует с сырых байтов и снова и снова сливает самую частую соседнюю пару, пока словарь не упрётся в бюджет. Результат: частые слова становятся одиночными токенами, редкие — 2–5 кусками, и нет ничего непредставимого — байтовый фолбэк покрывает любую строку, включая опечатки и эмодзи. Словари варьируются от ~30k записей (старые модели) до ~250k (современные мультиязычные):
| Токенизатор | Размер словаря | Примечание |
|---|---|---|
| GPT-2 | ~50k | Перекос в английский/веб |
| Llama 2 | ~32k | Корейский режется болезненно |
| Llama 3 | ~128k | Большой мультиязычный апгрейд |
| Qwen 2 / 2.5 / 3 | ~151k | Сильные CJK-слияния |
| Gemma | ~256k | Самый большой из распространённых словарей |
Токенизатор, обученный в основном на английском, агрессивно сливает английские фрагменты; корейские пары слогов получают куда меньше полезных слияний. Словарь определяет ваш «корейский налог» задолго до того, как дело дойдёт до того, насколько хороша сама модель в корейском.
Разбор на примере: английский
"Tokenization turns text into pieces."
→ ["Token", "ization", " turns", " text", " into", " pieces", "."]
→ 37 символов → 7 токенов ≈ 5.3 символа/токен
Обратите внимание на детали: частое слово "Token" — один кусок; суффикс получает собственный; пробел в начале принадлежит следующему токену (" turns", а не "turns"). Поэтому "hello", " hello" и "Hello" — разные токены: промпты, различающиеся пробелом или заглавной, токенизируются по-разному.
Разбор на примере: корейский
То же предложение, худшая экономика. Хангыль пишется слоговыми блоками, а корейские слияния редки в англо-тяжёлых словарях (разбиения ниже правдоподобны, не точны):
"안녕하세요, 만나서 반가워요." # 17 символов
сильный мультиязычный словарь (~150k записей):
["안녕", "하세요", ",", " 만나", "서", " 반", "가", "워", "요", "."]
→ ~10 токенов ≈ 1.7 символа/токен
слабый словарь (~32k записей, разбиение до джамо):
["안", "녕", "하", "세", "요", ",", " 만", "나", "서", ...]
→ 15+ токенов ≈ 1.1 символа/токен
Английский в среднем ~4 символа/токен. Корейский обычно попадает в 1–1.5. Тот же смысл — в 3–4 раза больше токенов. Поэтому:
- CJK-промпты пожирают окна контекста пугающе быстро — окно на 8k токенов вмещает примерно в 3–4 раза больше английского, чем корейского.
- Генерация «ощущается» медленнее на корейском при том же tok/s: больше токенов на предложение, меньше текста на токен.
- Корейский дороже на API с оплатой за токены, и цепочки рассуждений на корейском сжигают бюджет так же.
Почему модели проваливают подсчёт букв
Спросите «сколько r в strawberry» — и даже большие модели мажут. Причина: модель никогда не видит букв. "strawberry" приходит примерно как ["straw", "berry"] — два целых числа. Внутри целого числа нет счёта букв — как номер страницы не сообщает правописание страницы. Модель может воспроизвести зазубренные написания или рассуждать шаг за шагом, и оба пути ненадёжны. Если нужно сделать: заставьте модель сначала разобрать слово по одной букве в строке, а потом посчитайте строки.
Словарь поставляется вместе с моделью
- Файлы токенизатора (словарь, слияния) — часть релиза; GGUF встраивает их, репозитории safetensors несут их рядом с весами.
- Движки (llama.cpp, LM Studio, vLLM, ...) загружают правильный токенизатор автоматически. Вам никогда не нужно его выбирать.
- Количество токенов относительно модели: один и тот же текст может быть 1,000 токенов в словаре одного семейства и 1,150 в словаре другого. Никогда не хардкодьте токен-бюджеты между моделями.
- Качество словаря и качество модели — отдельные оси: модель со словарём 32k может справляться с корейским (она понимает свои токены); она просто тратит больше токенов на предложение.
Всё оценено в токенах
| Что вам важно | В чём измеряется |
|---|---|
| Окно контекста (4k → 128k+) | токены на входе + токены на выходе вместе |
| Скорость генерации | токенов в секунду |
| Память KV-кэша | растёт на каждый токен в контексте (KV-кэш) |
| Цены API | за миллион токенов |
| Лимиты длины вывода | максимальные выходные токены |
| Бюджеты рассуждения/thinking | токены, которые модель может потратить до ответа |
| Эмбеддинг документов для RAG | чанкуется и считается за миллионы токенов |
Специальные токены и чат-шаблон
Помимо кусочков текста, словарь резервирует управляющие ID: начало последовательности, конец последовательности и маркеры ролей. Когда ваш чат-UI отправляет сообщение, он отправляет не сырой текст — он заполняет чат-шаблон, который перемежает специальные токены вроде <|im_start|>user ... <|im_end|> вокруг ваших слов. Эта обёртка — причина, по которой счётчик токенов движка всегда чуть выше, чем текст вашего сообщения, и почему смешение шаблонов разных семейств путает модели.
Правила оценки: английский — символы ÷ 4. Корейский — символы ÷ 1.0–1.5, рассчитывайте на худшее. Симулятор использует приближение ~4 символа/токен и откалиброван на английском; для корейского мысленно завышайте.
Считаем токены сами
pip install tiktoken
python -c "import tiktoken as t; e = t.get_encoding('o200k_base'); print(len(e.encode('hello world')))"
- tiktoken (https://github.com/openai/tiktoken) покрывает кодировки семейства OpenAI; другие семейства отличаются, но для английского попадают примерно в ±15%.
- Страницы моделей на Hugging Face содержат просмотрщик токенизатора; песочницы токенизаторов позволяют вставить текст и посмотреть разбиение вживую.
- Локальные движки печатают счётчик токенов промпта в логах — сверьте один раз со своими реальными промптами, дальше оценивайте по пропорции.
Связанные разделы: внутри трансформера · префилл и декодирование · KV-кэш