Прямой ответ
Контекстное окно — максимальное число токенов, которое модель обрабатывает за один вызов: системный промпт, история, входные данные и генерируемый ответ. Ошибочное представление — «чем больше окно, тем лучше»: расход памяти под KV-кэш растёт линейно с длиной, а качество внимания к деталям в длинном контексте часто снижается.
Из чего складывается расход:
- Системная инструкция. Часто 200–2000 токенов, повторяется в каждом запросе.
- История диалога. Растёт неограниченно, если её не обрезать.
- Внешние данные. Фрагменты документов, логи, код — обычно самая большая часть.
- Резерв под ответ. Если не оставить место, вывод оборвётся на половине.
Практические следствия:
- Бюджет контекста нужно считать явно.
лимит = система + история + контекст_данных + max_tokens_ответа + запас. Без явного бюджета приложение «случайно» начинает обрезать важное. - «Потеря в середине» (lost in the middle). Модели лучше используют начало и конец контекста, чем середину. Критичные инструкции и ключевые факты стоит размещать ближе к краям.
- Длинный контекст не заменяет отбор данных. Включение десяти документов «на всякий случай» обычно ухудшает ответ: растёт объём нерелевантного текста, а доля полезного падает. Отбор через поиск — см. локальный RAG.
Как считать токены точно: по токенизатору модели, а не по числу символов. Методика и типичные расхождения — в статье про токенизацию.
Практика: стратегии управления контекстом
1. Обрезка истории по окну, а не по числу сообщений. Храните скользящее окно последних N токенов, а не «последние 10 сообщений»: одно сообщение может занимать и 50, и 3000 токенов.
def trim_history(messages, limit_tokens, tokenizer, reserve=1024):
system = [m for m in messages if m["role"] == "system"]
rest = [m for m in messages if m["role"] != "system"]
budget = limit_tokens - reserve - sum(count(m, tokenizer) for m in system)
out = []
for m in reversed(rest):
c = count(m, tokenizer)
if budget - c < 0:
break
out.insert(0, m); budget -= c
return system + out
2. Суммаризация старой истории. Когда диалог длинный, ранние сообщения сжимают в короткое резюме и кладут в начало. Это сохраняет факты, но теряет формулировки — для задач, где важна точность цитат, суммаризация неприемлема.
3. Приоритизация фрагментов. Ранжируйте найденные фрагменты и берите топ-K, а не всё. Полезный приём — класть самый релевантный фрагмент последним, ближе к запросу.
4. Разделение задач. Вместо одного запроса «прочитай 40 страниц и ответь» используйте два прохода: извлечение релевантного по частям, затем ответ по сжатой выжимке.
5. Явный резерв под ответ. Устанавливайте max_tokens осознанно: слишком малый обрезает вывод, слишком большой ограничивает допустимый вход.
Рекомендации:
- Логируйте фактический расход токенов по каждому запросу. Это единственный способ заметить, что какой-то сценарий стабильно упирается в лимит.
- Тестируйте поведение на границе. Что делает приложение, когда вход не помещается: обрезает, отбрасывает запрос или молча теряет часть данных? Поведение должно быть явным и задокументированным.
- Не увеличивайте окно «потому что можем». Память под KV-кэш конкурирует с весом модели и параллелизмом — на локальном железе это прямой компромисс, описанный в статье про квантование.
Практический сценарий: ассистент «забывает» начало разговора
Контекст: локальный ассистент разбора инцидентов; на длинных диалогах он теряет договорённости из первых сообщений.
- Диагностика. Логи показывают: к 25-му сообщению вход занимает 9.5K токенов при лимите 8K; библиотека молча отбрасывает начало истории, включая системную инструкцию с правилами формата.
- Проверка. Повторяем запрос с явным контролем: при переполнении теряется системный промпт, поэтому ответы «плывут» по формату и теряют ограничения.
- Исправление. (а) Системный промпт защищаем от обрезки — он всегда остаётся первым; (б) переходим на обрезку по токенам с резервом 1K под ответ; (в) старую часть диалога сжимаем в резюме; (г) большие вставки логов урезаем до релевантных строк через предварительный поиск.
- Проверка. На диалоге из 60 сообщений правила формата сохраняются, расход укладывается в 6.8K токенов; добавляем метрику доли запросов, упирающихся в лимит.
- Профилактика. Алерт на рост доли «обрезанных» запросов; в документацию добавляем правило: длинные вложения передаются ссылкой на извлечение, а не полным текстом.
Вывод: проблемы «модель забыла» почти всегда про механику обрезки контекста в приложении, а не про возможности модели.
FAQ
Как понять, какой контекст реально нужен? Измерьте распределение фактической длины входов: p50, p95 и максимум. Окно должно покрывать p95 с запасом на ответ; единичные сверхдлинные сценарии лучше решать отдельным потоком обработки, а не увеличением окна для всех.
Почему модель игнорирует инструкцию в длинном промпте? Чаще всего инструкция оказалась в середине объёмного контекста. Помогает перенос ключевых правил в начало и повтор критичных ограничений непосредственно перед запросом, а также сокращение нерелевантного текста.
Помогает ли увеличение окна в настройках сервера? Технически да, но ценой памяти и параллелизма: KV-кэш растёт линейно. На локальном запуске это часто приводит к вытеснению слоёв модели из видеопамяти и падению скорости.
Можно ли сжимать сам промпт без потери смысла? Частично: убрать повторы, объединить примеры, заменить длинные объяснения компактными правилами. Практика показывает, что системный промпт часто сокращается в 2–3 раза без изменения поведения; приёмы — в статье про system prompt.
Источники: «Lost in the Middle» (arXiv), Transformers docs, llama.cpp — ссылки в шапке.