Katkat

Implement and Benchmark Speculative Decoding

SpacexResearch Scientist · Technical Screen

Last updated

Implement autoregressive and speculative decoding algorithms with key-value cache management, along with a benchmarking harness to measure acceptance rates, latency, and throughput.

Problem Statement

In latency-critical inference pipelines, autoregressive decoding on large language models is frequently memory-bandwidth bound. Speculative decoding accelerates generation by using a small draft model to generate gamma candidate tokens sequentially, which the larger target model then verifies in a single parallel forward pass.

Implement greedy speculative decoding and an accompanying benchmarking routine.

Part 1: Speculative Decoding Function

Implement the function speculative_decode using greedy verification (where a draft token is accepted if and only if it matches the target model's greedy argmax prediction):

def speculative_decode(
    target_model,
    draft_model,
    input_ids: list[int],
    max_new_tokens: int,
    gamma: int,
    eos_token_id: int
) -> tuple[list[int], float]:
    """
    Returns:
        (generated_sequence, acceptance_rate)
    """

In each speculative step:

  1. The draft model generates K candidate tokens (K <= gamma), stopping early if it produces eos_token_id.
  2. The target model evaluates the candidates in a single batched pass.
  3. Verify candidates greedily: accept all draft tokens up to the first position i where draft_token[i] != target_token[i]. Append the target model's corrected token at position i.
  4. If all K tokens are accepted, append the target model's additional next-token prediction if max_new_tokens has not been reached.
  5. Roll back the draft and target models' internal KV caches to reflect only accepted tokens plus the correction.
  6. Stop if eos_token_id is emitted or max_new_tokens is reached.

Part 2: Benchmarking Harness

Implement benchmark_decoding to compare speculative decoding against standard autoregressive decoding:

def benchmark_decoding(
    target_model,
    draft_model,
    prompts: list[list[int]],
    max_new_tokens: int,
    gamma: int,
    eos_token_id: int,
    warmup_runs: int = 2,
    num_runs: int = 5
) -> dict:

The benchmark must include warm-up iterations, synchronize CUDA/accelerator timers where applicable, and return a dictionary containing:

  • mean_latency_speculative and mean_latency_autoregressive (in seconds)
  • speedup (autoregressive / speculative)
  • mean_acceptance_rate
  • tokens_per_second_speculative and tokens_per_second_autoregressive

Constraints

  • 1 <= len(input_ids) <= 2048
  • 1 <= gamma <= 16
  • 1 <= max_new_tokens <= 512
  • Both models expose forward(input_ids, use_cache=True, past_key_values=...) and a KV-cache truncation method truncate_cache(past_key_values, valid_length).

Hints

Hint 1

Remember that the target model generates logits for all candidate positions plus one additional token from the candidate prefix in a single forward evaluation.

Hint 2

When a draft token is rejected at index i, the target model's output at index i provides the ground-truth replacement token, meaning at least one token is always added per verification round.

Hint 3

Ensure cache truncation rolls back KV states to the exact length of the confirmed prefix before starting the next draft round to avoid stale attention states.

Follow-up questions

  • How does the verification condition change when extending greedy speculative decoding to stochastic sampling with temperature T > 0 using Leviathan et al.'s rejection sampling scheme?
  • Under what draft acceptance rate alpha and cost ratio (latency of draft vs. target model forward pass) does speculative decoding actually slow down wall-clock inference?
  • How can you use speculative execution with tree-based drafts (such as Medusa or Speculative Streaming) rather than a linear sequence of gamma tokens?