Repository navigation
lfm2: draft with LiquidAI's DSpark drafter (--drafter) - #1391
BlivionIaG wants to merge 27 commits into
Conversation
|
A small request, @BlivionIaG: this description links more than 20 issues and PRs, which is over Agent Traffic Control's limit, so ATC drops the PR from its board and collision checks (and marks every check incomplete for everyone). Could you trim the related-work list to the issues this PR actually fixes or depends on (20 at most)? Thanks. |
ashhart
left a comment
There was a problem hiding this comment.
Thanks @BlivionIaG, a lot of careful work in this stack. We mapped all 31 commits to their PRs and reviewed each on current main. For the DSpark drafter (this PR and its descendants):
- Loading changes the released BF16 weights.
dspark_weights.zigquantizes each dense matrix to 4/8-bit codes at startup (8-bit by default), and #1393 still discards the original. Target verification protects final tokens, but the engine doesn't create lossy copies of a checkpoint at serving time. Please run the released BF16 matrices, or refuse the format. Also honour each advertised--drafter-bitsmode or refuse it by name (the native adapter accepts the setting and then uses 8 regardless). - 4-bit mode reads past its buffer. With 4 bits the loader packs
markov_w2at that width, butlfm2_markov_scan(lfm2_dspark.metal) reads a byte per coefficient and the launcher passes no bit width. At 128000x256 the code buffer is 16,384,000 bytes and the shader can address byte 32,767,999 (found statically, not run on a GPU). Please refuse that mode before load or implement it correctly. - The held (asynchronous) path drops draft failure flags: the synchronous path refuses a NaN pick or invalid ID (
DraftNonfinite), butreadPicksafter an unwaited hold ignoresPick.nonfiniteand maps an invalid ID to 0, as does the GPU-held copy. Native serving enables that path, so please carry the flag through the fence and add planted NaN/Inf/invalid-ID controls for both paths. - Contract proof: the new operators report 0 planted controls and no recorded compiler/launch limits (the limit declarations use current-device checks with
offline = false). Please add the gates, red controls, served shapes and records. - Config admission doesn't check
hidden_actbefore the fixed SiLU path, and an explicit scheduled verify without a confidence head silently becomes depth mode; both should refuse by name. - The FP32 reference hard-codes a 6-bit target embedding and its host
matmuljoins workers only after all spawns succeed. - The device receipts are mostly on older bases; we'll need alternating A/B on current main per landing head (the stack's own notes report a 1.8-2.7% four-stream drop on Qwen3.8-27B + DFlash2 from the shared-lane changes, which needs explaining, not dismissing).
|
Thanks @ashhart. Pushed
|
|
Thanks @BlivionIaG. A lot of this is fixed, and fixed well:
What still needs to change:
Once these are in and the stack is rebased on current main without #1379 and #1382, we will run drafted against plain, |
… holds follow the grants A drafter that gives each held chain draft's chance (Drafter.VTable.chances) may set Facts.chances. Drafted.facts then sets Model.draft_probabilities and Model.granted_depth, so the window allocator prices each stream's verify rows by those chances, as it does a head's node chances. With Facts.confidence_floor it instead verifies the drafts at least that likely (a fixed window when 0). With granted_depth a stream's next hold is as deep as its last grant plus a slack: one more draft when the grant took every held draft, none when it stopped short of them (Stream.grant_short), and after a grant of none a probe of draft_slack drafts at the depth rule's probe interval. Facts.slack sets one slack for all three. A shared round's granted depths are cut so the drafting streams' blocks fit one hold pass of batch_rows. When the shared-round guard starts a drafted stretch, its first round grants every stream one draft, so the stretch measures drafted rounds rather than plain ones left by grants of none. All of it is behind Facts.chances and Model.granted_depth, off by default; windows.allocate records grant_short only for granted depths. Tests: chances price the chain's rows and drafted replies equal one-token decode alone and at 1, 4 and 8 shared streams, with the scheduled verify ragged across streams and verifying fewer rows than a fixed window; the facts reach the lane config; granted depths, their slacks and the one-pass cut; holds as wide as the grants.
…py, and holds kept only while they pay The depth rule asks one head draft for a stream with a copy ahead, and a shared round's granted holds were priced only by their verify rows. A drafter whose hold runs as a pass of its own (Facts.copy_first) pays that pass for the one draft, and the copy round then drops it; Model.copy_first now asks it for none. A drafter may also give its hold pass's timed cost by blocks and depth (Facts.hold_costs, priced by hold_price.zig). With granted depths a shared round then keeps granted holds, most expected drafts a row first, only while each raises the next round's expected tokens a ms over its verify rows and the pass; a probe after a grant of none keeps its hold. A stream's last grant records the drafts it expected to keep (Stream.grant_gain). Both are off by default (copy_first false, hold_costs empty). Tests: with copy_first no hold is asked for a copy round and replies equal one-token decode at 1 and 3 streams; a hold's cost between and past its timed passes; granted holds kept while they pay, none for a dear pass, and a probe's hold kept.
Bound the reused ordering buffer by batch_rows, and cover reuse with an allocator that refuses further allocations. Interpolate between the measured depths surrounding the requested depth, including nearest-end extrapolation. Query a stateful copy proposer once for a granted-depth budget decision.
The lane core can put an external drafter in front of a target. The drafter reads the target's hidden rows after some layers (its taps). LFM2-MoE had no taps, so it could draft only with the core's own heads. prepare_features names the layers. From then on, each forward copies each row's output of those layers into the scratch (lfm2_tap, a plain copy of bf16 values). features() gives a stream's rows of the latest verify or one-token round. prefill_features hands each prompt chunk's rows to the drafter before the chunk's end is kept for reuse. The prompt's last row is kept beside the stream's caches for its first draft. A drafting stream resumes only a kept state that also holds its drafter's context (Snap.draft). Without a drafter nothing changes: no taps are kept and every forward runs as before. lfm2_tap registers with the kernel contract (metal_lfm2_tap.zig): the third of five taps' columns at 1, 10, 32 and 2,048 rows must hold the layer's bits and the other columns what the call found. Gate 5 checks its launch against every Apple chip we serve and this GPU. test-lfm2-dspark checks the same copy through the family's own kernels.
35e9255 to
8e9b548
Compare
|
@ashhart Rebuilt on current main |
# Conflicts: # CHANGELOG.md
|
Complete own-head qualification for The entire required matrix completed: 144/144 cells, 306/306 oracle records, 512-token replies, three balanced rotations, code/chat, greedy/sampled, 1/4/8/16 streams. Plain/BF16/8-bit use the exact same frozen binary for this PR, not the cumulative stack's binary. Exactness and scenario failures are zero; all nine servers exited gracefully with code 0 and no forced kill. Hardware: M4 Pro / 48 GiB, not an M5 measurement. All comparisons below use per-cell medians of the three rotations:
Raw results remain immutable and source-bound; this failed run will not be pooled with other heads or a new candidate. The remaining landing heads continue sequentially without competing local builds or GPU/model work. Any optimization must preserve the accepted depth-rule/headless behavior, complete drafter context and fresh-zero reservations; no stream-count disabling or weakened gate. All nine exact-head CI checks and local native/scoped checks passed, but that does not close the performance gate. Historical receipts retain their original provenance. Source/binary/model/driver provenance, raw-file digests, every shutdown{
"source_revision": "7225d5478ad1eed4df8c683312d271ad6c7a4a69",
"binary_sha256": "bbbf1882660dc4a7742ebb6ebf5bbe46ebbafcf81a6625d5b24463efa6390987",
"pinned_main": "b17fb507af5ec1da599c5381b6990d9067e6a965",
"target_revision": "667408d494bde191c2cb9dd4f4549827816b9275",
"drafter_revision": "e052e5b1dd103c38cdd4f8ddc068d7dea4586f55",
"driver_source_revision": "a1b4f2b94d223d37b1ad0a3a4a67490859307aa8",
"driver_sha256": {
"bench_lfm2_dspark.py": "928a1c1b6751e184243c48d187e552b622874da0ec6168a101e3d636664e3e97",
"bench_lfm2_dspark_verdict.py": "c30d4c902979acaab150440bcf76d95492394e2abe0fe720903cb5f3090881ce",
"bench_concurrent.py": "3234c3edfc71725d9a66f1a6485f1bc43870a3ee0c797f90ac1e7ef9a6ae74e2",
"bench_evidence.py": "54c28474f0804dbd120e3fb5859be0d1667ef21ca502880d0bed098ec9bb42ff",
"bench_auth.py": "1896120b70d0df05646870a252697e20ce84c63e09dcba88b2a4e5a55ccb3c5b",
"bench_metrics.py": "26f4c3ecbb03df4f12e24d18840fea989d9ef8c63117ca11eb944ef670458a27",
"bench_timing.py": "158f636ef0b3bed460ce19ef4872b6d8607ff7a8590562d06bae8bd55e6f44ed"
},
"environment": {
"os": "macOS-26.5.1-arm64-arm-64bit-Mach-O",
"os_build": "25F80",
"machine": "arm64",
"hw_model": "Mac16,7",
"cpu": "Apple M4 Pro",
"memory_bytes": "51539607552",
"python": "3.14.7"
},
"raw_file_sha256": {
"metadata.json": "3de29c232bc6b2640bf11b3ac69ba754d0b42bfc6afde071bb220edb873a7b78",
"requests.jsonl": "bb9ec110d5893d98f074013e9b03f525adef0384ab075d5db42e0e396652ac5b",
"cells.jsonl": "0f591859dfa676eba796ff5f4b0f31cdf2ccf247ef8d015e3570c3f2b88f73c2",
"events.jsonl": "53f7ad4180e91521a572c34113ecf0575eec1b297dc5f7366b1d1b865de6d203",
"summary.json": "b49998fc29cfb1a3d8d1fc22dcd31c40a646a065d14f98f8a20e960cba5d3704"
},
"shutdowns": [
{
"event": "shutdown",
"at": "2026-11-01T08:17:06Z",
"label": "r0-plain",
"forced_kill": false,
"seconds": 0.6,
"returncode": 0
},
{
"event": "shutdown",
"at": "2026-11-01T08:26:11Z",
"label": "r0-bf16",
"forced_kill": false,
"seconds": 0.4,
"returncode": 0
},
{
"event": "shutdown",
"at": "2026-11-01T08:35:25Z",
"label": "r0-bits8",
"forced_kill": false,
"seconds": 0.4,
"returncode": 0
},
{
"event": "shutdown",
"at": "2026-11-01T08:46:01Z",
"label": "r1-bf16",
"forced_kill": false,
"seconds": 0.4,
"returncode": 0
},
{
"event": "shutdown",
"at": "2026-11-01T08:56:05Z",
"label": "r1-bits8",
"forced_kill": false,
"seconds": 0.3,
"returncode": 0
},
{
"event": "shutdown",
"at": "2026-11-01T09:05:18Z",
"label": "r1-plain",
"forced_kill": false,
"seconds": 0.4,
"returncode": 0
},
{
"event": "shutdown",
"at": "2026-11-01T09:14:18Z",
"label": "r2-bits8",
"forced_kill": false,
"seconds": 0.4,
"returncode": 0
},
{
"event": "shutdown",
"at": "2026-11-01T09:23:15Z",
"label": "r2-plain",
"forced_kill": false,
"seconds": 0.5,
"returncode": 0
},
{
"event": "shutdown",
"at": "2026-11-01T09:32:20Z",
"label": "r2-bf16",
"forced_kill": false,
"seconds": 0.3,
"returncode": 0
}
]
} |
Isolated source candidate: full pass costs and settled GPU timing, capacity-clipped staging, and behavioral regressions. Runtime checks and performance qualification are pending; the frozen matrix job is unchanged.
# Conflicts: # docs/receipts/pr1381-review/README.md # zig/src/core/lanes/config.zig # zig/src/core/lanes/depth.zig # zig/src/core/lanes/drafted.zig # zig/src/core/lanes/drafter.zig # zig/src/core/lanes/granted_reservation_test.zig # zig/src/core/lanes/hold_price.zig # zig/src/core/lanes/shared_drafts.zig
Source-only integration checkpoint. Compilation, native execution, and complete own-head performance qualification remain pending.
|
Own-head correctness replay completed on the newly published source below. Code/chat, greedy/sampled, one/four streams,64tokens; plain, BF16 and8-bit. All target hashes agree with solo, Not performance qualification. The separate complete512-token,1/4/8/16-stream, three-balanced-rotation gate is now running on immutable own-head HTTP server binaries. It must pass all32 individual drafter-versus-plain medians; earlier source failures and historical receipts are not relabelled. CI is still independently pending/observed, not waived. {
"scope": "Own-head code/chat, greedy/sampled, 1/4 streams, 64 tokens; not performance qualification",
"source_revision": "a87815724d8fc37597d9b9a65e78dea7ffec7fb6",
"server_binary_sha256": "9da45aff1ccc9d3599ec1c6e84aae3d6d8af613b751128a283f5c6fb25afe62a",
"pinned_main": "7651ce64fcfdcde9325721218534c50eb71c15ee",
"cells": 24,
"oracles": 30,
"exactness_failures": 0,
"clean_shutdowns": [
{
"event": "shutdown",
"at": "2026-11-01T14:26:21Z",
"label": "economics-r2-1391-plain",
"forced_kill": false,
"seconds": 0.5,
"returncode": 0
},
{
"event": "shutdown",
"at": "2026-11-01T14:26:53Z",
"label": "economics-r2-1391-bf16",
"forced_kill": false,
"seconds": 0.5,
"returncode": 0
},
{
"event": "shutdown",
"at": "2026-11-01T14:27:25Z",
"label": "economics-r2-1391-bits8",
"forced_kill": false,
"seconds": 0.7,
"returncode": 0
}
],
"core": "318 passed; 1 skipped; 0 failed.",
"serving_family_scope": "Build Summary: 72/72 steps succeeded; 130/130 tests passed",
"native_device_scenarios": 6,
"native_context_lengths": [
16,
48,
2144
],
"drafter_bits": [
0,
8
],
"new_lean_findings": 0,
"inherited_lean_findings": 424
} |
Scope and review changes
Adds the opt-in LiquidAI DSpark drafter through
--drafter, with released BF16 weights by default and explicit 8-bit quantization. The existing depth rule remains the headless scheduler. The rejected shared/lone guards and fixed scheduler modes are absent. Existing refusal/error-path fixes remain: unsupported rank/activation/rope and 4-bit admission, failed wider-lane refusal, held-pick validation, and scratch/warm cleanup.All stages use the existing allocator with the drafter’s measured stream-count/depth grid. Only neural blocks pay head cost; copy and forced rows still occupy verify capacity. Initial holds wait for a funded grant. Split holds pay for every physical pass, and settled GPU work is attributed at the existing wait without adding synchronization. Normal copy pricing is restored in drafter arms without changing the no-drafter configuration.
Prompt and verified taps remain owned before the target overwrites them, and are projected on a hold, snapshot or stage overflow. Zero-grant rounds retain contiguous context for later drafts; absorption is not simply skipped. No rejected stream-count guard is reintroduced.
Current source-bound checks
zig build native test-lfm2 test-lfm2-dspark test-native test-draft-metal-host test-drafter-skip-metal-host -Dcpu=apple_m1 -Dversion=1.2.0 -j1.draft:false, concurrent and cross-arm target token hashes match. This is correctness evidence, not the 512-token performance gate.Full performance gate and historical provenance
The prior own-head complete matrix belongs to
7225d5478ad1eed4df8c683312d271ad6c7a4a69: 144/144 cells and 306/306 oracle records exact, three balanced rotations and nine clean shutdowns, but 32/32 speed comparisons failed. It is not a measurement of the new head.The latest maintainer M5 measurement supersedes the older “1–2% tradeoff” interpretation: every 4-/8-stream cell was slower in BF16 and 8-bit, with additional sampled 1-/16-stream losses. The required new gate remains same-head no-drafter versus BF16 and 8-bit, code/chat, greedy/sampled, 1/4/8/16 streams, 512-token replies, three balanced rotations, exact tokens and clean exits. Every one of the 32 per-arm medians must reach its own plain baseline. No tolerance, pooled cells, selected greedy-only rows or short smoke substitutes.
Historical correctness, long-context and cancellation receipts and restack/device/load-memory receipts retain their original source/device provenance. They are not relabelled as measurements of this head. New short/native checks do not replace them. Fresh source-bound startup peaks now compare the new confidence/allocation heads in BF16 and 8-bit, two balanced orders, before any generation; they are not serving-memory or minimum-RAM claims.
Part of #1374. No force-push, assistant PR merge or CI-gate weakening. Ash merged #1381 himself; the remaining chain still needs performance qualification and maintainer review.