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 (без чанков)

В стандартном инференсе:

  1. Весь промпт длины L подаётся на вход модели за один forward pass.
  2. Модель вычисляет KV-кэш для всех L токенов.
  3. Начинается decode: генерация токенов по одному, используя готовый KV-кэш.

Недостаток пока идёт prefill, пользователь ждёт — первый токен появляется только после полной обработки промпта. Для L=100k это может быть 2–5 секунд даже на мощном GPU.


3. Идея chunked prefill

Chunked prefill разбивает промпт на N чанков (например, по 4k токенов). Вместо того чтобы ждать окончания prefill всего промпта, система:

  1. Выполняет prefill первого чанка.
  2. Сразу начинает decode — генерирует несколько токенов, используя частичный KV-кэш.
  3. Параллельно (или в промежутках) обрабатывает prefill следующего чанка, расширяя KV-кэш.
  4. Повторяет, пока не обработает весь промпт.

Таким образом, 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 (запросов/с)108

Когда оправдано когда 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 (опционально).

Шаги:

  1. Загрузите модель GPT-2 (или TinyLlama).
  2. Подготовьте длинный промпт (10k–20k токенов) — можно сгенерировать повторяющимся текстом.
  3. Реализуйте обычный prefill: замерьте время до первого токена (TTFT).
  4. Реализуйте chunked prefill: разбейте промпт на чанки по 2k токенов, чередуйте prefill и decode (по 1 токену между чанками). Замерьте TTFT.
  5. Сравните результаты. Постройте график зависимости 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 как метрика)

Навигация