Repository navigation
Conversation
Rotary.inverseFrequencies() builds RoPE's inverse frequencies as the CUDA backend does: theta to the 2i/128 in float64, rounded to float32, one over it in float32, zero past the rotated pairs. A test pins both of LFM3's settings (theta 1e6 partial 0.5, theta 1e4 partial 1) to torch 2.14's float32 rows bit for bit; a float32 pow is off by 1 to 3 ulps in 16 of 32 and 28 of 64 entries. count() gives the layers of a kind in the admitted schedule, LayerType.window() the keys a layer's attention reads back (the sliding window, or 0 for all of them), and ring() the keys a sliding ring holds when a pass writes its rows before the first of them reads its window. A test checks 13 conv, 6 full and 5 sliding layers and both sizes.
…E4B out, and nem_prow gets a 4 x 4 tile image for Gen12LP The Qwen3.8-27B runner and Gemma 4 E4B need DPAS, 2D block loads and nGEN, so `zig build xpu-native -Dxpu-device=tgllp` could not build. With -Dxpu-dpas=false the server is built without both, lists only the families it built, and refuses qwen3_5 and gemma4 naming DPAS; a bmg build is unchanged and still carries them. nem_prow.cl's PR and PO become overridable; the new image nem_prow_t4 (-DPR=4 -DPO=4) spills about 25 registers on Gen12LP where the 8 x 8 pdense spills about 210. The 14 existing bmg images stay byte-equal to main.
A sweep of nem_prow's PR and PO over {2, 4, 8} on Iris Xe (LFM3-1.7B prefill, 1,024 tokens, windows of
32, 64 and 128 rows) puts 8 prompt rows by 4 outputs a sub-group first: about 8 % above 4 x 4, while
PO 8 spills (8 x 8: about 210 registers, 4 x 8: about 575) and runs 2 to 10 times slower. The image is
renamed for what it is for; every pdense output sums as before, so the bits do not change. The 14
existing bmg images stay byte-equal to main.
Without torch (or with --numpy) the forward runs on numpy: MLX affine and bf16 weights are dequantized exactly; NVFP4 and FP8 stay torch-only and are refused by name. --bf16-points rounds where the Intel GPU engine stores bf16 (embedding rows and RoPE cos/sin too, attention P and logits never). dump writes the float64 hidden state after every layer and check replays an engine's greedy replies teacher-forced. The collect and miss-table CLI is unchanged.
…, gated GQA attention over a ring, sandwich norm, SwiGLU) and their FP64 op tests
…state and KV rings) loading by names.zig, its checkpoint test against ref64.py's float64 dumps
…pt projections on nem_prow_gen12 without DPAS, Iris Xe measurements
…pied on the device LFM3's per-stream state (the conv rows of 13 layers, the KV slots of 11, the position) moves out of the layers into a sequence the model binds; the engine makes, frees and binds sequences and saves and restores prompt states (the conv rows, the full layers' slots up to the mark, the sliding rings whole). The lane host serves LFM3 on one sequence until the engine verifies windows (a later commit).
… and RoPE with the KV store lfm3_mv.cl takes attn.cl's qmv_row verbatim (same compile options, so the same contraction) and runs w1 and w3 of a row with their SwiGLU in one launch, and q, k, v and the gate in one; lfm3_rope_store normalises and rotates the q and k heads and stores k and v in one launch, through the same per-head function lfm3_qk_norm_rope now calls. Both RoPE kernels require sub-groups of 16: their norm sums one partial a sub-group into part[4], which a compiler-chosen width of 8 would overrun and 32 would sum in another order. Every value is rounded as before: the greedy tokens of the three reference prompts are unchanged. Decode on Iris Xe: 15.2 to 17.2 tok/s (LFM3_FUSE selects the fusions for A/B timing).
On Iris Xe a prompt window of 128 rows beats 32 and 64 with the 8 x 4 prompt tiles (1,024-token prefill 84 against 78 tok/s at 64). The greedy tokens of the three reference prompts are unchanged at windows of 128 and of 64.
…ream, verified windows, shared forwards LFM3's engine takes the contract Gemma 4 E4B's takes on the XPU lane host (copy_only): one sequence a stream in up to 8 slots, the lane core's context copies verified in windows of up to 16 rows (verifyRows, keepRows), several streams' windows in one forward (verifyMulti, slotOf), window costs timed at load (1, 2, 4, 8 and 16 rows, the widths between interpolated: 8 s to load instead of 21 s, the same round plans), and prompt states kept anywhere (cache_rules). One line in the adapter (xpu_nem.zig): the host takes a family's copy_match when its table row declares one, before XPU_COPY_MATCH. LFM3's is 8: at the core's 4, copies cost a short chat 6% on Iris Xe; at 8, prose and code decode 65% and 38% faster than with --no-drafts, and the two chats within 2% of it (session noise). A window's rows sum as the decode steps do: its projections and head run on pdense, whose bf16 and fp32 outputs equal qmv4_bf and qmv4_f32 bit for bit (tf-xpu-lfm3_rows-test). Conv and attention run on each stream's own state and position; lfm3_conv_hist also records each row's B * x, so keeping the first c rows of a window restores the conv state those rows leave. Decode ids are kernel arguments, so no host memory the caller may reuse is read later. tf-xpu-lfm3_streams-test checks streams in shared forwards, freed and joined mid-way, and windows rolled back to their kept rows, against each stream alone.
…les, with copy drafts, streams and the prompt cache
xpu_model.zig drops its own invFreq (theta^e in f32, 1-3 ulp from torch in 16 of the full layers' 32 frequencies and 28 of the sliding layers' 64) for Rotary.inverseFrequencies() (f64 pow, then f32: torch's values), and sizes the sliding rings and windows by cfg.ring and LayerType.window, as the CUDA backend does.
This was referenced Nov 1, 2026
…loader (2.7x TTFT at 1k), verified windows keep pdense pgemm (lfm3_mv.cl) tiles 64 x 64 in SLM as vit_gemm does and dequantizes q * scale + bias as it loads a weight tile; each output sums an fma chain a 64-wide k block and adds the blocks in order, so a row's sum does not depend on how the prompt is cut. Model.forwardPrompt routes every projection of a prompt window to it (prefill, prefillSpan, prefillWindows); verified windows, shared rounds and decode keep pdense and qmv, so drafts stay bit-equal to one-row steps. lfm3_pgemm checks it against FP64 in groups of 64 and 32 (the other group size as the planted control) and every row alone = in a window; lfm3_chunks checks the model's windows of 64, 128, 256 and a ragged cut bit for bit (rows, logits, the next step), pdense's order as the control. On Iris Xe through the server, TTFT at 1,024 tokens goes from 12.3-12.4 s to 4.6-4.7 s and at 8,192 from 116.3-116.7 s to 55.4-55.6 s.
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.
Closes #1666 once its base lands. Draft: it carries #1692's two commits and #1839's one until those PRs merge, so the diff shrinks to the last eleven commits (from
aa50f38a1on). Rebased onto maina068484a0: #1672, #1673, #1691 and #1513 (with thenames.zigtable) are merged now, so their commits are gone from here.Stacked on:
6018304a2), carried here asfe5300709and2fcfc2d3e;715e23235, the shared rope frequencies, ring and window): the last commitd9e490cecdrops this backend's own copies. B2's 64 tokens are unchanged; two chat replies changed at near-ties (a 0.068 margin, now float64's top-1, and a 1.5e-4 tie);ref64.py's numpy path (aa50f38a1), which lfm3 CUDA: serve the block FP8 export weight-only and the MLX 4-bit export as stored (stacked on #1508) #1634 also carries: the checkpoint test reads its float64 dumps.What it adds
names.zig, MLX 4-bit group 64 as stored;ref64.py's float64 dumps.xpu-nativeserves LFM3 through main's lane host (copy_only), with the contract Gemma 4 E4B's engine implements:xpu_nem.zig): the host takes a family'scopy_matchwhen its table row declares one. LFM3 declares 8. At the core's default of 4, copies cost a short chat 6 % on Iris Xe.tf-xpu-lfm3_rows-test, bit for bit);nem_prow_gen12in windows of 128 rows.bb38b814b, a different summation order): prompt windows takepgemm(lfm3_mv.cl), which tiles 64 × 64 in SLM asvit_gemmdoes and dequantizes MLX 4-bit as it loads a weight tile. Each output sums an fma chain per 64-wide k block and adds the blocks in order, so a row's sum doesn't depend on how the prompt is cut. Verified windows, shared rounds and decode keeppdenseandqmv, so drafts stay bit-equal to one-row steps.docs/recipes/xpu-arc.md.Checks (Iris Xe, WSL2)
After the rebase onto
a068484a0: B2's 64 tokens on all three prompts,--parallel1/2/4 equal to solo, cache resume equal to cold,tf-xpu-lfm3_rows-test28/28, the tgllp images byte-equal to the previous head, and bmg's 14 images byte-equal to main's (lfm3,lfm3_mvandnem_prow_gen12are the new ones).Forward against float64: every layer of 3 prompts is at 0.95–1.05× the bf16 gap. The 64 greedy tokens match the CUDA engine (lfm3 CUDA: serve the block FP8 export weight-only and the MLX 4-bit export as stored (stacked on #1508) #1634, MLX 4-bit) on all 3 prompts, and match again after every speed change, at windows of 128 and 64, with each fusion on and off.
Streams: at
--parallel1, 2 and 4, 5 concurrent replies are byte-equal to solo, in two runs each.tf-xpu-lfm3_streams-testchecks streams in shared forwards, streams freed and joined mid-way, and windows rolled back, each against the stream alone.RoPE kernels: these now require sub-groups of 16. Before, the compiled width could change a reply from one process to the next.
Prompt cache: a resumed second turn is byte-equal to the cold one, at 1k and 8k tokens. TTFT is 0.56 s against 13.25 s at 1k, and 0.80 s against 122.6 s at 8k.
Speed, server:
--no-draftspgemm4.6–4.7 spgemm55.4–55.6 sWith copy drafts on, prose decodes 65 % and code 38 % faster than with
--no-drafts. The two chats stay within 2 % of--no-drafts, which is within the session noise. Prompts are timed with--context 16384, decode with--context 4096.pgemmagainst float64:lfm3_pgemmchecks it in groups of 64 and 32 (the other size as the planted control) and every row alone equal to the row in a window.lfm3_chunkschecks windows of 64, 128, 256 and a ragged cut bit for bit (rows, logits, the next step), withpdense's order as the control. Through the model, per layer against float64: the 750-row prompt, 1k and 8k are the same (≤ 1.002×) or closer (mean −0.3 to −0.7 %). The two short prompts are further: +5.3 % (20 rows) and +2.9 % (19 rows), single layers up to +8 %. At op level on real weights both orders have fp32 error 2e-7 and 0 bf16 flips, and three orders rank differently on each short prompt. B2's 64 tokens are unchanged; two chat replies changed at near-ties (one now float64's top-1, one now 0.075 behind it).Spark (GB10), head
bb38b814b: lean reports no new findings;zig build test, the contract, golden, security and fuzz steps pass;git diff --checkis clean.Overlaps
atc overlapreports file conflicts with:xpu/.I'll rebase onto whichever lands first.