Repository navigation
1bit serve: warm the backend before the first client request (the first request decodes 5-23% slower) - #492
Merged
Merged
Conversation
…st request decodes 5-23% slower) The first request after a fresh load pays a one-time cost that is not the model's steady state. Measured with engine main on strixhalo, Qwen3.8-27B on HRX, three fresh loads, three identical prompts then three different ones with cache_prompt off: - request 1 decoded 5 % slower than requests 2-3 (11.72/11.75/11.75 against 12.41/12.46/12.36 tok/s), in all three loads, and prefilled 20 % slower (869-945 against 714-733 ms) - Qwen3-4B with a cold model file: request 1 decoded 23 % slower (54.67 against 71-72 tok/s) It is not llama.cpp's warmup (--no-warmup leaves it at 11.66 tok/s), not the GPU ramping (9 s of continuous dispatch before the first request: 11.74 tok/s) and not prompt caching (all requests sent with cache_prompt off). It is HRX's per-shape command-program build and the first touch of that shape's buffers, paid by whichever request uses the shape first: a synthetic request of the same prompt length before the measured ones moved the penalty off request 1 (12.47 tok/s) and left requests 2-3 unchanged. So serve now sends one synthetic request per backend as soon as the backend answers /health, before it announces itself ready. Through 1bit serve, two loads per arm, requests 1-3 of the same prompt: warm-up off request 1 decoded 5.1 % / 7.2 % slower than the best of requests 2-3 warm-up on request 1 decoded 1.1 % / 1.1 % FASTER than the best of requests 2-3 Cost: 2.1-3.3 s of load time on the 27B (logged as "warm-up request on port N took M ms"). HRX backends only, and ONEBIT_NO_WARMUP=1 turns it off. What it does not fix, stated so nobody expects it to: only the decode shape and the warm-up's own prompt length are covered. A client prompt of another length still builds its own prefill program once, ~130-200 ms on the 27B (the warm-up above left that excess in place: request 1 prefill 872-930 ms against 720-726 ms for requests 2-3 of the same prompt). Making prefill shapes static (padding to a fixed ubatch) would remove it; that is a llama.cpp change, not a serve one. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
bong-water-water-bong
added a commit
that referenced
this pull request
Oct 21, 2026
…nt-backend carries the warm-up (#495) Lemonade's standard benchmark had Lemonade's own Qwen3.8-27B entry drafting with the model's MTP head (`--spec-type draft-mtp`, label "mtp") at 24-28 tok/s while the onebit recipe ran the same file without a drafter at 11-12. And on the one-hop route lemond starts llama-server itself, so the warm-up request serve sends a ready backend (#492) never ran. - launch_for: a .gguf with <architecture>.nextn_predict_layers > 0 on hrx gets `--spec-type draft-mtp` (no -md: llama-server loads the head from -m), as Lemonade's llamacpp recipe does; --mtp-max / --mtp-p-min apply, recipes see drafter=mtp. Not with --mtp / --dflash, --parallel > 1, --embedding / --reranking, on cpu, or with --no-mtp / ONEBIT_NO_MTP=1. Both serve and the one-hop plan. - --print-backend: an hrx chat plan carries "warmup": {path, body}, serve's own warm-up request (shared code now), for Lemonade's onebit recipe to send before it marks the model loaded (lemonade fork #16). ONEBIT_NO_WARMUP=1 leaves it out. - tests/print_backend.sh: the default drafter, its opt-outs and the warmup key. - docs/serve.md: both, with the Lemonade bench numbers (27B chat/code 11.1/11.9 -> 26.2/29.5 tok/s). Co-authored-by: bong-water-water-bong <bong-water-water-bong@1bit.gg> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
bong-water-water-bong
added a commit
that referenced
this pull request
Oct 22, 2026
…yout at load (27B chat-short TTFT 2.64 -> 0.80 s) (#498) Lemonade bench's chat-short TTFT on Qwen3.8-27B was 2.3-2.6 s after every reload against 0.5 s later. Measured per prompt length on fresh servers (JIT cache warm): 1-12 tokens 0.1-0.5 s, 13-20 tokens 2.03-2.07 s, 21-512 tokens 0.45-1.23 s. The 13-20 class is not a program or JIT build: perf shows ggml-hrx re-quantizing weights on the host (materialize_symmetric_k32_eightgroups_shared4 from Q4_K/Q5_K/IQ4_XS, 6.1 s of CPU, +1.0 GiB GTT) the first time its small-batch kernels run. llama-server splits a chat prompt at the last user message and before its last 4 tokens, so chat-short (67 tokens) runs as 50 + 13 + 4. - warmup_requests(): the chat request (#492) plus a 16-token /completion prompt (" a" x 16, n_predict 0), sent in order by serve and planned by --print-backend as "warmups" (with "warmup" = the first, for an older lemond). Token ids instead of text hit a 500 on Qwen3.5-4B (id 100 is a lone UTF-8 byte there). - tests/print_backend.sh, docs/serve.md (the class map, the cause, the numbers). Lemonade bench (P1, one hop, lemond on cores 0-3), TTFT ms before -> after: Qwen3.8-27B chat-short 2643 -> 796, code-short 824 -> 818; Qwen3.5-4B 263 -> 138, 268 -> 142. Decode unchanged. Load +2.1 s (27B), +0.2 s (4B). Greedy outputs identical with and without it. Co-authored-by: bong-water-water-bong <bong-water-water-bong@1bit.gg> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What was measured
A loom agent saw requests 1 and 2 on Flash-Next decoding and prefilling 15-20 % slower than request 3. The same
thing exists on HRX with engine main, and it is a property of the first request after a load, not of the prompt:
Qwen3.8-27B on HRX, three fresh
llama-serverloads, three identical prompts then three different ones, allwith
cache_promptoff,max_tokens48 (llama-server's own timings):Qwen3-4B, same protocol, three loads: the load whose model file was still cold decoded request 1 at 54.67 tok/s
against 71-72 later (−23 %); the two loads with the file in page cache showed −0.9 % and −1.4 %, so the 4B case
is dominated by first touch of the model.
What it is not
--no-warmupleaves request 1 at 11.66 tok/s (the warmup graph is not the shapethe client uses);
(and a temperature sweep of the same kernel from 56 °C to 81 °C moves it 291.08 → 294.55 µs, i.e. nothing);
cache_prompt: false.What it is
HRX's per-shape command-program build plus the first touch of that shape's buffers, paid by whichever request
uses the shape first. A synthetic request of the measured prompt's length, sent before the six measured ones,
moved the penalty onto itself and left request 1 at 12.47 tok/s, the same as requests 2-3 — and request 5 (a
different prompt length) then paid a fresh ~200 ms prefill build of its own.
The fix
serve_childnow sends one synthetic request per backend as soon as the backend answers/health, before theservice reports itself ready: a ~465-token prompt with 8 decode steps,
temperature 0,cache_prompt false,HRX backends only,
ONEBIT_NO_WARMUP=1to turn it off. It runs on the lazily started laya backends too.A/B through
1bit serve, two loads per arm, same protocol (request 1 against the best of requests 2-3):ONEBIT_NO_WARMUP=1Cost: 2.1-3.3 s of load time on the 27B, logged as
1bit serve: warm-up request on port N took M ms.What it does not fix
Only the decode shape and the warm-up's own prompt length are covered. A client prompt of another length still
builds its own prefill program once (~130-200 ms on the 27B — visible above as request 1 prefill 872-930 ms
against 720-726 ms for requests 2-3 of the same prompt). Removing that needs static prefill shapes (padding to
a fixed ubatch) in llama.cpp, which this change deliberately does not touch.
Checks
scripts/hw-gate.shfull on the binaries this engine ships (build-warm/hrx/llama/bin, llama.cpp5dcf6623418b, hrx-system82804197bcbe, gpu-kernels8ebe8da), commit5914c13: PASS on all sevenmodels — qwen3.8-27b kat/kld/placement (152 graphs), zaya1-8b + qwen3-coder-30b-a3b + qwen3-4b-iq4xs
kat/kld/placement, qwen3-0.6b + -kq8 + -kvq8 with ops 663/663, kat, kld, needle (7082-token prompt) and
placement; 0 amdgpu ring timeouts across the three runs.
~/wt/mtpgap/{warm,warmreport,serve-warm}.{py,sh}.🤖 Generated with Claude Code