From 8ec9ee5339561c36959684848655372e87ad73a1 Mon Sep 17 00:00:00 2001 From: ashhart <71890694+ashhart@users.noreply.github.com> Date: Sat, 7 Nov 2026 00:57:12 +0000 Subject: [PATCH 1/5] gemma4 host: pin affine_mm.metal's bytes after cf10a6b7d's comment edit Step: test-gemma4-affine-host. Error: "landed Metal source bytes are pinned without shader compilation" fails expectEqualStrings: expected d8f47cbb..., found a91a3539... Cause: cf10a6b7d rewrapped one comment in core/affine_mm.metal to fit 120 columns and left the pin at the old bytes (the file at 6d412e724 hashes to the old pin; that comment is the only change since). Not macOS-only: the step is built on every OS but is not in zig build test, so no CI job runs it. The lane_projection pin already matches. --- zig/gemma4_shared_affine_test.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/zig/gemma4_shared_affine_test.zig b/zig/gemma4_shared_affine_test.zig index 5c2b25871b..2f52f224c3 100644 --- a/zig/gemma4_shared_affine_test.zig +++ b/zig/gemma4_shared_affine_test.zig @@ -103,7 +103,7 @@ test "landed Metal source bytes are pinned without shader compilation" { { var hash: [32]u8 = undefined; std.crypto.hash.sha2.Sha256.hash(ks.core_affine_mm, &hash, .{}); - try std.testing.expectEqualStrings("d8f47cbbddf24502ca2c16cfdef0672731cc2dbe59ad027f47a3e662bcf8f627", &std.fmt.bytesToHex(hash, .lower)); + try std.testing.expectEqualStrings("a91a353981c4e24142673ab9c97e9a449b973751bd5df7ad7c980757046cf61e", &std.fmt.bytesToHex(hash, .lower)); } { var hash: [32]u8 = undefined; From 18864abb5ad08ff026c00d9bf7e51e6892c5c47f Mon Sep 17 00:00:00 2001 From: ashhart <71890694+ashhart@users.noreply.github.com> Date: Sat, 7 Nov 2026 00:58:03 +0000 Subject: [PATCH 2/5] build: the compile-only Metal contract runners import masked_sources too Steps: compile-fsr-metal-contract-host and compile-gemma4-contract-host. Error: "no module named 'masked_sources' available within module 'contract_main'" ('runner' for Gemma), from tests/contract/masked_attention_more.zig:5. Cause: 17e6242a7 made metal_main.zig import masked_sources and gave it to tf-kernel-contract (kernel_contract.zig) but not to these two compile-only copies of the same runner. Not macOS-only by logic: both steps exist only on macOS, and no CI job builds them. --- zig/build/fsr.zig | 1 + zig/build/gemma4_host.zig | 1 + 2 files changed, 2 insertions(+) diff --git a/zig/build/fsr.zig b/zig/build/fsr.zig index 02855d5a06..06dfc5c8e1 100644 --- a/zig/build/fsr.zig +++ b/zig/build/fsr.zig @@ -25,6 +25,7 @@ pub fn deviceSteps(b: *std.Build, metal: *std.Build.Module, engine: *std.Build.M b.step("test-fsr-metal", "GPU: uncached four-slot synthetic MoE equals resident at every node and logit").dependOn(&run.step); b.step("compile-fsr-metal-gate-host", "Compile the real FSR device gate without shader compilation or execution").dependOn(&device_test.step); const contract = b.createModule(.{ .root_source_file = b.path("zig/tests/contract/metal_main.zig"), .target = b.graph.host, .link_libc = true, .imports = &.{ .{ .name = "metal", .module = metal }, .{ .name = "tensorfold", .module = engine }, .{ .name = "kernel_sources", .module = sources }, .{ .name = "contract", .module = @import("kernel_contract.zig").module(b, b.graph.host, .debug) }, .{ .name = "h3_kernels", .module = b.createModule(.{ .root_source_file = b.path("zig/kernels/metal/h3/kernels.zig") }) }, .{ .name = "ling_metal", .module = b.createModule(.{ .root_source_file = b.path("zig/kernels/metal/ling/sources.zig") }) }, .{ .name = "ling_route", .module = b.createModule(.{ .root_source_file = b.path("zig/src/families/ling/route.zig") }) } } }); + contract.addImport("masked_sources", b.createModule(.{ .root_source_file = b.path("zig/masked_value_sources.zig"), .imports = &.{.{ .name = "kernel_sources", .module = sources }} })); const compile_contract = b.addObject(.{ .name = "fsr-metal-contract", .root_module = b.createModule(.{ .root_source_file = b.path("zig/fsr_contract_compile.zig"), .target = b.graph.host, .link_libc = true, .imports = &.{.{ .name = "contract_main", .module = contract }} }) }); _ = compile_contract.getEmittedBin(); b.step("compile-fsr-metal-contract-host", "Compile the actual Metal contract entry with inert embedded bytes and no execution").dependOn(&compile_contract.step); diff --git a/zig/build/gemma4_host.zig b/zig/build/gemma4_host.zig index 36958cb157..8fde58faae 100644 --- a/zig/build/gemma4_host.zig +++ b/zig/build/gemma4_host.zig @@ -40,6 +40,7 @@ pub fn compileHost(b: *std.Build, metal: *std.Build.Module, engine: *std.Build.M runner.addImport("h3_kernels", b.createModule(.{ .root_source_file = b.path("zig/kernels/metal/h3/kernels.zig") })); runner.addAnonymousImport("ling_metal", .{ .root_source_file = b.path("zig/kernels/metal/ling/sources.zig") }); runner.addAnonymousImport("ling_route", .{ .root_source_file = b.path("zig/src/families/ling/route.zig") }); + runner.addImport("masked_sources", b.createModule(.{ .root_source_file = b.path("zig/masked_value_sources.zig"), .imports = &.{.{ .name = "kernel_sources", .module = sources }} })); const contract_root = b.createModule(.{ .root_source_file = b.path("zig/gemma4_contract_compile.zig"), .target = b.graph.host, .link_libc = true, .imports = &.{.{ .name = "runner", .module = runner }} }); const contract_object = b.addObject(.{ .name = "gemma4-contract-compile", .root_module = contract_root }); _ = contract_object.getEmittedBin(); From f87d2e27a424ee3bb2f8028474c9f158a8fa5a97 Mon Sep 17 00:00:00 2001 From: ashhart <71890694+ashhart@users.noreply.github.com> Date: Sat, 7 Nov 2026 00:59:28 +0000 Subject: [PATCH 3/5] build: compile-metal-residency-host's native module imports kimi_k3 Step: compile-metal-residency-host. Error: "no module named 'kimi_k3' available within module 'host'" at native/kimi_metal.zig:4. Cause: 2c4fa0c1a made native/metal.zig open Kimi K3 through kimi_metal.zig, and 57ff96d51 wired kimi_k3 and its inert metallib into slide_host.zig's copy of that module but not into this one. The inert bytes are now one module shared by Drama and Kimi (a file roots one module). Not macOS-only by logic: the step exists only on macOS and no CI job builds it. test-metal-formats-host, on the same module, still passes. --- zig/build/residency_host.zig | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/zig/build/residency_host.zig b/zig/build/residency_host.zig index 55378f3021..e184514552 100644 --- a/zig/build/residency_host.zig +++ b/zig/build/residency_host.zig @@ -23,18 +23,20 @@ pub fn steps(b: *std.Build) void { pub fn compileHost(b: *std.Build, metal: *std.Build.Module, engine: *std.Build.Module, lanes: *std.Build.Module) void { const api = b.createModule(.{ .root_source_file = b.path("zig/src/core/engine_api.zig"), .target = b.graph.host, .link_libc = true, .imports = &.{.{ .name = "lanes", .module = lanes }} }); const host = b.createModule(.{ .root_source_file = b.path("zig/src/native/metal.zig"), .target = b.graph.host, .link_libc = true, .imports = &.{ .{ .name = "metal", .module = metal }, .{ .name = "tensorfold", .module = engine }, .{ .name = "engine_api", .module = api } } }); - const bytes = b.addWriteFiles().add("residency-drama-empty.zig", "pub const bytes: []const u8 = &.{};\n"); + const bytes = b.createModule(.{ .root_source_file = b.addWriteFiles().add("residency-drama-empty.zig", "pub const bytes: []const u8 = &.{};\n") }); const tokenizer = b.createModule(.{ .root_source_file = b.path("zig/src/core/tokenizer/tokenizer.zig"), .target = b.graph.host, .link_libc = true }); const rounds = b.createModule(.{ .root_source_file = b.path("zig/src/core/frame_rounds.zig"), .target = b.graph.host }); const shim = b.addWriteFiles().add("residency-drama-core.zig", "pub const safetensors = @import(\"tensorfold\").safetensors;\npub const tokenizer = @import(\"tokenizer\");\npub const frame_rounds = @import(\"frame_rounds\");\n"); const core = b.createModule(.{ .root_source_file = shim, .target = b.graph.host, .imports = &.{ .{ .name = "tensorfold", .module = engine }, .{ .name = "tokenizer", .module = tokenizer }, .{ .name = "frame_rounds", .module = rounds } } }); - const drama = b.createModule(.{ .root_source_file = b.path("zig/src/families/drama3/drama3_metal.zig"), .target = b.graph.host, .link_libc = true, .imports = &.{ .{ .name = "metal", .module = metal }, .{ .name = "core", .module = core }, .{ .name = "drama3_metallib", .module = b.createModule(.{ .root_source_file = bytes }) } } }); + const drama = b.createModule(.{ .root_source_file = b.path("zig/src/families/drama3/drama3_metal.zig"), .target = b.graph.host, .link_libc = true, .imports = &.{ .{ .name = "metal", .module = metal }, .{ .name = "core", .module = core }, .{ .name = "drama3_metallib", .module = bytes } } }); host.addImport("core", core); host.addImport("tokenizer", tokenizer); host.addImport("drama3", drama); // native/metal_slide.zig imports living: the engine module's own (a file belongs to one module) host.addImport("living", engine.import_table.get("living") orelse @panic("the engine has no living import")); @import("qwen4.zig").metalNative(b, b.graph.host, host); + host.addImport("kimi_k3", @import("metal_targets.zig").kimiModule(b, b.graph.host, .Debug, metal, engine, lanes)); + host.addImport("kimi_metallib", bytes); b.step("test-metal-formats-host", "Actual Metal format offers with inert images and no device").dependOn(&b.addRunArtifact(b.addTest(.{ .root_module = host, .filters = &.{"Metal capabilities:"} })).step); const root = b.createModule(.{ .root_source_file = b.path("zig/residency_native_compile.zig"), .target = b.graph.host, .link_libc = true, .imports = &.{.{ .name = "host", .module = host }} }); const object = b.addObject(.{ .name = "metal-residency-native", .root_module = root }); From 96dbedb1f7f30d5c209b6165aea7d876e4101d15 Mon Sep 17 00:00:00 2001 From: ashhart <71890694+ashhart@users.noreply.github.com> Date: Sat, 7 Nov 2026 01:03:21 +0000 Subject: [PATCH 4/5] glm metal: the backend fact tests give facts() a host device object Step: test-shared-head-cost-metal-host. Error: "shared head costs: GLM Metal MTP retains costed copy grants" terminated with signal ABRT, "Segmentation fault at address 0x10" in objc_msgSend from Device.responds <- generation <- tensorUnits <- Backend.facts. Cause: ae320aafb made facts() ask e.device.tensorUnits(), but this test and "zero grant: GLM Metal MTP resumes after a long priced skip" (test-drafter-skip-metal-host) leave the Engine undefined, so facts() messages an uninitialized id. The outcome changes from process to process: on origin/main the shared-head-cost binary aborted in 15 of 20 direct runs, and the zero-grant binary segfaulted in 20 of 20 direct runs while passing under the build runner. Both tests now set the NSObject host object that draft_sources_metal_test.zig already uses: it names no Metal architecture, so tensorUnits() is false without a GPU. Fixed binaries: 20 of 20 each. Not macOS-only by logic: the tests build only on macOS. --- zig/src/families/glm/backend.zig | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/zig/src/families/glm/backend.zig b/zig/src/families/glm/backend.zig index 70609d6f99..0365621332 100644 --- a/zig/src/families/glm/backend.zig +++ b/zig/src/families/glm/backend.zig @@ -441,6 +441,10 @@ test "zero grant: GLM Metal MTP resumes after a long priced skip" { var e: Engine = undefined; e.w = &w; e.ep = null; + const objc = @import("metal").objc; + const no_arch = objc.msg(objc.Id, objc.class("NSObject"), "new", .{}); // facts() asks it for tensor units, no GPU + defer objc.release(no_arch); + e.device = .{ .id = no_arch }; var slots: [16]slots_mod.Slot = undefined; var sl: slots_mod.Slots = undefined; sl.e = &e; @@ -457,6 +461,10 @@ test "shared head costs: GLM Metal MTP retains costed copy grants" { var e: Engine = undefined; e.w = &w; e.ep = null; + const objc = @import("metal").objc; + const no_arch = objc.msg(objc.Id, objc.class("NSObject"), "new", .{}); // facts() asks it for tensor units, no GPU + defer objc.release(no_arch); + e.device = .{ .id = no_arch }; var slots: [16]slots_mod.Slot = undefined; var sl: slots_mod.Slots = undefined; sl.e = &e; From 52cc3509d2af65a974b66c93a532575dd99b56d9 Mon Sep 17 00:00:00 2001 From: ashhart <71890694+ashhart@users.noreply.github.com> Date: Sat, 7 Nov 2026 04:36:57 +0000 Subject: [PATCH 5/5] changelog: #2409 under Unreleased --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 583887edda..681951936b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ Each release's page on GitHub has its notes and measurements. See [the 1.0.0 rel ## Unreleased +- Tests: four host steps that no CI job runs build and pass again: `test-gemma4-affine-host` pins `affine_mm.metal`'s current bytes, the compile-only Metal contract runners import `masked_sources`, `compile-metal-residency-host` imports `kimi_k3`, and the GLM Metal fact tests give `facts()` a host device object instead of an undefined one, which crashed them at random (#2409, part of #2031). + - Flash Next on CUDA with `TF_FLASHNEXT_KV_PAGES`: a shared round's grouped draft head reads and writes each stream's own pages, so paged streams' replies equal their runs alone again, and paged decode runs as fast as unpaged; default settings are unchanged (#2244). Thanks to @Arminova. - Tests: `zig build test-glm-snapshots-host` builds again: its copies module imports `core`, which `cuda_snaps.zig` reads for `TF_GLM_PROMPT_TAIL` since #2226 (#2403). Thanks to @Bizuayeu.