Skip to content

Add backend-agnostic speculative decoding to llm-runner (Part 1) - #284

Open
carinapeng wants to merge 3 commits into
apple:mainfrom
carinapeng:carina/spec-decode-generic
Open

carinapeng wants to merge 3 commits into
apple:mainfrom
carinapeng:carina/spec-decode-generic

Conversation

@carinapeng

Copy link
Copy Markdown
Contributor

Summary

Adds a pluggable, backend-agnostic speculative-decoding path to llm-runner, plus the framework it runs on. Draft and target engines sit behind a small protocol so different drafting mechanisms — a separate draft model, a model-free n-gram prompt-lookup drafter, or a future attached head / tree drafter — share one propose → verify → accept/rollback loop.

What's included

  • SpeculativeEngine — the propose/verify/accept/rollback surface a decoder needs from the draft and target engines. Draft and target may run on different backends, and the loop is testable against an in-memory double with no model assets. CoreAISequentialEngine conforms via three added primitives (advance, forwardWithPerPositionLogits, forwardPerPositionArgmax).
  • DrafterEngine — the seam behind which drafting mechanisms live: AutoregressiveDrafter (a separate draft model on any SpeculativeEngine) and a model-free PromptLookupDrafter (n-gram from the running context).
  • SpeculativeDecoder — proposes k tokens and verifies them in a single target pass. Greedy acceptance is token-for-token identical to the target; sampling uses the Chen et al. (2023) accept/reject scheme.
  • llm-runner flags--draft-model, --num-draft-tokens, --prompt-lookup. Draft and target are built via EngineFactory; the runner reports acceptance rate, tokens per target pass, and generation-only throughput.

Design

This PR intentionally general: DrafterFeatures and DraftProposal.parents already model attached-head (feature-injecting) and tree drafting, so a fused-target or attached-head drafter plugs into the same decoder without changing the verify/accept path.

Scope / follow-ups

  • Runs on the sequential engine in this PR. Additional engine backends conform to SpeculativeEngine by implementing the same primitives.
  • Concrete attached-head / tree drafters land in follow-ups.

Add advance, forwardWithPerPositionLogits, and forwardPerPositionArgmax:
prefill-without-logits plus single-pass per-position logits/argmax. These
are the propose/verify primitives a speculative decoder drives the target
and draft engines through; greedy uses the argmax fast path to avoid a
per-position vocab copy.
Introduce a pluggable speculative-decoding core:
- SpeculativeEngine: the propose/verify/accept/rollback surface a decoder
  needs from the draft and target engines, so they may run on different
  backends and the loop is testable against an in-memory double.
  CoreAISequentialEngine conforms.
- DrafterEngine: the seam behind which drafting mechanisms live, with an
  AutoregressiveDrafter (separate draft model) and a model-free
  PromptLookupDrafter (n-gram).
- SpeculativeDecoder: proposes k tokens and verifies them in one target
  pass; greedy acceptance is token-for-token identical to the target,
  sampling uses the Chen et al. (2023) accept/reject scheme.
- DraftProposal / DrafterFeatures / VerificationPolicy value types and
  SpeculativeMath selection primitives.
Add --draft-model, --num-draft-tokens, and --prompt-lookup. Draft and
target are built via EngineFactory and driven by SpeculativeDecoder; the
engine must conform to SpeculativeEngine (else a clear error). Report
acceptance rate, tokens per target pass, and generation-only throughput.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant