Оптимизация LLM запросов контекстным профилировщиком: снижаем токены | AiManual
AiManual Logo Ai / Manual.
12 Июн 2026 Гайд

Оптимизация LLM-запросов с помощью контекстного профилировщика: снижаем расход токенов

Практическое руководство по использованию контекстного профилировщика для сокращения расхода токенов в LLM. Анализ, настройка, примеры и типичные ошибки.

Реклама
vec_recv1

Почему ваш контекст стоит как чугунный мост

У каждого, кто хоть раз выкатывал LLM в продакшен, была минута откровения: вы смотрите на счёт за API и чувствуете, как кровь отливает от лица. Токены утекают рекой, а вы даже не знаете, какие именно куски промпта реально нужны модели. Я проработал с десятками команд, и почти везде одно и то же — в контекст пихают всё подряд: историю чата, результаты RAG-поиска, системные промпты на три страницы. Потом удивляются, почему ответы тормозят, а счёт в AWS или OpenAI растёт.

В 2026 году на рынке появился новый класс инструментов, которые я называю контекстными профилировщиками. Это не просто мониторинг токенов — это хирургический скальпель для вашего контекста. Они показывают, какие именно последовательности токенов модель действительно использует при генерации ответа, а какие висят мёртвым грузом. И да, есть открытая библиотека ContextProfiler (v2.1 на июнь 2026), которую мы и разберём.

Но прежде — короткая анатомия пустых трат. Вы знали, что до 40% токенов в промпте часто не участвуют в формировании ответа? Особенно это заметно в RAG-системах, где мы подмешиваем десять документов, а модель смотрит только в два из них. Я писал об этом в статье Почему RAG-система извлекает правильные данные, но даёт неверный ответ — там как раз про то, что найденные документы часто нерелевантны для конкретного запроса, и модель путается. Профилировщик решает эту проблему на корню: он скажет, какие чанки документа реально сработали.

Типичная боль: вы передаёте в промпт 20 страниц логов системы, чтобы модель проанализировала ошибку, но она использует только последние 10 строк. Остальные 19.5 страниц — деньги на ветер. Профилировщик показывает это чёрным по белому.

Как работает контекстный профилировщик (и почему это не очередная игрушка)

Идея проста до безобразия: мы перехватываем запрос к LLM, прогоняем его через модель анализа (обычно лёгкую, типа специально обученного кодировщика) и сопоставляем входные токены с выходными. Технически это делается через attention-карты: профилировщик смотрит, на какие входные токены модель обращала внимание при генерации каждого выходного токена. Суммируем по слоям — получаем веса важности каждого входного токена. Токены с весом ниже порога — кандидаты на удаление.

В ContextProfiler этот процесс называется context saliency analysis. Он не требует доступа к весам модели — работает через логпробс и хиты кэша. Да, точность не 100%, но на практике хватает, чтобы сократить контекст на 30-60% без потери качества.

Звучит логично, но есть нюанс: профилировщик не умеет предсказывать, что понадобится модели в будущем. Он только анализирует уже сделанные запросы. Значит, мы должны сначала запустить его на репрезентативной выборке, собрать статистику, а потом применить оптимизацию. Это цикл: измерь → вырежи → проверь → повтори.

Ставим ContextProfiler — три строчки, и ты в игре

Установка через pip:

pip install context-profiler

Библиотека поддерживает все популярные провайдеры: OpenAI, Anthropic, локальные модели через vLLM или llamacpp. Для интеграции с существующим кодом достаточно обернуть вызов LLM в профилировщик.

Вот как НЕ надо делать (типичная ошибка новичков):

from openai import OpenAI
client = OpenAI()
response = client.chat.completions.create(model="gpt-4o", messages=messages)
print(response.usage.total_tokens)  # бесполезно — цифра есть, а где срезать?

Этот код показывает только общее число токенов, но ничего не говорит о важности каждого блока. Правильно — использовать профилировщик как middleware:

from context_profiler import ContextProfiler
from openai import OpenAI

profiler = ContextProfiler(saliency_threshold=0.15)  # отбрасывать токены с весом < 15%
client = OpenAI()

with profiler.track() as session:
    response = client.chat.completions.create(
        model="gpt-4o",
        messages=[
            {"role": "system", "content": long_system_prompt},
            {"role": "user", "content": user_query_with_rag}
        ]
    )
    # Получаем отчёт по каждому сегменту контекста
    report = session.report()
    print(report.salient_tokens_ratio)  # 0.42 — значит 58% контекста не используется

1 Собираем профиль на репрезентативной выборке

Запустите профилировщик на, скажем, 1000 реальных запросах. Сохраните все отчёты. Обратите внимание на распределение важности по сегментам контекста: системный промпт, контекстные документы, история диалога. Часто оказывается, что системный промпт весит много, но его редко используют полностью. Или что история диалога из последних 10 сообщений важна, а из первых 20 — почти нет.

2 Оптимизируем контекст на основе профиля

Допустим, профиль показал, что в RAG-контексте 70% токенов имеют вес ниже 0.1. Это значит, что вы можете либо уменьшить количество возвращаемых документов (перейти с top_k=10 на top_k=4), либо обрезать сами документы до первых N токенов, где сосредоточена важность. Как это сделать правильно, я описывал в материале Когда токены вздуваются — обратите внимание на разницу в токенизации разных языков; профилировщик это учитывает автоматически.

3 Проверяем качество после сокращения

Самый страшный сон: вы сократили контекст на 40%, а модель начала ошибаться. Поэтому после каждой итерации нужно прогнать авто-тесты или хотя бы семплирование. ContextProfiler умеет сравнивать ответы модели до и после оптимизации с помощью встроенной метрики semantic similarity (на базе эмбеддингов). Если расхождение больше 5% — откатываем изменения.

Что показывает реальный профиль: разбор на примере

Недавно я профилировал один сервис поддержки на основе LLM. Контекст состоял из системного промпта (1800 токенов), истории чата (3000 токенов) и результатов RAG-поиска (5000 токенов). Итоговый профиль:

Сегмент Токенов Используется % мусора
System prompt 1800 850 53%
История (последние 5 сообщ.) 1200 1100 8%
История (сообщ. 6-10) 1800 200 89%
RAG-документы 5000 2100 58%

Очевидно, что историю глубже 5 сообщений можно смело выбрасывать — сэкономили 1800 токенов. Системный промпт переписал с 1800 до 900 токенов, выкинув инструкции, которые модель не читала. RAG-документы стали возвращать только топ-3 результата вместо топ-5 (убрали два самых тяжёлых и неиспользуемых). Итог: контекст сократился с 9800 до 4200 токенов. Расходы на API упали на 57%, а качество ответа по тестам не изменилось.

💡
При таком сокращении можно заодно уменьшить latency за счёт более быстрой префилл-фазы. Как именно — читайте в статье Cache-aware prefill–decode disaggregation: ускорение на 40%. Там показано, что длинный контекст тормозит именно на prefill, и его укорачивание даёт двойной профит: и по деньгам, и по скорости.

Три грабли, об которые спотыкаются даже сеньоры

Грабли 1: Доверие к единичному профилю. Профилировщик анализирует один запрос. На тысяче запросов картина может отличаться. Если вы оптимизируете на основе всего 10 запросов, рискуете вырезать то, что модель использует в других сценариях. Всегда собирайте профиль на репрезентативном датасете, а ещё лучше — внедрите непрерывное профилирование в production. Для этого можно использовать Tokentap — MitM-прокси для мониторинга токенов; он как раз подходит для сбора непрерывных профилей без изменения кода.

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

Грабли 3: Спрятанные в неактивных токенах подсказки. Даже с низкой салиенсностью некоторые токены могут быть важны для правильного понимания косвенных отсылок. Например, вы вырезали фрагмент документа, где упоминалось название компании, и модель перестала понимать контекст. Профилировщик может не показать высокий вес у такого токена, если он используется только в паре с другими. Поэтому после каждой итерации строго обязательна валидация качества — без неё вы рискуете получить модель, которая отвечает невпопад.

Как связать профилировщик с маршрутизацией и кэшированием

Когда вы знаете, какие части контекста реально нужны, вы можете пойти дальше: динамически подставлять только нужные сегменты в зависимости от запроса. На эту тему я уже писал про LLMRouter — снижение расходов на 30-50%. Комбинируя профилировщик и роутер, вы строите систему, которая модельному узлу отправляет минимально необходимый контекст, а дешёвому классификатору (например, на базе BERT) отдаёт всю историю для принятия решения — это радикально дешевле, чем гонять всё через LLM.

Плюс не забывайте про кэш. Если вы сократили контекст, запросы становятся более повторяемыми, и доля кэш-хитов растёт. В моём проекте после оптимизации hit rate кэша vLLM подскочил с 12% до 44%. Метрики для стабильной работы self-hosted LLM я разбирал в статье 5 ключевых метрик для self-hosted LLM — cache hit ratio там на первом месте.

Скрещиваем с топ-k и time-to-first-token

Ещё один приём: после обрезки контекста вы можете уменьшить количество генерируемых токенов (если ответы тоже были раздуты). Тут поможет оптимизированный Top-K на CPU — на инференсе он ускоряет выборку, а профилировщик подскажет, какие выходные токены избыточны.

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

А что с неанглийскими языками?

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

Когда профилировщик бессилен: честный диагноз

Есть случаи, когда контекстный профилировщик не спасёт:

  • Модель использует почти все токены равномерно (например, при суммаризации длинных текстов). Тогда вырезать нечего.
  • У вас одна-единственная цепочка запросов без повторяющихся паттернов — профиль не накопит статистику.
  • Вы работаете с закодированным или сильно структурированным контекстом (JSON, XML), где каждый токен важен для формата — но даже здесь можно выбросить лишние ключи.

Но в 90% продакшен-систем профилировщик даёт драматический эффект. Особенно если совместить его с маршрутизацией и правильной архитектурой промптов.

Не верьте никому, кто говорит, что можно срезать токены вслепую, без анализа. Только профилировщик даёт объективную картину. И да, я не постесняюсь сказать: если вы до сих пор не используете профилирование контекста, вы просто выбрасываете деньги в мусорку. Каждый день.

Подписаться на канал