ИИ для инженера: основы Токены, контекстное окно и память диалога
0%

Токены, контекстное окно и память диалога

Токены, контекстное окно и память диалога

Фрагмент лекции: «Все что нужно знать про ИИ айтишнику», 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 возвращает фактические счётчики использования. Это единственный источник правды о том, за что вы заплатили.

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

Из чего на самом деле собирается ваш запрос

Вы пишете модели одно короткое сообщение. В модель уходит намного больше. Ваш текст — это промпт; но перед отправкой он склеивается с несколькими другими частями.

Системный промпт — это надстройка, которую добавляет владелец сервиса. Он задаёт роль, правила поведения, запреты, формат ответа, иногда описание доступных инструментов. У публичных ассистентов он объёмный: утёкшие системные промпты популярных продуктов насчитывают десятки страниц текста. Публичный сборник таких утечек — jujumilk3/leaked-system-prompts; относиться к нему нужно с оговоркой, что подлинность утечек никем не гарантирована и проверить её со стороны невозможно.

Два факта про системный промпт, которые важны практически.

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

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

Контекстное окно: жёсткий лимит

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

Три свойства, которые надо усвоить.

Окно измеряется в токенах, а не в символах, словах или мегабайтах. Значит, реальный объём вашего текста, влезающий в окно, зависит от языка: русский документ займёт больше окна, чем его английский перевод.

Вывод тоже занимает место. Если запрос съел почти всё окно, места на ответ не останется, и вы получите либо ошибку, либо обрезанный посередине текст.

Заявленное окно не равно эффективному. Модель, формально принимающая огромный контекст, не использует всю его длину одинаково хорошо. Классический результат — Lost in the Middle (Liu et al., 2023): точность извлечения факта из длинного контекста максимальна, когда факт лежит в начале или в конце, и заметно проседает, когда он в середине. Кривая U-образная. Практический вывод: важное — в начало и в конец, «наполнитель» — в середину, и «свалю всё в окно на всякий случай» не работает.

На момент лекции, май 2026 года, типичные окна массовых моделей лежали в диапазоне от 128 тысяч токенов до миллиона. Конкретные цифры устаревают за месяцы, механизм — нет.

Что происходит при переполнении:

Важно, кто именно это делает. Ошибку возвращает API. А вот обрезку, сжатие и скользящее окно выполняет клиент — интерфейс чата или ваш код. Модель ничего не выкидывает: она либо получает запрос целиком, либо не получает его вовсе.

Главный факт: у модели нет памяти диалога

Теперь соберём всё вместе, потому что это самое неочевидное место темы.

Модель — чистая функция от входа. Ей подали последовательность токенов, она вернула распределение вероятностей следующего токена. Между запросами внутри модели ничего не сохраняется: веса не меняются, никакого «состояния разговора» не существует. Разговор — иллюзия, которую создаёт клиент, каждый раз пересылая всю переписку заново.

Из этого следуют два наблюдения, которые обычно объясняют магией.

«Он забыл, о чём я его просил в начале». Модель ничего не забывала — просто начало диалога больше не влезло в окно, и клиент его обрезал. Это не свойство характера, а арифметика: старые сообщения физически выпали из запроса.

«Чем дольше общаюсь, тем дороже отвечает». Каждый следующий ход тащит с собой всю предыдущую переписку, поэтому число входных токенов на запрос растёт линейно, а суммарный счёт — квадратично. Считаем ниже.

Оговорка, чтобы не создать обратного заблуждения: некоторые продукты действительно умеют «помнить» вас между сессиями — сохранённые заметки о пользователе, поиск по прошлым диалогам. Но это внешняя надстройка над моделью: она достаёт нужный кусок и вставляет его текстом во вход, за который вы платите. Как устроен такой поиск по внешним данным — в главе «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 с точки зрения инженера» — про токены, окно и стоимость запроса, «ИИ в продакшене» — про кэш, роутинг моделей и реальную экономику счёта.

Источники

Мини-итог

  • Токен — не буква и не слово, а кусок текста из словаря, набранного по частотам на обучающем корпусе; границы токенов определяются статистикой, а не смыслом.
  • Русский текст при равном смысле обычно стоит больше токенов, чем английский, потому что корпуса токенизаторов перекошены в сторону английского, а кириллица в UTF-8 занимает вдвое больше байтов; конкретное отношение «символов на токен» надо измерять, а не запоминать.
  • Токенизатор у каждого провайдера свой, поэтому сравнивать цены «за миллион токенов» напрямую некорректно — сравнивают стоимость одной и той же задачи на своих текстах.
  • В модель уходит не только ваше сообщение: системный промпт, описания инструментов, вся история диалога и служебная разметка — всё это входные токены, и все они оплачиваются; фактические счётчики возвращаются в ответе API.
  • Контекстное окно — жёсткий лимит на суммарное число токенов запроса вместе с ответом; при переполнении клиент обрезает или сжимает историю, а качество проседает в середине длинного контекста задолго до достижения лимита.
  • У модели нет памяти диалога: она чистая функция от входа, и вся переписка пересылается заново на каждом ходу.
  • Отсюда квадратичный рост: при примерно равном приросте за ход суммарные оплаченные входные токены за N ходов растут как O(N^2) — удвоение числа ходов учетверяет счёт.

Что дальше

Мы разобрались, что именно уходит в модель и почему это стоит денег. Осталась вторая половина вопроса: почему обработка этого массива токенов требует видеокарты, а не обычного процессора, что такое инференс с точки зрения счёта и памяти, и откуда берутся требования к железу, о которые спотыкаются все, кто пытается запустить модель локально.

Железо: почему для ИИ нужны видеокарты

Нашли неточность? Выделите фрагмент текста — рядом появится жучок.

Нужен разбор именно вашей ситуации?

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

Доска запросов