Repository navigation
CUDA backend: delete each output handle once when a program returns one tensor twice - #24420
Conversation
When a program returns the same tensor more than once, for example
y.detach() twice, AOTI puts one handle in each of those output slots:
output_handles[0] = buf0.release();
output_handles[1] = output_handles[0];
The CUDA backend copied each output into its ExecuTorch tensor and then
deleted the slot's handle, so the shared handle was deleted twice. The
second copy read a freed handle, and the process crashed:
SlimTensor storage_offset must not be negative, got -9007650542493611503
Failed to D2D copy GPU output 1 into ETensor
free(): double free detected in tcache 2
Now a handle is deleted only at the last slot that holds it, as PyTorch's
own AOTI runner does. The cleanup on error paths follows the same rule.
With CUDA graphs on, the capture step keeps every output handle for the
graph and deletes none of them, so it needs no change.
The comment in ReplaceViewCopyWithViewPass no longer says that a view
would make two outputs share one handle: two detached copies of one
tensor become two alias_copy outputs, and those share one handle too.
test_same_tensor_returned_twice returns one tensor in two output slots
that are not next to each other, with CUDA graphs on. So it runs the
method eagerly, then captures a graph and replays it, and compares every
output with eager. It crashes without this change.
🔗 Helpful Links🧪 See artifacts and rendered test results at hud.pytorch.org/pr/pytorch/executorch/24420
Note: Links to docs will display an error until the docs builds have been completed. ⏳ 1 Pending, 1 Unrelated FailureAs of commit dc628cc with merge base f2564fb ( BROKEN TRUNK - The following job failed but were present on the merge base:👉 Rebase onto the `viable/strict` branch to avoid these failures
This comment was automatically generated by Dr. CI and updates every 15 minutes. |
|
@claude /pr-review |
|
Claude finished @shoumikhin's task in 15m 28s —— View job PR Review: #24420I found no blockers: the change to delete each output handle at its last slot is correct on every path I traced, and the review below has five minor follow-ups, the most notable being that the Metal backend still deletes a repeated output handle once per slot. Detailed reviewSummaryThe PR makes the CUDA backend delete an output handle only at the last output slot that holds it, so a tensor that AOTI returns in several slots is no longer freed twice. I found no correctness problems in the new logic, including on the error and CUDA graph paths. The findings below are minor. They come from reading the code; no tests were run, because the runner has no GPU. Code Quality
Testing
RecommendationApprove: nothing blocks the merge. The Metal item can go in a follow-up PR. The rest are small edits to a comment, the PR description, and the test. |
|
@pytorchbot cherry-pick --onto release/1.6 -c critical |
Cherry picking #24420The cherry pick PR is at #24442 and it is recommended to link a critical cherry pick PR with an issue. Details for Dev Infra teamRaised by workflow job |
|
@pytorchbot cherry-pick --onto cherry-pick-24314-by-pytorch_bot_bot_ -c critical |
Cherry picking #24420Command Details for Dev Infra teamRaised by workflow job |
|
@pytorchbot cherry-pick --onto cherry-pick-24314-by-pytorch_bot_bot_ -c critical |
Cherry picking #24420The cherry pick PR is at #24448 and it is recommended to link a critical cherry pick PR with an issue. Details for Dev Infra teamRaised by workflow job |
Supersedes #24448 with a clean release pick from #24420. Deletes each aliased CUDA output handle once, including error cleanup, and carries its regression test. The original three source files are the complete scope of this pick. ## Scope and landing order Land after #24489 in the verified landing order. This branch starts directly at release/1.6 commit `dfb78b9ecd933a5fee33ebc94f69ed4b71cbb1e7`. Its GitHub diff contains 3 files for this pick; it does not carry duplicate #24298 history or the excluded unmerged QNN work. Dependencies are documented here rather than embedded as other cherry-pick commits. ## Verification All changed Python files compile and pass Ruff F821 checks. All seven replacements merge cleanly in the documented sequence, including simulated squash merges. No QNN files are changed. Native CUDA, OpenVINO and portable C++ suites were not run locally on this macOS host; PR CI remains required before landing. Prepared with OpenAI Codex.
What is wrong today
When a CUDA program returns the same tensor more than once, for example
y.detach()twice, AOTI (AOTInductor, PyTorch's ahead-of-time compiler that generates the CUDA code) puts one output handle in each of those output slots:The CUDA backend copied each output into its ExecuTorch tensor and then deleted that slot's handle. So it deleted the shared handle twice, and the process crashed, for example with:
What this change does
A handle is now deleted only at the last output slot that holds it, as PyTorch's own AOTI runner does. The cleanup on error paths follows the same rule. With CUDA graphs on, a successful capture keeps every output handle for the graph and deletes none of them, so nothing changes there. If ending the capture or instantiating the graph fails, the cleanup deletes the handles by the same rule.
A comment in
ReplaceViewCopyWithViewPasssaid that a view would make two outputs share one handle. That was misleading, because twoalias_copyoutputs of one tensor share one handle too, so the comment no longer says it.What was tested
test_same_tensor_returned_twicereturns one tensor in two output slots that are not next to each other, with CUDA graphs on: three eager calls, a graph capture and a replay, each compared with eager. With torch 2.14 on an A100 it passes, and it crashes without this change. The otherbackends/cudatests that CI runs in the same job still pass.