localaiforum
Войти
Руководство › Основы

Префилл и декодирование: как работает генерация

Нажали Enter — и генерация идёт в две фазы с совершенно разной физикой. Префилл обрабатывает весь ваш промпт одним параллельным проходом — много вычислений, быстро на токен, но вы ждёте всё это до первого слова. Декодирование затем выдаёт ответ по одному токену за раз — каждый шаг заново читает из памяти практически все веса модели, чтобы произвести один токен. «Первый токен медленный, потом стримит» — не причуда вашей установки; это и есть те две фазы.

Фаза 1: префилл — промпт

  • Все токены промпта проходят через модель одновременно: большие параллельные матричные умножения — ровно то, для чего созданы GPU. Утилизация высокая; токенов в секунду много.
  • Вы чувствуете это как время до первого токена (TTFT): тишину после нажатия Enter.
  • Проход также заполняет KV-кэш ключами/значениями каждого токена промпта — черновиком, который будет читать декодирование.
  • Длиннее промпт — линейно длиннее TTFT: большие системные промпты, документы RAG и история чата префиллятся каждый раз, если только движок не кэширует префиксы.

Фаза 2: декодирование — ответ

  • Один токен на прямой проход. Этому проходу нужны все веса; выдача одного токена означает стриминг всей модели из VRAM.
  • Потолок — пропускная способность памяти, а не вычисления: грубо decode tok/s ≈ эффективная пропускная способность ÷ байты весов. 8 GB весов на карте с ~400 GB/s эффективных ≈ потолок 50 tok/s.
  • Вот почему квантизация ускоряет декодирование: меньше бит на вес — меньше байтов на стриминг каждый шаг (см. скорость).

Почему асимметрия: префилл обрабатывает тысячи токенов на одно чтение весов (огромные матрицы, высокая арифметическая интенсивность — ограничение по вычислениям), декодирование обрабатывает ровно один (каждый байт весов достаётся одному токену — ограничение по пропускной способности). Те же веса, противоположные узкие места.

total_time ≈ prompt_tokens / prefill_tok_s  +  output_tokens / decode_tok_s

Разбор на примере

prompt:  1,000 токенов    префилл 2,000 tok/s → 0.5 с   (TTFT)
output:    300 токенов    декодирование 40 tok/s → 7.5 с
итого ≈ 8.0 с — 94% реального времени приходится на декодирование
  • Первая полсекунды обработала 1,000 токенов; следующие 7.5 секунд производят 300. Та же модель, та же GPU, разница в 50× — другая фаза, а не другое старание.
  • 40 tok/s — это примерно 160 английских символов в секунду (≈4 символа/токен): быстрее, чем кто-либо печатает, поэтому стриминг читается комфортно (токены).
  • Те же 40 tok/s на корейском — примерно 40–60 символов/с: та же скорость токенов, меньше текста на токен.
  • Длина вывода — ручка на стороне декодирования: max_tokens ограничивает длительность генерации, стоп-последовательности завершают её раньше. Обе меняют реальное время линейно (параметры движка).
  • Стриминг меняет восприятие, а не физику: вы смотрите, как происходит декодирование, вместо ожидания — при тех же 40 tok/s.

Разбор на примере 2: случай RAG

prompt: 12,000 токенов (система + 20 найденных чанков)  префилл 1,500 tok/s → 8.0 с
output:   500 токенов                                    декодирование 40 tok/s → 12.5 с
итого ≈ 20.5 с — один только TTFT — 39% ожидания

Такова форма локальных сетапов, перегруженных ретривалом: жирные промпты означают, что модель «думает» секунды, прежде чем начать печатать. Лекарства, в порядке эффективности:

  • Извлекайте меньше — топ 3–5 чанков обычно бьют топ 20 (RAG).
  • Держите системный промпт стабильным, чтобы движок мог переиспользовать кэшированный префикс.
  • Подрезайте историю: старые реплики можно суммаризовать, а не проигрывать целиком.

KV-кэш: растущая черновая память

Без кэша генерация токена N пересчитывала бы внимание по всем N−1 более ранним токенам с нуля — каждый шаг повторяет идентичную работу, полная стоимость квадратична по длине. Вместо этого:

  1. Префилл вычисляет ключи и значения всех токенов промпта и сохраняет их один раз.
  2. Каждый шаг декодирования вычисляет query/key/value только нового токена и дописывает его K/V в кэш.
  3. Внимание нового токена — его запрос, скалярно умноженный на все сохранённые ключи.
  4. Один декодированный токен, по порядку: эмбеддинг, прогон слоёв с чтением кэша, проекция на словарь, сэмплирование — дописать его K/V, повторить.

Теперь каждый шаг декодирования линеен по длине контекста (чтение кэша) вместо квадратичного (пересчёт всего). Цена — сам кэш, который растёт с каждым токеном:

KV bytes ≈ 2 × layers × kv_heads × head_dim × bytes_per_value × context

модель класса 8B (32 слоя, 8 KV-голов, размер головы 128, FP16):
  2 × 32 × 8 × 128 × 2 B ≈ 128 KiB на токен
  → ~2.6 GB при 20k контекста, ~17 GB при 128k

Этот кэш конкурирует с весами за VRAM — чаты с длинным контекстом роняют машины, которые спокойно гоняют ту же модель с короткими промптами (KV-кэш, математика VRAM).

Батчинг меняет экономику

Читать 8 GB весов, чтобы выдать один токен, расточительно; то же чтение может обслуживать много последовательностей сразу. Соберите в батч 8 запросов — один стрим весов производит 8 токенов — и пропускная способность растёт почти линейно, пока не насытятся вычисления. В этом весь трюк обслуживания (API для обслуживания, параллелизм). В одиночку на ноутбуке вы платите полную цену за токен; серверы амортизируют её между одновременными пользователями.

Чтение метрик

Метр Фаза Главный фактор
TTFT префилл длина промпта, вычисления, попадания кэша префиксов
Скорость обработки промпта (tok/s) префилл вычисления, размер батча
Скорость генерации (tok/s) декодирование пропускная способность памяти, байты весов, батч
Пропускная способность (req/s) обе размер батча, переиспользование кэша префиксов, параллельные слоты
Память контекста обе KV-байты на токен, квантизация кэша

Мерьте, прежде чем крутить: каждый серьёзный движок пишет скорости prompt-eval и генерации на каждый запрос. Минута логов скажет, какая фаза съедает вашу задержку; угадывание — нет.

Настройка следует за фазой: более короткие промпты и кэширование префиксов чинят TTFT; квантизация и меньшие веса чинят декодирование (параметры движка, диагностика).

Связанные разделы: скорость · KV-кэш · параллелизм · токены