Токены, контекстное окно и память диалога
Фрагмент лекции: «Все что нужно знать про ИИ айтишнику», 38:04 — отсюда взято содержание этой главы.
В предыдущей главе мы дошли до того, что языковая модель предсказывает следующий элемент последовательности. Слово «элемент» пора уточнить: модель работает не с буквами и не со словами, а с токенами. Это не педантизм — из устройства токена напрямую следуют три вещи, с которыми вы столкнётесь в первый же рабочий день: почему текст на русском стоит дороже английского при том же смысле, почему у моделей есть жёсткий лимит на длину запроса и почему длинный диалог с чат-ботом дорожает быстрее, чем кажется.
Последний пункт — главный в этой главе, и он же самый неочевидный. У модели нет памяти диалога. Совсем. То, что выглядит как разговор, на самом деле — серия независимых запросов, в каждом из которых вся предыдущая переписка отправляется заново.
Сразу про слово. Тему «сколько стоят токены и как на этом не разориться» удобно называть экономикой токенов. Термин «токеномика» лучше не использовать: он занят блокчейн-индустрией и означает совсем другое — модель выпуска и обращения криптовалютных токенов.
Токен: не буква и не слово
Модель видит на входе последовательность целых чисел — номеров записей в фиксированном словаре. Программа, которая превращает текст в эти номера и обратно, называется токенизатором. Она детерминирована: один и тот же текст всегда даёт одну и ту же последовательность номеров.
Ключевой вопрос — из чего состоит словарь. Не из букв: словарь из букв дал бы слишком длинные последовательности, и модели пришлось бы тратить ресурсы на восстановление слов из символов. Не из слов: словарь всех словоформ русского языка огромен, и любое незнакомое слово, опечатка или имя собственное оказались бы непредставимы.
Компромисс — подслова, куски слов. Доминирующий способ их подобрать называется BPE, byte-pair encoding: алгоритм сжатия, перенесённый в обработку языка в работе Neural Machine Translation of Rare Words with Subword Units (Sennrich et al., 2015). Идея умещается в четыре строки:
1. Начать со словаря из всех возможных байтов — 256 записей.
2. Пройти по обучающему корпусу и найти самую частую пару соседних токенов.
3. Слить эту пару в один новый токен и добавить его в словарь.
4. Повторять, пока словарь не дорастёт до заданного размера — обычно 50–200 тысяч записей.
Второе распространённое семейство — SentencePiece (Kudo & Richardson, 2018), который работает прямо с сырой строкой, не требуя предварительного разбиения по пробелам, и поэтому одинаково применим к языкам без пробельного разделения слов.
Отсюда — главный вывод про токены, который стоит запомнить вместо любых чисел:
Границы токенов определяются частотностью в обучающем корпусе, а не смыслом и не грамматикой.
Частое английское слово попадёт в словарь целиком и станет одним токеном. Редкое слово, фамилия, идентификатор в коде, длинное число — развалятся на несколько кусков. Число 1234567 вполне может разбиться как 123|456|7, и это одна из причин, почему языковые модели плохо считают в столбик: они не видят разрядов, они видят три произвольных обрывка.
Почему один и тот же смысл стоит по-разному
Корпуса, на которых обучают токенизаторы массовых моделей, перекошены в сторону английского. Следствие механическое: для английского алгоритм успел выучить много длинных слияний, для кириллицы — заметно меньше. Добавьте к этому кодировку: в UTF-8 латинская буква занимает один байт, кириллическая — два. Если подходящего слияния в словаре не нашлось, слово распадается вплоть до отдельных байтов, и счёт токенов растёт.
Практический итог: один и тот же по смыслу текст на русском обычно стоит больше токенов, чем на английском. Это не наценка за язык и не политика провайдера, а прямое следствие статистики обучающего корпуса токенизатора. Тот же эффект бьёт дважды: вы платите больше за вход и быстрее упираетесь в контекстное окно.
А вот чего делать не надо — запоминать коэффициент. В лекции звучит оценка «два-три токена на каждую букву»; это неверно, токенов почти всегда меньше, чем символов, а не больше. Реальное отношение «символов на токен» зависит от языка, от конкретного текста и от конкретного токенизатора, и меняется от поколения к поколению моделей. Правильная привычка — не помнить число, а измерить его.
Чем мерить:
- platform.openai.com/tokenizer — вставляете текст, видите разбиение и счёт.
- Tiktokenizer — то же самое, но с выбором из большего числа токенизаторов.
- Библиотека
tiktoken— если нужно считать программно; код ниже. huggingface/tokenizers— универсальная библиотека для BPE, WordPiece и Unigram, если работаете с открытыми моделями.
И ещё одна мысль, которую важно не смешивать с предыдущей. Разное число токенов — это про представление текста. Но есть и вторая, содержательная потеря: перевод между языками не взаимно-однозначен. Машинный перевод существует и работает, однако он не сохраняет смысл целиком: идиомы, коннотации, локальные отсылки и культурный контекст переводятся приблизительно или не переводятся вовсе. Фраза, у которой весь смысл держится на региональном меме, после перевода станет грамматически корректной и эмоционально пустой. К стоимости токенов это отношения не имеет, но объясняет, почему «просто переведите промпт на английский, там дешевле» — совет с оговорками: дешевле по токенам, но не бесплатно по качеству, если в тексте есть культурный слой.
У каждого провайдера свой токенизатор
Токенизатор — часть модели, а не общий стандарт. Один и тот же абзац у разных провайдеров и даже у разных поколений моделей одного провайдера даст разное число токенов.
Из этого следует неприятное для сравнения цен: цифра «цена за миллион токенов» сама по себе несравнима между провайдерами. Миллион токенов у одного и миллион у другого — это разный объём вашего текста. Корректное сравнение делается иначе: берёте свои реальные тексты, считаете токены токенизатором каждой модели, умножаете на её цену и сравниваете полученную стоимость одной и той же задачи.
Практические ориентиры:
- Для моделей OpenAI подсчёт локальный и точный —
tiktoken. - У других провайдеров, включая Anthropic, есть отдельный эндпоинт подсчёта токенов; он даёт точный ответ для конкретной модели, не тратя вывод. Использовать
tiktokenдля не-OpenAI модели нельзя: он систематически промахивается. - Любой ответ API возвращает фактические счётчики использования. Это единственный источник правды о том, за что вы заплатили.
Как из этих счётчиков собирается реальный месячный счёт и чем его сбивают — в статье «ИИ в продакшене: латентность, кэширование, роутинг моделей, стоимость».
Из чего на самом деле собирается ваш запрос
Вы пишете модели одно короткое сообщение. В модель уходит намного больше. Ваш текст — это промпт; но перед отправкой он склеивается с несколькими другими частями.
инструкции провайдера или ваши"] TOOLS["Описания инструментов
и схема ответа"] HIST["История диалога:
все прошлые ходы целиком"] USER["Текущее сообщение"] META["Служебная разметка:
роли, разделители, спецтокены"] end SYS --> TOK TOOLS --> TOK HIST --> TOK USER --> TOK META --> TOK TOK["Токенизация:
текст превращается в номера из словаря"] --> WIN{"Влезает
в контекстное окно?"} WIN -->|"нет"| ERR["Ошибка запроса
или обрезка истории"] WIN -->|"да"| GPU["Прямой проход на GPU"] GPU --> OUT["Выходные токены:
генерируются по одному
и стоят дороже входных"]
Системный промпт — это надстройка, которую добавляет владелец сервиса. Он задаёт роль, правила поведения, запреты, формат ответа, иногда описание доступных инструментов. У публичных ассистентов он объёмный: утёкшие системные промпты популярных продуктов насчитывают десятки страниц текста. Публичный сборник таких утечек — jujumilk3/leaked-system-prompts; относиться к нему нужно с оговоркой, что подлинность утечек никем не гарантирована и проверить её со стороны невозможно.
Два факта про системный промпт, которые важны практически.
Первый: он тарифицируется. Входные токены запроса включают системный промпт, описания инструментов и всю служебную разметку — платит за них тот, кто делает запрос. Это не предмет догадок: провайдеры возвращают в ответе API счётчики использования, где видно фактическое число входных и выходных токенов. Если вы работаете через чужой сервис, вы не видите текста системного промпта, но его объём всё равно заложен в вашу цену.
Второй: он имеет приоритет над вашим. Модели специально обучают ставить системные инструкции выше пользовательских, поэтому запрет из системного промпта вы своим сообщением обычно не перебьёте. «Обычно» — важное слово: это статистическое предпочтение, а не механизм безопасности, и на этом стоит целая дисциплина атак и защит, разобранная в статье «Безопасность и инъекции». А то, как системный промпт меняет саму подачу ответов, — тема главы «Провайдеры, фрейминг и цензура».
Контекстное окно: жёсткий лимит
Контекстное окно — максимальное число токенов, которое модель может обработать за один запрос. В него входит всё сразу: системный промпт, описания инструментов, вся история диалога, приложенные документы и генерируемый ответ.
Три свойства, которые надо усвоить.
Окно измеряется в токенах, а не в символах, словах или мегабайтах. Значит, реальный объём вашего текста, влезающий в окно, зависит от языка: русский документ займёт больше окна, чем его английский перевод.
Вывод тоже занимает место. Если запрос съел почти всё окно, места на ответ не останется, и вы получите либо ошибку, либо обрезанный посередине текст.
Заявленное окно не равно эффективному. Модель, формально принимающая огромный контекст, не использует всю его длину одинаково хорошо. Классический результат — Lost in the Middle (Liu et al., 2023): точность извлечения факта из длинного контекста максимальна, когда факт лежит в начале или в конце, и заметно проседает, когда он в середине. Кривая U-образная. Практический вывод: важное — в начало и в конец, «наполнитель» — в середину, и «свалю всё в окно на всякий случай» не работает.
На момент лекции, май 2026 года, типичные окна массовых моделей лежали в диапазоне от 128 тысяч токенов до миллиона. Конкретные цифры устаревают за месяцы, механизм — нет.
Что происходит при переполнении:
Важно, кто именно это делает. Ошибку возвращает API. А вот обрезку, сжатие и скользящее окно выполняет клиент — интерфейс чата или ваш код. Модель ничего не выкидывает: она либо получает запрос целиком, либо не получает его вовсе.
Главный факт: у модели нет памяти диалога
Теперь соберём всё вместе, потому что это самое неочевидное место темы.
Модель — чистая функция от входа. Ей подали последовательность токенов, она вернула распределение вероятностей следующего токена. Между запросами внутри модели ничего не сохраняется: веса не меняются, никакого «состояния разговора» не существует. Разговор — иллюзия, которую создаёт клиент, каждый раз пересылая всю переписку заново.
клиент хранит их у себя U->>C: ход 3 — «а теперь то же самое на английском» C->>A: системный промпт + описания инструментов +
ход 1 целиком + ход 2 целиком + новое сообщение A->>M: всё это одним массивом токенов M-->>A: ответ по одному токену за проход A-->>C: текст ответа + счётчики входных и выходных токенов C-->>U: показывает только новый ответ Note over M: после ответа модель
не помнит ничего —
на ходу 4 всё поедет заново
Из этого следуют два наблюдения, которые обычно объясняют магией.
«Он забыл, о чём я его просил в начале». Модель ничего не забывала — просто начало диалога больше не влезло в окно, и клиент его обрезал. Это не свойство характера, а арифметика: старые сообщения физически выпали из запроса.
«Чем дольше общаюсь, тем дороже отвечает». Каждый следующий ход тащит с собой всю предыдущую переписку, поэтому число входных токенов на запрос растёт линейно, а суммарный счёт — квадратично. Считаем ниже.
Оговорка, чтобы не создать обратного заблуждения: некоторые продукты действительно умеют «помнить» вас между сессиями — сохранённые заметки о пользователе, поиск по прошлым диалогам. Но это внешняя надстройка над моделью: она достаёт нужный кусок и вставляет его текстом во вход, за который вы платите. Как устроен такой поиск по внешним данным — в главе «RAG, MCP и агенты». Сама модель по-прежнему не помнит ничего.
Считаем: диалог из N ходов
Сначала измерим токены и прикинем цену одного запроса.
# pip install tiktoken
import tiktoken
# Токенизатор конкретной модели. У каждого провайдера он свой,
# поэтому для не-OpenAI моделей нужно спрашивать счёт у их API.
enc = tiktoken.get_encoding("o200k_base")
def count(text: str) -> int:
"""Число токенов в тексте для выбранного токенизатора."""
return len(enc.encode(text))
def report(label: str, text: str) -> None:
n = count(text)
# Отношение «символов на токен» — величина измеряемая, а не запоминаемая:
# она зависит от языка, от самого текста и от версии токенизатора.
print(f"{label}: {len(text):3d} символов -> {n:3d} токенов, "
f"{len(text) / n:.2f} символа на токен")
report("EN", "Unbelievable performance improvements across the board.")
report("RU", "Невероятные улучшения производительности по всем направлениям.")
# Полезно посмотреть глазами, как именно рвётся слово:
for tid in enc.encode("контекстное окно"):
print(tid, repr(enc.decode([tid])))
def cost_usd(input_tokens: int, output_tokens: int,
price_in: float, price_out: float) -> float:
"""Цена одного запроса. Цены задаются за миллион токенов;
выход у всех провайдеров дороже входа в несколько раз."""
return input_tokens / 1e6 * price_in + output_tokens / 1e6 * price_out
Запустите это на своих текстах: смысл упражнения не в конкретных числах, а в привычке проверять их для той модели, которой вы реально пользуетесь.
Теперь главное — рост стоимости диалога.
def dialog_input_tokens(system: int, per_turn: int, turns: int) -> list[int]:
"""Сколько входных токенов оплачивается на каждом ходу диалога.
system — системный промпт и описания инструментов, константа на всех ходах;
per_turn — средний прирост истории за ход: ваше сообщение плюс ответ модели;
turns — число ходов.
Ключевая строка — history += per_turn: история не заменяется,
а накапливается, потому что модель не помнит предыдущих ходов.
"""
history = 0
per_request: list[int] = []
for _ in range(turns):
history += per_turn
per_request.append(system + history)
return per_request
def total_input_tokens(system: int, per_turn: int, turns: int) -> int:
"""Суммарные оплаченные входные токены за весь диалог.
Закрытая форма: system * N + per_turn * N * (N + 1) / 2.
Первое слагаемое линейно по N, второе — квадратично, и оно доминирует.
"""
return system * turns + per_turn * turns * (turns + 1) // 2
SYSTEM, PER_TURN, PRICE_IN = 1200, 500, 3.0 # цена — за миллион входных токенов
for n in (5, 10, 20, 40):
last = dialog_input_tokens(SYSTEM, PER_TURN, n)[-1]
total = total_input_tokens(SYSTEM, PER_TURN, n)
print(f"{n:2d} ходов: последний запрос {last:6d} ток., "
f"всего оплачено {total:7d} ток., {total / 1e6 * PRICE_IN:.2f} USD")
Сложность: длина одного запроса растёт как O(N), суммарное число оплаченных входных токенов — как O(N^2). Память клиента на хранение истории — O(N). Числа для системного промпта в 1200 токенов и прироста 500 токенов за ход:
| Ходов | Токенов в последнем запросе | Всего оплачено входных | Цена при 3 USD за миллион |
|---|---|---|---|
| 5 | 3 700 | 13 500 | 0.04 USD |
| 10 | 6 200 | 39 500 | 0.12 USD |
| 20 | 11 200 | 129 000 | 0.39 USD |
| 40 | 21 200 | 458 000 | 1.37 USD |
Удвоение числа ходов даёт примерно четырёхкратный рост счёта. Выходные токены сюда не включены — они считаются отдельно и стоят дороже, так что реальная цифра ещё выше.
Один хорошо составленный запрос дешевле десяти уточняющих
Сравните последнюю строку таблицы с альтернативой: если ту же задачу решает один продуманный запрос на 2000 токенов, суммарный вход — 3200 токенов, то есть около 0.01 USD. Разница более чем стократная, и это ещё до учёта вашего времени.
Это и есть практический вывод лекции, и у него теперь есть арифметическое обоснование, а не только интуиция: сформулировать задачу целиком с первого раза дешевле, чем добираться до неё десятком уточнений. Как формулировать — глава «Промптинг: RCTF и STAR».
Честная оговорка, которой в лекции нет. Правило не абсолютно: когда задача плохо определена и вы сами не знаете, чего хотите, несколько коротких итераций дешевле, чем длинный промпт, написанный вслепую и не попавший в цель. Дорого другое — вести многоходовой диалог там, где требование было известно с самого начала, и каждый ход пересылать всю переписку заново.
Что с этим делают инженеры
Квадратичный рост — известная проблема с известным набором решений. Все они сводятся к тому, чтобы не пересылать одно и то же по полной цене:
- Кэширование префикса. Стабильное начало запроса — системный промпт, описания инструментов, неизменные документы — провайдер может закэшировать; чтение из кэша в разы дешевле обычного входа. Условие жёсткое: префикс должен совпадать побайтово, поэтому дата или случайный идентификатор внутри него молча обнуляют экономию.
- Суммаризация истории. Старые ходы заменяются коротким пересказом; диалог продолжается, объём входа перестаёт расти линейно, часть деталей теряется.
- Обрезка и скользящее окно. В запрос идут системный промпт плюс последние K ходов. Самое дешёвое и самое грубое решение.
- Вынос знаний наружу. Вместо того чтобы держать всё в истории, нужный фрагмент находится поиском и подставляется в запрос — это RAG.
Пересказывать эти механизмы здесь незачем, они разобраны подробно и с кодом: «Как устроена LLM с точки зрения инженера» — про токены, окно и стоимость запроса, «ИИ в продакшене» — про кэш, роутинг моделей и реальную экономику счёта.
Источники
- Sennrich et al., Neural Machine Translation of Rare Words with Subword Units — BPE в обработке языка
- Kudo & Richardson, SentencePiece — токенизация без предварительного разбиения по пробелам
- Liu et al., Lost in the Middle — деградация в середине длинного контекста
openai/tiktokenи интерактивный токенизатор — измерить самомуhuggingface/tokenizers— токенизаторы открытых моделей- jujumilk3/leaked-system-prompts — сборник утёкших системных промптов, подлинность не гарантирована
Мини-итог
- Токен — не буква и не слово, а кусок текста из словаря, набранного по частотам на обучающем корпусе; границы токенов определяются статистикой, а не смыслом.
- Русский текст при равном смысле обычно стоит больше токенов, чем английский, потому что корпуса токенизаторов перекошены в сторону английского, а кириллица в UTF-8 занимает вдвое больше байтов; конкретное отношение «символов на токен» надо измерять, а не запоминать.
- Токенизатор у каждого провайдера свой, поэтому сравнивать цены «за миллион токенов» напрямую некорректно — сравнивают стоимость одной и той же задачи на своих текстах.
- В модель уходит не только ваше сообщение: системный промпт, описания инструментов, вся история диалога и служебная разметка — всё это входные токены, и все они оплачиваются; фактические счётчики возвращаются в ответе API.
- Контекстное окно — жёсткий лимит на суммарное число токенов запроса вместе с ответом; при переполнении клиент обрезает или сжимает историю, а качество проседает в середине длинного контекста задолго до достижения лимита.
- У модели нет памяти диалога: она чистая функция от входа, и вся переписка пересылается заново на каждом ходу.
- Отсюда квадратичный рост: при примерно равном приросте за ход суммарные оплаченные входные токены за N ходов растут как
O(N^2)— удвоение числа ходов учетверяет счёт.
Что дальше
Мы разобрались, что именно уходит в модель и почему это стоит денег. Осталась вторая половина вопроса: почему обработка этого массива токенов требует видеокарты, а не обычного процессора, что такое инференс с точки зрения счёта и памяти, и откуда берутся требования к железу, о которые спотыкаются все, кто пытается запустить модель локально.