fix(BACKEND-ATTN-REGISTRY): CPU_ATTN was a name in a list, so a head size FLASH_ATTN declines left the CPU with no backend (#1371) - #1392
Merged
Conversation
…size FLASH_ATTN declines left the CPU with no backend (#1371) `CpuPlatform::get_attn_backend_priority` has returned `{CPU_ATTN, FLASH_ATTN}` since the platform seam landed, mirroring `cpu.py:75-87`, where CPU_ATTN is the only answer upstream ever gives on a CPU. Nothing ever registered it. Every CPU selection walked past the first name and stopped on FLASH_ATTN, and that was recorded as behavior-preserving, which it was: our CPU paged-attention kernel reads FlashAttention's NHD layout, so the second name described the CPU's real KV geometry. It was behavior-preserving only for as long as FLASH_ATTN accepted everything the CPU asked of it. #1332 M1 (`369ea7fd4`) ported `validate_configuration`, and with it `flash_attn.py:170-178`'s rule that a head size must be a multiple of 8. That rule is correct and it is about the FA2 kernel. It is now also, on four device types, about kernels that are not FA2, because `FlashAttentionBackend` is registered by NAME for kCPU, kMETAL, kVULKAN and kTENSTORRENT on the stated precondition that those kernels share its LAYOUT. A CPU request with head_size 6 therefore matched no registered backend at all and `SelectAttentionBackendName` threw out of `GPUModelRunner::initialize_kv_cache`. So the fallback was never removed. There was never a fallback: CPU had exactly one registered dense backend, and it stopped answering. This registers the other one. `CpuAttentionBackend` is ported from `cpu_attn.py:39-110` @ pin `5559679229`, and CPU selection now returns CPU_ATTN for every request, which is what upstream returns. FLASH_ATTN stays registered for kCPU as the second entry. The `head_size % 8` guard is untouched; a case added here asserts FLASH_ATTN still refuses head_size 6 on CPU, so repairing this by widening the guard turns that line red. Two deviations from `cpu_attn.py` are recorded in the new header. The KV shape is the NHD 5-dim one our CPU kernel reads by strides, not upstream's HND, which is the long-standing deviation `cpu_paged_attn.cpp:5-8` already carries and the reason CPU could fall through to FLASH_ATTN at all. And the head-size list is empty, meaning no constraint, where upstream lists eleven sizes: that list describes upstream's fixed-width vectorized CPU kernel, and ours is a scalar loop whose only specialization is the K/V cache dtype. Declaring upstream's list would refuse work this binary demonstrably performs, which is #1332's own defect pointed the other way. Evidence, on this tree, CPU-only, no lease. RED at the pinned base `5f68e60df22670a714f31d6362695b012b2598e2`: `test_nemotron_h_paged_forward` exits 1 with 12 cases, 2 passed, 10 failed, and 18 assertions. GREEN after: exit 0, 12 of 12 passed, 3256 assertions. The assertion count is the number to read. A thrown doctest case runs no assertions, so the failing run printed `0 failed` beside ten throws, and any gate matching on the assertions line saw a clean sweep of eighteen. Mutation: renaming the registered name to `MUTANT_NOT_CPU_ATTN` compiles (rc 0), and the same binary returns to exactly the red shape, 2 passed and 10 failed at 18 assertions. The first attempt at that mutation failed to compile and the stale binary reported SUCCESS, which is why the compile status is quoted beside the result. Metal, Vulkan and Tenstorrent carry the identical inherited rule and are filed as #1389 rather than fixed here: none has an upstream backend to mirror, so each needs its declared capabilities authored, and that is a spec. kROCM is unaffected because `RocmAttentionBackend` declares no head-size constraint. FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:claude-opus-5 [claude-code]
…iler and style gates stop skipping FOLLOWING_AGENTS_PROTOCOL `origin/main` had moved to `96ed8346f`, which is not an ancestor of this branch's head. `scripts/agent-preflight.sh` reacts to that by SKIPPING `commit-trailers` and `commit-style` rather than running them against a range it cannot compute, so the branch was carrying two gates that had never executed on its own commits. A skip is not a green, and this merge is what turns them back on. The only textual overlap is `.agents/issue-index.md`, whose `merge=union` driver appended both sides' rows. Verified after the merge: 443 rows, no duplicated row key, and this branch's #1371 and #1389 rows are byte-identical to the versions it authored. Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:claude-opus-5 [claude-code]
… port the upstream test for the one arm it widened (#1371) FOLLOWING_AGENTS_PROTOCOL Six of the seven declarations in the new `CpuAttentionBackend` were ungated. A review corrupted every one of them except `kName`/`get_name` and `get_kv_cache_shape` -- including WIDENING `get_supported_kernel_block_sizes` from `{16}` to `{1}`, which would let CPU_ATTN accept `block_size` 1 -- and every test binary stayed green at `Status: SUCCESS!`. A declaration nothing reads is a claim, and this registry turns claims into selections. `supported_dtypes` is the one that changes a live answer. It newly adds `vt::DType::kF32`, and `validate_configuration` consumes `cfg.dtype` through `supports_dtype`, so before this row a CPU f32 request was REFUSED with `dtype not supported`: FLASH_ATTN declares `{f16, bf16}` and there was nothing behind it. Upstream has a test for exactly that, and it was not ported. So port it. `tests/kernels/attention/test_attention_selector.py:230-241` @ pin `5559679229bc961848b121ccdeaa8fa5d79bec98`, `test_fp32_fallback`, cpu arm: `get_attn_backend(16, torch.float32, None).get_name() == "CPU_ATTN"` on `CpuPlatform`. Carried over field for field through `SelectAttentionBackendName`, with both halves asserted -- FLASH_ATTN's refusal and its single reason string, and CPU_ATTN validating the identical request with no reasons at all. The cuda and hip arms are NOT ported and the case says why rather than omitting them silently: this tree registers no FLEX_ATTENTION, and `RocmAttentionBackend` declares no head-size list, so the hip arm's expected throw would arrive for the dtype instead of for upstream's minimum head size of 32. A second case pins the rest, entry for entry against `cpu_attn.py`, asserting the DECLARED list and the predicate the base class derives from it, because those fail differently: deleting an override moves the list to the base default, while corrupting entries leaves the shape intact. Both directions are covered -- `supports_block_size(1)` and `(8)` are asserted FALSE, which is what the {16} -> {1} widening trips. Three comments that describe CPU selection were left saying the opposite of what the tree now does. `registry.h`'s own doc comment on `SelectAttentionBackendName` promised `"FLASH_ATTN"` on CPU; `runner.cpp` said the same in the selection block; and `porting-inventory.md`, which its header declares a living record kept up to date at all times, still described CPU_ATTN as named-but-unregistered. All three now state the CPU answer. `parity-ledger.md`'s 2026-07-19 row is left ALONE: it is a dated entry describing what that landing did, and it was true. Finally, there are THREE deviations from `cpu_attn.py`, not two, and the third's stated precedent does not exist. `supported_kv_cache_dtypes` drops `"fp8_e5m2"` from upstream's four entries, which is correct -- `KvKind::kFp8` resolves to `LoadKvFp8E4M3` alone and both vt entry points VT_CHECK-refuse e5m2 -- but the comment justified it by claiming FlashAttentionBackend's list is trimmed for the same reason. It is not trimmed. It is `flash_attn.py:71-78` verbatim, and upstream's FA backend never claimed e5m2 either, so it is no precedent for anything. The header now records three deviations and rests the third on our own kernel's single encoding. It also states outright what deviation 2 leaves implicit: an empty head-size list is WIDER than upstream's eleven sizes, 0 included. Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:claude-opus-5 [claude-code]
…c while the gate ran FOLLOWING_AGENTS_PROTOCOL `origin/main` advanced from `96ed8346f` to `7265cea08` between the first sync and the preflight run, so `commit-trailers` and `commit-style` reported SKIP a second time rather than a verdict. This re-merge is what makes them execute. `.agents/issue-index.md` is the only textual overlap and its `merge=union` driver appended both sides. Verified after the merge: no duplicated row key, and this branch's #1371 and #1389 rows are byte-identical to the versions it authored. `tests/vllm/platforms/test_platform.cpp` took 144 lines from main beside this branch's own edit to the CPU priority case, and git resolved the two hunks without overlap. Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:claude-opus-5 [claude-code]
FOLLOWING_AGENTS_PROTOCOL `main` advanced three more times while this branch's gate ran, and GitHub reported the pull request CONFLICTING again. `git merge-tree --write-tree` reported the same pair CLEAN, which is the known divergence: the forge does not apply this repository's `merge=union` driver for `.agents/issue-index.md`, so an append-only row reads as a conflict there and as no conflict here. An explicit merge commit settles it either way, and it also puts `origin/main` back in this branch's ancestry so `commit-trailers` and `commit-style` keep executing instead of reporting SKIP. Verified after the merge: `.agents/issue-index.md` has no duplicated row key, and this branch's #1371 and #1389 rows are byte-identical to the versions it authored. Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:claude-opus-5 [claude-code]
FOLLOWING_AGENTS_PROTOCOL One more commit landed on `main` after the previous sync, and the forge reported the pull request CONFLICTING again while `git merge-tree --write-tree` reported the same pair CLEAN. That is the `.agents/issue-index.md` divergence this branch has now hit four times: GitHub does not apply the repository's `merge=union` driver, so an append-only row reads as a conflict there and as nothing here. Verified after the merge: no duplicated row key in the index, and this branch's #1371 and #1389 rows are byte-identical to the versions it authored. The merged commit touches model registries and `vt` persistent step input; it changes none of the three files this branch's mutation evidence was measured over. Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:claude-opus-5 [claude-code]
localai-bot
pushed a commit
that referenced
this pull request
Aug 20, 2026
… is real
The new paged-forward case name carried a comma, which doctest's `-tc` splits
on, so a targeted run of it would have reported `0 cases ran` and `SUCCESS!`.
The file's own preamble warns about exactly that. Renamed.
The gate then ran, and the spec now records what it actually printed rather than
what it was expected to print.
`test_nemotron_h_moe_device` builds and exits 0 with 4 cases passed, and ALL
FOUR skip: there is no CUDA device here, so the 4 assertions are the skip
notices and the synthetic numeric gate examined nothing. It stays NEVER RUN.
`test_nemotron_h_paged_forward` exits 1 on this tree with 11 of 13 cases
throwing the identical "No valid attention backend for device type 0 from
{FLASH_ATTN: [head_size not supported]}" at GPUModelRunner construction. That is
#1371, 10 of the 11 are cases this row never touched, and overlaying #1392's
production fix in the working tree -- never committed, reverted afterwards --
turns the same binary green at 13/13 and 3269 assertions. The new case alone
passes 13 assertions. Worth recording: doctest printed `assertions: 18 | 18
passed | 0 failed` while 11 cases were throwing, so the assertion line alone
would have read as a pass.
Red-first, each applied to a scratch copy, proven applied by `git diff --stat`,
built before being run, and restored to an identical sha256. M1 substitutes the
request count for `n_out` at the host projection: compile 0, run 1, FAILURE --
the returned row count is 1 where the gather asked for 3. M2 stops filling
`trace->final_normed`: compile 0, run 1, FAILURE.
M4 restores the exact pre-repair two-download shape and stays GREEN. That is
reported rather than hidden. The duplicate copies the same unchanged buffer and
produces identical bytes, so nothing outside the function can see it; it is
repaired structurally and no assertion pretends to catch it.
FOLLOWING_AGENTS_PROTOCOL
Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: AGENT:claude-opus-5 [claude-code]
…t is, at flash_attn.py:74-80 (#1371) The previous repair replaced a false justification with a true one and cited it wrongly, in three places. `flash_attn.py:71` is a BLANK line at pin `5559679229bc961848b121ccdeaa8fa5d79bec98`; `supported_dtypes` is at `:73` and the five-entry `supported_kv_cache_dtypes` at `:74-80`. The range `71-78` that the comment offered as proof our list is upstream's VERBATIM stops at `"fp8",` and so does not contain `"fp8_e4m3"` or the closing bracket, which is to say it does not contain the list it was proving. The substance is unchanged and was independently confirmed: FLASH_ATTN's list IS upstream's verbatim, upstream's FA backend never claimed e5m2, and the third recorded deviation therefore rests on our own kernel's single encoding alone. Only the citation moves. Every corrected line was re-derived at the pin and asserted UNIQUE in that file, and each now matches the four sites this tree already had right, including `backend.h:384` on the very declaration under discussion. `docs/USAGE.md` gains what that re-derivation is worth to a reader. The page told a CPU user which KV-cache dtypes the CPU takes and that `fp8_e5m2` is refused, without ever saying what the GPU takes — so the one refusal a user can actually hit on either device read as this project's limitation. It is not: `fp8_e5m2` is outside the reference engine's own FLASH_ATTN list, which is the list this commit re-derived. `extensibility-platform-seam-2026-07-18.md` still stated the pre-#1371 CPU answer in the present tense. Its paragraph is kept exactly as written, because it is dated 2026-07-19 and records what was true then; a superseded marker is added beside it so a `grep` cannot land a reader on a false CURRENT answer with nothing to warn them. `.agents/parity-ledger.md` is deliberately NOT touched: it declares itself append-only, one dated row per commit, and editing a dated row there would falsify provenance rather than correct it. `docs/FEATURES.md` gains the CPU row's backend fact, in the shape the ROCm row already uses for the directly analogous `ROCM_ATTN` registration. Registering a CPU attention backend is a backend surface change, which is the trigger `AGENTS.md` names for that page. FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:claude-opus-5 [claude-code]
Four more commits landed on `main` while the final repair round ran, so the branch was behind again and `scripts/agent-preflight.sh --fail-on-skip` SKIPPED `commit-trailers` and `commit-style`: neither gate reported anything about this tree, which is not a green. This merge is what lets them run. Three files overlap, and all three merged without a conflict. `.agents/issue-index.md` stays a PURE APPEND against `origin/main` — two rows added, none removed, no duplicated row key, and this branch's #1371 and #1389 rows are byte-identical by sha256 to the versions it authored. `CMakeLists.txt` keeps this branch's `cpu_attn.cpp` source entry beside the new `vllm_bpe_encode_cost` object target. `docs/USAGE.md` keeps this branch's twenty added lines against `origin/main` while taking the incoming edit. Nothing else this branch owns is touched. The merged commits change the block-wise FP8 dense wiring, the BPE cost harness, two record checkers and several records; the C++ this branch's mutation evidence was measured over is outside every one of them. FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:claude-opus-5 [claude-code]
localai-bot
pushed a commit
that referenced
this pull request
Aug 20, 2026
…d on The `## Now` evidence tables were headed "a run on this box" and reported the new §12 case at `run_rc 0 / 1 passed / 13 assertions / SUCCESS!`, with M1 and M2 red beneath it. Every one of those numbers is real, and none of them is a property of this tree. On the reviewed head with nothing overlaid the §12 case is ALREADY `run_rc=1`: it throws #1371's `No valid attention backend for device type 0 from {FLASH_ATTN: [head_size not supported]}` at `GPUModelRunner` construction, before its first assertion. A mutation cannot be shown to turn a case red when the case is red without it, so as written the tables asserted the one thing their own numbers could not support. Both tables now carry an `overlay` column, state the un-overlaid baseline as its own row, and say why the overlay is what makes the vehicle able to report a green at all. Re-derived at `72d736867` rather than restated: §12 alone with no overlay gives `run_rc=1`, `1 | 0 passed | 1 failed | 12 skipped`, `assertions: 0`; with #1392's production fix overlaid, `run_rc=0`, `1 passed`, `13 assertions`; the whole binary overlaid, `run_rc=0`, 13/13, 3269 assertions; M1 `compile_rc=0 run_rc=1` throwing `gathered row count does not match hidden_size` at `nemotron_h.cpp:1029`; M2 `compile_rc=0 run_rc=1` on `REQUIRE( 0 == 288 )`. The overlay and every mutation were reverted to byte-identical sources (`nemotron_h_device.cpp` back to `sha256 abf6e21f...`). The Marlin object figure gets the same treatment for the same reason. It was quoted as 1 269 696 bytes with no commit beside it. That number is not wrong -- it reproduces exactly at `806b263e7`, `8fa900a62`, `1c62d9974` and at the reviewed head `29b1128e3` -- but the translation unit moved twice afterwards, to 1 272 808 at `fedf78d86` and to 1 268 552 from `bff2b7b2f` on, and an evidence block that names no SHA cannot tell a reader which of the three it means. The measured series is now in the file, the head value is anchored to `72d736867`, and compilation is shown deterministic here (two runs, identical `sha256 42d670b6...`, gcc 13.3.0, no `nvcc`). Also names the process-wide static this row does NOT decline. `dense_nvfp4::DenseMarlinWorkspace` (`dense_nvfp4_gemm.h:506`) is a process-static device allocation keyed on nothing, shared by every caller of `MatmulNvfp4MarlinD` and `GateUpFusedMarlinD`, and NemotronH's `lm_head` becomes its fourth consumer. It is pre-existing -- `80d1da096`, and the definition is byte-identical at this row's merge base and at its head -- and nothing here addresses it. In a row whose thesis is refusing to inherit the sibling process-static defect (#984), staying silent about the one it does inherit was an asymmetry worth one paragraph. The `fallback_gemms` armed-instrument anchor is corrected from `test_qwen3_forward.cpp:497`, the `TEST_CASE(` line, to `:559`, the assertion. No product code changes. FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:claude-opus-5 [claude-code]
localai-bot
pushed a commit
that referenced
this pull request
Aug 20, 2026
… red this branch inherited #1392 merged at 07:26Z and closed #1371, so `CpuAttentionBackend` is registered and `cpu_attn.cpp` is on `main`. This branch was gating against a `main` without that fix, and #1409 measured what that costs: `build-test-cpu` and BOTH `sanitize-cpu` jobs failed on the identical test, `175 - test_nemotron_h_paged_forward`, against a diff of zero code files. Picking up this `main` is what lets those three jobs report on THIS change rather than on #1371, so it is taken before the CI verdict is read rather than after. Whether the three clear is a prediction until the run says so. If a `sanitize-cpu` job still fails, the failing test gets named rather than the job being waved through as a known red. The keyed record is verified BY KEY, not by a clean automatic merge. `.agents/issue-index.md`: 461 rows on `origin/main`, 465 merged, zero duplicated keys, zero lost keys, every `origin/main` row byte-for-byte equal, `origin/main`'s order preserved as a subsequence, and the four branch-only rows (#926, #1290, #1416, #1431) byte-for-byte equal to the branch versions. The append stays an append. FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:claude-opus-5 [claude-code]
localai-bot
pushed a commit
that referenced
this pull request
Aug 20, 2026
…ce was reddening this branch (#1395) FOLLOWING_AGENTS_PROTOCOL `build-test-cpu` and both `sanitize-cpu` jobs were red here on one test, `175 - test_nemotron_h_paged_forward`, and the defect was never in this branch. #1371 left `CPU_ATTN` as a name in a candidate list with no registrar, so the CPU inherited FlashAttention's `head_size % 8` constraint with no fallback. `9ecaf1bb3` (#1392) fixes that. This merge is taken so the gates judge the branch against the tree it will land on, and so `commit-trailers` and `commit-style` keep RUNNING instead of skipping on a branch that is behind. The merged commit is `f07f96e1c` (#1444) rather than `9ecaf1bb3`, because `origin/main` advanced by one documentation commit while this merge was being prepared. `9ecaf1bb3` is an ancestor of it, so the fix this merge is taken for is present either way. Merging the older SHA would have left the branch behind again and skipped the same two gates a second time. `.agents/issue-index.md` is the one keyed record both sides touch, and it was verified by key rather than trusted to the automatic merge: `origin/main` carries 462 rows and this tree carries 463, all 462 of main's rows are present byte-identical and in main's own order, the preamble is byte-identical, and the one added row is this branch's own `#1395`. Nothing is removed and no key is duplicated. No spec, record or product file is touched beyond what the merge itself requires. Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:claude-opus-5 [claude-code]
localai-bot
pushed a commit
that referenced
this pull request
Aug 20, 2026
…ce was reddening this branch (#1313) FOLLOWING_AGENTS_PROTOCOL `build-test-cpu` and both `sanitize-cpu` jobs were red here on one test, `175 - test_nemotron_h_paged_forward`, and the defect was never in this branch. #1371 left `CPU_ATTN` as a name in a candidate list with no registrar, so the CPU inherited FlashAttention's `head_size % 8` constraint with no fallback. `9ecaf1bb3` (#1392) fixes that, and `f07f96e1c` (#1444) is the current `origin/main` that carries it. Merging the older SHA would have left this branch behind again, and `commit-trailers` and `commit-style` SKIP on a branch that is behind. A skip is not a green, which is the state this branch was already in. Four files are touched by both sides, and each was verified rather than trusted to the automatic merge. `.agents/issue-index.md` is the keyed record: main carries 462 rows and this tree carries 463, all 462 of main's rows are present byte-identical and in main's own order, the preamble is byte-identical, and the one added row is this branch's own `#1313`. Nothing is removed and no key is duplicated. For `docs/USAGE.md`, `src/vllm/v1/worker/gpu/runner.cpp` and `tests/CMakeLists.txt` the two sides edit disjoint regions, and that is measured rather than asserted: the added and removed content lines of `merged vs main` hash equal to this branch's own delta, and those of `merged vs branch` hash equal to main's own delta, for all three files. No file is touched beyond what the merge itself requires. Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:claude-opus-5 [claude-code]
localai-bot
pushed a commit
that referenced
this pull request
Aug 20, 2026
…ce was reddening this branch (#1074) FOLLOWING_AGENTS_PROTOCOL `build-test-cpu` and both `sanitize-cpu` jobs were red here on one test, `175 - test_nemotron_h_paged_forward`, and the defect was never in this branch, which carries no product code at all. #1371 left `CPU_ATTN` as a name in a candidate list with no registrar, so the CPU inherited FlashAttention's `head_size % 8` constraint with no fallback. `9ecaf1bb3` (#1392) fixes that, and `f07f96e1c` (#1444) is the current `origin/main` that carries it. Merging the older SHA would have left this branch behind again, and `commit-trailers` and `commit-style` SKIP on a branch that is behind. Four files are touched by both sides, and each was verified rather than trusted to the automatic merge. `.agents/issue-index.md` is the keyed record: main carries 462 rows and this tree carries 464, all 462 of main's rows are present byte-identical and in main's own order, the preamble is byte-identical, and the two added rows are this branch's own `#1074` and `#1080`. Nothing is removed and no key is duplicated. `docs/BENCHMARKS.md`, `docs/FEATURES.md` and `docs/STATUS.md` are projections whose two sides edit different rows, and that is measured rather than asserted: the added and removed content lines of `merged vs main` hash equal to this branch's own delta, and those of `merged vs branch` hash equal to main's own delta, for all three files. No file is touched beyond what the merge itself requires. Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:claude-opus-5 [claude-code]
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.
CpuPlatform::get_attn_backend_priorityhas returned{CPU_ATTN, FLASH_ATTN}since the platform seam landed, mirroring
cpu.py:75-87, where CPU_ATTN is theonly answer upstream ever gives on a CPU. Nothing ever registered it. Every CPU
selection walked past the first name and stopped on FLASH_ATTN, and that was
recorded as behavior-preserving, which it was: our CPU paged-attention kernel
reads FlashAttention's NHD layout, so the second name described the CPU's real
KV geometry.
It was behavior-preserving only for as long as FLASH_ATTN accepted everything
the CPU asked of it. #1332 M1 (
369ea7fd4) portedvalidate_configuration, andwith it
flash_attn.py:170-178's rule that a head size must be a multiple of 8.That rule is correct and it is about the FA2 kernel. It is now also, on four
device types, about kernels that are not FA2, because
FlashAttentionBackendisregistered by NAME for kCPU, kMETAL, kVULKAN and kTENSTORRENT on the stated
precondition that those kernels share its LAYOUT. A CPU request with head_size 6
therefore matched no registered backend at all and
SelectAttentionBackendNamethrew out of
GPUModelRunner::initialize_kv_cache.So the fallback was never removed. There was never a fallback: CPU had exactly
one registered dense backend, and it stopped answering. This registers the other
one.
CpuAttentionBackendis ported fromcpu_attn.py:39-110@ pin5559679229, and CPU selection now returns CPU_ATTN for every request, which iswhat upstream returns. FLASH_ATTN stays registered for kCPU as the second entry.
The
head_size % 8guard is untouched; a case here asserts FLASH_ATTN stillrefuses head_size 6 on CPU, so repairing this by widening the guard turns that
line red.
The capability surface is gated, and the upstream test for it is ported
Six of the seven declarations in the new class were ungated when it first landed.
A review corrupted every one except
kName/get_nameandget_kv_cache_shape-- including WIDENING
get_supported_kernel_block_sizesfrom{16}to{1},which would let CPU_ATTN accept
block_size1 -- and every test binary stayedgreen at
Status: SUCCESS!. A declaration nothing reads is a claim, and thisregistry turns claims into selections.
supported_dtypesis the one that changes a live answer. It addsvt::DType::kF32, andvalidate_configurationconsumescfg.dtypethroughsupports_dtype, so before this change a CPU f32 request was REFUSED withdtype not supported: FLASH_ATTN declares{f16, bf16}and nothing stoodbehind it. Upstream has a test for exactly that, and it was not ported.
It is ported now, from
tests/kernels/attention/test_attention_selector.py:230-241@ pin5559679229bc961848b121ccdeaa8fa5d79bec98--test_fp32_fallback, cpu arm,get_attn_backend(16, torch.float32, None).get_name() == "CPU_ATTN"onCpuPlatform. Carried over field for field throughSelectAttentionBackendName,asserting both halves: FLASH_ATTN's refusal with its single reason string, and
CPU_ATTN validating the identical request with no reasons at all. The cuda and
hip arms are NOT ported and the case says why rather than dropping them in
silence -- this tree registers no FLEX_ATTENTION, and
RocmAttentionBackenddeclares no head-size list, so the hip arm's expected throw would arrive for the
dtype rather than for upstream's minimum head size of 32.
A second case pins the rest, entry for entry against
cpu_attn.py. Each blockasserts the DECLARED list and the predicate the base class derives from it,
because they fail differently: deleting an override moves the list to the base
default, while corrupting entries leaves the shape intact. Widening is covered in
its own direction --
supports_block_size(1)and(8)are asserted FALSE, whichis what the
{16}->{1}corruption trips.Eight mutations on
b8049371, each printing its compile status beside itsverdict. Base:
test_attn_backend_registry20 cases, 125 assertions,Status: SUCCESS!, rc 0. The head moved to3853ec5fafterwards for two moreorigin/mainmerges, neither of which touches any of the three files involved --verified by
git diff b8049371..3853ec5fovercpu_attn.h,cpu_attn.cppandtest_attn_backend_registry.cpp, which is empty.compile_rcsupported_dtypesdropskF32supported_kv_cache_dtypesgainsfp8_e5m2get_supported_kernel_block_sizes{16}->{1}supports_non_causaltrue -> falsesupports_sliding_windowtrue -> falsesupports_attn_typedropsencoder_decoderget_supported_head_sizes{}-> upstream's elevenEvery one exits 1 at
Status: FAILURE!, and every one restored both filesbyte-for-byte, verified by sha256 rather than by
git status. M8 is thereachability re-proof on the repaired head: the class still compiles and still
constructs, and the gate goes red because nothing reaches it. Read its assertion
count against the others -- 71, not 125 -- because five thrown cases run no
assertions at all. That collapse is the second half of #1371's report, reproduced
here on purpose.
Three live comments said the opposite of what the tree does
registry.h's own doc comment onSelectAttentionBackendNamepromised"FLASH_ATTN"on CPU.runner.cppsaid the same in the selection block.porting-inventory.md, whose header declares it a living record kept up to dateat all times, still described CPU_ATTN as named-but-unregistered. All three now
state the CPU answer, and all three note that both names report the same NHD
layout, so the geometry did not change with the name.
One more live surface stated the old CPU answer in the present tense:
.agents/specs/extensibility-platform-seam-2026-07-18.md, under its dated"Item 4 -- LANDED 2026-07-19" heading. Its paragraph is kept exactly as written,
because it records what was true on that date. A superseded marker naming #1371
and this pull request is added beside it, so a
grepcannot land a reader on afalse CURRENT answer with nothing to warn them.
parity-ledger.md's 2026-07-19 row is deliberately left alone. It is a datedentry recording what that landing did, it was true when written, and the ledger
declares itself append-only, one row per commit, newest last. Editing a dated
row in a declared append-only log falsifies provenance rather than correcting
it. That is history;
porting-inventory.md, which declares itself kept up todate at all times, is live coverage, and it was corrected.
There are three deviations from
cpu_attn.py, not twosupported_kv_cache_dtypesdrops"fp8_e5m2"from upstream's four entries. Theomission is right --
KvKind::kFp8resolves toLoadKvFp8E4M3alone and both vtentry points VT_CHECK-refuse an e5m2 KV cache -- but the header said "TWO
RECORDED DEVIATIONS" and justified this one by claiming FlashAttentionBackend's
list is trimmed for the same reason. It is not trimmed. It is
flash_attn.py:74-80verbatim, and upstream's FA backend never claimed e5m2either, so it is no precedent for anything. The header now records three, and the
third rests on our own kernel's single encoding.
That anchor was wrong in the round that introduced it, and it is corrected here.
flash_attn.py:71is a BLANK line at the pin;supported_dtypesis at:73and the five-entry
supported_kv_cache_dtypesat:74-80. The range offered asproof the list is VERBATIM stopped at
"fp8",, so it did not contain"fp8_e4m3"or the closing bracket -- it did not contain the list it wasproving. Three sites carried it:
cpu_attn.h:70and two comments intest_attn_backend_registry.cpp. Each was re-derived at the pin and assertedUNIQUE in that file, and each now matches the four sites this tree already had
right, among them
backend.h:384on the very declaration under discussion andtest_attn_validate_configuration.cpp:110in the sibling suite.scripts/check-symbol-anchors.pycannot catch this: it documents that itdeliberately does not verify line citations, so its green was never coverage
here.
The header also states outright what deviation 2 left implicit: an empty
head-size list is WIDER than upstream's eleven sizes, 0 included. That is our
scalar kernel's genuine generality and the base class's own default, and it is
still a wider claim than the file this one is ported from makes.
docs/USAGE.mdnow says the user-visible half of the same thing: the CPU serves f32, and refuses
fp8_e5m2by name -- and, since the page named the CPU's KV-cache list withoutever naming the GPU's, that
fp8_e5m2refusal read as this project's limitationwhen it is the reference engine's own. Both lists are stated there now.
docs/FEATURES.mdrecords the backend surface change on the CPU row, in theshape the ROCm row already uses for the directly analogous
ROCM_ATTNregistration at #1056/#1065. Registering a CPU attention backend is a backend
surface change, which is the trigger
AGENTS.mdnames for that page. Theconvention is not uniform -- the CUDA row names no attention backend at all --
so this follows the one precedent that exists rather than inventing a shape.
Evidence
CPU-only, no lease,
-DVLLM_CPP_CUDA=OFF, on4c649319(Debug for the red;Release for the re-run after the fifth merge).
RED at the pinned base
5f68e60df22670a714f31d6362695b012b2598e2:test_nemotron_h_paged_forwardexits 1 with 12 cases, 2 passed, 10 failed, and18 assertions. GREEN after: exit 0, 12 of 12 passed, 3256 assertions. The
assertion count is the number to read. A thrown doctest case runs no assertions,
so the failing run printed
0 failedbeside ten throws, and any gate matching onthe assertions line saw a clean sweep of eighteen.
Focused gate, all rc 0 and re-run in full at
4c649319:test_attn_backend_registry20/20 at 125 assertions,test_attn_validate_configuration21/21 at 76,test_platform14/14 at 114,test_runner19/19 at 543,test_nemotron_h_paged_forward12/12 at 3256. Everyone reports
Status: SUCCESS!, which is read beside the assertion count ratherthan instead of it.
scripts/agent-preflight.sh --fail-on-skipat4c649319: All gates green,rc 0, ZERO skipped and ZERO failed.
commit-trailersandcommit-stylebothRAN and both report
okagainstorigin/main 63d87805c, which is what the fivemerges in this branch are for -- while the branch was behind, both gates
reported SKIP, and a skip is not a green: it means neither gate reported
anything about this tree.
test_cpu_x86_llamacpp_floor, which had refused tomeasure under
NO_QUIET_WINDOW ... load=194.85on earlier runs, passes on aquiet box; both
tests/scripts/test_cpu_x86_llamacpp_floor.pyandscripts/cpu-x86-llamacpp-floor.share byte-identical toorigin/mainhere andthis branch touches neither, so its earlier red was this box's load average and
never a verdict about any code.
test_nemotron_h_paged_forwardis the test currently failingbuild-test-cpuonevery open pull request, because it is the test #1371 breaks. It passes here.
test_qwen3_5_decode_graph_seamsegfaults onmainindependently(#1422) and is untouched by
this branch, so a red
build-test-cpuon this pull request has to be read byWHICH test failed, not by its colour.
Closes #1371.
Metal, Vulkan and Tenstorrent carry the identical inherited rule and are filed as
#1389 rather than repaired here: none has an upstream backend to mirror, so each
needs its declared capabilities authored, and that is a spec. kROCM is unaffected
because
RocmAttentionBackenddeclares no head-size constraint.FOLLOWING_AGENTS_PROTOCOL
Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: AGENT:claude-opus-5 [claude-code]