metal: code-layer performance wins (rb16 prefill dispatch, vectorized K/Q staging in the LLT scorer, double-buffered head tiles, simd_max reducers, host fixes, zero-copy payload spans)Code layer wins - #794
Open
daemon2k3 wants to merge 12 commits into
Conversation
Ports llama.cpp's Metal kernel_lightning_indexer organization to ds4's ratio-4 (CSA) indexer score stage on pre-M5/non-NAX paths, replacing both the prefill tiled kernels and the decode direct score kernel. Structure: each 256-thread threadgroup owns 64 compressed rows, staged once and transposed into per-simdgroup REGISTER matrices; the 64-head loop then reuses register-resident K instead of re-reading threadgroup K per head. Heads are processed in 8-head tiles (single Q stage + pre-scaled head weights, 2 barriers per tile vs 3 per head); prefill iterates 8 tokens per kernel pass against resident K; decode runs the same kernel with n_tokens=1. Per-cell math preserved (f32->half staged rows, ascending depth-8 simdgroup matrix steps, relu then w*scale per head ascending, ds4 causal -inf epilogue for prefill / all-rows for decode). Measured on Apple M3 Ultra 512 GB, DeepSeek V4 Flash resident, ctx frontier probes over HTTP + ds4-bench + variant benches: * decode bit-exact vs kernel_dsv4_indexer_score_one_direct: metal_decode_schedule_bench --candidate-env DS4_METAL_DISABLE_INDEXER_LLT --include-selection: exact_rows=529, exact_floats=68389120, exact_selected_ids=528 * decode @117.6k ctx: 39.3 -> 42.2 / 41.5 t/s (+6-7%, two replicates) * short-ctx decode 48.0 t/s, prefill cold @117.6k 405 t/s: parity * prefill score stage @29.4k rows: parity vs scores_tiled (dual-head / wide-tile / NSG4 alternate organizations were also measured and revert for net-negative; knobs preserved below) Env knobs: DS4_METAL_DISABLE_INDEXER_LLT (rollback, variant-bench control), DS4_METAL_INDEXER_LLT_NBPTG=16|32 (token batch per pass, measured flat), DS4_METAL_INDEXER_LLT_NSG4 (4-simdgroup / 2-TG-per-core variant, measured prefill-negative/decode-slightly-positive, off).
…cers, LLT vectorized K/Q staging, getenv cache, session verify scratch, tokenizer lens, kvstore refresh gating, DS4_LTO knob
…MMAing tile t (F3)
…ers (unified memory), rollback env DS4_DISABLE_ZEROCOPY_PAYLOAD_SPANS
…6-4x; env DS4_SAMPLE_ACCELERATE_EXP; default bit-exact)
…crambled FILE* byte order)
…n caused nondeterministic lineage drift in A/B runs)
…nd-resolution was A/B lineage-drift)
…(GPU race: partial cache pages visible mid-enqueue)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
PR: Metal code-layer performance wins (rb16 prefill dispatch, vectorized K/Q staging in the LLT scorer, double-buffered head tiles, simd_max reducers, host fixes, zero-copy payload spans)
Summary
A set of code-layer (non-algorithmic) optimizations over the DS4 Metal backend, measured end-to-end on an Apple M3 Ultra 512GB + DeepSeek V4 Flash (q4, 153GiB, resident) against the current
main+ LLT scorer. Every change preserves existing semantics; the two sampling-path items are strictly opt-in via env flags and change behavior only when explicitly requested. Deterministic per build per lineage; the deep-context dump-diff verification protocol below is included.What is in this PR
heads8_rb16on prefill (washeads8) — 16 staged rows per stage kernel with identical per-row online-softmax sequencesimdgroup_multiply_accumulate-ing tile tsimd_max+ one cross-group exchange (maxis IEEE-exact — bit-identical)environscans per token across 43 layers)spec_row_logits(was 1.3–2.6 MBxmalloc+free per 1–2 committed tokens with DSpark/MTP)sizeof(lit)-1) instead ofstrlenper scanned bytest_mtimecan skip fresh entries in the same second — deterministic lineage integrity beats ms-scale refresh savings)fwrite/freaddirectly into shared (unified-memory) Metal tensor backing stores + GPU fence (fwd both ways), removing the 8MB staging loopDS4_SAMPLE_ACCELERATE_EXP=1: Acceleratevvexpf(measured 0.35 ns/op vs scalarexpf1.29 ns/op ≈ 2.6–4×) — not bit-identical to scalar expf, changes the sampling stream by construction — off by default-framework Acceleratelink (for the vvexpf opt-in);DS4_LTO=thinopt-in (Apple Silicon release builds)End-to-end measured outcomes
Isolated identical-instance pair on the same q4 model,
--dspark+--mtpproduction-config, 5+ alternating trials each metric:Verification protocol used for this PR's bit-stability claim
The in-tree
speed-bench/*_variant_benchharnesses show a slot-order bias on this configuration (full-vocab mismatch on control-vs-control) — I filed a separate issue report for it, attached. So the bit-stability was verified via the engine's own debug dump-diff protocol instead:# for each variant: dump the layer-43 result_output tensor at pos0, binary-compare DS4_METAL_GRAPH_DUMP_PREFIX=/tmp/up_a DS4_METAL_GRAPH_DUMP_NAME=result_output \ DS4_METAL_GRAPH_DUMP_POS=0 ./ds4 -m ds4flash.gguf --prompt-file /tmp/prompt.txtCompared across factory
main, kernel-subset, host-subset, and full branch tips at multiple prompt sizes (96 / 512 / 2048 / 5000-word):nan=0 inf=0everywhere, deterministic per build, no all-scan anomalies. Several negative candidates were found on the way (not in this PR): a Metal runtime-arg toggle family that produced broken Metal shader states (archived attoggle-attempts-refindaemon2k3/ds4for the record, plus bisect notes), and an f16 indexer-compressed-K cache (indexer-comp-f16— nondeterministic at depth, parked).Honest gaps: precision interplay with the existing NAX/quality paths (M5-gated; untouched by this PR); stream-changing vvexpf is opt-in only; Apple M3 Ultra is the only hardware this was measured on (Kernel-level invariant claims rest on identical math/order, marked as such in each commit message).
How to review
Branch:
daemon2k3/ds4:code-layer-wins(tip594757c). Recommended reading order: the big host-side commit, then the kernel commit, then the zero-copy/C1 commits. Rollback flags follow the engine's one-env-per-change convention where applicable (DS4_DISABLE_ZEROCOPY_PAYLOAD_SPANS;DS4_SAMPLE_ACCELERATE_EXPis itself an opt-in).Open upstream-prep question kept from the session: the slot-order bias in the bench ABBA harness (separate issue with exact repro lines included).
Evidence
A/B ledger, dump-diff protocol table, C1 (vvexpf) micro-data, the toggle-series bisect kill-chain, and ops notes:
research/m3ultra-dsv4-study/evidence/README.md(same fork,researchbranch).