Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
94 commits
Select commit Hold shift + click to select a range
8b619a5
Add checked BF16 Vulkan tensor operations and operator gate
joshuaswarren Oct 28, 2026
d81d1d5
Add checked BF16 Vulkan norm and MLP operator gate
joshuaswarren Oct 28, 2026
c54ffed
Add native Vulkan attention, recurrent and greedy operator gates
joshuaswarren Oct 28, 2026
0cbcf80
Report the first residual-add class mismatch without changing acceptance
joshuaswarren Oct 28, 2026
aa6ccd9
Make attention and recurrent probes reject unwritten outputs and matc…
joshuaswarren Oct 28, 2026
7d86382
Add the native Vulkan dense Qwen3.5-2B greedy engine and oracle probe
joshuaswarren Oct 28, 2026
ac472da
Qualify residual denormals by device float controls and request suppo…
joshuaswarren Oct 28, 2026
525259e
Fix Vulkan attention gate widening and diagnose GDN reference failures
joshuaswarren Oct 28, 2026
f361d58
Separate Vulkan prefill snapshot dummy from read-only slot
joshuaswarren Oct 28, 2026
ae1f83b
Match Vulkan decode raw-gate flag to the shader mask
joshuaswarren Oct 28, 2026
784273c
fix(vulkan): admit parsed checkpoint geometry and validate full load
joshuaswarren Oct 28, 2026
271fe46
feat(vulkan): chunk Qwen3.5 prefill within the context ceiling
joshuaswarren Oct 28, 2026
7ee5c6d
fix(vulkan): use one GDN path for prefill and decode
joshuaswarren Oct 28, 2026
83d8081
build(vulkan): give the contract runner every operator's SPIR-V and a…
joshuaswarren Oct 28, 2026
7604e32
contract(vulkan): the gdn group's GatedDeltaNet shaders under the ker…
joshuaswarren Oct 28, 2026
b654bfb
contract(vulkan): fix the gdn group's fixture against the first devic…
joshuaswarren Oct 28, 2026
0632b15
contract(vulkan): keep the gdn split's served a_log normal and state …
joshuaswarren Oct 28, 2026
b560b32
contract(vulkan): the datamove group's seven exact kernels through ga…
joshuaswarren Oct 28, 2026
cbb9d41
contract(vulkan): the attention group over the engine's served shapes
joshuaswarren Oct 28, 2026
250908f
contract(vulkan): the greedy group's composed draw through gates 1-4
joshuaswarren Oct 28, 2026
4947c77
contract(vulkan): the pointwise group's six closures over the product…
joshuaswarren Oct 28, 2026
3427d05
build(vulkan): run the datamove host tests on the build host, not the…
joshuaswarren Oct 28, 2026
f39f10f
contract(vulkan): refuse odd 16-bit edge shapes in the datamove casts…
joshuaswarren Oct 28, 2026
fa44af2
contract(vulkan): the gdn prefill's uploads must not slice out past w…
joshuaswarren Oct 28, 2026
275a647
contract(vulkan): upload the greedy shell's guard pattern instead of …
joshuaswarren Oct 28, 2026
e7ada64
contract(vulkan): model the preserve add as the pipeline that is actu…
joshuaswarren Oct 28, 2026
7e1013e
fix(vulkan): the GDN bf16 store keeps infinity infinity
joshuaswarren Oct 28, 2026
6006745
contract(vulkan): record the preserve-add subnormal flush as a known …
joshuaswarren Oct 28, 2026
c57e6a8
contract(vulkan): the gated norm gate at the production per-head geom…
joshuaswarren Oct 28, 2026
40b76fb
style: zig fmt, one-line comments and the changelog entry for #1544
joshuaswarren Oct 30, 2026
85f58ce
style: every comment one line, the changelog credit for #1544
joshuaswarren Oct 30, 2026
bf33d0e
style: shorten every added comment to one line of at most 120 columns
joshuaswarren Oct 30, 2026
45c8062
style(vulkan): the attention header's comments one line each
joshuaswarren Nov 3, 2026
1c8c72d
lean: the stage 2 fixture's cases in their own file
joshuaswarren Nov 3, 2026
593847e
lean: the stage 3 fixture's cases in their own file
joshuaswarren Nov 3, 2026
6426cba
refactor(vulkan): the GDN geometry plans move to their own module
joshuaswarren Nov 3, 2026
9cefea7
refactor(vulkan): the datamove contract's rig moves to its own module
joshuaswarren Nov 3, 2026
43d7096
refactor(vulkan): the Qwen3.5 probe's receipt moves to its own module
joshuaswarren Nov 3, 2026
58396f9
build: the Vulkan stages' build steps move into their own file
joshuaswarren Nov 3, 2026
ad1ab86
style: the trig header's comments are one line each
joshuaswarren Nov 3, 2026
a064c90
style: split the stage 4 fixture and probe into lean files
joshuaswarren Nov 3, 2026
a6faa5c
lean: the gdn contract's layers in their own files
joshuaswarren Nov 3, 2026
4d87b55
qwen3_5 Vulkan: drop stale full_every pin, honor prefill max_rows
joshuaswarren Nov 7, 2026
9cf9e81
feat(vulkan): qmm_q4_vec4, the q4 projection with 16-byte weight and …
joshuaswarren Nov 3, 2026
810907c
contract(vulkan): register qmm_q4_vec4 and run it through the same ga…
joshuaswarren Nov 3, 2026
0557fab
docs(vulkan): receipts for qmm_q4_vec4 on a 13-inch M1, and the chang…
joshuaswarren Nov 3, 2026
a016801
docs(vulkan): contract runner output for qmm_q4_vec4 on a 13-inch M1
joshuaswarren Nov 3, 2026
47098fd
Rename ASH labels to oracle and correct file headers on the vulkan qm…
joshuaswarren Nov 9, 2026
8035564
style(vulkan): shorten the two renamed comments to 120 columns, so le…
joshuaswarren Nov 10, 2026
a9d3775
shaders: f32 activation variants for the wide layers 0-3 stores
joshuaswarren Nov 5, 2026
5e576fb
vulkan ops: f32 activation pipelines for the wide-layer stores
joshuaswarren Nov 5, 2026
39b891d
qwen35 engine: per-layer activation kind, layers 0-3 f32
joshuaswarren Nov 5, 2026
e8da1a1
tests: f32 pipeline fixtures, the wide-layer sweep, lean formatting
joshuaswarren Nov 5, 2026
a8cad7a
style: one-line comments at the layer-kind call sites
joshuaswarren Nov 5, 2026
5d064f4
style: keep the lean check at base parity
joshuaswarren Nov 5, 2026
6a73886
qwen35 vulkan: make the f32 layers 0-3 path run end to end
joshuaswarren Nov 5, 2026
0e428f2
cli: the backend flag lists only the choices this build serves
joshuaswarren Nov 9, 2026
6d666f2
docs: the credits page names what the vulkan qwen3.5 engine follows
joshuaswarren Nov 9, 2026
eb9f149
server: --backend choices list the whole set so unserved picks get th…
joshuaswarren Nov 10, 2026
8ee01ac
server: vulkan joins the backend choices; the credits name the omarch…
joshuaswarren Nov 11, 2026
2a0e4e6
docs: the stack's changelog lines move under a new Unreleased heading
joshuaswarren Nov 11, 2026
f98b0a1
server: the backend-choice test moves to its own file under the 600-l…
joshuaswarren Nov 11, 2026
61060a0
server: the test export keeps cuda_build next to backend_values
joshuaswarren Nov 11, 2026
6b660fe
docs: qmm_q4_vec4 figures measured on the float32-fixed head
joshuaswarren Nov 11, 2026
97a7cb0
vulkan: origin and copyright headers, plain comments, and our own nam…
joshuaswarren Nov 12, 2026
7d76c23
contract(vulkan): sync contract_qmm with main's adopt-based Fixture.p…
joshuaswarren Nov 10, 2026
1512b1e
feat(vulkan): qmm_q4_tile, the q4 projection over weights repacked in…
joshuaswarren Nov 3, 2026
c1f989b
contract(vulkan): register qmm_q4_tile and run it through the same ga…
joshuaswarren Nov 3, 2026
6d4e7c5
fix(vulkan): the tiled image serves every n; the last tile is padded …
joshuaswarren Nov 3, 2026
ac7dbf2
docs(vulkan): receipts and the changelog line for qmm_q4_tile on a 13…
joshuaswarren Nov 3, 2026
c725be2
style(vulkan): one-line comments in the tile files, so lean_check add…
joshuaswarren Nov 3, 2026
150c172
fix(vulkan): float32 layers stay on row weights, and each qmm fixture…
joshuaswarren Nov 11, 2026
6756669
docs: the tile changelog line moves under the Unreleased heading
joshuaswarren Nov 11, 2026
c521419
style(vulkan): format weights.zig and keep the tile doc comment to on…
joshuaswarren Nov 11, 2026
c9b3acc
docs: qmm_q4_tile figures measured on the float32-fixed heads
joshuaswarren Nov 11, 2026
5b83e4e
vulkan: q4 tile image reads and stores f32 activations
joshuaswarren Nov 12, 2026
ea27e78
vulkan: kernel contract fixture for the f32 q4 tile image
joshuaswarren Nov 12, 2026
0907481
vulkan: keep the tiledActivation comment on one line
joshuaswarren Nov 12, 2026
301d845
docs: pinned head-to-head timing and ten runner outputs for qmm_q4_ti…
joshuaswarren Nov 12, 2026
15442e5
perf(vulkan): record dispatches into one command buffer and submit on…
joshuaswarren Nov 3, 2026
2282de9
docs(vulkan): receipts and the changelog line for batched submission …
joshuaswarren Nov 3, 2026
478550b
style(vulkan): one-line comment on Commands.submit, so lean_check add…
joshuaswarren Nov 3, 2026
b7623a3
style(vulkan): shorten the Commands.submit comment to 120 columns
joshuaswarren Nov 3, 2026
861b33e
docs(vulkan): say that Commands.deinit drops unsubmitted dispatches a…
joshuaswarren Nov 3, 2026
a11edd8
vulkan: exit on fence-unknown GPU errors in the datamove and greedy c…
joshuaswarren Nov 5, 2026
e9bfbd2
vulkan: drop unsubmitted records and poison the stream when a contrac…
joshuaswarren Nov 5, 2026
4a0f32e
zig: join two-line comments to one line for the lean gate
joshuaswarren Nov 5, 2026
98369eb
docs: the batched-submission changelog line moves under the Unrelease…
joshuaswarren Nov 11, 2026
fb720cc
docs: batched-submission figures measured on the float32-fixed heads
joshuaswarren Nov 11, 2026
9aa1c99
vulkan: qmm_q4_tile_rows computes four rows and two columns of a prom…
joshuaswarren Nov 12, 2026
f7049f7
contract(vulkan): register qmm_q4_tile_rows, serve rows 5 and 33, pla…
joshuaswarren Nov 12, 2026
f0cef54
docs(vulkan): figures and changelog lines for qmm_q4_tile_rows on a 1…
joshuaswarren Nov 12, 2026
65c04e5
vulkan: keep the qmm_q4_tile_rows header comment within 120 columns
joshuaswarren Nov 12, 2026
4bffa08
vulkan: name the tile image choice in tileImage and pin it with a hos…
joshuaswarren Nov 12, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,10 +72,17 @@ Each release's page on GitHub has its notes and measurements. See [the 1.0.0 rel
- Qwen3.8-27B on Metal: prompt passes no longer write the GDN tails buffer, which nothing read. Replies are unchanged; on an M5 Ultra, time to first token is about 0.5% shorter at 2K, 8K and 32K prompts, one and four streams (#2915, for #2905).

- Flash Next on CUDA at `--tp 2`: a prompt pass wider than 64 rows reduce-scatters its routed experts' FP32 slots by row halves, sums each rank's rows and gathers the sums, where it all-reduced every slot; decode rounds and verify windows keep the all-reduce. Every reduced value is the same single add and the slots are summed in the writeback's order, so replies keep their tokens. Over two GB10s' RoCE link rank 0 sends 3.43 MiB a prompt token instead of 5.81, and cold prefill of 2k to 32k tokens is 26.5% to 26.8% faster there and 30.9% to 33.9% faster on two RTX PRO 6000s (an 8,192-token prompt: 7.12 to 5.62 s and 3.61 to 2.70 s) (#2977). Thanks to @swbiggart.
- Vulkan: the native dense Qwen3.5-2B greedy engine and its operator gates, the second part of the opt-in Vulkan backend built only with `-Dvulkan=true`: checked BF16 tensor operations, the norm/MLP, attention (RoPE, causal GQA SDPA, output gate) and GatedDeltaNet gates, one GDN path for prefill and decode, prefill chunked within the context ceiling, and the kernel contract extended to every operator's SPIR-V through gates 1 to 4 with planted controls and recorded device findings (residual denormals, the preserve-add subnormal flush, the gated norm at the production per-head geometry). `zig build vulkan-kernel-contract -Dvulkan=true` runs it; default builds are unchanged (#1544). Thanks to @joshuaswarren.

- The Vulkan engine reads Qwen3.5's 4-bit projection weights and activations 16 bytes at a time (`qmm_q4_vec4`) when both are 16-byte aligned, and keeps the scalar kernel otherwise. Each output keeps the same sum in the same order, so the logits are byte-identical. On a 13-inch M1 with Qwen3.5-2B 4-bit, a run (prefill, 63 decode steps and the per-step logits readback) took 34.8% to 43.0% less time than the same build with the scalar kernel selected, on three inputs: a 24-token prompt, and documents of 256 and 1,024 tokens (Linux 7.3.0-2-20261110.1-lab-ARCH; the first measurement, against #1544's head on an earlier kernel and Mesa, gave 40% to 52%). The kernel contract checks the new kernel with the same gates as `qmm_bf16`. Receipts: [docs/receipts/vulkan-qmm-vec4-m1](docs/receipts/vulkan-qmm-vec4-m1/README.md).

- Tests: `tf-cuda-test flashnext-rank RANK PORT timeout` passes again on two GPUs. NCCL start-up keeps its 30 s bound and only the stalled collective gets 200 ms, and both ranks meet once their communicators are up, so `timeout` and `eof` fail the collective under test, not the start-up (#2976, fixes #2975). Thanks to @swbiggart.
- The Vulkan engine stores each Qwen3.5 4-bit projection in tiles of 32 columns, repacked once at load, and reads them with `qmm_q4_tile`: a group of 32 lanes then loads one contiguous 512-byte run instead of 32 rows a kilobyte apart. The loop, the sums and their order are `qmm_q4_vec4`'s, so the logits are byte-identical. A projection is padded to whole tiles, and the tied output head is a second, tiled copy of the embedding table (about 320 MB more device memory). On a 13-inch M1 with Qwen3.5-2B 4-bit, a run took 10.9% less time than with `qmm_q4_vec4` on a 24-token prompt, 35.6% less on a 256-token document and 44.9% less on a 1,024-token document (Linux 7.3.0-2-20261110.1-lab-ARCH; earlier builds on an earlier kernel and Mesa gave 19%, 50% and 63%). The kernel contract checks `qmm_q4_tile` with the same gates as `qmm_bf16`. Receipts: [docs/receipts/vulkan-qmm-tile-m1](docs/receipts/vulkan-qmm-tile-m1/README.md).

- Tokenizer: a split pattern is held to a work allowance per piece (2,048 instructions a byte, counted, not timed) and refused at load when too deep or too large, so a crafted tokenizer or prompt can't hang the pre-tokenizer; every split of the patterns we serve is unchanged (#3071).
- The Vulkan engine records its dispatches into one command buffer and submits when 128 are waiting, or when the next upload, download or fill needs the queue, instead of one submission and fence wait per dispatch. Each pipeline holds a ring of 256 descriptor sets. No kernel changes, so the logits are byte-identical. On a 13-inch M1 with Qwen3.5-2B 4-bit and tiled projections, a run took 58% less time on a 24-token prompt, 34% less on a 256-token document and 17% less on a 1,024-token document; the gain depends on the kernel and Mesa build (20% on the 256-token document with earlier builds in the same environment). Receipts: [docs/receipts/vulkan-submit-batch-m1](docs/receipts/vulkan-submit-batch-m1/README.md).

- The Vulkan engine computes four rows of x and two columns a thread (`qmm_q4_tile_rows`) for prompt chunks of four rows or more, so a weight is dequantized once for four outputs and each activation is unpacked once for two weights. Each output keeps its own accumulator and the same order of sums as `qmm_q4_tile`, so the logits are byte-identical. Decode and chunks of one to three rows keep `qmm_q4_tile`. On a 13-inch M1 with Qwen3.5-2B 4-bit, a run took 4% less time on a 24-token prompt, 12% less on a 256-token document and 15% less on a 1,024-token document (6%, 29% and 38% in an earlier run on older builds, an older kernel and an older Mesa). The kernel contract checks `qmm_q4_tile_rows` with the same gates as `qmm_bf16`. Receipts: [docs/receipts/vulkan-qmm-tile-rows-m1](docs/receipts/vulkan-qmm-tile-rows-m1/README.md).

- Cluster pull: a pull that would leave a file held only in part is refused unless the caller allows unverified bytes (off by default); a pull that ends whole still resumes and is checked against the manifest's hash (#3074).

Expand Down Expand Up @@ -134,6 +141,8 @@ Each release's page on GitHub has its notes and measurements. See [the 1.0.0 rel
- Metal: kernels nothing launches are removed: Qwen3.8-27B's `prompt_glue.metal` and `prompt_state.metal` (six `q27_prompt_*` kernels) and core's `tf_lane_rope` with `Attention.rotate`; saved Qwen3.8-27B prompt states on Metal are recomputed once after upgrading, since their identity covers the kernel sources (#3065, part of #2251). Thanks to @jschmied.
- Tests: main's nightly is green again. The Flash Next learn, prompt and compare tools build again on Linux (#2585 had put one file in two modules), and the dense serving and serving-identity pins take the changes #2579, #2585, #2285, #2205 and #2789 made, each named beside its pin. Serving is unchanged (#3002).
- HIP: Qwen3.8-Flash-Next's MTP head takes a prompt chunk on the prompt tiles, not a decode window at a time, so cold prompts finish about 5% sooner at 8K and 32K on a Radeon 8060S (gfx1151). Replies are unchanged; the head's caches and drafts are not bit-identical to before (#1878). Thanks to @gilby.
- The Vulkan engine stores each Qwen3.5 4-bit projection in tiles of 32 columns, repacked once at load, and reads them with `qmm_q4_tile`: a group of 32 lanes then loads one contiguous 512-byte run instead of 32 rows a kilobyte apart. The loop, the sums and their order are `qmm_q4_vec4`'s, so the logits are byte-identical. A projection is padded to whole tiles, and the tied output head is a second, tiled copy of the embedding table (about 320 MB more device memory). On a 13-inch M1 with Qwen3.5-2B 4-bit, a run took 19% less time than with `qmm_q4_vec4` on a 24-token prompt, 50% less on a 256-token document and 63% less on a 1,024-token document. The kernel contract checks `qmm_q4_tile` with the same gates as `qmm_bf16`. Receipts: [docs/receipts/vulkan-qmm-tile-m1](docs/receipts/vulkan-qmm-tile-m1/README.md).
- The Vulkan engine records its dispatches into one command buffer and submits when 128 are waiting, or when the next upload, download or fill needs the queue, instead of one submission and fence wait per dispatch. Each pipeline holds a ring of 256 descriptor sets. No kernel changes, so the logits are byte-identical. On a 13-inch M1 with Qwen3.5-2B 4-bit and tiled projections, a run took 68% less time on a 24-token prompt, 49% less on a 256-token document and 30% less on a 1,024-token document. Receipts: [docs/receipts/vulkan-submit-batch-m1](docs/receipts/vulkan-submit-batch-m1/README.md).

- Cortheon (`--cortheon`): DuckDuckGo results are kept, and its ads still skipped, when only the spelling differs (either quote style, a `class` attribute in any letter case, an unquoted `href`, spaces around its `=`, or a line break before it); a duplicate whose scheme or host differs only in letter case is dropped while paths and queries keep their case, and the PyPI, npm and crates.io allow-list takes the host in any case with the path exact (#2750). Thanks to @ppardus.

Expand Down Expand Up @@ -1090,8 +1099,6 @@ Each release's page on GitHub has its notes and measurements. See [the 1.0.0 rel

- `zig build test-info-config` runs info's issue-934 test again: it was built from the CUDA CLI root, which never reached it (#2006). Thanks to @jishnuvenugopal.

- The Nemotron XPU loader and the XPU safetensors reader read shard headers through core, so a malformed or oversized header is refused before anything is read (#1919). Thanks to @jschmied.

- Windows: a cost-cache read that lands mid-replace is counted as a miss (safe: the window is priced again), so its concurrency test no longer flakes (#2001).

- GLM on CUDA with TF_GLM_KV_SHARE no longer reports a balanced idle KV pool as (UNEVEN) at close; nothing else changes (#1993). Thanks to @Bizuayeu.
Expand Down
29 changes: 24 additions & 5 deletions THIRD_PARTY_NOTICES.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,12 +71,31 @@ The native engine carries that adaptation. `zig/kernels/metal/qwen3_5/gdn_pre.me
`zig/kernels/metal/core/deltanet.metal` and Flash Next's `zig/kernels/metal/flashnext/q4_gdn_*.metal` run the same
model math.

## Vulkan affine projections
## Vulkan shaders from omarchy-mlx

These files in `zig/kernels/vulkan/` come from [omarchy-mlx](https://github.com/joshuaswarren/omarchy-mlx),
MIT License, Copyright © 2026 Joshua Warren and mlx-omarchy contributors. The first lines of each file name its
source path and commit. The native Vulkan path does not link MLX.

- `argreduce_chunk.comp`
- `argreduce_combine.comp`
- `binary_vec.comp`
- `cast.comp`
- `conv_dw1d.comp`
- `copy_general.comp`
- `dequant.comp`
- `fast_norm.comp`
- `fast_norm_gated.comp`
- `fast_rope.comp`
- `gated_delta_decode.comp`
- `gated_delta_prefill_ash.comp`
- `gather_take.comp`
- `gdn_conv_decode.comp`
- `omarchy_trig.h`
- `qmm.comp`
- `sdpa_decode_native.comp`
- `swiglu.comp`

`zig/kernels/vulkan/qmm.comp` adapts the GLSL shader from
[mlx-omarchy](https://github.com/joshuaswarren/omarchy-mlx), MIT License,
Copyright © 2026 Joshua Warren and mlx-omarchy contributors.
Its arithmetic is unchanged; the native Vulkan path does not link MLX.
See [the license text](LICENSES/mlx-omarchy-MIT.txt).

## Qwen Flash Next
Expand Down
5 changes: 4 additions & 1 deletion build.zig
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,10 @@ pub fn build(b: *std.Build) void {
if (with_hip) hip_build.steps(b, target, optimize, build_options, test_step, hip_options);
const with_vulkan = b.option(bool, "vulkan", "Enable the opt-in Vulkan backend and its host/device targets") orelse false;
const vulkan_options = vulkan_build.options(b); // declared on every target, used only with -Dvulkan=true
if (with_vulkan) vulkan_build.steps(b, target, optimize, test_step, vulkan_options);
if (with_vulkan) {
vulkan_build.steps(b, target, optimize, test_step, vulkan_options);
@import("zig/build/vulkan_model.zig").steps(b, target, optimize, test_step, vulkan_options, build_options);
}
exlr8_build.hostTests(b, target, test_step);
xpu_build.steps(b, target, optimize, build_options);
video_build.steps(b, target, test_step);
Expand Down
8 changes: 8 additions & 0 deletions docs/credits.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,14 @@ contributions are in it.
([lane_qmm.py](https://github.com/ashhart/TensorFold/blob/v0.6.6/src/tensorfold/kernels/qwen/dense/v1/lane_qmm.py)),
widening each group to nibbles for the 4-bit tensor op. The native `zig/kernels/metal/core/lane_projection.metal`
stages 2- and 3-bit codes the same way.
- The native Vulkan dense Qwen3.5-2B engine (`zig/src/families/qwen3_5/backend/vulkan/`) follows the same frozen
dense-lane design ([lane_qmm.py](https://github.com/ashhart/TensorFold/blob/v0.6.6/src/tensorfold/kernels/qwen/dense/v1/lane_qmm.py),
[#34](https://github.com/ashhart/TensorFold/pull/34)) for its per-lane qmm decomposition. Fifteen of its compute
shaders and `omarchy_trig.h` come from Joshua Warren's MIT-licensed omarchy-mlx unchanged (`argreduce_chunk`,
`argreduce_combine`, `binary_vec`, `cast`, `conv_dw1d`, `copy_general`, `dequant`, `fast_norm`, `fast_norm_gated`,
`fast_rope`, `gather_take`, `gated_delta_decode`, `gated_delta_prefill_oracle`, `gdn_conv_decode`, `sdpa_decode_native`
and `swiglu`); `qmm.comp` is derived from it. The rest (`attention_gate`, `finite_logits`, `gdn_split`, `qmm_q4_vec4`
and the contract controls) are new work, written for this engine.
- @di37 made the engine read the GPU generation once from `applegpu_gN`, and a virtual machine's GPU as none, in
[#209](https://github.com/ashhart/TensorFold/pull/209)
([device.py](https://github.com/ashhart/TensorFold/blob/v0.6.6/src/tensorfold/kernels/device.py)), as
Expand Down
48 changes: 48 additions & 0 deletions docs/receipts/vulkan-qmm-tile-m1/PREREG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# qmm_q4_tile pre-registration (written 2026-11-03 before any GPU run of this change)

## Question

Does storing each projection's weights, scales and biases in 32-column tiles (a load-time repack) make a Qwen3.5-2B run shorter
on the 13-inch M1, with byte-identical logits? The kernel is `qmm_q4_vec4` with only its address formulas changed, so every sum is the same.

## Arms (two ELFs of `tf-qwen35-model`, ReleaseSafe, aarch64)

- old: the vec4 build, commit `e742e023`, ELF `683fcf46...`.
- new: the same plus the tiled image: weights of every projection with n a multiple of 32 are repacked at load; the lm head is a second,
tiled copy of the embedding table (the embedding keeps the row layout). Projections with n = 16 keep the vec4 image.

## Gates (every process, both arms)

1. Exit 0 and a complete receipt (the house receipt check).
2. Generated ids equal the pinned ids.
3. Logits SHA-256 of every process equals one digest per case, the same digest the earlier vec4 runs gave (unicode-24 `e5447d9a...`,
document-256 `147a2d2e...`, document-1024 `7f9d8e65...`).
4. Before the pairs: the stage 1 probe on the GPU runs the tiled image on every tileable projection case and compares bytes to the default image.

## Metric and design

Run 2 duration from the probe's stderr milestones (prefill plus 63 decode steps plus the per-step logits readback), as before.
Three pairs per case, order old,new,new,old,old,new. Cases: unicode-24, document-256, document-1024.

## Decision rule

Speedup: median pair gain of at least 3 percent and all three pair gains positive and all gates hold. Otherwise reported as no speedup and not proposed.

## Forecast (a prediction; I do not know the answer)

Gain of 20 to 45 percent of run 2 on unicode-24 and similar on the documents. Why not more: qmm is about 63 percent of the run net of the submit
cost, the multi-row result showed qmm is not simply load-count limited, and the hardware cache may already hold part of the 1 KiB stride.
I give 20 percent to a gain under 3 percent, and 10 percent to a regression.

## Not claimed

Other chips, other models, other quantizations, the lm head on devices whose storage range is below the table size (they keep the row layout).
The software-device pass is not GPU evidence. Memory: the tied lm head adds about 320 MB of device memory.

## Addendum, written after the final pairs ran (2026-11-03)

The first contract run on the GPU refused the served shape 1x16x64: a registered kernel must serve every shape the contract serves, and the build
above kept the n = 16 projections on the vec4 image. The design changed before the final runs: the repack pads the last tile with zero columns, so the tiled
image serves every n, the n = 16 projections are tiled too, and the arms read "old: the vec4 build, new: the padded tile build". The gates, metric and
decision rule did not change. This addendum was written after the final pairs; the three earlier pairs on the unpadded build are kept in the receipts as an
earlier measurement and are not the result. The forecast above was for the unpadded build; it was not revised.
Loading
Loading