Repository navigation
Conversation
…ost, equal to its Python within fp32 rounding
…ine's final rows; the Qwen 27B CUDA family gives them
…ies in encoder order
… block, and its attention split by query rows across threads; bits unchanged
ashhart
left a comment
There was a problem hiding this comment.
Thanks @gilby. Chat code and launches are untouched, good. On top of #1361's points: Lora.open loads lazily on the engine thread (an estimated 1-2 GB) outside the memory budget; please open and reserve it at startup. Add a guard that the LoRA never runs during stream capture, and reach cuda_lora.zig's test from a host step. Please point the docs at #1366's rebuild tool rather than a personal re-upload of the adapter.
…s the qwen3_5 CUDA family
…pass, on a cache reserved at startup
…vs 4 head threads bit-equal, in CI
…s decision passes only, y += (bf16(x A^T)) B^T in fp32; never inside a capture
6a97715 to
36b0c00
Compare
|
Thanks. Rebased on #1361's new head, and the adapter commit was reworked (
Gate on this head (one DGX Spark, GB10, CUDA 13.0,
|
|
Review against main e9b69ed (shared notes on #1361). Scoping is right: the adapter is set only for the decision pass's fresh cache and cleared after, and chat never sees it. The blocker is exactness. The runtime computes |
… (tensor cores), packed and their scratch reserved at startup; DenseRows stays the fallback
|
The adapter's two products now run on the base's bf16 lane GEMM (tensor cores) instead of |
|
Thanks for the review. Agreed: a rank-truncated bf16 adapter can't reproduce the merged release's arithmetic, and a second resident copy of the decision projections is the opposite of what this PR was for (one copy of the base weights). So I'm closing this rather than turning it into the merged-weights path. #1361 stays the way to serve a Clef release upstream. The lane-GEMM speedup and numbers above stay here for reference. |
Part of #1360. Stacked on #1361 (Clef-Flash: the record encoder, joint head and
--clefendpoint); the last commit is new here.Cloudflare Clef for Qwen3.8-27B ships as a merged checkpoint. Folding the adapter into a 4-bit pack re-quantizes the touched projections and loses most of it, so this applies Clef's adapter at runtime instead, on the base checkpoint the server already serves for chat:
qwen3_5/cuda_lora.zig: loadsadapter.safetensors(the rank-256 / rank-48 LoRA pairs on layers 40–63, checked against each projection's widths) and computesy = bf16(y + bf16(x A^T) B^T)with fp32 sums and a bf16 add.cuda_lora_lane.zig: on a base with the lane kernels (ModelOpt / compressed-tensors: NVFP4, FP8, bf16) both products run on the same bf16 lane GEMM (tensor cores) the base's projections use; each factor pair is packed at startup (A as a(rank, k)projection, B as an(n, rank)one; a rank below a whole 64-group is zero-padded, which adds exact zeros) and the products' scratch for onemax_rowspiece is reserved with it. Other bases keep theDenseRowspath. Either way a row's bits do not depend on the row count or the chunking (tf-cuda-test modelopt: the lane at the adapter's shapes, 600 rows at once byte-equal to pieces of 1–255).cuda_forward.zig: six hooks after the adapted projections, no-ops unless a decision pass enables the adapter. The decision pass runs outside the captured CUDA graphs; chat and decode run exactly as before.--clef DIRwithDIR/adapter.safetensorsopens the adapter in the family'sopen, beside clef: Cloudflare Clef decisions on /v1/systemone through its joint schema head (--clef DIR) #1361's reserved decision cache, so the memory budget counts it; only decision pieces turn it on. It refuses to run inside a stream capture (error.QwenLoraDuringCapture).cuda_lora.zig's test runs on the host fromtest-qwen35-adapter-host. One server answers chat with the base weights and/v1/systemonewith Clef.Adapter weights
Cloudflare publishes Clef only as merged weights. Build the adapter directory from the public releases with #1366's
tools/clef_adapter.py --merged <Cloudflare/clef> --base <Qwen/Qwen3.8-27B> --out <dir>(rank-256 LoRA on the 186 projections of layers 40–63, Cloudflare's joint head files,lm_head_rows.safetensors, a manifest with both revisions) and pass the folder to--clef. Base + adapter vs the merged release: mean KL 3.9e-7, top choice 100%.Receipts (one DGX Spark, GB10 128 GB, driver 580.178, CUDA 13.0; this branch, re-run on head
472497a5dfor the NVFP4 row)Cloudflare's 60 records / 557 questions against Cloudflare's own bf16 merged-model output; the gate is mean KL ≤ 0.05 and top choice ≥ 95%. Chat is a greedy reply sent before and after the decisions.
RadixArk/Qwen3.8-27B-NVFP4-BF16-LMHeadTensorFold/Qwen3.8-27B-MLX-4bit(earlier tree)The quality depends on the 4-bit base: on NVIDIA's NVFP4 weights the runtime adapter matches the Python engine's Clef (KL 0.0048–0.0074, top choice 95.7–95.9% on EXL3 4bpw / NVFP4 bases).
bench_concurrent --tokens 128 --reps 3 --temperatures 0 --levels 1,4 --alone --serial: 0 unequal, 0 failed.Decision latency (same 60 records, one at a time,
--parallel 4 --context 32768, NVFP4 base)The first version ran the two products on
DenseRows, a CUDA-core kernel; nsys on a 16K-token decision showed it taking 80% of the kernel time. On the base's tensor-core GEMM the adapter's share is small:DenseRowsp50 / p95 (s)Both builds pass the gate (
DenseRows0.0085 / 95.87%, lane 0.0085 / 95.69%); their probabilities differ in the last bits because the sums run in another order. The packed adapter and its scratch take 0.29 GiB more than theDenseRowscopy, counted in the startup line (model 24.33 → 24.62 GiB). Chat replies are the same with both builds.zig build test --summary all: 502/502 steps;zig fmtclean;lean_check.pyadds no problems overmain.