Repository navigation
Commit 1e716a6
Stop ignoring a failed cudaSetDevice in CudaAllocator (pytorch#22093)
### Summary
`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:
```cpp
(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. `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 surfaces much later as a wrong result or
an
unrelated CUDA error somewhere else.
On a machine with one GPU:
```cpp
auto r = CudaAllocator::instance().allocate(1024, /*index=*/1);
// before: r.ok() == true, and the pointer is really on device 0
// after: r.ok() == false, r.error() == Error::Internal
```
What changes:
* `allocate` logs the CUDA error and returns `Error::Internal` instead
of
allocating on the wrong device. A device that cannot be selected is not
an
out-of-memory condition, and `Error::Internal` is what `Exception.h`
maps
every CUDA error to and what `cuda_mutable_state.cpp` already returns
for
this same call.
* The host to device and device to host copy helpers log the CUDA error
and
return `Error::Internal` instead of copying against the wrong device.
* All three paths also fail when `cudaGetDevice` fails, instead of
carrying on
without switching. Without the current device there is no way to switch
to the
requested one or restore afterwards, so the work would run against
whatever
device happened to be current. This is the branch the tests cannot
reach: on a
working CUDA host `cudaGetDevice` does not fail, so it is verified by
inspection rather than by the new tests.
* `deallocate` returns `void`, and `cudaFree` accepts a pointer from any
device
under 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.cpp`
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`
`DeviceIndex` is `int8_t`, so each of the three first checks that
one-past-the-last
ordinal still fits in it: at 128 or more visible GPUs the count wraps to
a negative
index, or at 256 back onto real device 0, and neither is a valid absent
ordinal. The
check is per-test rather than in `SetUp` so it does not also skip the
six device-0
tests. 127 devices still runs; 128 skips.
Built and ran the suite on one NVIDIA H100.
With this change:
```
[ PASSED ] 9 tests.
```
Confirmed the tests really catch the bug. 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 shows the old behavior directly: the call reported
success
for a device that does not exist.
The message the fixed build logs:
```
CudaAllocator::allocate: cudaSetDevice(1) failed: invalid device ordinal
```
The six tests already in that file still pass, so the change does not
disturb
the normal single device path.
`clang-format` reports no changes needed on either touched file.
### Not covered
The `cudaGetDevice` failure branch is not covered by these tests. On a
working CUDA
host that call does not fail, and there is no injection point in the
suite, so
reverting just that branch leaves all three tests passing. Reaching it
would need a
function-pointer seam or an injected fake CUDA runtime, both larger than
the guard
they would protect.
`deallocate` only logs, so no test asserts on it. Everything here ran on
a
single 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.
---------
Co-authored-by: r <r@e>1 parent 4a61ef4 commit 1e716a6
2 files changed
Lines changed: 158 additions & 19 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
61 | 61 | | |
62 | 62 | | |
63 | 63 | | |
64 | | - | |
| 64 | + | |
65 | 65 | | |
66 | 66 | | |
67 | | - | |
68 | | - | |
69 | | - | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
70 | 94 | | |
71 | 95 | | |
72 | 96 | | |
| |||
79 | 103 | | |
80 | 104 | | |
81 | 105 | | |
82 | | - | |
| 106 | + | |
83 | 107 | | |
84 | 108 | | |
85 | 109 | | |
| |||
130 | 154 | | |
131 | 155 | | |
132 | 156 | | |
133 | | - | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
134 | 176 | | |
135 | | - | |
136 | | - | |
137 | | - | |
138 | | - | |
139 | 177 | | |
140 | | - | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
| 188 | + | |
| 189 | + | |
| 190 | + | |
| 191 | + | |
141 | 192 | | |
142 | 193 | | |
143 | 194 | | |
| |||
178 | 229 | | |
179 | 230 | | |
180 | 231 | | |
181 | | - | |
| 232 | + | |
182 | 233 | | |
183 | 234 | | |
184 | | - | |
185 | | - | |
186 | | - | |
| 235 | + | |
| 236 | + | |
| 237 | + | |
| 238 | + | |
| 239 | + | |
| 240 | + | |
| 241 | + | |
| 242 | + | |
| 243 | + | |
| 244 | + | |
| 245 | + | |
| 246 | + | |
| 247 | + | |
| 248 | + | |
| 249 | + | |
| 250 | + | |
| 251 | + | |
| 252 | + | |
| 253 | + | |
| 254 | + | |
| 255 | + | |
| 256 | + | |
187 | 257 | | |
188 | 258 | | |
189 | 259 | | |
190 | 260 | | |
191 | 261 | | |
192 | | - | |
| 262 | + | |
193 | 263 | | |
194 | 264 | | |
195 | 265 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
11 | 11 | | |
12 | 12 | | |
13 | 13 | | |
| 14 | + | |
14 | 15 | | |
15 | 16 | | |
16 | 17 | | |
| |||
20 | 21 | | |
21 | 22 | | |
22 | 23 | | |
| 24 | + | |
23 | 25 | | |
24 | 26 | | |
25 | 27 | | |
26 | 28 | | |
27 | 29 | | |
28 | 30 | | |
29 | | - | |
30 | | - | |
31 | | - | |
| 31 | + | |
| 32 | + | |
32 | 33 | | |
33 | 34 | | |
34 | 35 | | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
35 | 53 | | |
36 | 54 | | |
37 | 55 | | |
| |||
112 | 130 | | |
113 | 131 | | |
114 | 132 | | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
0 commit comments