Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
82 commits
Select commit Hold shift + click to select a range
06a5236
server: capabilities --json's Engines struct moves to cli_capabilitie…
BlivionIaG Oct 26, 2026
9a8d654
lanes: Drafted passes held windows to a target that reads them itself
BlivionIaG Oct 26, 2026
47eecb5
lanes: a lone stream's pipelined steps move to pipelined.zig
BlivionIaG Oct 26, 2026
5d9fcf6
lanes: a shared round drafts only while drafting measures faster (Mod…
BlivionIaG Oct 26, 2026
1b57e58
lanes: a shared round the guard keeps plain proposes no copies, and a…
BlivionIaG Oct 26, 2026
47eaf78
lanes: an external drafter's chain chances price verify rows, and its…
BlivionIaG Oct 26, 2026
2708936
lanes: a drafter whose hold is a pass of its own: no hold beside a co…
BlivionIaG Oct 26, 2026
b66a762
lanes: a lone stream holds drafts only while its drafted rounds measu…
BlivionIaG Oct 26, 2026
1e71845
lanes: a copy window may be priced by its stream's own copies (Model.…
BlivionIaG Oct 26, 2026
471193c
lfm2: a verify's greedy rows take the GPU's pick of their logits
BlivionIaG Oct 26, 2026
886d417
lfm2: the conv, qk, attention and merge kernels' bodies as inline fun…
BlivionIaG Oct 26, 2026
793b68e
build: test-lfm2 runs the LFM2-MoE family's tests alone
BlivionIaG Oct 26, 2026
807aa4d
lfm2: a shared round runs each layer's conv and attention once over a…
BlivionIaG Oct 26, 2026
520d617
tile_qmm: the twin's MMA window only for matrices of 4,096 outputs or…
BlivionIaG Oct 26, 2026
9df2cd9
docs: the LFM2.5-24B-A2B recipe, its shared rounds and their M5 Ultra…
BlivionIaG Oct 26, 2026
79eed6c
Merge the lane core's held windows and shared-round guard under the L…
BlivionIaG Oct 26, 2026
b18058e
lfm2: draft with LiquidAI's DSpark drafter (--drafter)
BlivionIaG Oct 26, 2026
092254c
lfm2: a DSpark hold is not waited on; the verify copies its drafts on…
BlivionIaG Oct 26, 2026
970f10d
lfm2: keep a drafter's tapped rows on every forward
BlivionIaG Oct 24, 2026
5760863
docs: the LFM2 recipe's DSpark drafts with LiquidAI's drafter
BlivionIaG Oct 26, 2026
dc866a5
Merge the lane core's chances, lone guard and copy rates and the capa…
BlivionIaG Oct 26, 2026
c5d6c2d
lfm2: a DSpark confidence logit kernel, registered with the kernel co…
BlivionIaG Oct 26, 2026
6e13409
lfm2: the DSpark confidence head schedules the verify (--draft-verify…
BlivionIaG Oct 26, 2026
0f609b3
lfm2: a short prompt's tapped rows wait in the DSpark stage like veri…
BlivionIaG Oct 26, 2026
df6743b
lfm2: DSpark prices its own hold pass for the lane core
BlivionIaG Oct 26, 2026
262208f
lfm2: tf-lfm2-dspark times shared holds; tf-lfm2-verify-costs reads r…
BlivionIaG Oct 26, 2026
0b76aee
lfm2: the DSpark drafter loads one matrix at a time and keeps a lane …
BlivionIaG Oct 26, 2026
7dd1a2e
docs: the LFM2 recipe's DSpark verify schedule and its flags
BlivionIaG Oct 26, 2026
9d35bdc
docs: the LFM2 recipe's DSpark loader and lane memory
BlivionIaG Oct 26, 2026
4fa0292
lanes: the shared guard is off when Config.timed is false, so a sched…
BlivionIaG Oct 26, 2026
91b0cf2
Merge the guard's timed fix into lanes-chances
BlivionIaG Oct 26, 2026
a4df83d
Merge the guard's timed fix into dspark-scheduled
BlivionIaG Oct 26, 2026
b128df5
Merge the guard's timed fix into dspark-alloc
BlivionIaG Oct 26, 2026
5194e19
Merge the guard's timed fix into lanes-lone-guard
BlivionIaG Oct 26, 2026
db5130b
lfm2 dspark: a held pick that is nonfinite or past the vocabulary is …
BlivionIaG Oct 26, 2026
0437d36
tf-lfm2-dspark: the reference reads the target's embedding width from…
BlivionIaG Oct 26, 2026
74b2310
Merge the drafter fixes into dspark-scheduled
BlivionIaG Oct 26, 2026
51c2ba3
Merge the drafter fixes into dspark-alloc
BlivionIaG Oct 26, 2026
f635e63
lfm2 dspark: run the released bf16 matrices as stored (--drafter-bits…
BlivionIaG Oct 26, 2026
9f74d91
kernel contract: LFM2's shared-round launches carry recorded M1/M2 li…
BlivionIaG Oct 26, 2026
b71e5f1
lfm2 dspark: one-line comments for the held-pick refusal
BlivionIaG Oct 26, 2026
4197db2
Merge the drafter fixes into dspark-scheduled
BlivionIaG Oct 26, 2026
874856e
lfm2 dspark: one-line comment for Options
BlivionIaG Oct 26, 2026
a5f60e4
Merge the drafter fixes into dspark-alloc
BlivionIaG Oct 26, 2026
095c4d7
kernel contract: the draft ids probe zeroes its bad flag each run
BlivionIaG Oct 26, 2026
898cd5f
Merge the drafter fixes into dspark-scheduled
BlivionIaG Oct 26, 2026
9e36fdb
Merge the drafter fixes into dspark-alloc
BlivionIaG Oct 26, 2026
34348fe
kernel contract: the tap copy carries a recorded M1/M2 limit and two …
BlivionIaG Oct 26, 2026
ac73ee3
Merge the drafter fixes into the bf16 drafter
BlivionIaG Oct 26, 2026
faacaff
kernel contract: the drafter's kernels carry recorded M1/M2 limits an…
BlivionIaG Oct 26, 2026
2d3ee7a
Merge the drafter fixes into the kernel contract records
BlivionIaG Oct 26, 2026
9beda28
lfm2 dspark: the bf16 tile kernel loads its tiles 16 bytes at a time
BlivionIaG Oct 26, 2026
d03b0a9
Merge the drafter's contract records into dspark-scheduled
BlivionIaG Oct 26, 2026
1cf5175
lfm2 dspark: Options' comment fits 120 columns
BlivionIaG Oct 26, 2026
3b3a554
Merge the contract records into dspark-alloc
BlivionIaG Oct 26, 2026
050e400
Merge the drafter's contract records into the bf16 drafter
BlivionIaG Oct 26, 2026
1987a20
lfm2 dspark: --drafter-bits 8 serves the 8-bit codes, named in --help…
BlivionIaG Oct 26, 2026
4aa86a4
lfm2 dspark: the weights loader's comment fits 120 columns
BlivionIaG Oct 26, 2026
9c94ead
kernel contract: the drafter's bf16 linears and bf16 Markov scan carr…
BlivionIaG Oct 26, 2026
05d83e8
lfm2: serve only GPUs from the M3 on, and say an M3 is untested
BlivionIaG Oct 26, 2026
675b8d3
Merge the LFM2 shared-round decode into dspark-taps
BlivionIaG Oct 26, 2026
23c0bf6
Merge the LFM2 shared-round decode into the bf16 drafter
BlivionIaG Oct 26, 2026
18a23ed
lfm2: --drafter is refused by name on an M1 or M2 GPU, and an M3 is l…
BlivionIaG Oct 26, 2026
423d64c
Merge the bf16 drafter into dspark-scheduled
BlivionIaG Oct 26, 2026
dc23017
Merge dspark-scheduled into dspark-alloc
BlivionIaG Oct 26, 2026
6539b72
lanes: a lone stream that drafting loses on keeps its loss for the ne…
BlivionIaG Oct 26, 2026
3ebef8f
Merge the lone guard's kept verdict into dspark-scheduled
BlivionIaG Oct 26, 2026
a116ee9
Merge dspark-scheduled into dspark-alloc
BlivionIaG Oct 26, 2026
68b4d40
Merge main into lfm2/plain-decode
BlivionIaG Oct 26, 2026
5eb25ab
Merge main and lfm2/plain-decode into dspark-taps
BlivionIaG Oct 26, 2026
2009450
Merge dspark-drafter into dspark-scheduled
BlivionIaG Oct 26, 2026
56bc40e
Merge dspark-taps into dspark-drafter
BlivionIaG Oct 26, 2026
d9decfc
Merge dspark-scheduled into dspark-alloc
BlivionIaG Oct 26, 2026
caf9c60
metal.zig back to 600 lines: its one comment line goes, the --draft-v…
BlivionIaG Oct 26, 2026
abcc9f1
Merge m-dspark-scheduled into dspark-alloc
BlivionIaG Oct 26, 2026
70f74ca
lfm2: M1 and M2 are served again
BlivionIaG Oct 26, 2026
3df2d05
Merge lfm2/plain-decode into dspark-taps
BlivionIaG Oct 26, 2026
35e9255
Merge dspark-taps into dspark-drafter; the drafter is served on M1 an…
BlivionIaG Oct 26, 2026
5f64667
Merge dspark-drafter into dspark-scheduled
BlivionIaG Oct 26, 2026
07e4f21
Merge dspark-scheduled into dspark-alloc
BlivionIaG Oct 26, 2026
1bb6e5d
lfm2: the drafter's verify is not a choice: the confidence head's cha…
BlivionIaG Oct 26, 2026
7ef59b8
Merge dspark-scheduled into dspark-alloc
BlivionIaG Oct 26, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ Conversions are explicitly marked: their upstream weights need the linked conver
| GLM-5.3-Flash, FP4-Local | `tensorfold pull nvidia/GLM-5.3-Flash-FP4-Local@76348ddb2be2e35e39ecfc00e75dc07703f4f3d5` | Two GB10s at TP=2, or three at TP=3 over a ring of direct links: @Bizuayeu loaded it as stored and served it at both sizes. 165.3 GiB of weights, 13% less than NVFP4's 190.4 GiB. | [Native CUDA](docs/recipes/glm-5.3-flash-cuda.md), in 1.1.5 (#1050). NVIDIA's mixed-precision checkpoint: NVFP4 routed experts with compressed block scales, MXFP8 and 128×128-block FP8 for most of the rest. As close to BF16 as NVFP4 at its smaller size: at TP=2 its NLL is 1.11% above BF16's against NVFP4's 1.37% ([contributed receipt](docs/receipts/glm-fp4local-80517fc3/README.md) on #1050's head, not rerun on main), a difference the per-document intervals in [XSFP4's card](https://huggingface.co/Bizuayeu/GLM-5.3-Flash-XSFP4#measured) do not resolve. |
| GLM-5.3-Flash, XSFP4 | `tensorfold pull Bizuayeu/GLM-5.3-Flash-XSFP4@88736f916f8939607fde8a4b881dcae749f7551d` | Two GB10s at TP=2, or three at TP=3 over a ring of direct links: on main `8086c205c` @Bizuayeu loaded it in 370.7 s at TP=2 (rank 0's model 96.97 GiB at a 524,288-token window) and in 313.8 s at TP=3 (63.60 GiB at 1,048,576), and both served. 165.7 GiB of weights, 0.4 GiB more than FP4-Local's. | [Native CUDA](docs/recipes/glm-5.3-flash-cuda.md), in 1.1.5 through FP4-Local's path. FP4-Local with its routed experts' NVFP4 blocks fitted again from the BF16 weights, a smoothing folded into them, so it runs on the same kernels. Closer to BF16 than FP4-Local and NVFP4 at TP=2 by all three measures in [its card](https://huggingface.co/Bizuayeu/GLM-5.3-Flash-XSFP4#measured), on #1050's code: top-1 agreement 93.26%, NLL 0.85% above and a KL bound of 0.0431, against FP4-Local's 92.82%, 1.11% and 0.0510. |
| GLM-5.3, EXLR8 `home` | `tensorfold pull kindlingai/glm-5.3-exlr8-k2-k3-k4@f459f5d0272a529403dd9b3fd32f771f4250a0d4 --exlr8 home --node 0/2 --dir ~/models/glm-5.3-exlr8-home` | Two M5 Ultra Macs, 256 GB each; about 160 GB resident per Mac at 8k context / four streams. | [Two-Mac recipe](docs/glm-5.3-exlr8.md). Use `--node 1/2` on the other Mac; Thunderbolt 5 and MCDMA required. No CUDA backend. |
| LFM2.5-24B-A2B, MLX 4 / 6 / 8-bit | `tensorfold pull LiquidAI/LFM2.5-24B-A2B-MLX-4bit@667408d494bde191c2cb9dd4f4549827816b9275`<br>`tensorfold pull LiquidAI/LFM2.5-24B-A2B-MLX-6bit@1deec53eb49571e21cdcbf82fd5839248f07ed88`<br>`tensorfold pull LiquidAI/LFM2.5-24B-A2B-MLX-8bit@67ab417dd02d0c9545802a726df668620eae049d` | One Mac; M4 Pro checked. | [Native operator recipe](docs/recipes/README.md), [release history](CHANGELOG.md). No separate native model recipe yet. |
| LFM2.5-24B-A2B, MLX 4 / 6 / 8-bit | `tensorfold pull LiquidAI/LFM2.5-24B-A2B-MLX-4bit@667408d494bde191c2cb9dd4f4549827816b9275`<br>`tensorfold pull LiquidAI/LFM2.5-24B-A2B-MLX-6bit@1deec53eb49571e21cdcbf82fd5839248f07ed88`<br>`tensorfold pull LiquidAI/LFM2.5-24B-A2B-MLX-8bit@67ab417dd02d0c9545802a726df668620eae049d` | One Mac; M4 Pro checked. | [Native recipe](docs/recipes/lfm2.5-24b-a2b.md), [release history](CHANGELOG.md). LiquidAI's DSpark drafter with `--drafter`. |
| Qwen3.6-35B-A3B, MLX with MTP | `tensorfold pull TensorFold/Qwen3.6-35B-A3B-MLX-4bit-MTP@81169a9bc511a27c1b4eedb77a2cd98ced431847` | One GB10; 20.9 GB checkpoint. | [Qwen3.6](docs/recipes/qwen3.6-moe.md). Native CUDA; the recipe's Metal path belongs to Python. |
| Gemma 4 31B, MLX 4-bit | `tensorfold pull mlx-community/gemma-4-31B-it-4bit@696d436c404745a59f30e4939a658162b0a9e57f`<br>`tensorfold pull z-lab/gemma-4-31B-it-DFlash@eabd648301ce28583cc14757912e5e0f84e152e1` | One M5 Mac; M5 Ultra checked. M1-M4 refused. | [Native 31B](docs/recipes/gemma-4-native.md). Name the drafter with `--drafter`; images with `--vision`. |
| Gemma 4 12B, converted MLX 4-bit | `hf download google/gemma-4-12B-it --revision 707f0a3b8a3c7ad586ed01e27eafbad8a27dd0f7 --local-dir ~/models/gemma-4-12b-hf`<br>`hf download google/gemma-4-12B-it-assistant --revision 46d4c6f13f0ac0ad827b915669b8df9b81c64c51 --local-dir ~/models/gemma-4-12b-assistant` | One M5 Mac; converted text weights 6.3 GB, image weights 100 MB, plus assistant/caches. M1-M4 refused. | [Convert and serve](docs/recipes/gemma-4-12b-native.md). Upstream download needs `hf`; conversion needs Python, mlx and mlx-lm. Serving is native. |
Expand Down Expand Up @@ -287,7 +287,7 @@ The binary's `capabilities --json` response lists its supported flags and platfo
| `--loop-guard` | Close reasoning that repeats itself verbatim (a short cycle, or a long passage) where the engine supports it. |
| `--no-drafts` | Produce the plain reference through the same engine. |
| `--drafter DRAFTER` | An external drafter, as a directory or a repo id in the Hugging Face cache, for models that take one: Qwen3.8-27B uses DFlash2 and Gemma 4 31B the drafter named in its recipe. Without one they decode plain. |
| `--drafter-bits 0\|4` | Drafter weight precision where the model offers a choice: 4 bits (the default), or 0 to keep bf16. Qwen3.8-27B on CUDA takes 4 only. |
| `--drafter-bits 0\|4\|8` | Drafter weight precision where the model offers a choice: 4 bits (the default), or 0 to keep bf16. Qwen3.8-27B on CUDA takes 4 only. LFM2.5-24B-A2B's DSpark drafter runs bf16 as released (0, the default when not given) or 8-bit codes quantized at load (8); it has no 4-bit mode. |
| `--record DIR` | Write each stream's committed tokens, positions and tapped rows under DIR, for drafter training. CUDA Nemotron only; a single stream runs through the lane rounds, and recording stops below 25 GiB free disk. |
| `--keep-warm SECONDS` | Keep the Metal GPU active while idle for this long after a request, default 900; zero disables it. |
| `--cfg-gamma GAMMA` | Metal Drama speech: semantic guidance strength, finite 1–5; 0/default disables it. Only reference-conditioned requests are guided. Enabled replies report `X-Cfg-Gamma`; codec heads stay conditional. |
Expand Down
7 changes: 7 additions & 0 deletions docs/kernel-contract.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,13 @@ Metal: softmax, RMS norm, bf16 top-k, the row projection (3, 4, 6 and 8 bits), a
formats on the M5's tensor units, where its reg kernel must equal the cooperative one bit for bit), and tile_qmm's
prompt tiles and their decode twin (4, 6 and 8 bits; the twin bit-equal to the tiles). Gate 5 also holds the rest of
tile_qmm's pipelines and GLM-5.3-Flash's hc_expand_10.
LFM2.5-24B-A2B's shared rounds: the greedy pick (`lfm2_argmax`) and the conv and attention kernels that run every
stream's rows in one dispatch, each bit-equal to the per-stream kernels it replaces. Four controls edit the source: a tie
to the highest id, a nonfinite logit not flagged, and every stream reading or writing the first stream's state or cache.
The DSpark drafter's tap copy, Markov scan and pick, draft ids, block attention and confidence logit are there too, with seven more controls.
Its bf16 linears (few rows, 64x64 tiles) and the Markov scan over a bf16 W2 have four and two more.
Their launches are in `lfm2_records.zig` with the M1 and M2 limits from `--offline`; `lfm2_attn_segs` and
`lfm2_attn_block` reserve their 128 threads in their source, as Metal cannot build them for M1 or M2 at another count.
CUDA: the RMS norm rows (16-byte form and the fp32-out tree), the bf16 residual's norm tails (MLX's rounded rows and
the once-rounded ones, each checked on its residual and its output, with four controls each dropping a rounding point), Nemotron's router logits (and their 128-row tiles,
bit-equal to them) and top-k, the latent top-k's decode form (bit-equal to `tf_super_topk`), the latent combine and
Expand Down
1 change: 1 addition & 0 deletions docs/recipes/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ For agent-assisted contributions, read the [TensorFold contributor skill](SKILL.
| Qwen3.6-35B-A3B | [MLX with MTP drafts, one-GPU CUDA](qwen3.6-moe.md) |
| Step-5-Preview | [Native Metal on one 256 GB Mac: 2-bit experts, MTP drafts, images](step-5-preview.md) |
| Kolibri-1 | [Native CUDA, block FP8, research checkpoint](kolibri-1.md) |
| LFM2.5-24B-A2B | [Native Metal, MLX 4-bit, shared rounds, DSpark drafts](lfm2.5-24b-a2b.md) |
| Spark-X2.6-4B | [Native CUDA](spark-x2.6-4b-cuda.md), [native Metal, the bf16 release, contexts to 1M](spark-x2.6-4b-metal.md) |

## Capability floor
Expand Down
110 changes: 110 additions & 0 deletions docs/recipes/lfm2.5-24b-a2b.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
# LFM2.5-24B-A2B, native Metal

This recipe covers [`LiquidAI/LFM2.5-24B-A2B-MLX-4bit`](https://huggingface.co/LiquidAI/LFM2.5-24B-A2B-MLX-4bit)
at revision `667408d494bde191c2cb9dd4f4549827816b9275`. The numbers below are measured on this 4-bit export only.

```bash
hf download LiquidAI/LFM2.5-24B-A2B-MLX-4bit \
--revision 667408d494bde191c2cb9dd4f4549827816b9275 --local-dir "$HOME/models/lfm2.5-24b-a2b-4bit"
zig build native -Dcpu=apple_m1
zig-out/native/bin/tensorfold-native serve "$HOME/models/lfm2.5-24b-a2b-4bit" --port 8080 --parallel 16
```

A Mac with less memory may not hold 16 streams at the checkpoint's 131,072-token window; the server then names how
many fit. `--context 40960` holds 16 on a 48 GB M4 Pro.

## Shared rounds

Every stream's tokens go through the lane rounds. A round of several streams is one forward over all their rows.
In it each layer's short conv (30 layers) and each attention layer's qk, attention and merge (10 layers) run once
over every row (`lfm2_segments.metal`), from a table of each stream's cache addresses, row offsets and key counts
(`segments.zig`). Each row's arithmetic is the per-stream kernel's, so a reply equals its solo run. Prompt calls and
one-stream windows keep the per-stream launches. A greedy row takes the GPU's pick of its logits (`lfm2_argmax`);
sampled and masked rows draw on the host. The dense twin takes its MMA window only for matrices of 4,096 outputs or
more (the short conv's input and the head); narrower ones keep the scalar passes.

`zig build test-lfm2` runs the family's tests. `tf-kernel-contract --only lfm2` checks the shared-round kernels and
the pick, and `tf-lfm2-exact` compares windows and shared rounds with one-row decode on the real weights.

## Measured

Greedy, 256 tokens, 16 public prompts, a different one a stream, 3 reps in each of 3 alternated rounds on an M5 Ultra;
aggregate tok/s, the range of the rounds' medians. Before is main at 0d24ecb3 (each stream's own launches), after adds
the shared-round kernels on it. The M4 Pro is not yet measured on a quiet machine.

| Streams | before | after |
| --- | --- | --- |
| 1 | 258.5-259.2 | 266.5-267.7 |
| 4 | 483.5-486.5 | 565.2-566.1 |
| 8 | 551.5-551.9 | 732.2-735.6 |
| 16 | 660.7-661.9 | 922.5-923.5 |

Every reply was token for token equal before and after, and equal to its solo run and to `"draft": false`.
Cold prefill at 2k, 8k and 32k tokens and peak memory did not change.

## DSpark drafts

LiquidAI's DSpark drafter for this model is
[`LiquidAI/LFM2.5-24B-A2B-DSpark`](https://huggingface.co/LiquidAI/LFM2.5-24B-A2B-DSpark/tree/e052e5b1dd103c38cdd4f8ddc068d7dea4586f55)
at revision `e052e5b1dd103c38cdd4f8ddc068d7dea4586f55` (327.7M bf16 parameters, 655 MB). Its weights are under the
[LFM Open License v1.0](https://huggingface.co/LiquidAI/LFM2.5-24B-A2B-DSpark/blob/e052e5b1dd103c38cdd4f8ddc068d7dea4586f55/LICENSE);
TensorFold does not ship them, so read the license before use.

```bash
hf download LiquidAI/LFM2.5-24B-A2B-DSpark --revision e052e5b1dd103c38cdd4f8ddc068d7dea4586f55 \
config.json model.safetensors LICENSE README.md --local-dir "$HOME/models/LFM2.5-24B-A2B-DSpark"
zig-out/native/bin/tensorfold-native serve "$MODEL_DIR" --port 8080 --parallel 16 \
--drafter "$HOME/models/LFM2.5-24B-A2B-DSpark"
```

Without `--drafter` the engine serves as before. `"draft": false` on a request, or `--no-drafts`, turns the drafter
off; the lane core verifies every draft, so a drafted reply equals its `"draft": false` reply token for token.

The drafter has no embedding and no head of its own:

- Context: the target's rows after layers 2, 10, 18, 26 and 34 (the backend's taps, `lfm2_tap`), through `fc` and
`hidden_norm`, give each of its five layers' context keys and values (k_norm, then RoPE at the row's position).
- Block: the pending token and mask tokens run five Qwen3 layers with non-causal attention over the context and the
block, then the target's head. A block is as long as the deepest chain its pass holds, at most the nine rows the
drafter was trained on.
- Markov head: row i's logits plus `W2 W1[previous token]` (rank 256); draft i is their argmax.

The drafter runs the released bf16 matrices as they are (`--drafter-bits 0`, the default). `--drafter-bits 8` instead
quantizes them at load to 8-bit MLX affine codes in groups of 64, about 5% faster on one stream and 361 MiB of weights
against 625 MiB; the server logs which mode runs, and 4 bits is refused. Both run on the family's kernels
(bf16 on `lfm2_bf16_rows`, `lfm2_bf16_mma` and `lfm2_markov_scan_bf16`, 8-bit on `core/tile_qmm`); the q and k rows are reordered so that the target's half-split RoPE kernel gives the
checkpoint's interleaved RoPE. Its own kernels are `lfm2_tap`, `lfm2_attn_block` and `lfm2_attn_block_merge`
(non-causal block attention, the target's attention kernels with a template flag), `lfm2_markov_scan` and
`lfm2_markov_pick` (a Markov step) and `lfm2_draft_ids` (a hold's drafts into the next verify's rows on the GPU, so
the host does not wait for a hold that fits one pass). Each kept prompt state keeps the drafter's context too, so a
drafting stream resumes prompts. Shared rounds take the lane core's shared-round guard: they draft only while
drafted rounds measure faster than plain ones at that stream count.

`tf-kernel-contract --only lfm2` checks these kernels. `tf-lfm2-dspark MODEL DRAFTER TOKENS [ROWS]` runs the drafter
against an fp32 host reference of the released checkpoint (the same taps and the target's embedding and head),
prints each draft against the reference's pick with the margin between the reference's two best tokens, and times
the drafter's passes; a near tie can pick the other token.

### Verify schedule

The drafter also carries a confidence head: a linear map of a draft's final block row and `W1[previous token]`
(2,304 inputs) to one logit, through a sigmoid (`lfm2_dspark_confidence`). It does not change the drafts.
The verify rows follow it: a chain's chance at draft j is the product of its first j confidences. Each round the
lane core's window allocator takes the likeliest drafts over all streams while the expected tokens a ms rise, so
each stream gets its own verify length, and the next hold drafts as deep as the stream's grant. A hold still
running on the GPU is priced by its lane's last confidences, so the host does not wait for it. A checkpoint
without the head verifies by the core's depth rule, from each stream's measured acceptance by depth.

A stream with a copy ahead verifies the copy and asks no hold, and a drafting stream prices its copy
windows by its own copies. The lane core also weighs the hold pass itself: the drafter times holds of 1 to 16
blocks at load and corrects those prices toward each settled hold's GPU time. A lone stream holds drafts only while
its drafted rounds measure faster than plain ones (`zig/src/core/lanes/lone_guard.zig`); shared rounds keep the
shared-round guard.

`tf-lfm2-dspark` also prints the confidence
head against its fp32 reference and times shared holds of 2 to 16 streams.

The loader reads the checkpoint one matrix at a time and keeps only the 8-bit codes; a stream's drafter lane is one
Metal buffer (its layers' keys and values, then its staged rows), and its bytes join the streams the load limit
admits. At load the drafter logs its weights, scratch and a stream's bytes. `tf-lfm2-dspark` also counts host
allocations over twenty served drafter rounds, which must be none.
5 changes: 5 additions & 0 deletions zig/build/metal_targets.zig
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,7 @@ pub fn metalTargets(b: *std.Build, target: std.Build.ResolvedTarget, optimize: s
.{ .name = "tf-lfm2-forward", .path = "zig/tests/lfm2_forward.zig", .about = "LFM2-MoE native teacher-forced logits" },
.{ .name = "tf-lfm2-bench", .path = "zig/tests/lfm2_bench.zig", .about = "LFM2-MoE prompt-chunk and one-row decode timing" },
.{ .name = "tf-lfm2-exact", .path = "zig/tests/lfm2_exact.zig", .about = "LFM2-MoE prompt chunks, windows and committed-state exactness" },
.{ .name = "tf-lfm2-dspark", .path = "zig/tests/lfm2_dspark.zig", .about = "LFM2's DSpark drafter on the GPU against an fp32 host reference of the released drafter" },
.{ .name = "tf-lfm2-fp64", .path = "zig/tests/lfm2_fp64.zig", .about = "LFM2 projection classes and attention on the tensor units, the tiles and the twin, each against float64" },
.{ .name = "tensorfold", .path = "zig/src/main.zig", .about = "The native engine's command line" },
.{ .name = "tf-nemotron-fixtures", .path = "zig/tests/nemotron_fixtures.zig", .about = "Nemotron kernels against the Python engine's captured ops" },
Expand Down Expand Up @@ -455,6 +456,10 @@ pub fn metalTargets(b: *std.Build, target: std.Build.ResolvedTarget, optimize: s
});
test_step.dependOn(&b.addRunArtifact(b.addTest(.{ .root_module = helpers })).step);
test_step.dependOn(&b.addRunArtifact(b.addTest(.{ .root_module = engine })).step);
const lfm2_tests = b.addRunArtifact(b.addTest(.{ .root_module = engine, .filters = &.{"lfm2_moe"} }));
b.step("test-lfm2", "The LFM2-MoE family's tests: config, draws, state and its shared rounds on this Mac's GPU").dependOn(&lfm2_tests.step);
const lfm2_dspark = b.addRunArtifact(b.addTest(.{ .root_module = engine, .filters = &.{"lfm2 dspark:"} }));
b.step("test-lfm2-dspark", "LFM2's DSpark drafter and its taps: config, quantizer and kernels on this Mac's GPU").dependOn(&lfm2_dspark.step);
test_step.dependOn(&b.addRunArtifact(b.addTest(.{ .root_module = lanes })).step);
// the fake fabric maps its windows with mmap: the Macs' RDMA, not Windows
if (target.result.os.tag != .windows) test_step.dependOn(&b.addRunArtifact(b.addTest(.{ .root_module = fabric })).step);
Expand Down
Loading
Loading