Skip to content

DeepSeek V4.1: the family — configuration, Engram tables, resident weights and the Metal operators - #825

Open
akol1 wants to merge 14 commits into
ashhart:mainfrom
akol1:pr-dsv41-bc-family
Open

akol1 wants to merge 14 commits into
ashhart:mainfrom
akol1:pr-dsv41-bc-family

Conversation

@akol1

@akol1 akol1 commented Oct 14, 2026 •

Copy link
Copy Markdown
Contributor

Piece (b)+(c)'s kernel side of the #690 split, rebased onto the 1.1.0 release (76aafcf27) and stacked on #824: the DS4.1 Flash family — configuration, Engram table staging, resident weight staging — and the Metal operators with their fp64-dot reference and qualification runner. The fp64 test harnesses follow in the next piece.

What lands:

  • Configuration and weight loading for the resident model: the text_config parse, the two ~101 GB Engram tables, 413 GB of EXL3 weights staged resident on one Mac, under the 70%-of-RAM default with GLM_LOAD_LIMIT_GB as the opt-in.
  • The pack-shape fixes jayleaton's 2.8bpw repack surfaced: a shard contributes only the tensors the index maps to it (his native.safetensors duplicates 11 tensors across shards, and the index decides ownership); the router gate and bias ride the pack's dtypes, as do the Engram gate weights (f32 or bf16).
  • The kept-dense EXL3 form (bf16 .weight + f16 .suh/.svh, no trellis) for rank-split repacks, with the reference apply.
  • The Metal operators with the fp64-dot reference and the qualification runner: plan, teacher, oracle, seams, trace, the same-input captures and the profile modes, plus the GPU counters. THIRD_PARTY_NOTICES attributes the MIT inference reference.
  • The token map from DeepSeek V4.1: the Engram token map built in Zig, byte-identical to the release's #824 is carried here too, so the family branch is self-contained for load-time use.

Host tests: test-deepseek-v41-weights (13/13) and the shard-plan tests (the duplicate-tensor case, the noncontiguous-table case, the basename escapes) — green. zig build test -Dcpu=apple_m1: exit 0, 304/304 steps; zig build native -Dcpu=apple_m1: 54/54 steps. Both packs load end to end; the lanes receipt shows 0 bit disagreements between two slots and their solo runs on either pack; the oracle on this series reproduces the retained 4.0960584769% whole-model number digit-for-digit.

Rebase notes: this branch resolves onto 1.1.0's rewritten shared infrastructure — the new paced_read admission gauge, the tokenizer/engine module ownership, and the merged metal.zig families table — rather than carrying the older forms. One rebase-residue commit folds a duplicate counters binding and a brace. The comment-only tidy commit keeps the whole family inside the lean checker's 120-column, one-line-comment rule: tools/zig/lean_new.py reports 502 problems against 503 on the 1.1.0 base, 0 new.

Receipts and measurement logs live on the fork beside the branch; the tree carries the tests that regenerate them. #826 stacks on this branch.

@akol1

akol1 commented Oct 14, 2026

Copy link
Copy Markdown
Contributor Author

The desktop check's single failure — security-purge-runtime.test.ts's "seeded data is not present on disk after a successful purge" — is the app suite's own flake, not this series: the same test failed on main's own runs on June 23 (two failures at 14:43 and 14:45, then a pass at 15:07), and it passes on the token-map and lane pieces' heads. Nothing in this series touches the app's purge path; 454/455 with one assertion failing on a disk-state check is the timing shape of that flake. I can't re-run the job without admin rights; a maintainer re-run should go green.

@ashhart

ashhart commented Oct 17, 2026

Copy link
Copy Markdown
Owner

Thank you, @akol1, we will carry the Metal loader and operator work with your credit into the Metal backend of the single DeepSeek family; the landing order and remaining checks are in our reply on #827.

Posted by Ash's AI Assistant Synthena

@ashhart ashhart mentioned this pull request Oct 17, 2026
@akol1

akol1 commented Oct 18, 2026 •

Copy link
Copy Markdown
Contributor Author

Status on this head (645a96ad): the only non-green check is desktop in run 38524199347. It fails one test, app/electron/main/host/security-purge-runtime.test.ts:222, with seeded data is not present on disk after a successful purge — 454/455 passed. This branch changes only build.zig, THIRD_PARTY_NOTICES.md and files under zig/; it does not touch app/, so the failure is unrelated to the branch. fmt, zig and mobile are green on the same head, and the second desktop entry is the earlier cancelled run, not a failure.

I cannot re-run workflows on this repository (no admin rights). A re-run of desktop should settle it if, as it looks, the failure is the purge-timing flake.

Separately, GitHub reports the branch as conflicting with current main: it was cut from c126fe16 on 2026-10-13 and main has moved since. I can rebase this branch onto current main, force-push it here, and follow with #826 and #827 in order — or hold until the landing order is settled. Which do you prefer?

akol1 added 14 commits October 18, 2026 13:41
…he release's

tf-dsv41-token-map derives the compressed key map from the original's
tokenizer.json and inference config with no Python step: decode each id,
fall back to the raw token when the decoder emits U+FFFD, normalize
(NFKD, strip category-M marks, lowercase, the whitespace replaces, strip —
the Rust chain NFKC, NFD, StripAccents, Lowercase, replaces, strip
composes to this), dedupe first-seen. The multipliers come from numpy's
PCG64 exactly: SeedSequence's hashmix pool, the XSL-RR output and Lemire's
bounded draw, checked against the release's recorded words. The generated
nfkd_data.zig comes from unicodedata 16.0.0 (the version unicode_data.zig
pins); the generator stays in the fork. The golden test is the release's
own 517,680-byte engram-token-map.bin, reproduced byte for byte.
… staging

Config parsing, the Engram hash layout, table plans over the original shards' embed
weight and scale, and the EXL3 resident weight staging behind paced reads. Host tests
cover exact integer boundaries, table selection and the staged bytes. On a 512 GiB Mac
the resident payload is about 385 GiB; freeBytes counts reclaimable speculative pages
so the load admission sees real room. No kernels; the served engine comes with the
lane backend.
… qualification runner

The Engram hash/commit, sparse and compressed attention (CSA2), the hyperconnection
mix, routed and shared experts, the cache and rotary glue as source Metal kernels,
with zig/tests/deepseek_v41_reference as the independent CPU oracle: plain reductions
in fp64, no native statistics, no injected coefficients. tf-dsv41-run drives admission,
teacher-forced logits, chunk seams, same-input operator replays and profiling on the
resident checkpoint. The retained whole-model gate stays failed at 4.0961% NRMS
against the unchanged 1% limit; operator checks are evidence, not qualification.
…ps to it

Rank-split packs (jayleaton's 2.8bpw) duplicate whole-load tensors in
native.safetensors beside the per-layer shards; the index's weight map decides
each name's owner file, so the plan no longer refuses the pack as duplicates.
The released checkpoints store the gate f16 and its bias f16; rank-split repacks
store the weight bf16 and the bias f32. The dense kernel's weight dtype and the
route kernel's bias dtype already decode all three, so the loader accepts the
range and the encoders follow the stored tensor.
The gate kernel decodes its q/k weights by dtype, the Gate descriptor carries it,
and the loader accepts the rank-split packs' bf16 projections beside the released
f32 ones.
Jayleaton's 2.8bpw stores the compressor and indexer matrices as a bf16 weight
beside the rotation scales, and prefers it over the trellis copy it also ships.
The exl3 module gains the dense form: a plain product kernel over the rotated
rows writing the sums region the rotate_out stage reads, so the stage
composition and its bit boundaries stay the ones the reference defines. The
loader rides the pack's dtypes for the router gate, bias, Engram gate weights
and the indexer's weights projection; the reference takes the union and names
the dense apply it still owes. The 2.8bpw plan passes: 406.6 GB, 44 shards.
The fp64 oracle's dense path mirrors the quantized one's boundaries: the input
rotation keeps its f16 rounding, the product is an FP64 dot over the f16-rounded
rotated row and the bf16 weight, and the output stages are the rotate_out
contract. The reference errors until now named the missing half; the union is
complete.
…ument, and the duplicate-tensor case stops logging at error level

d58f281f grew Plan.add an owned-index argument and left the in-file tests
calling the old shape; the duplicate-tensor path also logged at error level,
which the test runner treats as a failure even where the test expects the
error.
…ests

The generated nfkd_data.zig now comes from tools/zig/nfkd_tables.py, a peer
of tools/zig/unicode_tables.py: it rebuilds the table from unicodedata
16.0.0 and zig fmt, and its --check fails on drift. The table is one file
with a generated header, and lean_check.py's generator-output exemption
covers it, as it already covers the Flash Next generator outputs.
tf-dsv41-token-map's test blocks were never reachable because the build
registered the program only as an executable; a host_tests flag in the
build's engine programs table makes zig build test run them, so the
normalizer chain and the PCG64 draws are gated from now on. The tests'
Hangul case expected a recomposed syllable; NFKD decomposes it and the
release's chain never recomposes, so the expectation and a comment say so.
…-column rule

Folds the remaining comment groups in the V4.1 reference, family, backend,
native and Metal sources into single lines of at most 120 columns, keeping
the technical content. The lean budget counts a comment group as a
multi-line comment, so this keeps the tree at its pinned baseline.
@ashhart

ashhart commented Oct 19, 2026

Copy link
Copy Markdown
Owner

Thanks @akol1, alongside @jayleaton's CUDA work. 1.1.1 includes the reconciled shared configuration, checkpoint descriptors, stored parameter precision and Engram table contracts. The complete Metal backend remains separate. The refreshed 1eccf66 stack's kept-dense EXL3, dtype-aware Engram/router loads, indexed-shard handling and reference/table-generator updates go through the credited 1.1.2 family carry and loader checks. Keeping this PR open: the reference repairs, real MTP and whole-model/residency gates remain outstanding, and no release binary serves DeepSeek checkpoints yet.

Posted by Ash's AI Assistant Synthena

@akol1

akol1 commented Oct 19, 2026

Copy link
Copy Markdown
Contributor Author

Family stack refreshed onto the 1.1.1 main, and the five carry risks

The 12-commit family stack is now rebased onto d1018b318 (1.1.1 main) rather than 76aafcf27, head 4f69fea10, and pushed to akol1/TensorFold as pr-dsv41-bc-family-11 so it is a mergeable base-main view if that helps the carry. Shape against d1018b318: 12 commits, 64 files, +8564/-2.

Upstream-owned files are touched additively only: build registration in zig/build/metal_targets.zig, the new sources in zig/kernels/metal/sources.zig, exports in zig/src/tensorfold.zig, the generated NFKD header now naming tools/zig/nfkd_tables.py (one comment line), the THIRD_PARTY_NOTICES entry for the MIT reference, and the EXL3 projection wiring left as before. No file is rewritten or dropped.

Refresh evidence at 4f69fea10, M3 Ultra (80-core GPU, 512 GB unified memory), macOS 26.6.2, Zig 0.17.0 from .zig-version:

  • zig build test-deepseek-v41 test-deepseek-v41-reference test-deepseek-v41-weights test-dsv41-token-map test-dsv41-shared - 55/55 steps, 166/167 tests (1 skipped): 78/78, 13/13, 13/13, 10/10, 52 pass and 1 skip.
  • zig fmt --check zig and git diff --check clean; lean_check.py - 444 findings, the identical set at d1018b318 and at this head (0 added).
  • Not run: CUDA, Linux, GPU or whole-model qualification.

The five carry risks, answered rather than left implicit:

  • R1. The zig/src/families/deepseek_v41/metal/ layout stays family-local here; moving it into a shared Metal layout belongs with the backend relocation, and this branch does not pre-empt it.
  • R2. Kernel-contract registrations for the new kernels are not in this branch. Each entry needs the contract harness and the launch registry, so they travel with the kernel-contract work and are not claimed by the carry.
  • R3. NFKD generator convention is settled: tools/zig/nfkd_tables.py, header included, replacing the fork-local path.
  • R4. The generator's provenance header is in 4e81059d7.
  • R5. No GPU benchmark receipt is claimed for the new operators; the reference and qualification runs are host-side, and the whole-model/residency receipt remains the DeepSeek V4.1: the lane backend — one stream per slot on the lane core #827 gate.

#825 stays the tracking PR and no action is requested on this comment; the branch is only there as a base-main reference for the carry. DeepSeek serving still waits for the whole-model gate.

@ashhart

ashhart commented Oct 20, 2026

Copy link
Copy Markdown
Owner

Thanks for the DeepSeek V4.1 work. We reviewed the whole stack (#825, #826, #827, #1051, #1052) together against the upcoming 1.1.2 line, and it can't land as it stands. Three bugs need fixing first:

  1. Shared API break (all five PRs). zig/src/core/gpu_counters.zig's Recorder.beginSpan gains a metadata: u32 argument, but zig/src/metal_counter_consumer.zig still calls it with two (lines 84 and 210), so the metal_counters build step no longer compiles. zig build test doesn't build that consumer, which is why CI passes.
  2. zig/tests/deepseek_v41_reference/operator_csa2_index_fp64.zig:240 (DeepSeek V4.1: fp64 reference tests for the attention and hyperconnection operators #826, DeepSeek V4.1: the lane backend — one stream per slot on the lane core #827, DeepSeek V4.1: fp64 reference tests for the Engram hash, gather and gate #1051, DeepSeek V4.1: the FP32 and FP64 oracles run side by side #1052). for (cells[128..], cells[0..128]) zips slices of different lengths (130,944 and 128 at 1,024 keys). Debug builds panic; ReleaseFast drops the length check, so the tie case only passes by accident.
  3. zig/tests/deepseek_v41_reference/projection.zig:338 (DeepSeek V4.1: the FP32 and FP64 oracles run side by side #1052). @as(u8, 1) << @intCast(i % 16) shifts a byte by up to 15 and packs 16 sign bits into each byte. Debug panics, and the frozen baseline built on it needs regenerating once the packing is fixed.

Two notes. #1051's new Engram gate passes its bound, but five BF16 words differ from the contract, so it's a bound pass rather than bit identity; its two gather layouts are bit-equal. And the stack has split into two lines: #826 then #1051 (precision), and #826's first 26 commits then #827 then #1052 (serving). #825's 14th commit and #826's last five are in neither serving PR.

Since these branches were cut, 1.1.2 has moved config, load and state, the KV state, the runner and the pack core onto shared owners, so they no longer apply cleanly. What we'd suggest: fix the three bugs, pick one lineage (precision with #1051, or serving with #1052), and rebase onto 1.1.2 once it's out, keeping the current typed CLI and backend-neutral config/load/state rather than the older whole files. A small first PR that could land on its own: tools/zig/nfkd_tables.py with generated-table provenance and a pinned Unicode 16.0.0 check.

Leaving the PRs open meanwhile.

- Synthema, Ash's AI Assistant.

@akol1

akol1 commented Oct 21, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for reviewing the whole stack at once — the three bugs are all real, and I reproduced each one before touching anything.

Bug 2 — verified and fixed. cells is spec.keys * 128, so at 1,024 keys cells[128..] is 130,944 elements against cells[0..128]'s 128. Debug panics for loop over objects with non-equal lengths at operator_csa2_index_fp64.zig:241; ReleaseFast passes with the length check dropped, exactly as you said. Replaced with an indexed broadcast of row 0:

for (cells[128..], 0..) |*dst, i| dst.* = cells[i % 128];

I checked this is bit-identical to the slow row := row 0 form across keys = 1, 2, 3, 8, 1024, 4096 with NaN, -0.0, inf and subnormal payloads, in Debug and ReleaseFast. Fixed on the #826, #827, #1051 and #1052 heads.

Bug 3 — verified and fixed. @as(u8, 1) << @intCast(i % 16) shifts a byte by up to 15. Reproduced: Debug panic: integer does not fit in destination type, ReleaseFast green. I confirmed the intended layout from three independent consumers that agree — the local scale() reading a little-endian u16 per 16 values at bit i % 16, the m.k * 2 / 16 sizing in core/exl3_projection.zig, and the Metal kernel tf_exl3_scale doing p[i / 16] >> (i % 16). The packing is now one bit per value, 16 values per little-endian word:

var signs: [block_size / 8]u8 = @splat(0);
for (0..block_size) |i| {
    if (i % 3 == 1) signs[2 * (i / 16) + (i % 16) / 8] |= @as(u8, 1) << @intCast(i % 8);
}

Producer and consumer now agree at 128/128 positions. The regression is contained to the #1052 line: #827 never had this producer, so only #1052 (and our internal carry) needed it.

The frozen baseline is regenerated, and you were right that it had to be. 8 of 16 frozen_rotation words change (only indices 8–15) and all 16 frozen_narrowed words change. That partial change is the causal signature rather than a rubber stamp: old and new packing differ at 56 of 128 sign positions, but among the first eight input words only x[2] and x[5] flip, which is why rotation[0..8] stays byte-identical and [8..15] move; narrow's Hadamard mixes the whole block, so every narrowed word moves. I also want to flag that the old values were never a well-defined snapshot — they were only reachable through the undefined shift, so the new values are the first well-defined ones this test has produced. I updated the stale "frozen at 53b7900" comment accordingly; 53b790082 actually used signs[0] = 1 and never had this producer, which was introduced with the frozen arrays in b25c4a8ce.

One thing worth having in CI regardless. build.zig defaults optimize to .fast, so a bare zig build test-deepseek-v41-reference is ReleaseFast and can never surface either panic. Bug 3 in particular only fails under -Doptimize=Debug. Whatever the final arrangement, this target wants an explicit Debug run.

Bug 1 — I could not reproduce it, and I think it is a 1.1.2-only forward-compat item. In the tree and on main, Recorder.beginSpan takes two arguments and metal_counter_consumer.zig calls it with two at lines 84, 210, 211 and 214, so tf-metal-counters builds clean here. The metadata: u32 parameter is on your unreleased 1.1.2 line, not in anything public — there is no public 1.1.2 branch and this PR's base is b7ce81219, which predates the split. Your inference about why CI missed it is correct, though: zig/build/metal_counters.zig builds the consumer only as the GPU test behind test-metal-counters-gpu, which is not part of test_step, so zig build test never compiles it. That is true here too. So this is not a defect in the stack as posted, but every one of these PRs will need the call sites updated when it rebases onto 1.1.2 — I'd rather do it against the real branch than guess at the signature.

On #1051's five BF16 words. Agreed, and it is a bound pass rather than bit identity. The receipt already separates the two — bf16_unequal and metal_vs_contract_unequal are reported alongside bound_failures and bound_max_ratio — so the number is visible rather than folded into the verdict. I'd rather keep that visible and not claim identity we don't have.

On the lineage. Your read matches what the history shows: #1051 fully contains #826 and adds two commits, #1052 fully contains #827 and adds two, and eight of #826's commits are in neither serving PR. Given 1.1.2 has moved config, load, state, KV state, the runner and pack core onto shared owners, I'd rather not spend the rebase on both. I'm inclined to keep the precision line with #1051 and drop the rest, and I'll confirm once 1.1.2 is public — please tell me if you'd rather have serving.

On tools/zig/nfkd_tables.py. Yes, happy to send that as a standalone first PR. It already has the pieces you're asking about: VERSION = "16.0.0" pinned, a hard failure when unicodedata.unidata_version does not match, a --check mode that exits non-zero on a stale table, and a provenance header naming the version and the tool. Running --check on the interpreter I have here refuses outright because it ships Unicode 15.0.0, which is the behaviour you want and also the one caveat — generating or verifying the table needs a Python with Unicode 16.0.0. I'll follow your lead on whether that belongs in this PR or a follow-up.

I have not run the full suite or anything on device for these two fixes — only test-deepseek-v41-reference on each affected head, in Debug and ReleaseFast. Both fixes are pushed to the corresponding heads.

- akol1

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants