Repository navigation
llama: a smaller Llama drafting for the served one (--drafter), replies unchanged (builds on #1415) - #1417
llama: a smaller Llama drafting for the served one (--drafter), replies unchanged (builds on #1415)#1417BobClawblaw wants to merge 11 commits into
Conversation
|
Drafted equals plain per the receipts, and there's no lane-core change or new kernel. That's good. Two things:
|
f7c7562 to
5d4a3af
Compare
|
Thanks. Pushed 5d4a3af (one commit, on #1416's new head):
Shared rounds. Both of your options, in turn:
One correction to the old receipt: the 87-117 tok/s single-stream figures from Also: the 3B is admitted as a drafter only ( On these commits over main a1e6d34: |
|
Thanks @BobClawblaw. The drafter's default now reads the published 4-bit layout, or bf16 with an explicit width of 0 ( Three asks:
|
5d4a3af to
b652456
Compare
|
Thanks. Pushed b652456 (one commit on #1416's new head): 1. One memory budget. The backend's admission now runs on one ledger ( 2. Shared rounds are priced, and drafting stays on.
3. Warm-up failure. The harness's quick plan on the drafted The doc's drafter section is rewritten for the budget and the priced rounds. CI on these commits over main 03d08f1 (the pushed heads are rebased onto d0c9dc9, which adds only a CI workflow time limit): on #1417's head, which carries all three, |
|
Thanks @BobClawblaw. On One ordering problem is left. The checker and float64 requests on #1415 and #1416 also apply to this stack. |
A new `llama` family for the native Metal engine: dense Llama-architecture checkpoints (heads of 128, default RoPE, no biases, an untied head) in bf16, read in place from the shards with nothing converted. It keeps HF modeling_llama's rounding points (a bf16 residual, LlamaRMSNorm, RoPE with bf16 cos and sin, SiLU), runs prompt chunks on gemm_nax's tensor-unit GEMM and decode windows on a simdgroup-a-row matvec, decode attention on sdpa_vector lanes at absolute positions, and sampled draws on tf_sample_full. tf-llama-run checks greedy tokens, every position's logits, and that windows and one-row steps agree bit for bit. Measured on an M5 Ultra against HF's bf16 run of the release: 1,500 positions teacher-forced at mean KL 0.0015 and top-1 99.1%; decode 16.4 tokens/s a stream and 28-32 with two; cold prompts 1.3k tokens/s at 8k tokens and 563 at 131k. docs/recipes/nanbeige-4.3-33b-metal.md has the rest.
The family's Metal kernels through gates 1-5 (zig/tests/contract/metal_llama*.zig, the roll call and sources in contracts.zig): the glue (embedding, the norms at HF's rounding points, the residual add, RoPE with bf16 cos and sin, the SiLU gate, the decode attention's partials and their merge, the argmax draw by its stated NaN rule), the six row-projection forms, the four GEMM forms and the prompt attention at both served geometries, each with its float64 reference, served shapes and tiles, launched as forward.zig launches it; gate 5 reads the pipelines on this GPU. tf-kernel-contract --only llama on the M5 Ultra: 21 kernels, 21 pass gates 1-4, 0 fail, 0 known findings, 0 limit problems.
…out tensor units by name; the teacher-forced misses and the licence in the doc tf-llama-run takes exact's window width as an argument and loses its environment switches (LLAMA_LAYERS, LLAMA_WINDOW).
…hecks a planted row is reported
… near-tie, a float64 reference, the doc's misses and a one-process receipt
…ty, on either side, before comparing
…s rounding points (--bf16-points)
…rafts verified by the model's windows Nanbeige4.1-3B shares Nanbeige4.3-33B's tokenizer, so the family admits it as a drafter only (config.zig's qualified list has a role; serving the 3B on its own is refused by name). drafter.zig is the lane host's priced copy proposer: one cache a stream synced to the context by longest common prefix (made and grown in 4,096-row steps), greedy steps chained on the GPU through token slots, and a depth from measured rates (the stream's acceptance, a chained step's time, a line fitted to the round's time by its window's rows). While more than one stream holds a drafter cache it proposes nothing, so shared rounds decode as undrafted. The model's window keeps the drafts its own draws agree with, so replies keep its bits. --drafter-bits 4 (the default) serves an MLX 4-bit drafter as published (mlx_lm's layout, read as the served model's quants are), 0 its bf16 release. Model.loadOn puts the drafter on the served model's queue and residency set; budget.longestWith counts its caches. M5 Ultra, the bf16 33B with mlx-community/Nanbeige4.1-3B-4bit: drafted greedy tokens equal plain greedy tokens on nine prompts in tf-llama-run's new draft mode (the bf16 drafter on two of them too), 23.2-46.4 tok/s against 16.0-16.3; served with --parallel 1, one stream 99.5-116.8 tok/s on the harness's ruler (16.4 undrafted) and every quick-plan check passes. With --parallel 2 two streams decode together at the undrafted rate less the lane core's copy drafts (code 31.5 against 34.4), so the recipe doc recommends the drafter with --parallel 1.
…ound priced and drafted, a failed warm-up frees the drafter
… leaves before allocating, tested with a recorder
…after-bits 4 and quantized drafters refused by name
b652456 to
50fe245
Compare
|
Thanks, that ordering was wrong. Pushed 50fe245. This PR now builds on #1415 alone: we've withdrawn #1416 (the quants; the reasons are there), so the drafter is served from its bf16 release as published. Admission before allocation.
The host tests drive the same function with a fake store, drafter slot and allocation recorder that tracks the most the ledger and the kept states hold at once:
The drafter without #1416.
CI on these commits over main ea01ae1: |
What
--drafterfor thellamafamily: a smaller Llama on the same tokenizer proposes a stream's next tokens, and the served model verifies them in its windows, so replies keep the model's bits (builds on #1415). It was brought up with Nanbeige/Nanbeige4.1-3B drafting for Nanbeige4.3-33B, both served from their bf16 releases as published. The two share the tokenizer exactly (166,100 ids, merges, added tokens, bos and eos); only theirvocab_sizes pad differently. The 3B is admitted as a drafter only: serving it on its own is refused by name.--drafter-bits 4and quantized drafter checkpoints are refused by name too: the drafter is the bf16 release, nothing prepared at load.How
zig/src/families/llama/drafter.zigis the lane host's priced copy proposer (LaneHost.proposers). A round asks it for up to 15 tokens after the stream's context. The window verifies them row by row and keeps the prefix the model's own draws agree with, as it keeps any copy draft. There is no lane-core change and no new kernel: the drafter runs the family's own forward on the served model's queue (Model.loadOn) and in its residency set.budget.Ledger). A stream's admission (budget.admitStream) checks its peak first (its caches, its drafter cache's growth and the old state's copy), kept drafter caches shedding first; trims the kept prompt states to what that peak leaves, freeing a restore entry that can't stay beside it; and only then allocates. A freed drafter cache keeps its tokens for the next request sharing its prefix, counted, and gives way first.pricing.depthShared), nothing only where drafting costs the round more than it lands.Drafter.startowns the model from the call and frees the drafter if its warm-up fails; the host frees the drafter before the backend whose ledger it gives back to.tf-llama-run <model> <tokens> draft <drafter>,<n>decodes greedily through the drafter's windows and prints the tokens with the rounds and drafts kept.Measured (M5 Ultra, the bf16 33B and the bf16 3B, 28 Oct 2026, one model process on the Mac)
Identity. On eight chat prompts (128 tokens, thinking on and off, English and Chinese) and after a 1,500-token prompt (256 tokens), the drafted greedy tokens equal the plain greedy tokens exactly: 21.1-40.7 tok/s drafted against 15.8-16.1 plain, 1.73-4.54 tokens landing a round, a chained bf16 step 8.5-8.9 ms. The recipe doc has each prompt.
Two streams. On fresh servers (
--context 32768 --parallel 2 --prompt-cache-gib 8), eight new prompts sent two at a time (greedy, 256 tokens, thinking off) decode together at 30.1-40.9 tok/s (36.1 on average) with--drafter, against 26.6-30.1 (29.0) without; every reply's token ids are equal. Streams joining, leaving, queued, refused (HTTP 400) and cancelled each return the ids of their plain solo run, a reply alone after a cancel is unchanged, and shared rounds keep drafting (199 drafts offered, 125 kept).Receipt
--drafterNanbeige/Nanbeige4.1-3B @ 9c4555c37921c0982af8fffeee1e0a6e6b953c4f (--drafter-bits 0),LLAMA_LOAD_LIMIT_GB=150.zig build testandtest-kernel-contractexit 0 on llama: Nanbeige4.3-33B on Metal, the bf16 release served as published, every kernel in the contract #1415's head; on llama: a smaller Llama drafting for the served one (--drafter), replies unchanged (builds on #1415) #1417's head, which carries llama: Nanbeige4.3-33B on Metal, the bf16 release served as published, every kernel in the contract #1415,zig build test,test-kernel-contract,test-golden,fuzz-cortheon,dev-tools,test-server-securityandtest-pull-securityall exit 0;zig fmt --checkis clean, the lean check finds 0 new problems (426 on main and on each head),llama_teacher_judge_test.pypasses, andtf-kernel-contract --only llamaon the M5 Ultra givesSUMMARY 21 kernels: 21 pass gates 1-4, 0 fail, 0 known findings; 0 controls: 0 red as planted, 0 missed; 0 limit problems. Rebased onto main e4cc630 for this push (its lane-host commits change onlyTENSORFOLD_PREFILL_SHARE, off by default, and a flag only GLM's CUDA backend reads): both heads rebuilt,zig build testexits 0 on both andtest-kernel-contracton llama: Nanbeige4.3-33B on Metal, the bf16 release served as published, every kernel in the contract #1415's, fmt is clean, lean finds 0 new, the bf16 1,500-position logits are bit-identical to the judged run on both, and a drafted greedy chat equals the plain one.Checks
zig build testcovers the depth rule and its shared-round pricing, the cost fit, the acceptance history, the drafter role, the roll call, the one budget (two drafted 240,000-token requests that each fit a 100 GiB budget alone are refused together), admission's order with an allocation recorder (kept prompt states trimmed before anything is made, a failed growth giving every byte back) and a failed warm-up freeing the drafter. Without--drafter, the served path is #1415's: the 1,500-position logits are bit-identical to its judged run.