Use NAX attention for short causal D256 prefill - #4476
Open
wyanzhao wants to merge 1 commit into
Open
Conversation
wyanzhao
marked this pull request as ready for review
September 8, 2026 20:00
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.
Causal fp16/bf16 D256 prefill with 512–1023 query rows currently defaults to unfused attention. This extends the existing NAX split-head-dimension kernel to fp16/bf16 chunks with 512–1023 query rows and at most 1536 keys, where the measurements below favor it. Query/value dimensions must match and query length must not exceed key length.
This window covers the first few 512-token prefill chunks in models such as Qwen3.5-35B-A3B, whose full-attention layers use D256 and 16/2 query/KV heads. It requires an ordinary floating-point KV cache; full-size 2048-token chunks and quantized KV cache do not benefit.
Apple M5 Max,
MLX_ENABLE_TF32=0. Times are arm medians; ratios are paired geometric means of main/PR time, with 95% CIs. These are operator measurements, not whole-model throughput. The six window cells were measured at runtimebb0b8a33e; final head69e88a18achanges only tests and was used for the two control measurements in a separate session.The k1537 and float32 controls both passed calibration and drift checks. Neither showed a significant difference, and both 95% intervals lie within the predeclared main/PR ratio band of 0.95–1.05. This bounds these two controls; other head/batch/sinks combinations remain unmeasured.
Tests cover both dtypes, boundary lengths, sliced KV, batch 2 and sinks. From
python/tests,MLX_ENABLE_TF32=0 python -m unittest -v test_fast_sdpapassed (27 tests, 2 skipped).uvx pre-commit run --all-filespassed.Benchmark reproduction
Measured builds: main
5778a97c0, candidate runtimebb0b8a33e(controls at test-only final head69e88a18a); Apple M5 Max, 128 GiB, macOS 27.0 (26A5425a). Build separate source checkouts with identical Release settings and Python bindings under each checkout'spython/directory. Save the script below assdpa_microbench.py.Set
--dtype,--q,--k,--hq,--hkand--batchfor each row. The script reports seconds per call using a four-call dependent chain. Keep other GPU work idle; thermal-limit telemetry was unavailable during these measurements.Use 15 fixed main/main calibration pairs followed by 15 main/PR pairs, except fp16 q512/k1536 and both controls use 30 of each. Alternate package order. Initial preconditioning/cooldown settings were 10s/10s, except fp16 q512/k512 used 60s/10s and fp16 q512/k1536 and each control used 60s/30s after five minutes of initial cooling.
Reject arm-median drift above 5% between session halves; allow one retry after 120 seconds of cooling with doubled preconditioning. Retain all pairs, including flagged outliers, in the paired log-ratio mean and Student-t 95% interval. Do not pool sessions; treat overlap with the calibration interval as unresolved.