MAATRIX / Блог / Контекстное окно и память: как считать

Контекстное окно и память: как считать

Контекстное окно и память: как считать

MAATRIX

«Модель поддерживает 128k контекста» — строка из карточки, которая не говорит, сколько это весит в оперативной памяти и сколько токенов останется на ответ после истории диалога. Контекстное окно — это общий бюджет токенов, а не резиновая величина, и его нехватка выглядит не как понятная ошибка, а как модель, которая вдруг «забыла» начало разговора или обрывает ответ на полуслове. Разберём, как токены считаются для русского текста, откуда берётся память под контекст и как посчитать её заранее — а не по факту падения сервиса.

Обсудить статью, задать вопрос или начать новую тему

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

Перейти в сообщество →

Контекстное окно — это токены, а не мегабайты текста

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

У путаницы два источника: архитектурный максимум модели, заложенный при обучении и зашитый в веса, и num_ctx — реальный размер окна, который выставляется при запуске и упирается в доступную память. Числа разные: в карточке модели указано первое, а падает у вас именно второе.

Посмотреть архитектурный максимум конкретной модели:

ollama show llama3.1:8b

  Model
    architecture        llama
    parameters          8.0B
    context length       131072
    embedding length     4096
    quantization         Q4_K_M

context length здесь — не то, что реально запущено, а потолок, который поддерживает архитектура. По умолчанию Ollama выделяет под диалог num_ctx = 4096 — из паспортных 131072 токенов без явной настройки работает тридцать вторая часть. Изменить дефолт на весь сервис можно переменной OLLAMA_CONTEXT_LENGTH в systemd-юните, а под конкретный запрос — параметром num_ctx в теле запроса к /api/generate или /api/chat. Полную формулу перевода токенов в гигабайты разбирали в статье «Сколько RAM нужно для Ollama»; здесь — откуда берутся сами токены и почему у моделей одного размера разная цена контекста.

Токены: как считать по-русски, а не по правилу из англоязычного гайда

Токен — не буква и не слово, а кусок, который выделил токенизатор конкретной модели: частое слово превращается в один токен, редкое или составное — распадается на два-три, а для не-латиницы кусков обычно требуется больше. Llama, Qwen и Mistral используют BPE-токенизатор, обученный на корпусе с сильным перекосом в сторону английского и кода; кириллица в такой словарь входит менее эффективными кусками, чем латиница. Формулы «столько-то символов на токен» из англоязычных гайдов для русского текста систематически завышают, сколько у вас реально влезет в num_ctx.

Правильный способ — не гадать по правилу, а посчитать на своём тексте. Быстрее всего — через --verbose у ollama run:

ollama run qwen2.5:7b --verbose
>>> Объясни в двух предложениях разницу между контекстным окном и памятью модели.
[текст ответа]

prompt eval count:    32 token(s)
prompt eval rate:     68.0 tokens/s
eval count:           214 token(s)
eval rate:              7.4 tokens/s

prompt eval count — сколько токенов занял ваш запрос, eval count — сколько сгенерировано в ответе; их сумма и есть то, что реально израсходовано из num_ctx за один обмен. Ставки eval rate и prompt eval rate в примере — наш замер qwen2.5:7b в Q4_K_M на AMD EPYC 9554 при 16 потоках (Ollama 0.33.1); на другом железе цифры будут своими, а число токенов в запросе от процессора не зависит.

То же самое видно из API без интерактивного режима — оба поля приходят в финальном JSON ответа:

curl -s http://127.0.0.1:11434/api/generate \
  -d '{"model":"qwen2.5:7b","prompt":"Тест","stream":false}' \
  | jq '.prompt_eval_count, .eval_count'

Для офлайн-подсчёта до отправки — например, чтобы заранее обрезать историю, а не ловить обрыв ответа, — нужен токенизатор именно вашей модели: у Qwen, Llama и Mistral разные BPE-словари, и счёт одного на тексте другого ошибается на десятки процентов. Библиотека transformers грузит родной токенизатор по имени модели — AutoTokenizer.from_pretrained('Qwen/Qwen2.5-7B-Instruct') — и считает точно; универсального «токенизатора вообще» не существует.

Развернуть за пару минут

Готовый образ на VPS MAATRIX: NVMe, AMD EPYC, root-доступ. Локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Развернуть Ollama

Почему память растёт вместе с контекстом: KV-кэш и разная цена у похожих моделей

Каждый токен контекста не растворяется бесследно — модель хранит его векторы ключей и значений (KV, key-value), чтобы на следующем шаге не пересчитывать внимание по всей истории заново. Это и есть KV-кэш, и растёт он линейно с числом токенов в num_ctx, независимо от того, заполнен контекст текстом или просто зарезервирован под будущее.

Дело в архитектуре внимания: у классического multi-head attention число голов для ключей-значений равно числу голов внимания вообще, и KV-кэш растёт вместе с обоими. Современные модели почти поголовно перешли на GQA (grouped-query attention) — голов внимания по-прежнему много, а голов для ключей-значений заметно меньше, несколько голов делят одну KV-голову между собой. Качество почти не страдает, память под контекст падает кратно.

Разница видна на двух моделях сходного размера. У llama3.1:8b — 32 слоя, 32 головы внимания и только 8 KV-голов, размер головы 128. У qwen2.5:7b — 28 слоёв, 28 голов внимания и всего 4 KV-головы при том же размере головы 128. В байтах на токен по формуле KV = 2 × слои × KV-головы × размер головы × 2 байта (fp16): у Llama 3.1 8B — 2 × 32 × 8 × 128 × 2 = 131 072 байта на токен, у Qwen2.5 7B — 2 × 28 × 4 × 128 × 2 = 57 344 байта. Разница — почти в 2,3 раза, и она не связана с тем, что одна модель «умнее» другой, только с формой внимания. Точное число KV-голов смотрите в поле num_key_value_heads файла config.json модели на HuggingFace.

Таблица: сколько памяти добавляет контекст на разных моделях

Переведя цену токена в гигабайты по длине контекста, разница становится наглядной:

КонтекстKV-кэш, Llama 3.1 8B (8 KV-голов)KV-кэш, Qwen2.5 7B (4 KV-головы)
40960,5 ГБ0,22 ГБ
81921,0 ГБ0,44 ГБ
327684,0 ГБ1,75 ГБ
655368,0 ГБ3,5 ГБ
13107216,0 ГБ7,0 ГБ

Таблица — расчёт по формуле, не замер, без +10% на буферы вычислительного графа из разбора RAM. Масштаб виден и так: на полном паспортном контексте KV-кэш qwen2.5:7b — 7 ГБ, больше, чем сама модель в памяти: в наших тестах на AMD EPYC 9554 qwen2.5:7b в Q4_K_M резидентно занимает 5,1 ГБ. При контексте 128k сам контекст обходится дороже, чем всё знание модели, сжатое в веса.

Вывод жёстче, чем кажется: паспортный максимум не бесплатен даже тогда, когда вы им не пользуетесь. Ollama резервирует KV-кэш под весь заявленный num_ctx сразу при загрузке, а не постепенно по мере заполнения диалога — настройка «на всякий случай» это гигабайты, съеденные без единого токена в истории. Снизить цену без потери длины помогает квантование кэша — OLLAMA_FLASH_ATTENTION=1 с OLLAMA_KV_CACHE_TYPE=q8_0 режут числа из таблицы почти вдвое.

RoPE-масштабирование: почему заявленные 128k — не всегда 128k без потерь

Число в карточке модели — не гарантия ровного качества на всей его длине. Многие модели достигают заявленного контекста не тем, что их с нуля обучили на текстах такой длины, а расширением через масштабирование позиционных эмбеддингов — RoPE scaling: линейное, NTK-aware, YaRN. Механизм рабочий, но за пределами исходной, «нативной» длины обучения точность поиска нужного факта внутри длинного контекста обычно падает — модель не столько «не помнит», сколько хуже находит фрагмент среди тысяч нерелевантных токенов, особенно ближе к середине контекста.

Практический вывод: не берите паспортный максимум как точку по умолчанию. Если задаче нужно 32k, а не 128k, не выставляйте num_ctx в максимум «на будущее» — это не только лишняя память из таблицы выше, но и работа там, где модель не тестировалась так же тщательно, как на нативной длине. Нерасширенную длину обучения смотрите в config.json модели на HuggingFace, в поле max_position_embeddings до применения rope_scalingollama show отдаёт уже итоговое, расширенное число.

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

Многоходовые диалоги: контекст — это весь разговор, а не последнее сообщение

В чате num_ctx делится между входом и выходом одновременно и заполняется не только последним сообщением, а всей историей: система, прошлые реплики, новый вопрос — и только то, что осталось, достаётся на генерацию. Каждый запрос к /api/chat заново отправляет весь массив сообщений целиком: не Ollama «помнит» разговор между запросами, а клиент — Open WebUI, скрипт, любое приложение — присылает нарастающую историю заново.

Отсюда конкретная ловушка: если история уже заняла 3900 токенов из num_ctx = 4096, модели физически некуда генерировать ответ — резерва в 196 токенов хватит на пару предложений, и вывод оборвётся на середине мысли. В JSON-ответе это видно по полю done_reason: "stop" — модель закончила сама, встретив стоп-токен, "length" — её остановил потолок контекста:

curl -s http://127.0.0.1:11434/api/chat -d '{...}' | jq -r '.done_reason'

Значение length — сигнал не «модель отвечает криво», а «увеличьте num_ctx или обрежьте историю», и это почти всегда дешевле решить укорачиванием контекста, чем добавлением памяти в сервер.

Для RAG бюджет ещё жёстче: в контекст помимо истории добавляются найденные фрагменты документов, и формула держит все слагаемые сразу — промпт, история, top_k фрагментов по chunk_size каждый, вопрос и резерв под ответ, а сумма не должна подходить к num_ctx вплотную. При top_k=5 и chunk_size=512 пять фрагментов уже съедают 2560 токенов до того, как модель увидела вопрос — на num_ctx=4096 это почти не оставляет места для ответа. Как собирать данные под RAG — тема статьи про сбор данных парсингом сайтов; здесь важно, что бюджет считается до обрезанного ответа на проде, а не после.

Какой сервер под контекст брать в MAATRIX

Бюджет памяти под контекст стоит посчитать до заказа. Три ориентира: короткий чат — хватает num_ctx 4–8k; RAG над документами — обычно 16–32k с запасом под фрагменты; целый документ или часть кодовой базы в промпте — 64–128k, и здесь разница Llama-подобной и Qwen-подобной формы внимания становится разницей между «влезло» и OOM, а не теорией.

На машине с 16 ГБ RAM: qwen2.5:7b (5,1 ГБ резидентно, наш замер) при 32k добавляет 1,75 ГБ KV — вместе 6,85 ГБ, комфортно и с Open WebUI рядом. llama3.1:8b (5,6 ГБ) на том же контексте берёт 4 ГБ KV — вместе 9,6 ГБ, тоже влезает, но запаса на второй запрос или эмбеддер уже нет.

Больше vCPU не значит больше контекста и не всегда быстрее: на нашем стенде (AMD EPYC 9554, 16 vCPU, Ollama 0.33.1) генерация qwen2.5:7b выходит на полку уже на 4 потоках (7,6 ток/с) и держится там до 16 (7,4 ток/с), а обработка промпта растёт с потоками — 12,6 → 31,0 → 61,7 → 68,0 на 2/4/8/16 — и обваливается на 32 при 16 физических vCPU: 0,35 и 10,9 ток/с. За скорость первого токена отвечают ядра, за то, влезет ли контекст вообще, — память.

Честный минимум — 8 vCPU, 16 ГБ RAM, 100 ГБ NVMe: модели до 8B с GQA уровня Qwen тянут 32k, у Llama-подобных — ближе к 16–24k. Комфорт — 16 vCPU, 32 ГБ RAM, 200 ГБ NVMe: помещается 64k почти на любой архитектуре или умеренный контекст на 24–32B. Для регулярного разбора документов на 100k+ токенов на скорости — выбор GPU для Великобритании.

Ollama — приложение каталога apps.maatrix.io, на тарифе Heka разворачивается автоматически при заказе, доступ появляется в кабинете сразу после установки. Локация — Лондон (UK): модель считает локально, расстояние до провайдеров ИИ ни при чём, а вот содержимое контекста — часто чужие документы и код — делает юрисдикцию небезразличной; для данных из ЕС площадка в Великобритании привычна по соседству с GDPR. Оплата — картами российских банков, по СБП, криптовалютой или токеном MAAT, зарубежная карта не нужна. Если Ollama ещё нет — пошаговая инструкция; если генерация медленная сама по себе — отдельный разбор.

Развернуть за пару минут

Готовый образ на VPS MAATRIX: NVMe, AMD EPYC, root-доступ. Локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Развернуть Ollama

Обсудить статью, задать вопрос или начать новую тему

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

Перейти в сообщество →

Частые вопросы

Сколько токенов выходит из N символов русского текста?

Однозначного коэффициента нет — считайте на своей модели: ollama run <модель> --verbose печатает prompt eval count для реального запроса, а curl .../api/generate со "stream":false отдаёт то же число полем prompt_eval_count в JSON. Готовые «символов на токен» из англоязычных гайдов для кириллицы обычно занижают реальный расход.

Можно поставить num_ctx в максимум, который поддерживает модель?

Не стоит: Ollama резервирует память под весь num_ctx сразу при загрузке, даже если диалог короткий, а качество поиска у RoPE-расширенных моделей за пределами нативной длины обучения обычно снижается. Берите контекст под задачу, а не с запасом «на всякий случай».

Почему две модели на 7–8 миллиардов параметров требуют разной памяти под один контекст?

Дело в числе KV-голов архитектуры внимания (GQA): у llama3.1:8b их 8, у qwen2.5:7b — 4 при том же размере головы, и цена токена контекста у Qwen выходит больше чем вдвое ниже. Смотрите num_key_value_heads в config.json модели на Hugging Face.

Нужны сами нейросети для контента?

Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.