Grimoire
Sheet
pathinference/decoding
difficultyadvanced
timeO(γ·n)
described2022
revised6d ago

Speculative Decoding

A small model guesses γ tokens ahead; the large model checks all of them in a single forward pass. The accept/reject rule is chosen so the output distribution is exactly unchanged.

Theory

Verifying γ\gamma tokens costs one forward pass of the target model, because the pass is memory-bound and processing γ\gamma positions instead of one is nearly free. The saving is real only if the draft is usually right.

Modified rejection sampling makes the scheme exact. Accept a drafted token with probability min(1,p/q)\min(1, p/q); on rejection, resample from the normalised positive part of the difference.

accept x with probability min ⁣(1,p(x)q(x))otherwise xnorm(max(0,pq))\begin{aligned} &\text{accept } x \text{ with probability } \min\!\left(1, \tfrac{p(x)}{q(x)}\right) \\[2pt] &\text{otherwise } x \sim \norm\bigl(\max(0,\, p - q)\bigr) \end{aligned}
eq. 1 — p target, q draftLeviathan et al. §2.1

With per-token acceptance rate α\alpha the expected number of tokens produced per target pass is a geometric sum, so the speedup saturates: past α0.8\alpha \approx 0.8 there is little to gain from drafting deeper.

E[tokens]=1αγ+11α\E[\text{tokens}] = \frac{1 - \alpha^{\gamma+1}}{1 - \alpha}
eq. 2
Implementation
python · torch ≥ 2.1
import torch
from torch import Tensor


def verify(draft_tokens: Tensor, p: Tensor, q: Tensor) -> tuple[Tensor, int]:
    """p, q: [gamma, V] target and draft distributions over the draft.
    Returns accepted tokens and the count accepted."""
    accepted = []
    for i, tok in enumerate(draft_tokens):
        ratio = (p[i, tok] / q[i, tok]).clamp(max=1.0)
        if torch.rand(()) < ratio:
            accepted.append(tok)
            continue
        # rejection: resample from the residual distribution
        residual = (p[i] - q[i]).clamp(min=0)
        residual = residual / residual.sum()
        return torch.tensor(accepted + [residual.multinomial(1).item()]), i

    # all accepted: take a free bonus token from the target
    return torch.cat([draft_tokens, p[-1].multinomial(1)]), len(draft_tokens)

The bonus token on full acceptance is what makes the expected-tokens formula reach γ+1\gamma + 1, and dropping it costs roughly one token of throughput per accepted block. The residual must be clamped before normalising; normalising a signed vector produces negative probabilities and a silent corruption of the output distribution.

Accept rate α
0.74
Tokens / pass
2.9
Latency
−58%
70B target, 7B draft, γ = 5, sampling at T = 0.7
Related
References
[1]Leviathan et al. — Fast Inference from Transformers via Speculative Decoding (2022)arXiv:2211.17192
[2]Chen et al. — Accelerating Large Language Model Decoding with Speculative Sampling (2023)arXiv:2302.01318
[3]Cai et al. — Medusa: Simple LLM Inference Acceleration Framework (2024)arXiv:2401.10774