Skip to content

metal: DSV4 M1 Ultra prefill/decode campaign - #2

Open
auroter wants to merge 6 commits into
QuixiAI:mainfrom
auroter:metal-m1ultra-campaign
Open

metal: DSV4 M1 Ultra prefill/decode campaign#2
auroter wants to merge 6 commits into
QuixiAI:mainfrom
auroter:metal-m1ultra-campaign

Conversation

@auroter

@auroter auroter commented Aug 15, 2026

Copy link
Copy Markdown

Prefill and decode optimization for DeepSeek V4 Flash 0731 (dsv4-xxs-1 profile) on Apple Metal, M1 Ultra.

The point of this campaign: match and beat antirez's ds4 — the optimized reference engine for this model — on the same box, with the full SlimServe/vLLM serving stack (OpenAI API, DSpark speculative decoding, TurboQuant draft KV, prefix caching). Both engines measured on the same M1 Ultra with the exact-token harness.

SlimServe vs ds4 (same box, same model, same workloads)

Workload antirez ds4 SlimServe (this PR) vs ds4
Prefill, 2048-token chunk 277 tok/s 369 tok/s 133%
Decode, 1k-in/2k-out anchor 21.1 tok/s end-to-end (25.5 decode-only) 31.6 tok/s end-to-end (33.3 decode-only) 150%

Campaign start was 79 tok/s prefill and 15 tok/s decode, so this is 4.7× / 2.1× over where the stack began.

Where the speed comes from

Decode (15 → 31.6 tok/s)

  • Per-step marshalling memos: dtype casts, weight-slice views, and rope tables are converted once per step/load instead of per layer. This alone roughly doubled decode — per-layer host overhead was starving the GPU.
  • Fused mHC pre/post kernels: one dispatch per token replaces ~230 aten launches per call, including the full Sinkhorn iteration.
  • Native dsv4_router_topk, sum-folded q2_K down projection (GEMV + weighted-sum in one kernel), split-K sparse-MLA decode, fp16-direct KV insert, single-dispatch inverse RoPE.
  • Load-time q2_K SoA repack so the decode GEMVs read coalesced planes.

Prefill (79 → 369 tok/s)

  • Tiled MoE prefill GEMM: a qc_moe_mm_map0 work queue feeds a 64-slot dual-half iq2_xxs simdgroup-MMA tile and a q2_K SoA down twin — MoE layers stop being token-at-a-time GEMVs at prefill widths. Biggest single prefill win.
  • Dense-causal MMA flash attention over pre-decoded half scratches, replacing the decode-shape candidate walk on prefill steps.
  • Fused compressor front (cr=4 and cr=128) and a wide-tile transposed-store q8_0 GEMM for the large-M dense projections.
  • Profile fix: max_num_batched_tokens 2048 → 2176 so the scheduler's draft-token reserve stops splitting a 2048-token prompt into two chunks.

Also in this PR

  • Cleanup pass: env switches 37 → 12, dead experiments removed end-to-end, one latent bug fixed (pointer-vs-string compare in the native tape).
  • Merged with current main (A100 Q4_K fused MoE, NaN split-K fix, Muse-Glimmer Metal): the campaign's fast paths and Muse-Glimmer's coexist behind format/dtype dispatch, and the profile ships main's 262144-token sizing — which this branch's Metal indexer port actually makes reachable, and which measures slightly faster at the decode anchor.
  • Six kernel oracle tests under tests/kernels/ (runnable directly on the box, no pytest needed).
  • Campaign records in perf/ (method, per-wave evidence, raw-artifact locations).

Validation

Zero-numeric-change discipline for the cleanup and the main merge: pinned to the benchmark config, the 8-tok, off1-2000, and 2500-token serving anchors are bit-exact (output sha256 + spec-decode counters) against the pre-cleanup build, and all six kernel oracles pass. The table above is measured on the merged tree under the shipping profile.

Known issues (documented in-code; fixes coming as separate PRs)

  • A pre-existing async-output completion-event race can park the engine on a boot's first multi-chunk prefill; the boot-ramp protocol in perf/prefill_handoff.md avoids it deterministically, and vllm/platforms/metal_compat.py documents the details.
  • The step-tape diagnostic (VLLM_QC_STEP_TAPE, default off) needs route re-validation before its tape-only mode is trusted; see vllm/models/deepseek_v4/metal_tape.py.

@coderabbitai

coderabbitai Bot commented Aug 15, 2026

Copy link
Copy Markdown

Important

Review available on request

  • 🔍 Trigger review

Reviews should be triggered manually for repositories with fewer than 10 stars. Select Trigger review above or comment @coderabbitai review to review the latest changes. For a full review, comment @coderabbitai full review.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: c6b5309d-0e13-49a2-b010-2880a05d4cc4


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Prefill and decode optimization for dsv4-xxs-1 on Apple Metal, plus the
cleanup pass that made the tree PR-ready.

Prefill: 2048-token chunk 79 -> 371.6 tok/s (4.7x; 134% of the ds4
reference's 277). Tiled MoE prefill GEMM (qc_moe_mm_map0 + 64-slot
dual-half iq2_xxs tile, q2_K SoA down twin), dense-causal MMA prefill
FA over pre-decoded scratches, fused compressor front (cr=4/cr=128),
wide-tile transposed-store q8_0 qgemm, prefill-width mhc, and a
max_num_batched_tokens fix (2048 -> 2176) that stops the scheduler
splitting a 2048-token prompt into two chunks.

Decode: 15 -> 30.5 tok/s end-to-end on the 1k-in/2k-out anchor.
Per-step marshalling memos, fused mhc pre/post, dsv4 router top-k,
sum-folded q2_K down projection, split-K sparse-MLA, load-time q2_K
SoA repack, fp16-direct kv insert, single-dispatch inverse RoPE.

Gates: 8-tok, off1-2000, and 2500-token anchors bit-exact (shas plus
spec-decode counters) across the cleanup; six kernel oracles pass;
prefill walls at the standing baseline. Method and evidence in
perf/optimization_status.md and perf/baseline_status.md.

Known open issues, documented in-code and in perf/: the async-output
completion-event race at multi-chunk request completion (boot ramp
protocol in perf/prefill_handoff.md; fix planned as a separate PR) and
the step-tape diagnostic pending route re-validation.
auroter added 2 commits August 14, 2026 22:56
…ltra campaign

Semantic resolutions:
- qc_mmvq: campaign mb fast path keeps precedence for its gated envelope
  (batch 2-8, q8_0/q2_K/iq2_xxs/q6_K); Muse-Glimmer's qgemv_mm row-block
  walk becomes the general path, with the q8_0-small summation-order
  carve-out extended to it.
- rms_norm: single binding dispatches bf16 contiguous inputs to the
  Muse-Glimmer fixed-D kernels and everything else (DSV4 fp16 strided
  q/k splits) to the w32/strided variants; duplicate binding and Python
  wrapper removed; Muse encodes labeled for the CB census.
- compressor: NaN-hunt write-site debug hooks composed with the
  comp_full_compress phase bracket.
- tk_launch.h: both launch_qgemv_mb and launch_qgemv_mm retained.
- dsv4-xxs-1 metal profile: main's 262144/16 GiB sizing retained with the
  campaign's fp16 dtype and 2176 batched-token reserve; long-context note
  rewritten (Metal indexer chain now implemented, boot-ramp caveat).
- benchmark harness: --dump-completions and chars_per_token both kept.
- notebooks: both campaign histories merged chronologically.
- metallib rebuilt from merged kernel sources; extension rebuilt; all six
  Metal kernel oracles pass.
… M1 Ultra campaign

Two conflicts, both trivial: build_attn_metadata grew both sides' new
kwargs (our num_computed_tokens_cpu, main's steady_cache), and the
notebook keeps both appended sections in chronological order. The steady
uniform-decode path is inert on Metal (requires FULL CUDA-graph mode and
the VLLM_STEADY_DECODE_META opt-in). Profile tests pass (48/48).
auroter added 3 commits August 17, 2026 12:03
…fill host guards

Verified all 8 findings from the QuixiCore-Metal PR QuixiAI#3 review against this
tree; applied what holds, documented what was already guarded, rejected one.

- mla fp8 insert (all three kernels): build 2^-e from the float bit pattern
  instead of fast-math exp2, which measures 2 ulps low at negative integer
  inputs on M1 Ultra (probe in perf/results/2026-08-17/coderabbit_fixes/).
  Matches the indexer kernels and the exact fp32 reference; cached codes
  change only for blocks with amax > 448. Scale byte derives from the same
  clamped ei (bit-identical in the reachable exponent range). Anchors must
  be re-gated on next boot (see notebook).
- multi-row MoE GEMV hosts: N-divisibility now enforced — a8_vec routes
  non-multiple N to the one-row kernel, swiglu/sum TORCH_CHECK. The mr grid
  ceil-divides N and tail simdgroups read weight rows past N before the
  store guards; every DSV4 dim divides, so serving behavior is unchanged.
- prefill FA: nc/ns % 32 TORCH_CHECK enforcing the _pad_slots contract;
  contract documented on the kernel and both launchers.
- tk_launch.h: host-checked contracts documented on the router (<=1024
  experts, topk <= 8), indexer compress (cr == 4 vs HISTORY_MAX), compress
  front (cr in {4,128}) and prefill launchers.
- moe_mm_id: AoS qs base is 4-byte aligned (84*blk+16), not 16 — comment
  corrected before it justified a bad widening.
- rms_norm: 256-thread dispatch contract documented (shm slots are only
  written by simdgroups that run).
- save_partial_states: bf16 score+ape add documented as required for bit
  parity with the Triton reference (review suggested widening; rejected).

Rebuilt metallib; full kernel suites pass (prefill FA oracle, 36 tiled-GEMM
checks, SoA/sum6 bit-identical, compress-front c128 bitwise, indexer topk).
Fast-math metal::exp2 is 2 ulps low at negative integer inputs, so decode
was reconstructing every typical KV scale slightly below what the insert
kernels used. Reconstruct 2^(e-127) exactly from the float bit pattern via
a shared mla_ue8m0_scale helper. Metallib rebuilt; all six Metal kernel
suites pass. Anchors re-gated with the insert-scale fix in one boot.
Fresh ramped boot on the fixed build: 8-tok 573db39598e7, off1-2000
bb83cc3054a3 (57.3 s, step ms unchanged), 2500x64 f75e1d41ac3d. All
deterministic across repeat runs; wall shifts are acceptance-mix effects
of the re-rolled trajectories. baseline_status UPDATE 30.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant