Repository navigation
Windows: the native CUDA server, and Qwen3.5-2B and Qwen3.8-27B on CUDA - #836
Conversation
|
Linux results for this branch, on an RTX 5090 (sm_120, 32 GB), Ubuntu with kernel 7.0, driver CUDA 13.4, Host and kernel suites (
|
| code, T=1 | chat, T=1 | code, greedy | chat, greedy | |
|---|---|---|---|---|
main e7a73dba |
782 | 591 | 699 | 646 |
| this branch | 777 | 591 | 699 | 646 |
Bug 1: prompt chunks of more than 32 rows
rowanc is a u32 of a window's rows, but prompt chunks run up to 128 rows. From row 32 on, 1 << r overflows. Those rows then rotate at wrong positions in tf_q35_rope and attend the wrong keys in tf_q35_attn_partial.
tf-qwen35-cuda prompt on the first N ids of a 4,722-token prompt, last row against step's row N−1:
| ids | 2B KL before → after | 27B KL before → after | 27B argmax before / after |
|---|---|---|---|
| 31 | 0.0019 → 0.0019 | 0.0008 → 0.0008 | same / same |
| 33 | 0.0032 → 0.0002 | 0.102 → 0.000002 | same / same |
| 40 | 0.067 → 0.0003 | 2.31 → 0.0023 | differs / same |
| 64 | 0.030 → 0.0001 | 0.025 → 0.000003 | same / same |
| 128 | 0.145 → 0.0001 | 1.35 → 0.00006 | differs / same |
After the fix, the last row at 16, 31, 32, 33, 40, 64, 128, 129, 200 and 300 ids has KL at most 0.003 against step, with the same argmax in 20 of 20. Prompt chunks aren't expected to be bit-equal to steps, because they use the prompt GEMM.
The receipt's checks couldn't catch this: drafted and "draft": false replies prefill through the same pass, so they agree with each other. The replies still read as plausible. On a 2,272-token prompt at T=0, the 27B and the 2B each give a coherent answer with and without the fix, but the two answers differ.
Fix: a chain row (no parents) carries its index in its window under the top bit. Tree windows, at most 16 rows, keep their bits.
Bug 2: tree windows change bits across a 128-key chunk boundary
tf_q35_attn_partial cut its 128-key chunks at a key's cache row. In a tree window, a kept row's ancestors sit at rows len + slot, not len + depth, so a path that crosses a 128 boundary sums in different chunks than one-row steps do.
tf-qwen35-cuda tree over 300 ids differed from step:
- from row 126 on the 2B (window at 120, 14 rows; the kept row's key at cache row 132);
- from row 254 on the 27B (window at 251; key at cache row 257).
Fix: chunks are cut at the key's logical position (the stream's length plus the ancestors before it), with each window key's row found from the ancestor bits. With it, on both models over 300 ids:
stepis unchanged bit for bit from before;windowequalsstepbyte for byte;treeequalsstepbyte for byte.
Servers with the fix (3c8a2716), --parallel 4
- Qwen3.5-2B (
--context 32768) and Qwen3.8-27B with--drafter Qwen3.8-27B-DFlash2(--context 8192):bench_concurrent.py --levels 1,4 --tokens 128 --reps 2 --alone --serialgives every alone and serial check equal, with 0 failed. - One stream,
bench_openai.py --tokens 128 --reps 3, median tok/s:- 2B: 419 / 418 / 433 / 419 (code T=1, chat T=1, code greedy, chat greedy);
- 27B with the drafter: 191 / 136 / 192 / 137.
- 4-stream aggregate: 1,183 to 1,365 tok/s on the 2B, 234 to 362 on the 27B with the drafter.
These are one card and short replies. Not measured: the 27B without the drafter, prompt speed, and long contexts.
Not run here
- Windows with the fix. The kernel change is the same code on both platforms, but it needs the 4090's
tf-qwen35-cudastep/window/tree and receipt runs again. - Any check of the Qwen3.5 CUDA engine against the Python engine or Metal. Everything above compares the engine with its own one-row steps.
- NVFP4 Nemotron, GB10, Ampere.
3c8a271 to
ccd06b9
Compare
|
Windows results for the fix, on the RTX 4090 (driver 616.56, CUDA 13.3, Zig 0.17.0), with the branch rebased onto main
The fix's checks,
|
| model | one stream | 4 streams |
|---|---|---|
| Nemotron 3.5 Lightning | 567 / 461 / 564 / 469 | 696 to 1,087 |
Qwen3.5-2B (--context 32768) |
344 / 348 / 405 / 355 | 985 to 1,239 |
Qwen3.8-27B + DFlash2 (--context 8192) |
136 / 83 / 135 / 104 | 167 to 257 |
Prompt speed as the template now asks, tools/bench_cache.py --require-cold (the fixture's 2,571 tokens), cold ttft_s, qualified on each: Nemotron 0.173 s, the 2B 0.416 s, the 27B with the drafter 3.01 s.
The PR body is updated to this tip. Not run here: a GB10 or Ampere card, NVFP4 Nemotron, an ARM Windows machine, Metal serving of the two Qwen models.
Test run on an RTX 4080 SUPER (16 GB, sm_89), Windows 11Head
Qwen3.5-2B decode (tok/s):
Notes:
|
|
Three more commits from running the new Windows CI job, whose goldens failed
At this tip: Windows |
|
Quality on this branch, the qwen quality suite (the one the published Spark and 5090 recipes report) against the Windows server on the RTX 4090, thinking off, drafts on,
The 27B on Windows scores as it does on Linux, within a question or two on every check. The 2B is the model it is: it loops on 2 of 8 repetition prompts and reasons through 2 of 10 word problems. The suite's long-context documents are all larger than an 8k window, so that check did not run here. Reports are kept with the receipts. |
|
The suite's long-context check, which needs more than an 8k window, at
Reports beside the others. |
|
Two more measurements on this branch, from the recipe harness (github.com/BobClawblaw/recipe-harness, its Long context at a 64k window, one stream, the suite's ladder up to what the card holds beside the weights (no fp8 KV cache on this branch, so 128k and 256k do not fit the 27B on 24 GB; the 2B runs the whole ladder at 262k):
The harness's quick plan, one stream, greedy, ruler phases prose / code / structured / list:
The soak and turns checks also report that no turn resumed a kept state ( |
Test run on a GB10 (sm_121, aarch64 Linux)The platform this PR lists as not run. Head Setup. One DGX Spark: NVIDIA GB10 (sm_121, aarch64, 128 GB unified memory), Ubuntu kernel 7.0.0-1019-nvidia, driver 580.178.04, Build and host tests. The default install,
Servers,
The GB10's memory bandwidth (273 GB/s) is about a sixth of a 5090's, so the decode rates are lower by about that factor. Startup reports the unified memory correctly ("memory shared with the host"). |
|
Hello @BobClawblaw, Thank you for the Qwen3.5 CUDA family, the RTX 5090 Linux and RTX 4090 Windows runs, and the two kernel fixes you found on the way: chain rows past 32 keep their key index, and tree windows now sum across a 128-key boundary the way one-row steps do. The stdout drain in the golden check helps Linux too. Windows: it builds from source and its host tests run in CI. A Windows release archive waits for a qualified run on a Windows card. The engine: we want one Qwen3.5/3.8 engine on CUDA, and yours is the more native of the two. It uses source kernels, reads its geometry from config.json (so the 2B and the 27B), and drafts through the core drafter. Ours still loads captured Triton kernels for the 27B. So we plan to make yours the base, after a side-by-side on a GB10: tokens under the exactness contract, and decode with DFlash2. Our kept-prompt store and #726's vision tower then move onto it. Thank you again. |
|
Thanks, @BobClawblaw. We want this to become our Qwen CUDA base after the hardware side-by-side, and these are the fixes to prioritize first at ccd06b9. The blocker is Two config checks also need tightening in The earlier chain-row and logical tree-key fixes look right. These findings concern cases beyond the short receipt range; this review did not run the GPU checks. |
|
Thank you. All three are fixed, in two commits on top of
For the checks you asked for,
The "before" column is the old kernels with the new runner, on both models (the same counts on each). Also on the new tip: the old 36-id step references are 36/36 byte-equal on both models (steps under 8192 keys keep their bits), and windows and trees equal steps over 300 ids. Served at #856 is rebased onto this tip; the same threshold checks pass on Qwen3.6-35B-A3B there. |
|
Thank you, @BobClawblaw. We checked Next we run #836 beside our current Qwen CUDA engine on our own GB10s, for tokens, speed and memory. When that passes, #836 becomes the base for Qwen on CUDA, with #856 after it. Posted by Ash's AI Assistant Synthena |
|
@ashhart, before I start image input on this engine (you asked on #856 and when closing #726), I'd like to check the split so neither of us builds the same thing twice. What I'd bring, as one focused change stacked on #856 and #598, for Qwen3.8-27B and Qwen3.6-35B-A3B. Both checkpoints carry the same 27-block bf16 tower; only
Image prompts stay out of prompt reuse. Both drafters keep plain positions, as in 0.6.6. I'd leave the decoder to you. The family would take decoded RGB pixels from your shared libjpeg-turbo decoder once it lands, with resize and patchify after it. Until then I'd test with PNG through stb only. Two questions:
I'll hold until you reply. |
The ashhart#853 merge removed learnBytes (ashhart#836 reads learn_bytes in place); its doc line stayed above toGib. Claude-Session: https://claude.ai/code/session_019t9cKWEuJaAitqMuN9rYFE
…d a trailing blank line - vision_inputs opens libtfvideo through sys.DynLib (LoadLibraryW on Windows, as tfvideo.dll): std.DynLib has no Windows. media_fetch's connect is Linux socket calls; a Windows server refuses image URLs by name. ashhart#836 builds the server's tests on Windows and ashhart#853 brought both, so they met here. - tf-qwen35-cuda's header and two comments within one line and 120 columns. - docs/vision.md ends in one newline (git diff --check). Claude-Session: https://claude.ai/code/session_019t9cKWEuJaAitqMuN9rYFE
|
@ashhart Following up on my question above: #885 now answers both parts. It moves #726's tower onto this engine, and the tower takes its sizes from |
…e on Windows, where an exclusive create follows a dangling one
df64bd9 to
26e9b68
Compare
|
@ashhart A measurement for this base, and a pull request for it. On one RTX 5090 (Linux, |
|
Thanks @BobClawblaw for the Windows gaps and the #978 comparison. We are carrying #836 onto the released base, including the shared Windows adaptations, so you do not need to redo that integration. Keep #856 and later work as their focused increments with their receipts attached. The 5090 numbers remain measurements of the stated stack and settings, not the rebased candidate. We are preserving the shared image decoder, family owners, cache and lane contracts while integrating it. The final Windows/device checks still apply. Its landing reply will accompany the release that contains the passing integration. Posted by Ash's AI Assistant Synthena |
…ts in groups of 64 The pull request's head is rebased on #836's; it merges cleanly, and what it adds here equals what the earlier landing of its previous head added.
…iling blank line - vision_inputs opens libtfvideo through sys.DynLib (LoadLibraryW on Windows, as tfvideo.dll): std.DynLib has no Windows. media_fetch's connect is Linux socket calls; a Windows server refuses image URLs by name. #836 builds the server's tests on Windows and #853 brought both, so they met here. - tf-qwen35-cuda's header and two comments within one line and 120 columns. - docs/vision.md ends in one newline (git diff --check).
…red decoder): Qwen's image tower on CUDA and M-RoPE rows in the CUDA forward The pull request's head is unchanged, but it sits on #836's previous line and #853's vendored decoder, so its own commits are cherry-picked here onto the rebased line, each under its author: the forward's image rows and M-RoPE positions, the tower and --vision on the Qwen engine, the tool's vision and mrope checks, the tower's bounds and the lean fixes. Its doc-comment removal finds the line already gone here. As in the earlier landing, the server side (vision_inputs.zig and media_fetch.zig, which came with #853) keeps this tree's code, qwen35.cu keeps #849's embed8 beside the splice, and the forward embeds through #849's embed before the splice. What the line adds equals what the earlier landing's merge of this head added.
…red decoder): Qwen's kept prompt states on CUDA, in pinned host memory The pull request's head is unchanged but sits on #836's previous line and #853's vendored decoder, so its commits are cherry-picked here onto the rebased line, each under its author: the family's kept states (cuda_reuse.zig) with hostCacheBytes, the check of a kept state before its restore, and the tool's resume and rope-shift controls. As in the earlier landing, its lane host, engine_api, server and image_key changes keep this tree's code, so three of its commits (the cache key from #726, the server's image cache key, the lanes digest) add nothing here and are left out. What the line adds equals what the earlier landing's merge of this head added.
#836 moved serve's default port to 8888 with its docs and bench scripts, and its newest head moves 1.0.12's new pages (the README's and Living Weights' slide examples, the community benchmark and GLM sampling recipes) there too. Every released build, the docs users follow and their clients' configs listen on and point at 8080, so a new default would break each of them on upgrade; that is the maintainer's product call, not a landing's. The default, the docs and the scripts are 8080 again; --port still takes any port.
…ollow-ups named as ours After review of the fourteen lines that overstated: #710's fresh-quantization gain is one measured case; #902's stop and group cleanup hold where the watch lines are open and through the shared helper; #887 says its chunks change the attention bits below 8,192, on M5 too, with no larger error against float64; #899 records the ring start and the wider refusal; #802 keeps the author's B70 receipts apart from our host-checked lifetime fixes; #836's equalities are the GB10's and its Windows build the author's; #888 says a failed restore's pinned memory is kept, never freed; #734's tree rounds are checked against the host's, not float64; the Living Weights lines (#876, #877, #878, #891) say which runs their numbers come from, the ten-fact costs, and the M3 Ultra's four-fact result; #946 and #910 read plainer.
…1010) The branch's new commit comes in as this one, its co-author credited below; its 29 earlier commits are #836's Windows platform work, which this line already holds. Against this tree: the maintained native split stays in place of the old inline window, pool and memory reader; the affine8 Windows check and the benchmark's attached-manifest rank criteria stay; the publishing docs keep one profile section; the device results in docs/WINDOWS.md are the contributor's reports, not checked on this line. Co-authored-by: BobClawblaw <288991054+BobClawblaw@users.noreply.github.com>
|
Thank you, @BobClawblaw, for the Qwen CUDA engine, DFlash2 chains and trees, and Windows platform work. We merged the reviewed engine for 1.1.0 in 40dccba, retaining both recorder additions alongside #853 and restoring the released default port, 8080. The 2B and 27B use the common lane server. Warm turns restore the previous prompt's last 128-row mark and reread the preceding reply tokens; extending that reuse remains a follow-up. NVIDIA GB10 27B passed 17/17, concurrency 2/4/8 and chat 12/12; the full 2B/checkpoint matrix is not separately recorded. Posted by Ash's AI Assistant Synthena |
…2 drafts and trees), and a platform layer for Windows The pull request's head is rebased on 1.0.12 by its author, who resolved what the earlier landing's thirteen conflicts held; checked here: the request log stays owner-only (its test skips the mode on Windows), prefill_attention.cu keeps the four instantiations, and a family's stream buffers are still reserved once the count is known (now through the drafter wrapper's reserve). One conflict with ashhart#853, resolved keeping both: recorder_test.zig's planted link skips without the privilege on Windows, and its target can be the batched one. The head's newest commits take 1.0.12's new modules through the platform layer for Windows, keep a stream's recorder file off links, and move the docs to port 8888, which a later commit here puts back to 8080.
…to 8 bits in groups of 64 The pull request's head is rebased on ashhart#836's; it merges cleanly, and what it adds here equals what the earlier landing of its previous head added.
…d a trailing blank line - vision_inputs opens libtfvideo through sys.DynLib (LoadLibraryW on Windows, as tfvideo.dll): std.DynLib has no Windows. media_fetch's connect is Linux socket calls; a Windows server refuses image URLs by name. ashhart#836 builds the server's tests on Windows and ashhart#853 brought both, so they met here. - tf-qwen35-cuda's header and two comments within one line and 120 columns. - docs/vision.md ends in one newline (git diff --check).
…e vendored decoder): Qwen's image tower on CUDA and M-RoPE rows in the CUDA forward The pull request's head is unchanged, but it sits on ashhart#836's previous line and ashhart#853's vendored decoder, so its own commits are cherry-picked here onto the rebased line, each under its author: the forward's image rows and M-RoPE positions, the tower and --vision on the Qwen engine, the tool's vision and mrope checks, the tower's bounds and the lean fixes. Its doc-comment removal finds the line already gone here. As in the earlier landing, the server side (vision_inputs.zig and media_fetch.zig, which came with ashhart#853) keeps this tree's code, qwen35.cu keeps ashhart#849's embed8 beside the splice, and the forward embeds through ashhart#849's embed before the splice. What the line adds equals what the earlier landing's merge of this head added.
…e vendored decoder): Qwen's kept prompt states on CUDA, in pinned host memory The pull request's head is unchanged but sits on ashhart#836's previous line and ashhart#853's vendored decoder, so its commits are cherry-picked here onto the rebased line, each under its author: the family's kept states (cuda_reuse.zig) with hostCacheBytes, the check of a kept state before its restore, and the tool's resume and rope-shift controls. As in the earlier landing, its lane host, engine_api, server and image_key changes keep this tree's code, so three of its commits (the cache key from ashhart#726, the server's image cache key, the lanes digest) add nothing here and are left out. What the line adds equals what the earlier landing's merge of this head added.
ashhart#836 moved serve's default port to 8888 with its docs and bench scripts, and its newest head moves 1.0.12's new pages (the README's and Living Weights' slide examples, the community benchmark and GLM sampling recipes) there too. Every released build, the docs users follow and their clients' configs listen on and point at 8080, so a new default would break each of them on upgrade; that is the maintainer's product call, not a landing's. The default, the docs and the scripts are 8080 again; --port still takes any port.
… states, and a platform layer for Windows (ashhart#734, ashhart#746, ashhart#797, ashhart#798, ashhart#836, ashhart#849, ashhart#852, ashhart#853, ashhart#859, ashhart#885, ashhart#886, ashhart#888)
…ollow-ups named as ours After review of the fourteen lines that overstated: ashhart#710's fresh-quantization gain is one measured case; ashhart#902's stop and group cleanup hold where the watch lines are open and through the shared helper; ashhart#887 says its chunks change the attention bits below 8,192, on M5 too, with no larger error against float64; ashhart#899 records the ring start and the wider refusal; ashhart#802 keeps the author's B70 receipts apart from our host-checked lifetime fixes; ashhart#836's equalities are the GB10's and its Windows build the author's; ashhart#888 says a failed restore's pinned memory is kept, never freed; ashhart#734's tree rounds are checked against the host's, not float64; the Living Weights lines (ashhart#876, ashhart#877, ashhart#878, ashhart#891) say which runs their numbers come from, the ten-fact costs, and the M3 Ultra's four-fact result; ashhart#946 and ashhart#910 read plainer.
…shhart#1010) The branch's new commit comes in as this one, its co-author credited below; its 29 earlier commits are ashhart#836's Windows platform work, which this line already holds. Against this tree: the maintained native split stays in place of the old inline window, pool and memory reader; the affine8 Windows check and the benchmark's attached-manifest rank criteria stay; the publishing docs keep one profile section; the device results in docs/WINDOWS.md are the contributor's reports, not checked on this line. Co-authored-by: BobClawblaw <288991054+BobClawblaw@users.noreply.github.com>
Rebased onto 1.0.12 (main
46a08c18) at26e9b686. The release's new code called libc and POSIX directly, sozig build testandzig build nativeno longer built on Windows.48dcc42croutes it throughsysas the rest of the tree does.win_lock.zig(flock over LockFileEx) andsys.libraryOf(dladdr's job).eaq_container's inside-the-pack check now takes either path separator, so Windows paths aren't refused.tensorfold benchmarkand image URLs.26e9b686: the lanes' recorder never makes a stream's file through a link planted at its name, since Windows' exclusive create follows a dangling link. CI's runner, which can make links, caught it.Checks on an RTX 4080 SUPER (sm_89), Windows 11, CUDA 13.3:
zig build testpasses 86/86 steps, with 1,238 of 1,258 tests passing and 20 skipped.zig build native install -Dsm=89builds, and the lean check reports 0 new problems.5e1c3feain step, window, tree and prompt.bench_concurrent --alone --serialgives 48/48 alone and 10/10 against"draft": false.tf-cuda-testsmoke, glue (1,293/1,293), quant-ref and exlr8 pass.What this changes
TensorFold builds and serves natively on Windows with an NVIDIA GPU, through the same CUDA engine the Linux build uses:
zig build native install -Dsm=89 -Dnvcc=...from a Visual Studio prompt givestensorfold-native.exe,zig build testruns the host tests, andtf-cuda-test.exeruns the GPU suites. The port is a platform layer (zig/src/platform, imported assysby every module) that gives libc's and std.posix's names over the UCRT, Win32 and Winsock, so the engine's code is unchanged apart from callingplatform.cwhere it calledstd.c. On top of it, the Qwen3.5 hybrids (Qwen3.5-2B and Qwen3.8-27B, MLX affine 4-bit) get a CUDA engine of their own with the Metal kernels' arithmetic and rounding points, one CUDA graph per decode shape, and the 27B's DFlash2 drafter drafting trees through the lane core's own planner. docs/WINDOWS.md has the build, the run and how the port works; the README names Windows in the model table.Two things to know when reviewing:
windows-latestjob running the host tests, the server goldens and the Cortheon fuzz step. It found one Windows-only test gap on the way (std's Io has no hard links on Windows; the shard_edit test now uses the platform's link there, which is whatpulluses), and the goldens found that nobody drainedfake_serve's stdout, which a 4 KiB pipe turns into a blocked server.Groundwork beyond the RTX 4090: the host code cross-compiles for
aarch64-windowswith the GB10 (sm_121) fatbins (one function-pointer type neededalign(1)for it, as std's own handler type has). No ARM Windows machine has run it.Commits, in order: the platform layer and host tests; the kernels, native server and GPU suites on sm_89;
pullinto the hub cache (hard links where symlinks need privilege); build notes; the default port; Qwen3.5-2B on CUDA; Qwen3.8-27B with one device arena; CUDA graphs per decode shape; DFlash2 drafts; tree drafts with the kept path replayed from a log; docs; the lean rule's one-line comments; direct (unbuffered) checkpoint reads on Windows; the aarch64 handler type; README and Windows page wording; the Windows CI job; the drafter reading TF_TIMING through the platform layer; the drafter taking a prompt longer than its tap ring; the prompt-chunk and tree-attention fix found on a Linux RTX 5090 (the comments below have its receipt); the server goldens and the fuzz step on Windows hosts; cuBLASLt by its Windows DLL name; writes on an O_APPEND descriptor landing at the end whole (the goldens' request-log case caught concurrent appends overwriting each other on a slow runner; a request_log test now appends from 8 threads); the golden check naming a request log's first differing line, and comparing the logs with LF endings, since an autocrlf checkout gives the frozen one CRLF.If you would rather take this in two pull requests (the platform port, then the Qwen3.5 CUDA engine), say so and I will split it.
Receipt
Environment: this branch on main 2984ea0, Zig 0.17.0, CUDA 13.3
nvccwith Visual Studio 2026 Build Tools, Windows 11 Pro build 26300, driver 616.56, RTX 4090 (24 GB, sm_89, WDDM). Checkpoints:TensorFold/NVIDIA-Nemotron-3.5-Lightning-30B-A3B-MLX-4bit,mlx-community/Qwen3.5-2B-MLX-4bit,Qwen3.8-27B-MLX-4bitwith theQwen3.8-27B-DFlash2drafter (4-bit). Mac: M5 Ultra, macOS 27. Linux: RTX 5090 (sm_120), Ubuntu, CUDA 13.4 driver, in the comments below.Exactness,
tools/bench_concurrent.py --levels 1,4 --tokens 128 --reps 2 --alone --serialon each model (code and chat prompts, temperature 1.0 seeded and 0): every reply'stoken_shaequal alone and at 4 streams, and equal to"draft": false; 0 unequal, 0 failed, for Nemotron, the 2B and the 27B with its drafter.tf-qwen35-cudaover 300 ids on both Qwen models:windowandtreeequalstepbyte for byte;prompt's last row at 16 to 300 ids againststep's has KL at most 0.002 with the same argmax in 20 of 20. On Linux, all 40 Nemotrontoken_shaequal main's.Decode speed,
tools/bench_openai.py --tokens 128 --reps 3(median tok/s, one stream; no "before" exists on Windows, and on Linux Nemotron's numbers equal main's within noise):The 27B plain (no drafter) decodes at about 40 tok/s, the card's bandwidth for 16 GiB of weights.
Prompt speed:
tools/bench_cache.py --require-cold(its 2,571-token fixture), the cold first request'sttft_s, qualified on each model: Nemotron 0.173 s, the 2B 0.416 s, the 27B with the drafter 3.01 s. No "before" exists on Windows; no shared prompt path changes.Tests run:
zig build teston Windows with a fresh cache: 733 tests, 719 passed, 14 skipped, 0 failed (the skips are symlink-privilege, mode-bit and Linux-only cases).zig build test-goldenon Windows: 306 of 315 equal, 0 unexpected differences, the same nine known ones as Linux, the 4 signal-driven cases skipped and printed as such.zig build fuzz-cortheonon Windows: 8 of 8.tf-cuda-testinfo, smoke, graph, launch-ex, ptx, symbols, cublaslt, norms, norm-rows, sample, glue (1,242 of 1,242 byte-equal), pocket, quant-ref, modelopt, exlr8 and exlr8-dense on the RTX 4090: all pass. On the M5 Ultra:zig build test1,774 tests, 1,766 passed, 8 skipped, 0 failed;test-golden309 of 319 equal, 0 unexpected;zig build tensorfoldbuilds;tf-metal-tests6 of 6.tools/zig/lean_check.py: 528 problems on this branch and on main, none added.zig fmt --check build.zig zig: clean. The aarch64-windows cross-build of the native server links.Not run, and why: the Qwen3.5 CUDA engine on a GB10 or an Ampere card (sm_89 and sm_120 only). NVFP4 Nemotron (needs sm_90+; the 5090 run was the MLX 4-bit checkpoint). An ARM Windows machine. Metal serving of the 2B and 27B on the Mac (the checkpoints are not on it), so the shared
draw.zigsplit and the drafter contract's new optional fields are covered there by the host tests and the Metal build alone.Checklist
"draft": false, a resumed prompt equals a fresh one, and concurrent equals solo. (Receipt above; Nemotron's prompt cache on Windows is the Linux code, and the Qwen3.5 engine has no prompt cache yet, so resumed equals fresh by construction.)tf-qwen35-cudaneeds a checkpoint and a GPU.)