Skip to content

[Fix] Don't retain the last tensor in layout-dynamic DLPack fills - #1321

Merged
sjfeng1999 merged 2 commits into
ROCm:mainfrom
josh-schell-amd:fix/dlpack-layout-fill-retains-tensor
Oct 28, 2026
Merged

sjfeng1999 merged 2 commits into
ROCm:mainfrom
josh-schell-amd:fix/dlpack-layout-fill-retains-tensor

Conversation

@josh-schell-amd

@josh-schell-amd josh-schell-amd commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

Motivation

In the layout-dynamic from_dlpack path, DLTensorJitArg.__c_abi_spec__ hands the launch's DLTensorAdaptor from the pointer fill to the layout fill via a thread-local (_shared.dladaptor, added in #682) and never clears it. The fills live as long as the cached CallState, so each such argument keeps its last launch's tensor alive until the next launch.

In AITER's gfx950 A16W16 GEMM (ROCm/aiter#6853), whose arguments use layout-dynamic from_dlpack, FlyDSL kept each launch's input and output tensors alive after the launch. During startup, vLLM runs MiniMax-M3's vision-encoder projection once with 768000 rows (input 768000×1280, output 768000×6144). FlyDSL held on to that input (1.8 GiB) and output (8.8 GiB) after vLLM had freed them, so 10.6 GiB stayed allocated, and vLLM's KV cache came out 7.4 GiB smaller on MI355X. The static-layout from_dlpack path and from_torch_tensor (TorchTensorJitArg) don't keep tensors alive, so this looks unintended.

Technical Details

  • jit_argument.py: the layout fill clears _shared.dladaptor after reading it. The handoff and slot order are unchanged; the cost is one attribute store per layout-dynamic argument per launch.
  • test_callstate_dispatch.py: new test runs both fills on a CPU tensor, drops the tensor, and asserts a weakref to it is dead.

The retained reference was never a keepalive for in-flight kernels (it covered only the last launch per thread), so stream safety is unchanged.

Test Plan

pytest tests/unit/test_callstate_dispatch.py against FlyDSL 0.4.0 (identical code for this function) with and without the change; black and ruff on the changed files. GPU check on MI355X: torch memory_reserved() after the AITER GEMM above, then del + empty_cache().

Test Result

Without fix With fix
test_callstate_dispatch.py new test fails (tensor still alive), 11 pass 12 pass
Reserved after free (MI355X) 10.64 GiB 0.02 GiB

test_launch_overhead.py not run.

Submission Checklist

AI-assisted (Claude Code); results were run and checked on MI355X.

🤖 Generated with Claude Code

DLTensorJitArg's layout-dynamic __c_abi_spec__ passes the per-launch
DLTensorAdaptor from the pointer fill to the layout fill through a
thread-local, but never clears it. The fills live as long as the cached
CallState, and the adaptor keeps the tensor alive, so every layout-dynamic
from_dlpack argument pins its most recent launch's tensor until the next
launch replaces it.

In AITER's gfx950 A16W16 GEMM this pinned 10.6 GiB (A + C of a 768000-row
ViT projection run once during vLLM's encoder profiling), which vLLM then
counted as persistent memory, shrinking the KV cache by 7.4 GiB on MiniMax-M3
(MI355X). Clear the thread-local in the layout fill once it has been read.

Co-Authored-By: Claude <noreply@anthropic.com>
@josh-schell-amd
josh-schell-amd force-pushed the fix/dlpack-layout-fill-retains-tensor branch from 203d2c4 to 8fdabcb Compare October 26, 2026 16:48
@sjfeng1999
sjfeng1999 merged commit 64e409b into ROCm:main Oct 28, 2026
12 checks passed
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.

3 participants