Skip to content

vllm::Pool() free list is keyed by size class with no DEVICE in the key: a cudaMalloc'd block can be handed to a CPU DBuf #516

Description

@localai-bot

Found while repairing LTX-2.5 phases L7/L8 (#435, PR #437). Not an LTX defect — it is a latent fault in a shared hot path that LTX merely became the first caller to reach.

The defect

vllm::Pool() is a process-wide singleton whose free list is keyed by size class only. The device is not part of the key. So a block allocated with cudaMalloc for a CUDA arm is returned to the pool and then handed straight to a CPU-backend DBuf on the next allocation of the same size class, which copies into it with a host memcpy.

Symptom

test_ltx2_device SIGSEGV on GB10:

__memcpy_sve  <-  UploadStream  <-  PrepareStreamDev

8 cases run, 1 failed, 5 never reached — this project's "changed COUNT = RED" signature.

compute-sanitizer reports 0 device errors, which is the tell: the fault is host-side, dereferencing a device pointer from the CPU. A tool aimed at the GPU cannot see it.

Why it stayed hidden

It requires a bf16 CPU-backend device forward to run after a bf16 CUDA one in the same process. No test had ever done that. At f32 the two arms land in different size classes and never trade blocks, so the f32 gates could not reach it either.

That is spec §7.0(c) again (): a fixture that cannot enter the regime that discriminates. The bug is old; only the ordering is new.

Immediate mitigation (in the LTX repair, not a fix for this issue)

The CPU arm now runs under its own DevicePool via ActivePoolScope. That seam already exists for exactly this shape — AuxPool() is documented as existing because "two streams sharing one pool BREAKS" its reuse ordering. test_ltx2_device is back to 13/498 SUCCESS on the CPU backend.

That scopes one caller away from the hazard. It does not fix the pool.

What this issue wants

Put the device in the free-list key, so a block allocated on one device can never be handed to another. It is a shared hot path, so:

  • it needs its own row, spec and red-first test;
  • the test must be the ordering that exposes it (same size class, CUDA allocation freed, then a host-backend allocation), because nothing currently in the tree produces it;
  • worth auditing whether any other caller mixes backends within a size class today, since the failure mode is a host dereference of a device pointer and would present as an unrelated segfault.

Blast radius, honestly stated

Unknown and worth establishing. Any code path that allocates on CUDA, frees, then allocates the same size class on a host backend in the same process is exposed. It has been latent long enough that nothing in the current suite triggers it, so "no failures today" is not evidence of safety — it is evidence that no test has produced the ordering.

Related: #449 (an instrument that could not see the defect class it existed to catch), .agents/specs/ltx-2-5.md §7.0.

🤖 Generated with Claude Code

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions