ealeng

Episode 29 of 56 · Serving models

Quantization: a smaller model, measured

Why an open model is slow on your own machine (every token reads every weight from memory), and what storing those weights in fewer bits buys and costs: Qwen3 at 16, 8, 4 and 3 bits on 300 support emails, on a long-pile test that the short test missed, and a fine-tuned model shrunk to a third of a gigabyte.

Lab 12 · Inference & Serving10 min read
Video transcript8:32

0:00Cold open

0:01

Our support team wants to run an AI model on our own machine.

0:05

So I made a smaller copy of one: under a third of the size. It wrote nearly three times faster.

0:12

On our usual test, short support emails, it scored a little lower: sixty percent right, against sixty-six.

0:20

Then I tried something long: fifty-two emails pasted together, and one question. Which customer wrote about order A-3235?

0:30

The original model said Fatima. Right.

0:34

The smaller copy said Ravi. Wrong: that’s another customer.

0:40

Over ten questions like it, the original got eight. The copy got one.

0:46

So what did the short test miss?

0:49Intro

0:51

Hands-on AI engineering: serving models, part one.

0:55

Today: why a model is slow on your own machine, and what making it smaller costs.

1:01Meet Lea

1:03

I’m Lea, an ML engineer at an outdoor gear shop. In the fine-tuning series, I trained a small model to sort our support emails.

1:12

Now we want to run models on our own machines, instead of paying a company for every answer.

1:19

Running a model so that people can send it requests is called serving. That’s this series.

1:25

Everything here runs on my laptop, with Qwen three: free models you can download.

1:32

A model is a huge list of numbers, learned in training. The smallest here has six hundred million of them. The biggest, eight billion.

1:4201 · One token at a time

1:44

First, how a model writes. It writes one small piece at a time, called a token. A token is about three quarters of a word.

1:53

To pick each token, the chip does math with every number in the model. So it has to read all of them from memory.

2:01

For the eight-billion model, that’s sixteen gigabytes, read for one token. Then all of it again, for the next.

2:09

Imagine writing a letter where, before each word, you have to read a whole encyclopedia. How fast you read decides how fast you write.

2:2002 · The speed limit

2:21

So how fast can my laptop read? I measured it: about four hundred and sixty gigabytes a second.

2:28

Sixteen gigabytes for every token, at that speed, gives about twenty-eight tokens a second. That’s a rough ceiling: my reading-speed test runs a little below the chip’s real peak.

2:41

Measured: twenty-five. Close to it.

2:45

So it’s not the math that holds it back. Most of the time, the chip is waiting for numbers to arrive from memory.

2:5403 · Bigger is slower

2:55

That predicts something. A smaller model has less to read, so it should write faster.

3:01

The smallest one writes two hundred and eighty-four tokens a second.

3:07

The biggest has fourteen times as many bytes to read. How much slower do you think it is?

3:15

Eleven times slower. Almost in step with its size. Small models don’t quite reach their ceiling.

3:22

So to write faster, give the chip fewer bytes to read.

3:2804 · Shrinking

3:29

One way to have fewer bytes: store each number with less detail. That’s called quantization. I’ll call it shrinking.

3:37

Numbers are stored in bits. A bit is a zero or a one. Sixteen bits can make about sixty-five thousand different values. That’s how each number is normally stored.

3:49

Here are five real numbers from a model, stored that way.

3:54

With eight bits, there are only two hundred and fifty-six values. So each number is rounded to the nearest one, like measuring with a ruler that has fewer marks. Across the whole table, numbers end up off by under one percent, on average.

4:10

With four bits: just sixteen values. Rounded harder: about ten percent off, on average. The smallest number here became zero.

4:20

With three bits: eight values. About twenty percent off. One detail: each small group of sixty-four numbers gets its own ruler, stretched to fit it. Storing those rulers costs a little, so four bits ends up just under a third of the size, not a quarter.

4:3805 · What it buys

4:40

Here’s Qwen three, four billion, at each size. Fewer bits: smaller, and faster. These speeds were timed on a different prompt than before, and speeds vary a little from run to run.

4:52

At four bits, it’s under a third of the size, and nearly three times as fast. That’s the copy from the start.

5:00

Now our usual test: three hundred made-up support emails. For each one, the model fills in four things: what the customer wants, the order number, the product, and how urgent it is.

5:13

An answer counts as right only if all four are right. Full size: sixty-six percent.

5:19

Eight bits: the same. Four bits: sixty. Three bits breaks it.

5:25

But the score hides something. The last column shows how often the copy gave exactly the same answer, all four things, as the full-size model.

5:35

At four bits, fifty of three hundred answers changed. More than half of those were answers it already had wrong.

5:43

Twenty-two went from right to wrong, and three the other way. Only those move the score. So the score fell six points, yet one answer in six changed.

5:5406 · Long emails

5:56

Now the long test. Many emails pasted together, and one question: which customer wrote about this order? The answer is one name, so it’s easy to check.

6:07

At about four thousand tokens, around six pages, the full-size model found eight of ten.

6:14

Four bits lost only six points on the short test. And here? You saw it at the start.

6:21

One of ten. Eight bits stayed close: seven.

6:26

At full size, eight and four bits, every miss named another customer from the pile. It never said it couldn’t find the email. At four bits, that happened far more often.

6:37

Why does a long input break it? I measured that it happens, not why. A likely guess: tiny rounding errors add up over thousands of words.

6:47

So that’s what the short test missed. It asked about one email at a time, with a few examples. It never asked the model to find one email in a pile of fifty.

7:0007 · My model, shrunk

7:01

Does shrinking always cost points on the short test? I tried my own model, from the fine-tuning series.

7:09

Its job is one email at a time, never a pile. So its own email test, with no examples in the prompt, is the one that counts.

7:18

At four bits: still ninety-nine percent right, the same as full size. And a third of a gigabyte.

7:26

At three bits, it fell to sixty-one.

7:30

Same rounding, very different damage. My guess: a model trained for one narrow job is sure of its answers, so there are few close calls for rounding to tip. So you can’t assume. You test the shrunk model itself. This four-bit copy is the one I’ll put up against a paid service in part three.

7:5008 · What I do now

7:51

So here’s what I do now. Eight bits by default: half the size, and here, ninety-nine percent of answers unchanged.

8:00

Smaller than that, I test the shrunk model itself, on the real task.

8:05

And on the longest inputs it will ever get, because a short test can pass while long ones break.

8:12

Next time: many people sending requests at once.Lea: Shrink, then test.

8:17Summary

8:18

A model reads all of its numbers for every token. So fewer bytes means faster writing.

8:24

Shrinking is rounding. Test what the rounding broke, on long inputs too.Lea: Thanks!

Key takeaways

  • ✓Writing each token reads every weight of the model from memory, so decode speed is capped by memory bandwidth ÷ bytes of weights. This laptop reads 464 GB/s; Qwen3-8B's 16.4 GB gives a rough ceiling of about 28 tokens/s, and it measured 25. With 14× the bytes of Qwen3-0.6B, it was 11× slower.
  • ✓Quantization stores each number in fewer bits, so it is rounded to fewer possible values: 256 at 8 bits, 16 at 4, 8 at 3. Over one real weight matrix, numbers ended up 0.6%, 9.9% and 20.6% off on average. Each group of 64 numbers keeps its own scale, so 4 bits is just under a third of the size, not a quarter.
  • ✓Qwen3-4B at 8 bits: 4.3 GB instead of 8.0, faster, the same 66% on 300 emails, and 99% of answers unchanged. At 4 bits: 2.3 GB, nearly 3× as fast, 60%. At 3 bits: 39%. Shrinking buys speed; how much it costs depends on the bits.
  • ✓The score hides how much changed. At 4 bits, 50 of 300 answers changed (17%), but only 22 right → wrong and 3 wrong → right move the score, so it fell 6 points. Compare answers with the full-size model's, not only the score.
  • ✓The short test missed the long input. Finding one customer's email in a pile of about 50 (about 4,000 tokens), Qwen3-4B got 8 of 10 at full size, 7 at 8 bits, and 1 at 4 bits. The lab measured that this happens, not why.
  • ✓Same rounding, different damage: the fine-tuned 0.6B from the fine-tuning series kept 99% at 4 bits (0.34 GB, 98% of answers unchanged) and fell to 61% at 3 bits. 8 bits is a safe default; below that, test the shrunk model itself, on its task and on its longest inputs.

Running a model yourself, instead of paying a company per answer, is called serving. On your own machine the first surprise is speed: a free open model can write slowly, and the bigger it is, the slower. This chapter covers why, and what the usual fix, quantization (storing each number in fewer bits), buys and costs. The short version: it buys a lot of speed, it cost a few points on the usual test, and it broke a test nobody had run.

The examples come from Lab 12 (Inference & Serving), Part 2, scripts 07_local_speed.py (speed on your own hardware) and 08_quantization.py (quantization), recorded on one laptop (an Apple M4 Max) with free Qwen3 models through MLX. The 300 support emails are made up, with known answers: the outdoor-gear shop's emails from the fine-tuning chapters. Lab 12's Part 1 (streaming, prompt caching, the Batch API, a semantic cache and a cascade over paid APIs) and its scripts on speculative decoding and the KV cache aren't covered here. Batching is chapter 30; self-hosting against an API is chapter 31.

One token at a time

A model is a huge list of numbers learned in training (its weights): 0.6 billion for the smallest Qwen3 here, 8 billion for the biggest. It writes one token (about three quarters of a word) at a time, and to pick each token, the chip does math with every weight. So writing a token means reading the whole model from memory, and the next token reads it all again.

That makes decoding (writing the answer) limited by memory bandwidth, how many bytes per second the chip can read, not by how fast it does math:

decode tokens/s ≤ memory bandwidth ÷ bytes of weights

Script 07 measured this laptop at 464 GB/s. Qwen3-8B at 16 bits is 16.4 GB, so 464 ÷ 16.4 gives about 28 tokens/s. It measured 25. That's a rough ceiling, not an exact one: the bandwidth test runs below the chip's quoted peak (546 GB/s), and other runs of the same model reached 29–31.

Bigger is slower

If reading is the limit, a smaller model should write faster, roughly in step with its size:

Model Weights (16-bit) Ceiling (464 ÷ GB) Measured tokens/s Share of the ceiling
Qwen3-0.6B 1.2 GB 389 284 73%
Qwen3-1.7B 3.4 GB 135 118 88%
Qwen3-4B 8.0 GB 58 45 78%
Qwen3-8B 16.4 GB 28 25 89%

The biggest has 14× the bytes of the smallest and is 11× slower. The smallest gets the lowest share because fixed per-token costs (Python, launching work on the chip) matter more when each token is quick. So the way to write faster on the same chip is to give it fewer bytes to read.

Try it · Bytes in, tokens outLab 12 · script 07 · 16-bit weights, 464 GB/s measured
Read per token
16.4 GB
Ceiling
≈ 28/s
Measured
25/s
Of the ceiling
89%

464 GB/s ÷ 16.4 GB ≈ 28 tokens/s at most (a rough ceiling)

Measured decode speed Ceiling: bandwidth ÷ weights

One request, 200 tokens each. The biggest has 14× the bytes of the smallest and is 11× slower. The bandwidth test runs below the chip's quoted peak, so other runs of the 8B model reached 29–31 tokens/s.

Shrinking: fewer bits

Numbers are stored in bits, each a 0 or a 1. A model is trained with 16 bits per number, which allows 65,536 possible values. Quantization stores each number with fewer bits, so with fewer possible values, and every number is rounded to the nearest one: like measuring with a ruler that has fewer marks.

Bits Possible values Average rounding error (one real matrix)
16 65,536 as trained
8 256 0.6%
4 16 9.9%
3 8 20.6%

The errors are the average size of the change over one whole weight matrix of Qwen3-4B (layer 10, 4096 × 2560 numbers), rounded with MLX's own quantize, as script 08 does. Each group of 64 numbers gets its own scale and offset, its own ruler stretched to fit it. Storing those costs about half a bit per number, so 4 bits ends up just under a third of the 16-bit size, not a quarter (Qwen3-4B: 2.3 GB, not 2.0).

Here are five real numbers from one group, at each size. At 4 bits, the smallest became 0:

Try it · Fewer bits, fewer marks on the rulerLab 12 · Qwen3-4B, layer 10, one group of 64

Store each number in

Possible values
16
Off on average (whole matrix)
9.9%
0
As trained (16 bits) Stored in 4 bits
As trained4 bitsOff by
0.00500.0000−0.0050
-0.0544-0.0528+0.0016
-0.0432-0.0422+0.0010
0.03370.0317−0.0020
0.02270.0211−0.0016

Rounded with MLX's own quantize, as in script 08: each group of 64 numbers gets its own scale and offset (its own ruler), which is why 4 bits ends up just under a third of the size, not a quarter. The average error is over the whole 4096 × 2560 matrix, not these five.

What it buys, and what it costs

Script 08 ran Qwen3-4B at each size on the usual test: 300 made-up support emails, each prompted with the full rules and 10 worked examples (about 1,800 tokens). For each email the model fills in four things (what the customer wants, the order number, the product, and how urgent it is), and an answer counts as right only if all four are right. "Same answer" counts the emails where the shrunk model gave exactly the 16-bit model's answer, right or wrong.

Qwen3-4B Weights Tokens/s All 4 right (n = 300) Same answer as 16-bit
16-bit 8.0 GB 55 66% —
8-bit 4.3 GB 94 66% 99%
4-bit 2.3 GB 151 60% 83%
3-bit 1.8 GB 170 39% 49%

These speeds come from script 08's second run, timed on a support-email prompt, so the 16-bit 55 isn't the same measurement as script 07's 45. Speeds vary a little from run to run.

  • 8 bits was free here: half the size, faster, the same score, 99% of answers unchanged.
  • 4 bits is under a third of the size and nearly 3× as fast, for 6 points (a significant drop by McNemar's test in the lab).
  • 3 bits breaks it, and doesn't buy much more speed.

The score hides how much changed. At 4 bits, 50 of 300 answers changed: 22 went from right to wrong, 3 from wrong to right, and 25 from one wrong answer to a different wrong one. Only the right ↔ wrong flips move the score, a net 19 answers, about 6 points. So the score fell 6 points, yet one answer in six changed. A regression test that compares only scores would understate how different the shrunk model is.

Qwen3-8B followed the same pattern: 76%, 76%, 71% and 61% at 16, 8, 4 and 3 bits, with 99%, 88% and 70% of answers unchanged. At 4 bits (4.6 GB) it beat the 16-bit Qwen3-4B (8.0 GB) on this test, 71% against 66%, in less memory.

A long pile: find one name

The short test asks about one email at a time. Script 08 also ran a long test: paste many emails together, about 50 for 4,000 tokens (around six pages), and ask one question, such as "What is the first name of the customer whose email mentions order A-3235? Reply with the name only." The answer is one name, so it's easy to check. Ten questions per length:

Qwen3-4B ~1,500 tokens ~4,000 tokens ~8,000 tokens
16-bit 10/10 8/10 7/10
8-bit 10/10 7/10 6/10
4-bit 8/10 1/10 1/10
3-bit 4/10 1/10 0/10

The 4-bit model lost 6 points on the short test, and here fell from 8 of 10 to 1 of 10. With 10 questions the intervals are wide (8/10 is 49–94%, 1/10 is 2–40%), but they don't overlap. In the A-3235 question (52 emails, about 4,300 tokens), the full-size model said Fatima, which is right; the 4-bit model said Ravi, another customer. At 16, 8 and 4 bits, every miss named another customer from the pile; it never said it couldn't find the email. At 3 bits, 7 of its 24 misses weren't a customer in the pile at all: made-up names or broken sentences.

Qwen3-8B held up better at 4 bits (7 of 10 at 4,000 tokens, against 9 at 16 bits) but still lost ground at 8,000 (4 against 7).

Why does a long input break it? The lab measured that it happens, not why. A likely guess: small rounding errors add up over thousands of tokens, while the model has to pick out one small signal among many.

So that's what the short test missed. It asked about one email at a time, with a few examples; it never asked the model to find one email in a pile of fifty.

Try it · What shrinking buys and costsLab 12 · script 08 · 300 test emails, 30 long piles
Size
2.3 GB
Tokens/s
151
All 4 right
60%
Same answer as 16-bit
83%

50 of 300 answers changed: 22 right → wrong, 3 wrong → right, 25 wrong → a different wrong. Only the first two move the score: net 19 answers, about 6 points.

The long-pile test · find the one email about an order, name its customer

Email 13 of 52 · about 4,335 tokens in all

“Hi Lantern Outfitters, I’m writing about my order A-3235—I received the wrong color of the water filter, and I think it was a mix-up; could you please help me sort this out? Thanks so much, Fatima”

Asked: what is the first name of the customer whose email mentions order A-3235? The other emails are from other customers, about other orders.

Graded as the lab does: the reply, trimmed of spaces and full stops, must equal the name. At 16, 8 and 4 bits, every miss named another customer from the pile; at 3 bits, 7 of 24 misses for the 4B model didn't. Ten questions per length, so the intervals are wide.

Speeds are from script 08's second run, timed on a support email, so the 16-bit 4B (55) differs from script 07's 45. Speeds vary a little from run to run.

The fine-tuned model, shrunk

Does shrinking always cost points? Script 08 also shrank the fine-tuned Qwen3-0.6B from chapter 23 (its LoRA merged into the weights first). Its job is one email at a time, never a pile, so its own test (the same 300 emails, no examples in the prompt) is the one that counts:

Bits Weights All 4 right Same answer as 16-bit
16 1.19 GB 99% —
8 0.63 GB 99% 100%
4 0.34 GB 99% 98%
3 0.26 GB 61% 59%

At 4 bits it lost nothing, in a third of a gigabyte. At 3 bits it fell the furthest of the three models, 38 points. The same rounding changed 17% of the prompted 4B's answers and 2% of the fine-tune's. A plausible reason, not tested: a model trained for one narrow job is sure of its answers, so there are few close calls for rounding to tip. You can't assume either way; test the shrunk model itself. This 4-bit copy is the one chapter 31 puts up against a paid API.

Caveats

  • One laptop. Speeds are an Apple M4 Max's, through MLX, and vary from run to run (Qwen3-8B at 16 bits: 25 in one run, 29–31 in others). Compare speeds from the same run, as each table here does.
  • MLX's quantization. Affine, in groups of 64, at 8, 4 and 3 bits. It's the same idea as GPTQ- or AWQ-style 4-bit, not the same algorithm; GPU formats like FP8 weren't tested.
  • Made-up emails. Both tests use synthetic support emails with known answers. The long test has only 10 questions per length.
  • The why isn't measured. The lab shows that long inputs break at 4 bits, and that the fine-tune shrank well; both explanations above are guesses.
  • The rounding errors are averages over one weight matrix, not over the whole model.

The checklist

  1. Know your ceiling: memory bandwidth ÷ bytes of weights. Decode speed won't beat it.
  2. Start at 8 bits: here it halved the size, sped up decoding, and kept 99% of answers.
  3. Below 8 bits, test the shrunk model itself, on the real task, against the full-size one.
  4. Compare answers, not just scores: count how many changed, and which way.
  5. Test the longest inputs it will get: a short test can pass while long ones break.
  6. Retest each model: the same bits did very different damage to different models.

Practice: the arithmetic of shrinking

Compute the decode-speed ceiling, the size of a model at 16, 8, 4 and 3 bits (with each group's scale and offset), how many answers changed against the full-size model and which way, and the long-pile score graded the lab's way. The tests use the recorded sizes (8.0, 4.3, 2.3 and 1.8 GB for Qwen3-4B), the 4-bit model's 22 / 3 / 25 changed answers, and its real answers on the 4,000-token pile: 8 of 10 at 16 bits, 1 of 10 at 4.

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

Quiz

Try it · Check your understanding0 of 6 answered
  1. 1.Why does a bigger model write tokens more slowly on the same laptop?

  2. 2.The laptop reads 464 GB/s. About how fast can a 16.4 GB model write?

  3. 3.What does storing a model in 4 bits instead of 16 do to each number?

  4. 4.At 4 bits, Qwen3-4B's score fell 6 points, yet 50 of 300 answers changed. How?

  5. 5.Which test caught what the 4-bit model lost?

  6. 6.Lea's fine-tuned 0.6B kept 99% at 4 bits. What should you conclude?

Check your understanding

Try each question before opening its answer.

Why is a model slow on your own machine?

Each token reads every weight from memory, so decode speed is limited by memory bandwidth ÷ bytes of weights. At 464 GB/s, Qwen3-8B's 16.4 GB allows about 28 tokens/s; it measured 25. The chip mostly waits for numbers, not for math.

What is quantization, and why is 4 bits not a quarter of the size?

Storing each number in fewer bits, so it's rounded to fewer possible values (16 at 4 bits). Each group of 64 numbers also keeps its own scale and offset, about half a bit per number, so 4 bits is just under a third of 16 bits: 2.3 GB for an 8.0 GB model.

Why compare answers with the full-size model, not just the score?

A score only moves on right ↔ wrong flips. At 4 bits, 50 of 300 answers changed, but 25 of those went from one wrong answer to another, so the score fell only 6 points. One answer in six was different.

What did the short test miss, and how would you catch it?

Long inputs. Finding one customer's email in a pile of about 50, the 4-bit Qwen3-4B got 1 of 10 against 8 at full size, while the short test said it had lost 6 points. Test a shrunk model at the longest input you serve, against the full-size model.

Is 8 bits safe? Is 4?

8 bits was close to free here: half the size, the same score, 99% of answers unchanged, recall within one question. 4 bits depends on the model and the task: it cost Qwen3-4B 6 points and its long-pile recall, and cost the fine-tuned 0.6B nothing. Test each one.

Lea's fine-tuned 0.6B kept 99% at 4 bits. Does that mean 4 bits is safe for it?

For this job, as tested: 4 bits changed only 2% of its answers, against 17% for the prompted Qwen3-4B. A likely reason, not tested, is that a model trained for one narrow job has few close calls for rounding to tip. Check it again on your own longest inputs before trusting it.

Next up: Chapter 30 serves the model to many people at once: why the first word took 14 seconds, and the one change that made it 0.4.

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.