Главная / Статьи

Спекулятивная декодировка, ранний выход и квантование: более быстрая декодировка больших языковых моделей

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

Издатель: REACTAPP.TOP8 мин чтения1877 слов

Источники: **Foundations of LLM Inference Optimization: Speculative Decoding and Early Exit | Part 3D**

Интерпретация LLM происходит в два этапа. Этап предзаполнения обрабатывает весь запрос параллельно и может удерживать GPU в активном состоянии. Этап декодирования выдает по одному токену за раз, причем каждый токен зависит от предыдущего, поэтому оборудование в основном находится в состоянии ожидания. Именно эта последовательная зависимость делает задержку в интерактивном режиме высокой, даже если с точки зрения расчётов количество операций FLOPs кажется достаточным.

Prefill phase:
  Input: [token_1, token_2, ..., token_512]  → all 512 tokens processed in parallel
  Matrix shape: [batch, 512, 4096]
  GPU utilization: high — large matrix, full Tensor Core throughput

Decode phase (one step):
  Input: [token_513]  → one token processed
  Matrix shape: [batch, 1, 4096]
  GPU utilization: low - tiny matrix, most CUDA cores idle

Почему декодирование находится на критическом пути

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

One attention head, decode step:

Query Q: [8, 1, 128]   →  8 × 128 = 1,024 elements
Keys  K: [8, 2048, 128] →  8 × 2048 × 128 = 2M elements
Values V: [8, 2048, 128] →  same

Matrix multiply (QKᵀ) per head:
  Shape: [8, 1, 128] × [8, 128, 2048] → [8, 1, 2048]
  FLOPs: 8 × 1 × 128 × 2048 ≈ 2.1M FLOPs per head

For 32 heads: ≈ 67M FLOPs per layer
For 32 layers: ≈ 2.1B FLOPs total per decode step

A100 peak at BF16: ~312 TFLOPS = 312 × 10¹² FLOPs/sec

Time to execute 2.1B FLOPs at 100% utilization:
  2.1 × 10⁹ / 312 × 10¹² ≈ 0.0067 ms

Actual observed decode latency per step: ~10–30ms

Effective compute utilization: < 0.1%

Эффективная загрузка менее 0,1% означает, что процесс декодирования ограничивается объемом данных, проходящих через память, и количеством выполненных шагов, а не собственно вычислительной мощностью. Существует два способа преодолеть это ограничение: получать больше принятых токенов за один дорогостоящий шаг или выполнять меньше работы за шаг. Спекулятивное декодирование использует первый подход, а раннее завершение — второй.

Спекулятивное декодирование: предварительная разработка, затем проверка

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

Without speculative decoding:
  Generate 5 tokens: 5 × 30ms = 150ms

With speculative decoding (K=4):
  Draft 4 tokens: 4 × 1.5ms = 6ms
  1 target verification pass: ~35ms  (slightly longer than decode,
                                       processes K+1=5 positions)
  Expected accepted tokens per round:
    (1 - 0.80^5) / (1 - 0.80) ≈ 3.36 tokens
  Time per round: 6ms + 35ms = 41ms
  Time per token: 41ms / 3.36 ≈ 12.2ms
Speedup: 30ms → 12.2ms ≈ 2.5×

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

import torch
import torch.nn.functional as F

def speculative_decode(target_model, draft_model, input_ids,
                       max_new_tokens, K=4, temperature=1.0):
    """
    Conceptual speculative decoding loop.
    Real implementations handle KV cache management across both models.
    """
    generated = input_ids.clone()
    while generated.shape[1] - input_ids.shape[1] < max_new_tokens:
        # --- Draft phase ---
        draft_tokens = []
        draft_probs = []
        draft_input = generated.clone()
        for _ in range(K):
            with torch.no_grad():
                draft_logits = draft_model(draft_input).logits[:, -1, :]
            q = F.softmax(draft_logits / temperature, dim=-1)
            token = torch.multinomial(q, num_samples=1)
            draft_tokens.append(token)
            draft_probs.append(q)
            draft_input = torch.cat([draft_input, token], dim=1)
        # --- Verify phase: one target forward pass over all K+1 positions ---
        verify_input = torch.cat([generated] + draft_tokens, dim=1)
        with torch.no_grad():
            target_logits = target_model(verify_input).logits
        # target_logits[:, -K-1:, :] covers all K draft positions + bonus
        # --- Accept/reject ---
        accepted = 0
        for i in range(K):
            p = F.softmax(target_logits[:, -(K+1)+i, :] / temperature, dim=-1)
            q = draft_probs[i]
            token = draft_tokens[i]
            # Acceptance probability
            accept_prob = torch.min(
                torch.ones_like(p.gather(1, token)),
                p.gather(1, token) / (q.gather(1, token) + 1e-9)
            )
            if torch.rand(1) < accept_prob:
                generated = torch.cat([generated, token], dim=1)
                accepted += 1
            else:
                # Sample corrected token and stop this round
                corrected_dist = F.relu(p - q)
                corrected_dist = corrected_dist / corrected_dist.sum(dim=-1, keepdim=True)
                corrected_token = torch.multinomial(corrected_dist, num_samples=1)
                generated = torch.cat([generated, corrected_token], dim=1)
                break
        else:
            # All K accepted - take bonus token
            bonus_logits = target_logits[:, -1, :]
            p_bonus = F.softmax(bonus_logits / temperature, dim=-1)
            bonus_token = torch.multinomial(p_bonus, num_samples=1)
            generated = torch.cat([generated, bonus_token], dim=1)
    return generated

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

Когда черновики отклоняются

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

Быстрая проверка с округленными цифрами: предположим, что целевой шаг стоит 10 мс, а черновик предлагает 5 токенов, из которых в среднем принимается около 3 (60%). Стоимость за принятый токен снижается по сравнению с шагами по одному токену, даже с учетом дополнительных затрат черновика, при условии стабильного уровня принятия. Если уровень принятия падает до примерно одного токена, схема становится неэффективной. Именно из-за такой чувствительности панели управления превосходят анекдотические примеры.

Ранний выход: пропускайте глубокие уровни для простых токенов

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

Layer distribution of exits:Exit at layers 1–8  (very easy tokens like punctuation, articles):  15%
Exit at layers 9–16 (medium tokens, common continuations):           35%
Exit at layers 17–24 (harder tokens, named entities, numbers):       30%
Exit at layers 25–32 (full computation required):                    20%Weighted average layers executed:
  0.15 × 6 + 0.35 × 12 + 0.30 × 20 + 0.20 × 32
  = 0.90 + 4.20 + 6.00 + 6.40
  = 17.5 layers averageSpeedup vs always running 32 layers:
  32 / 17.5 ≈ 1.83×

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

Сочетание с квантизацией, пакетной обработкой и кэшем KV

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

Combined stack example:

Target: 7B model, BF16, FlashAttention, PagedAttention
  → Model: ~14 GB, memory-efficient attention, paged KV cache

Draft: 70M model, INT4, FlashAttention
  → Model: ~35 MB, near-zero memory overhead

Speculative decode:
  → with K=4, α=0.80
  → ~2.5× token generation speedup on long outputs

Full stack speedup vs FP32 no-optimization baseline:
  - Quantization: 2–2.3× throughput
  - FlashAttention: 20–40% attention latency reduction
  - Speculative decoding: 2–2.5× decode speedup (on eligible requests)

Combined: 5–8× improvement in end-to-end tokens/second

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

Когда каждая техника приносит пользу

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

Конкретный пример: целевая модель объемом 7 миллиардов параметров, обеспечивающая чат с ответами длиной от 50 до 150 токенов. Заполнение системного промпта объемом 2 тысячи токенов требует много ресурсов, но происходит редко за один ход, тогда как шаги декодирования являются основной причиной задержек, которые замечают пользователи. Сокращение количества шагов декодирования с помощью спекулятивного принятия решений или уменьшение объема работы за шаг через досрочный выход — вот что на самом деле влияет на производительность.

Имеет ли это значение для крошечных моделей, работающих непосредственно на устройстве? Обычно меньше, чем для крупных моделей. Может ли прототип работать на процессоре? Иногда; необходимо это проверить. По порядку приоритетов сначала нужно устранить проблемы с группировкой операций и кэшированием, затем внедрить спекулятивное декодирование, и только потом — досрочный выход, если ваша технологическая стек поддерживает его.

Внедрение в производственную среду инференса больших языковых моделей

  • Сначала определите базовые показатели задержки p95 и качества работы до внесения каких-либо изменений.
  • Размещайте проектную модель и целевую модель на одном узле. Использование проектов на разных хостах приводит к сетевым колебаниям, которые могут свести на нет все преимущества, а две модели вместе с кэшем KV могут исчерпать память устройства, которое ранее комфортно вмещало одну модель.
  • Фиксируйте гистограммы принятия, среднюю длину принятых ответов, показатели оценки качества, уровень использования GPU и задержку p95.
  • Следите за балансом работы CPU и GPU при выполнении проектов на разных устройствах.
  • Оценивайте качество вариантов A/B для тестов на фактические данные. Перед глобальным внедрением протестируйте фиксированные промпты в тестах на факты, программирование и отклонение запросов, сравнивая показатели идентичности токенов при настройке спекулятивной декодировки для точного совпадения распределения. Для раннего анализа отслеживайте процент побед в задачах с оценкой качества и предпочтения людей, если таковые имеются.
  • Размещайте каждую оптимизацию за флагом функции с возможностью мгновенного возврата к стандартной декодировке, и отрепетируйте процедуру отката.
  • Повторно проверьте всё после изменения токенизатора или настроек тонкой настройки.

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

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

Оставшийся узкий место

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

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

Связанная литература

  • Инференс LLM от начала до конца: предзаполнение, декодирование, кэш KV и выборка: Пошаговое рассмотрение процесса токенизации в режиме потоковой обработки, причин, по которым предзаполнение ограничено вычислительными мощностями, а декодирование — пропускной способностью, а также того, как методы GQA, пагинация и спекуляция влияют на эффективность работы.
  • Кэш KV — это скрытая база данных, истощающая память GPU при декодировании в LLM: Сравнение предзаполнения и декодирования, математика работы кэша KV, методы GQA/MQA, PagedAttention, квантизация и механизмы освобождения памяти для производственной инференсной обработки.