Москва: проверенные б/у ноутбуки с гарантией и проверкой перед оплатой
+7 906 722-94-45

Главная → Статьи → Контекстное окно LLM: управление длиной контекста

Контекстное окно LLM: как уложить задачу в лимит и не потерять важное

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

Прямой ответ

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

Из чего складывается расход:

  1. Системная инструкция. Часто 200–2000 токенов, повторяется в каждом запросе.
  2. История диалога. Растёт неограниченно, если её не обрезать.
  3. Внешние данные. Фрагменты документов, логи, код — обычно самая большая часть.
  4. Резерв под ответ. Если не оставить место, вывод оборвётся на половине.

Практические следствия:

  • Бюджет контекста нужно считать явно. лимит = система + история + контекст_данных + 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 осознанно: слишком малый обрезает вывод, слишком большой ограничивает допустимый вход.

Рекомендации:

  1. Логируйте фактический расход токенов по каждому запросу. Это единственный способ заметить, что какой-то сценарий стабильно упирается в лимит.
  2. Тестируйте поведение на границе. Что делает приложение, когда вход не помещается: обрезает, отбрасывает запрос или молча теряет часть данных? Поведение должно быть явным и задокументированным.
  3. Не увеличивайте окно «потому что можем». Память под KV-кэш конкурирует с весом модели и параллелизмом — на локальном железе это прямой компромисс, описанный в статье про квантование.

Практический сценарий: ассистент «забывает» начало разговора

Контекст: локальный ассистент разбора инцидентов; на длинных диалогах он теряет договорённости из первых сообщений.

  1. Диагностика. Логи показывают: к 25-му сообщению вход занимает 9.5K токенов при лимите 8K; библиотека молча отбрасывает начало истории, включая системную инструкцию с правилами формата.
  2. Проверка. Повторяем запрос с явным контролем: при переполнении теряется системный промпт, поэтому ответы «плывут» по формату и теряют ограничения.
  3. Исправление. (а) Системный промпт защищаем от обрезки — он всегда остаётся первым; (б) переходим на обрезку по токенам с резервом 1K под ответ; (в) старую часть диалога сжимаем в резюме; (г) большие вставки логов урезаем до релевантных строк через предварительный поиск.
  4. Проверка. На диалоге из 60 сообщений правила формата сохраняются, расход укладывается в 6.8K токенов; добавляем метрику доли запросов, упирающихся в лимит.
  5. Профилактика. Алерт на рост доли «обрезанных» запросов; в документацию добавляем правило: длинные вложения передаются ссылкой на извлечение, а не полным текстом.

Вывод: проблемы «модель забыла» почти всегда про механику обрезки контекста в приложении, а не про возможности модели.

FAQ

Как понять, какой контекст реально нужен? Измерьте распределение фактической длины входов: p50, p95 и максимум. Окно должно покрывать p95 с запасом на ответ; единичные сверхдлинные сценарии лучше решать отдельным потоком обработки, а не увеличением окна для всех.

Почему модель игнорирует инструкцию в длинном промпте? Чаще всего инструкция оказалась в середине объёмного контекста. Помогает перенос ключевых правил в начало и повтор критичных ограничений непосредственно перед запросом, а также сокращение нерелевантного текста.

Помогает ли увеличение окна в настройках сервера? Технически да, но ценой памяти и параллелизма: KV-кэш растёт линейно. На локальном запуске это часто приводит к вытеснению слоёв модели из видеопамяти и падению скорости.

Можно ли сжимать сам промпт без потери смысла? Частично: убрать повторы, объединить примеры, заменить длинные объяснения компактными правилами. Практика показывает, что системный промпт часто сокращается в 2–3 раза без изменения поведения; приёмы — в статье про system prompt.

Источники: «Lost in the Middle» (arXiv), Transformers docs, llama.cpp — ссылки в шапке.