Repository navigation
Conversation
|
Thanks @jschmied. A float64 reference that agrees with the official modelling code at every position is the evidence we want. Admitting only the export's exact ignore list keeps the scheme honest. Before this leaves draft:
To your questions:
Then we'll run the contract and its limits. We'll serve the NVFP4 export on a GB10: drafted against plain, concurrent against solo, prompts across the sliding window, and resumed against fresh. |
|
@jschmied Could you rebase the CUDA follow-up onto the updated LFM3 admission/Metal work, preserving both authorships, so the shared admission and build files have a common base? The Metal chain is now split into admission #1513, text #1514 and vision #1502. The admission branch is The Metal chain remains draft while numerical and performance qualification continues. This request is to reconcile the overlapping admission/build changes; it does not claim the chain is ready to land. |
The shared-window chunk and merge bodies move to paged_attn_segs.cuh, included twice by nemotron_attention.cu: Nemotron's head-128 kernels keep their names and SASS, and head 64 adds tf_paged_attn_chunk_segs_64 and tf_paged_attn_merge_segs_64. prefill_attention.cu instantiates pattn_kernel<64, 8, 4, 8> over paged caches. sassid on sm_80/86/89/120/121: 0 functions changed, 15 added.
lfm2_moe.cu holds what the shared operators do not: the short conv over each stream's state with every window row's snapshot, the commit of an accepted row's conv state, and the router (fp32 weights, top-4 of sigmoid + bias, ties to the lower id, a NaN ranked as -inf). Each has a planted control. Launch records cover these kernels and the head-64 paged attention on nvcc 13.0, with one 13.2 override.
The MLX 4-bit checkpoint (embedding at 4, 6 or 8 bits) on the shared affine-4 projections and experts, with prompt chunks of 1024 rows, one-row decode and copy-drafted windows over --parallel streams. The ten attention layers keep their keys and values in 512-position pages, and each stream has its own conv state. There is no drafter and no prompt cache yet.
…ount The gated short conv over each stream's state and the conv-state commit move out of lfm2_moe.cuh into liquid_conv.cuh, templated on the tap count, so LFM3 (7 taps) can take the same bodies as LFM2.5 (3). lfm2_moe.cu keeps the router and instantiates the 3-tap pair as tf_liquid_conv_3 and tf_liquid_commit_3; the controls become tf_ctl_liquid_conv_3 and tf_ctl_liquid_commit_3. cuobjdump -sass on sm_80/86/89/120/121, nvcc 13.0 and 13.2: the moved bodies are identical under their new names, every other function unchanged.
… against float64 tf_paged_attn_chunk_segs_64 with tf_paged_attn_merge_segs_64 and pattn_kernel<64, 8, 4, 8> run over shuffled 512-key pages, decode rows over up to eight streams a launch, and are held head by head to contract_attn's float64 bound. Two controls in contract_controls.cu read the values from the neighbouring lane's dims (decode) and from the neighbouring 8 dims (prompt rows). To build them from the same bodies, nemotron_attention.cu's page and tile helpers move to paged_attn.cuh and prefill_attention.cu's to prefill_attention.cuh; every existing function's SASS is unchanged on sm_80/86/89/120/121 under nvcc 13.0 and 13.2.
… of every top-1 miss ref64.py dequantizes the MLX 4-bit weights exactly and runs the forward in float64; --bf16 adds the engine's bf16 storage points. It checks the CUDA engine's greedy picks on teacher-forced prompt rows and on reply rows; REF64.md lists each miss with the prompt, the position, both tokens and both margins.
The recipe gets a CUDA section (build, serve, revision, GB10 receipt, licence), the README row names one NVIDIA GPU beside the Mac, and the contract's coverage lists LFM2.5's CUDA kernels.
…m leasing The kernels already read keys and values through a slot's page table, so the pool changes only what the table holds and how large the arena is; no kernel changes. A page is 512 positions of the ten attention layers' keys and values (10 MiB, 20 KiB a token); the conv states stay a fixed part a slot (240 KiB). - Loaded.layout with engine_paged, as Dory's: the plan sets a window of pages aside for the first stream; each stream leases its prompt, its reply and one verify window at its prompt pass and gives them back when it ends. A request that cannot lease waits (LaneMemory); fits is the host's admission. - Model.attach makes the arena and the tables and conv states of the slots the plan names; forward refuses rows past a slot's pages. The idle server logs the pool's census. - TENSORFOLD_LFM2_KV_PAGES=0 keeps every stream's own window of pages, made at open. Served on the GB10 at --parallel 8, 16k: drafted == plain and concurrent == solo, also with 8 long prompts on a 52-page pool where the last waits for pages; the replies equal the fixed pages' ones.
…the pages' rows through its position A state at `at` is the slot's conv states and its keys and values at positions [0, at) of the ten attention layers: one copy a run of consecutive whole pages, then the last page's rows a layer's keys and values. It is copied behind the chunk that ends at the mark and restored into the new stream's leased pages before its next chunk, so a later turn's pass starts at its history. Lookahead 0, as the other headless families. States live in 2 MiB snapshot pages allocated once at startup inside the prompt-cache budget (`snap_ready`), as GLM's: a keep or a restore leases or reads pages and allocates nothing, and a dropped state's pages go back to the pool at once (every copy that reads or writes them is queued on the backend's one stream). The store counts held and spare pages through `snap_charged` / `snap_spare` / `snap_reuses`.
…dds no pass A chat mark inside a prompt chunk ended the chunk there, and every extra prompt pass reads the weights once more: a cold 10k chat turn, whose two marks sit in its last chunk, ran 12 passes instead of 10. Now up to four marks inside a chunk are tapped instead: the conv kernel runs once more on each mark's last row alone, writing the same y and that row's conv states into the model's tap buffer, and the keep copies those states with the K/V rows before the mark. The pass goes on to its grid line; a fifth mark still ends it. The kernel is unchanged.
Prepare text and VL 4/6/8-bit checkpoints with revision-pinned header inspection and native metadata validation. Inference remains unregistered pending operator and model qualification. Refs ashhart#1491
…KV head prefill_attention.cu instantiates pattn_kernel<128, 8, 4, 8>: Nemotron's <128, 8, 8, 8> needs eight query heads a KV head, and LFM3's full-attention layers have four (16 over 4). Its launch record is measured on nvcc 13.0 and 13.2 from every SM's image (206 registers on sm_80/86/89, 231 on sm_120/121, no static shared memory). sassid on sm_80/86/89/120/121, both toolkits: 0 functions changed, 5 added.
LiquidAI/LFM3-1.7B-NVFP4 stores only the FFN as NVFP4: w1, w3 and w2 as e2m1 codes in groups of 16 with e4m3 block scales, an fp32 tensor scale and a static input scale. Every mixer projection, the embedding and the tied head are bf16, and the depthwise conv kernel is [channels, 1, taps]. - config.zig reads `quantization_config` with quant_method "modelopt" as Scheme.modelopt_nvfp4: quant_algo NVFP4, one Linear group of static FP4 g16 weights and inputs, and an ignore list that is exactly the head, the embedding and every layer's mixer. Anything else is UnsupportedLfm3Quantization. MLX configs parse as before (Scheme.mlx_affine), so a decoder that reads one scheme can refuse the other by name. - weights.zig's schema takes that layout for the bare namespace. tf-lfm3-check passes the published header (b30025bb): 502 text tensors, 1,829,689,408 bytes. The MLX-4bit header still passes (594 tensors, 973,008,384 bytes). Host tests 26/26.
…tract lfm3.cu instantiates liquid_conv<7,0> and liquid_commit<7,0>; contract_controls.cu adds their <7,1> controls. contract_lfm2_moe.zig's Conv and Commit take the tap count; contract_lfm3.zig checks the served 7-tap shapes. Records are identical on nvcc 13.0 and 13.2. contract_limits.zig now lists the lfm2_moe and lfm3 images.
Conv layers keep a 6-row state per slot; full attention runs on kv_pool pages with partial RoPE (32 of 64 pairs); sliding attention runs on a 1280-row per-slot ring through the shared window kernels at window 256. FFN w1|w3 is one FP4 lane weight (shared weight_scale_2), w2 FP4; q|k|v|gate, o, conv and the tied head are bf16. Adds --parallel lanes, drafted rows, and a copy-based prompt cache (conv states, rings, pages [0, at)).
…and LFM3 on CUDA zig/src/families/liquid/ holds what the two Liquid families shared as copies: the lane backend (cuda_lanes.zig), the kept states in the prompt cache's preallocated pages and the walk over a slot's state (cuda_snap.zig), and the native glue: open, admission, the pool and the prompt-cache hooks (cuda_native.zig). Each is generic over the family's model and forward, as liquid_conv.cuh is over the conv's taps. A model names its fixed regions (LFM2.5: the conv states; LFM3: the conv states and every sliding layer's rings) and its paged layers; each family keeps its config, weights, forward, refusals and model. lfm2_moe's backend, snap and native are now thin instances, and lfm3's no longer copy them.
…4, with a control LFM3's full-attention prompt rows (16 query heads over 4 of 128) join the head-64 paged attention's gates, held head by head to contract_attn's float64 bound over shuffled 512-key pages; tf_ctl_pattn_128 reads its values from the neighbouring 8 dims.
config.servedOn(config, backend) refuses modelopt_nvfp4 on Metal with Lfm3ModeloptNvfp4NotOnMetal; CUDA serves it, and both serve the MLX layouts. Metal's LFM3 open (ashhart#1514) calls it.
The export's static FP4 input scales are read, as the admission requires, and not applied: activations stay bf16. The load logs it and the loader's comment says so.
…top-1 miss ref64.py dequantizes the NVFP4 weights exactly and runs the forward in float64; --bf16 adds the engine's bf16 storage points. REF64.md lists each miss on teacher-forced prompt rows and reply rows with both tokens and both margins.
08b338c to
2909751
Compare
|
Thanks @ashhart. All seven are done; the head is
Metal refusal: #1513 is still open, so this PR adds it. The served checks were rerun on this head, and every token hash is equal to the previous head's:
|
|
@BlivionIaG Done: #1508 now stacks on #1513's head
|
Part of #1491 (CUDA side). Stacked on BlivionIaG's LFM3 admission #1513 (head
b3d9d6a4e,feature/lfm3-admission). His two commits are cherry-picked unchanged with his authorship:e003af71b(=8192e5437) and5e8e2f84d(=b3d9d6a4e). Below them are #1492 (LFM2.5 on CUDA) and #1503 (its pool pages and prompt cache). The ten commits after5e8e2f84dare new here. It stays a draft until those land, then I'll rebase onto main. There is no rootbuild-lfm3.zig.What
LFM3-1.7B text served natively on CUDA from
LiquidAI/LFM3-1.7B-NVFP4(ModelOpt NVFP4, revisionb30025bb). It is weight-only: the FFN's FP4 weights are used as stored, and every activation stays bf16. The export's static FP4 input scales are read, because the admission requires them, but not applied. The load says so in its log, and the loader's comment atcuda_weights.zig:118says so too. MLX affine and FP8 come next on the same family; VL after that.d7735968f, on top of lfm3: admit official MLX text and VL checkpoints #1513): the ModelOpt NVFP4 scheme in the sharedlfm3/config.zig/weights.zig, with the export's exact exclusion list. Only the FFN (w1, w3, w2) is FP4; conv, attention, gate, embedding and head are bf16. The MLX layouts are unchanged.f8097cbbb):config.servedOn(config, .metal)returnsLfm3ModeloptNvfp4NotOnMetalformodelopt_nvfp4, with a test. Metal's LFM3 open in lfm3: serve the MLX text decoder on Metal #1514 calls it.4a61a437f: the 7-tap instances of lfm2_moe: serve LFM2.5-24B-A2B on CUDA (plain serve, shared kernels) #1492'sliquid_conv.cuh(tf_liquid_conv_7/tf_liquid_commit_7), under the contract with controls.554a30843:prefill_attention's template at head dim 128 with 4 query heads a KV head.7fd391ec2):act_rows.headGate) and the shared norms.48f23c96b):zig/src/families/liquid/holds the lane backend, the kept-state walk and the native glue (open, admission, pool, prompt-cache hooks), generic over the family's model and forward, asliquid_conv.cuhis over taps.cuda_backend.zigfiles are 17 lines each.2c863784a):pattn_kernel<128, 8, 4, 8>againstcontract_attn.zig's float64 bound over shuffled pages, with a control (values from the neighbouring 8 dims).942eab46b):tools/lfm3/ref64.pyand its receiptREF64.md.290975190):docs/recipes/lfm3-cuda.md, with the command, the revision, the weight-only section, the receipt and the licence.Licence
The weights are under Liquid AI's LFM Open License v1.0 (
license: other,lfm1.0; the text is the checkpoint'sLICENSE), as the MLX exports are.Verification (GB10, sm_121, real weights)
modeling_lfm3.pyrun in float32):REF64.mdlists all 5 misses with both tokens and both margins (float64 margins 0.003–0.153).TENSORFOLD_LFM3_KV_PAGES=0);prefill_attention40 functions (+5) andcontract_controls265 (+15).pattn_kernel<128>pass gates 1–4. In the filtered runs (paged,liquid) all 13 controls go red, theirs among them. Limit records for the Liquid images pass on nvcc 13.0 and 13.2; the only failures are main's two staleqwen35records.lean_check: 0 new against lfm2_moe CUDA: attention pages from the shared KV pool, and the prompt cache (stacked on #1492) #1503's head.zig fmt --check zigandzig build testpass.Speed (GB10)
The same cold turn with the cache off takes 0.774 s. The 31 ms keep cost is LFM3's two marks still cutting the prompt pass; taps that also save the rings would remove it. LFM2.5 on this head runs its cold turn in 1.218 s, against 1.216 s on #1503 and 1.211 s on #1492.
Only the cold-turn rows are new this round. Decode, the cold TTFTs and the warm turn are from the previous head.
Settled in review