test_ltx2_device fails under ASan with a heap-use-after-free that is not the
cross-thread race #904 described and closed. The staged DiT weights are destroyed
and then read on the same thread, inside Ltx2DitForwardDevice.
Evidence
sanitize-cpu (address,undefined), job on PR #945's head 7389d2600, 2026-08-17
11:46 UTC. 99% tests passed, 1 tests failed out of 485 — 72 - test_ltx2_device.
Read — thread T0:
READ of size 2 at 0x50a000038c40 thread T0
#0 LoadF32At src/vt/cpu/cpu_layernorm.cpp:33
#1 operator() src/vt/cpu/cpu_layernorm.cpp:121
#2 AddKernel (vt::cpu anonymous namespace)
#6 operator() src/vt/cpu/cpu_threadpool.cpp:452 (ParallelForRows)
Freed — also thread T0:
freed by thread T0 here:
#1 FreeAligned64 src/vt/cpu/cpu_backend.cpp:28
#2 Free src/vt/cpu/cpu_backend.cpp:46
#3 operator() src/vllm/model_executor/models/ltx2_device.cpp:1088
#4 _M_dispose shared_ptr_base.h:527
#14 vllm::Ltx2DitDeviceWeights::~Ltx2DitDeviceWeights()
#17 vllm::Ltx2DitForwardDevice(vt::Queue&, vllm::Ltx2DitParams const&, ...)
Allocated:
#1 AllocAligned64 src/vt/cpu/cpu_backend.cpp:20
#2 Alloc src/vt/cpu/cpu_backend.cpp:42
#3 vllm::Ltx2StageDitWeightsToDevice(...)
#4 DOCTEST_ANON_FUNC_76 tests/vllm/models/test_ltx2_device.cpp:724
The owning handle is out.storage.emplace_back(dev, [&backend](void* p) { backend.Free(p); })
in Ltx2StageDitWeightsToDevice (ltx2_device.cpp ~:1088), so every staged buffer's
lifetime is the Ltx2DitDeviceWeights::storage vector. Frame #14 shows that vector's
destructor running inside Ltx2DitForwardDevice, while view.data raw pointers
handed to the kernels still point into those buffers.
Why this is not #904
#904 ("frees staged buffers while a
threadpool worker is still reading them") and its duplicate
#905 were both closed COMPLETED on
2026-08-15, and describe a cross-thread race: T0 frees, a threadpool worker T2 reads.
Here both the free and the read are T0. ParallelForRows appears in the read stack,
but the faulting access is on the calling thread, so a thread-synchronisation fix does not
address it. The lifetime is simply ending too early on one thread.
This is the failure mode #904's own thread-framing invites: the free site identifies the
bug, not the reading thread.
Why it has not been caught
PR #945 is markdown, CI-workflow and include-only, and cannot reach either file — it is the
messenger, not the cause.
What to look at
Ltx2DitForwardDevice's handling of the Ltx2DitDeviceWeights it receives or constructs:
whether it takes it by value, rebinds it, or lets a temporary expire while vt::Tensor::data
raw pointers derived from storage remain in flight. The destructor firing at frame #14
inside the forward is the anomaly to explain.
Reopening #904 would also be reasonable if maintainers prefer one thread for this defect —
filed separately because the mechanism recorded there is not the one observed.
test_ltx2_devicefails under ASan with a heap-use-after-free that is not thecross-thread race #904 described and closed. The staged DiT weights are destroyed
and then read on the same thread, inside
Ltx2DitForwardDevice.Evidence
sanitize-cpu (address,undefined), job on PR #945's head7389d2600, 2026-08-1711:46 UTC.
99% tests passed, 1 tests failed out of 485—72 - test_ltx2_device.Read — thread T0:
Freed — also thread T0:
Allocated:
The owning handle is
out.storage.emplace_back(dev, [&backend](void* p) { backend.Free(p); })in
Ltx2StageDitWeightsToDevice(ltx2_device.cpp~:1088), so every staged buffer'slifetime is the
Ltx2DitDeviceWeights::storagevector. Frame #14 shows that vector'sdestructor running inside
Ltx2DitForwardDevice, whileview.dataraw pointershanded to the kernels still point into those buffers.
Why this is not #904
#904 ("frees staged buffers while a
threadpool worker is still reading them") and its duplicate
#905 were both closed COMPLETED on
2026-08-15, and describe a cross-thread race: T0 frees, a threadpool worker T2 reads.
Here both the free and the read are T0.
ParallelForRowsappears in the read stack,but the faulting access is on the calling thread, so a thread-synchronisation fix does not
address it. The lifetime is simply ending too early on one thread.
This is the failure mode #904's own thread-framing invites: the free site identifies the
bug, not the reading thread.
Why it has not been caught
src/vllm/model_executor/models/ltx2_device.cppandsrc/vt/cpu/cpu_layernorm.cppareunchanged on
mainsince5a0ffe9e3(2026-08-15), i.e. since before main red on sanitize-cpu: Ltx2DitDeviceWeights frees staged buffers while a threadpool worker is still reading them #904 was closed.Nothing has fixed this.
workflow_dispatchbaseline run onac5057960(run
32044993401,conclusion=success) had both sanitizer arms green on the samecode. So a single green sanitize run does not clear this path.
mainsanitizer runs are cancelled by the next merge (main is not verified by its own CI: every run is cancelled, and 5 tests fail under ASan/UBSan #274), so the sample is thin.PR #945 is markdown, CI-workflow and include-only, and cannot reach either file — it is the
messenger, not the cause.
What to look at
Ltx2DitForwardDevice's handling of theLtx2DitDeviceWeightsit receives or constructs:whether it takes it by value, rebinds it, or lets a temporary expire while
vt::Tensor::dataraw pointers derived from
storageremain in flight. The destructor firing at frame #14inside the forward is the anomaly to explain.
Reopening #904 would also be reasonable if maintainers prefer one thread for this defect —
filed separately because the mechanism recorded there is not the one observed.