English translation is not available yet. Showing Russian content.
Что такое chunked prefill и зачем он нужен?
Краткий тезис
prefill|Chunked prefill — это техника оптимизации инференса LLM, при которой длинный входной промпт разбивается на несколько частей (чанков), и этапы prefill (обработка промпта) и decode (генерация токенов) чередуются. Основная цель — снизить TTFT (Time-to-First-Token) для сверхдлинных контекстов (например, 100k токенов) ценой небольшого уменьшения throughput. Техника особенно актуальна в RAG и агентных системах, где промпты часто превышают 10k токенов из-за вставки документов или истории диалога.
1. Проблема: длинные промпты и высокий TTFT
При инференсе LLM с длинным промптом (например, 50k–100k токенов) стандартный подход — выполнить prefill (однопроходное вычисление всех ключей и значений для промпта) и только потом начать decode (авторегрессивную генерацию). Prefill для такого объёма может занимать секунды, из-за чего первый токен приходит с большой задержкой. Это критично для интерактивных сценариев: чат-боты, агенты, RAG с подгрузкой документов.
Ключевые термины
- Prefill — этап, на котором модель обрабатывает все токены промпта]] параллельно (за один forward pass), вычисляя скрытые состояния и кэш KV (Key-Value).
- Decode — этап авторегрессивной генерации: на каждом шаге подаётся один новый токен, и модель обновляет KV-кэш.
- TTFT (Time-to-First-Token) — время от отправки запроса до получения первого сгенерированного токена.
- Throughput — количество запросов (или токенов), обрабатываемых в единицу времени.
| Метрика | Без chunked prefill (длинный промпт) | С chunked prefill |
|---|---|---|
| TTFT | Высокий (секунды) | Ниже (доли секунды) |
| Throughput | Высокий (один большой prefill) | Немного ниже (дополнительные overhead) |
| Задержка на токен | Низкая после prefill | Может быть чуть выше из-за фрагментации |
2. Как работает обычный prefill (без чанков)
В стандартном инференсе:
- Весь промпт длины
Lподаётся на вход модели за один forward pass. - Модель вычисляет KV-кэш для всех
Lтокенов. - Начинается decode: генерация токенов по одному, используя готовый KV-кэш.
Недостаток пока идёт prefill, пользователь ждёт — первый токен появляется только после полной обработки промпта. Для L=100k это может быть 2–5 секунд даже на мощном GPU.
3. Идея chunked prefill
Chunked prefill разбивает промпт на N чанков (например, по 4k токенов). Вместо того чтобы ждать окончания prefill всего промпта, система:
- Выполняет prefill первого чанка.
- Сразу начинает decode — генерирует несколько токенов, используя частичный KV-кэш.
- Параллельно (или в промежутках) обрабатывает prefill следующего чанка, расширяя KV-кэш.
- Повторяет, пока не обработает весь промпт.
Таким образом, decode стартует раньше, и первый токен приходит быстрее. При этом для корректности генерации необходимо, чтобы decode не «забегал вперёд» — обычно decode выполняется только для тех токенов, которые уже имеют полный KV-кэш для предыдущих частей промпта.
Важно chunked prefill не меняет семантику генерации — модель всё равно «видит» весь промпт, но частично в момент decode. Это достигается за счёт того, что KV-кэш от предыдущих чанков уже доступен.
4. Зачем нужен chunked prefill: основные сценарии
4.1. RAG с огромными документами
В RAG-системе промпт часто содержит десятки тысяч токенов (документы, чанки). Chunked prefill позволяет начать генерацию ответа, не дожидаясь полной загрузки всех документов. Это особенно полезно при стриминге ответа пользователю.
4.2. Агентные системы с длинной историей
Агенты (например, на базе ReAct или Plan-and-Execute) накапливают историю шагов, которая может достигать 50k+ токенов. Chunked prefill снижает задержку первого токена на каждом шаге агента.
4.3. Интерактивные чат-боты
Если пользователь вставил большой текст (код, документ) в диалог, chunked prefill ускоряет начало ответа.
5. Механизм чередования prefill и decode
Рассмотрим псевдокод для chunked prefill:
def chunked_prefill(prompt, model, chunk_size=4096):
# Разбиваем промпт на чанки
chunks = [prompt[i:i+chunk_size] for i in range(0, len(prompt), chunk_size)]
kv_cache = None
generated_tokens = []
for idx, chunk in enumerate(chunks):
# Prefill текущего чанка
kv_cache = model.prefill(chunk, kv_cache=kv_cache)
# Если это не последний чанк, делаем несколько decode-шагов
if idx < len(chunks) - 1:
# Генерируем, например, 1 токен (или больше — зависит от стратегии)
token = model.decode(kv_cache)
generated_tokens.append(token)
# Обновляем kv_cache с учётом сгенерированного токена
kv_cache = model.update_cache(token, kv_cache)
# После обработки всех чанков — обычный decode до конца
while not eos:
token = model.decode(kv_cache)
generated_tokens.append(token)
kv_cache = model.update_cache(token, kv_cache)
return generated_tokens
Ключевой момент decode во время prefill возможен только потому, что модель уже имеет KV-кэш для обработанных чанков. Генерация идёт «впереди» полного промпта, но это допустимо, если мы не требуем, чтобы модель «увидела» весь контекст перед первым токеном. На практике это не влияет на качество для большинства задач, так как первые токены обычно определяются началом промпта.
6. Trade-off: снижение throughput
Chunked prefill уменьшает TTFT, но платит за это:
- Дополнительные вычисления prefill чанков выполняется не одним большим forward pass, а несколькими меньшими. Это может увеличить общее время обработки запроса (latency) из-за накладных расходов на запуск ядер GPU.
- Фрагментация KV-кэша при чередовании prefill и decode кэш может стать менее эффективным для batch-обработки.
- Снижение throughput система обрабатывает меньше запросов в секунду, так как каждый запрос требует большего числа мелких операций.
| Аспект | Без chunked prefill | С chunked prefill |
|---|---|---|
| TTFT | ~2-5 с (100k токенов) | ~0.5-1 с |
| Общее время генерации | ~3-6 с | ~3.5-7 с |
| Throughput (запросов/с) | 10 | 8 |
Когда оправдано когда TTFT критичен (интерактивные сценарии), а throughput не является узким местом. Для batch-обработки (offline) chunked prefill обычно не нужен.
7. Сравнение с другими техниками снижения TTFT
| Техника | Механизм | Влияние на TTFT | Влияние на throughput |
|---|---|---|---|
| Chunked prefill | Чередование prefill/decode | Снижает | Небольшое снижение |
| Speculative decoding | Генерация черновиков малой моделью | Снижает (косвенно) | Повышает |
| Streaming LLM | Окно внимания + эвикшн | Снижает для длинных контекстов | Повышает |
| Prefix caching | Кэширование KV для общих префиксов | Снижает (при совпадении) | Повышает |
Chunked prefill часто комбинируют с prefix caching: если часть промпта повторяется (например, системный промпт), её prefill можно выполнить один раз.
8. Реализация в современных инференс-движках
Chunked prefill поддерживается в:
- vLLM — опция
--enable-chunked-prefill(или--max-num-batched-tokensдля управления размером чанка). - TensorRT-LLM — встроенная поддержка через
gptManagerс параметромmaxTokensInPagedKvCache. - Hugging Face TGI — использует похожий механизм (continuous batching).
Пример конфигурации для vLLM:
from vllm import LLM, SamplingParams
llm = LLM(
model="meta-llama/Llama-2-7b-hf",
enable_chunked_prefill=True,
max_num_batched_tokens=4096, # размер чанка
)
9. Когда НЕ стоит использовать chunked prefill
- Короткие промпты (< 1k токенов): overhead от разбиения превышает выигрыш.
- Offline batch-обработка TTFT не важен, важнее throughput.
- Сценарии, где первый токен не критичен (например, генерация отчётов).
- Модели с очень малым контекстным окном (например, 2k токенов) — разбивать нечего.
10. Связь с Agentic RAG
В агентных RAG-системах промпт часто состоит из:
- Системного промпта (фиксированный, ~1k токенов)
- Истории диалога (растёт с каждым шагом)
- Извлечённых документов (чанки, до 50k токенов)
Chunked prefill позволяет агенту начать «думать» (генерировать первый токен рассуждения), не дожидаясь полной загрузки всех документов. Это ускоряет каждый шаг агента, что критично для многошаговых сценариев (например, ReAct с 5+ шагами).
Пет-проект для закрепления
Задача Реализовать симуляцию chunked prefill на небольшой LLM (например, GPT-2) и сравнить TTFT с обычным prefill.
Инструменты Python, Hugging Face Transformers, CUDA (опционально).
Шаги:
- Загрузите модель GPT-2 (или TinyLlama).
- Подготовьте длинный промпт (10k–20k токенов) — можно сгенерировать повторяющимся текстом.
- Реализуйте обычный prefill: замерьте время до первого токена (TTFT).
- Реализуйте chunked prefill: разбейте промпт на чанки по 2k токенов, чередуйте prefill и decode (по 1 токену между чанками). Замерьте TTFT.
- Сравните результаты. Постройте график зависимости TTFT от размера чанка.
Ожидаемый результат Вы увидите, что chunked prefill даёт меньший TTFT (например, 0.3 с против 1.2 с), но общее время генерации может немного вырасти. Вы также заметите, что при слишком маленьких чанках overhead растёт.
Дополнительно Попробуйте разные размеры чанков (512, 1024, 2048, 4096) и запишите throughput.
Связь с другими вопросами
| Вопрос | Тема |
|---|---|
| 445 | Что такое speculative decoding и как он ускоряет инференс? |
| 447 | Как работает prefix caching в LLM-инференсе? |
| 448 | Какие стратегии управления KV-кэшем вы знаете? |
| 450 | Как оптимизировать latency для агентных RAG-систем? |
| 430 | Что такое continuous batching и зачем он нужен? |
| 410 | Как вы оцениваете качество RAG-системы? (TTFT как метрика) |
Навигация
- Предыдущий: 445
- Следующий: 447
- Индекс: 00. Индекс разборов