Перейти к содержимому
16 окт. 2025 г.·7 мин чтения

Повторное использование KV-cache в длинных диалогах

Повторное использование KV-cache ускоряет длинные диалоги, если у запросов совпадает начало истории. Разберем схему, риски, метрики и проверки.

Повторное использование KV-cache в длинных диалогах

Почему длинные диалоги начинают тормозить

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

Из-за этого задержка растет еще до первого токена. Пользователь видит паузу и думает, что модель "долго думает", хотя часто она просто повторно обрабатывает старые сообщения, системные инструкции и прошлые ответы. Чем длиннее история, тем дольше этот этап.

Короткий новый вопрос почти не помогает. Даже если человек написал два слова, модели все равно нужен предыдущий контекст. Иначе она потеряет смысл беседы, правила ответа, ограничения по тону и детали, которые уже обсуждались.

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

Последствия простые: ответ начинает стартовать позже, GPU или внешний провайдер тратит больше вычислений на повтор, очередь растет в пиковые часы, а стоимость инференса увеличивается даже без новой полезной информации.

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

Здесь повторное использование KV-cache уже не выглядит мелкой оптимизацией. Это обычная экономия времени и денег. Модель не становится "умнее", но перестает делать лишнюю работу там, где новая информация еще не появилась.

Что хранит KV-cache

KV-cache хранит не "память" в человеческом смысле и не готовый ответ. Он сохраняет промежуточные числовые состояния, которые модель уже посчитала для прочитанных токенов. Проще говоря, модель один раз разбирает начало истории и оставляет рабочие данные, чтобы не выполнять тот же расчет заново.

Буквы KV означают key и value. Это части механизма внимания в трансформере. Для каждого уже прочитанного токена модель создает внутренние представления на разных слоях. Они нужны, чтобы следующие токены могли учитывать прошлый контекст без полного пересчета всей истории.

Поэтому повторное использование KV-cache помогает только там, где у запросов есть общий префикс. Если первые 800 токенов у двух запросов совпадают символ в символ, модель может взять готовую основу для этих 800 токенов и продолжить расчет с места, где начинается различие. Если отличие появляется в первых строках, пользы почти не будет.

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

У такого кэша есть жесткие границы. Он не переносится между разными моделями. Даже близкие модели хранят отдельные состояния, потому что у них разные внутренние слои и веса. Обычно кэш нельзя делить и между разными версиями одной модели. Если команда отправила один и тот же префикс сначала в одну модель, а потом в другую, каждая из них посчитает свой KV-cache отдельно.

Это особенно важно при маршрутизации. Общий текст истории еще не означает общий кэш. Совпадать должны и токены, и сама модель, и ее версия.

Лучше воспринимать KV-cache как сохраненный "черновик вычислений" для точного начала диалога. Он не заменяет контекст и не сокращает историю сам по себе. Он просто убирает повторный расчет там, где история действительно одинакова.

Когда повторное использование дает заметный эффект

Повторное использование KV-cache лучше всего работает там, где у многих запросов одно и то же начало. Обычно это длинный system prompt, набор правил ответа, формат вывода и служебные инструкции, которые почти не меняются от диалога к диалогу. Модель уже один раз обработала этот кусок и в следующих запросах не тратит на него то же время.

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

Обычно эффект хорошо виден, когда совпадает system prompt и стартовый блок инструкций, общая часть истории заметно длиннее новых сообщений, ответ нужен без лишних 300-800 мс на повторную обработку старта, а запросы приходят достаточно часто, чтобы кэш оставался "теплым".

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

Хороший пример - поддержка, где каждый диалог начинается одинаково: правила тона, запрет на лишние обещания, формат ответа, короткая сводка по продукту. Клиент пишет новую реплику, операторский ассистент видит почти тот же контекст плюс свежий хвост. В длинной переписке это может убрать заметную часть задержки, особенно на моделях, где этап prefill сам по себе дорогой.

Если общий префикс короткий или постоянно меняется, кэш почти ничего не даст. Если префикс стабилен, сессии длинные, а поток запросов ровный, повторное использование KV-cache окупается быстро.

Как собрать схему с общим префиксом

Схема работает только тогда, когда вы четко делите историю на две части: постоянный префикс и новый хвост. В префикс обычно входят system-инструкция, правила ответа, служебные роли, приветствие и те сообщения, которые повторяются у многих диалогов.

Переменная часть начинается там, где пользователь добавляет что-то свое: новый вопрос, свежие данные, вложения, результаты поиска, ответы оператора. Если смешать это с постоянным блоком, кэш быстро распадется на мелкие варианты и пользы почти не будет.

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

Базовая схема выглядит так:

  1. Соберите префикс по одному и тому же шаблону.
  2. Токенизируйте его тем же токенизатором, что использует модель.
  3. Посчитайте хеш уже после токенизации, а не по сырому тексту.
  4. Сохраняйте KV-cache с привязкой к модели, версии шаблона и длине контекста.
  5. В новом запросе добавляйте только хвост.

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

На практике запись кэша обычно привязывают к таким полям, как model_id, tokenizer_version, template_version, prefix_hash и context_limit. Иногда добавляют регион, если инференс идет в разных контурах. Для команд, которым важно хранить данные внутри страны, это обычная мера.

Нужен и TTL. Шаблоны меняются, системные инструкции правят, и старый кэш может тихо давать неверное ускорение или ломать воспроизводимость. Поэтому задайте срок жизни и делайте явный сброс при любой смене шаблона, ролей или логики сборки промпта.

Пример: чат поддержки с одинаковым стартом

Сравните затраты честно
Тестируйте модели через единый API с тарификацией по ставкам провайдеров.

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

Представьте чат банка или интернет-магазина. Один клиент спрашивает про возврат, другой про доставку, третий про смену номера телефона. Вопросы разные, но начало у всех почти одинаковое. Если считать весь этот префикс заново для каждого диалога, модель каждый раз тратит время на уже знакомый кусок истории.

Здесь и помогает повторное использование KV-cache. Команда один раз прогоняет общий префикс, сохраняет его, а потом подставляет в новые запросы. Когда приходит свежая реплика клиента, система не пересчитывает первые сотни токенов, а обрабатывает только новое сообщение и строит ответ поверх готового состояния.

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

Ориентир простой: чем длиннее общий префикс и чем чаще он повторяется, тем заметнее выигрыш. Если у вас стандартный старт на 500 токенов и короткий вопрос клиента на 30 токенов, пересчитывать весь ввод заново просто невыгодно.

Как не смешать чужие данные

Самая частая ошибка проста: команда выносит в общий префикс не только системные инструкции, но и куски живого диалога. После этого повторное использование KV-cache уже не ускоряет работу безопасно, а создает риск утечки контекста между людьми или компаниями.

В общий префикс стоит класть только то, что одинаково для всех запросов внутри одной узкой группы. Это могут быть правила ответа, описание продукта, общая структура диалога, стандартные дисклеймеры. Имя клиента, номер договора, история заказов, диагноз, адрес доставки и любой другой персональный фрагмент туда попадать не должен.

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

Даже безопасный префикс лучше делить на отдельные сегменты. Один и тот же шаблон на русском и казахском языке разумно хранить раздельно. То же касается разных клиентских контуров и разных версий промпта. Если банк и ритейлер используют похожий стартовый текст, кэш у них все равно должен жить отдельно.

Роли доступа тоже нельзя смешивать. Префикс для обычного оператора, внутреннего аналитика и администратора должен храниться в разных областях. Иначе модель может получить лишние инструкции или данные, которые относятся к более широким правам.

Короткий TTL часто полезнее сложных правил очистки. Для чувствительных сценариев лучше держать кэш минуты, а не часы. Да, попаданий станет меньше. Зато снизится риск того, что старый контекст случайно доживет до чужого запроса.

Нужен и отдельный учет событий. Логируйте попадания в кэш и сбросы по разным причинам: истек TTL, изменилась версия шаблона, сменился клиентский контур, запрос ушел в другой языковой поток. Тогда команда увидит не только экономию на задержке, но и опасные места в схеме.

Небольшой пример: в чате поддержки телеком-компании можно переиспользовать общий старт с правилами тона, списком разрешенных действий и форматом ответа. Но как только пользователь пишет номер телефона или ИИН, этот кусок должен жить уже вне общего префикса.

Если вы строите такую схему через AI Router, имеет смысл включить маскирование PII и аудит-логи на стороне шлюза. Это не заменяет разделение кэша, но помогает быстрее замечать ошибки в сборке префикса.

Где кэш ломается

PII под контролем
Включите маскирование PII и аудит-логи, чтобы быстрее ловить ошибки в общем префиксе.

KV-cache чаще всего ломается не из-за модели, а из-за мелких отличий в начале запроса. На бумаге префикс один и тот же, а в реальном трафике он уже другой. Этого достаточно, чтобы кэш не подошел и модель пересчитала все заново.

Первая частая ошибка - в общий префикс слишком рано попадают данные, которые меняются на каждом запросе. Это может быть текущее время, ID сессии, случайный маркер, trace id или даже дата в system prompt. Если такой токен стоит в первых строках, общего начала у запросов уже нет.

Вторая проблема встречается постоянно: команда меняет system prompt и забывает сменить его версию в кэше. Вчерашний кэш формально существует, но относится к другому тексту. В итоге вы либо теряете ускорение, либо получаете трудноуловимые ошибки в ответах.

Иногда все ломает не смысл, а формат. Один сервис добавляет лишний пробел в конце строки. Другой меняет порядок ролей. Третий сериализует один и тот же блок с другой пунктуацией. Для человека это один и тот же текст, для токенизатора - уже нет.

Отдельный риск связан с обновлением модели. Если провайдер поменял ее версию, старый KV-cache лучше сразу считать недействительным. Даже при том же промпте внутренние представления могут уже не совпасть.

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

Для проверки обычно хватает пяти пунктов:

  • выносите время, session id и случайные поля за пределы общего префикса;
  • версионируйте system prompt и включайте версию в ключ кэша;
  • нормализуйте пробелы, переносы строк и порядок ролей;
  • сбрасывайте кэш при смене модели;
  • отдельно проверяйте логику обрезки истории.

Если кэш внезапно "просел", сначала сравните первые токены двух запросов побайтно. В большинстве случаев проблема находится за несколько минут.

Как измерить выигрыш, а не гадать

Смотрите не на ощущение "стало быстрее", а на разницу между двумя одинаковыми запросами. Первый прогон идет без кэша, второй использует уже готовый общий префикс истории. Сравнивать нужно на одной и той же модели, с тем же провайдером, тем же размером контекста и теми же параметрами генерации. Если менять сразу несколько условий, цифры теряют смысл.

Лучше всего работает простой тестовый набор из длинных диалогов, где начало истории часто совпадает. Например, чат поддержки: системный промпт, правила безопасности, описание продукта и первые реплики пользователя одинаковы, а дальше меняется только текущий вопрос. На таких сценариях повторное использование KV-cache показывает честный эффект.

Замеряйте как минимум две метрики. Первая - время до первого токена, или TTFT. Она показывает, насколько быстро модель начала отвечать. Вторая - полная задержка до конца ответа. Иногда TTFT заметно падает, а общее время меняется слабо, если ответ длинный. Бывает и наоборот.

Среднее значение почти всегда приукрашивает картину. Поэтому смотрите и распределение: p50 показывает обычный запрос, p95 - то, что замечает заметная часть пользователей в плохие моменты, p99 ловит редкие, но самые неприятные хвосты, а hit rate по кэшу показывает, как часто схема реально помогает.

Отдельно проверяйте hit rate не на синтетике, а на живом потоке. В тестах у вас может быть 90% попаданий, а в проде только 35%, потому что пользователи меняют язык, канал или формат первого сообщения. Тогда ускорение на бумаге есть, а в реальной нагрузке его почти не видно.

Полезно считать выигрыш не только в миллисекундах. Если общий префикс содержит 8-10 тысяч токенов, каждое попадание в кэш убирает повторный prefill для этого куска. Это уже экономия GPU-времени. Ее стоит считать в GPU-секундах или хотя бы в сумме сохраненных токенов prefill за день.

Хороший ориентир простой: теплый запрос должен заметно выигрывать у холодного по TTFT, p95 не должен расползаться, а hit rate должен держаться на таком уровне, который окупает сложность всей схемы.

Быстрая проверка перед запуском

TTFT и hit rate
Возьмите типовой чат и сравните 2-3 модели на одном префиксе.

Перед включением повторного использования KV-cache в продакшене посмотрите на реальные запросы, а не на красивую схему в голове. Кэш окупается только там, где у заметной доли диалогов есть одинаковое начало. Если общий префикс встречается редко, выигрыш часто съедают сложность и дополнительные проверки.

Быстрый тест можно пройти за один рабочий день.

Сначала возьмите логи за несколько дней и сравните первые сообщения в диалогах. Ищите одинаковые системные инструкции, стартовые реплики и шаблонные блоки. Если совпадений мало, повторное использование KV-cache пока не даст заметной пользы.

Потом проверьте, что все сервисы отправляют историю одинаково. Веб, мобильное приложение, бот и внутренний сервис должны использовать один порядок сообщений, те же роли и один формат вставки служебного текста. Даже мелкие отличия в сериализации легко ломают попадание в кэш.

Дальше задайте простые правила жизни кэша: нужен TTL, версия шаблона и ручной сброс. TTL спасает от устаревших префиксов, версия отделяет старый шаблон от нового, а ручной сброс нужен, когда команда срочно меняет системный промпт или логику диалога.

До запуска на весь трафик включите логи и смотрите не только на hit rate, но и на причины промаха. Это может быть изменившийся префикс, истекший TTL, другая версия шаблона или иной формат истории от конкретного сервиса. Без этих данных вы будете гадать, почему задержка не падает.

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

Один практичный пример: чат поддержки часто начинает разговор с одинаковых правил, дисклеймера и первых двух сообщений. Но если мобильное приложение добавляет скрытую служебную реплику, а веб-чат нет, hit rate резко просядет. Снаружи это выглядит как "кэш не работает", хотя причина совсем прозаичная - разные форматы истории.

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

Что сделать дальше

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

Потом запустите A/B-тест. В группе A оставьте обычную обработку без прогретого кэша. В группе B используйте теплый путь с тем же общим префиксом. Смотрите не только на среднее время, но и на p95, число промахов по кэшу и долю ошибок. Среднее часто улучшается быстро, а длинный хвост задержек почти не двигается.

После этого сравните несколько моделей на одном и том же префиксе. Иначе вы не поймете, где выигрыш дал сам кэш, а где его дала другая модель. Для честного сравнения держите одинаковыми текст префикса, параметры генерации, ожидаемую длину ответа и способ расчета задержки и стоимости.

Если вы уже работаете через единый OpenAI-совместимый эндпоинт, такие тесты проще вести в одном слое. В AI Router можно менять модель или провайдера без переписывания SDK, кода и промптов, поэтому сравнение получается чище и быстрее.

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

Хороший первый шаг выглядит просто: выберите один поток, соберите цифры за несколько дней, прогоните холодный и теплый путь, а затем сравните 2-3 модели на одном префиксе. После этого уже понятно, стоит ли расширять схему на другие диалоги или лучше искать выигрыш в другом месте.

Часто задаваемые вопросы

Что такое KV-cache простыми словами?

KV-cache хранит не ответ, а уже посчитанные внутренние состояния модели для начала диалога. Если следующий запрос начинается теми же токенами, модель не считает этот кусок заново и быстрее переходит к новой части.

Когда повторное использование KV-cache реально ускоряет чат?

Он дает хороший эффект, когда у многих запросов один и тот же длинный старт: system prompt, правила ответа, приветствие и первые служебные сообщения. Чем длиннее общий префикс и чем чаще он повторяется, тем заметнее выигрыш по задержке и расходу вычислений.

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

Потому что модель обычно читает всю историю перед новым ответом. Даже короткий вопрос в конце не убирает длинный префикс, если без него модель потеряет правила, тон и смысл беседы.

Что класть в общий префикс?

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

Что нельзя класть в общий префикс?

Не кладите туда имя клиента, номер договора, телефон, адрес, историю заказов и любые другие персональные данные. Простое правило такое: если этот текст нельзя спокойно показать другому пользователю, держите его вне общего префикса.

Можно ли использовать один и тот же кэш для разных моделей?

Нет, так делать не стоит. KV-cache привязан к конкретной модели и обычно к ее версии, потому что внутренние состояния у них разные.

Почему одинаковый на вид префикс иногда не дает попадание в кэш?

Обычно виноваты мелкие отличия в первых токенах. Лишний пробел, другая дата, иной порядок ролей, session id или trace id в начале запроса сразу ломают совпадение.

Как правильно идентифицировать запись кэша?

Считайте хеш после токенизации, а не по сырому тексту. В записи кэша держите model_id, версию токенизатора, версию шаблона, хеш префикса и лимит контекста.

Как измерить пользу, а не гадать по ощущениям?

Сравните холодный и теплый путь на одной модели и с одними параметрами. Смотрите на TTFT, полную задержку, p95 и hit rate, а не только на среднее число.

С чего начать пилот с KV-cache в продакшене?

Начните с одного потока, где много одинаковых стартов, например с чата поддержки. Зафиксируйте шаблон, задайте TTL, включите логи причин промаха и проверьте, что персональные данные не попадают в общий префикс.