(tensor)
| 224 | |
| 225 | |
| 226 | def reduce_tensor(tensor): |
| 227 | if tensor.requires_grad and not tensor.is_leaf: |
| 228 | raise RuntimeError( |
| 229 | "Cowardly refusing to serialize non-leaf tensor which requires_grad, " |
| 230 | "since autograd does not support crossing process boundaries. " |
| 231 | "If you just want to transfer the data, call detach() on the tensor " |
| 232 | "before serializing (e.g., putting it on the queue)." |
| 233 | ) |
| 234 | |
| 235 | check_serializing_named_tensor(tensor) |
| 236 | torch.utils.hooks.warn_if_has_hooks(tensor) |
| 237 | |
| 238 | # Note [CUDA IPC and the caching allocator] |
| 239 | # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| 240 | # When you send a CUDA tensor over IPC, you might expect that you will |
| 241 | # get out the same storage from the other end. However, the CUDA caching |
| 242 | # allocator makes it difficult to preserve this invariant. Consider |
| 243 | # the following situation: a tensor of size 0x100 points to offset 0x20 of |
| 244 | # a storage at 0xA100 of size 0x100. (For simplicity, all of these |
| 245 | # sizes are given in bytes). HOWEVER, with the caching allocator, this storage |
| 246 | # might be part of a larger cudaMalloc allocation 0xA000 of size 0x4000. |
| 247 | # |
| 248 | # When we want to send this CUDA tensor over IPC, we must send the |
| 249 | # *entire* cudaMalloc allocation, i.e., the 0xA000 region, not just |
| 250 | # the storage 0xA100 (because that is what CUDA supports). So, on the |
| 251 | # other end, there simply isn't any way to say, "Wait, you gave me |
| 252 | # a bigger region (0xA000) than the one I wanted (0xA100)". |
| 253 | # |
| 254 | # OK, so if you sent the cudaMalloc allocation, can you just wrap that up as |
| 255 | # one storage itself? No, because this cudaMalloc allocation might contain |
| 256 | # storages of mixed types: float, bytes, double... If you make the entire |
| 257 | # allocation a single storage of a type A, we'll hit an error when constructing |
| 258 | # a tensor of type B on the storage. |
| 259 | # |
| 260 | # cudaIpcMemHandle is an identifier to access the sender cudaMalloc allocation on the |
| 261 | # receiver side. However, cudaIpcMemHandles from each device in a given process may |
| 262 | # only be opened by one context per device per other process. |
| 263 | # If we open and close a memory handle multiples times in a process, CUDA is allowed |
| 264 | # to give it a different address; similarly, once we close the memory, we're not |
| 265 | # allowed to access it(and the storage/tensor built on top of it), even if it is |
| 266 | # still live in the original process. As we cannot make a cudaMalloc allocation |
| 267 | # to a single storage in one go, this requires us to cache the device pointer for |
| 268 | # each cudaIpcMemHandle on C++ side to reconstruct types of storages, while keep |
| 269 | # the old ones alives. |
| 270 | # See [https://docs.nvidia.com/cuda/cuda-runtime-api/group__CUDART__DEVICE.html] |
| 271 | # |
| 272 | # This is fine, because all we need to do is to save our position in the allocation, |
| 273 | # and reconstruct storage and tensor from it. |
| 274 | # 0xA000 -> -------CUDA Allocation------ |
| 275 | # | | |
| 276 | # | | |
| 277 | # | | |
| 278 | # | | |
| 279 | # 0xA100 -> --------storage1 begin------ |
| 280 | # | | |
| 281 | # 0xA120 -> --------tensor1 begin ------ |
| 282 | # | | |
| 283 | # | | |
no test coverage detected