Skip to content
← Speculative & Constrained Decoding / 03
easyNewOpenAIAnthropicNVIDIA

Implement speculative-decoding verification for temperature zero. How do you handle the bonus token and the rejected drafts' KV?

At temperature zero, verification is a prefix match, and the whole implementation fits in a dozen vectorized lines. Interviewers grade the three details around it: the bonus token, the KV slots to roll back, and why the output can still differ from plain greedy.

Updated Oct 2026 · Worked answers on inference mechanisms, with calculations, implementation details, and follow-up questions.

TL;DR: Run the target once over the last accepted token plus the γ\gamma drafts, take its argmax at all γ+1\gamma + 1 positions, accept the longest prefix where draft equals argmax, and emit one more token from the target: the correction at the first mismatch or the bonus after a full match. Every step emits between 1 and γ+1\gamma + 1 tokens, 3.36 on average at α=0.8,γ=4\alpha = 0.8, \gamma = 4, and the KV written for rejected drafts must be discarded.

How to approach it

Draw the verify pass before writing code: which tokens go in, which logit rows come out, and which row decides what. Then ask two questions that change the implementation: is the batch ragged (different draft counts per request, which happens with n-gram drafters that sometimes find nothing), and must the engine stop on EOS inside an accepted run? Both turn a one-liner into a batched kernel with masks.

A strong answer

The verify forward pass takes γ+1\gamma + 1 input tokens per request: the last committed token t0t_0 and drafts d1,…,dγd_1, \dots, d_\gamma. Causal attention means the logit row at input ii predicts the token after it, so the target produces its own argmax a1,…,aγ+1a_1, \dots, a_{\gamma+1} in one pass. Draft did_i is correct exactly when di=aid_i = a_i, and a mismatch at position ii invalidates everything after it, because di+1d_{i+1} was conditioned on the wrong did_i.

Position12345
draft did_iabcd
target argmax aia_iabxyz
outcomeacceptacceptreject, emit xdiscarddiscard

That step emits three tokens: a, b and the correction x. If all four drafts had matched, the row at position 5 would still be a valid target prediction conditioned on a fully correct prefix, which is the bonus token, for γ+1=5\gamma + 1 = 5 tokens. One observation makes the code short: the accepted drafts equal the target's argmax, so the emitted tokens are always a1,…,an+1a_1, \dots, a_{n+1} where nn is the length of the matching prefix.

import numpy as np

def verify_greedy(draft, target_argmax, num_draft, eos_id=None):
    """draft: [B, G] proposed ids (padded), target_argmax: [B, G+1] target's
    argmax at each verified position, num_draft: [B] real drafts per row.
    Returns (tokens [B, G+1], n_emit [B]); row b emits tokens[b, :n_emit[b]]."""
    B, G = draft.shape
    valid = np.arange(G)[None, :] < num_draft[:, None]
    match = (draft == target_argmax[:, :G]) & valid
    n_acc = np.cumprod(match, axis=1).sum(axis=1)      # leading run of matches
    n_emit = n_acc + 1                                  # correction or bonus
    tokens = target_argmax.copy()     # accepted drafts equal the target's argmax
    if eos_id is not None:                              # stop at the first EOS
        is_eos = (tokens == eos_id) & (np.arange(G + 1)[None, :] < n_emit[:, None])
        first = np.where(is_eos.any(axis=1), is_eos.argmax(axis=1) + 1, G + 1)
        n_emit = np.minimum(n_emit, first)
    return tokens, n_emit

Tested cases: a mismatch at position 3 emits 3 tokens; a full match emits 5; a mismatch at position 1 emits only the correction; a row padded to 2 real drafts that both match emits 3, with the bonus read from position 3 rather than 5; an EOS inside the accepted run truncates there; and a row with zero drafts degrades to ordinary decoding, one token. A 200,000-row simulation at per-position match probability 0.8 averaged 3.360 tokens per step, against the closed form:

E[tokens] = (1 - alpha^(gamma+1)) / (1 - alpha)
alpha 0.8, gamma 4:  (1 - 0.8^5) / 0.2 = (1 - 0.32768) / 0.2 = 3.36
alpha 0.6, gamma 4:  (1 - 0.6^5) / 0.4 = 2.31
alpha 0.9, gamma 4:  (1 - 0.9^5) / 0.1 = 4.10
bounds: 1 token (first draft wrong) to gamma + 1 = 5 tokens (all right)

Now the KV cache. The verify pass wrote KV for all five inputs at positions nn to n+4n+4. With two drafts accepted, the entries for t0t_0, a and b are correct; the entries for c and d were computed from a wrong prefix and must not be read again. Nothing needs zeroing: the engine rolls the request's computed-token count back to n+3n + 3, and the next step's first input (the correction x) overwrites the slot c occupied. Blocks allocated for the speculative tail are kept for the next step rather than freed and reallocated.

When does the "lossless" claim fail at temperature zero? The guarantee is that the output is the target's greedy continuation as computed by the verify pass. That pass runs a different shape (five query tokens per request, a different batch composition) than plain decode, so kernels can pick different reduction orders. On a near-tie between two logits, the argmax can flip. Spec-on and spec-off greedy outputs therefore agree on almost every token but not on every one unless the engine is batch-invariant, and a regression test that diffs them bit for bit will flake.

What interviewers probe next

"Now temperature is 0.7." Replace the equality test with acceptance probability min⁡(1,p/q)\min(1, p/q) and resample rejections from the normalized (p−q)+(p - q)_+; with a greedy draft, qq is one-hot and the acceptance probability is just p(di)p(d_i).

"Why not stop the verify pass at the first mismatch?" All positions are computed in one parallel pass, so there is nothing to stop; the cost is the same whether one draft or all four survive.

"Where does this run, CPU or GPU?" On the GPU, as a fused kernel over the batch, because copying B×(γ+1)B \times (\gamma + 1) logit rows to the host would add a sync and megabytes of traffic per step.

How it's scored

Weak: emits only accepted drafts and loses the extra target token, or checks each draft against the target's prediction from the previous step.

Hire: gets the off-by-one between input positions and logit rows right, emits the correction or the bonus, and handles ragged draft counts.

Strong Hire: explains the KV rollback without zeroing, handles EOS inside the accepted run, and notes that greedy spec-on and spec-off outputs can differ on near-ties without batch-invariant kernels.

Common mistakes

  • Accepting every draft that matches, not just the leading run, which commits tokens conditioned on a rejected one.
  • Reading the bonus token from row γ+1\gamma + 1 for a request that only proposed two drafts.
  • Freeing and reallocating KV blocks after each rejection instead of moving the computed-token pointer.
  • Counting acceptance as "tokens per step" without the bonus, which understates mean accepted length by exactly one.
You just read a free answer. Every topic opens its first 5 answers to anyone. Sign in and the count per topic goes to 10, and your progress through each topic is saved.Google sign-in · free · no payment details
HOW DID IT GO?
0
READING SIGNED OUT

Signing in doubles your free answers, from 5 to 10 per topic, and the site starts remembering you: mastery per topic, bookmarks, and a next-focus recommendation. Free, no card.

Sign in free
UP NEXT ON YOUR JOURNEY
DISCUSSION · 0

Nobody has commented on this answer. Add a correction or another way to work it.