The problem
Generating with a large model is slow because every token needs a full pass through it, and a pass is limited by reading memory. But scoring several tokens in one pass costs almost the same as scoring one. Speculative decoding uses this: a small, fast draft model proposes (gamma) tokens, and the large target model checks all of them in one pass. You will write the step that decides which proposals to keep, so that the result is distributed exactly as if the target had sampled alone (Leviathan et al., 2023).
The inputs:
draft: the proposed tokens;q, shape : the draft's distribution over the tokens at each proposed position;p, shape : the target's distribution at the same positions, plus one more after the last proposal.
The rule
For , with = draft[i]:
- draw
u = rng.random()and accept the token ifu < p[i, x] / q[i, x]: if the target likes it at least as much as the draft () it is always accepted; otherwise it is accepted with probability ; - if rejected, sample a replacement from the residual distribution , where divides by the total so that it sums to 1, and stop;
- if all proposals were accepted, sample one more token from
p[γ], which costs nothing extra.
Return the list of new tokens: the accepted ones, followed by the replacement or the extra token. It always has between 1 and tokens.
Example. , . Target =
[[0.2, 0.5, 0.3], [0.1, 0.1, 0.8], [0.3, 0.3, 0.4]], draft =[[0.5, 0.3, 0.2], [0.2, 0.2, 0.6]].All accepted:
draft = [1, 2]and the random numbers come out .
- Position 0, token 1: ; : accepted.
- Position 1, token 2: ; : accepted.
- All accepted: one more token from
p[2]=[0.3, 0.3, 0.4]with the next random number . The cumulative sums are , and falls in the first: token 0. The result is[1, 2, 0]: 3 tokens from one target pass.Rejection:
draft = [0, 2], same random numbers.
- Position 0, token 0: ; : rejected.
- Residual: , normalized . The next random number, , falls in the third interval (cumulative sums ): token 2.
- Stop. The result is
[2]: the second proposal is never looked at.
Why the result is exactly : the replacement comes only from tokens the target prefers to the draft, which are exactly the ones the draft under-proposes. The theory page checks it with numbers.
What to implement
speculative_step(p, q, draft, rng): return the list of new tokens. Sample from a distribution with sample(probs, rng), which is given: the inverse CDF (cumulative distribution function) method with one rng.random() call.
Requirements
- Use the random numbers exactly in this order (one per accept test, one per sample), so the tests can reproduce your choices.
u < p/qis the same as accepting with probability .