# DeepSeek-V4.1-Flash on WASTE `deepseek-ai/DeepSeek-V4.1-Flash` — a 552 B backbone plus 197 B of n-gram memory, 510 GB as published, `DeepseekV41ForCausalLM`. It activates 8 B parameters on a prefill token and 16 B on a decode token. This document is the plan and the arithmetic, written **before** the download. It exists because the release is further from the Kimi/DeepSeek-V3 family than GLM-5.3-Flash was, and the distance is worth stating precisely rather than discovering a layer at a time. Status of the work is at the end. Sources read: the release's `config.json`, `inference/` (`model.py`, `kernel.py`, `engram.py`, `vision.py`, `convert.py`), `encoding/` and `model.safetensors.index.json` — the last read through the safetensors headers over HTTP range requests, so every shape and dtype below is the checkpoint's own and not an inference from the config. ## The shape | | | |---|---:| | layers | 40, every one MoE — no dense prefix | | hidden | 5120 | | routed experts | 384 per layer, top-6, 2304 wide | | shared experts | 1, same width | | router | `sqrtsoftplus` scores, `noaux_tc` bias, `route_scale` 1.5, renormalized | | attention | 64 heads x 512, `q_lora` 1280, no kv LoRA: one 512-wide KV per token | | output proj | low-rank *and* grouped: `o_groups` 8, `o_lora_rank` 1024 | | rope | 64 of the 512 dims, YaRN factor 16 over a 65536 training context | | residual | mHC, `hc_mult` 4, 20 Sinkhorn iterations — **single-pass** | | memory | Engram at layers 1 and 14, 768 M rows x 256 | | MTP | 3 DSpark draft layers, 128 experts each, block size 5 | | vision | 32-layer ViT, 1024 wide, 2D rope, 3x3 pixel-unshuffle aligner | | context | 1,048,576 | | vocab | 129,280 (128,000 BPE + 1,283 added, 3 of them inside the BPE table) | `quantization_config` as published: fp8 `e4m3` trunk with `ue8m0` scales on 32x32 blocks, routed experts fp4 `e2m1` packed two per byte with one `ue8m0` scale per 32 input values per row — which is MXFP4, the format `tools/mxfp4.py` already reads. ## What the engine already has More than the size of the diff suggests. Against `0.7.2`: - **mHC.** `hc_collapse` / `hc_scatter` / `hc_head` in `src/model.c`, the 4x4 Sinkhorn included, written for GLM-5.3-Flash. The weight names and the `(2 + hc) * hc` mixing layout are identical. - **The clamped SwiGLU.** `swiglu_limit` 10.0, the same constant, and `waste_act_pair_range` already applies it at every call site. - **A top-k router with a selection bias that does not scale the weights.** K3's `noaux_tc` is the same mechanism; only the score function differs. - **MXFP4 reading**, block-scaled fp8 reading, and the expert-per-record container the routed experts want. - **YaRN**, in `rope_init`, following `DeepseekV3YarnRotaryEmbedding` — which is the same ramp this release's `precompute_freqs_cis` computes. - **An indexer that scores compressed positions and keeps the best k.** GLM's k-pool DSA is a cousin of CSA2's second level. ## What is new Six things, roughly in order of how much C they cost. ### 1. CSA2 — Compressed Sparse Attention, second generation Not a variant of the k-pool DSA already in the engine. A layer attends over **two concatenated KV sources** in one softmax: - a sliding window of `window_size` = 128 raw KV vectors, its own per layer; - when `compress_ratios[L] > 0`, up to `index_topk` = 512 *compressed* positions, each standing for `compress_ratio` tokens. Three static modes, which is what "Full / Reindex / Reuse" names: | layers | ratio | KV | indexer | |---|---|---|---| | 0, 1 | 0 | window only | — | | 2 … 19 | 2 | compresses (2, 8, 14) or reads | 2, 8, 14 own one | | 20 … 39 | 1 | compresses (20) or reads | 20, 24, 28, 32, 36 own one | `kv_source_layers` = [2, 8, 14, 20] are the only four layers that build a compressed KV cache; `index_source_layers` = [2, 8, 14, 20, 24, 28, 32, 36] are the only eight that run an indexer. Every other layer **reuses** the most recent of each — that is the KV sharing the announcement's "1/4 the HBM" comes from, and it is why the global KV cost is 890 bytes per token. Four sub-mechanisms, none of which the engine has: - **The compressor.** `compress_ratio` consecutive tokens are pooled into one 512-wide latent by a learned softmax gate (`wgate`), in fp32, then RMS-normed. At ratio 1 it degenerates to a plain projection and the gate tensor is absent — which is exactly why the checkpoint has 4 `wkv` and only 3 `wgate`. During decode it emits a latent only every `ratio` steps and carries the partial group in state. - **The two-level indexer.** A 32-head, 128-wide side attention scores every compressed position; scores are relu'd and combined by a per-token `weights_proj`. Layer 20 is the **candidate source**: it keeps the best 2048 blocks of 8 positions, and every later indexer scores only inside that mask. This is the "hierarchical sparse indexer". - **An attention sink.** One learned fp32 scalar per head, added to the softmax denominator and to nothing else. Cheap, and silently wrong if omitted — it is a per-head temperature on the whole attention output. - **Rotation removed from the output.** The rope dims of the attention *output* are rotated by the conjugate of the query's rotation. That is what lets one cache serve layers with different rope thetas, and it has no analogue anywhere in this engine. Two rope schedules per model: `rope_theta` 10000 with **YaRN off** for the window-only layers, `compress_rope_theta` 160000 with YaRN on for the compressed ones. `rope_init` currently computes one table. ### 2. Single-pass mHC The mechanism is GLM's; the schedule is not. In GLM each sublayer's `hc_collapse` produces the `pre` it immediately uses. Here a sublayer's mixing projection produces the `pre` for the **next** sublayer: ``` attn_pre, attn_post, attn_comb = mixes(x, hc_attn_*) # attn_pre is for the FFN x = collapse(x, pre_from_previous_sublayer) ... attention ... x = scatter(x, attn_post, attn_comb) ffn_pre, ffn_post, ffn_comb = mixes(x, hc_ffn_*) # ffn_pre is for the next layer x = collapse(x, attn_pre) ``` Layer 0's attention uses a one-hot `pre` (stream 0 only), and the **final collapse before the head is the last FFN's `pre`**, not the unweighted mean `hc_head` does today. One extra float array carried across the layer loop, and a diff that produces plausible-looking logits if you get it wrong. The `hc_*_fn` projection is `24 x 20480` fp32 per site, 80 sites: 157 M parameters, resident. ### 3. Engram Two layers (1 and 14) add a gated n-gram lookup into the residual stream. Each position is hashed as a 2-, 3- and 4-gram over a **compressed token id** — a normalization that collapses `" The"`, `"the"` and `"THE"` onto one id, 129,280 ids down to 99,092 — with 8 heads per n-gram size, so 24 rows of 256 values per layer per token. The 24 buckets are prime-sized and disjoint; the primes are drawn in order from `engram_vocab_size - 1` = 15,999,999 and never reused, and every hash multiplier derives from the compressed vocab size, so **a compressed vocab of any other size rehashes the whole table into noise**. The release asserts that size; so must the converter. The gate is a normalized dot product of the residual stream against a key derived from the lookup, per hc copy, with a signed square root before the sigmoid. The tables are 768,022,850 rows x 256 — 197 B parameters, 203 GB as published, and **40% of the download**. They are also the easiest thing in the model to stream: 48 rows per token, 264 bytes each, which is 48 O_DIRECT reads of one 4 KiB page. Against 3.19 GB of experts per token that is noise. They are an `on_disk` tensor with an index, not a bank, and not resident. Image-span tokens take no part in an n-gram and get no engram term. ### 4. The router's third score function `sqrtsoftplus`: `sqrt(softplus(x))`. Unbounded above, unlike sigmoid and softmax, which is why `norm_topk_prob` divides by the sum with a fixed `1e-20` — *not* `norm_eps`, which here is also 1e-20 but for unrelated reasons. There is a second bias vector, `bias_vl`, selected per token by whether the token is inside an image span. ### 5. A different pre-tokenizer DeepSeek's `tokenizer.json` is byte-level BPE with merges in result-id order — so `src/tokenizer.c`'s tiktoken merge loop is correct unchanged, and `tools/hf_tokenizer.py` can re-encode the vocabulary as it does for GLM. The **pattern is not cl100k's**. Three isolating splits in sequence: ``` \p{N}{1,3} [一-龥぀-ゟ゠-ヿ]+ [!"#$%&'()*+,\-./:;<=>?@\[\\\]^_`{|}~][A-Za-z]+|[^\r\n\p{L}\p{P}\p{S}]?[\p{L}\p{M}]+| ?[\p{P}\p{S}]+[\r\n]*|\s*[\r\n]+|\s+(?!\S)|\s+ ``` `tok_han_split` is not enough to express this: the CJK branch covers kana as well as Han, punctuation and symbols are their own classes where cl100k has "not letter, not number", and there is a branch for a punctuation mark followed by ASCII letters that cl100k has nothing like. `\p{P}`, `\p{S}` and `\p{M}` have to be coded into `tokenizer.c` the way `\p{L}` and `\p{N}` already are, behind a third pre-tokenizer mode. One more thing the other containers do not have: ids 0, 1 and 2 are special tokens **inside** the BPE vocabulary, not appended after it. ### 6. The tower, the chat format, and DSpark - **Vision** is a third tower: 32 blocks, 2D rope over (h, w) with no learned grid, biases on qkv/o and not on the MLP, a fused `w1` emitting gate and up together, RMSNorm, GELU in the aligner, and a 3x3 pixel-unshuffle instead of a 2x2 merge. Two things in it are not a variant of anything already here. **The rotation is split-halves**: each head's 64 dims are halved and the first half rotated against the second, where every other rotation in this engine — the text model's, K3's tower, GLM's — pairs *adjacent* elements. And **the span is not the image**: the LLM sees `[start] ([image] * n_w [newline]) * n_h [end]`, so an image of n_h x n_w tokens costs `n_h * (n_w + 1) + 2` positions, and the three delimiters are learned embeddings living in the *text* trunk. The tower emits them itself, which is what lets the engine's media queue stay what it is — one row per placeholder, consumed in order. The preprocessing differs too, in the way that is invisible afterwards: the image is **contained and grey-padded**, not stretched. A 16:9 photo reaching a square grid keeps its proportions and gains two grey bands. - **The chat format** is DSML, with a numeric reasoning effort (1–100) in a `<|System|>` prefix, `` blocks, and `<|DSML| calls>` tool markup (note the space — V4 had none). `serve/dsml.py` is the port, and it is diffed against `encoding/encoding.py` the way `serve/xtml.py` is against K3's `encoding_k3.py` — including the release's own five checked-in golden outputs, which cover a mid-conversation system turn, an internal task token and a two-image turn. Sixteen control tokens, and the one that matters is `|DSML|`: the tag NAME is not part of it. `<|DSML| calls>` is three segments — `<`, the marker, ` calls>` — so a tool result containing the literal `|DSML|` goes through the text entry point and cannot open a block. `` is not a control token at all; it is four characters the model was trained to read. - **DSpark** is speculative decoding: 3 draft layers, a Markov head, a confidence head, semi-autoregressive drafts of 5. The release's own `model.py` implements the forward pass and says the loop "is out of scope for this repo". **Gate 9 deferred it, and the reason is this engine's and not the model's.** Verifying K drafts in one pass is nearly free on a GPU; here the pass is the expert reads, and a window of five consecutive decode tokens touches **3.45x** what one token does — measured on Kimi-Linear, GLM-5.3-Flash and K3, which agree to a tenth of a point at every K. So 3.45 of the five have to be accepted just to break even on bytes, and DeepSeek-V4.1 routes top-6 of 384, sparser than all three. Whether DSpark clears that is the one number left, and it needs the container. The MTP weights are dropped from the conversion meanwhile. ## The budget — gate 8 *Protects:* a 510 GB download and a ~310 GB conversion. Every number below is arithmetic over the checkpoint's real shapes. None of it is measured; the measurement is what the gate schedules, not what it reports. **One expert record.** `w1 + w3` are 2304x5120 and `w2` is 5120x2304, so a record is 3 x 11.8 M = 35.39 M parameters. At the 3-bit VQ3R operating point that is **13.27 MB**, padded to 4 KiB. **Per-token working set.** top-6 x 40 layers = 240 records = **3.19 GB**. That is the number that decides whether this runs, and it is worth putting beside the two containers already measured: | | bank | working set / token | measured decode | |---|---:|---:|---:| | Kimi K3 2.78 T | 963 GB | 17.0 GB | 0.45–0.62 tok/s | | GLM-5.3-Flash 313 B | 106 GB | 3.16 GB | 3.32 tok/s | | **DeepSeek-V4.1-Flash 552 B** | **204 GB** | **3.19 GB** | *projected below* | The working set is GLM's to within 1%, on a bank twice the size. At the 46 GB budget this machine resolves, the cache covers 20% of the bank against GLM's 39%, so the hit rate lands lower — call it 70–80% against GLM's 87.7% — for 0.6–1.0 GB read per token, 0.05–0.08 s on the internal SSD's measured 12.78 GB/s. **Compute.** 17.4 B MAC per decode token at a 32 K context: 5.06 B trunk attention, 8.49 B routed experts, 1.42 B shared, 0.66 B head, 1.68 B attention scores, 0.85 B indexer, the rest small. The indexer is the only term that grows with context — 3.4 B at 128 K, because five of its eight layers index an uncompressed axis. **Projection: 1.5–2.5 tok/s**, I/O no longer dominant. Between GLM's 3.32 and K3's 0.55, nearer GLM, and for the first time in this family a container where the arithmetic and the disk cost about the same. *This is a projection. Do not quote it as a result.* ### Measured The container exists now, so the projection above is kept as written and this is what it turned out to be. 18-token prompt, 60 decode steps through `test_forward` (which does not stop at EOS), M-series laptop, 64 GB, the internal SSD. Only `WASTE_CACHE_MB` varies. | expert cache | s/token | tok/s | demand hit | disk read | |---:|---:|---:|---:|---:| | 152 MB — the floor | 0.50 | 2.00 | 0.0% | 234.70 GB | | 512 MB | 0.37 | 2.70 | 66.4% | 307.77 GB | | 3.2 GB — one working set | 0.33 | 3.03 | 70.8% | 255.66 GB | | 9.6 GB | 0.28 | **3.57** | 78.6% | 170.42 GB | | 17 GB | 0.28 | **3.57** | 83.0% | 130.45 GB | | 30 GB | 0.29 | 3.45 | 87.0% | 96.62 GB | | 41.74 GB — what no `--budget` resolves to | 0.30 | 3.33 | 89.0% | 81.13 GB | **3.33 tok/s is what a user gets**, since that last row is the automatic budget on this machine; 3.57 is the best the curve reaches. Both are above the 1.5–2.5 projected, and above GLM-5.3-Flash's 3.32 on a bank twice the size — the working set being GLM's to within 1% is what carries it, exactly as the table above argued. Through the CLI, which is what README quotes and a different harness from the table above — `waste run`, the automatic budget, a prompt the model keeps answering — it is **3.77 tok/s over 64 tokens at 93% hit and 3.71 over 200 at 94%**. Flat, where every other container in this family is faster over 200 than over 64: the cache is already full by the 64th token, so the longer run has nothing left to win. The curve is flat from 9.6 GB on and then declines slightly. Nothing is gained past ~17 GB and a little is lost, which is the same shape as K3's (README: 58 GB measured 8x slower than 46 GB) without the cliff, because DS41's cache never approaches the fraction of RAM where that one bites. Two columns are worth reading carefully, because they are not the same measurement. **Demand hit** is the fraction of expert lookups the cache answered; **disk read** is all traffic, read-ahead included. At the floor they agree exactly — 0% hit, and 18960 misses x 12.68 MB is the 234.70 GB reported to the byte — while every other row reads 3.1-3.9x its demand misses. That gap is the read-ahead, and it is also why 512 MB of cache (40 records, against the 240 a token routes) shows 66.4%: most of those hits are the read-ahead arriving first, not reuse. Cross-token reuse cannot be 66% on 40 records, and reading the column as if it were would put the locality of this router an order of magnitude above what gate 0 measured. **Where the time goes**, and it moves with the cache: | | at the 152 MB floor | at 17 GB | |---|---:|---:| | moe (all) | 85.2% | 72.9% | | — expert I/O | **58.7%** | **10.2%** | | — expert matmul | 21.9% | 53.3% | | attention (mla/CSA2) | 14.0% | 25.6% | So "I/O no longer dominant" is right, but only once the cache can hold something: starved, this is a disk-bound engine like K3, and fed, it is a compute-bound one like nothing else here. The projection's 17.4 B MAC per token is the budget that matters at the operating point, not the bytes. **Footprint.** | | as published | in the container | |---|---:|---:| | routed experts | 272 GB fp4 | 204 GB VQ3R | | engram tables | 203 GB fp8 | 111 GB at 4 bits, on disk | | trunk (8.79 B params) | 9 GB fp8 | 4.9 GB at 4 bits, resident | | vision + aligner | 1 GB bf16 | 0.3 GB | | **total** | **510 GB** | **~320 GB** | Both fit: 982 GB free on `/Volumes/WasteDisk` for the download, 499 GB free on the internal SSD for the container. Gate H's placement rule stands — staging external, container internal. **Resident RAM.** 4.9 GB of trunk against K3's 29.19 GB and GLM's 5.14 GB, plus the CSA2 state. That state is the pleasant surprise: 128 window slots x 512 per layer is 2.6 MB fixed, and the compressed caches exist on four layers only — 890 bytes per token globally, so a 128 K context costs 109 MB. K3 spends more than that on latents by 8 K tokens. **Verdict: feasible, and a better fit for this engine than K3.** The one thing in the arithmetic that could have killed it has been measured and did not — **gate 8, run 2026-09-15**: 3-bit VQ over these already-fp4 experts costs **19.95%**, against 20.3% for K3's and 19.4% for gate 3's bf16 Kimi experts. The fp4 source costs nothing extra. The measurement fetched 283 M parameters over HTTP range requests — 190 MB of the 510 GB — with `tools/hf_peek.py`; [GATES.md](GATES.md) has the table and three side findings, including that 94 M of these parameters hold **28 distinct values**. Two risks remain, and neither is a quantization risk: 1. **The indexer's cost at long context**, which no model in this engine has had to pay per token. Five of its eight layers index an uncompressed axis, so it is linear in context with no ratio to divide it down. 2. **The compressed-KV decode schedule**, which makes a decode step stateful in a way `waste_model_step` has never been: a layer that emits a latent only every other token, and seven layers downstream reading the cache it wrote two steps ago. ## The container New relative to v0, all additive: - **`engram-L{n}.bin`**, one per Engram layer: the hash table as row records, payload then scales, contiguous — so a lookup is ONE pread of one page rather than two of two. Not a bank (a record is 36–144 bytes, not 13 MB) and not a trunk tensor (98 GB cannot be held, and `--skip-trunk` would have to rewrite it). - **`engram.json`** and **`engram-tokmap.bin`**: the compressed token map (129,280 int32), the 48 bucket primes and their offsets, and the hash multipliers. Derived at conversion from the tokenizer and a fixed RNG seed, so the container has to carry the derivation — `tools/ds41_engram.py` builds it, and checks it two ways against what the release states: the map came out **99,092 ids** against `engram_compressed_vocab_size` 99,092, and the primes summed to **384,006,168** and **384,016,682** against the two `engram_num_embeddings`. Both agreed on the first run, which is the evidence that the normalizer chain and the prime order are the release's. - manifest keys, mostly the release's own spellings: `compress_ratios`, `kv_source_layer_ids`, `index_source_layer_ids`, `candidate_*`, `sliding_window`, `head_dim`, `o_groups`, `o_lora_rank`, `compress_rope_theta`, `scoring_func`, `routing_bias_vl`, `engram_*`, and `tokenizer_pattern`. - `vision.json` will gain `tower: "ds41"` at stage 8. The tower's weights are carried now — 485 M parameters, left on disk unless a caller asks — because `--reclaim` would otherwise mean downloading 510 GB again to add images. `format_version` does not move: a v0 reader refuses an unknown architecture before it reaches any of these keys. ### Two bugs, and which side each was on Both were found the same way — the residual stream after every layer, diffed against the oracle, at several token counts — and neither would have shown up in the logits alone. **The engine's:** the indexer derives its key from the compressor's *unrotated* latent, so it runs between the compressor and the rotation. It was handed the same scratch buffer, so what got cached as the layer's compressed KV was the index key. Same argmax, 0.73% relative L2, plausible logits; the layer-by-layer diff put it at layer 2, the first layer that compresses. **The oracle's,** and it is the more instructive one. Upstream keeps what a source publishes in a module-level singleton and says why: "layers run in order and every source writes before its consumers read, so one slot each is enough and nothing needs resetting between forwards." The oracle rebuilt it per step. On a step whose compressor does not complete a group — every other token at ratio 2 — the index keys then read as absent and the layer attended over the window alone. **The engine was right and the oracle was wrong**, and what said so was not the size of the error but its shape: exact at one, two and four tokens, 6% at three. ### Two things the converter had to be told rather than shown **The companion scale is spelled `.scale`, not `.weight_scale`.** K3 appends a suffix to the weight's own name; this release replaces it. A reader that knows only K3's spelling finds `layers.0.ffn.experts.0.w1.weight`, sees an `I8` tensor, and returns the raw nibble pairs as floats — every shape checks out and every value is wrong. `mxfp4.ST.tensor` now decides on the companion it can find rather than on the suffix it expected, and refuses an `int8` tensor with no scale beside it, because a real int8 weight and packed fp4 read identically. **Every tensor is renamed.** The engine looks its tensors up by fixed strings and this release shares none of them — `layers.0.attn.wq_a.weight` against `model.layers.0.self_attn.q_a_proj.weight`. `ds41_rename` is deliberately *not* a catch-all: an unrecognised tail comes back unchanged so `ds41_check_names` can report it, because a tail given a plausible new name is one nothing will look up and nothing will notice. All 1264 non-expert names in the release's index rename; `tests/test_convert_ds41.py` pins 40 of them and checks that an invented one is still reported. ## Download and convert **Room needed:** 475 GiB of published weights on the staging disk, 299 GiB for the container. The container belongs on internal NVMe — an external one is correct and slow, 12.78 GB/s against 0.94 on a tested enclosure — and the staging weights can live anywhere. The whole thing, unattended and resumable: ```bash MODEL=ds41 SRC=/Volumes/staging/ds41 OUT=~/models/ds41.waste \ tools/pipeline.sh ``` Six stages: download until every shard verifies; convert one MoE layer and the trunk; round-trip that layer against the source weights; convert the rest; run the engine; diff its logits against `tools/ds41_ref.py`. Each refuses to start on a failed predecessor, each is skipped once it has passed, and the log and a final `REPORT.md` land in `$OUT.runs`, beside the container rather than inside it. The round-trip sits *before* the bulk conversion on purpose. It is the only check that reads the source weights, and `RECLAIM=on` deletes each shard as the converter finishes with it — so a verification placed after that has nothing left to compare against. It is the better order regardless: a quantizer bug costs one layer instead of a night. Run end to end here, on a container that was already complete: ``` stage 1 done: all 48 shards verified stage 2 skipped: layer 0 and the trunk are already published stage 3 done: round-trip PASS stage 4 done in 0 min, 299G stage 5 done: The capital of France is Paris. stage 6: max|diff| 3.853e-04 rel 2.409e-05 argmax MATCH top-10 identical ``` By hand, if you would rather watch each part: ```bash tools/fetch_weights.sh --repo deepseek-ai/DeepSeek-V4.1-Flash \ --dest /Volumes/staging/ds41 --dry-run # preflight: shards, size, space tools/fetch_weights.sh --repo deepseek-ai/DeepSeek-V4.1-Flash \ --dest /Volumes/staging/ds41 # 48 shards, 475 GiB, resumable uv run --with torch python tools/convert.py \ --src /Volumes/staging/ds41 --out ~/models/ds41.waste --jobs 3 ``` There are no DeepSeek-specific flags. The converter recognises the architecture from `config.json`, derives the Engram token map and bucket primes that the checkpoint does not ship (checked against the two row counts the release states), writes the DSML chat format, and re-encodes the tokenizer. `--engram-bits` is 4 by default and 8 doubles those tables to 209 GB without changing what a token reads. `tools/ds41_preflight.py` checks every name and shape the engine will demand against the checkpoint's own index before a byte is written, and `convert.py` calls it in-process. The reason is the arithmetic of the failure: a load refuses on the *first* missing name, and on a 510 GB checkpoint the hours are already spent by then. ## Stages Ordered so that each one is checkable on its own, and so that the expensive download is not the first thing that happens. | # | stage | check | |---|---|---| | 0 | this document | — | | 1 | ✅ **VQ3R-on-fp4 gate** — 19.95%, passed 2026-09-15 | `tools/hf_peek.py` + `tools/quant_lab.py --npy`, 190 MB fetched | | 2 | ✅ **config + manifest + `cfg_sane` + synthetic container** | `make_test_container.py --ds41`; the load accepts it and refuses one missing a tensor | | 3 | ✅ **tokenizer** — third pre-tokenizer mode, classes generated | `tokdiff.py --wide 20000`: 24021/24021 | | 4 | ✅ **converter** — renaming, fp8/fp4 reading, engram tables and hashing | `tests/test_convert_ds41.py`; 1264 real names, 0 unrenamed | | 5 | ✅ **engine, text only** — single-pass mHC, sqrtsoftplus, CSA2, Engram | `tools/ds41_ref.py`: **0.000018% rel L2** on the logits | | 6 | ✅ **the download and the real conversion** | 299 GiB container; round-trip 19.5–20.4% across 40 layers against gate 8's 19.95–20.74%; **0.0025% rel L2** vs the oracle | | 7 | ✅ **serve** — DSML encoding and the reply reader | 21 checks against `encoding/encoding.py`, the release's five goldens included | | 8 | ✅ **vision tower** — 2D rope, 3x3 pixel-unshuffle, the span | `tools/ds41_vision_ref.py`: **6e-7 rel L2** on five grids | | 9 | ⏸ **DSpark** — deferred by gate 9: break-even is 3.45 of 5 drafts | `tools/spec_window.py` on three real containers | Stage 1 is the gate. Stage 6 is the long operation it protects. ## Status 2026-09-15. **The model is converted and running on the real weights.** Stages 0 through 8 are done and 9 is deferred by gate 9. A 299 GiB container from the 510 GB release: 552.37 B parameters total, 16.62 B active per token, which is what DeepSeek publishes. It answers, at **3.77 tok/s over 64 tokens and 3.71 over 200** — above the 1.5–2.5 this document projected, and above GLM-5.3-Flash on a bank twice the size. Against `tools/ds41_ref.py` on the real container: **0.0025% relative L2**, top-5 identical. The measured section above has the cache-size curve and the phase profile at both ends of it. `tools/pipeline.sh` takes it from nothing to that, unattended: `MODEL=ds41` runs download, probe, round-trip, convert, generate and oracle-diff, each stage resumable and each refusing to start on a failed predecessor. On the synthetic test container, **the engine agrees with an oracle to 1.8e-7 relative L2** on the logits over twelve tokens, and to the same on the residual stream after every layer — CSA2's two KV sources, the compressor, the two-level indexer, the attention sink, the inverse rotation, the grouped low-rank output projection, single-pass mHC, Engram at two layers, and the sqrt-softplus router. `tools/ds41_ref.py` reads the same container, so that number is arithmetic and not quantization. Chunked prefill is bit-identical to the sequential path, as it is on GLM. Twelve tokens and not four: the test container's window is four slots, so below five tokens the ring never wraps, no compressed cache fills, and the candidate filter has nothing to choose between. A four-token diff is green over three mechanisms that never ran. The server speaks DSML: `serve/dsml.py` renders the conversation and reads the reply back, `tests/serve/test_dsml_upstream.py` diffs both against the release's own encoder (21 checks, including its five golden outputs), and `tests/serve/test_dsml.py` holds what that diff cannot see — which segments are markup, and what the renderer refuses. The tower agrees with `tools/ds41_vision_ref.py` to **6e-7 relative L2** on five patch grids, three of them not multiples of the downsample so the unfold's zero padding actually runs, and the image geometry agrees with the release's on seven source sizes including both collapse cases. A synthetic container at test scale (`make_test_container.py --ds41`) opens: the config parses, `cfg_sane` bounds it, `validate_text_tensors` demands every CSA2, Engram and mHC shape, and a container missing one of them is refused by name. Sessions round-trip: the window ring, the compressed latents and index keys of the four KV source layers, the pooling group still filling, and Engram's n-gram history — whose ids are compressed by a map only the container has, so a caller holding the prompt cannot rebuild it. The tokenizer is exact — 24021 of 24021 strings identical to the release's own, over a corpus drawn from the whole codepoint space. Getting there cost more than the new pattern: `\p{L}`, `\p{N}` and `\s` in `src/tokenizer.c` were hand-written ranges, and generating them fixed two defects that had been mis-encoding about 4.5% of such strings on **Kimi-Linear and GLM-5.3-Flash** as well. LEARNED §75. Every shape here comes from the checkpoint's own safetensors headers, and every quantization number from real parameters — 283 M of them at gate 8, before the download, and since confirmed across all 40 layers of the finished container at 19.5–20.4% per-expert error against gate 8's projected 19.95–20.74%. The throughput projection was the one figure in this file that had not been measured; it now sits beside the measurement, kept as written. Left out deliberately: images, which the tower implements and the oracle agrees with but the container does not carry, and DSpark, deferred by gate 9 with its kill and revival criteria stated there.