The 8 GB Lab Book · Chapter 22

Prefill, decode, and the wrong headline

Board
Jetson Orin Nano Super
Mode
15W
Summary
One Qwen3-4B tool call took 2,061 ms on the board, the median of five, and three reruns on 2026-10-04 gave 2,070 / 2,061 / 2,066 ms (15 calls, p10 1,431, p90 4,076): 796 ms to the first token, then 1,265 ms for 10 JSON tokens at 7.1 tok/s on that call. The 1,755 ms I first printed was arithmetic at the chat decode rate. By subtraction, about 658 ms of the 820 ms median TTFT is the fixed schema.

What I expected

I wrote down 20 to 40 tokens for a tool call. I expected that the term would be most of the wait. 30 tokens at 10.7 is 2.8 s, and I imagined a of a few hundred ms. I expected that decode would be the term to attack.

I expected that the of the tool prompt would be higher than the TTFT of chat. I did not write a number for the difference.

The theory

Two terms, and they live on opposite sides of the ridge

Prefill side (estimate)

4 B params x 2 flop = 8 GFLOP per token
fp32 peak 1.25 TFLOP/s: 6.4 ms per token
820 - 162 = 658 ms extra for the schema
658 / 6.4 = about 103 schema tokens
AI = 100 tok x 2 flop / 0.6 B per weight = 333 flop per byte
= 333 >> ridge 18: compute side

Decode side (arithmetic, chat rate)

1 / 10.7 tok/s = 93.5 ms per token (chat)
weights 4 B x 4.9 bit / 8 = 2.45 GB (est.)
2.45 GB / 93.5 ms = 26 GB/s moved
Day 2's SLM pull: 28 GB/s. same place
AI = 2 flop / 0.6 B = 3.3 flop per byte
= 3.3 << ridge 18: memory side

One tool call reads the full model again ten times: 10 x 2.45 GB = 24.5 GB of DRAM traffic for approximately forty characters of JSON. The roof at 68 GB/s would give 36 ms per token, which is 28 tok/s. The board runs at 2.6x that floor.

WHAT EACH ATTACK REMOVES FROM THE MEASURED 2,061 ms (arithmetic)

cache the prefix
-658 ms
halve the JSON
-632 ms
decode 2x faster
-632 ms
658 = 820 - 162, the schema's share, if a cached prefix returns TTFT to the chat number. 632 = half of the 1,265 ms the median call spent on its ten tokens. The cache wins on price, not size: it is a server flag, the second is a schema redesign (Chapter 23), the third is a hardware wish.

The setup

From the Chapter 21 run
python3 -c "import json;s=json.load(open('$HOME/slm/qwen3-4b.json'));print([round(c['ttft_ms']) for c in s['chat']],[round(t['ttft_ms']) for t in s['tool']],[t['tokens'] for t in s['tool']])"
Attack TTFT: cache the fixed prefix
~/llama.cpp/build/bin/llama-server -m ~/models/X.gguf -ngl 99 -c 4096 --port 8080 --cache-prompt   # then send the same system prompt twice and compare TTFT

The measurement

WhatExpectedMeasuredNoteRerun 2026-10-04, n=3 (15 calls)
Qwen3-4B Q4: TTFT chat, no image-162 msmedian of 5162 ms, p10 137, p90 177
TTFT tool prompts (schema in system)higher820 msmedian of 5820 ms, p10 797, p90 1,639
decode tok/s, chat-10.7median of 510.7, p10 9.9, p90 11.0
decode tok/s, during a tool call-6.0median of 5, 1.6 to 7.26.0, p10 1.6, p90 7.1
tokens until tool call complete~20-4010median of 510, p10 2, p90 19; 15 of 15 returned a tool_call
t_tool, end to end1755 ms (arithmetic at the chat rate)2061 msmedian of 5: 3172 / 1813 / 2061 / 1432 / 40712,066 ms, p10 1,431, p90 4,076; run medians 2,070 / 2,061 / 2,066
Measured on the board unless the row says estimate or predicted. The last column is the same harness run three more times on 2026-10-04: each of the five prompts reproduced its time within 10 ms, since at temperature 0 the model returns the same tokens.
Qwen3-4B Q4: the two terms a robot pays, per tool call, 15W
Fig 22.1Qwen3-4B Q4: the two terms a robot pays, per tool call, 15W.

The clock: a shaded prefill, then ten ticks

TTFT chat
162 ms
TTFT tool
796 ms
10 JSON tok
1,265 ms
t_tool
2,061 ms
The median call: 796 ms to the first token, then 1,265 ms for ten tokens, 126 ms per tick. The robot stands still for the whole bar.
  1. Same schemaevery call
  2. Cache its KVa server flag
  3. Prefill onlythe user turn
  4. TTFT to ~162prediction
Prediction: cached t_tool = 2,061 - 658 = about 1,403 ms.
termmeasuredtag
TTFT, chat prompt, no image162 ms* median of 5
TTFT, tool prompt, schema in system820 ms* median of 5
TTFT with the robot-default imagenot measured yetDay 19 VLM
decode, chat10.7 tok/s* median of 5
decode, during a tool call6.0 tok/s* median of 5
tokens until the tool call is complete10* median of 5
t_tool = TTFT + tokens / chat decode1755 msarithmetic
t_tool, end to end2061 ms* median of 5, the robot's
t_tool, rerun 2026-10-04, 3 runs of 52,066 ms* median of 15, p10 1,431, p90 4,076; 15 valid tool calls
Measured: Qwen3-4B Q4, 15W, llama-server, 4k context, from qwen3-4b.json tool[].total_ms. An image adds N_v prefill tokens (Chapter 19).

Where it broke

The JSON is 10 tokens, not 30. I imagined pretty-printed JSON with spaces and newlines. The tokenizer reads a one-line object as ten pieces. So decode is about 1.3 s, not 2.8 s. On the median call the split was 796 ms of prefill and 1,265 ms of decode, 39 and 61 percent.

I first printed t_tool as 820 + 10 x 93.5 = 1,755 ms, with the chat decode rate, and tagged it measured. The log has the five totals: 3,172 / 1,813 / 2,061 / 1,432 / 4,071 ms, median 2,061. Decode during a tool call ran at 6.0 tok/s, median, not the chat's 10.7. Most likely the server's tool-call parsing per token; not tested. On 2026-10-04 I ran the same harness three more times: the run medians were 2,070, 2,061 and 2,066 ms, each prompt within 10 ms of its first time, 15 of 15 calls a valid tool_call, TTFT median 820 ms and decode 6.0 tok/s during the calls. The spread on this page is between prompts, not between runs.

The schema costs 4x the chat prefill: 658 / 162 = 4.1x, and the whole tool prefill is 5.1x. That is 658 ms for each call, for bytes that never change. That term has a free fix, so prefill is the first target.

My prior for the JSON was 3x too long, because I never tokenized the target.

Summary

  • tok/s is not what the robot waits for. The robot waits for TTFT plus the tokens until the tool call is complete. A model can stream prose at 30 tok/s and take 900 ms to start. For the robot, it is slower than a model at 15 tok/s that starts in 200.
  • Vision tokens are prefill tokens. An attached image has the same cost as a long pasted prompt. Chapter 19 measures exactly how long.
  • The JSON was 10 tokens, not the 20 to 40 that I guessed. It still cost 1,265 ms on the median call, 61 percent of it. The schema's 658 ms of prefill repeats on each call, and that repetition is why I attack it first.

Sources

These sources are not mine. Measured numbers came off my own board; specs, estimates and paper figures are labeled as such.

  • Standard LLM serving literature (prefill vs decode)
  • Edge-LLM features: speculative / EAGLE3 rows in the Nano table
  • Your Day 2 roofline
  • day22/rerun-2026-10-04/summary.json: slm_eval.py run three more times on 2026-10-04 (rerun1004-day22-run1..3.json, tegrastats beside each)