Stop ignoring a failed cudaSetDevice in CudaAllocator - #22093
Open
shoumikhin wants to merge 1 commit into
Open
Conversation
CudaAllocator switches the current CUDA device before it allocates, frees or copies, and switches back afterwards. The result of that switch was thrown away in all three places. When the switch fails, for example because the requested device index does not exist on this machine, the work still went ahead on whatever device happened to be current. allocate() then returned success with a pointer that lives on a different device than the caller asked for. The caller stores the requested index next to that pointer, so the pointer and its recorded device disagree from then on, and the mistake only shows up much later as a wrong result or an unrelated CUDA error. Now allocate() and the copy helpers log the CUDA error and return Error::MemoryAllocationFailed and Error::Internal instead of going ahead. deallocate() returns void and cudaFree works on a pointer from any device under unified addressing, so it logs the error and still frees rather than leaking. Example, on a machine with one GPU: ``` auto r = CudaAllocator::instance().allocate(1024, /*index=*/1); // before: r.ok() == true, pointer actually on device 0 // after: r.ok() == false, r.error() == Error::MemoryAllocationFailed ``` Test plan: Added three tests to test_cuda_allocator.cpp that ask for device index device_count, which is one past the last valid ordinal on any machine, so the switch always fails: AllocateOnMissingDeviceFails CopyHostToDeviceOnMissingDeviceFails CopyDeviceToHostOnMissingDeviceFails Built and ran the suite on one NVIDIA H100. With this change: [ PASSED ] 9 tests. With the allocator reverted to the old code and the same three tests: [ FAILED ] CudaAllocatorTest.AllocateOnMissingDeviceFails [ FAILED ] CudaAllocatorTest.CopyHostToDeviceOnMissingDeviceFails [ FAILED ] CudaAllocatorTest.CopyDeviceToHostOnMissingDeviceFails The allocate failure showed the old behavior directly: the call reported success for a device that does not exist. The logged message on the fixed build: CudaAllocator::allocate: cudaSetDevice(1) failed: invalid device ordinal clang-format reports no changes needed on either touched file. Not covered: the deallocate path only logs, so no test asserts on it.
🔗 Helpful Links🧪 See artifacts and rendered test results at hud.pytorch.org/pr/pytorch/executorch/22093
Note: Links to docs will display an error until the docs builds have been completed. ⏳ No Failures, 3 PendingAs of commit 4b04163 with merge base 9a2d135 ( This comment was automatically generated by Dr. CI and updates every 15 minutes. |
This PR needs a
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
CudaAllocatorswitches the current CUDA device before it allocates, frees orcopies, and switches back afterwards. The result of that switch was thrown away
in all three places:
(void)cudaSetDevice(index);When the switch fails, for example because the requested device index does not
exist on this machine, the work still went ahead on whatever device happened to
be current.
allocatethen returned success with a pointer that lives on adifferent device than the caller asked for. The caller stores the requested
index next to that pointer, so the pointer and its recorded device disagree
from then on, and the mistake surfaces much later as a wrong result or an
unrelated CUDA error somewhere else.
On a machine with one GPU:
What changes:
allocatelogs the CUDA error and returnsError::MemoryAllocationFailedinstead of allocating on the wrong device.
return
Error::Internalinstead of copying against the wrong device.deallocatereturnsvoid, andcudaFreeaccepts a pointer from any deviceunder unified addressing, so it logs the error and still frees the pointer
rather than leaking it. The failure is no longer silent.
Only the switch to the requested device is checked. The switch back to the
previous device stays best effort, because there is nothing useful to do if
restoring fails.
Test plan
Three new tests in
backends/cuda/runtime/test/test_cuda_allocator.cppask fordevice index
device_count, which is one past the last valid ordinal on anymachine, so the switch always fails:
AllocateOnMissingDeviceFailsCopyHostToDeviceOnMissingDeviceFailsCopyDeviceToHostOnMissingDeviceFailsBuilt and ran the suite on one NVIDIA H100.
With this change:
Confirmed the tests really catch the bug. With the allocator reverted to the
old code and the same three tests:
The allocate failure shows the old behavior directly: the call reported success
for a device that does not exist.
The message the fixed build logs:
The six tests already in that file still pass, so the change does not disturb
the normal single device path.
clang-formatreports no changes needed on either touched file.Not covered
deallocateonly logs, so no test asserts on it. Everything here ran on asingle GPU machine, so the switch always failed with an invalid ordinal. A
device that exists but is unavailable, for example one in exclusive compute
mode, takes the same code path but was not exercised.