Skip to content

record(VT-REFTIER-HOST-ADDRESSABLE): the test cited as pinning CUDA reads the CPU backend - #1639

Merged
localai-bot merged 9 commits into
mainfrom
row/RECORD-CUDA-UNIFIED-MEM-CITATION
Aug 22, 2026
Merged

record(VT-REFTIER-HOST-ADDRESSABLE): the test cited as pinning CUDA reads the CPU backend#1639
localai-bot merged 9 commits into
mainfrom
row/RECORD-CUDA-UNIFIED-MEM-CITATION

Conversation

@localai-bot

@localai-bot localai-bot commented Aug 21, 2026

Copy link
Copy Markdown
Collaborator

A post-hoc review of four merged records pull requests found that #1620 cited tests/vllm/platforms/test_platform.cpp as pinning the CUDA backend's DeviceMemoryIsHostAddressable() default. It does not.

FakeUnifiedAddressablePlatform's device_type() override returns DeviceType::kCUDA, but its backend() override returns vt::GetBackend(DeviceType::kCPU). So the assertion:

CHECK_FALSE(gb10.backend().DeviceMemoryIsHostAddressable());

reads the CPU backend. The fixture's own comment says so. Nothing in the tree pins the real CudaBackend.

What survives, and what does not

The conclusion is still true and no code changes here: CudaBackend declares no override, so it inherits Backend::DeviceMemoryIsHostAddressable's false default in include/vt/backend.h. But it holds by ABSENCE of an override, which is a strictly weaker claim than a pin, and the record read as though a test stood behind it.

No gate could have caught this. The assertion passes, and it passes for a reason unrelated to what it claims to measure — the same shape as a mutation that never applied reading as a passing test.

Why a new row rather than an edit

The wrong citation landed in three places:

  • the VT_ADOPT_DEVICE_BYTES row of docs/ENVIRONMENT.mdcorrected in this change
  • the commit message — immutable, and stands as what was believed at the time
  • the #1502 row of .agents/issue-index.mdappend-only, and can never be edited

That last one is the whole reason this is a record-only change with a new row. CLAUDE.md makes the index append-only and gates it; the correction cannot be applied in place. Per policy a record-only pull request is correct when the record IS the work — a newly filed gap — which is this case.

Both citations name a ROW, not a line

An earlier revision of this branch cited the wrong claim's home by LINE NUMBER, and that citation was already false. origin/main appended rows above #1502 while this branch was open, so the line that revision named had stopped identifying that row before the branch had shipped even once — verified in the merged tree here, where the cited line holds a different row entirely. A permanent line number into a file nobody may edit would have shipped wrong on its first day, which is exactly the defect this row exists to describe, committed inside the row describing it. It is also #844's own rule: grep the symbol, because this index is append-only and a line number here would be permanent.

Correcting the number to its new value was rejected. A corrected line number is still a line number in an append-only file, and it rots again on the next append above it. So is a count of the rows that pushed it down, which is why neither this body nor the row states one. An earlier draft of both explained the stale line by naming how many lines origin/main had moved #1502, and the next merge falsified that figure exactly as the merge before it had falsified the line — the same defect, one level up, inside the change that exists to condemn it. The argument needs no quantity: rows landed above #1502, so the line stopped naming it. Stated that way it survives the merge this branch still has to do before it lands, and every merge after that. Every reference now names the row instead, and the row's citations into tests/vllm/platforms/test_platform.cpp and include/vt/backend.h name the two overrides, the assertion, and the virtual rather than four line numbers — #844's criterion is the file the number is WRITTEN IN, which is this append-only index and this permanent message, not the file it points at. The docs/ENVIRONMENT.md anchor got the same treatment even though that file is editable and the risk is lower: the row has to say where the claim lives either way, and naming the row costs nothing and cannot go stale.

Owed

Pinning the real CudaBackend needs a CUDA device and stays owed. It is the ## Owed bullet of .agents/specs/vt-reference-tier-host-addressable.md opening "Nothing in the tree pins the real CudaBackend's" — grep that, rather than counting down to a bullet position that the next appended bullet would move, for the reason this whole change is about.

Tracked by #1635, which stays OPEN. This change corrects the citation and records the gap; the pin itself is the other half of what #1635 owes, it needs a CUDA device, and it is not done here. No closing keyword appears anywhere in this body, for any issue, deliberately.

Verification

  • agent-preflight.sh on the merged head: every gate ok and zero SKIPPED, including issue-index append-only, doc-checkpoint range, now-current range, commit-trailers and commit-style.
  • check-commit-style.py --range and check-commit-trailers.py --filled exit 0 over $(git merge-base origin/main HEAD)..HEAD, and check-issue-index-append-only.py exits 0 over the same base. The base is written as that command rather than as a SHA, because the base moves every time this branch merges origin/main, and a pasted SHA names whichever merge happened to be last. check-agent-record.py takes no base — it reads one tree — and exits 0 on the merged tree.
  • The merged index is origin/main's file plus exactly this branch's one row: deleting the #1635 row from it reproduces git show origin/main:.agents/issue-index.md BYTE FOR BYTE. So no existing row is edited or removed, no issue number is duplicated, #1635 appears once, and no conflict marker survives. That byte identity is the check, and it replaces the row-count arithmetic an earlier draft used — another number that was only true of whichever origin/main was merged last. The merge was done LOCALLY because GitHub does not honour the merge=union driver.
  • test_cpu_x86_llamacpp_floor fails locally under load, with a failure count that varies run to run at load averages of 174 and 41 — the known contention flake, not reachable from three record and document files.

FOLLOWING_AGENTS_PROTOCOL

Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: Claude:claude-opus-5 [Claude Code]

mudler added 9 commits August 21, 2026 20:20
…eads the CPU backend

#1620 recorded that `tests/vllm/platforms/test_platform.cpp` pins the CUDA
backend's `DeviceMemoryIsHostAddressable()` default. It does not.
`FakeUnifiedAddressablePlatform` reports `device_type() == kCUDA` at `:284`, but
its `backend()` returns `vt::GetBackend(DeviceType::kCPU)` at `:285`, so the
`CHECK_FALSE(gb10.backend().DeviceMemoryIsHostAddressable())` at `:329` asserts
against the CPU backend. The fixture's own comment says so.

The conclusion survives and the code is unchanged: `CudaBackend` declares no
override, so it inherits the base `false` at `include/vt/backend.h:77`. But it
holds by ABSENCE of an override, which is a weaker claim than a pin, and the
record read as though a test stood behind it. Nothing in the tree pins the real
`CudaBackend`.

No gate could have caught this. The assertion passes, and it passes for a reason
unrelated to what it claims to measure -- the same shape as a mutation that never
applied reading as a passing test.

The wrong citation landed in three places and one of them is permanent:
`docs/ENVIRONMENT.md:210` is corrected here, the commit message stands as what
was believed at the time, and `.agents/issue-index.md:552` is append-only and can
never be edited. That last one is why this is a new row rather than an edit, and
why a record-only change is the right shape: the record IS the work.

Pinning the real `CudaBackend` needs a CUDA device and stays owed on the row.

FOLLOWING_AGENTS_PROTOCOL

Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: AGENT:claude-opus-5 [Claude Code]
`origin/main` gained six `.agents/issue-index.md` rows between this branch's
base and now, from other sessions landing concurrently. Merging locally is not a
formality here: GitHub does NOT honour the `merge=union` driver declared in
`.gitattributes`, so a stale branch carrying an append to that file is resolved
by the forge without it. Doing the merge here lets the driver union the appends,
which is the only reason two concurrent appends to an append-only log are safe.

The only shared file is that index, and both sides only append. No source file is
touched by both sides.

FOLLOWING_AGENTS_PROTOCOL

Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: AGENT:claude-opus-5 [Claude Code]
…d`, and cite the index by ROW

Two review findings on the CUDA unified-memory citation correction.

The `#1635` bullet was written into `## Scope`, under "Out of scope, and
why", because the authoring script anchored on `s.index("## Owed")` and
matched a PROSE MENTION of that string inside `## Scope` — the "Recorded
under `## Owed`" cross-reference in the `#1435` bullet above it — rather
than the heading. It is now the last bullet of the real `## Owed`
section, anchored on a line that begins `## Owed`. The content was
miscategorised as well as misplaced: "#1635 is OPEN and owned here, and
pinning stays owed" states an obligation, not an exclusion from scope.

The row also baked a line number into the append-only index. It cited
the wrong claim's home as `.agents/issue-index.md:552`, and that was
already false: `origin/main` landed five rows above `#1502` while this
branch was open, so the merged tree puts it at `:557`. A permanent line
number into a file nobody may edit would have been wrong on the day it
shipped — which is the defect this row exists to describe, committed
inside the row describing it, and it is `#844`'s own rule. Every
reference now names the ROW: "the `#1502` row of this index", and "the
`VT_ADOPT_DEVICE_BYTES` row of `docs/ENVIRONMENT.md`". The second was
lower risk than the first, because `docs/ENVIRONMENT.md` is editable, but
a line number is a line number and the row already had to say where the
claim lives; naming it costs nothing and cannot rot. A corrected `:557`
was rejected: it rots again on the next append above it.

Editing the `#1635` row rather than appending another is not an
append-only violation. The row exists only on this branch — `git show
origin/main:.agents/issue-index.md` has no `#1635` row — and the rule
forbids editing a row already in the log, which this is not.
`issue-index append-only` passes.

Records only. No product code, no test, and no behaviour changes.

FOLLOWING_AGENTS_PROTOCOL

Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: Claude:claude-opus-5 [Claude Code]
`origin/main` gained three commits while the review ran, and five of them
land index rows ABOVE `#1502`. Merged rather than rebased, because a
rebase would need a force-push.

The merge is done LOCALLY on purpose: GitHub does not honour the
`merge=union` driver on `.agents/issue-index.md`, so resolving the append
on the forge is not the same operation as resolving it here.

This merge is also the evidence for the second review finding. At the
pre-merge head `#1502` sat at `:552`, which is what the `#1635` row used
to cite; in this merged tree it sits at `:557`. A line number into an
append-only file was therefore already wrong before the row had shipped
once, which is why every reference now names the ROW.

Index after the merge: 546 rows = 540 at the merge base + 5 from
`origin/main` + 1 from this branch. `#1635` appears exactly once, no row
present at the merge base is missing or altered, every `origin/main` row
survives byte-for-byte, and no issue number is duplicated.

FOLLOWING_AGENTS_PROTOCOL

Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: Claude:claude-opus-5 [Claude Code]
…use #844's rule is about the file the number is written IN

The `#1635` row invoked #844's rule -- "grep the symbol, because this index is
append-only and a line number here would be permanent" -- while itself keeping
four permanent line numbers: `:284`, `:285` and `:329` into
`tests/vllm/platforms/test_platform.cpp`, and `include/vt/backend.h:77`.

The distinction the previous wording drew was "editable target, so keep the
anchor". That is not #844's distinction. #844's criterion is the file the
number is WRITTEN IN, not the file it POINTS AT: this index is append-only and
no checker scans it, so a number here can never be repaired once it rots, no
matter how editable its target is. A row that argues a rule and then breaks it
teaches the wrong criterion to the next reader.

The four numbers are replaced by names that cannot rot: the `device_type()` and
`backend()` overrides of `FakeUnifiedAddressablePlatform`, the assertion
`CHECK_FALSE(gb10.backend().DeviceMemoryIsHostAddressable())`, and
`Backend::DeviceMemoryIsHostAddressable`'s `false` default. The file names stay,
so the row is still grep-navigable.

The row also now states that the landing pull request carries no closing
keyword and that #1635 stays OPEN. It carried `Fixes #1635` in its body, which
would have closed the issue at the squash and falsified this row's own OWED
clause and the `## Owed` bullet in
`.agents/specs/vt-reference-tier-host-addressable.md`, both of which say #1635
is open. The second owed item -- pinning the real `CudaBackend`, which needs a
CUDA device -- is not done here, so closing it would leave that work tracked by
a spec bullet alone.

Only the `#1635` row is touched. It exists on this branch only and is not yet
in the log, so this is not an append-only violation: `check-issue-index-append-only.py`
diffs `merge-base(origin/main, HEAD)..HEAD` and reports ok.

FOLLOWING_AGENTS_PROTOCOL

Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: Claude:claude-opus-5 [Claude Code]
…r the two review findings

`origin/main` advanced to `5453e571d` while the two fresh-review findings were
being repaired. Merged rather than rebased, because a rebase would need a
force-push and `main` never takes one. Merged LOCALLY, because GitHub does not
honour this repository's `merge=union` driver on `.agents/issue-index.md`.

Row arithmetic: 549 rows = 548 on `origin/main` at `5453e571d` plus the one
`#1635` row this branch appends. The merged file with that single row deleted is
byte-identical to `origin/main`'s copy, so the union merge added nothing,
removed nothing and edited no existing row. `#1635` appears exactly once and no
issue number is duplicated. No conflict markers survive.

The `5453e571d` side is the ModelOpt 27B NVFP4 change, which touches no file
this branch writes apart from the index, so the other paths merge by
fast-forward of their `origin/main` state.

FOLLOWING_AGENTS_PROTOCOL

Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: Claude:claude-opus-5 [Claude Code]
…ng line number carried a rotting count

The `#1635` row and the pull request body both explained the line-number
citation by saying `origin/main` had pushed the `#1502` row five lines further
down. That was true at the previous merge and false at this head, where the
figure is larger, because a third merge landed more rows above it. A count read
off whatever `origin/main` happened to be merged last rots by exactly the
mechanism the paragraph exists to condemn, and this branch will merge
`origin/main` at least once more before it lands, so replacing five with the
current figure would only schedule the next instance.

Both sentences now carry no count and no line number. What survives is the
argument, which no merge can falsify: `origin/main` appended rows above `#1502`
while this branch was open, so the line the first draft named stopped
identifying that row, which is why every citation names the row instead. The
row also says outright that no count is written, so the next reader does not
supply one.

The `docs/ENVIRONMENT.md` sentence loses its `:329`. That file is editable, so a
stale number there is repairable rather than permanent, but the sentence already
quotes the assertion verbatim and the number adds nothing except one more thing
to go stale -- in the very file this change is rewriting to remove a bad
citation.

Records only. No `src/`, `include/` or `tests/` path is touched, and #1635 stays
open.

FOLLOWING_AGENTS_PROTOCOL

Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: Claude:claude-opus-5 [Claude Code]
…r the count was removed

The fourth merge on this branch, and the first one that cannot falsify anything
this branch says. `origin/main` appended five more index rows, which moved the
`#1502` row from line 559 to 564 -- so the "five lines" this branch had just
finished deleting would have been wrong again, by a different amount, for the
third time. The rewritten paragraph names no count and no line, and it reads
true at this head for the same reason it read true at the last one.

The index merged by union and needed no manual reconciliation. Verified rather
than assumed: deleting the single `#1635` row from the merged file reproduces
`origin/main`'s `.agents/issue-index.md` byte for byte (sha256
`dbd24385...`), so this branch edits no existing row and deletes none.

FOLLOWING_AGENTS_PROTOCOL

Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: Claude:claude-opus-5 [Claude Code]
GitHub refused the squash with a conflict on `.agents/issue-index.md`. That file
carries `merge=union` in `.gitattributes`, and the forge does NOT honour the
driver -- so two branches that each append a row conflict there even though the
union resolves cleanly. Doing the merge locally is what applies the driver.

`#1622` landed between this branch's last merge and the squash attempt. The only
shared file is that append-only index, and both sides only append. No source file
is touched by both.

FOLLOWING_AGENTS_PROTOCOL

Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: Claude:claude-opus-5 [Claude Code]
@localai-bot
localai-bot merged commit 726cfe1 into main Aug 22, 2026
2 of 16 checks passed
@localai-bot
localai-bot deleted the row/RECORD-CUDA-UNIFIED-MEM-CITATION branch August 22, 2026 02:19
localai-bot added a commit that referenced this pull request Aug 23, 2026
…nswer in the two halves it actually has (#1778)

`CudaBackend::DeviceMemoryIsHostAddressable()` gates the portable CPU
reference
tier (`src/vt/op_provider.cpp`), the weight loader's
`VT_ADOPT_DEVICE_BYTES`
adoption (`src/vllm/model_executor/models/qwen3_5_weights.cpp`) and the
logits-processor bounce
(`src/vllm/v1/sample/logits_processor/builtin.cpp`).
Being wrong there hands a device pointer to a host memcpy, which is the
SIGSEGV
that #844 and #1435 measured. Nothing in the tree held that answer.

The test cited as the pin reads a different backend:
`FakeUnifiedAddressablePlatform` in
`tests/vllm/platforms/test_platform.cpp`
reports `device_type() == kCUDA` while its `backend()` returns
`vt::GetBackend(DeviceType::kCPU)`, so its `CHECK_FALSE` measures the
CPU
backend. #1639 corrected that fixture's comment; this change supplies
the pin
the record was still owed.

## A runtime pin was rejected on a CI fact, not a preference

No job in `.github/workflows/ci.yml` has a GPU. `cuda-fat-build` is the
only job
with a CUDA toolchain, it runs the `nvidia/cuda:13.3.0-devel` container
on
`ubuntu-latest`, it configures `-DVLLM_CPP_BUILD_TESTS=OFF` and it
builds the
`vllm` target alone. The CUDA registrar returns early when
`cudaGetDeviceCount`
finds no device, so `vt::GetBackend(kCUDA)` throws on every machine this
project's CI owns. A `TEST_CASE` reading the real backend would report a
skip on
every lane forever, and a skip reads as a pass. That is the shape of
evidence
#1635 was filed about, so writing one and calling the debt discharged
would
repeat the defect rather than close it.

## One claim, two halves, each checked where it executes

The record's claim decomposes, and neither half alone is sufficient.

**`CudaBackend`'s answer IS the base default.**
`src/vt/cuda/cuda_backend.cu`
gains a `static_assert` beside the class requiring
`decltype(&CudaBackend::DeviceMemoryIsHostAddressable)` to be
`bool (Backend::*)() const`. Taking the address of an inherited member
through a
derived class yields a pointer-to-member of the class that DECLARES it,
so that
type holds exactly while `CudaBackend` declares no override of its own,
and
becomes `bool (CudaBackend::*)() const` the moment somebody adds one. It
fires on
ANY override, including one returning `false`, because an override
invalidates
the reasoning whatever it returns. `cuda-fat-build` compiles this
translation
unit on every push.

**The base default is `false`.** `tests/vt/test_backend.cpp` gains a
`Backend`
subclass that implements the pure virtuals and deliberately declares no
`DeviceMemoryIsHostAddressable`. Every other fake in the tree overrides
that
method and takes the answer as a constructor argument, so each measures
its own
override and none reads the default. This one does, and it answers
`UnifiedMemory() == true` on purpose so the case cannot pass by the two
predicates happening to agree. Every host lane runs it.

Together they are a mechanical proof of `CudaBackend` answering `false`,
checked
on surfaces that execute rather than skip.

## Mutation evidence

Half one, against `nvcc` 13.3.33 from the CI container, compiling the
real file:

| Tree | `nvcc -std=c++20 -Iinclude -Isrc -c
src/vt/cuda/cuda_backend.cu` |
|---|---|
| clean, sha256 `d9d11f96...` | rc=0 |
| `+ bool DeviceMemoryIsHostAddressable() const override { return true;
}` | rc=2, `static assertion failed` at `cuda_backend.cu(363)` |
| same override returning `false` | rc=2, identical message |
| restored, sha256 `d9d11f96...` | rc=0 |

Half two, `include/vt/backend.h` default flipped to `true` and the
target
rebuilt:

| Tree | `test_backend -tc="Backend::DeviceMemoryIsHostAddressable
defaults to false"` |
|---|---|
| clean, sha256 `80edf388...` | `assertions: 3 \| 3 passed`, rc=0 |
| default `false` -> `true` | `assertions: 3 \| 1 passed \| 2 failed`,
rc=1 |
| restored, sha256 `80edf388...` | `assertions: 3 \| 3 passed`, rc=0 |

Both mutated builds compiled, so neither red is a build failure wearing
a test
failure, and both files were restored byte-for-byte and re-verified by
sha256
and by re-running the check.

Suites: `test_backend`, `test_backend_cross_device`,
`test_backend_cross_device_vt_attn_decode_d128`, `test_cuda_backend`,
`test_op_provider`, `test_reference_tier` -- 6/6 pass; `test_backend`
alone is
9 cases / 51 assertions.

## What is still not held, stated rather than left to be inferred

No CI surface observes a live `CudaBackend` object answering the
question,
because no CI surface has a device. `tests/vt/test_cuda_backend.cpp`
gains that
observation using the skip convention every case in that file already
follows,
and on a CPU host it reports `assertions: 0` and says in its own skip
message
that nothing was observed and where the answer actually lives. It is the
empirical belt to the two structural braces. Citing it as the pin would
repeat
#1635 exactly, and both the case and the spec say so.

`docs/ENVIRONMENT.md` claimed nothing pinned the real `CudaBackend`,
which this
change makes false, so it now names both halves and warns off the wrong
citation. The `#1502` row of `.agents/issue-index.md` keeps the original
wrong
citation, because that index is append-only and can never be edited; no
row is
edited here.

Fixes #1635

FOLLOWING_AGENTS_PROTOCOL

Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: Claude:claude-opus-5 [Claude Code]

---------

Co-authored-by: Ettore Di Giacinto <mudler@localai.io>
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.

2 participants