ealeng

Episode 56 of 56 · Speed & cost

Speculative decoding: a small model helps a big one

A small draft model guesses a few tokens, the big model checks them all in one pass. On a laptop, Qwen3-8B with Qwen3-0.6B drafting wrote replies and stories almost 1.5× as fast, though not always word for word the same. The same trick on the 4-bit model from chapter 29 made replies and stories slower at every setting, down to half speed.

Lab 12 · Inference & Serving13 min read
Video transcript8:56

0:00Cold open

0:01

I had a big AI model on my laptop write eight short stories.

0:05

Then I added a small helper model. Same eight prompts.

0:10

The stories came back almost one and a half times as fast.

0:14

And every word was still chosen by the big model. Though, as we’ll see, not always the same words.

0:22

Then I tried the same trick on the shrunk, four-bit model from part one.

0:26

The helper was shrunk too. Same prompts.

0:30

This time, it got slower at every setting I tried. At the worst, about half as fast.

0:37

So why did the same trick speed up one model, and slow down the other?

0:42Intro

0:43

Serving models, part seven: speculative decoding.

0:47Where we are

0:49

I’m Lea, an ML engineer at an outdoor gear shop.

0:53

In part one, we saw why a model on my laptop is slow.

0:57

We also shrunk it, storing each number in four bits instead of sixteen. That made it faster.

1:04

Today’s big model is Qwen three, with eight billion numbers inside. Its helper is a tiny model from the same family.

1:13

Everything here is real, timed runs on my laptop.

1:17

I gave them three tasks, with eight prompts each.

1:20

First, turning a support email into JSON: a fixed list of named fields.

1:26

Second, writing a reply to a customer. Third, a short story.

1:31

And I ran the whole experiment three times.

1:3501 · Decoding

1:36

First, how a model writes. One token at a time.

1:40

A token is a piece of text, about three quarters of a word.

1:45

Writing the answer this way, token by token, is called decoding.

1:50

For every single token, the big model reads all sixteen gigabytes of its numbers from memory.

1:57

That reading is the slow part. So plain decoding here runs at about thirty-two tokens a second.

2:0502 · The idea

2:06

Here’s the idea. Picture a junior writer and a senior editor.

2:11

The junior quickly drafts the next few words.

2:14

The editor reads them all in one go.

2:18

She keeps them, up to the first word she’d have written differently. Then she writes that word herself.

2:24

That’s speculative decoding. The junior is the draft model: small and quick.

2:30

The editor is the target model: the big one, whose answer we want.

2:35

Each round, the draft guesses a few tokens. How many is called k.

2:40

Here’s a real round, with k of four. The draft guessed four tokens.

2:45

The big model checked all four in one pass: one read through all its numbers.

2:51

It kept the first two: “reaching” and “out”. Then it wrote its own next token: “and”.

2:58

So that one pass gave three tokens instead of one.

3:0203 · Checking is cheap

3:04

But the big model still checks every guess. Doesn’t that cost as much as writing them?

3:10

I timed one pass of the big model. With one new token, it took thirty-six milliseconds.

3:17

With five tokens at once, it took forty-two. Only a little more.

3:22

Why? The pass reads all sixteen gigabytes once, either way. The extra math is small next to that.

3:30

And one step of the draft model takes just a few milliseconds.

3:34

That’s a tenth to a sixth of a big step. So guessing is cheap too.

3:4004 · The same answer?

3:41

The big model checks every token. And here, it always picks its most likely next word.

3:48

So in theory, the answer should be exactly what it writes alone. Is it?

3:53

First, plain decoding, run twice: identical every time, on all twenty-four prompts.

4:00

With the draft? The JSON tickets matched, eight of eight.

4:04

But only four of eight support replies matched, and three of eight stories.

4:10

Here’s one reply. The two versions start out word for word the same.

4:15

Then plain says the jacket is on its way. With the draft: in the process of being shipped.

4:21

Both are the big model’s own words.

4:25

My likely explanation: checking several tokens at once does the math in a slightly different order. When two words are nearly tied, the other one can win. I didn’t prove that.

4:3705 · Acceptance rate

4:39

How often does the draft guess right?

4:42

That’s the acceptance rate, called alpha: the chance that one guessed token is kept. I estimate it from how many tokens came from the draft.

4:51

For the JSON tickets, alpha was about one. The draft was almost never wrong.

4:57

Here’s a ticket, colored by who wrote each token. Green tokens are the draft’s guesses that were kept.

5:05

The format is fixed, so the small model guesses nearly all of it.

5:09

The purple ones are the big model’s own token, one per round. They aren’t misses.

5:15

Replies and stories are open-ended, so they’re harder to guess.

5:20

For replies, each guess had about a two-in-three chance of being right. For stories, three in five.

5:2806 · Longer guesses?

5:29

So should the draft guess more tokens each round?

5:33

A bigger k gives more tokens per pass when the guesses are right. When they’re wrong, more guesses are thrown away.

5:40

For each task, would you pick k of two, or k of six?

5:46

The JSON tickets: the longer the guess, the faster, in every run. At k of six, about one point six times as fast.

5:55

Replies: k of two beat k of six, in all three runs.

6:00

Stories, the same. One wrong guess throws away every guess after it. At three in five, all six guesses are right only about one time in twenty.

6:10

The laptop was busy with other work, so speeds varied from run to run. I show the middle of three runs, with the range below it.

6:2007 · The formula

6:21

There’s a formula for the expected speed-up.

6:25

It divides the tokens a round gives by what a round costs. Its prediction is now under each measurement.

6:33

For the JSON at k of two, it predicted two and a half times as fast. I measured one point four.

6:40

Why the gap? Mostly this: before writing anything, the model first reads the whole prompt.

6:47

Here, the JSON prompt was a long list of rules. Reading it took almost half of the plain time.

6:54

The formula counts writing only. And the draft can’t speed up the reading.

6:59

On stories, with short prompts, the formula came closer.

7:0508 · The 4-bit model

7:06

Now the four-bit model. Shrunk to four bits, the big model alone is about three times faster.

7:13

About a hundred and five tokens a second on a short test prompt, instead of thirty-two.

7:19

But with the draft, replies and stories got slower, at every value of k.

7:25

The JSON gained twelve percent at most.

7:28

Were the guesses worse? Only a little. Alpha dropped just slightly.

7:33

So I timed the big model’s pass again, at four bits.

7:38

Seven tokens at once took about two and a half times as long as one.

7:43

At full size, it was only about one point two times.

7:48

So at four bits, checking a long guess is no longer cheap.

7:52

And drafting isn’t cheap either: six draft steps took longer than one big step.

7:58

Together, that explains most of the slowdown, not all of it.

8:03

Even short guesses were ten to twenty percent slower, which these timings don’t explain.

8:10What I do now

8:11

So, what I do now.

8:13

A big model at full size, writing open text? I try a draft model from the same family.

8:20

It has to split text into the same tokens, or its guesses can’t be checked.

8:26

If four bits is good enough for the task, that alone was two to two and a half times as fast as the full-size model with a helper, on replies and stories.

8:36

Already shrunk to four bits? I measure first. Here, the draft made replies and stories slower.

8:43

And I compare the outputs, not only the speed.

8:47

That’s the end of this series. A small model guesses; a big one checks. Measure it on your own prompts.Lea: Thanks!

Key takeaways

  • ✓A model writes one token at a time, and for each one Qwen3-8B reads all 16.4 GB of its numbers. That read is the slow part: about 32 tokens/s here. Speculative decoding lets a small draft model (Qwen3-0.6B) guess k tokens, and the big model checks them all in one pass, keeping the guesses up to the first wrong one and adding one token of its own.
  • ✓Checking is cheap at full size: one pass took 36 ms for 1 new token and 42 ms for 5, because the 16.4 GB is read once either way. A draft step costs a tenth to a sixth of a big step. With k = 2, replies and stories came out about 1.46× as fast, ticket JSON 1.37× (median of 3 runs).
  • ✓The acceptance rate α is the chance one guess is kept: about 1.00 for ticket JSON, 0.64 for replies, 0.60 for stories. Longer guesses paid on JSON (1.63× at k = 6) but not on open text, where k = 2 beat k = 6 in every run: at α = 0.6, all six guesses are right only about 1 time in 20.
  • ✓In theory the output is exactly the big model's. Plain decoding run twice matched 24 of 24 prompts; with the draft (k = 2), JSON matched 8/8, replies 4/8, stories 3/8. Every word is still the big model's, but near-ties can flip, likely from doing the math in a different order. Compare outputs, not only speed.
  • ✓The formula (tokens per round ÷ cost of a round) predicted 2.5× for JSON at k = 2; it measured 1.4×. 44% of the plain time was reading the long rules prompt, which the formula leaves out and the draft can't speed up.
  • ✓At 4 bits the big model alone is about 3× faster (about 105 tokens/s), and the draft made replies and stories slower at every k, down to 0.47×. The guesses were nearly as good (α 0.58 vs 0.64 and 0.60), but a 7-token pass now took 2.5× a 1-token pass (1.2× at full size), and six draft steps cost more than one big step.

A big model on a laptop writes slowly: every token reads the whole model from memory. Speculative decoding lets a small model guess the next few tokens and the big model check them all at once. This chapter measures what that buys on three tasks, whether the answer really stays the same, what a simple formula predicts and misses, and why the same trick made a 4-bit model slower.

The source is Lab 12 (Inference & Serving), script 10_speculative.py with the formula in serving.py, recorded for the video on November 2, 2026: Qwen3-8B as the big model, Qwen3-0.6B drafting, on one MacBook Pro (Apple M4 Max, 48 GB), three tasks with 8 prompts each, greedy decoding, the whole experiment run 3 times. Two small scripts added for the video time one pass of the big model and record which tokens came from the draft. The laptop was running other work during the runs, so speeds varied; numbers below are the median of the 3 runs, with the range where it matters. The acceptance rates and outputs were the same in every run. The lab's README describes an earlier run with somewhat different numbers; see "Also in the lab". The 4-bit models are the ones from chapter 29.

One token at a time

A model writes its answer one token (a piece of text, about ¾ of a word) at a time. That's decoding. For every token, Qwen3-8B reads all 16.4 GB of its numbers from memory, and that read, not the arithmetic, is the slow part (chapter 29 measured it). Plain decoding here ran at about 32 tokens/s.

The three tasks, all for the made-up outdoor gear shop, Lantern Outfitters:

Task Prompt Answer
Ticket JSON a support email after a ~690-token list of rules (from Lab 11) 4 named fields, about 38 tokens (60 max)
Support reply a customer email and a one-line instruction up to 128 tokens
Short story "Write a short story about …" up to 128 tokens

A junior writes, an editor checks

Picture a junior writer and a senior editor. The junior quickly drafts the next few words. The editor reads them all in one go, keeps them up to the first word she'd have written differently, and writes that word herself.

  • The draft model is the junior: small and quick (Qwen3-0.6B, 1.2 GB).
  • The target model is the editor: the big one, whose answer we want (Qwen3-8B).
  • Each round, the draft guesses k tokens. The target checks all of them in one pass (one read through all its numbers), keeps the guesses up to the first wrong one, and adds one token of its own.

So a round always gives at least one token (the target's own) and at most k + 1. Step through a real round:

Try it · One round, k = 4Lab 12 · script 10 · a real support reply, full size

The draft (Qwen3-0.6B) guesses the next 4 tokens, one quick step each.

…Jack, Thank you for reaching out. I
Guessed
4
Kept
?
Tokens from 1 pass
?

The first rejected guess ends the round: “.” and “ I” are thrown away, even if “ I” would have been right later. The two thrown-away guesses were rebuilt afterwards by running the draft alone from the same text.

Why checking is cheap

Doesn't checking every guess cost as much as writing them? The video timed one pass of the big model, with the prompt already read:

Pass over 1 new token 2 3 5 (k = 4) 7 (k = 6)
Full size, ms 36.1 34.1 39.2 42.3 43.5

Five tokens cost 42 ms against 36 for one: the pass reads the 16.4 GB once either way, and the extra arithmetic is small next to that. (Two tokens were slightly faster than one in every run, likely a different math routine for a single token; that's a guess.) And one step of the draft took 5.7 ms: a tenth to a sixth of a big step, depending on how you measure it (a tenth from the two models' plain decode speeds, 315 vs 32 tokens/s, which is what the lab's formula uses).

The same answer?

The big model checks every token, and with greedy decoding it always picks its most likely next one. So, in theory, the answer is exactly what it writes alone. Is it?

Ticket JSON Support reply Short story
Plain decoding, run twice: identical 8 of 8 8 of 8 8 of 8
With the draft (k = 2): identical to plain 8 of 8 4 of 8 3 of 8

One reply starts out word for word the same for 48 tokens. Then plain decoding says the jacket "is on its way"; with the draft, it's "in the process of being shipped". Both are the big model's own words, and both read fine.

The likely explanation, not proven: checking several tokens at once does the math in a slightly different order, and the rounding differs. When two words are nearly tied, the other one can win, and the text goes its own way from there. The evidence for it: plain decoding is perfectly repeatable, and the point where a reply diverges is the same at k = 1, 2 and 4. If you need identical output, don't assume it; compare.

How often does the draft guess right?

The acceptance rate, α, is the chance that one guessed token is kept. The lab estimates it from the share of output tokens that came from the draft (each pass contributes exactly one token that isn't the draft's, so the tokens per pass tell you α).

  • Ticket JSON: α ≈ 1.00. The format is fixed, so the small model guesses nearly all of it.
  • Support replies: 0.64. Each guess has about a two-in-three chance of being kept.
  • Short stories: 0.60. Open-ended text is harder to guess.

Color each output token by who wrote it:

Try it · Who wrote each tokenLab 12 · script 10 · one prompt per task, k = 4, full size

a support email turned into 4 named fields, after a ~690-token rules prompt; 60 tokens max

{↵∅ "intent": "order_status",↵∅ "order_id": "A-9387",↵∅ "sku": "BOOT-W",↵∅ "urgency": "high"↵}
Draft's guess, kept Big model's own token, one per round (not a miss)
Tokens
39
From the draft
77%

↵ is a line break and ∅ a token that prints nothing. Fixed formats are easy to guess; open text is not. The big model still checked every kept guess.

The purple tokens aren't misses: every round ends with one token from the big model, even when every guess was right.

Longer guesses, and the formula

A bigger k gives more tokens per pass when the guesses are right, and throws more away when they're wrong. One wrong guess throws away every guess after it. Measured, full size, end to end (reading the prompt included), median of 3 runs:

Task Plain, tokens/s k = 1 k = 2 k = 4 k = 6 α
Ticket JSON 16.5 1.18× 1.37× 1.53× 1.63× 1.00
Support reply 26.5 1.29× 1.46× 1.24× 1.34× 0.64
Short story 26.3 1.34× 1.46× 1.44× 1.32× 0.60
  • JSON got faster with every step up in k, in every run. The draft was almost never wrong.
  • Replies and stories: k = 2 beat k = 6 in every run. At α = 0.6, all six guesses are right only about 1 time in 20 (0.6⁶ ≈ 0.047).
  • Every cell was faster than plain decoding, in every run. Single cells moved by up to 0.37× between runs (replies at k = 6: 1.01–1.38×), which is why the medians are shown.

The lab's formula predicts the speed-up from α, k, and what a draft step costs relative to a big step (0.10 here):

tokens per pass = 1 + α + α² + … + αᵏ  =  (1 − αᵏ⁺¹) ÷ (1 − α)
speed-up        = tokens per pass ÷ (1 + k × cost of a draft step)
At k = 2 Predicted Measured
Ticket JSON 2.49× 1.37×
Support reply 1.71× 1.46×
Short story 1.63× 1.46×

The big gap is JSON, where the guesses were nearly perfect. Mostly, it's the prompt. Before writing anything, the model reads the whole prompt, and here that's a ~690-token list of rules for a ~38-token answer: 44% of the plain time (37–54% across runs). The formula counts writing only, and the draft can't speed up the reading. Stories have short prompts, and came closer. On top of that, the formula assumes a pass costs the same however many tokens it checks: true enough at full size, and the next section shows when it isn't.

The 4-bit model

Chapter 29 shrank the big model to 4 bits per number. Alone, it's about three times faster: about 105 tokens/s on a short test prompt, against 32. Now add the draft, shrunk to 4 bits too:

  • Replies and stories got slower, at every k, in every run (0.45–0.95×). At k = 6: replies 0.49×, stories 0.47×, about half speed.
  • Ticket JSON gained 12% at most (1.02–1.12×).
  • The guesses were only a little worse: α at k = 2 went from 1.00, 0.64, 0.60 to 0.99, 0.58, 0.58.

So the video timed the 4-bit pass, too:

Pass over 1 new token 2 3 5 7
Full size, ms 36.1 34.1 39.2 42.3 43.5
4-bit, ms 11.9 12.4 13.5 19.2 29.1

At full size, 7 tokens cost 1.2× one token. At 4 bits, 2.5× (at least 2.4× in every run): checking a long guess is no longer cheap. And drafting isn't cheap either: six 4-bit draft steps (17 ms) cost more than one big step (12 ms). Put those timings into a round (k draft steps plus one pass over k + 1 tokens) and the cost model predicts 0.58× for stories at k = 6, against 0.47× measured. That explains most of the slowdown, not all of it. Short guesses were also 10–20% slower (0.81–0.91× at k = 1–2), which these timings don't explain.

The lab's README offers a reason for the expensive pass (4-bit weights have to be unpacked for real arithmetic on every extra token, as with batching in chapter 30); the video measured the cost, not that reason. Either way, quantization and speculative decoding attack the same bottleneck, so their gains don't simply multiply. And here, if 4 bits was good enough for the task, the 4-bit model alone was 1.9× (stories) to 2.5× (replies) as fast as the full-size model with a helper.

Try it · Faster, or slower?Lab 12 · script 10 · median of 3 runs, range in brackets

The big model

Task

plain26.5 tokens/s end to end · 1.00× by definition
k = 11.29× (1.28–1.54)
k = 21.46× (1.18–1.55)
k = 41.24× (1.20–1.47)
k = 61.34× (1.01–1.38)
Measured speed-up Formula's prediction 1× (no change)
α (k = 2)
0.64
Same output, k = 2
4/8
Pass: 1 token
36.1 ms
Pass: 7 tokens
1.2×

Full size: plain decoding about 32.4 tokens/s. One pass of the big model takes 36.1 ms for 1 new token and 43.5 ms for 7, so checking a guess is nearly free; a draft step is 5.7 ms. The laptop was running other work, so speeds varied between runs; α and the outputs didn't.

Also in the lab

  • The README's earlier run (October 2026, same laptop and script) found a draft step costing 0.20 of a big step (the draft ran at 166 tokens/s, not 315), ticket JSON at 1.75× for k = 6, and short stories at 0.94× for k = 6, slower than plain. The video's three runs didn't reproduce those: stories at k = 6 were 1.29–1.33×, JSON 1.35–1.63×. What did reproduce: replies around 1.5× at k = 2, 4-bit replies at k = 6 around half speed, JSON identical 8 of 8 while open text wasn't, and plain decoding repeatable. A single run on a laptop is a rough number.
  • Busy servers. The lab's "think about" question: with many users and continuous batching, a server already makes many tokens per read of the weights, which is the same spare capacity the draft would use. So speculative decoding helps less there; measure it under real load.
  • Why the same family. The draft must split text into the same tokens as the big model, or its guesses can't be checked token by token. A draft trained on similar data also guesses the big model's next token more often.

What to do

  • Big model at full size, writing open text? Try a draft model from the same family, at a few values of k. Here k = 2 was best for replies and stories.
  • Predictable output (JSON, fixed formats)? Longer guesses pay, but short answers after long prompts gain less than the formula says.
  • Already shrunk to 4 bits? Measure first. Here the draft made open text slower.
  • Consider shrinking instead. If 4 bits is good enough for the task, that alone beat full size with a helper by 1.9–2.5× here.
  • Compare outputs, not only speed. Identical output is the theory; here it held for JSON only.
  • Time end to end, on your own prompts. Prompt reading counts, and the speed-up formula doesn't include it.

Caveats

  • One laptop, one model pair. Apple M4 Max with MLX; a GPU server with vLLM has different pass costs, and other model pairs have different α.
  • 8 prompts per task. The best k is reported on the same prompts it was measured on.
  • A busy machine. Other work ran during all 3 runs; speeds varied by up to 0.37× per cell, so the medians are rough. α and the outputs didn't vary.
  • One user at a time. No batching; on a busy server the gain is likely smaller.
  • Why outputs differ isn't proven. Near-ties flipping is the likely reason; the run shows that they differ, and where.
  • α is estimated, from the share of tokens that came from the draft, assuming each guess is kept independently.

The checklist

  1. Pick a draft from the same family, so it splits text into the same tokens.
  2. Measure end to end at a few values of k (1, 2, 4, 6), on your real prompts and answer lengths.
  3. Record α per task, from the share of tokens that came from the draft.
  4. Compare outputs with plain decoding, not only the speed.
  5. Time one pass over 1 and k + 1 tokens on your hardware, to see if checking is still cheap.
  6. Re-measure after quantizing, and compare against the quantized model alone.
  7. Run it more than once, and report a range, if the machine is shared.

Practice: speculative decoding in code

Write one round of draft-and-check, the expected tokens per pass, the formula's speed-up, and α worked back from a run's counts. The tests use the video's round (" reaching", " out", then " and") and the recorded counts: 202 of 303 JSON tokens from the draft, 526 of 1,024 for replies and 504 of 1,024 for stories, and expect the lab's α (1.00, 0.64, 0.60) and predictions (2.49×, 1.71×, 1.63×).

Try it · Practice in PythonRuns in your browser · ⌘/Ctrl + Enter to run
spec_practice.py46 lines

Quiz

Try it · Check your understanding0 of 6 answered
  1. 1.In speculative decoding, what do the draft model and the target model each do?

  2. 2.Why is checking 4 guesses not 4 times as expensive as writing 1 token, on the full-size model?

  3. 3.The acceptance rate α was about 1.00 for ticket JSON and 0.60 for stories. What does that change?

  4. 4.With greedy decoding the output should be identical to plain decoding. What did the run show?

  5. 5.For ticket JSON at k = 2, the formula predicted 2.5× and the run measured 1.4×. Why?

  6. 6.Both models shrunk to 4 bits: why did replies and stories get slower with the draft?

Check your understanding

Try each question before opening its answer.

Why is plain decoding of Qwen3-8B on a laptop slow, and what does speculative decoding use to speed it up?

Every token reads all 16.4 GB of the model's numbers from memory, and that read is the slow part. A pass over several tokens reads the numbers once, so the big model can check a few guessed tokens for little more than the price of one: 42 ms for 5 tokens against 36 ms for 1.

In one round with k = 4, the draft's first two guesses are right and the third is wrong. How many tokens does the round produce?

Three: the two kept guesses plus the big model's own token at the position of the wrong guess. The fourth guess is thrown away even if it would have been right, because it followed a wrong one.

Why did longer guesses help ticket JSON but not stories?

On JSON the draft was almost never wrong (α ≈ 1.00), so each extra guess usually adds a token. On stories α was 0.60: one wrong guess throws away the rest, and all six are right only about 1 time in 20, while every guess still costs a draft step. k = 2 beat k = 6 on stories in every run.

The output should be identical to plain decoding. Why check it anyway?

Because here it wasn't, for open text: replies matched 4 of 8 and stories 3 of 8 (JSON 8 of 8), though plain decoding repeated itself 24 of 24. Checking several tokens at once likely rounds differently, and near-ties flip. Every word is still the big model's, but not always the same words.

What does the speed-up formula leave out, and where did that show?

Reading the prompt, and any extra cost of checking more tokens in one pass. For JSON at k = 2 it predicted 2.5× and the run measured 1.4×: 44% of the time was reading a long rules prompt, which the draft can't speed up. At 4 bits the second assumption broke too.

At 4 bits, the guesses were nearly as good. Why did replies and stories get slower?

Checking stopped being cheap: a 7-token pass took 2.5× a 1-token pass (1.2× at full size). And six draft steps (17 ms) cost more than one big step (12 ms). A timing model from those predicts 0.58× for stories at k = 6, against 0.47× measured: most of the slowdown, not all.

Next up: That's the end of the published chapters, and of the Speed & cost section. The next section, Self-improving systems, is coming soon: tests that write themselves, models checking their own answers, and loops that chase the wrong reward.

Learning something useful?

New labs, articles, and videos on evaluating and testing AI systems come out regularly. Follow along so you don't miss the next one.