Что такое prefix caching и когда он эффективен?
Краткий тезис
Prefix caching — это техника оптимизации инференса LLM, при которой KV-кэш (ключи и значения attention) для общего начального фрагмента последовательности (префикса) сохраняется и переиспользуется между разными запросами. Это эффективно, когда множество запросов начинаются с одного и того же длинного префикса — например, системного промпта в чат-боте или набора инструкций в агентной системе. Основной выигрыш — снижение времени первого токена (TTFT) и увеличение пропускной способности (throughput) без потери качества.
1. Термин: Prefix caching (кэширование префиксов)
Prefix caching — это метод, при котором KV-кэш (тензоры ключей и значений, вычисленные для каждого слоя трансформера) для общего начального сегмента входной последовательности сохраняется в отдельном хранилище. При поступлении нового запроса система проверяет, совпадает ли его префикс с одним из сохранённых. Если совпадает, то вычисления для этого префикса не повторяются — используется готовый кэш.
KV-кэш — это структура данных, которая хранит вычисленные Key и Value матрицы для каждого слоя self-attention на каждом шаге генерации. Без кэша каждый новый токен требовал бы пересчёта attention для всей предыдущей последовательности, что даёт квадратичную сложность O(n²). Кэш позволяет вычислять attention только для нового токена, снижая сложность до O(n) на шаг.
Префикс — это начальная часть входной последовательности, которая одинакова для группы запросов. Например, системный промпт: «Ты — полезный ассистент. Отвечай кратко и по делу.» или набор документов в RAG-системе.
2. Как работает инференс LLM без prefix caching
При генерации ответа LLM работает авторегрессивно: каждый новый токен предсказывается на основе всех предыдущих. На каждом шаге:
- Входная последовательность (промпт + уже сгенерированные токены) подаётся на вход модели.
- Вычисляются Key и Value для каждого слоя attention.
- Attention слой использует эти Key/Value для вычисления контекстуализированных представлений.
- Последний скрытый слой выдаёт логиты, из которых выбирается следующий токен.
Если не использовать кэш, то на каждом шаге пришлось бы заново вычислять Key/Value для всей последовательности, включая префикс. Это крайне неэффективно, особенно при длинных префиксах.
Prefill-фаза (обработка всего промпта) и decode-фаза (генерация токенов по одному) обычно разделены. Во время decode-фазы KV-кэш уже заполнен для всех токенов промпта, и новые токены просто добавляются в кэш. Однако если два запроса имеют разный промпт, но общий префикс, то без prefix caching каждый запрос будет заново вычислять KV для этого префикса во время prefill.
3. Механизм prefix caching
Prefix caching работает на уровне prefill-фазы (или при первом проходе). Алгоритм:
- Разбиение запроса на префикс и суффикс. Система определяет, какая часть входной последовательности является общим префиксом. Обычно это первые N токенов (например, системный промпт).
- Поиск в кэше По хешу префикса (или по самому префиксу) проверяется, есть ли уже сохранённый KV-кэш для этого префикса.
- Использование кэша Если кэш найден, то prefill-фаза выполняется только для суффикса (оставшейся части запроса). KV-кэш для префикса берётся из хранилища.
- Обновление кэша Если кэша нет, то prefill выполняется полностью, и KV-кэш для префикса сохраняется для будущих запросов.
Пример реализации (псевдокод на Python с использованием абстрактного LLM-сервера):
class PrefixCache:
def __init__(self):
self.cache = {} # prefix_hash -> (kv_cache, prefix_tokens)
def get_or_compute(self, prompt_tokens, model, prefix_length):
prefix = tuple(prompt_tokens[:prefix_length])
suffix = prompt_tokens[prefix_length:]
prefix_hash = hash(prefix)
if prefix_hash in self.cache:
# используем готовый кэш для префикса
prefix_kv = self.cache[prefix_hash]
# prefill только для suffix
suffix_kv = model.prefill(suffix, past_key_values=prefix_kv)
full_kv = merge(prefix_kv, suffix_kv)
else:
# полный prefill
full_kv = model.prefill(prompt_tokens)
# сохраняем кэш для префикса
self.cache[prefix_hash] = extract_prefix_kv(full_kv, prefix_length)
return full_kv
Важно prefix caching требует, чтобы модель поддерживала передачу past_key_values (стандартный интерфейс в Hugging Face Transformers). Также нужно аккуратно работать с position IDs — позиции токенов суффикса должны быть продолжением префикса.
4. Когда prefix caching эффективен?
Эффективность определяется двумя факторами: длина общего префикса и частота его повторения.
4.1. Сценарии высокой эффективности
| Сценарий | Пример | Длина префикса | Частота повторения | Выигрыш |
|---|---|---|---|---|
| Чат-боты с системным промптом | «Ты — эксперт по Python. Отвечай с примерами кода.» | 50–200 токенов | Каждый запрос | Значительное снижение TTFT |
| Агентные системы с фиксированными инструкциями | «Ты — агент, выполняющий задачи. Используй инструменты: search, calculator.» | 100–500 токенов | Каждый шаг агента | Ускорение многошаговых рассуждений |
| RAG с общим контекстом | Все запросы к одной базе знаний содержат одинаковый набор документов в начале | 1000+ токенов | Для всех запросов в сессии | Огромный выигрыш (prefill для документов не повторяется) |
| Пакетная обработка (batch inference) | Несколько запросов с одинаковым префиксом обрабатываются вместе | Любая | Внутри батча | Снижение общего времени обработки батча |
Ключевой показатель если префикс составляет >50% от длины промпта и повторяется хотя бы 2–3 раза, prefix caching даёт заметный прирост производительности.
4.2. Когда prefix caching неэффективен или вреден
| Сценарий | Причина |
|---|---|
| Короткие префиксы (<10 токенов) | Overhead на поиск в кэше может превысить выигрыш от переиспользования |
| Уникальные префиксы для каждого запроса | Кэш никогда не попадает, только тратится память |
| Очень большие префиксы (весь промпт уникален) | Нет общего сегмента |
| Высокая вариативность префиксов (например, разные системные промпты для каждого пользователя) | Кэш быстро заполняется, hit rate низкий |
| Модели с очень быстрым prefill (например, оптимизированные kernels) | Выигрыш от кэша может быть меньше, чем затраты на управление кэшем |
5. Сравнение с другими видами кэширования в LLM
| Тип кэширования | Что кэшируется | Когда используется | Влияние на качество |
|---|---|---|---|
| KV cache (стандартный) | Key/Value для каждого шага генерации | Внутри одного запроса (decode-фаза) | Нет |
| Prefix caching | KV для общего префикса между запросами | При совпадении начальных токенов | Нет (те же вычисления) |
| Semantic caching | Ответы LLM на семантически похожие запросы | При высокой семантической близости (cosine similarity) | Может ухудшить, если ответ не релевантен |
| Prompt caching (общее название) | Любая часть промпта (не только префикс) | Разные стратегии | Зависит от реализации |
Prefix caching — это частный случай prompt caching, но сфокусированный именно на начале последовательности. Он не меняет логику генерации, поэтому качество ответа не страдает.
6. Реализация в популярных фреймворках
6.1. vLLM
vLLM поддерживает automatic prefix caching (APC) через аргумент --enable-prefix-caching. Он автоматически обнаруживает общие префиксы в запросах, используя хеширование токенов. Работает как для offline batch, так и для онлайн-сервиса.
python -m vllm.entrypoints.openai.api_server \
--model meta-llama/Llama-2-7b-chat-hf \
--enable-prefix-caching
6.2. Text Generation Inference (TGI)
TGI от Hugging Face имеет встроенный prefix caching через параметр --prefix-caching. Он использует алгоритм PagedAttention и кэширует страницы KV для общих префиксов.
6.3. SGLang
SGLang предоставляет RadixAttention — продвинутую версию prefix caching, которая кэширует не только префиксы, но и произвольные подпоследовательности с помощью дерева префиксов (radix tree). Это позволяет переиспользовать KV для любых общих фрагментов, а не только с начала.
7. Влияние на метрики производительности
| Метрика | Без prefix caching | С prefix caching (длинный общий префикс) |
|---|---|---|
| TTFT (Time to First Token) | Зависит от длины промпта (prefill O(n²)) | Значительно меньше (prefill только для суффикса) |
| Throughput (запросов/сек) | Ограничен prefill для каждого запроса | Выше, так как prefill для префикса выполняется один раз |
| Memory usage | KV-кэш хранится только для активных запросов | Дополнительная память для кэша префиксов (но обычно меньше, чем выигрыш) |
| Latency p99 | Может быть высоким при длинных промптах | Стабильнее, так как prefill ускорен |
Формула оценки ускорения prefill
Пусть длина префикса = P, длина суффикса = S. Время prefill пропорционально O((P+S)²) без кэша и O(S²) с кэшем (если пренебречь overhead). Ускорение ≈ ((P+S)/S)². При P = 1000, S = 100 → ускорение в 121 раз (теоретически). На практике из-за констант и overhead — в 5–20 раз.
8. Связь с Agentic RAG
В Agentic RAG агенты часто используют длинные системные промпты, содержащие описание инструментов, правил и контекста. Например:
Системный промпт:
Ты — исследовательский агент. У тебя есть доступ к инструментам:
- search(query): поиск в корпоративной базе знаний
- calculator(expression): вычисление математических выражений
- summarize(text): краткое изложение
Инструкции:
1. Сначала проанализируй запрос.
2. Если нужны факты, используй search.
3. Если нужны вычисления, используй calculator.
4. Всегда предоставляй ответ с пояснениями.
Каждый шаг агента (вызов инструмента, анализ результата) генерирует новый запрос к LLM, но системный промпт остаётся неизменным. Prefix caching позволяет не пересчитывать KV для этого промпта на каждом шаге, что критически ускоряет многошаговые рассуждения.
Кроме того, в RAG-системах, если все запросы в сессии используют один и тот же набор ретривированных документов (например, «все документы по проекту X»), эти документы можно поместить в префикс и кэшировать.
9. Ограничения и подводные камни
- Память Хранение KV-кэша для длинных префиксов требует памяти. Если префиксов много, кэш может стать большим. Нужна политика вытеснения (LRU, TTL).
- Точность совпадения Кэш работает только при точном совпадении токенов. Даже один лишний пробел или разный токенизатор могут привести к промаху.
- Position IDs: При использовании кэша нужно правильно выставлять позиции для суффикса. Большинство фреймворков делают это автоматически.
- Динамические префиксы Если префикс меняется от запроса к запросу (например, добавляется история диалога), кэш может стать бесполезным. В таких случаях помогает prefix caching с деревом (RadixAttention), который может переиспользовать части префикса.
Пет-проект для закрепления
Задача Реализовать простой сервер для LLM с prefix caching на основе библиотеки Hugging Face Transformers.
Инструменты Python, PyTorch, Hugging Face Transformers, FastAPI.
Шаги:
- Загрузите небольшую модель (например,
Qwen2.5-0.5B-Instruct). - Реализуйте класс
PrefixCache, который хранит KV-кэш для префиксов в словаре (ключ — хеш префикса, значение — кортеж(past_key_values, prefix_length)). - Напишите функцию
generate_with_prefix_cache(prompt, prefix_length), которая:- Разбивает промпт на префикс и суффикс.
- Ищет префикс в кэше.
- Если найден — вызывает
model.generateсpast_key_valuesиз кэша и только суффиксом. - Если не найден — генерирует полный ответ и сохраняет кэш для префикса.
- Оберните в FastAPI endpoint
/chat, который принимаетpromptиprefix_length. - Протестируйте: отправьте 10 запросов с одинаковым префиксом (например, «Ты — полезный ассистент.») и разными суффиксами. Замерьте время выполнения.
Ожидаемый результат Вы увидите, что время первого запроса (без кэша) больше, чем последующих (с кэшем). Для длинного префикса (100+ токенов) ускорение должно быть заметным (2–5x).
Расширение Добавьте поддержку LRU-вытеснения и измерьте hit rate.
Связь с другими вопросами
| Вопрос | Тема |
|---|---|
| 7 | Как вы уменьшаете latency RAG-системы (время ответа)? |
| 9 | Как вы обновляете документы в существующей RAG-системе? |
| 207 | Что такое semantic caching и как его использовать в RAG? |
| 210 | Как устроена архитектура агентной RAG-системы? |
| 205 | Какие существуют стратегии кэширования в LLM-инференсе? |
| 206 | Как работает PagedAttention и чем он отличается от стандартного attention? |
Навигация
- Предыдущий: 207
- Следующий: 209
- Индекс: 00. Индекс разборов