From 0974b34dc25b00c522c34f352772f84d9626bd88 Mon Sep 17 00:00:00 2001 From: Vincent Moens Date: Wed, 24 Jul 2024 16:51:26 +0100 Subject: [PATCH 01/54] init --- _static/img/pinmem.png | Bin 0 -> 73722 bytes intermediate_source/pinmem_nonblock.py | 334 +++++++++++++++++++++++++ 2 files changed, 334 insertions(+) create mode 100644 _static/img/pinmem.png create mode 100644 intermediate_source/pinmem_nonblock.py diff --git a/_static/img/pinmem.png b/_static/img/pinmem.png new file mode 100644 index 0000000000000000000000000000000000000000..9d84e9d229d5cb42e982cadeb388d59153918591 GIT binary patch literal 73722 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≤ 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Ɗ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 CPU etc.)](#other-directions) +# - [Practical recommendations](#practical-recommendations) +# - [Case studies](#case-studies) +# - [Conclusion](#conclusion) +# - [Additional resources](#additional-resources) +# +# +# Background +# ---------- +# +# Memory management basics +# ~~~~~~~~~~~~~~~~~~~~~~~~ +# +# When one creates a CPU tensor in PyTorch, the content of this tensor needs to be placed +# in memory. The memory we talk about here is a rather complex concept worth looking at carefully. +# We distinguish two types of memories that are handled by the Memory Management Unit: the main memory (for simplicity) +# and the disk (which may or may not be the hard drive). Together, the available space in disk and RAM (physical memory) +# make up the virtual memory, which is an abstraction of the total resources available. +# In short, the virtual memory makes it so that the available space is larger than what can be found on RAM in isolation +# and creates the illusion that the main memory is larger than it actually is. +# +# In normal circumstances, a regular CPU tensor is _paged_, which means that it is divided in blocks called _pages_ that +# can live anywhere in the virtual memory (both in RAM or on disk). As mentioned earlier, this has the advantage that +# the memory seems larger than what the main memory actually is. +# +# Typically, when a program accesses a page that is not in RAM, a "page fault" occurs and the operating system (OS) then brings +# back this page into RAM (_swap in_ or _page in_). +# In turn, the OS may have to _swap out_ (or _page out_) another page to make room for the new page. +# +# In contrast to pageable memory, a _pinned_ (or _page-locked_ or _non-pegeable_) memory is a type of memory that cannot be swapped out to disk. +# It allows for faster and more predictable access times, but has the downside that it is more limited than the +# pageable memory (aka the main memory). +# +# .. figure:: /_static/img/pinmem.png +# :alt: +# +# CUDA and (non-)pageable memory +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# +# To understand how CUDA copies a tensor from CPU to CUDA, let's consider the two scenarios above: +# - If the memory is page-locked, the device can access the memory directly in the main memory. The memory addresses are well +# defined and functions that need to read these data can be significantly accelerated. +# - If the memory is pageable, all the pages will have to be brought to the main memory before being sent to the GPU. +# This operation may take time and is less predictable than when executed on page-locked tensors. +# +# More precisely, when CUDA sends pageable data from CPU to GPU, it must first create a page-locked copy of that data +# before making the transfer. +# +# Asynchronous vs. Synchronous Operations with `non_blocking=True` (CUDA `cudaMemcpyAsync`) +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# +# When executing a copy from a host (e.g., CPU) to a device (e.g., GPU), the CUDA toolkit offers modalities to do these +# operations synchronously or asynchronously with respect to the host. In the synchronous case, the call to `cudaMemcpy` +# that is queries by `tensor.to(device)` is blocking in the python main thread, which means that the code will stop until +# the data has been transferred to the device. +# +# When calling `tensor.to(device)`, PyTorch always makes a call to [`cudaMemcpyAsync`](https://docs.nvidia.com/cuda/cuda-runtime-api/group__CUDART__MEMORY.html#group__CUDART__MEMORY_1g85073372f776b4c4d5f89f7124b7bf79). If `non_blocking=False` (default), a `cudaStreamSynchronize` will be called after each and every `cudaMemcpyAsync`. If `non_blocking=True`, no synchronization is triggered, and the main thread on the host is not blocked. +# Therefore, from the host perspective, multiple tensors can be sent to the device simultaneously in the latter case, as the thread does not need for one transfer to be completed to initiate the other. +# +# .. note:: In general, the transfer is blocking on the device size even if it's not on the host side: the copy on the device cannot +# occur while another operation is being executed. However, in some advanced scenarios, multiple copies or copy and kernel +# executions can be done simultaneously on the GPU side. To enable this, three requirements must be met: +# +# 1. The device must have at least one free DMA (Direct Memory Access) engine. Modern GPU architectures such as Volterra, +# Tesla or H100 devices have more than one DMA engine. +# +# 2. The transfer must be done on a separate, non-default cuda stream. In PyTorch, cuda streams can be handles using +# `torch.cuda.Stream`. +# +# 3. The source data must be in pinned memory. +# +# +# A PyTorch perspective +# --------------------- +# +# `pin_memory()` +# ~~~~~~~~~~~~~~ +# +# PyTorch offers the possibility to create and send tensors to page-locked memory through the `pin_memory` functions and +# arguments. +# Any cpu tensor on a machine where a cuda is initialized can be sent to pinned memory through the `pin_memory` +# method. Importantly, `pin_memory` is blocking on the host: the main thread will wait for the tensor to be copied to +# page-locked memory before executing the next operation. +# New tensors can be directly created in pinned memory with functions like `torch.zeros`, `torch.ones` and other +# constructors. +# +# Let us check the speed of pinning memory and sending tensors to cuda: + + +import torch +import gc +from torch.utils.benchmark import Timer + +tensor_pageable = torch.randn(100_000) + +tensor_pinned = torch.randn(100_000, pin_memory=True) + +print("Regular to(device)", + Timer("tensor_pageable.to('cuda:0')", globals=globals()).adaptive_autorange()) +print("Pinned to(device)", + Timer("tensor_pinned.to('cuda:0')", globals=globals()).adaptive_autorange()) +print("pin_memory() along", + Timer("tensor_pageable.pin_memory()", globals=globals()).adaptive_autorange()) +print("pin_memory() + to(device)", + Timer("tensor_pageable.pin_memory().to('cuda:0')", globals=globals()).adaptive_autorange()) +del tensor_pageable, tensor_pinned +gc.collect() + + +###################################################################### +# We can observe that casting a pinned-memory tensor to GPU is indeed much faster than a pageable tensor, because under the hood, a pageable tensor must be copied to pinned memory before being sent to GPU. +# +# However, calling `pin_memory()` on a pageable tensor before casting it to GPU does not bring any speed-up, on the contrary this call is actually slower than just executing the transfer. Again, this makes sense, since we're actually asking python to execute an operation that CUDA will perform anyway before copying the data from host to device. +# +# `non_blocking=True` +# ~~~~~~~~~~~~~~~~~~~ +# +# As mentioned earlier, many PyTorch operations have the option of being executed asynchronously with respect to the host through the `non_blocking` argument. +# Here, to account accurately of the benefits of using `non_blocking`, we will design a slightly more involved experiment since we want to assess how fast it is to send multiple tensors to GPU with and without calling `non_blocking`. +# + + +def copy_to_device(*tensors, display_peak_mem=False): + result = [] + for tensor in tensors: + result.append(tensor.to("cuda:0")) + return result +def copy_to_device_nonblocking(*tensors, display_peak_mem=False): + result = [] + for tensor in tensors: + result.append(tensor.to("cuda:0", non_blocking=True)) + # We need to synchronize + torch.cuda.synchronize() + return result + +tensors = [torch.randn(1000) for _ in range(1000)] +print("Call to `to(device)`", Timer("copy_to_device(*tensors)", globals=globals()).adaptive_autorange()) +print("Call to `to(device, non_blocking=True)`", Timer("copy_to_device_nonblocking(*tensors)", + globals=globals()).adaptive_autorange()) + + +###################################################################### +# To get a better sense of what is happening here, let us run a profiling of these two code executions: + + +from torch.profiler import profile, record_function, ProfilerActivity + +def profile_mem(cmd): + with profile(activities=[ProfilerActivity.CPU]) as prof: + exec(cmd) + print(cmd) + print(prof.key_averages().table(row_limit=10)) + +print("Call to `to(device)`", profile_mem("copy_to_device(*tensors)")) +print("Call to `to(device, non_blocking=True)`", profile_mem("copy_to_device_nonblocking(*tensors)")) + + +###################################################################### +# The results are without any doubt better when using `non_blocking=True`, as all transfers are initiated simultaneously on the host side. +# Note that, interestingly, `to("cuda")` actually performs the same asynchrous device casting operation as the one with `non_blocking=True` with a synchronization point after each copy. +# +# The benefit will vary depending on the number and the size of the tensors as well as depending on the hardware being used. +# +# Synergies +# ~~~~~~~~~ +# +# Now that we have made the point that data transfer of tensors already in pinned memory to GPU is faster than from pageable memory, and that we know that doing these transfers asynchronously is also faster than synchronously, we can benchmark the various combinations at hand: + + +def pin_copy_to_device(*tensors): + result = [] + for tensor in tensors: + result.append(tensor.pin_memory().to("cuda:0")) + return result +def pin_copy_to_device_nonblocking(*tensors): + result = [] + for tensor in tensors: + result.append(tensor.pin_memory().to("cuda:0", non_blocking=True)) + # We need to synchronize + torch.cuda.synchronize() + return result + +print("\nCall to `pin_memory()` + `to(device)`") +print("pin_memory().to(device)", + Timer("pin_copy_to_device(*tensors)", globals=globals()).adaptive_autorange()) +print("pin_memory().to(device, non_blocking=True)", + Timer("pin_copy_to_device_nonblocking(*tensors)", + globals=globals()).adaptive_autorange()) + +print("\nCall to `to(device)`") +print("to(device)", + Timer("copy_to_device(*tensors)", globals=globals()).adaptive_autorange()) +print("to(device, non_blocking=True)", + Timer("copy_to_device_nonblocking(*tensors)", + globals=globals()).adaptive_autorange()) + +print("\nCall to `to(device)` from pinned tensors") +tensors_pinned = [torch.zeros(1000, pin_memory=True) for _ in range(1000)] +print("tensor_pinned.to(device)", + Timer("copy_to_device(*tensors_pinned)", globals=globals()).adaptive_autorange()) +print("tensor_pinned.to(device, non_blocking=True)", + Timer("copy_to_device_nonblocking(*tensors_pinned)", + globals=globals()).adaptive_autorange()) + +del tensors, tensors_pinned +gc.collect() + + +###################################################################### +# Other directions (GPU -> CPU, CPU -> MPS etc.) +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# +# So far, we have assumed that doing asynchronous copies from CPU to GPU was safe. +# Indeed, it is a safe thing to do because CUDA will synchronize whenever it is needed to make sure that the data being read is not garbage. +# However, any other copy (e.g., from GPU to CPU) has no guarantee whatsoever that the copy will be completed when the data is read. In fact, if no explicit synchronization is done, the data on the host can be garbage: +# + + + +tensor = torch.arange(1, 1_000_000, dtype=torch.double, device="cuda").expand(100, 999999).clone() +torch.testing.assert_close(tensor.mean(), torch.tensor(500_000, dtype=torch.double, device="cuda")), tensor.mean() +try: + i = -1 + for i in range(100): + cpu_tensor = tensor.to("cpu", non_blocking=True) + torch.testing.assert_close(cpu_tensor.mean(), torch.tensor(500_000, dtype=torch.double)) + print("No test failed with non_blocking") +except AssertionError: + print(f"One test failed with non_blocking: {i}th assertion!") +try: + i = -1 + for i in range(100): + cpu_tensor = tensor.to("cpu", non_blocking=True) + torch.cuda.synchronize() + torch.testing.assert_close(cpu_tensor.mean(), torch.tensor(500_000, dtype=torch.double)) + print("No test failed with synchronize") +except AssertionError: + print(f"One test failed with synchronize: {i}th assertion!") + + +###################################################################### +# The same observation could be made with copies from CPU to a non-CUDA device such as MPS. +# +# In summary, copying data from CPU to GPU is safe when using `non_blocking=True`, but for any other direction, `non_blocking=True` can still be used but the user must make sure that a device synchronization is executed after the data is accessed. +# +# Practical recommendations +# ------------------------- +# +# We can now wrap up some early recommendations based on our observations: +# In general, `non_blocking=True` will provide a good speed of transfer, regardless of whether the original tensor is or isn't in pinned memory. If the tensor is already in pinned memory, the transfer can be accelerated, but sending it to pin memory manually is a blocking operation on the host and hence will anihilate much of the benefit of using `non_blocking=True` (and CUDA does the `pin_memory` transfer anyway). +# +# One might now legitimetely ask what use there is for the `pin_memory()` method within the `torch.Tensor` class. In the following section, we will explore further how this can be used to accelerate the data transfer even more. +# +# Additional considerations +# ------------------------- +# +# PyTorch notoriously provides a `DataLoader` class that accepts a `pin_memory` argument. +# Given everything we have said so far about calls to `pin_memory`, how does the dataloader manage to accelerate data transfers? +# +# The answer is resides in the fact that the dataloader reserves a separate thread to copy the data from pageable to pinned memory, thereby avoiding to block the main thread with this. Consider the following example, where we send a list of tensors to cuda after calling pin_memory on a separate thread: +# +# A more isolated example of this is the TensorDict primitive from the homonymous library: when calling `TensorDict.to(device)`, the default behaviour is to send these tensors to the device asynchronously and make a `device.synchronize()` call after. `TensorDict.to()` also offers a `non_blocking_pin` argument which will spawn multiple threads to do the calls to `pin_memory()` before launching the calls to `to(device)`. +# This can further speed up the copies as the following example shows: +# +# .. code-block:: bash +# +# !pip3 install https://github.com/pytorch/tensordict +# + +from tensordict import TensorDict +import torch +from torch.utils.benchmark import Timer + +td = TensorDict({str(i): torch.randn(1_000_000) for i in range(100)}) + +print(Timer("td.to('cuda:0', non_blocking=False)", globals=globals()).adaptive_autorange()) +print(Timer("td.to('cuda:0')", globals=globals()).adaptive_autorange()) +print(Timer("td.to('cuda:0', non_blocking=True, non_blocking_pin=True)", globals=globals()).adaptive_autorange()) + + +###################################################################### +# As a side note, it may be tempting to create everlasting buffers in pinned memory and copy tensors from pageable memory to pinned memory, and use these as shuttle before sending the data to GPU. +# Unfortunately, this does not speed up computation because the bottleneck of copying data to pinned memory is still present. +# +# Another consideration is that transferring data that is stored on disk (shared memory or files) to GPU will usually require the data to be copied to pinned memory (which is on RAM) as an intermediate step. +# +# Using `non_blocking` in these context for large amount of data may have devastating effects on RAM consumption. In practice, there is no silver bullet, and the performance of any combination of multithreaded pin_memory and non_blocking will depend on multiple factors such as the system being used, the OS, the hardware and the tasks being performed. +# +# Finally, creating a large number of tensors or a few large tensors in pinned memory will effectively reserve more RAM than pageable tensors would, thereby lowering the amount of available RAM for other operations (such as swapping pages in and out), which can have a negative impact over the overall runtime of an algorithm. + +###################################################################### +# ## Conclusion +# +# ## Additional resources +# From 5f0b6aea4ed0f2c9f53bec1359126539c1b56bd2 Mon Sep 17 00:00:00 2001 From: Vincent Moens Date: Wed, 24 Jul 2024 17:01:06 +0100 Subject: [PATCH 02/54] index.rst --- index.rst | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/index.rst b/index.rst index 1ddba17d1b..2ca7721a88 100644 --- a/index.rst +++ b/index.rst @@ -93,6 +93,13 @@ Welcome to PyTorch Tutorials :link: intermediate/tensorboard_tutorial.html :tags: Interpretability,Getting-Started,TensorBoard +.. customcarditem:: + :header: Good usage of `non_blocking` and `pin_memory()` in PyTorch + :card_description: A guide on best practices to copy data from CPU to GPU. + :image: _static/img/pinmem.png + :link: intermediate/pinmem_nonblock.html + :tags: Getting-Started + .. Image/Video .. customcarditem:: From 72f8951f1bfc96c1237d4eff1697ea2dca522d11 Mon Sep 17 00:00:00 2001 From: Vincent Moens Date: Wed, 24 Jul 2024 17:06:19 +0100 Subject: [PATCH 03/54] index.rst --- index.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/index.rst b/index.rst index 2ca7721a88..749fc16436 100644 --- a/index.rst +++ b/index.rst @@ -949,6 +949,7 @@ Additional Resources beginner/basics/autogradqs_tutorial beginner/basics/optimization_tutorial beginner/basics/saveloadrun_tutorial + intermediate/pinmem_nonblock advanced/custom_ops_landing_page .. toctree:: From 4fe1b2d7760e427cbb2729858a58349a352e4209 Mon Sep 17 00:00:00 2001 From: Vincent Moens Date: Wed, 24 Jul 2024 17:18:02 +0100 Subject: [PATCH 04/54] spelling --- en-wordlist.txt | 12 +++++++++--- intermediate_source/pinmem_nonblock.py | 11 ++++++----- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/en-wordlist.txt b/en-wordlist.txt index b52d8374d3..5dcc5ea764 100644 --- a/en-wordlist.txt +++ b/en-wordlist.txt @@ -1,3 +1,4 @@ + ACL ADI AOT @@ -50,6 +51,7 @@ DDP DDPG DDQN DLRM +DMA DNN DQN DataLoaders @@ -139,6 +141,7 @@ MKLDNN MLP MLPs MNIST +MPS MUC MacBook MacOS @@ -219,6 +222,7 @@ STR SVE SciPy Sequentials +Sharding Sigmoid SoTA Sohn @@ -254,6 +258,7 @@ VLDB VQA VS Code ViT +Volterra WMT WSI WSIs @@ -336,11 +341,11 @@ dataset’s deallocation decompositions decorrelated -devicemesh deserialize deserialized desynchronization deterministically +devicemesh dimensionality dir discontiguous @@ -384,6 +389,7 @@ hessian hessians histoencoder histologically +homonymous hotspot hvp hyperparameter @@ -459,6 +465,7 @@ optimizer's optimizers otsu overfitting +pageable parallelizable parallelization parametrization @@ -522,7 +529,6 @@ runtime runtimes scalable sharded -Sharding softmax sparsified sparsifier @@ -609,4 +615,4 @@ warmstarting warmup webp wsi -wsis +wsis \ No newline at end of file diff --git a/intermediate_source/pinmem_nonblock.py b/intermediate_source/pinmem_nonblock.py index fc035cd161..281ea49556 100644 --- a/intermediate_source/pinmem_nonblock.py +++ b/intermediate_source/pinmem_nonblock.py @@ -10,7 +10,8 @@ - Calling `tensor.pin_memory().to(device, non_blocking=True)` can be as twice as slow as a plain `tensor.to(device)`; - `tensor.to(device, non_blocking=True)` is usually a good choice; -- `cpu_tensor.to("cuda", non_blocking=True).mean()` is ok, but `cuda_tensor.to("cpu", non_blocking=True).mean()` will produce garbage. +- `cpu_tensor.to("cuda", non_blocking=True).mean()` will work, but `cuda_tensor.to("cpu", non_blocking=True).mean()` + will produce garbage. """ @@ -196,7 +197,7 @@ def profile_mem(cmd): ###################################################################### # The results are without any doubt better when using `non_blocking=True`, as all transfers are initiated simultaneously on the host side. -# Note that, interestingly, `to("cuda")` actually performs the same asynchrous device casting operation as the one with `non_blocking=True` with a synchronization point after each copy. +# Note that, interestingly, `to("cuda")` actually performs the same asynchronous device casting operation as the one with `non_blocking=True` with a synchronization point after each copy. # # The benefit will vary depending on the number and the size of the tensors as well as depending on the hardware being used. # @@ -286,9 +287,9 @@ def pin_copy_to_device_nonblocking(*tensors): # ------------------------- # # We can now wrap up some early recommendations based on our observations: -# In general, `non_blocking=True` will provide a good speed of transfer, regardless of whether the original tensor is or isn't in pinned memory. If the tensor is already in pinned memory, the transfer can be accelerated, but sending it to pin memory manually is a blocking operation on the host and hence will anihilate much of the benefit of using `non_blocking=True` (and CUDA does the `pin_memory` transfer anyway). +# In general, `non_blocking=True` will provide a good speed of transfer, regardless of whether the original tensor is or isn't in pinned memory. If the tensor is already in pinned memory, the transfer can be accelerated, but sending it to pin memory manually is a blocking operation on the host and hence will annihilate much of the benefit of using `non_blocking=True` (and CUDA does the `pin_memory` transfer anyway). # -# One might now legitimetely ask what use there is for the `pin_memory()` method within the `torch.Tensor` class. In the following section, we will explore further how this can be used to accelerate the data transfer even more. +# One might now legitimately ask what use there is for the `pin_memory()` method within the `torch.Tensor` class. In the following section, we will explore further how this can be used to accelerate the data transfer even more. # # Additional considerations # ------------------------- @@ -298,7 +299,7 @@ def pin_copy_to_device_nonblocking(*tensors): # # The answer is resides in the fact that the dataloader reserves a separate thread to copy the data from pageable to pinned memory, thereby avoiding to block the main thread with this. Consider the following example, where we send a list of tensors to cuda after calling pin_memory on a separate thread: # -# A more isolated example of this is the TensorDict primitive from the homonymous library: when calling `TensorDict.to(device)`, the default behaviour is to send these tensors to the device asynchronously and make a `device.synchronize()` call after. `TensorDict.to()` also offers a `non_blocking_pin` argument which will spawn multiple threads to do the calls to `pin_memory()` before launching the calls to `to(device)`. +# A more isolated example of this is the TensorDict primitive from the homonymous library: when calling `TensorDict.to(device)`, the default behavior is to send these tensors to the device asynchronously and make a `device.synchronize()` call after. `TensorDict.to()` also offers a `non_blocking_pin` argument which will spawn multiple threads to do the calls to `pin_memory()` before launching the calls to `to(device)`. # This can further speed up the copies as the following example shows: # # .. code-block:: bash From 5f57f5013ba74d683db996539d4c45541f7ea09d Mon Sep 17 00:00:00 2001 From: Vincent Moens Date: Wed, 24 Jul 2024 17:25:37 +0100 Subject: [PATCH 05/54] black --- intermediate_source/pinmem_nonblock.py | 278 +++++++++++++++++-------- 1 file changed, 186 insertions(+), 92 deletions(-) diff --git a/intermediate_source/pinmem_nonblock.py b/intermediate_source/pinmem_nonblock.py index 281ea49556..e7d12e407b 100644 --- a/intermediate_source/pinmem_nonblock.py +++ b/intermediate_source/pinmem_nonblock.py @@ -16,6 +16,7 @@ """ import torch + assert torch.cuda.is_available(), "A cuda device is required to run this tutorial" @@ -27,9 +28,10 @@ # Given this, users should have a good understanding of what tools and options they should be using # when moving data from one device to another. # -# This tutorial focuses on two aspects of device-to-device transfer: `Tensor.pin_memory()` and `Tensor.to(device, non_blocking=True)`. +# This tutorial focuses on two aspects of device-to-device transfer: `Tensor.pin_memory()` and `Tensor.to(device, +# non_blocking=True)`. # We start by outlining the theory surrounding these concepts, and then move to concrete test examples of the features. -# +# # - [Background](#background) # - [Memory management basics](#memory-management-basics) # - [CUDA and (non-)pageable memory](#cuda-and-non-pageable-memory) @@ -43,8 +45,8 @@ # - [Case studies](#case-studies) # - [Conclusion](#conclusion) # - [Additional resources](#additional-resources) -# -# +# +# # Background # ---------- # @@ -58,34 +60,35 @@ # make up the virtual memory, which is an abstraction of the total resources available. # In short, the virtual memory makes it so that the available space is larger than what can be found on RAM in isolation # and creates the illusion that the main memory is larger than it actually is. -# +# # In normal circumstances, a regular CPU tensor is _paged_, which means that it is divided in blocks called _pages_ that # can live anywhere in the virtual memory (both in RAM or on disk). As mentioned earlier, this has the advantage that # the memory seems larger than what the main memory actually is. -# +# # Typically, when a program accesses a page that is not in RAM, a "page fault" occurs and the operating system (OS) then brings # back this page into RAM (_swap in_ or _page in_). # In turn, the OS may have to _swap out_ (or _page out_) another page to make room for the new page. -# -# In contrast to pageable memory, a _pinned_ (or _page-locked_ or _non-pegeable_) memory is a type of memory that cannot be swapped out to disk. +# +# In contrast to pageable memory, a _pinned_ (or _page-locked_ or _non-pegeable_) memory is a type of memory that cannot +# be swapped out to disk. # It allows for faster and more predictable access times, but has the downside that it is more limited than the # pageable memory (aka the main memory). -# +# # .. figure:: /_static/img/pinmem.png # :alt: -# +# # CUDA and (non-)pageable memory # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # # To understand how CUDA copies a tensor from CPU to CUDA, let's consider the two scenarios above: # - If the memory is page-locked, the device can access the memory directly in the main memory. The memory addresses are well # defined and functions that need to read these data can be significantly accelerated. -# - If the memory is pageable, all the pages will have to be brought to the main memory before being sent to the GPU. +# - If the memory is pageable, all the pages will have to be brought to the main memory before being sent to the GPU. # This operation may take time and is less predictable than when executed on page-locked tensors. -# +# # More precisely, when CUDA sends pageable data from CPU to GPU, it must first create a page-locked copy of that data # before making the transfer. -# +# # Asynchronous vs. Synchronous Operations with `non_blocking=True` (CUDA `cudaMemcpyAsync`) # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # @@ -93,14 +96,18 @@ # operations synchronously or asynchronously with respect to the host. In the synchronous case, the call to `cudaMemcpy` # that is queries by `tensor.to(device)` is blocking in the python main thread, which means that the code will stop until # the data has been transferred to the device. -# -# When calling `tensor.to(device)`, PyTorch always makes a call to [`cudaMemcpyAsync`](https://docs.nvidia.com/cuda/cuda-runtime-api/group__CUDART__MEMORY.html#group__CUDART__MEMORY_1g85073372f776b4c4d5f89f7124b7bf79). If `non_blocking=False` (default), a `cudaStreamSynchronize` will be called after each and every `cudaMemcpyAsync`. If `non_blocking=True`, no synchronization is triggered, and the main thread on the host is not blocked. -# Therefore, from the host perspective, multiple tensors can be sent to the device simultaneously in the latter case, as the thread does not need for one transfer to be completed to initiate the other. -# +# +# When calling `tensor.to(device)`, PyTorch always makes a call to +# [`cudaMemcpyAsync`](https://docs.nvidia.com/cuda/cuda-runtime-api/group__CUDART__MEMORY.html#group__CUDART__MEMORY_1g85073372f776b4c4d5f89f7124b7bf79). +# If `non_blocking=False` (default), a `cudaStreamSynchronize` will be called after each and every `cudaMemcpyAsync`. +# If `non_blocking=True`, no synchronization is triggered, and the main thread on the host is not blocked. +# Therefore, from the host perspective, multiple tensors can be sent to the device simultaneously in the latter case, +# as the thread does not need for one transfer to be completed to initiate the other. +# # .. note:: In general, the transfer is blocking on the device size even if it's not on the host side: the copy on the device cannot # occur while another operation is being executed. However, in some advanced scenarios, multiple copies or copy and kernel # executions can be done simultaneously on the GPU side. To enable this, three requirements must be met: -# +# # 1. The device must have at least one free DMA (Direct Memory Access) engine. Modern GPU architectures such as Volterra, # Tesla or H100 devices have more than one DMA engine. # @@ -108,8 +115,8 @@ # `torch.cuda.Stream`. # # 3. The source data must be in pinned memory. -# -# +# +# # A PyTorch perspective # --------------------- # @@ -117,13 +124,13 @@ # ~~~~~~~~~~~~~~ # # PyTorch offers the possibility to create and send tensors to page-locked memory through the `pin_memory` functions and -# arguments. +# arguments. # Any cpu tensor on a machine where a cuda is initialized can be sent to pinned memory through the `pin_memory` # method. Importantly, `pin_memory` is blocking on the host: the main thread will wait for the tensor to be copied to # page-locked memory before executing the next operation. # New tensors can be directly created in pinned memory with functions like `torch.zeros`, `torch.ones` and other # constructors. -# +# # Let us check the speed of pinning memory and sending tensors to cuda: @@ -135,29 +142,44 @@ tensor_pinned = torch.randn(100_000, pin_memory=True) -print("Regular to(device)", - Timer("tensor_pageable.to('cuda:0')", globals=globals()).adaptive_autorange()) -print("Pinned to(device)", - Timer("tensor_pinned.to('cuda:0')", globals=globals()).adaptive_autorange()) -print("pin_memory() along", - Timer("tensor_pageable.pin_memory()", globals=globals()).adaptive_autorange()) -print("pin_memory() + to(device)", - Timer("tensor_pageable.pin_memory().to('cuda:0')", globals=globals()).adaptive_autorange()) +print( + "Regular to(device)", + Timer("tensor_pageable.to('cuda:0')", globals=globals()).adaptive_autorange(), +) +print( + "Pinned to(device)", + Timer("tensor_pinned.to('cuda:0')", globals=globals()).adaptive_autorange(), +) +print( + "pin_memory() along", + Timer("tensor_pageable.pin_memory()", globals=globals()).adaptive_autorange(), +) +print( + "pin_memory() + to(device)", + Timer( + "tensor_pageable.pin_memory().to('cuda:0')", globals=globals() + ).adaptive_autorange(), +) del tensor_pageable, tensor_pinned gc.collect() ###################################################################### -# We can observe that casting a pinned-memory tensor to GPU is indeed much faster than a pageable tensor, because under the hood, a pageable tensor must be copied to pinned memory before being sent to GPU. -# -# However, calling `pin_memory()` on a pageable tensor before casting it to GPU does not bring any speed-up, on the contrary this call is actually slower than just executing the transfer. Again, this makes sense, since we're actually asking python to execute an operation that CUDA will perform anyway before copying the data from host to device. -# +# We can observe that casting a pinned-memory tensor to GPU is indeed much faster than a pageable tensor, because under +# the hood, a pageable tensor must be copied to pinned memory before being sent to GPU. +# +# However, calling `pin_memory()` on a pageable tensor before casting it to GPU does not bring any speed-up, on the +# contrary this call is actually slower than just executing the transfer. Again, this makes sense, since we're actually +# asking python to execute an operation that CUDA will perform anyway before copying the data from host to device. +# # `non_blocking=True` # ~~~~~~~~~~~~~~~~~~~ # -# As mentioned earlier, many PyTorch operations have the option of being executed asynchronously with respect to the host through the `non_blocking` argument. -# Here, to account accurately of the benefits of using `non_blocking`, we will design a slightly more involved experiment since we want to assess how fast it is to send multiple tensors to GPU with and without calling `non_blocking`. -# +# As mentioned earlier, many PyTorch operations have the option of being executed asynchronously with respect to the host +# through the `non_blocking` argument. +# Here, to account accurately of the benefits of using `non_blocking`, we will design a slightly more involved experiment +# since we want to assess how fast it is to send multiple tensors to GPU with and without calling `non_blocking`. +# def copy_to_device(*tensors, display_peak_mem=False): @@ -165,6 +187,8 @@ def copy_to_device(*tensors, display_peak_mem=False): for tensor in tensors: result.append(tensor.to("cuda:0")) return result + + def copy_to_device_nonblocking(*tensors, display_peak_mem=False): result = [] for tensor in tensors: @@ -173,10 +197,18 @@ def copy_to_device_nonblocking(*tensors, display_peak_mem=False): torch.cuda.synchronize() return result + tensors = [torch.randn(1000) for _ in range(1000)] -print("Call to `to(device)`", Timer("copy_to_device(*tensors)", globals=globals()).adaptive_autorange()) -print("Call to `to(device, non_blocking=True)`", Timer("copy_to_device_nonblocking(*tensors)", - globals=globals()).adaptive_autorange()) +print( + "Call to `to(device)`", + Timer("copy_to_device(*tensors)", globals=globals()).adaptive_autorange(), +) +print( + "Call to `to(device, non_blocking=True)`", + Timer( + "copy_to_device_nonblocking(*tensors)", globals=globals() + ).adaptive_autorange(), +) ###################################################################### @@ -185,26 +217,35 @@ def copy_to_device_nonblocking(*tensors, display_peak_mem=False): from torch.profiler import profile, record_function, ProfilerActivity + def profile_mem(cmd): with profile(activities=[ProfilerActivity.CPU]) as prof: exec(cmd) print(cmd) print(prof.key_averages().table(row_limit=10)) + print("Call to `to(device)`", profile_mem("copy_to_device(*tensors)")) -print("Call to `to(device, non_blocking=True)`", profile_mem("copy_to_device_nonblocking(*tensors)")) +print( + "Call to `to(device, non_blocking=True)`", + profile_mem("copy_to_device_nonblocking(*tensors)"), +) ###################################################################### -# The results are without any doubt better when using `non_blocking=True`, as all transfers are initiated simultaneously on the host side. -# Note that, interestingly, `to("cuda")` actually performs the same asynchronous device casting operation as the one with `non_blocking=True` with a synchronization point after each copy. -# +# The results are without any doubt better when using `non_blocking=True`, as all transfers are initiated simultaneously +# on the host side. +# Note that, interestingly, `to("cuda")` actually performs the same asynchronous device casting operation as the one with +# `non_blocking=True` with a synchronization point after each copy. +# # The benefit will vary depending on the number and the size of the tensors as well as depending on the hardware being used. -# +# # Synergies # ~~~~~~~~~ # -# Now that we have made the point that data transfer of tensors already in pinned memory to GPU is faster than from pageable memory, and that we know that doing these transfers asynchronously is also faster than synchronously, we can benchmark the various combinations at hand: +# Now that we have made the point that data transfer of tensors already in pinned memory to GPU is faster than from +# pageable memory, and that we know that doing these transfers asynchronously is also faster than synchronously, we can +# benchmark the various combinations at hand: def pin_copy_to_device(*tensors): @@ -212,6 +253,8 @@ def pin_copy_to_device(*tensors): for tensor in tensors: result.append(tensor.pin_memory().to("cuda:0")) return result + + def pin_copy_to_device_nonblocking(*tensors): result = [] for tensor in tensors: @@ -220,27 +263,43 @@ def pin_copy_to_device_nonblocking(*tensors): torch.cuda.synchronize() return result + print("\nCall to `pin_memory()` + `to(device)`") -print("pin_memory().to(device)", - Timer("pin_copy_to_device(*tensors)", globals=globals()).adaptive_autorange()) -print("pin_memory().to(device, non_blocking=True)", - Timer("pin_copy_to_device_nonblocking(*tensors)", - globals=globals()).adaptive_autorange()) +print( + "pin_memory().to(device)", + Timer("pin_copy_to_device(*tensors)", globals=globals()).adaptive_autorange(), +) +print( + "pin_memory().to(device, non_blocking=True)", + Timer( + "pin_copy_to_device_nonblocking(*tensors)", globals=globals() + ).adaptive_autorange(), +) print("\nCall to `to(device)`") -print("to(device)", - Timer("copy_to_device(*tensors)", globals=globals()).adaptive_autorange()) -print("to(device, non_blocking=True)", - Timer("copy_to_device_nonblocking(*tensors)", - globals=globals()).adaptive_autorange()) +print( + "to(device)", + Timer("copy_to_device(*tensors)", globals=globals()).adaptive_autorange(), +) +print( + "to(device, non_blocking=True)", + Timer( + "copy_to_device_nonblocking(*tensors)", globals=globals() + ).adaptive_autorange(), +) print("\nCall to `to(device)` from pinned tensors") tensors_pinned = [torch.zeros(1000, pin_memory=True) for _ in range(1000)] -print("tensor_pinned.to(device)", - Timer("copy_to_device(*tensors_pinned)", globals=globals()).adaptive_autorange()) -print("tensor_pinned.to(device, non_blocking=True)", - Timer("copy_to_device_nonblocking(*tensors_pinned)", - globals=globals()).adaptive_autorange()) +print( + "tensor_pinned.to(device)", + Timer("copy_to_device(*tensors_pinned)", globals=globals()).adaptive_autorange(), +) +print( + "tensor_pinned.to(device, non_blocking=True)", + Timer( + "copy_to_device_nonblocking(*tensors_pinned)", globals=globals() + ).adaptive_autorange(), +) del tensors, tensors_pinned gc.collect() @@ -251,19 +310,28 @@ def pin_copy_to_device_nonblocking(*tensors): # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # # So far, we have assumed that doing asynchronous copies from CPU to GPU was safe. -# Indeed, it is a safe thing to do because CUDA will synchronize whenever it is needed to make sure that the data being read is not garbage. -# However, any other copy (e.g., from GPU to CPU) has no guarantee whatsoever that the copy will be completed when the data is read. In fact, if no explicit synchronization is done, the data on the host can be garbage: -# - +# Indeed, it is a safe thing to do because CUDA will synchronize whenever it is needed to make sure that the data being +# read is not garbage. +# However, any other copy (e.g., from GPU to CPU) has no guarantee whatsoever that the copy will be completed when the +# data is read. In fact, if no explicit synchronization is done, the data on the host can be garbage: +# -tensor = torch.arange(1, 1_000_000, dtype=torch.double, device="cuda").expand(100, 999999).clone() -torch.testing.assert_close(tensor.mean(), torch.tensor(500_000, dtype=torch.double, device="cuda")), tensor.mean() +tensor = ( + torch.arange(1, 1_000_000, dtype=torch.double, device="cuda") + .expand(100, 999999) + .clone() +) +torch.testing.assert_close( + tensor.mean(), torch.tensor(500_000, dtype=torch.double, device="cuda") +), tensor.mean() try: i = -1 for i in range(100): cpu_tensor = tensor.to("cpu", non_blocking=True) - torch.testing.assert_close(cpu_tensor.mean(), torch.tensor(500_000, dtype=torch.double)) + torch.testing.assert_close( + cpu_tensor.mean(), torch.tensor(500_000, dtype=torch.double) + ) print("No test failed with non_blocking") except AssertionError: print(f"One test failed with non_blocking: {i}th assertion!") @@ -272,7 +340,9 @@ def pin_copy_to_device_nonblocking(*tensors): for i in range(100): cpu_tensor = tensor.to("cpu", non_blocking=True) torch.cuda.synchronize() - torch.testing.assert_close(cpu_tensor.mean(), torch.tensor(500_000, dtype=torch.double)) + torch.testing.assert_close( + cpu_tensor.mean(), torch.tensor(500_000, dtype=torch.double) + ) print("No test failed with synchronize") except AssertionError: print(f"One test failed with synchronize: {i}th assertion!") @@ -280,26 +350,38 @@ def pin_copy_to_device_nonblocking(*tensors): ###################################################################### # The same observation could be made with copies from CPU to a non-CUDA device such as MPS. -# -# In summary, copying data from CPU to GPU is safe when using `non_blocking=True`, but for any other direction, `non_blocking=True` can still be used but the user must make sure that a device synchronization is executed after the data is accessed. -# +# +# In summary, copying data from CPU to GPU is safe when using `non_blocking=True`, but for any other direction, +# `non_blocking=True` can still be used but the user must make sure that a device synchronization is executed after +# the data is accessed. +# # Practical recommendations # ------------------------- # # We can now wrap up some early recommendations based on our observations: -# In general, `non_blocking=True` will provide a good speed of transfer, regardless of whether the original tensor is or isn't in pinned memory. If the tensor is already in pinned memory, the transfer can be accelerated, but sending it to pin memory manually is a blocking operation on the host and hence will annihilate much of the benefit of using `non_blocking=True` (and CUDA does the `pin_memory` transfer anyway). -# -# One might now legitimately ask what use there is for the `pin_memory()` method within the `torch.Tensor` class. In the following section, we will explore further how this can be used to accelerate the data transfer even more. -# +# In general, `non_blocking=True` will provide a good speed of transfer, regardless of whether the original tensor is or +# isn't in pinned memory. If the tensor is already in pinned memory, the transfer can be accelerated, but sending it to +# pin memory manually is a blocking operation on the host and hence will annihilate much of the benefit of using +# `non_blocking=True` (and CUDA does the `pin_memory` transfer anyway). +# +# One might now legitimately ask what use there is for the `pin_memory()` method within the `torch.Tensor` class. In the +# following section, we will explore further how this can be used to accelerate the data transfer even more. +# # Additional considerations # ------------------------- # # PyTorch notoriously provides a `DataLoader` class that accepts a `pin_memory` argument. -# Given everything we have said so far about calls to `pin_memory`, how does the dataloader manage to accelerate data transfers? -# -# The answer is resides in the fact that the dataloader reserves a separate thread to copy the data from pageable to pinned memory, thereby avoiding to block the main thread with this. Consider the following example, where we send a list of tensors to cuda after calling pin_memory on a separate thread: -# -# A more isolated example of this is the TensorDict primitive from the homonymous library: when calling `TensorDict.to(device)`, the default behavior is to send these tensors to the device asynchronously and make a `device.synchronize()` call after. `TensorDict.to()` also offers a `non_blocking_pin` argument which will spawn multiple threads to do the calls to `pin_memory()` before launching the calls to `to(device)`. +# Given everything we have said so far about calls to `pin_memory`, how does the dataloader manage to accelerate data +# transfers? +# +# The answer is resides in the fact that the dataloader reserves a separate thread to copy the data from pageable to +# pinned memory, thereby avoiding to block the main thread with this. Consider the following example, where we send a list of +# tensors to cuda after calling pin_memory on a separate thread: +# +# A more isolated example of this is the TensorDict primitive from the homonymous library: when calling `TensorDict.to(device)`, +# the default behavior is to send these tensors to the device asynchronously and make a `device.synchronize()` call after. +# `TensorDict.to()` also offers a `non_blocking_pin` argument which will spawn multiple threads to do the calls to `pin_memory()` +# before launching the calls to `to(device)`. # This can further speed up the copies as the following example shows: # # .. code-block:: bash @@ -313,23 +395,35 @@ def pin_copy_to_device_nonblocking(*tensors): td = TensorDict({str(i): torch.randn(1_000_000) for i in range(100)}) -print(Timer("td.to('cuda:0', non_blocking=False)", globals=globals()).adaptive_autorange()) +print( + Timer("td.to('cuda:0', non_blocking=False)", globals=globals()).adaptive_autorange() +) print(Timer("td.to('cuda:0')", globals=globals()).adaptive_autorange()) -print(Timer("td.to('cuda:0', non_blocking=True, non_blocking_pin=True)", globals=globals()).adaptive_autorange()) +print( + Timer( + "td.to('cuda:0', non_blocking=True, non_blocking_pin=True)", globals=globals() + ).adaptive_autorange() +) ###################################################################### -# As a side note, it may be tempting to create everlasting buffers in pinned memory and copy tensors from pageable memory to pinned memory, and use these as shuttle before sending the data to GPU. +# As a side note, it may be tempting to create everlasting buffers in pinned memory and copy tensors from pageable memory +# to pinned memory, and use these as shuttle before sending the data to GPU. # Unfortunately, this does not speed up computation because the bottleneck of copying data to pinned memory is still present. -# -# Another consideration is that transferring data that is stored on disk (shared memory or files) to GPU will usually require the data to be copied to pinned memory (which is on RAM) as an intermediate step. -# -# Using `non_blocking` in these context for large amount of data may have devastating effects on RAM consumption. In practice, there is no silver bullet, and the performance of any combination of multithreaded pin_memory and non_blocking will depend on multiple factors such as the system being used, the OS, the hardware and the tasks being performed. -# -# Finally, creating a large number of tensors or a few large tensors in pinned memory will effectively reserve more RAM than pageable tensors would, thereby lowering the amount of available RAM for other operations (such as swapping pages in and out), which can have a negative impact over the overall runtime of an algorithm. +# +# Another consideration is that transferring data that is stored on disk (shared memory or files) to GPU will usually +# require the data to be copied to pinned memory (which is on RAM) as an intermediate step. +# +# Using `non_blocking` in these context for large amount of data may have devastating effects on RAM consumption. +# In practice, there is no silver bullet, and the performance of any combination of multithreaded pin_memory and +# non_blocking will depend on multiple factors such as the system being used, the OS, the hardware and the tasks being performed. +# +# Finally, creating a large number of tensors or a few large tensors in pinned memory will effectively reserve more RAM +# than pageable tensors would, thereby lowering the amount of available RAM for other operations (such as swapping pages +# in and out), which can have a negative impact over the overall runtime of an algorithm. ###################################################################### # ## Conclusion -# +# # ## Additional resources -# +# From 67f4d1a9c31dd1908edce21cf482f3e360f85497 Mon Sep 17 00:00:00 2001 From: Vincent Moens Date: Wed, 24 Jul 2024 17:30:05 +0100 Subject: [PATCH 06/54] pegeable -> pageable --- intermediate_source/pinmem_nonblock.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/intermediate_source/pinmem_nonblock.py b/intermediate_source/pinmem_nonblock.py index e7d12e407b..41cdb6b505 100644 --- a/intermediate_source/pinmem_nonblock.py +++ b/intermediate_source/pinmem_nonblock.py @@ -69,7 +69,7 @@ # back this page into RAM (_swap in_ or _page in_). # In turn, the OS may have to _swap out_ (or _page out_) another page to make room for the new page. # -# In contrast to pageable memory, a _pinned_ (or _page-locked_ or _non-pegeable_) memory is a type of memory that cannot +# In contrast to pageable memory, a _pinned_ (or _page-locked_ or _non-pageable_) memory is a type of memory that cannot # be swapped out to disk. # It allows for faster and more predictable access times, but has the downside that it is more limited than the # pageable memory (aka the main memory). From 07456f9ef45d60d68665c021828e974903da6888 Mon Sep 17 00:00:00 2001 From: Vincent Moens Date: Wed, 24 Jul 2024 17:43:02 +0100 Subject: [PATCH 07/54] update requirements --- .ci/docker/requirements.txt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.ci/docker/requirements.txt b/.ci/docker/requirements.txt index 7aede51dda..710dbed386 100644 --- a/.ci/docker/requirements.txt +++ b/.ci/docker/requirements.txt @@ -28,8 +28,9 @@ tensorboard jinja2==3.1.3 pytorch-lightning torchx -torchrl==0.3.0 -tensordict==0.3.0 +# TODO: use stable 0.5 when released +-e git+https://github.com/pytorch/torchrl.git +-e git+https://github.com/pytorch/tensordict.git ax-platform nbformat>==5.9.2 datasets From ffdcef9e754597028640c1bd34bbb80e54efb299 Mon Sep 17 00:00:00 2001 From: Vincent Moens Date: Wed, 24 Jul 2024 17:43:17 +0100 Subject: [PATCH 08/54] update requirements --- .ci/docker/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci/docker/requirements.txt b/.ci/docker/requirements.txt index 710dbed386..c4c419f4a8 100644 --- a/.ci/docker/requirements.txt +++ b/.ci/docker/requirements.txt @@ -29,7 +29,7 @@ jinja2==3.1.3 pytorch-lightning torchx # TODO: use stable 0.5 when released --e git+https://github.com/pytorch/torchrl.git +-e git+https://github.com/pytorch/rl.git -e git+https://github.com/pytorch/tensordict.git ax-platform nbformat>==5.9.2 From f6ba5687766fc11fb55a3d578f1c13e5e9f48a94 Mon Sep 17 00:00:00 2001 From: Vincent Moens Date: Wed, 24 Jul 2024 18:25:22 +0100 Subject: [PATCH 09/54] update requirements --- intermediate_source/pinmem_nonblock.rst | 601 ++++++++++++++++++++++++ 1 file changed, 601 insertions(+) create mode 100644 intermediate_source/pinmem_nonblock.rst diff --git a/intermediate_source/pinmem_nonblock.rst b/intermediate_source/pinmem_nonblock.rst new file mode 100644 index 0000000000..0dbc340d23 --- /dev/null +++ b/intermediate_source/pinmem_nonblock.rst @@ -0,0 +1,601 @@ +A guide on good usage of ``non_blocking`` and ``pin_memory()`` in PyTorch +========================================================================= + +TL;DR +----- + +Sending tensors from CPU to GPU can be made faster by using asynchronous +transfer and memory pinning, but: + +- Calling ``tensor.pin_memory().to(device, non_blocking=True)`` can be + as twice as slow as a plain ``tensor.to(device)``; +- ``tensor.to(device, non_blocking=True)`` is usually a good choice; +- ``cpu_tensor.to("cuda", non_blocking=True).mean()`` is ok, but + ``cuda_tensor.to("cpu", non_blocking=True).mean()`` will produce + garbage. + +.. code:: ipython3 + + import torch + assert torch.cuda.is_available(), "A cuda device is required to run this tutorial" + +Introduction +------------ + +Sending data from CPU to GPU is a cornerstone of many applications that +use PyTorch. Given this, users should have a good understanding of what +tools and options they should be using when moving data from one device +to another. This tutorial focuses on two aspects of device-to-device +transfer: ``Tensor.pin_memory()`` and +``Tensor.to(device, non_blocking=True)``. We start by outlining the +theory surrounding these concepts, and then move to concrete test +examples of the features. + +- `Background <#background>`__ + + - `Memory management basics <#memory-management-basics>`__ + - `CUDA and (non-)pageable memory <#cuda-and-non-pageable-memory>`__ + - `Asynchronous vs synchronous + operations <#asynchronous-vs-synchronous-operations>`__ + +- `Deep dive <#deep-dive>`__ + + - ```pin_memory()`` <#pin_memory>`__ + - ```non_blocking=True`` <#non_blockingtrue>`__ + - `Synergies <#synergies>`__ + - `Other directions (GPU -> CPU etc.) <#other-directions>`__ + +- `Practical recommendations <#practical-recommendations>`__ +- `Case studies <#case-studies>`__ +- `Conclusion <#conclusion>`__ +- `Additional resources <#additional-resources>`__ + +Background +---------- + +Memory management basics +~~~~~~~~~~~~~~~~~~~~~~~~ + +When one creates a CPU tensor in PyTorch, the content of this tensor +needs to be placed in memory. The memory we talk about here is a rather +complex concept worth looking at carefully. We distinguish two types of +memories that are handled by the Memory Management Unit: the main memory +(for simplicity) and the disk (which may or may not be the hard drive). +Together, the available space in disk and RAM (physical memory) make up +the virtual memory, which is an abstraction of the total resources +available. In short, the virtual memory makes it so that the available +space is larger than what can be found on RAM in isolation and creates +the illusion that the main memory is larger than it actually is. + +In normal circumstances, a regular CPU tensor is *paged*, which means +that it is divided in blocks called *pages* that can live anywhere in +the virtual memory (both in RAM or on disk). As mentioned earlier, this +has the advantage that the memory seems larger than what the main memory +actually is. + +Typically, when a program accesses a page that is not in RAM, a “page +fault” occurs and the operating system (OS) then brings back this page +into RAM (*swap in* or *page in*). In turn, the OS may have to *swap +out* (or *page out*) another page to make room for the new page. + +In contrast to pageable memory, a *pinned* (or *page-locked* or +*non-pegeable*) memory is a type of memory that cannot be swapped out to +disk. It allows for faster and more predictable access times, but has +the downside that it is more limited than the pageable memory (aka the +main memory). + +.. figure:: /_static/img/pinmem.png + :alt: + +CUDA and (non-)pageable memory +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +To understand how CUDA copies a tensor from CPU to CUDA, let’s consider +the two scenarios above: - If the memory is page-locked, the device can +access the memory directly in the main memory. The memory addresses are +well defined and functions that need to read these data can be +significantly accelerated. - If the memory is pageable, all the pages +will have to be brought to the main memory before being sent to the GPU. +This operation may take time and is less predictable than when executed +on page-locked tensors. + +More precisely, when CUDA sends pageable data from CPU to GPU, it must +first create a page-locked copy of that data before making the transfer. + +Asynchronous vs. Synchronous Operations with ``non_blocking=True`` (CUDA ``cudaMemcpyAsync``) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +When executing a copy from a host (e.g., CPU) to a device (e.g., GPU), +the CUDA toolkit offers modalities to do these operations synchronously +or asynchronously with respect to the host. In the synchronous case, the +call to ``cudaMemcpy`` that is queries by ``tensor.to(device)`` is +blocking in the python main thread, which means that the code will stop +until the data has been transferred to the device. + +When calling ``tensor.to(device)``, PyTorch always makes a call to +```cudaMemcpyAsync`` `__. +If ``non_blocking=False`` (default), a ``cudaStreamSynchronize`` will be +called after each and every ``cudaMemcpyAsync``. If +``non_blocking=True``, no synchronization is triggered, and the main +thread on the host is not blocked. Therefore, from the host perspective, +multiple tensors can be sent to the device simultaneously in the latter +case, as the thread does not need for one transfer to be completed to +initiate the other. + +Note +^^^^ + +In general, the transfer is blocking on the device size even if it’s not +on the host side: the copy on the device cannot occur while another +operation is being executed. However, in some advanced scenarios, +multiple copies or copy and kernel executions can be done simultaneously +on the GPU side. To enable this, three requirements must be met: + +1. The device must have at least one free DMA (Direct Memory Access) + engine. Modern GPU architectures such as Volterra, Tesla or H100 + devices have more than one DMA engine. +2. The transfer must be done on a separate, non-default cuda stream. In + PyTorch, cuda streams can be handles using ``torch.cuda.Stream``. +3. The source data must be in pinned memory. + +A PyTorch perspective +--------------------- + +``pin_memory()`` +~~~~~~~~~~~~~~~~ + +PyTorch offers the possibility to create and send tensors to page-locked +memory through the ``pin_memory`` functions and arguments. Any cpu +tensor on a machine where a cuda is initialized can be sent to pinned +memory through the ``pin_memory`` method. Importantly, ``pin_memory`` is +blocking on the host: the main thread will wait for the tensor to be +copied to page-locked memory before executing the next operation. New +tensors can be directly created in pinned memory with functions like +``torch.zeros``, ``torch.ones`` and other constructors. + +Let us check the speed of pinning memory and sending tensors to cuda: + +.. code:: ipython3 + + import torch + import gc + from torch.utils.benchmark import Timer + + tensor_pageable = torch.randn(100_000) + + tensor_pinned = torch.randn(100_000, pin_memory=True) + + print("Regular to(device)", + Timer("tensor_pageable.to('cuda:0')", globals=globals()).adaptive_autorange()) + print("Pinned to(device)", + Timer("tensor_pinned.to('cuda:0')", globals=globals()).adaptive_autorange()) + print("pin_memory() along", + Timer("tensor_pageable.pin_memory()", globals=globals()).adaptive_autorange()) + print("pin_memory() + to(device)", + Timer("tensor_pageable.pin_memory().to('cuda:0')", globals=globals()).adaptive_autorange()) + del tensor_pageable, tensor_pinned + gc.collect() + + + +.. parsed-literal:: + + Regular to(device) + tensor_pageable.to('cuda:0') + Median: 35.26 us + IQR: 0.04 us (35.23 to 35.28) + 4 measurements, 10000 runs per measurement, 1 thread + Pinned to(device) + tensor_pinned.to('cuda:0') + Median: 19.70 us + IQR: 0.03 us (19.69 to 19.72) + 4 measurements, 10000 runs per measurement, 1 thread + pin_memory() along + tensor_pageable.pin_memory() + Median: 11.82 us + IQR: 0.03 us (11.80 to 11.83) + 4 measurements, 10000 runs per measurement, 1 thread + pin_memory() + to(device) + tensor_pageable.pin_memory().to('cuda:0') + Median: 40.84 us + IQR: 0.14 us (40.78 to 40.93) + 4 measurements, 10000 runs per measurement, 1 thread + + + + +.. parsed-literal:: + + 12 + + + +We can observe that casting a pinned-memory tensor to GPU is indeed much +faster than a pageable tensor, because under the hood, a pageable tensor +must be copied to pinned memory before being sent to GPU. + +However, calling ``pin_memory()`` on a pageable tensor before casting it +to GPU does not bring any speed-up, on the contrary this call is +actually slower than just executing the transfer. Again, this makes +sense, since we’re actually asking python to execute an operation that +CUDA will perform anyway before copying the data from host to device. + +``non_blocking=True`` +~~~~~~~~~~~~~~~~~~~~~ + +As mentioned earlier, many PyTorch operations have the option of being +executed asynchronously with respect to the host through the +``non_blocking`` argument. Here, to account accurately of the benefits +of using ``non_blocking``, we will design a slightly more involved +experiment since we want to assess how fast it is to send multiple +tensors to GPU with and without calling ``non_blocking``. + +.. code:: ipython3 + + def copy_to_device(*tensors, display_peak_mem=False): + result = [] + for tensor in tensors: + result.append(tensor.to("cuda:0")) + return result + def copy_to_device_nonblocking(*tensors, display_peak_mem=False): + result = [] + for tensor in tensors: + result.append(tensor.to("cuda:0", non_blocking=True)) + # We need to synchronize + torch.cuda.synchronize() + return result + + tensors = [torch.randn(1000) for _ in range(1000)] + print("Call to `to(device)`", Timer("copy_to_device(*tensors)", globals=globals()).adaptive_autorange()) + print("Call to `to(device, non_blocking=True)`", Timer("copy_to_device_nonblocking(*tensors)", + globals=globals()).adaptive_autorange()) + + +.. parsed-literal:: + + Call to `to(device)` + copy_to_device(*tensors) + Median: 11.03 ms + IQR: 0.09 ms (10.98 to 11.07) + 4 measurements, 10 runs per measurement, 1 thread + Call to `to(device, non_blocking=True)` + copy_to_device_nonblocking(*tensors) + Median: 5.88 ms + IQR: 0.38 ms (5.82 to 6.20) + 4 measurements, 10 runs per measurement, 1 thread + + +To get a better sense of what is happening here, let us run a profiling +of these two code executions: + +.. code:: ipython3 + + from torch.profiler import profile, record_function, ProfilerActivity + + def profile_mem(cmd): + with profile(activities=[ProfilerActivity.CPU]) as prof: + exec(cmd) + print(cmd) + print(prof.key_averages().table(row_limit=10)) + + print("Call to `to(device)`", profile_mem("copy_to_device(*tensors)")) + print("Call to `to(device, non_blocking=True)`", profile_mem("copy_to_device_nonblocking(*tensors)")) + + +.. parsed-literal:: + + copy_to_device(*tensors) + + +.. parsed-literal:: + + STAGE:2024-07-24 08:29:14 2357923:2357923 ActivityProfilerController.cpp:314] Completed Stage: Warm Up + STAGE:2024-07-24 08:29:14 2357923:2357923 ActivityProfilerController.cpp:320] Completed Stage: Collection + STAGE:2024-07-24 08:29:14 2357923:2357923 ActivityProfilerController.cpp:324] Completed Stage: Post Processing + + +.. parsed-literal:: + + ------------------------- ------------ ------------ ------------ ------------ ------------ ------------ + Name Self CPU % Self CPU CPU total % CPU total CPU time avg # of Calls + ------------------------- ------------ ------------ ------------ ------------ ------------ ------------ + aten::to 11.39% 2.118ms 88.36% 16.432ms 16.432us 1000 + aten::_to_copy 11.87% 2.208ms 86.48% 16.083ms 16.083us 1000 + aten::empty_strided 26.90% 5.002ms 26.90% 5.002ms 5.002us 1000 + aten::copy_ 16.27% 3.026ms 49.84% 9.269ms 9.269us 1000 + cudaMemcpyAsync 11.25% 2.092ms 11.25% 2.092ms 2.092us 1000 + cudaStreamSynchronize 22.32% 4.151ms 22.32% 4.151ms 4.151us 1000 + ------------------------- ------------ ------------ ------------ ------------ ------------ ------------ + Self CPU time total: 18.597ms + + Call to `to(device)` None + + +.. parsed-literal:: + + STAGE:2024-07-24 08:29:14 2357923:2357923 ActivityProfilerController.cpp:314] Completed Stage: Warm Up + STAGE:2024-07-24 08:29:14 2357923:2357923 ActivityProfilerController.cpp:320] Completed Stage: Collection + STAGE:2024-07-24 08:29:14 2357923:2357923 ActivityProfilerController.cpp:324] Completed Stage: Post Processing + + +.. parsed-literal:: + + copy_to_device_nonblocking(*tensors) + ------------------------- ------------ ------------ ------------ ------------ ------------ ------------ + Name Self CPU % Self CPU CPU total % CPU total CPU time avg # of Calls + ------------------------- ------------ ------------ ------------ ------------ ------------ ------------ + aten::to 12.48% 1.621ms 88.23% 11.457ms 11.457us 1000 + aten::_to_copy 15.72% 2.042ms 85.95% 11.162ms 11.162us 1000 + aten::empty_strided 35.68% 4.633ms 35.68% 4.633ms 4.633us 1000 + aten::copy_ 16.69% 2.167ms 35.85% 4.655ms 4.655us 1000 + cudaMemcpyAsync 19.30% 2.506ms 19.30% 2.506ms 2.506us 1000 + cudaDeviceSynchronize 0.13% 17.000us 0.13% 17.000us 17.000us 1 + ------------------------- ------------ ------------ ------------ ------------ ------------ ------------ + Self CPU time total: 12.986ms + + Call to `to(device, non_blocking=True)` None + + +The results are without any doubt better when using +``non_blocking=True``, as all transfers are initiated simultaneously on +the host side. Note that, interestingly, ``to("cuda")`` actually +performs the same asynchrous device casting operation as the one with +``non_blocking=True`` with a synchronization point after each copy. + +The benefit will vary depending on the number and the size of the +tensors as well as depending on the hardware being used. + +Synergies +~~~~~~~~~ + +Now that we have made the point that data transfer of tensors already in +pinned memory to GPU is faster than from pageable memory, and that we +know that doing these transfers asynchronously is also faster than +synchronously, we can benchmark the various combinations at hand: + +.. code:: ipython3 + + + def pin_copy_to_device(*tensors): + result = [] + for tensor in tensors: + result.append(tensor.pin_memory().to("cuda:0")) + return result + def pin_copy_to_device_nonblocking(*tensors): + result = [] + for tensor in tensors: + result.append(tensor.pin_memory().to("cuda:0", non_blocking=True)) + # We need to synchronize + torch.cuda.synchronize() + return result + + print("\nCall to `pin_memory()` + `to(device)`") + print("pin_memory().to(device)", + Timer("pin_copy_to_device(*tensors)", globals=globals()).adaptive_autorange()) + print("pin_memory().to(device, non_blocking=True)", + Timer("pin_copy_to_device_nonblocking(*tensors)", + globals=globals()).adaptive_autorange()) + + print("\nCall to `to(device)`") + print("to(device)", + Timer("copy_to_device(*tensors)", globals=globals()).adaptive_autorange()) + print("to(device, non_blocking=True)", + Timer("copy_to_device_nonblocking(*tensors)", + globals=globals()).adaptive_autorange()) + + print("\nCall to `to(device)` from pinned tensors") + tensors_pinned = [torch.zeros(1000, pin_memory=True) for _ in range(1000)] + print("tensor_pinned.to(device)", + Timer("copy_to_device(*tensors_pinned)", globals=globals()).adaptive_autorange()) + print("tensor_pinned.to(device, non_blocking=True)", + Timer("copy_to_device_nonblocking(*tensors_pinned)", + globals=globals()).adaptive_autorange()) + + del tensors, tensors_pinned + gc.collect() + + + +.. parsed-literal:: + + + Call to `pin_memory()` + `to(device)` + pin_memory().to(device) + pin_copy_to_device(*tensors) + Median: 17.18 ms + IQR: 0.04 ms (17.16 to 17.20) + 4 measurements, 10 runs per measurement, 1 thread + pin_memory().to(device, non_blocking=True) + pin_copy_to_device_nonblocking(*tensors) + Median: 15.42 ms + IQR: 0.08 ms (15.38 to 15.47) + 4 measurements, 10 runs per measurement, 1 thread + + Call to `to(device)` + to(device) + copy_to_device(*tensors) + Median: 13.15 ms + IQR: 0.06 ms (13.13 to 13.20) + 4 measurements, 10 runs per measurement, 1 thread + to(device, non_blocking=True) + copy_to_device_nonblocking(*tensors) + Median: 8.26 ms + IQR: 0.05 ms (8.23 to 8.27) + 4 measurements, 10 runs per measurement, 1 thread + + Call to `to(device)` from pinned tensors + tensor_pinned.to(device) + copy_to_device(*tensors_pinned) + Median: 13.28 ms + IQR: 0.35 ms (13.13 to 13.48) + 4 measurements, 10 runs per measurement, 1 thread + tensor_pinned.to(device, non_blocking=True) + copy_to_device_nonblocking(*tensors_pinned) + Median: 8.16 ms + IQR: 0.08 ms (8.15 to 8.23) + 4 measurements, 10 runs per measurement, 1 thread + + + + +.. parsed-literal:: + + 40087 + + + +Other directions (GPU -> CPU, CPU -> MPS etc.) +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +So far, we have assumed that doing asynchronous copies from CPU to GPU +was safe. Indeed, it is a safe thing to do because CUDA will synchronize +whenever it is needed to make sure that the data being read is not +garbage. However, any other copy (e.g., from GPU to CPU) has no +guarantee whatsoever that the copy will be completed when the data is +read. In fact, if no explicit synchronization is done, the data on the +host can be garbage: + +.. code:: ipython3 + + tensor = torch.arange(1, 1_000_000, dtype=torch.double, device="cuda").expand(100, 999999).clone() + torch.testing.assert_close(tensor.mean(), torch.tensor(500_000, dtype=torch.double, device="cuda")), tensor.mean() + try: + i = -1 + for i in range(100): + cpu_tensor = tensor.to("cpu", non_blocking=True) + torch.testing.assert_close(cpu_tensor.mean(), torch.tensor(500_000, dtype=torch.double)) + print("No test failed with non_blocking") + except AssertionError: + print(f"One test failed with non_blocking: {i}th assertion!") + try: + i = -1 + for i in range(100): + cpu_tensor = tensor.to("cpu", non_blocking=True) + torch.cuda.synchronize() + torch.testing.assert_close(cpu_tensor.mean(), torch.tensor(500_000, dtype=torch.double)) + print("No test failed with synchronize") + except AssertionError: + print(f"One test failed with synchronize: {i}th assertion!") + + + +.. parsed-literal:: + + One test failed with non_blocking: 0th assertion! + No test failed with synchronize + + +The same observation could be made with copies from CPU to a non-CUDA +device such as MPS. + +In summary, copying data from CPU to GPU is safe when using +``non_blocking=True``, but for any other direction, +``non_blocking=True`` can still be used but the user must make sure that +a device synchronization is executed after the data is accessed. + +Practical recommendations +------------------------- + +We can now wrap up some early recommendations based on our observations: +In general, ``non_blocking=True`` will provide a good speed of transfer, +regardless of whether the original tensor is or isn’t in pinned memory. +If the tensor is already in pinned memory, the transfer can be +accelerated, but sending it to pin memory manually is a blocking +operation on the host and hence will anihilate much of the benefit of +using ``non_blocking=True`` (and CUDA does the ``pin_memory`` transfer +anyway). + +One might now legitimetely ask what use there is for the +``pin_memory()`` method within the ``torch.Tensor`` class. In the +following section, we will explore further how this can be used to +accelerate the data transfer even more. + +Additional considerations +------------------------- + +PyTorch notoriously provides a ``DataLoader`` class that accepts a +``pin_memory`` argument. Given everything we have said so far about +calls to ``pin_memory``, how does the dataloader manage to accelerate +data transfers? + +The answer is resides in the fact that the dataloader reserves a +separate thread to copy the data from pageable to pinned memory, thereby +avoiding to block the main thread with this. Consider the following +example, where we send a list of tensors to cuda after calling +pin_memory on a separate thread: + +A more isolated example of this is the TensorDict primitive from the +homonymous library: when calling ``TensorDict.to(device)``, the default +behaviour is to send these tensors to the device asynchronously and make +a ``device.synchronize()`` call after. ``TensorDict.to()`` also offers a +``non_blocking_pin`` argument which will spawn multiple threads to do +the calls to ``pin_memory()`` before launching the calls to +``to(device)``. This can further speed up the copies as the following +example shows: + +.. code:: ipython3 + + from tensordict import TensorDict + import torch + from torch.utils.benchmark import Timer + + td = TensorDict({str(i): torch.randn(1_000_000) for i in range(100)}) + + print(Timer("td.to('cuda:0', non_blocking=False)", globals=globals()).adaptive_autorange()) + print(Timer("td.to('cuda:0')", globals=globals()).adaptive_autorange()) + print(Timer("td.to('cuda:0', non_blocking=True, non_blocking_pin=True)", globals=globals()).adaptive_autorange()) + + +.. parsed-literal:: + + /home/vmoens/.conda/envs/torchrl/lib/python3.10/site-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html + from .autonotebook import tqdm as notebook_tqdm + + +.. parsed-literal:: + + + td.to('cuda:0', non_blocking=False) + Median: 35.55 ms + IQR: 0.38 ms (35.43 to 35.81) + 4 measurements, 10 runs per measurement, 1 thread + + td.to('cuda:0') + Median: 32.59 ms + IQR: 0.10 ms (32.55 to 32.65) + 4 measurements, 10 runs per measurement, 1 thread + + td.to('cuda:0', non_blocking=True, non_blocking_pin=True) + Median: 23.63 ms + IQR: 0.39 ms (23.45 to 23.84) + 4 measurements, 1 runs per measurement, 1 thread + + +As a side note, it may be tempting to create everlasting buffers in +pinned memory and copy tensors from pageable memory to pinned memory, +and use these as shuttle before sending the data to GPU. Unfortunately, +this does not speed up computation because the bottleneck of copying +data to pinned memory is still present. + +Another consideration is that transferring data that is stored on disk +(shared memory or files) to GPU will usually require the data to be +copied to pinned memory (which is on RAM) as an intermediate step. + +Using ``non_blocking`` in these context for large amount of data may +have devastating effects on RAM consumption. In practice, there is no +silver bullet, and the performance of any combination of multithreaded +pin_memory and non_blocking will depend on multiple factors such as the +system being used, the OS, the hardware and the tasks being performed. + +Finally, creating a large number of tensors or a few large tensors in +pinned memory will effectively reserve more RAM than pageable tensors +would, thereby lowering the amount of available RAM for other operations +(such as swapping pages in and out), which can have a negative impact +over the overall runtime of an algorithm. + +Conclusion +---------- + +Additional resources +-------------------- + From 85d07be8c4195be8a16ee1150dcf1ee5fb80fcd6 Mon Sep 17 00:00:00 2001 From: Vincent Moens Date: Wed, 24 Jul 2024 18:45:36 +0100 Subject: [PATCH 10/54] update requirements --- .ci/docker/requirements.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.ci/docker/requirements.txt b/.ci/docker/requirements.txt index c4c419f4a8..9dfdf6bb93 100644 --- a/.ci/docker/requirements.txt +++ b/.ci/docker/requirements.txt @@ -29,8 +29,8 @@ jinja2==3.1.3 pytorch-lightning torchx # TODO: use stable 0.5 when released --e git+https://github.com/pytorch/rl.git --e git+https://github.com/pytorch/tensordict.git +-e git+https://github.com/pytorch/rl.git#egg=torchrl +-e git+https://github.com/pytorch/tensordict.git#egg=tensordict ax-platform nbformat>==5.9.2 datasets From 9b4640a773aa43c971eb5eb9d5d93b82a279e185 Mon Sep 17 00:00:00 2001 From: Vincent Moens Date: Wed, 24 Jul 2024 18:47:26 +0100 Subject: [PATCH 11/54] amend --- intermediate_source/pinmem_nonblock.rst | 601 ------------------------ 1 file changed, 601 deletions(-) delete mode 100644 intermediate_source/pinmem_nonblock.rst diff --git a/intermediate_source/pinmem_nonblock.rst b/intermediate_source/pinmem_nonblock.rst deleted file mode 100644 index 0dbc340d23..0000000000 --- a/intermediate_source/pinmem_nonblock.rst +++ /dev/null @@ -1,601 +0,0 @@ -A guide on good usage of ``non_blocking`` and ``pin_memory()`` in PyTorch -========================================================================= - -TL;DR ------ - -Sending tensors from CPU to GPU can be made faster by using asynchronous -transfer and memory pinning, but: - -- Calling ``tensor.pin_memory().to(device, non_blocking=True)`` can be - as twice as slow as a plain ``tensor.to(device)``; -- ``tensor.to(device, non_blocking=True)`` is usually a good choice; -- ``cpu_tensor.to("cuda", non_blocking=True).mean()`` is ok, but - ``cuda_tensor.to("cpu", non_blocking=True).mean()`` will produce - garbage. - -.. code:: ipython3 - - import torch - assert torch.cuda.is_available(), "A cuda device is required to run this tutorial" - -Introduction ------------- - -Sending data from CPU to GPU is a cornerstone of many applications that -use PyTorch. Given this, users should have a good understanding of what -tools and options they should be using when moving data from one device -to another. This tutorial focuses on two aspects of device-to-device -transfer: ``Tensor.pin_memory()`` and -``Tensor.to(device, non_blocking=True)``. We start by outlining the -theory surrounding these concepts, and then move to concrete test -examples of the features. - -- `Background <#background>`__ - - - `Memory management basics <#memory-management-basics>`__ - - `CUDA and (non-)pageable memory <#cuda-and-non-pageable-memory>`__ - - `Asynchronous vs synchronous - operations <#asynchronous-vs-synchronous-operations>`__ - -- `Deep dive <#deep-dive>`__ - - - ```pin_memory()`` <#pin_memory>`__ - - ```non_blocking=True`` <#non_blockingtrue>`__ - - `Synergies <#synergies>`__ - - `Other directions (GPU -> CPU etc.) <#other-directions>`__ - -- `Practical recommendations <#practical-recommendations>`__ -- `Case studies <#case-studies>`__ -- `Conclusion <#conclusion>`__ -- `Additional resources <#additional-resources>`__ - -Background ----------- - -Memory management basics -~~~~~~~~~~~~~~~~~~~~~~~~ - -When one creates a CPU tensor in PyTorch, the content of this tensor -needs to be placed in memory. The memory we talk about here is a rather -complex concept worth looking at carefully. We distinguish two types of -memories that are handled by the Memory Management Unit: the main memory -(for simplicity) and the disk (which may or may not be the hard drive). -Together, the available space in disk and RAM (physical memory) make up -the virtual memory, which is an abstraction of the total resources -available. In short, the virtual memory makes it so that the available -space is larger than what can be found on RAM in isolation and creates -the illusion that the main memory is larger than it actually is. - -In normal circumstances, a regular CPU tensor is *paged*, which means -that it is divided in blocks called *pages* that can live anywhere in -the virtual memory (both in RAM or on disk). As mentioned earlier, this -has the advantage that the memory seems larger than what the main memory -actually is. - -Typically, when a program accesses a page that is not in RAM, a “page -fault” occurs and the operating system (OS) then brings back this page -into RAM (*swap in* or *page in*). In turn, the OS may have to *swap -out* (or *page out*) another page to make room for the new page. - -In contrast to pageable memory, a *pinned* (or *page-locked* or -*non-pegeable*) memory is a type of memory that cannot be swapped out to -disk. It allows for faster and more predictable access times, but has -the downside that it is more limited than the pageable memory (aka the -main memory). - -.. figure:: /_static/img/pinmem.png - :alt: - -CUDA and (non-)pageable memory -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -To understand how CUDA copies a tensor from CPU to CUDA, let’s consider -the two scenarios above: - If the memory is page-locked, the device can -access the memory directly in the main memory. The memory addresses are -well defined and functions that need to read these data can be -significantly accelerated. - If the memory is pageable, all the pages -will have to be brought to the main memory before being sent to the GPU. -This operation may take time and is less predictable than when executed -on page-locked tensors. - -More precisely, when CUDA sends pageable data from CPU to GPU, it must -first create a page-locked copy of that data before making the transfer. - -Asynchronous vs. Synchronous Operations with ``non_blocking=True`` (CUDA ``cudaMemcpyAsync``) -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -When executing a copy from a host (e.g., CPU) to a device (e.g., GPU), -the CUDA toolkit offers modalities to do these operations synchronously -or asynchronously with respect to the host. In the synchronous case, the -call to ``cudaMemcpy`` that is queries by ``tensor.to(device)`` is -blocking in the python main thread, which means that the code will stop -until the data has been transferred to the device. - -When calling ``tensor.to(device)``, PyTorch always makes a call to -```cudaMemcpyAsync`` `__. -If ``non_blocking=False`` (default), a ``cudaStreamSynchronize`` will be -called after each and every ``cudaMemcpyAsync``. If -``non_blocking=True``, no synchronization is triggered, and the main -thread on the host is not blocked. Therefore, from the host perspective, -multiple tensors can be sent to the device simultaneously in the latter -case, as the thread does not need for one transfer to be completed to -initiate the other. - -Note -^^^^ - -In general, the transfer is blocking on the device size even if it’s not -on the host side: the copy on the device cannot occur while another -operation is being executed. However, in some advanced scenarios, -multiple copies or copy and kernel executions can be done simultaneously -on the GPU side. To enable this, three requirements must be met: - -1. The device must have at least one free DMA (Direct Memory Access) - engine. Modern GPU architectures such as Volterra, Tesla or H100 - devices have more than one DMA engine. -2. The transfer must be done on a separate, non-default cuda stream. In - PyTorch, cuda streams can be handles using ``torch.cuda.Stream``. -3. The source data must be in pinned memory. - -A PyTorch perspective ---------------------- - -``pin_memory()`` -~~~~~~~~~~~~~~~~ - -PyTorch offers the possibility to create and send tensors to page-locked -memory through the ``pin_memory`` functions and arguments. Any cpu -tensor on a machine where a cuda is initialized can be sent to pinned -memory through the ``pin_memory`` method. Importantly, ``pin_memory`` is -blocking on the host: the main thread will wait for the tensor to be -copied to page-locked memory before executing the next operation. New -tensors can be directly created in pinned memory with functions like -``torch.zeros``, ``torch.ones`` and other constructors. - -Let us check the speed of pinning memory and sending tensors to cuda: - -.. code:: ipython3 - - import torch - import gc - from torch.utils.benchmark import Timer - - tensor_pageable = torch.randn(100_000) - - tensor_pinned = torch.randn(100_000, pin_memory=True) - - print("Regular to(device)", - Timer("tensor_pageable.to('cuda:0')", globals=globals()).adaptive_autorange()) - print("Pinned to(device)", - Timer("tensor_pinned.to('cuda:0')", globals=globals()).adaptive_autorange()) - print("pin_memory() along", - Timer("tensor_pageable.pin_memory()", globals=globals()).adaptive_autorange()) - print("pin_memory() + to(device)", - Timer("tensor_pageable.pin_memory().to('cuda:0')", globals=globals()).adaptive_autorange()) - del tensor_pageable, tensor_pinned - gc.collect() - - - -.. parsed-literal:: - - Regular to(device) - tensor_pageable.to('cuda:0') - Median: 35.26 us - IQR: 0.04 us (35.23 to 35.28) - 4 measurements, 10000 runs per measurement, 1 thread - Pinned to(device) - tensor_pinned.to('cuda:0') - Median: 19.70 us - IQR: 0.03 us (19.69 to 19.72) - 4 measurements, 10000 runs per measurement, 1 thread - pin_memory() along - tensor_pageable.pin_memory() - Median: 11.82 us - IQR: 0.03 us (11.80 to 11.83) - 4 measurements, 10000 runs per measurement, 1 thread - pin_memory() + to(device) - tensor_pageable.pin_memory().to('cuda:0') - Median: 40.84 us - IQR: 0.14 us (40.78 to 40.93) - 4 measurements, 10000 runs per measurement, 1 thread - - - - -.. parsed-literal:: - - 12 - - - -We can observe that casting a pinned-memory tensor to GPU is indeed much -faster than a pageable tensor, because under the hood, a pageable tensor -must be copied to pinned memory before being sent to GPU. - -However, calling ``pin_memory()`` on a pageable tensor before casting it -to GPU does not bring any speed-up, on the contrary this call is -actually slower than just executing the transfer. Again, this makes -sense, since we’re actually asking python to execute an operation that -CUDA will perform anyway before copying the data from host to device. - -``non_blocking=True`` -~~~~~~~~~~~~~~~~~~~~~ - -As mentioned earlier, many PyTorch operations have the option of being -executed asynchronously with respect to the host through the -``non_blocking`` argument. Here, to account accurately of the benefits -of using ``non_blocking``, we will design a slightly more involved -experiment since we want to assess how fast it is to send multiple -tensors to GPU with and without calling ``non_blocking``. - -.. code:: ipython3 - - def copy_to_device(*tensors, display_peak_mem=False): - result = [] - for tensor in tensors: - result.append(tensor.to("cuda:0")) - return result - def copy_to_device_nonblocking(*tensors, display_peak_mem=False): - result = [] - for tensor in tensors: - result.append(tensor.to("cuda:0", non_blocking=True)) - # We need to synchronize - torch.cuda.synchronize() - return result - - tensors = [torch.randn(1000) for _ in range(1000)] - print("Call to `to(device)`", Timer("copy_to_device(*tensors)", globals=globals()).adaptive_autorange()) - print("Call to `to(device, non_blocking=True)`", Timer("copy_to_device_nonblocking(*tensors)", - globals=globals()).adaptive_autorange()) - - -.. parsed-literal:: - - Call to `to(device)` - copy_to_device(*tensors) - Median: 11.03 ms - IQR: 0.09 ms (10.98 to 11.07) - 4 measurements, 10 runs per measurement, 1 thread - Call to `to(device, non_blocking=True)` - copy_to_device_nonblocking(*tensors) - Median: 5.88 ms - IQR: 0.38 ms (5.82 to 6.20) - 4 measurements, 10 runs per measurement, 1 thread - - -To get a better sense of what is happening here, let us run a profiling -of these two code executions: - -.. code:: ipython3 - - from torch.profiler import profile, record_function, ProfilerActivity - - def profile_mem(cmd): - with profile(activities=[ProfilerActivity.CPU]) as prof: - exec(cmd) - print(cmd) - print(prof.key_averages().table(row_limit=10)) - - print("Call to `to(device)`", profile_mem("copy_to_device(*tensors)")) - print("Call to `to(device, non_blocking=True)`", profile_mem("copy_to_device_nonblocking(*tensors)")) - - -.. parsed-literal:: - - copy_to_device(*tensors) - - -.. parsed-literal:: - - STAGE:2024-07-24 08:29:14 2357923:2357923 ActivityProfilerController.cpp:314] Completed Stage: Warm Up - STAGE:2024-07-24 08:29:14 2357923:2357923 ActivityProfilerController.cpp:320] Completed Stage: Collection - STAGE:2024-07-24 08:29:14 2357923:2357923 ActivityProfilerController.cpp:324] Completed Stage: Post Processing - - -.. parsed-literal:: - - ------------------------- ------------ ------------ ------------ ------------ ------------ ------------ - Name Self CPU % Self CPU CPU total % CPU total CPU time avg # of Calls - ------------------------- ------------ ------------ ------------ ------------ ------------ ------------ - aten::to 11.39% 2.118ms 88.36% 16.432ms 16.432us 1000 - aten::_to_copy 11.87% 2.208ms 86.48% 16.083ms 16.083us 1000 - aten::empty_strided 26.90% 5.002ms 26.90% 5.002ms 5.002us 1000 - aten::copy_ 16.27% 3.026ms 49.84% 9.269ms 9.269us 1000 - cudaMemcpyAsync 11.25% 2.092ms 11.25% 2.092ms 2.092us 1000 - cudaStreamSynchronize 22.32% 4.151ms 22.32% 4.151ms 4.151us 1000 - ------------------------- ------------ ------------ ------------ ------------ ------------ ------------ - Self CPU time total: 18.597ms - - Call to `to(device)` None - - -.. parsed-literal:: - - STAGE:2024-07-24 08:29:14 2357923:2357923 ActivityProfilerController.cpp:314] Completed Stage: Warm Up - STAGE:2024-07-24 08:29:14 2357923:2357923 ActivityProfilerController.cpp:320] Completed Stage: Collection - STAGE:2024-07-24 08:29:14 2357923:2357923 ActivityProfilerController.cpp:324] Completed Stage: Post Processing - - -.. parsed-literal:: - - copy_to_device_nonblocking(*tensors) - ------------------------- ------------ ------------ ------------ ------------ ------------ ------------ - Name Self CPU % Self CPU CPU total % CPU total CPU time avg # of Calls - ------------------------- ------------ ------------ ------------ ------------ ------------ ------------ - aten::to 12.48% 1.621ms 88.23% 11.457ms 11.457us 1000 - aten::_to_copy 15.72% 2.042ms 85.95% 11.162ms 11.162us 1000 - aten::empty_strided 35.68% 4.633ms 35.68% 4.633ms 4.633us 1000 - aten::copy_ 16.69% 2.167ms 35.85% 4.655ms 4.655us 1000 - cudaMemcpyAsync 19.30% 2.506ms 19.30% 2.506ms 2.506us 1000 - cudaDeviceSynchronize 0.13% 17.000us 0.13% 17.000us 17.000us 1 - ------------------------- ------------ ------------ ------------ ------------ ------------ ------------ - Self CPU time total: 12.986ms - - Call to `to(device, non_blocking=True)` None - - -The results are without any doubt better when using -``non_blocking=True``, as all transfers are initiated simultaneously on -the host side. Note that, interestingly, ``to("cuda")`` actually -performs the same asynchrous device casting operation as the one with -``non_blocking=True`` with a synchronization point after each copy. - -The benefit will vary depending on the number and the size of the -tensors as well as depending on the hardware being used. - -Synergies -~~~~~~~~~ - -Now that we have made the point that data transfer of tensors already in -pinned memory to GPU is faster than from pageable memory, and that we -know that doing these transfers asynchronously is also faster than -synchronously, we can benchmark the various combinations at hand: - -.. code:: ipython3 - - - def pin_copy_to_device(*tensors): - result = [] - for tensor in tensors: - result.append(tensor.pin_memory().to("cuda:0")) - return result - def pin_copy_to_device_nonblocking(*tensors): - result = [] - for tensor in tensors: - result.append(tensor.pin_memory().to("cuda:0", non_blocking=True)) - # We need to synchronize - torch.cuda.synchronize() - return result - - print("\nCall to `pin_memory()` + `to(device)`") - print("pin_memory().to(device)", - Timer("pin_copy_to_device(*tensors)", globals=globals()).adaptive_autorange()) - print("pin_memory().to(device, non_blocking=True)", - Timer("pin_copy_to_device_nonblocking(*tensors)", - globals=globals()).adaptive_autorange()) - - print("\nCall to `to(device)`") - print("to(device)", - Timer("copy_to_device(*tensors)", globals=globals()).adaptive_autorange()) - print("to(device, non_blocking=True)", - Timer("copy_to_device_nonblocking(*tensors)", - globals=globals()).adaptive_autorange()) - - print("\nCall to `to(device)` from pinned tensors") - tensors_pinned = [torch.zeros(1000, pin_memory=True) for _ in range(1000)] - print("tensor_pinned.to(device)", - Timer("copy_to_device(*tensors_pinned)", globals=globals()).adaptive_autorange()) - print("tensor_pinned.to(device, non_blocking=True)", - Timer("copy_to_device_nonblocking(*tensors_pinned)", - globals=globals()).adaptive_autorange()) - - del tensors, tensors_pinned - gc.collect() - - - -.. parsed-literal:: - - - Call to `pin_memory()` + `to(device)` - pin_memory().to(device) - pin_copy_to_device(*tensors) - Median: 17.18 ms - IQR: 0.04 ms (17.16 to 17.20) - 4 measurements, 10 runs per measurement, 1 thread - pin_memory().to(device, non_blocking=True) - pin_copy_to_device_nonblocking(*tensors) - Median: 15.42 ms - IQR: 0.08 ms (15.38 to 15.47) - 4 measurements, 10 runs per measurement, 1 thread - - Call to `to(device)` - to(device) - copy_to_device(*tensors) - Median: 13.15 ms - IQR: 0.06 ms (13.13 to 13.20) - 4 measurements, 10 runs per measurement, 1 thread - to(device, non_blocking=True) - copy_to_device_nonblocking(*tensors) - Median: 8.26 ms - IQR: 0.05 ms (8.23 to 8.27) - 4 measurements, 10 runs per measurement, 1 thread - - Call to `to(device)` from pinned tensors - tensor_pinned.to(device) - copy_to_device(*tensors_pinned) - Median: 13.28 ms - IQR: 0.35 ms (13.13 to 13.48) - 4 measurements, 10 runs per measurement, 1 thread - tensor_pinned.to(device, non_blocking=True) - copy_to_device_nonblocking(*tensors_pinned) - Median: 8.16 ms - IQR: 0.08 ms (8.15 to 8.23) - 4 measurements, 10 runs per measurement, 1 thread - - - - -.. parsed-literal:: - - 40087 - - - -Other directions (GPU -> CPU, CPU -> MPS etc.) -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -So far, we have assumed that doing asynchronous copies from CPU to GPU -was safe. Indeed, it is a safe thing to do because CUDA will synchronize -whenever it is needed to make sure that the data being read is not -garbage. However, any other copy (e.g., from GPU to CPU) has no -guarantee whatsoever that the copy will be completed when the data is -read. In fact, if no explicit synchronization is done, the data on the -host can be garbage: - -.. code:: ipython3 - - tensor = torch.arange(1, 1_000_000, dtype=torch.double, device="cuda").expand(100, 999999).clone() - torch.testing.assert_close(tensor.mean(), torch.tensor(500_000, dtype=torch.double, device="cuda")), tensor.mean() - try: - i = -1 - for i in range(100): - cpu_tensor = tensor.to("cpu", non_blocking=True) - torch.testing.assert_close(cpu_tensor.mean(), torch.tensor(500_000, dtype=torch.double)) - print("No test failed with non_blocking") - except AssertionError: - print(f"One test failed with non_blocking: {i}th assertion!") - try: - i = -1 - for i in range(100): - cpu_tensor = tensor.to("cpu", non_blocking=True) - torch.cuda.synchronize() - torch.testing.assert_close(cpu_tensor.mean(), torch.tensor(500_000, dtype=torch.double)) - print("No test failed with synchronize") - except AssertionError: - print(f"One test failed with synchronize: {i}th assertion!") - - - -.. parsed-literal:: - - One test failed with non_blocking: 0th assertion! - No test failed with synchronize - - -The same observation could be made with copies from CPU to a non-CUDA -device such as MPS. - -In summary, copying data from CPU to GPU is safe when using -``non_blocking=True``, but for any other direction, -``non_blocking=True`` can still be used but the user must make sure that -a device synchronization is executed after the data is accessed. - -Practical recommendations -------------------------- - -We can now wrap up some early recommendations based on our observations: -In general, ``non_blocking=True`` will provide a good speed of transfer, -regardless of whether the original tensor is or isn’t in pinned memory. -If the tensor is already in pinned memory, the transfer can be -accelerated, but sending it to pin memory manually is a blocking -operation on the host and hence will anihilate much of the benefit of -using ``non_blocking=True`` (and CUDA does the ``pin_memory`` transfer -anyway). - -One might now legitimetely ask what use there is for the -``pin_memory()`` method within the ``torch.Tensor`` class. In the -following section, we will explore further how this can be used to -accelerate the data transfer even more. - -Additional considerations -------------------------- - -PyTorch notoriously provides a ``DataLoader`` class that accepts a -``pin_memory`` argument. Given everything we have said so far about -calls to ``pin_memory``, how does the dataloader manage to accelerate -data transfers? - -The answer is resides in the fact that the dataloader reserves a -separate thread to copy the data from pageable to pinned memory, thereby -avoiding to block the main thread with this. Consider the following -example, where we send a list of tensors to cuda after calling -pin_memory on a separate thread: - -A more isolated example of this is the TensorDict primitive from the -homonymous library: when calling ``TensorDict.to(device)``, the default -behaviour is to send these tensors to the device asynchronously and make -a ``device.synchronize()`` call after. ``TensorDict.to()`` also offers a -``non_blocking_pin`` argument which will spawn multiple threads to do -the calls to ``pin_memory()`` before launching the calls to -``to(device)``. This can further speed up the copies as the following -example shows: - -.. code:: ipython3 - - from tensordict import TensorDict - import torch - from torch.utils.benchmark import Timer - - td = TensorDict({str(i): torch.randn(1_000_000) for i in range(100)}) - - print(Timer("td.to('cuda:0', non_blocking=False)", globals=globals()).adaptive_autorange()) - print(Timer("td.to('cuda:0')", globals=globals()).adaptive_autorange()) - print(Timer("td.to('cuda:0', non_blocking=True, non_blocking_pin=True)", globals=globals()).adaptive_autorange()) - - -.. parsed-literal:: - - /home/vmoens/.conda/envs/torchrl/lib/python3.10/site-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html - from .autonotebook import tqdm as notebook_tqdm - - -.. parsed-literal:: - - - td.to('cuda:0', non_blocking=False) - Median: 35.55 ms - IQR: 0.38 ms (35.43 to 35.81) - 4 measurements, 10 runs per measurement, 1 thread - - td.to('cuda:0') - Median: 32.59 ms - IQR: 0.10 ms (32.55 to 32.65) - 4 measurements, 10 runs per measurement, 1 thread - - td.to('cuda:0', non_blocking=True, non_blocking_pin=True) - Median: 23.63 ms - IQR: 0.39 ms (23.45 to 23.84) - 4 measurements, 1 runs per measurement, 1 thread - - -As a side note, it may be tempting to create everlasting buffers in -pinned memory and copy tensors from pageable memory to pinned memory, -and use these as shuttle before sending the data to GPU. Unfortunately, -this does not speed up computation because the bottleneck of copying -data to pinned memory is still present. - -Another consideration is that transferring data that is stored on disk -(shared memory or files) to GPU will usually require the data to be -copied to pinned memory (which is on RAM) as an intermediate step. - -Using ``non_blocking`` in these context for large amount of data may -have devastating effects on RAM consumption. In practice, there is no -silver bullet, and the performance of any combination of multithreaded -pin_memory and non_blocking will depend on multiple factors such as the -system being used, the OS, the hardware and the tasks being performed. - -Finally, creating a large number of tensors or a few large tensors in -pinned memory will effectively reserve more RAM than pageable tensors -would, thereby lowering the amount of available RAM for other operations -(such as swapping pages in and out), which can have a negative impact -over the overall runtime of an algorithm. - -Conclusion ----------- - -Additional resources --------------------- - From a688b9034984be0f49e7dddb9beb3b0dcd936ea0 Mon Sep 17 00:00:00 2001 From: Vincent Moens Date: Wed, 24 Jul 2024 19:47:46 +0100 Subject: [PATCH 12/54] amend --- intermediate_source/dqn_with_rnn_tutorial.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/intermediate_source/dqn_with_rnn_tutorial.py b/intermediate_source/dqn_with_rnn_tutorial.py index 991a0ff8bd..6ea0955939 100644 --- a/intermediate_source/dqn_with_rnn_tutorial.py +++ b/intermediate_source/dqn_with_rnn_tutorial.py @@ -298,7 +298,7 @@ # either by passing a string or an action-spec. This allows us to use # Categorical (sometimes called "sparse") encoding or the one-hot version of it. # -qval = QValueModule(action_space=env.action_spec) +qval = QValueModule(spec=env.action_spec) ###################################################################### # .. note:: From d706405ddbf964ecd059a68999d21d5afb4d862d Mon Sep 17 00:00:00 2001 From: Vincent Moens Date: Wed, 24 Jul 2024 21:04:47 +0100 Subject: [PATCH 13/54] amend --- advanced_source/coding_ddpg.py | 34 ++++++++++++++++++---------------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/advanced_source/coding_ddpg.py b/advanced_source/coding_ddpg.py index 7dd3acf238..c634932971 100644 --- a/advanced_source/coding_ddpg.py +++ b/advanced_source/coding_ddpg.py @@ -182,7 +182,7 @@ # Later, we will see how the target parameters should be updated in TorchRL. # -from tensordict.nn import TensorDictModule +from tensordict.nn import TensorDictModule, TensorDictSequential def _init( @@ -290,12 +290,11 @@ def _loss_actor( ) -> torch.Tensor: td_copy = tensordict.select(*self.actor_in_keys) # Get an action from the actor network: since we made it functional, we need to pass the params - td_copy = self.actor_network(td_copy, params=self.actor_network_params) + with self.actor_network_params.to_module(self.actor_network): + td_copy = self.actor_network(td_copy) # get the value associated with that action - td_copy = self.value_network( - td_copy, - params=self.value_network_params.detach(), - ) + with self.value_network_params.detach().to_module(self.value_network): + td_copy = self.value_network(td_copy) return -td_copy.get("state_action_value") @@ -317,7 +316,8 @@ def _loss_value( td_copy = tensordict.clone() # V(s, a) - self.value_network(td_copy, params=self.value_network_params) + with self.value_network_params.to_module(self.value_network): + self.value_network(td_copy) pred_val = td_copy.get("state_action_value").squeeze(-1) # we manually reconstruct the parameters of the actor-critic, where the first @@ -332,9 +332,8 @@ def _loss_value( batch_size=self.target_actor_network_params.batch_size, device=self.target_actor_network_params.device, ) - target_value = self.value_estimator.value_estimate( - tensordict, target_params=target_params - ).squeeze(-1) + with target_params.to_module(self.actor_critic): + target_value = self.value_estimator.value_estimate(tensordict).squeeze(-1) # Computes the value loss: L2, L1 or smooth L1 depending on `self.loss_function` loss_value = distance_loss(pred_val, target_value, loss_function=self.loss_function) @@ -717,7 +716,7 @@ def get_env_stats(): ActorCriticWrapper, DdpgMlpActor, DdpgMlpQNet, - OrnsteinUhlenbeckProcessWrapper, + OrnsteinUhlenbeckProcessModule, ProbabilisticActor, TanhDelta, ValueOperator, @@ -776,15 +775,18 @@ def make_ddpg_actor( # Exploration # ~~~~~~~~~~~ # -# The policy is wrapped in a :class:`~torchrl.modules.OrnsteinUhlenbeckProcessWrapper` +# The policy is passed into a :class:`~torchrl.modules.OrnsteinUhlenbeckProcessModule` # exploration module, as suggested in the original paper. # Let's define the number of frames before OU noise reaches its minimum value annealing_frames = 1_000_000 -actor_model_explore = OrnsteinUhlenbeckProcessWrapper( +actor_model_explore = TensorDictSequential( actor, - annealing_num_steps=annealing_frames, -).to(device) + OrnsteinUhlenbeckProcessModule( + spec=actor.spec.clone(), + annealing_num_steps=annealing_frames, + ).to(device), +) if device == torch.device("cpu"): actor_model_explore.share_memory() @@ -1168,7 +1170,7 @@ def ceil_div(x, y): ) # update the exploration strategy - actor_model_explore.step(current_frames) + actor_model_explore[1].step(current_frames) collector.shutdown() del collector From 5ae66ec7a7f26f7c48f42a87570af0542f23321e Mon Sep 17 00:00:00 2001 From: Vincent Moens Date: Thu, 25 Jul 2024 08:18:58 +0100 Subject: [PATCH 14/54] amend --- intermediate_source/pinmem_nonblock.py | 103 ++++++++++++++++++++++--- 1 file changed, 93 insertions(+), 10 deletions(-) diff --git a/intermediate_source/pinmem_nonblock.py b/intermediate_source/pinmem_nonblock.py index 41cdb6b505..fa544dd519 100644 --- a/intermediate_source/pinmem_nonblock.py +++ b/intermediate_source/pinmem_nonblock.py @@ -163,6 +163,60 @@ del tensor_pageable, tensor_pinned gc.collect() +###################################################################### +# Another size (TODO: Remove the one less concinving) +tensor_pageable = torch.randn(1_000_000) + +tensor_pinned = torch.randn(1_000_000, pin_memory=True) + +print( + "Regular to(device)", + Timer("tensor_pageable.to('cuda:0')", globals=globals()).adaptive_autorange(), +) +print( + "Pinned to(device)", + Timer("tensor_pinned.to('cuda:0')", globals=globals()).adaptive_autorange(), +) +print( + "pin_memory() along", + Timer("tensor_pageable.pin_memory()", globals=globals()).adaptive_autorange(), +) +print( + "pin_memory() + to(device)", + Timer( + "tensor_pageable.pin_memory().to('cuda:0')", globals=globals() + ).adaptive_autorange(), +) +del tensor_pageable, tensor_pinned +gc.collect() + + +###################################################################### +# Another size (TODO: Remove the one less concinving) +tensor_pageable = torch.randn(10_000) + +tensor_pinned = torch.randn(10_000, pin_memory=True) + +print( + "Regular to(device)", + Timer("tensor_pageable.to('cuda:0')", globals=globals()).adaptive_autorange(), +) +print( + "Pinned to(device)", + Timer("tensor_pinned.to('cuda:0')", globals=globals()).adaptive_autorange(), +) +print( + "pin_memory() along", + Timer("tensor_pageable.pin_memory()", globals=globals()).adaptive_autorange(), +) +print( + "pin_memory() + to(device)", + Timer( + "tensor_pageable.pin_memory().to('cuda:0')", globals=globals() + ).adaptive_autorange(), +) +del tensor_pageable, tensor_pinned +gc.collect() ###################################################################### # We can observe that casting a pinned-memory tensor to GPU is indeed much faster than a pageable tensor, because under @@ -393,17 +447,46 @@ def pin_copy_to_device_nonblocking(*tensors): import torch from torch.utils.benchmark import Timer -td = TensorDict({str(i): torch.randn(1_000_000) for i in range(100)}) +for s0 in (100, 1000, 10_000, 1_000_000): + for s1 in (10, 100, 1000): + print("\n\n\n\n", s0, s1) + td = TensorDict({str(i): torch.randn(s0) for i in range(s1)}) -print( - Timer("td.to('cuda:0', non_blocking=False)", globals=globals()).adaptive_autorange() -) -print(Timer("td.to('cuda:0')", globals=globals()).adaptive_autorange()) -print( - Timer( - "td.to('cuda:0', non_blocking=True, non_blocking_pin=True)", globals=globals() - ).adaptive_autorange() -) + print( + Timer("td.to('cuda:0', non_blocking=False)", globals=globals()).adaptive_autorange() + ) + print(Timer("td.to('cuda:0')", globals=globals()).adaptive_autorange()) + print(torch.get_num_threads()) + print( + Timer( + "td.to('cuda:0', non_blocking_pin=True, num_threads=2)", globals=globals() + ).adaptive_autorange() + ) + print( + Timer( + "td.to('cuda:0', non_blocking_pin=True, num_threads=4)", globals=globals() + ).adaptive_autorange() + ) + print( + Timer( + "td.to('cuda:0', non_blocking_pin=True, num_threads=8)", globals=globals() + ).adaptive_autorange() + ) + print( + Timer( + "td.to('cuda:0', non_blocking_pin=True, num_threads=16)", globals=globals() + ).adaptive_autorange() + ) + print( + Timer( + "td.to('cuda:0', non_blocking_pin=True, num_threads=32)", globals=globals() + ).adaptive_autorange() + ) + print( + Timer( + "td.to('cuda:0', non_blocking_pin=True, num_threads=64)", globals=globals() + ).adaptive_autorange() + ) ###################################################################### From dc862591490c5832ca7223c8e448a8d0210b8b0e Mon Sep 17 00:00:00 2001 From: Vincent Moens Date: Thu, 25 Jul 2024 10:08:27 +0100 Subject: [PATCH 15/54] amend --- intermediate_source/pinmem_nonblock.py | 260 ++++++++++--------------- 1 file changed, 108 insertions(+), 152 deletions(-) diff --git a/intermediate_source/pinmem_nonblock.py b/intermediate_source/pinmem_nonblock.py index fa544dd519..cb344bda87 100644 --- a/intermediate_source/pinmem_nonblock.py +++ b/intermediate_source/pinmem_nonblock.py @@ -137,88 +137,47 @@ import torch import gc from torch.utils.benchmark import Timer +import matplotlib.pyplot as plt -tensor_pageable = torch.randn(100_000) -tensor_pinned = torch.randn(100_000, pin_memory=True) +def timer(cmd): + return Timer(cmd, globals=globals()).adaptive_autorange().median * 1000 -print( - "Regular to(device)", - Timer("tensor_pageable.to('cuda:0')", globals=globals()).adaptive_autorange(), -) -print( - "Pinned to(device)", - Timer("tensor_pinned.to('cuda:0')", globals=globals()).adaptive_autorange(), -) -print( - "pin_memory() along", - Timer("tensor_pageable.pin_memory()", globals=globals()).adaptive_autorange(), -) -print( - "pin_memory() + to(device)", - Timer( - "tensor_pageable.pin_memory().to('cuda:0')", globals=globals() - ).adaptive_autorange(), -) -del tensor_pageable, tensor_pinned -gc.collect() -###################################################################### -# Another size (TODO: Remove the one less concinving) -tensor_pageable = torch.randn(1_000_000) +pageable_tensor = torch.randn(1_000_000) -tensor_pinned = torch.randn(1_000_000, pin_memory=True) +pinned_tensor = torch.randn(1_000_000, pin_memory=True) -print( - "Regular to(device)", - Timer("tensor_pageable.to('cuda:0')", globals=globals()).adaptive_autorange(), -) -print( - "Pinned to(device)", - Timer("tensor_pinned.to('cuda:0')", globals=globals()).adaptive_autorange(), -) -print( - "pin_memory() along", - Timer("tensor_pageable.pin_memory()", globals=globals()).adaptive_autorange(), -) -print( - "pin_memory() + to(device)", - Timer( - "tensor_pageable.pin_memory().to('cuda:0')", globals=globals() - ).adaptive_autorange(), -) -del tensor_pageable, tensor_pinned -gc.collect() +pageable_to_device = timer("pageable_tensor.to('cuda:0')") +pinned_to_device = timer("pinned_tensor.to('cuda:0')") +pin_mem = timer("pageable_tensor.pin_memory()") +pin_mem_to_device = timer("pageable_tensor.pin_memory().to('cuda:0')") +r1 = pinned_to_device / pageable_to_device +r2 = pin_mem_to_device / pageable_to_device +fig, ax = plt.subplots() -###################################################################### -# Another size (TODO: Remove the one less concinving) -tensor_pageable = torch.randn(10_000) +xlabels = ["Pageable Tensor", "Pinned tensor", "Pageable Tensor with pin"] +bar_labels = [ + "pageable_tensor.to(device) (1x)", + f"pinned_tensor.to(device) ({r1:4.4f}x)", + f"pageable_tensor.pin_memory().to(device) ({r2:4.4f}x)", +] +values = [pageable_to_device, pinned_to_device, pin_mem_to_device] -tensor_pinned = torch.randn(10_000, pin_memory=True) +ax.bar(xlabels, values, label=bar_labels) -print( - "Regular to(device)", - Timer("tensor_pageable.to('cuda:0')", globals=globals()).adaptive_autorange(), -) -print( - "Pinned to(device)", - Timer("tensor_pinned.to('cuda:0')", globals=globals()).adaptive_autorange(), -) -print( - "pin_memory() along", - Timer("tensor_pageable.pin_memory()", globals=globals()).adaptive_autorange(), -) -print( - "pin_memory() + to(device)", - Timer( - "tensor_pageable.pin_memory().to('cuda:0')", globals=globals() - ).adaptive_autorange(), -) -del tensor_pageable, tensor_pinned +ax.set_ylabel("Runtime (ms)") +ax.set_title("Device casting runtime (pin-memory)") +ax.legend() + +plt.show() + +del pageable_tensor, pinned_tensor gc.collect() ###################################################################### +# # We can observe that casting a pinned-memory tensor to GPU is indeed much faster than a pageable tensor, because under # the hood, a pageable tensor must be copied to pinned memory before being sent to GPU. # @@ -253,16 +212,22 @@ def copy_to_device_nonblocking(*tensors, display_peak_mem=False): tensors = [torch.randn(1000) for _ in range(1000)] -print( - "Call to `to(device)`", - Timer("copy_to_device(*tensors)", globals=globals()).adaptive_autorange(), -) -print( - "Call to `to(device, non_blocking=True)`", - Timer( - "copy_to_device_nonblocking(*tensors)", globals=globals() - ).adaptive_autorange(), -) +to_device = timer("copy_to_device(*tensors)") +to_device_nonblocking = timer("copy_to_device_nonblocking(*tensors)") + +fig, ax = plt.subplots() + +xlabels = ["to(device)", "to(device, non_blocking=True)"] +bar_labels = xlabels +values = [to_device, to_device_nonblocking] + +ax.bar(xlabels, values, label=bar_labels) + +ax.set_ylabel("Runtime (ms)") +ax.set_title("Device casting runtime (non-blocking)") +ax.legend() + +plt.show() ###################################################################### @@ -318,42 +283,44 @@ def pin_copy_to_device_nonblocking(*tensors): return result -print("\nCall to `pin_memory()` + `to(device)`") -print( - "pin_memory().to(device)", - Timer("pin_copy_to_device(*tensors)", globals=globals()).adaptive_autorange(), -) -print( - "pin_memory().to(device, non_blocking=True)", - Timer( - "pin_copy_to_device_nonblocking(*tensors)", globals=globals() - ).adaptive_autorange(), -) +pin_and_copy = timer("pin_copy_to_device(*tensors)") +pin_and_copy_nb = timer("pin_copy_to_device_nonblocking(*tensors)") -print("\nCall to `to(device)`") -print( - "to(device)", - Timer("copy_to_device(*tensors)", globals=globals()).adaptive_autorange(), -) -print( - "to(device, non_blocking=True)", - Timer( - "copy_to_device_nonblocking(*tensors)", globals=globals() - ).adaptive_autorange(), -) +page_copy = timer("copy_to_device(*tensors") +page_copy_nb = timer("copy_to_device_nonblocking(*tensors_pinned))") -print("\nCall to `to(device)` from pinned tensors") -tensors_pinned = [torch.zeros(1000, pin_memory=True) for _ in range(1000)] -print( - "tensor_pinned.to(device)", - Timer("copy_to_device(*tensors_pinned)", globals=globals()).adaptive_autorange(), -) -print( - "tensor_pinned.to(device, non_blocking=True)", - Timer( - "copy_to_device_nonblocking(*tensors_pinned)", globals=globals() - ).adaptive_autorange(), -) +tensors_pinned = [torch.randn(1000, pin_memory=True) for _ in range(1000)] + +pinned_copy = timer("copy_to_device(*tensors") +pinned_copy_nb = timer("copy_to_device_nonblocking(*tensors_pinned))") + +strategies = ("pageable copy", "pinned copy", "pin and copy") +blocking = { + "blocking": [page_copy, pinned_copy, pin_and_copy], + "non-blocking": [page_copy_nb, pinned_copy_nb, pin_and_copy_nb], +} + +x = [0, 1, 2] +width = 0.25 +multiplier = 0 + + +fig, ax = plt.subplots(layout="constrained") + +for attribute, runtimes in blocking.items(): + offset = width * multiplier + rects = ax.bar(x + offset, runtimes, width, label=attribute) + ax.bar_label(rects, padding=3) + multiplier += 1 + +# Add some text for labels, title and custom x-axis tick labels, etc. +ax.set_ylabel("Runtime (ms)") +ax.set_title("Runtime (pin-mem and non-blocking)") +ax.set_xticks(x + width, strategies) +ax.legend(loc="upper left", ncols=3) +ax.set_ylim(0, 250) + +plt.show() del tensors, tensors_pinned gc.collect() @@ -447,47 +414,36 @@ def pin_copy_to_device_nonblocking(*tensors): import torch from torch.utils.benchmark import Timer -for s0 in (100, 1000, 10_000, 1_000_000): - for s1 in (10, 100, 1000): - print("\n\n\n\n", s0, s1) - td = TensorDict({str(i): torch.randn(s0) for i in range(s1)}) +td = TensorDict({str(i): torch.randn(1_000_000) for i in range(100)}) - print( - Timer("td.to('cuda:0', non_blocking=False)", globals=globals()).adaptive_autorange() - ) - print(Timer("td.to('cuda:0')", globals=globals()).adaptive_autorange()) - print(torch.get_num_threads()) - print( - Timer( - "td.to('cuda:0', non_blocking_pin=True, num_threads=2)", globals=globals() - ).adaptive_autorange() - ) - print( - Timer( - "td.to('cuda:0', non_blocking_pin=True, num_threads=4)", globals=globals() - ).adaptive_autorange() - ) - print( - Timer( - "td.to('cuda:0', non_blocking_pin=True, num_threads=8)", globals=globals() - ).adaptive_autorange() - ) - print( - Timer( - "td.to('cuda:0', non_blocking_pin=True, num_threads=16)", globals=globals() - ).adaptive_autorange() - ) - print( - Timer( - "td.to('cuda:0', non_blocking_pin=True, num_threads=32)", globals=globals() - ).adaptive_autorange() - ) - print( - Timer( - "td.to('cuda:0', non_blocking_pin=True, num_threads=64)", globals=globals() - ).adaptive_autorange() - ) +copy_blocking = timer("td.to('cuda:0', non_blocking=False)") +copy_non_blocking = timer("td.to('cuda:0')") +copy_pin_nb = timer("td.to('cuda:0', non_blocking_pin=True, num_threads=0)") +copy_pin_multithread_nb = timer("td.to('cuda:0', non_blocking_pin=True, num_threads=4)") + + +r1 = copy_non_blocking / copy_blocking +r2 = copy_pin_nb / copy_blocking +r3 = copy_pin_multithread_nb / copy_blocking + +fig, ax = plt.subplots() + +xlabels = [0, 1, 2, 3] +bar_labels = [ + "Blocking copy (1x)", + f"Non-blocking copy ({r1:4.4f}x)", + f"Blocking pin, non-blocking copy ({r2:4.4f}x)", + f"Non-blocking pin, non-blocking copy ({r3:4.4f}x)", +] +values = [copy_blocking, copy_non_blocking, copy_pin_nb, copy_pin_multithread_nb] + +ax.bar(xlabels, values, label=bar_labels) + +ax.set_ylabel("Runtime (ms)") +ax.set_title("Device casting runtime") +ax.legend() +plt.show() ###################################################################### # As a side note, it may be tempting to create everlasting buffers in pinned memory and copy tensors from pageable memory From 4af8cae9a18c16b98756568f49381de2400ba143 Mon Sep 17 00:00:00 2001 From: Vincent Moens Date: Thu, 25 Jul 2024 10:26:48 +0100 Subject: [PATCH 16/54] amend --- intermediate_source/pinmem_nonblock.py | 54 ++++++++++++++------------ 1 file changed, 30 insertions(+), 24 deletions(-) diff --git a/intermediate_source/pinmem_nonblock.py b/intermediate_source/pinmem_nonblock.py index cb344bda87..14db7d5b48 100644 --- a/intermediate_source/pinmem_nonblock.py +++ b/intermediate_source/pinmem_nonblock.py @@ -157,18 +157,19 @@ def timer(cmd): fig, ax = plt.subplots() -xlabels = ["Pageable Tensor", "Pinned tensor", "Pageable Tensor with pin"] +xlabels = [0, 1, 2] bar_labels = [ "pageable_tensor.to(device) (1x)", - f"pinned_tensor.to(device) ({r1:4.4f}x)", - f"pageable_tensor.pin_memory().to(device) ({r2:4.4f}x)", + f"pinned_tensor.to(device) ({r1:4.2f}x)", + f"pageable_tensor.pin_memory().to(device) ({r2:4.2f}x)", ] values = [pageable_to_device, pinned_to_device, pin_mem_to_device] - -ax.bar(xlabels, values, label=bar_labels) +colors = ["tab:blue", "tab:red", "tab:orange"] +ax.bar(xlabels, values, label=bar_labels, color=colors) ax.set_ylabel("Runtime (ms)") ax.set_title("Device casting runtime (pin-memory)") +ax.set_xticks([]) ax.legend() plt.show() @@ -195,14 +196,14 @@ def timer(cmd): # -def copy_to_device(*tensors, display_peak_mem=False): +def copy_to_device(*tensors): result = [] for tensor in tensors: result.append(tensor.to("cuda:0")) return result -def copy_to_device_nonblocking(*tensors, display_peak_mem=False): +def copy_to_device_nonblocking(*tensors): result = [] for tensor in tensors: result.append(tensor.to("cuda:0", non_blocking=True)) @@ -215,16 +216,20 @@ def copy_to_device_nonblocking(*tensors, display_peak_mem=False): to_device = timer("copy_to_device(*tensors)") to_device_nonblocking = timer("copy_to_device_nonblocking(*tensors)") +r1 = to_device_nonblocking / to_device + fig, ax = plt.subplots() -xlabels = ["to(device)", "to(device, non_blocking=True)"] -bar_labels = xlabels +xlabels = [0, 1] +bar_labels = [f"to(device) (1x)", f"to(device, non_blocking=True) ({r1:4.2f}x)"] +colors = ["tab:blue", "tab:red"] values = [to_device, to_device_nonblocking] -ax.bar(xlabels, values, label=bar_labels) +ax.bar(xlabels, values, label=bar_labels, color=colors) ax.set_ylabel("Runtime (ms)") ax.set_title("Device casting runtime (non-blocking)") +ax.set_xticks([]) ax.legend() plt.show() @@ -283,16 +288,16 @@ def pin_copy_to_device_nonblocking(*tensors): return result +tensors_pinned = [torch.randn(1000, pin_memory=True) for _ in range(1000)] + pin_and_copy = timer("pin_copy_to_device(*tensors)") pin_and_copy_nb = timer("pin_copy_to_device_nonblocking(*tensors)") -page_copy = timer("copy_to_device(*tensors") -page_copy_nb = timer("copy_to_device_nonblocking(*tensors_pinned))") - -tensors_pinned = [torch.randn(1000, pin_memory=True) for _ in range(1000)] +page_copy = timer("copy_to_device(*tensors)") +page_copy_nb = timer("copy_to_device_nonblocking(*tensors)") -pinned_copy = timer("copy_to_device(*tensors") -pinned_copy_nb = timer("copy_to_device_nonblocking(*tensors_pinned))") +pinned_copy = timer("copy_to_device(*tensors_pinned)") +pinned_copy_nb = timer("copy_to_device_nonblocking(*tensors_pinned)") strategies = ("pageable copy", "pinned copy", "pin and copy") blocking = { @@ -300,7 +305,7 @@ def pin_copy_to_device_nonblocking(*tensors): "non-blocking": [page_copy_nb, pinned_copy_nb, pin_and_copy_nb], } -x = [0, 1, 2] +x = torch.arange(3) width = 0.25 multiplier = 0 @@ -310,15 +315,14 @@ def pin_copy_to_device_nonblocking(*tensors): for attribute, runtimes in blocking.items(): offset = width * multiplier rects = ax.bar(x + offset, runtimes, width, label=attribute) - ax.bar_label(rects, padding=3) + ax.bar_label(rects, padding=3, fmt="%.2f") multiplier += 1 # Add some text for labels, title and custom x-axis tick labels, etc. ax.set_ylabel("Runtime (ms)") ax.set_title("Runtime (pin-mem and non-blocking)") -ax.set_xticks(x + width, strategies) +ax.set_xticks([]) ax.legend(loc="upper left", ncols=3) -ax.set_ylim(0, 250) plt.show() @@ -431,16 +435,18 @@ def pin_copy_to_device_nonblocking(*tensors): xlabels = [0, 1, 2, 3] bar_labels = [ "Blocking copy (1x)", - f"Non-blocking copy ({r1:4.4f}x)", - f"Blocking pin, non-blocking copy ({r2:4.4f}x)", - f"Non-blocking pin, non-blocking copy ({r3:4.4f}x)", + f"Non-blocking copy ({r1:4.2f}x)", + f"Blocking pin, non-blocking copy ({r2:4.2f}x)", + f"Non-blocking pin, non-blocking copy ({r3:4.2f}x)", ] values = [copy_blocking, copy_non_blocking, copy_pin_nb, copy_pin_multithread_nb] +colors = ["tab:blue", "tab:red", "tab:orange", "tab:green"] -ax.bar(xlabels, values, label=bar_labels) +ax.bar(xlabels, values, label=bar_labels, color=colors) ax.set_ylabel("Runtime (ms)") ax.set_title("Device casting runtime") +ax.set_xticks([]) ax.legend() plt.show() From 73ab3bace72982adb20133b8c71e3bb522f8febc Mon Sep 17 00:00:00 2001 From: Vincent Moens Date: Thu, 25 Jul 2024 11:02:47 +0100 Subject: [PATCH 17/54] amend --- intermediate_source/pinmem_nonblock.py | 111 +++++++++++++++---------- 1 file changed, 69 insertions(+), 42 deletions(-) diff --git a/intermediate_source/pinmem_nonblock.py b/intermediate_source/pinmem_nonblock.py index 14db7d5b48..7f0d8a89b0 100644 --- a/intermediate_source/pinmem_nonblock.py +++ b/intermediate_source/pinmem_nonblock.py @@ -3,15 +3,24 @@ A guide on good usage of `non_blocking` and `pin_memory()` in PyTorch ===================================================================== -TL;DR ------ +**Author**: `Vincent Moens `_ -Sending tensors from CPU to GPU can be made faster by using asynchronous transfer and memory pinning, but: +Introduction +------------ -- Calling `tensor.pin_memory().to(device, non_blocking=True)` can be as twice as slow as a plain `tensor.to(device)`; -- `tensor.to(device, non_blocking=True)` is usually a good choice; -- `cpu_tensor.to("cuda", non_blocking=True).mean()` will work, but `cuda_tensor.to("cpu", non_blocking=True).mean()` - will produce garbage. +Transferring data from the CPU to the GPU is fundamental in many PyTorch applications. +It's crucial for users to understand the most effective tools and options available for moving data between devices. +This tutorial examines two key methods for device-to-device data transfer in PyTorch: +:meth:`~torch.Tensor.pin_memory` and :meth:`~torch.Tensor.to` with the `non_blocking=True` option. + +Key Learnings +~~~~~~~~~~~~~ +Optimizing the transfer of tensors from the CPU to the GPU can be achieved through asynchronous transfers and memory +pinning. However, there are important considerations: +- Using `tensor.pin_memory().to(device, non_blocking=True)` can be up to twice as slow as a straightforward `tensor.to(device)`. +- Generally, `tensor.to(device, non_blocking=True)` is an effective choice for enhancing transfer speed. +- While `cpu_tensor.to("cuda", non_blocking=True).mean()` executes correctly, attempting + `cuda_tensor.to("cpu", non_blocking=True).mean()` will result in erroneous outputs. """ @@ -21,38 +30,34 @@ ###################################################################### -# Introduction -# ------------ -# -# Sending data from CPU to GPU is a cornerstone of many applications that use PyTorch. -# Given this, users should have a good understanding of what tools and options they should be using -# when moving data from one device to another. # -# This tutorial focuses on two aspects of device-to-device transfer: `Tensor.pin_memory()` and `Tensor.to(device, -# non_blocking=True)`. # We start by outlining the theory surrounding these concepts, and then move to concrete test examples of the features. # -# - [Background](#background) -# - [Memory management basics](#memory-management-basics) -# - [CUDA and (non-)pageable memory](#cuda-and-non-pageable-memory) -# - [Asynchronous vs synchronous operations](#asynchronous-vs-synchronous-operations) -# - [Deep dive](#deep-dive) -# - [`pin_memory()`](#pin_memory) -# - [`non_blocking=True`](#non_blockingtrue) -# - [Synergies](#synergies) -# - [Other directions (GPU -> CPU etc.)](#other-directions) -# - [Practical recommendations](#practical-recommendations) -# - [Case studies](#case-studies) -# - [Conclusion](#conclusion) -# - [Additional resources](#additional-resources) +# - :ref:`Background ` +# - :ref:`Memory management basics ` +# - :ref:`CUDA and (non-)pageable memory ` +# - :ref:`Asynchronous vs. Synchronous Operations with `non_blocking=True` ` +# - :ref:`A PyTorch perspective ` +# - :ref:`pin_memory ` +# - :ref:`non_blocking=True ` +# - :ref:`Synergies ` +# - :ref:`Other directions (GPU -> CPU) ` +# - :ref:`Practical recommendations ` +# - :ref:`Additional considerations ` +# - :ref:`Conclusion ` +# - :ref:`Additional resources ` # # # Background # ---------- # +# .. _pinmem_background: +# # Memory management basics # ~~~~~~~~~~~~~~~~~~~~~~~~ # +# .. _pinmem_mem: +# # When one creates a CPU tensor in PyTorch, the content of this tensor needs to be placed # in memory. The memory we talk about here is a rather complex concept worth looking at carefully. # We distinguish two types of memories that are handled by the Memory Management Unit: the main memory (for simplicity) @@ -80,6 +85,8 @@ # CUDA and (non-)pageable memory # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # +# .. _pinmem_cuda_pageable_mem: +# # To understand how CUDA copies a tensor from CPU to CUDA, let's consider the two scenarios above: # - If the memory is page-locked, the device can access the memory directly in the main memory. The memory addresses are well # defined and functions that need to read these data can be significantly accelerated. @@ -92,37 +99,43 @@ # Asynchronous vs. Synchronous Operations with `non_blocking=True` (CUDA `cudaMemcpyAsync`) # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # +# .. _pinmem_async_sync: +# # When executing a copy from a host (e.g., CPU) to a device (e.g., GPU), the CUDA toolkit offers modalities to do these -# operations synchronously or asynchronously with respect to the host. In the synchronous case, the call to `cudaMemcpy` -# that is queries by `tensor.to(device)` is blocking in the python main thread, which means that the code will stop until -# the data has been transferred to the device. +# operations synchronously or asynchronously with respect to the host. # -# When calling `tensor.to(device)`, PyTorch always makes a call to +# In practice, when calling :meth:`~torch.Tensor.to`, PyTorch always makes a call to # [`cudaMemcpyAsync`](https://docs.nvidia.com/cuda/cuda-runtime-api/group__CUDART__MEMORY.html#group__CUDART__MEMORY_1g85073372f776b4c4d5f89f7124b7bf79). -# If `non_blocking=False` (default), a `cudaStreamSynchronize` will be called after each and every `cudaMemcpyAsync`. +# If `non_blocking=False` (default), a `cudaStreamSynchronize` will be called after each and every `cudaMemcpyAsync`, making +# the call to :meth:`~torch.Tensor.to` blocking in the main thread. # If `non_blocking=True`, no synchronization is triggered, and the main thread on the host is not blocked. -# Therefore, from the host perspective, multiple tensors can be sent to the device simultaneously in the latter case, -# as the thread does not need for one transfer to be completed to initiate the other. +# Therefore, from the host perspective, multiple tensors can be sent to the device simultaneously, +# as the thread does not need to wait for one transfer to be completed to initiate the other. # -# .. note:: In general, the transfer is blocking on the device size even if it's not on the host side: the copy on the device cannot -# occur while another operation is being executed. However, in some advanced scenarios, multiple copies or copy and kernel +# .. note:: In general, the transfer is blocking on the device side (even if it isn't on the host side): +# the copy on the device cannot occur while another operation is being executed. +# However, in some advanced scenarios, multiple copies or copy and kernel # executions can be done simultaneously on the GPU side. To enable this, three requirements must be met: # -# 1. The device must have at least one free DMA (Direct Memory Access) engine. Modern GPU architectures such as Volterra, -# Tesla or H100 devices have more than one DMA engine. +# 1. The device must have at least one free DMA (Direct Memory Access) engine. Modern GPU architectures such as Volterra, +# Tesla or H100 devices have more than one DMA engine. # -# 2. The transfer must be done on a separate, non-default cuda stream. In PyTorch, cuda streams can be handles using -# `torch.cuda.Stream`. +# 2. The transfer must be done on a separate, non-default cuda stream. In PyTorch, cuda streams can be handles using +# :class:`~torch.cuda.Stream`. # -# 3. The source data must be in pinned memory. +# 3. The source data must be in pinned memory. # # # A PyTorch perspective # --------------------- # +# .. _pinmem_pt_perspective: +# # `pin_memory()` # ~~~~~~~~~~~~~~ # +# .. _pinmem_pinmem: +# # PyTorch offers the possibility to create and send tensors to page-locked memory through the `pin_memory` functions and # arguments. # Any cpu tensor on a machine where a cuda is initialized can be sent to pinned memory through the `pin_memory` @@ -189,6 +202,8 @@ def timer(cmd): # `non_blocking=True` # ~~~~~~~~~~~~~~~~~~~ # +# .. _pinmem_nb: +# # As mentioned earlier, many PyTorch operations have the option of being executed asynchronously with respect to the host # through the `non_blocking` argument. # Here, to account accurately of the benefits of using `non_blocking`, we will design a slightly more involved experiment @@ -267,6 +282,8 @@ def profile_mem(cmd): # Synergies # ~~~~~~~~~ # +# .. _pinmem_synergies: +# # Now that we have made the point that data transfer of tensors already in pinned memory to GPU is faster than from # pageable memory, and that we know that doing these transfers asynchronously is also faster than synchronously, we can # benchmark the various combinations at hand: @@ -334,6 +351,8 @@ def pin_copy_to_device_nonblocking(*tensors): # Other directions (GPU -> CPU, CPU -> MPS etc.) # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # +# .. _pinmem_otherdir: +# # So far, we have assumed that doing asynchronous copies from CPU to GPU was safe. # Indeed, it is a safe thing to do because CUDA will synchronize whenever it is needed to make sure that the data being # read is not garbage. @@ -383,6 +402,8 @@ def pin_copy_to_device_nonblocking(*tensors): # Practical recommendations # ------------------------- # +# .. _pinmem_recom: +# # We can now wrap up some early recommendations based on our observations: # In general, `non_blocking=True` will provide a good speed of transfer, regardless of whether the original tensor is or # isn't in pinned memory. If the tensor is already in pinned memory, the transfer can be accelerated, but sending it to @@ -395,6 +416,8 @@ def pin_copy_to_device_nonblocking(*tensors): # Additional considerations # ------------------------- # +# .. _pinmem_considerations: +# # PyTorch notoriously provides a `DataLoader` class that accepts a `pin_memory` argument. # Given everything we have said so far about calls to `pin_memory`, how does the dataloader manage to accelerate data # transfers? @@ -470,5 +493,9 @@ def pin_copy_to_device_nonblocking(*tensors): ###################################################################### # ## Conclusion # +# .. _pinmem_conclusion: +# # ## Additional resources # +# .. _pinmem_resources: +# \ No newline at end of file From 5cb0510639885c3661e264338175c4fc2adab9f0 Mon Sep 17 00:00:00 2001 From: Vincent Moens Date: Thu, 25 Jul 2024 12:23:39 +0100 Subject: [PATCH 18/54] black --- intermediate_source/pinmem_nonblock.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/intermediate_source/pinmem_nonblock.py b/intermediate_source/pinmem_nonblock.py index 7f0d8a89b0..f220aa00a9 100644 --- a/intermediate_source/pinmem_nonblock.py +++ b/intermediate_source/pinmem_nonblock.py @@ -498,4 +498,4 @@ def pin_copy_to_device_nonblocking(*tensors): # ## Additional resources # # .. _pinmem_resources: -# \ No newline at end of file +# From 01e580da0662a8fce8a7a14d935967d62b7f245e Mon Sep 17 00:00:00 2001 From: Vincent Moens Date: Thu, 25 Jul 2024 14:36:32 +0100 Subject: [PATCH 19/54] amend --- intermediate_source/pinmem_nonblock.py | 250 +++++++++++++++++-------- 1 file changed, 175 insertions(+), 75 deletions(-) diff --git a/intermediate_source/pinmem_nonblock.py b/intermediate_source/pinmem_nonblock.py index f220aa00a9..2682c1f0ef 100644 --- a/intermediate_source/pinmem_nonblock.py +++ b/intermediate_source/pinmem_nonblock.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- """ -A guide on good usage of `non_blocking` and `pin_memory()` in PyTorch -===================================================================== +A guide on good usage of ``non_blocking`` and ``pin_memory()`` in PyTorch +========================================================================= **Author**: `Vincent Moens `_ @@ -11,16 +11,17 @@ Transferring data from the CPU to the GPU is fundamental in many PyTorch applications. It's crucial for users to understand the most effective tools and options available for moving data between devices. This tutorial examines two key methods for device-to-device data transfer in PyTorch: -:meth:`~torch.Tensor.pin_memory` and :meth:`~torch.Tensor.to` with the `non_blocking=True` option. +:meth:`~torch.Tensor.pin_memory` and :meth:`~torch.Tensor.to` with the ``non_blocking=True`` option. Key Learnings ~~~~~~~~~~~~~ Optimizing the transfer of tensors from the CPU to the GPU can be achieved through asynchronous transfers and memory pinning. However, there are important considerations: -- Using `tensor.pin_memory().to(device, non_blocking=True)` can be up to twice as slow as a straightforward `tensor.to(device)`. -- Generally, `tensor.to(device, non_blocking=True)` is an effective choice for enhancing transfer speed. -- While `cpu_tensor.to("cuda", non_blocking=True).mean()` executes correctly, attempting - `cuda_tensor.to("cpu", non_blocking=True).mean()` will result in erroneous outputs. + +- Using ``tensor.pin_memory().to(device, non_blocking=True)`` can be up to twice as slow as a straightforward ``tensor.to(device)``. +- Generally, ``tensor.to(device, non_blocking=True)`` is an effective choice for enhancing transfer speed. +- While ``cpu_tensor.to("cuda", non_blocking=True).mean()`` executes correctly, attempting + ``cuda_tensor.to("cpu", non_blocking=True).mean()`` will result in erroneous outputs. """ @@ -34,14 +35,18 @@ # We start by outlining the theory surrounding these concepts, and then move to concrete test examples of the features. # # - :ref:`Background ` +# # - :ref:`Memory management basics ` # - :ref:`CUDA and (non-)pageable memory ` -# - :ref:`Asynchronous vs. Synchronous Operations with `non_blocking=True` ` +# - :ref:`Asynchronous vs. Synchronous Operations with non_blocking=True ` +# # - :ref:`A PyTorch perspective ` +# # - :ref:`pin_memory ` # - :ref:`non_blocking=True ` # - :ref:`Synergies ` -# - :ref:`Other directions (GPU -> CPU) ` +# - :ref:`Other copy directions (GPU -> CPU) ` +# # - :ref:`Practical recommendations ` # - :ref:`Additional considerations ` # - :ref:`Conclusion ` @@ -96,7 +101,7 @@ # More precisely, when CUDA sends pageable data from CPU to GPU, it must first create a page-locked copy of that data # before making the transfer. # -# Asynchronous vs. Synchronous Operations with `non_blocking=True` (CUDA `cudaMemcpyAsync`) +# Asynchronous vs. Synchronous Operations with ``non_blocking=True`` (CUDA ``cudaMemcpyAsync``) # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # # .. _pinmem_async_sync: @@ -105,10 +110,10 @@ # operations synchronously or asynchronously with respect to the host. # # In practice, when calling :meth:`~torch.Tensor.to`, PyTorch always makes a call to -# [`cudaMemcpyAsync`](https://docs.nvidia.com/cuda/cuda-runtime-api/group__CUDART__MEMORY.html#group__CUDART__MEMORY_1g85073372f776b4c4d5f89f7124b7bf79). -# If `non_blocking=False` (default), a `cudaStreamSynchronize` will be called after each and every `cudaMemcpyAsync`, making +# `cudaMemcpyAsync `_. +# If ``non_blocking=False`` (default), a ``cudaStreamSynchronize`` will be called after each and every ``cudaMemcpyAsync``, making # the call to :meth:`~torch.Tensor.to` blocking in the main thread. -# If `non_blocking=True`, no synchronization is triggered, and the main thread on the host is not blocked. +# If ``non_blocking=True``, no synchronization is triggered, and the main thread on the host is not blocked. # Therefore, from the host perspective, multiple tensors can be sent to the device simultaneously, # as the thread does not need to wait for one transfer to be completed to initiate the other. # @@ -131,17 +136,17 @@ # # .. _pinmem_pt_perspective: # -# `pin_memory()` -# ~~~~~~~~~~~~~~ +# ``pin_memory()`` +# ~~~~~~~~~~~~~~~~ # # .. _pinmem_pinmem: # -# PyTorch offers the possibility to create and send tensors to page-locked memory through the `pin_memory` functions and -# arguments. -# Any cpu tensor on a machine where a cuda is initialized can be sent to pinned memory through the `pin_memory` -# method. Importantly, `pin_memory` is blocking on the host: the main thread will wait for the tensor to be copied to +# PyTorch offers the possibility to create and send tensors to page-locked memory through the +# :meth:`~torch.Tensor.pin_memory` method and constructor arguments. +# Cpu tensors on a machine where a cuda is initialized can be cast to pinned memory through the :meth:`~torch.Tensor.pin_memory` +# method. Importantly, ``pin_memory`` is blocking on the main thread of the host: it will wait for the tensor to be copied to # page-locked memory before executing the next operation. -# New tensors can be directly created in pinned memory with functions like `torch.zeros`, `torch.ones` and other +# New tensors can be directly created in pinned memory with functions like :func:`~torch.zeros`, :func:`~torch.ones` and other # constructors. # # Let us check the speed of pinning memory and sending tensors to cuda: @@ -157,17 +162,23 @@ def timer(cmd): return Timer(cmd, globals=globals()).adaptive_autorange().median * 1000 +# A tensor in pageable memory pageable_tensor = torch.randn(1_000_000) +# A tensor in page-locked (pinned) memory pinned_tensor = torch.randn(1_000_000, pin_memory=True) +# Runtimes: pageable_to_device = timer("pageable_tensor.to('cuda:0')") pinned_to_device = timer("pinned_tensor.to('cuda:0')") pin_mem = timer("pageable_tensor.pin_memory()") pin_mem_to_device = timer("pageable_tensor.pin_memory().to('cuda:0')") + +# Ratios: r1 = pinned_to_device / pageable_to_device r2 = pin_mem_to_device / pageable_to_device +# Create a figure with the results fig, ax = plt.subplots() xlabels = [0, 1, 2] @@ -187,6 +198,7 @@ def timer(cmd): plt.show() +# Clear tensors del pageable_tensor, pinned_tensor gc.collect() @@ -195,22 +207,26 @@ def timer(cmd): # We can observe that casting a pinned-memory tensor to GPU is indeed much faster than a pageable tensor, because under # the hood, a pageable tensor must be copied to pinned memory before being sent to GPU. # -# However, calling `pin_memory()` on a pageable tensor before casting it to GPU does not bring any speed-up, on the -# contrary this call is actually slower than just executing the transfer. Again, this makes sense, since we're actually -# asking python to execute an operation that CUDA will perform anyway before copying the data from host to device. +# However, contrary to a somewhat common belief, calling :meth:`~torch.Tensor.pin_memory()` on a pageable tensor before +# casting it to GPU should not bring any speed-up, on the contrary this call is usually slower than just executing +# the transfer. This makes sense, since we're actually asking python to execute an operation that CUDA will perform +# anyway before copying the data from host to device. # -# `non_blocking=True` -# ~~~~~~~~~~~~~~~~~~~ +# ``non_blocking=True`` +# ~~~~~~~~~~~~~~~~~~~~~ # # .. _pinmem_nb: # # As mentioned earlier, many PyTorch operations have the option of being executed asynchronously with respect to the host -# through the `non_blocking` argument. -# Here, to account accurately of the benefits of using `non_blocking`, we will design a slightly more involved experiment -# since we want to assess how fast it is to send multiple tensors to GPU with and without calling `non_blocking`. +# through the ``non_blocking`` argument. +# +# Here, to account accurately of the benefits of using ``non_blocking``, we will design a slightly more complex +# experiment since we want to assess how fast it is to send multiple tensors to GPU with and without calling +# ``non_blocking``. # +# A simple loop that copies all tensors to cuda def copy_to_device(*tensors): result = [] for tensor in tensors: @@ -218,6 +234,7 @@ def copy_to_device(*tensors): return result +# A loop that copies all tensors to cuda asynchronously def copy_to_device_nonblocking(*tensors): result = [] for tensor in tensors: @@ -227,12 +244,15 @@ def copy_to_device_nonblocking(*tensors): return result +# Create a list of tensors tensors = [torch.randn(1000) for _ in range(1000)] to_device = timer("copy_to_device(*tensors)") to_device_nonblocking = timer("copy_to_device_nonblocking(*tensors)") +# Ratio r1 = to_device_nonblocking / to_device +# Plot the results fig, ax = plt.subplots() xlabels = [0, 1] @@ -251,10 +271,10 @@ def copy_to_device_nonblocking(*tensors): ###################################################################### -# To get a better sense of what is happening here, let us run a profiling of these two code executions: +# To get a better sense of what is happening here, let us profile these two functions: -from torch.profiler import profile, record_function, ProfilerActivity +from torch.profiler import profile, ProfilerActivity def profile_mem(cmd): @@ -264,7 +284,16 @@ def profile_mem(cmd): print(prof.key_averages().table(row_limit=10)) +###################################################################### +# Let's see the call stack with a regular ``to(device)`` first: +# + print("Call to `to(device)`", profile_mem("copy_to_device(*tensors)")) + +###################################################################### +# and now the ``non_blocing`` version: +# + print( "Call to `to(device, non_blocking=True)`", profile_mem("copy_to_device_nonblocking(*tensors)"), @@ -272,12 +301,14 @@ def profile_mem(cmd): ###################################################################### -# The results are without any doubt better when using `non_blocking=True`, as all transfers are initiated simultaneously -# on the host side. -# Note that, interestingly, `to("cuda")` actually performs the same asynchronous device casting operation as the one with -# `non_blocking=True` with a synchronization point after each copy. +# The results are without any doubt better when using ``non_blocking=True``, as all transfers are initiated simultaneously +# on the host side and only one synchronization is done. +# +# The benefit will vary depending on the number and the size of the tensors as well as depending on the hardware being +# used. # -# The benefit will vary depending on the number and the size of the tensors as well as depending on the hardware being used. +# .. note:: Interestingly, the blocking ``to("cuda")`` actually performs the same asynchronous device casting operation +# (``cudaMemcpyAsync``) as the one with ```non_blocking=True`` with a synchronization point after each copy. # # Synergies # ~~~~~~~~~ @@ -286,7 +317,9 @@ def profile_mem(cmd): # # Now that we have made the point that data transfer of tensors already in pinned memory to GPU is faster than from # pageable memory, and that we know that doing these transfers asynchronously is also faster than synchronously, we can -# benchmark the various combinations at hand: +# benchmark combinations of these approaches. First, let's write a couple of new functions that will call ``pin_memory`` +# and ``to(device)`` on each tensor: +# def pin_copy_to_device(*tensors): @@ -305,8 +338,14 @@ def pin_copy_to_device_nonblocking(*tensors): return result +###################################################################### +# Let's also create a list of pinned tensors +# tensors_pinned = [torch.randn(1000, pin_memory=True) for _ in range(1000)] +###################################################################### +# And now the runs: +# pin_and_copy = timer("pin_copy_to_device(*tensors)") pin_and_copy_nb = timer("pin_copy_to_device_nonblocking(*tensors)") @@ -316,6 +355,7 @@ def pin_copy_to_device_nonblocking(*tensors): pinned_copy = timer("copy_to_device(*tensors_pinned)") pinned_copy_nb = timer("copy_to_device_nonblocking(*tensors_pinned)") +# Plot strategies = ("pageable copy", "pinned copy", "pin and copy") blocking = { "blocking": [page_copy, pinned_copy, pin_and_copy], @@ -348,16 +388,17 @@ def pin_copy_to_device_nonblocking(*tensors): ###################################################################### -# Other directions (GPU -> CPU, CPU -> MPS etc.) -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# Other copy directions (GPU -> CPU, CPU -> MPS etc.) +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # # .. _pinmem_otherdir: # -# So far, we have assumed that doing asynchronous copies from CPU to GPU was safe. -# Indeed, it is a safe thing to do because CUDA will synchronize whenever it is needed to make sure that the data being -# read is not garbage. -# However, any other copy (e.g., from GPU to CPU) has no guarantee whatsoever that the copy will be completed when the -# data is read. In fact, if no explicit synchronization is done, the data on the host can be garbage: +# Until now, we have operated under the assumption that asynchronous copies from the CPU to the GPU are safe. +# This is generally true because CUDA automatically handles synchronization to ensure that the data being accessed is +# valid at read time. +# However, this guarantee does not extend to transfers in the opposite direction, from GPU to CPU. +# Without explicit synchronization, these transfers offer no assurance that the copy will be complete at the time of +# data access. Consequently, the data on the host might be incomplete or incorrect, effectively rendering it garbage: # @@ -393,10 +434,12 @@ def pin_copy_to_device_nonblocking(*tensors): ###################################################################### -# The same observation could be made with copies from CPU to a non-CUDA device such as MPS. +# The same considerations apply to copies from the CPU to non-CUDA devices, such as MPS. +# Generally, asynchronous copies to a device are safe without explicit synchronization only when the target is a +# CUDA-enabled device. # -# In summary, copying data from CPU to GPU is safe when using `non_blocking=True`, but for any other direction, -# `non_blocking=True` can still be used but the user must make sure that a device synchronization is executed after +# In summary, copying data from CPU to GPU is safe when using ``non_blocking=True``, but for any other direction, +# ``non_blocking=True`` can still be used but the user must make sure that a device synchronization is executed after # the data is accessed. # # Practical recommendations @@ -405,35 +448,40 @@ def pin_copy_to_device_nonblocking(*tensors): # .. _pinmem_recom: # # We can now wrap up some early recommendations based on our observations: -# In general, `non_blocking=True` will provide a good speed of transfer, regardless of whether the original tensor is or -# isn't in pinned memory. If the tensor is already in pinned memory, the transfer can be accelerated, but sending it to -# pin memory manually is a blocking operation on the host and hence will annihilate much of the benefit of using -# `non_blocking=True` (and CUDA does the `pin_memory` transfer anyway). # -# One might now legitimately ask what use there is for the `pin_memory()` method within the `torch.Tensor` class. In the -# following section, we will explore further how this can be used to accelerate the data transfer even more. +# In general, ``non_blocking=True`` will provide a good throughput, regardless of whether the original tensor is or +# isn't in pinned memory. +# If the tensor is already in pinned memory, the transfer can be accelerated, but sending it to +# pin memory manually from python main thread is a blocking operation on the host, and hence will annihilate much of +# the benefit of using ``non_blocking=True`` (as CUDA does the `pin_memory` transfer anyway). +# +# One might now legitimately ask what use there is for the :meth:`~torch.Tensor.pin_memory` method. +# In the following section, we will explore further how this can be used to accelerate the data transfer even more. # # Additional considerations # ------------------------- # # .. _pinmem_considerations: # -# PyTorch notoriously provides a `DataLoader` class that accepts a `pin_memory` argument. -# Given everything we have said so far about calls to `pin_memory`, how does the dataloader manage to accelerate data -# transfers? +# PyTorch notoriously provides a :class:`~torch.utils.data.DataLoader` class which constructor accepts a +# ``pin_memory`` argument. +# Considering our previous discussion on ``pin_memory``, you might wonder how the ``DataLoader`` manages to +# accelerate data transfers if memory pinning is inherently blocking. +# +# The key lies in the DataLoader's use of a separate thread to handle the transfer of data from pageable to pinned +# memory, thus preventing any blockage in the main thread. # -# The answer is resides in the fact that the dataloader reserves a separate thread to copy the data from pageable to -# pinned memory, thereby avoiding to block the main thread with this. Consider the following example, where we send a list of -# tensors to cuda after calling pin_memory on a separate thread: +# To illustrate this, we will use the TensorDict primitive from the homonymous library. +# When invoking :meth:`~tensordict.TensorDict.to`, the default behavior is to send tensors to the device asynchronously, +# followed by a single call to ``torch.device.synchronize()`` afterwards. # -# A more isolated example of this is the TensorDict primitive from the homonymous library: when calling `TensorDict.to(device)`, -# the default behavior is to send these tensors to the device asynchronously and make a `device.synchronize()` call after. -# `TensorDict.to()` also offers a `non_blocking_pin` argument which will spawn multiple threads to do the calls to `pin_memory()` -# before launching the calls to `to(device)`. -# This can further speed up the copies as the following example shows: +# Additionally, ``TensorDict.to()`` includes a ``non_blocking_pin`` option which initiates multiple threads to execute +# ``pin_memory()`` before proceeding with to ``to(device)``. +# This approach can further accelerate data transfers, as demonstrated in the following example: # # .. code-block:: bash # +# # Install tensordict with the following command # !pip3 install https://github.com/pytorch/tensordict # @@ -441,18 +489,21 @@ def pin_copy_to_device_nonblocking(*tensors): import torch from torch.utils.benchmark import Timer +# Create the dataset td = TensorDict({str(i): torch.randn(1_000_000) for i in range(100)}) +# Runtimes copy_blocking = timer("td.to('cuda:0', non_blocking=False)") copy_non_blocking = timer("td.to('cuda:0')") copy_pin_nb = timer("td.to('cuda:0', non_blocking_pin=True, num_threads=0)") copy_pin_multithread_nb = timer("td.to('cuda:0', non_blocking_pin=True, num_threads=4)") - +# Rations r1 = copy_non_blocking / copy_blocking r2 = copy_pin_nb / copy_blocking r3 = copy_pin_multithread_nb / copy_blocking +# Figure fig, ax = plt.subplots() xlabels = [0, 1, 2, 3] @@ -475,26 +526,75 @@ def pin_copy_to_device_nonblocking(*tensors): plt.show() ###################################################################### -# As a side note, it may be tempting to create everlasting buffers in pinned memory and copy tensors from pageable memory -# to pinned memory, and use these as shuttle before sending the data to GPU. -# Unfortunately, this does not speed up computation because the bottleneck of copying data to pinned memory is still present. +# As an additional note, while it might seem advantageous to create permanent buffers in pinned memory to shuttle +# tensors from pageable memory before transferring them to the GPU, this strategy does not necessarily expedite +# computation. The inherent bottleneck caused by copying data into pinned memory remains a limiting factor. +# +# Moreover, transferring data that resides on disk (whether in shared memory or files) to the GPU typically requires an +# intermediate step of copying the data into pinned memory (located in RAM). +# Utilizing non_blocking for large data transfers in this context can significantly increase RAM consumption, +# potentially leading to adverse effects. +# +# In practice, there is no one-size-fits-all solution. +# The effectiveness of using multithreaded ``pin_memory`` combined with ``non_blocking`` transfers depends on a +# variety of factors, including the specific system, operating system, hardware, and the nature of the tasks +# being executed. +# Here is a list of factors to check when trying to speed-up data transfers between CPU and GPU, or comparing +# throughput's across scenarios: +# +# - **Number of available cores** +# +# How many CPU cores are available? Is the system shared with other users or processes that might compete for +# resources? +# +# - **Core utilization** +# +# Are the CPU cores heavily utilized by other processes? Does the application perform other CPU-intensive tasks +# concurrently with data transfers? +# +# - **Memory utilization** # -# Another consideration is that transferring data that is stored on disk (shared memory or files) to GPU will usually -# require the data to be copied to pinned memory (which is on RAM) as an intermediate step. +# How much pageable and page-locked memory is currently being used? Is there sufficient free memory to allocate +# additional pinned memory without affecting system performance? Remember that nothing comes for free, for instance +# ``pin_memory`` will consume RAM and may impact other tasks. # -# Using `non_blocking` in these context for large amount of data may have devastating effects on RAM consumption. -# In practice, there is no silver bullet, and the performance of any combination of multithreaded pin_memory and -# non_blocking will depend on multiple factors such as the system being used, the OS, the hardware and the tasks being performed. +# - **CUDA Device Capabilities** +# +# Does the GPU support multiple DMA engines for concurrent data transfers? What are the specific capabilities and +# limitations of the CUDA device being used? +# +# - **Number of tensors to be sent** +# +# How many tensors are transferred in a typical operation? +# +# - **Size of the tensors to be sent** +# +# What is the size of the tensors being transferred? A few large tensors or many small tensors may not benefit from +# the same transfer program. +# +# - **System Architecture** +# +# How is the system's architecture influencing data transfer speeds (e.g., bus speeds, network latency)? +# +# Additionally, allocating a large number of tensors or sizable tensors in pinned memory can monopolize a substantial +# portion of RAM. +# This reduces the available memory for other critical operations, such as paging, which can negatively impact the +# overall performance of an algorithm. # -# Finally, creating a large number of tensors or a few large tensors in pinned memory will effectively reserve more RAM -# than pageable tensors would, thereby lowering the amount of available RAM for other operations (such as swapping pages -# in and out), which can have a negative impact over the overall runtime of an algorithm. ###################################################################### # ## Conclusion # # .. _pinmem_conclusion: # +# Throughout this tutorial, we have explored several critical factors that influence transfer speeds and memory +# management when sending tensors from the host to the device. We've learned that using ``non_blocking=True`` generally +# accelerates data transfers, and that :meth:`~torch.Tensor.pin_memory` can also enhance performance if implemented +# correctly. However, these techniques require careful design and calibration to be effective. +# +# Remember that profiling your code and keeping an eye on the memory consumption are essential to optimize resource +# usage and achieve the best possible performance. +# # ## Additional resources # # .. _pinmem_resources: From 3ad73ad313d2f6fe3ad83d10a7d4a38a710e47ab Mon Sep 17 00:00:00 2001 From: Vincent Moens Date: Thu, 25 Jul 2024 15:59:10 +0100 Subject: [PATCH 20/54] amend --- intermediate_source/pinmem_nonblock.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/intermediate_source/pinmem_nonblock.py b/intermediate_source/pinmem_nonblock.py index 2682c1f0ef..069f7a223b 100644 --- a/intermediate_source/pinmem_nonblock.py +++ b/intermediate_source/pinmem_nonblock.py @@ -159,7 +159,14 @@ def timer(cmd): - return Timer(cmd, globals=globals()).adaptive_autorange().median * 1000 + median = ( + Timer(cmd, globals=globals()) + .adaptive_autorange(min_run_time=1.0, max_run_time=20.0) + .median + * 1000 + ) + print(f"{cmd}: {median: 4.4f} ms") + return median # A tensor in pageable memory @@ -378,7 +385,7 @@ def pin_copy_to_device_nonblocking(*tensors): # Add some text for labels, title and custom x-axis tick labels, etc. ax.set_ylabel("Runtime (ms)") ax.set_title("Runtime (pin-mem and non-blocking)") -ax.set_xticks([]) +ax.set_xticks(strategies) ax.legend(loc="upper left", ncols=3) plt.show() From 8b2ec64b7ea5ed6e2d504bf091f7434833584e82 Mon Sep 17 00:00:00 2001 From: Vincent Moens Date: Thu, 25 Jul 2024 16:03:52 +0100 Subject: [PATCH 21/54] amend --- intermediate_source/pinmem_nonblock.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/intermediate_source/pinmem_nonblock.py b/intermediate_source/pinmem_nonblock.py index 069f7a223b..bb1aac5901 100644 --- a/intermediate_source/pinmem_nonblock.py +++ b/intermediate_source/pinmem_nonblock.py @@ -192,7 +192,8 @@ def timer(cmd): bar_labels = [ "pageable_tensor.to(device) (1x)", f"pinned_tensor.to(device) ({r1:4.2f}x)", - f"pageable_tensor.pin_memory().to(device) ({r2:4.2f}x)", + f"pageable_tensor.pin_memory().to(device) ({r2:4.2f}x)" + f"\npin_memory()={100*pin_mem/pin_mem_to_device:.2f}\% of runtime.", ] values = [pageable_to_device, pinned_to_device, pin_mem_to_device] colors = ["tab:blue", "tab:red", "tab:orange"] From d9805533999d7095825b4fd81a50bef5ecf5a2e6 Mon Sep 17 00:00:00 2001 From: Vincent Moens Date: Thu, 25 Jul 2024 16:09:11 +0100 Subject: [PATCH 22/54] amend --- intermediate_source/pinmem_nonblock.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/intermediate_source/pinmem_nonblock.py b/intermediate_source/pinmem_nonblock.py index bb1aac5901..4e1f35e22e 100644 --- a/intermediate_source/pinmem_nonblock.py +++ b/intermediate_source/pinmem_nonblock.py @@ -193,7 +193,7 @@ def timer(cmd): "pageable_tensor.to(device) (1x)", f"pinned_tensor.to(device) ({r1:4.2f}x)", f"pageable_tensor.pin_memory().to(device) ({r2:4.2f}x)" - f"\npin_memory()={100*pin_mem/pin_mem_to_device:.2f}\% of runtime.", + f"\npin_memory()={100*pin_mem/pin_mem_to_device:.2f}% of runtime.", ] values = [pageable_to_device, pinned_to_device, pin_mem_to_device] colors = ["tab:blue", "tab:red", "tab:orange"] @@ -386,7 +386,9 @@ def pin_copy_to_device_nonblocking(*tensors): # Add some text for labels, title and custom x-axis tick labels, etc. ax.set_ylabel("Runtime (ms)") ax.set_title("Runtime (pin-mem and non-blocking)") -ax.set_xticks(strategies) +ax.set_xticks([0, 1, 2]) +ax.set_xticklabels(strategies) +plt.setp(ax.get_xticklabels(), rotation=45, ha="right", rotation_mode="anchor") ax.legend(loc="upper left", ncols=3) plt.show() From ac5b5e463d44ae2a1c307e956adac1b65d7750a6 Mon Sep 17 00:00:00 2001 From: Vincent Moens Date: Thu, 25 Jul 2024 17:56:11 +0100 Subject: [PATCH 23/54] amend --- intermediate_source/pinmem_nonblock.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/intermediate_source/pinmem_nonblock.py b/intermediate_source/pinmem_nonblock.py index 4e1f35e22e..a52dc55ceb 100644 --- a/intermediate_source/pinmem_nonblock.py +++ b/intermediate_source/pinmem_nonblock.py @@ -208,7 +208,7 @@ def timer(cmd): # Clear tensors del pageable_tensor, pinned_tensor -gc.collect() +_ = gc.collect() ###################################################################### # @@ -394,7 +394,7 @@ def pin_copy_to_device_nonblocking(*tensors): plt.show() del tensors, tensors_pinned -gc.collect() +_ = gc.collect() ###################################################################### From 68241feea997cca7b397b737f62e3da0d8731f76 Mon Sep 17 00:00:00 2001 From: Vincent Moens Date: Fri, 26 Jul 2024 07:35:28 +0100 Subject: [PATCH 24/54] amend --- intermediate_source/pinmem_nonblock.py | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/intermediate_source/pinmem_nonblock.py b/intermediate_source/pinmem_nonblock.py index a52dc55ceb..63ae1cbde3 100644 --- a/intermediate_source/pinmem_nonblock.py +++ b/intermediate_source/pinmem_nonblock.py @@ -500,13 +500,18 @@ def pin_copy_to_device_nonblocking(*tensors): from torch.utils.benchmark import Timer # Create the dataset -td = TensorDict({str(i): torch.randn(1_000_000) for i in range(100)}) - -# Runtimes -copy_blocking = timer("td.to('cuda:0', non_blocking=False)") -copy_non_blocking = timer("td.to('cuda:0')") -copy_pin_nb = timer("td.to('cuda:0', non_blocking_pin=True, num_threads=0)") -copy_pin_multithread_nb = timer("td.to('cuda:0', non_blocking_pin=True, num_threads=4)") +for s0 in (10, 100, 1000, 10_000, 100_000, 1_000_000): + for s1 in (10, 100, 1000, 10_000): + if s0 * s1 >= 1e9: + continue + print(f"\n\ns0={s0}, s1={s1}") + td = TensorDict({str(i): torch.randn(1_000_000) for i in range(100)}) + + # Runtimes + copy_blocking = timer("td.to('cuda:0', non_blocking=False)") + copy_non_blocking = timer("td.to('cuda:0')") + copy_pin_nb = timer("td.to('cuda:0', non_blocking_pin=True, num_threads=0)") + copy_pin_multithread_nb = timer("td.to('cuda:0', non_blocking_pin=True, num_threads=4)") # Rations r1 = copy_non_blocking / copy_blocking From 96e1582f8862c2867029e6372c7b61b4fc134b8f Mon Sep 17 00:00:00 2001 From: Vincent Moens Date: Fri, 26 Jul 2024 07:35:48 +0100 Subject: [PATCH 25/54] amend --- intermediate_source/pinmem_nonblock.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/intermediate_source/pinmem_nonblock.py b/intermediate_source/pinmem_nonblock.py index 63ae1cbde3..4dbf31f806 100644 --- a/intermediate_source/pinmem_nonblock.py +++ b/intermediate_source/pinmem_nonblock.py @@ -501,11 +501,11 @@ def pin_copy_to_device_nonblocking(*tensors): # Create the dataset for s0 in (10, 100, 1000, 10_000, 100_000, 1_000_000): - for s1 in (10, 100, 1000, 10_000): + for s1 in (10, 100, 1000, 10_000, 100_000, 1_000_000): if s0 * s1 >= 1e9: continue print(f"\n\ns0={s0}, s1={s1}") - td = TensorDict({str(i): torch.randn(1_000_000) for i in range(100)}) + td = TensorDict({str(i): torch.randn(s0) for i in range(s1)}) # Runtimes copy_blocking = timer("td.to('cuda:0', non_blocking=False)") From 96e703aeb9e3a0dad86150909578259ce8577678 Mon Sep 17 00:00:00 2001 From: Vincent Moens Date: Fri, 26 Jul 2024 13:51:49 +0100 Subject: [PATCH 26/54] amend --- .ci/docker/requirements.txt | 2 +- intermediate_source/pinmem_nonblock.py | 19 +++++++------------ 2 files changed, 8 insertions(+), 13 deletions(-) diff --git a/.ci/docker/requirements.txt b/.ci/docker/requirements.txt index 9dfdf6bb93..9e379ea6e0 100644 --- a/.ci/docker/requirements.txt +++ b/.ci/docker/requirements.txt @@ -30,7 +30,7 @@ pytorch-lightning torchx # TODO: use stable 0.5 when released -e git+https://github.com/pytorch/rl.git#egg=torchrl --e git+https://github.com/pytorch/tensordict.git#egg=tensordict +-e git+https://github.com/pytorch/tensordict.git@del-futures#egg=tensordict ax-platform nbformat>==5.9.2 datasets diff --git a/intermediate_source/pinmem_nonblock.py b/intermediate_source/pinmem_nonblock.py index 4dbf31f806..3e259a02d7 100644 --- a/intermediate_source/pinmem_nonblock.py +++ b/intermediate_source/pinmem_nonblock.py @@ -500,18 +500,13 @@ def pin_copy_to_device_nonblocking(*tensors): from torch.utils.benchmark import Timer # Create the dataset -for s0 in (10, 100, 1000, 10_000, 100_000, 1_000_000): - for s1 in (10, 100, 1000, 10_000, 100_000, 1_000_000): - if s0 * s1 >= 1e9: - continue - print(f"\n\ns0={s0}, s1={s1}") - td = TensorDict({str(i): torch.randn(s0) for i in range(s1)}) - - # Runtimes - copy_blocking = timer("td.to('cuda:0', non_blocking=False)") - copy_non_blocking = timer("td.to('cuda:0')") - copy_pin_nb = timer("td.to('cuda:0', non_blocking_pin=True, num_threads=0)") - copy_pin_multithread_nb = timer("td.to('cuda:0', non_blocking_pin=True, num_threads=4)") +td = TensorDict({str(i): torch.randn(1_000_000) for i in range(1000)}) + +# Runtimes +copy_blocking = timer("td.to('cuda:0', non_blocking=False)") +copy_non_blocking = timer("td.to('cuda:0')") +copy_pin_nb = timer("td.to('cuda:0', non_blocking_pin=True, num_threads=0)") +copy_pin_multithread_nb = timer("td.to('cuda:0', non_blocking_pin=True, num_threads=4)") # Rations r1 = copy_non_blocking / copy_blocking From 8a6f90de83f146b87243cf5d678ef2ad350426e7 Mon Sep 17 00:00:00 2001 From: Vincent Moens Date: Fri, 26 Jul 2024 13:56:35 +0100 Subject: [PATCH 27/54] amend --- intermediate_source/pinmem_nonblock.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/intermediate_source/pinmem_nonblock.py b/intermediate_source/pinmem_nonblock.py index 3e259a02d7..6ca0e4dde1 100644 --- a/intermediate_source/pinmem_nonblock.py +++ b/intermediate_source/pinmem_nonblock.py @@ -487,7 +487,7 @@ def pin_copy_to_device_nonblocking(*tensors): # # Additionally, ``TensorDict.to()`` includes a ``non_blocking_pin`` option which initiates multiple threads to execute # ``pin_memory()`` before proceeding with to ``to(device)``. -# This approach can further accelerate data transfers, as demonstrated in the following example: +# This approach can further accelerate data transfers, as demonstrated in the following example. # # .. code-block:: bash # @@ -536,6 +536,11 @@ def pin_copy_to_device_nonblocking(*tensors): plt.show() ###################################################################### +# In this example, we are transferring many large tensors from the CPU to the GPU. +# This scenario is ideal for utilizing multithreaded ``pin_memory()``, which can significantly enhance performance. +# However, if the tensors are small, the overhead associated with multithreading may outweigh the benefits. +# Similarly, if there are only a few tensors, the advantages of pinning tensors on separate threads become limited. +# # As an additional note, while it might seem advantageous to create permanent buffers in pinned memory to shuttle # tensors from pageable memory before transferring them to the GPU, this strategy does not necessarily expedite # computation. The inherent bottleneck caused by copying data into pinned memory remains a limiting factor. From 8aa882d43f4d5d64c47566de00b3e9fb25414fec Mon Sep 17 00:00:00 2001 From: Vincent Moens Date: Fri, 26 Jul 2024 15:31:22 +0100 Subject: [PATCH 28/54] amend --- .ci/docker/requirements.txt | 2 +- intermediate_source/pinmem_nonblock.py | 13 +++++++++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/.ci/docker/requirements.txt b/.ci/docker/requirements.txt index 9e379ea6e0..9dfdf6bb93 100644 --- a/.ci/docker/requirements.txt +++ b/.ci/docker/requirements.txt @@ -30,7 +30,7 @@ pytorch-lightning torchx # TODO: use stable 0.5 when released -e git+https://github.com/pytorch/rl.git#egg=torchrl --e git+https://github.com/pytorch/tensordict.git@del-futures#egg=tensordict +-e git+https://github.com/pytorch/tensordict.git#egg=tensordict ax-platform nbformat>==5.9.2 datasets diff --git a/intermediate_source/pinmem_nonblock.py b/intermediate_source/pinmem_nonblock.py index 6ca0e4dde1..7490747648 100644 --- a/intermediate_source/pinmem_nonblock.py +++ b/intermediate_source/pinmem_nonblock.py @@ -598,7 +598,8 @@ def pin_copy_to_device_nonblocking(*tensors): # ###################################################################### -# ## Conclusion +# Conclusion +# ---------- # # .. _pinmem_conclusion: # @@ -610,7 +611,15 @@ def pin_copy_to_device_nonblocking(*tensors): # Remember that profiling your code and keeping an eye on the memory consumption are essential to optimize resource # usage and achieve the best possible performance. # -# ## Additional resources +# Additional resources +# -------------------- # # .. _pinmem_resources: # +# If you are dealing with issues with memory copies when using CUDA devices or want to learn more about +# what was discussed in this tutorial, check the following references: +# +# - `CUDA toolkit memory management doc `_ +# - `CUDA pin-memory note `_ +# - tensordict :meth:`~tensordict.TensorDict.to` method; +# \ No newline at end of file From f9471effbdf628dc118b7dca15f579846a3238e7 Mon Sep 17 00:00:00 2001 From: Vincent Moens Date: Sun, 28 Jul 2024 10:57:55 +0100 Subject: [PATCH 29/54] Apply suggestions from code review Co-authored-by: Jane (Yuan) Xu <31798555+janeyx99@users.noreply.github.com> Co-authored-by: mikaylagawarecki Co-authored-by: Shagun Sodhani <1321193+shagunsodhani@users.noreply.github.com> --- intermediate_source/pinmem_nonblock.py | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/intermediate_source/pinmem_nonblock.py b/intermediate_source/pinmem_nonblock.py index 7490747648..bb450a67dd 100644 --- a/intermediate_source/pinmem_nonblock.py +++ b/intermediate_source/pinmem_nonblock.py @@ -44,7 +44,7 @@ # # - :ref:`pin_memory ` # - :ref:`non_blocking=True ` -# - :ref:`Synergies ` +# - :ref:`Synergies # - :ref:`Other copy directions (GPU -> CPU) ` # # - :ref:`Practical recommendations ` @@ -65,18 +65,18 @@ # # When one creates a CPU tensor in PyTorch, the content of this tensor needs to be placed # in memory. The memory we talk about here is a rather complex concept worth looking at carefully. -# We distinguish two types of memories that are handled by the Memory Management Unit: the main memory (for simplicity) -# and the disk (which may or may not be the hard drive). Together, the available space in disk and RAM (physical memory) +# We distinguish two types of memory that are handled by the Memory Management Unit: the main memory (for simplicity) +# and the swap space on disk (which may or may not be the hard drive). Together, the available space in disk and RAM (physical memory) # make up the virtual memory, which is an abstraction of the total resources available. # In short, the virtual memory makes it so that the available space is larger than what can be found on RAM in isolation # and creates the illusion that the main memory is larger than it actually is. # -# In normal circumstances, a regular CPU tensor is _paged_, which means that it is divided in blocks called _pages_ that +# In normal circumstances, a regular CPU tensor is pageable which means that it is divided in blocks called pages that # can live anywhere in the virtual memory (both in RAM or on disk). As mentioned earlier, this has the advantage that # the memory seems larger than what the main memory actually is. # # Typically, when a program accesses a page that is not in RAM, a "page fault" occurs and the operating system (OS) then brings -# back this page into RAM (_swap in_ or _page in_). +# back this page into RAM ("swap in" or "page in"). # In turn, the OS may have to _swap out_ (or _page out_) another page to make room for the new page. # # In contrast to pageable memory, a _pinned_ (or _page-locked_ or _non-pageable_) memory is a type of memory that cannot @@ -93,6 +93,7 @@ # .. _pinmem_cuda_pageable_mem: # # To understand how CUDA copies a tensor from CPU to CUDA, let's consider the two scenarios above: +# # - If the memory is page-locked, the device can access the memory directly in the main memory. The memory addresses are well # defined and functions that need to read these data can be significantly accelerated. # - If the memory is pageable, all the pages will have to be brought to the main memory before being sent to the GPU. @@ -143,7 +144,7 @@ # # PyTorch offers the possibility to create and send tensors to page-locked memory through the # :meth:`~torch.Tensor.pin_memory` method and constructor arguments. -# Cpu tensors on a machine where a cuda is initialized can be cast to pinned memory through the :meth:`~torch.Tensor.pin_memory` +# CPU tensors on a machine where CUDA is initialized can be cast to pinned memory through the :meth:`~torch.Tensor.pin_memory` # method. Importantly, ``pin_memory`` is blocking on the main thread of the host: it will wait for the tensor to be copied to # page-locked memory before executing the next operation. # New tensors can be directly created in pinned memory with functions like :func:`~torch.zeros`, :func:`~torch.ones` and other @@ -299,7 +300,7 @@ def profile_mem(cmd): print("Call to `to(device)`", profile_mem("copy_to_device(*tensors)")) ###################################################################### -# and now the ``non_blocing`` version: +# and now the ``non_blocking`` version: # print( @@ -316,7 +317,7 @@ def profile_mem(cmd): # used. # # .. note:: Interestingly, the blocking ``to("cuda")`` actually performs the same asynchronous device casting operation -# (``cudaMemcpyAsync``) as the one with ```non_blocking=True`` with a synchronization point after each copy. +# (``cudaMemcpyAsync``) as the one with ``non_blocking=True`` with a synchronization point after each copy. # # Synergies # ~~~~~~~~~ @@ -429,7 +430,7 @@ def pin_copy_to_device_nonblocking(*tensors): ) print("No test failed with non_blocking") except AssertionError: - print(f"One test failed with non_blocking: {i}th assertion!") + print(f"{i}th test failed with non_blocking. Skipping remaining tests") try: i = -1 for i in range(100): @@ -459,7 +460,7 @@ def pin_copy_to_device_nonblocking(*tensors): # # We can now wrap up some early recommendations based on our observations: # -# In general, ``non_blocking=True`` will provide a good throughput, regardless of whether the original tensor is or +# In general, ``non_blocking=True`` will provide good throughput, regardless of whether the original tensor is or # isn't in pinned memory. # If the tensor is already in pinned memory, the transfer can be accelerated, but sending it to # pin memory manually from python main thread is a blocking operation on the host, and hence will annihilate much of @@ -473,7 +474,7 @@ def pin_copy_to_device_nonblocking(*tensors): # # .. _pinmem_considerations: # -# PyTorch notoriously provides a :class:`~torch.utils.data.DataLoader` class which constructor accepts a +# PyTorch notoriously provides a :class:`~torch.utils.data.DataLoader` class whose constructor accepts a # ``pin_memory`` argument. # Considering our previous discussion on ``pin_memory``, you might wonder how the ``DataLoader`` manages to # accelerate data transfers if memory pinning is inherently blocking. @@ -498,7 +499,7 @@ def pin_copy_to_device_nonblocking(*tensors): from tensordict import TensorDict import torch from torch.utils.benchmark import Timer - +import matplotlib.pyplot as plt # Create the dataset td = TensorDict({str(i): torch.randn(1_000_000) for i in range(1000)}) From 26bb6699c40792d1b648e752d74da7768a1bf8a4 Mon Sep 17 00:00:00 2001 From: Vincent Moens Date: Sun, 28 Jul 2024 10:58:51 +0100 Subject: [PATCH 30/54] Update intermediate_source/pinmem_nonblock.py --- intermediate_source/pinmem_nonblock.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/intermediate_source/pinmem_nonblock.py b/intermediate_source/pinmem_nonblock.py index bb450a67dd..812e86adf7 100644 --- a/intermediate_source/pinmem_nonblock.py +++ b/intermediate_source/pinmem_nonblock.py @@ -597,8 +597,6 @@ def pin_copy_to_device_nonblocking(*tensors): # This reduces the available memory for other critical operations, such as paging, which can negatively impact the # overall performance of an algorithm. # - -###################################################################### # Conclusion # ---------- # From 085ee0d0581f4c80c19b845d86acd512e7ef7c9f Mon Sep 17 00:00:00 2001 From: Vincent Moens Date: Sun, 28 Jul 2024 16:09:00 -0400 Subject: [PATCH 31/54] more explicit example --- intermediate_source/pinmem_nonblock.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/intermediate_source/pinmem_nonblock.py b/intermediate_source/pinmem_nonblock.py index 812e86adf7..41721a3e7a 100644 --- a/intermediate_source/pinmem_nonblock.py +++ b/intermediate_source/pinmem_nonblock.py @@ -44,7 +44,7 @@ # # - :ref:`pin_memory ` # - :ref:`non_blocking=True ` -# - :ref:`Synergies +# - :ref:`Synergies ` # - :ref:`Other copy directions (GPU -> CPU) ` # # - :ref:`Practical recommendations ` @@ -348,22 +348,21 @@ def pin_copy_to_device_nonblocking(*tensors): ###################################################################### -# Let's also create a list of pinned tensors +# The benefits of using :meth:`~torch.Tensor.pin_memory` are more pronounced for +# somewhat large batches of large tensors: # -tensors_pinned = [torch.randn(1000, pin_memory=True) for _ in range(1000)] - -###################################################################### -# And now the runs: -# -pin_and_copy = timer("pin_copy_to_device(*tensors)") -pin_and_copy_nb = timer("pin_copy_to_device_nonblocking(*tensors)") +tensors = [torch.randn(1_000_000) for _ in range(1000)] page_copy = timer("copy_to_device(*tensors)") page_copy_nb = timer("copy_to_device_nonblocking(*tensors)") +tensors_pinned = [torch.randn(1_000_000, pin_memory=True) for _ in range(1000)] pinned_copy = timer("copy_to_device(*tensors_pinned)") pinned_copy_nb = timer("copy_to_device_nonblocking(*tensors_pinned)") +pin_and_copy = timer("pin_copy_to_device(*tensors)") +pin_and_copy_nb = timer("pin_copy_to_device_nonblocking(*tensors)") + # Plot strategies = ("pageable copy", "pinned copy", "pin and copy") blocking = { @@ -500,6 +499,7 @@ def pin_copy_to_device_nonblocking(*tensors): import torch from torch.utils.benchmark import Timer import matplotlib.pyplot as plt + # Create the dataset td = TensorDict({str(i): torch.randn(1_000_000) for i in range(1000)}) @@ -621,4 +621,4 @@ def pin_copy_to_device_nonblocking(*tensors): # - `CUDA toolkit memory management doc `_ # - `CUDA pin-memory note `_ # - tensordict :meth:`~tensordict.TensorDict.to` method; -# \ No newline at end of file +# From 84373c8162df025b254f1a2df2782b9d0e538e85 Mon Sep 17 00:00:00 2001 From: Vincent Moens Date: Mon, 29 Jul 2024 10:34:27 -0400 Subject: [PATCH 32/54] pyspelling --- en-wordlist.txt | 2 + intermediate_source/pinmem_nonblock.py | 52 +++++++++++++------------- 2 files changed, 28 insertions(+), 26 deletions(-) diff --git a/en-wordlist.txt b/en-wordlist.txt index 5dcc5ea764..62762ab69c 100644 --- a/en-wordlist.txt +++ b/en-wordlist.txt @@ -70,6 +70,8 @@ Ecker ExportDB FC FGSM +tensordict +DataLoader's FLAVA FSDP FX diff --git a/intermediate_source/pinmem_nonblock.py b/intermediate_source/pinmem_nonblock.py index 41721a3e7a..18278c0c7e 100644 --- a/intermediate_source/pinmem_nonblock.py +++ b/intermediate_source/pinmem_nonblock.py @@ -34,34 +34,34 @@ # # We start by outlining the theory surrounding these concepts, and then move to concrete test examples of the features. # -# - :ref:`Background ` +# - :ref:`Background ` # -# - :ref:`Memory management basics ` -# - :ref:`CUDA and (non-)pageable memory ` -# - :ref:`Asynchronous vs. Synchronous Operations with non_blocking=True ` +# - :ref:`Memory management basics ` +# - :ref:`CUDA and (non-)pageable memory ` +# - :ref:`Asynchronous vs. Synchronous Operations with non_blocking=True ` # -# - :ref:`A PyTorch perspective ` +# - :ref:`A PyTorch perspective ` # -# - :ref:`pin_memory ` -# - :ref:`non_blocking=True ` -# - :ref:`Synergies ` -# - :ref:`Other copy directions (GPU -> CPU) ` +# - :ref:`pin_memory ` +# - :ref:`non_blocking=True ` +# - :ref:`Synergies ` +# - :ref:`Other copy directions (GPU -> CPU) ` # -# - :ref:`Practical recommendations ` -# - :ref:`Additional considerations ` -# - :ref:`Conclusion ` -# - :ref:`Additional resources ` +# - :ref:`Practical recommendations ` +# - :ref:`Additional considerations ` +# - :ref:`Conclusion ` +# - :ref:`Additional resources ` # # # Background # ---------- # -# .. _pinmem_background: +# .. _pinned_memory_background: # # Memory management basics # ~~~~~~~~~~~~~~~~~~~~~~~~ # -# .. _pinmem_mem: +# .. _pinned_memory_memory: # # When one creates a CPU tensor in PyTorch, the content of this tensor needs to be placed # in memory. The memory we talk about here is a rather complex concept worth looking at carefully. @@ -90,7 +90,7 @@ # CUDA and (non-)pageable memory # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # -# .. _pinmem_cuda_pageable_mem: +# .. _pinned_memory_cuda_pageable_mem: # # To understand how CUDA copies a tensor from CPU to CUDA, let's consider the two scenarios above: # @@ -105,7 +105,7 @@ # Asynchronous vs. Synchronous Operations with ``non_blocking=True`` (CUDA ``cudaMemcpyAsync``) # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # -# .. _pinmem_async_sync: +# .. _pinned_memory_async_sync: # # When executing a copy from a host (e.g., CPU) to a device (e.g., GPU), the CUDA toolkit offers modalities to do these # operations synchronously or asynchronously with respect to the host. @@ -135,12 +135,12 @@ # A PyTorch perspective # --------------------- # -# .. _pinmem_pt_perspective: +# .. _pinned_memory_pt_perspective: # # ``pin_memory()`` # ~~~~~~~~~~~~~~~~ # -# .. _pinmem_pinmem: +# .. _pinned_memory_pinned: # # PyTorch offers the possibility to create and send tensors to page-locked memory through the # :meth:`~torch.Tensor.pin_memory` method and constructor arguments. @@ -224,7 +224,7 @@ def timer(cmd): # ``non_blocking=True`` # ~~~~~~~~~~~~~~~~~~~~~ # -# .. _pinmem_nb: +# .. _pinned_memory_non_blocking: # # As mentioned earlier, many PyTorch operations have the option of being executed asynchronously with respect to the host # through the ``non_blocking`` argument. @@ -322,7 +322,7 @@ def profile_mem(cmd): # Synergies # ~~~~~~~~~ # -# .. _pinmem_synergies: +# .. _pinned_memory_synergies: # # Now that we have made the point that data transfer of tensors already in pinned memory to GPU is faster than from # pageable memory, and that we know that doing these transfers asynchronously is also faster than synchronously, we can @@ -401,7 +401,7 @@ def pin_copy_to_device_nonblocking(*tensors): # Other copy directions (GPU -> CPU, CPU -> MPS etc.) # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # -# .. _pinmem_otherdir: +# .. _pinned_memory_other_direction: # # Until now, we have operated under the assumption that asynchronous copies from the CPU to the GPU are safe. # This is generally true because CUDA automatically handles synchronization to ensure that the data being accessed is @@ -455,7 +455,7 @@ def pin_copy_to_device_nonblocking(*tensors): # Practical recommendations # ------------------------- # -# .. _pinmem_recom: +# .. _pinned_memory_recommendations: # # We can now wrap up some early recommendations based on our observations: # @@ -471,7 +471,7 @@ def pin_copy_to_device_nonblocking(*tensors): # Additional considerations # ------------------------- # -# .. _pinmem_considerations: +# .. _pinned_memory_considerations: # # PyTorch notoriously provides a :class:`~torch.utils.data.DataLoader` class whose constructor accepts a # ``pin_memory`` argument. @@ -600,7 +600,7 @@ def pin_copy_to_device_nonblocking(*tensors): # Conclusion # ---------- # -# .. _pinmem_conclusion: +# .. _pinned_memory_conclusion: # # Throughout this tutorial, we have explored several critical factors that influence transfer speeds and memory # management when sending tensors from the host to the device. We've learned that using ``non_blocking=True`` generally @@ -613,7 +613,7 @@ def pin_copy_to_device_nonblocking(*tensors): # Additional resources # -------------------- # -# .. _pinmem_resources: +# .. _pinned_memory_resources: # # If you are dealing with issues with memory copies when using CUDA devices or want to learn more about # what was discussed in this tutorial, check the following references: From 171b350d75a74e304764f20620fd0fe3660f2569 Mon Sep 17 00:00:00 2001 From: Vincent Moens Date: Mon, 29 Jul 2024 11:33:33 -0400 Subject: [PATCH 33/54] adding stream exps --- _static/img/{ => pinmem}/pinmem.png | Bin .../img/pinmem/trace_streamed0_pinned0.png | Bin 0 -> 83185 bytes .../img/pinmem/trace_streamed0_pinned1.png | Bin 0 -> 83389 bytes _static/img/pinmem/trace_streamed1_pinned.png | Bin 0 -> 92727 bytes .../img/pinmem/trace_streamed1_pinned0.png | Bin 0 -> 87483 bytes intermediate_source/pinmem_nonblock.py | 92 +++++++++++++++++- 6 files changed, 89 insertions(+), 3 deletions(-) rename _static/img/{ => pinmem}/pinmem.png (100%) create mode 100644 _static/img/pinmem/trace_streamed0_pinned0.png create mode 100644 _static/img/pinmem/trace_streamed0_pinned1.png create mode 100644 _static/img/pinmem/trace_streamed1_pinned.png create mode 100644 _static/img/pinmem/trace_streamed1_pinned0.png diff --git a/_static/img/pinmem.png b/_static/img/pinmem/pinmem.png similarity index 100% rename from _static/img/pinmem.png rename to _static/img/pinmem/pinmem.png diff --git a/_static/img/pinmem/trace_streamed0_pinned0.png b/_static/img/pinmem/trace_streamed0_pinned0.png new file mode 100644 index 0000000000000000000000000000000000000000..dedac997b0b0cce16c5e36a94903edd2dfc463eb GIT binary patch literal 83185 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

6<371BsWimP@COb)O_q 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

    o65{3^3GVY!o+qJP&DeYOd*R}N6i+Vm2R68l? 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 literal 0 HcmV?d00001 diff --git a/_static/img/pinmem/trace_streamed0_pinned1.png b/_static/img/pinmem/trace_streamed0_pinned1.png new file mode 100644 index 0000000000000000000000000000000000000000..2d5ff462e1aa64f4d9207b619157b311878b9cf6 GIT binary patch literal 83389 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 |KY!l1?r=Nm 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 literal 0 HcmV?d00001 diff --git a/_static/img/pinmem/trace_streamed1_pinned0.png b/_static/img/pinmem/trace_streamed1_pinned0.png new file mode 100644 index 0000000000000000000000000000000000000000..130182a19781653874ca34ff38b31ea80cbbe5f4 GIT binary patch literal 87483 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 z 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 literal 0 HcmV?d00001 diff --git a/intermediate_source/pinmem_nonblock.py b/intermediate_source/pinmem_nonblock.py index 18278c0c7e..92c1540213 100644 --- a/intermediate_source/pinmem_nonblock.py +++ b/intermediate_source/pinmem_nonblock.py @@ -84,7 +84,7 @@ # It allows for faster and more predictable access times, but has the downside that it is more limited than the # pageable memory (aka the main memory). # -# .. figure:: /_static/img/pinmem.png +# .. figure:: /_static/img/pinmem/pinmem.png # :alt: # # CUDA and (non-)pageable memory @@ -120,8 +120,8 @@ # # .. note:: In general, the transfer is blocking on the device side (even if it isn't on the host side): # the copy on the device cannot occur while another operation is being executed. -# However, in some advanced scenarios, multiple copies or copy and kernel -# executions can be done simultaneously on the GPU side. To enable this, three requirements must be met: +# However, in some advanced scenarios, a copy and a kernel execution can be done simultaneously on the GPU side. +# As the following example will show, three requirements must be met to enable this: # # 1. The device must have at least one free DMA (Direct Memory Access) engine. Modern GPU architectures such as Volterra, # Tesla or H100 devices have more than one DMA engine. @@ -131,6 +131,92 @@ # # 3. The source data must be in pinned memory. # + +import contextlib + +import torch +from torch.cuda import Stream + + +s = Stream() + +torch.manual_seed(42) +t1_cpu_pinned = torch.randn(1024**2 * 5, pin_memory=True) +t2_cpu_paged = torch.randn(1024**2 * 5, pin_memory=False) +t3_cuda = torch.randn(1024**2 * 5, device="cuda:0") + +assert torch.cuda.is_available() +device = torch.device("cuda", torch.cuda.current_device()) + + +def inner(pinned: bool, streamed: bool): + with torch.cuda.stream(s) if streamed else contextlib.nullcontext(): + if pinned: + t1_cuda = t1_cpu_pinned.to(device, non_blocking=True) + else: + t2_cuda = t2_cpu_paged.to(device, non_blocking=True) + t2_h2d_event = s.record_event() + # This operation can be executed during the CPU to GPU copy iff the tensor is pinned and the copy is + # done in the other stream + t3_cuda_mul = t3_cuda * t3_cuda * t3_cuda + t1_h2d_event = torch.cuda.current_stream().record_event() + t1_h2d_event.synchronize() + t2_h2d_event.synchronize() + + +def benchmark_with_profiler( + pinned, + streamed, +) -> None: + torch._C._profiler._set_cuda_sync_enabled_val(True) + wait, warmup, active = 1, 1, 2 + num_steps = wait + warmup + active + rank = 0 + with torch.profiler.profile( + activities=[ + torch.profiler.ProfilerActivity.CPU, + torch.profiler.ProfilerActivity.CUDA, + ], + schedule=torch.profiler.schedule( + wait=wait, warmup=warmup, active=active, repeat=1, skip_first=1 + ), + ) as prof: + for step_idx in range(1, num_steps + 1): + inner(streamed=streamed, pinned=pinned) + if rank is None or rank == 0: + prof.step() + prof.export_chrome_trace(f"trace_streamed{int(streamed)}_pinned{int(pinned)}.json") + + +benchmark_with_profiler(streamed=False, pinned=False) +benchmark_with_profiler(streamed=True, pinned=False) +benchmark_with_profiler(streamed=False, pinned=True) +benchmark_with_profiler(streamed=True, pinned=True) + + +###################################################################### +# Loading these profile traces in chrome (``chrome://tracing``) shows the following results: first, let's see +# what happens if both the arithmetic operation on ``t3_cuda`` is executed after the pageable tensor is sent to GPU +# in the main stream: +# +# .. figure:: /_static/img/pinmem/trace_streamed0_pinned0.png +# :alt: +# +# Using a pinned tensor doesn't change the trace much, both operations are still executed consecutively: +# +# .. figure:: /_static/img/pinmem/trace_streamed0_pinned1.png +# :alt: +# +# Sending a pageable tensor to GPU on a separate stream is also a blocking operation: +# +# .. figure:: /_static/img/pinmem/trace_streamed1_pinned0.png +# :alt: +# +# Only pinned tensors copies to GPU on a separate stream can be executed whilst an arithmetic operation is executed on +# the main stream: +# +# .. figure:: /_static/img/pinmem/trace_streamed1_pinned1.png +# :alt: # # A PyTorch perspective # --------------------- From 5138deaf8f09854acf2453e47c6b26beddcfacc4 Mon Sep 17 00:00:00 2001 From: Vincent Moens Date: Mon, 29 Jul 2024 11:36:48 -0400 Subject: [PATCH 34/54] fix filename --- ...amed1_pinned.png => trace_streamed1_pinned1.png} | Bin intermediate_source/pinmem_nonblock.py | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename _static/img/pinmem/{trace_streamed1_pinned.png => trace_streamed1_pinned1.png} (100%) diff --git a/_static/img/pinmem/trace_streamed1_pinned.png b/_static/img/pinmem/trace_streamed1_pinned1.png similarity index 100% rename from _static/img/pinmem/trace_streamed1_pinned.png rename to _static/img/pinmem/trace_streamed1_pinned1.png diff --git a/intermediate_source/pinmem_nonblock.py b/intermediate_source/pinmem_nonblock.py index 92c1540213..032d9ea013 100644 --- a/intermediate_source/pinmem_nonblock.py +++ b/intermediate_source/pinmem_nonblock.py @@ -212,7 +212,7 @@ def benchmark_with_profiler( # .. figure:: /_static/img/pinmem/trace_streamed1_pinned0.png # :alt: # -# Only pinned tensors copies to GPU on a separate stream can be executed whilst an arithmetic operation is executed on +# Only pinned tensors copies to GPU on a separate stream overlap with another cuda kernel executed on # the main stream: # # .. figure:: /_static/img/pinmem/trace_streamed1_pinned1.png From 3869413aa50572817008c3f81bdd677b04d67545 Mon Sep 17 00:00:00 2001 From: Vincent Moens Date: Mon, 29 Jul 2024 13:00:26 -0400 Subject: [PATCH 35/54] amend --- intermediate_source/pinmem_nonblock.py | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/intermediate_source/pinmem_nonblock.py b/intermediate_source/pinmem_nonblock.py index 032d9ea013..ab23c026ff 100644 --- a/intermediate_source/pinmem_nonblock.py +++ b/intermediate_source/pinmem_nonblock.py @@ -131,6 +131,8 @@ # # 3. The source data must be in pinned memory. # +# We demonstrate this by running profiles on the following script. +# import contextlib @@ -149,6 +151,7 @@ device = torch.device("cuda", torch.cuda.current_device()) +# The function we want to profile def inner(pinned: bool, streamed: bool): with torch.cuda.stream(s) if streamed else contextlib.nullcontext(): if pinned: @@ -164,6 +167,7 @@ def inner(pinned: bool, streamed: bool): t2_h2d_event.synchronize() +# Our profiler: profiles the `inner` function and stores the results in a .json file def benchmark_with_profiler( pinned, streamed, @@ -188,30 +192,40 @@ def benchmark_with_profiler( prof.export_chrome_trace(f"trace_streamed{int(streamed)}_pinned{int(pinned)}.json") -benchmark_with_profiler(streamed=False, pinned=False) -benchmark_with_profiler(streamed=True, pinned=False) -benchmark_with_profiler(streamed=False, pinned=True) -benchmark_with_profiler(streamed=True, pinned=True) - - ###################################################################### # Loading these profile traces in chrome (``chrome://tracing``) shows the following results: first, let's see # what happens if both the arithmetic operation on ``t3_cuda`` is executed after the pageable tensor is sent to GPU # in the main stream: # + +benchmark_with_profiler(streamed=False, pinned=False) + +###################################################################### # .. figure:: /_static/img/pinmem/trace_streamed0_pinned0.png # :alt: # + +benchmark_with_profiler(streamed=True, pinned=False) + +###################################################################### # Using a pinned tensor doesn't change the trace much, both operations are still executed consecutively: # # .. figure:: /_static/img/pinmem/trace_streamed0_pinned1.png # :alt: # + +benchmark_with_profiler(streamed=False, pinned=True) + +###################################################################### # Sending a pageable tensor to GPU on a separate stream is also a blocking operation: # # .. figure:: /_static/img/pinmem/trace_streamed1_pinned0.png # :alt: # + +benchmark_with_profiler(streamed=True, pinned=True) + +###################################################################### # Only pinned tensors copies to GPU on a separate stream overlap with another cuda kernel executed on # the main stream: # From b1488d5b1587b721f48b32d4687b0d2ee8d3bb29 Mon Sep 17 00:00:00 2001 From: Vincent Moens Date: Mon, 29 Jul 2024 18:08:05 -0400 Subject: [PATCH 36/54] amend --- .ci/docker/requirements.txt | 2 +- intermediate_source/pinmem_nonblock.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.ci/docker/requirements.txt b/.ci/docker/requirements.txt index 9dfdf6bb93..c87982f266 100644 --- a/.ci/docker/requirements.txt +++ b/.ci/docker/requirements.txt @@ -28,8 +28,8 @@ tensorboard jinja2==3.1.3 pytorch-lightning torchx +torchrl==0.4.0 # TODO: use stable 0.5 when released --e git+https://github.com/pytorch/rl.git#egg=torchrl -e git+https://github.com/pytorch/tensordict.git#egg=tensordict ax-platform nbformat>==5.9.2 diff --git a/intermediate_source/pinmem_nonblock.py b/intermediate_source/pinmem_nonblock.py index ab23c026ff..6960d0eb95 100644 --- a/intermediate_source/pinmem_nonblock.py +++ b/intermediate_source/pinmem_nonblock.py @@ -37,7 +37,7 @@ # - :ref:`Background ` # # - :ref:`Memory management basics ` -# - :ref:`CUDA and (non-)pageable memory ` +# - :ref:`CUDA and (non-)pageable memory ` # - :ref:`Asynchronous vs. Synchronous Operations with non_blocking=True ` # # - :ref:`A PyTorch perspective ` @@ -90,7 +90,7 @@ # CUDA and (non-)pageable memory # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # -# .. _pinned_memory_cuda_pageable_mem: +# .. _pinned_memory_cuda_pageable_memory: # # To understand how CUDA copies a tensor from CPU to CUDA, let's consider the two scenarios above: # @@ -159,7 +159,7 @@ def inner(pinned: bool, streamed: bool): else: t2_cuda = t2_cpu_paged.to(device, non_blocking=True) t2_h2d_event = s.record_event() - # This operation can be executed during the CPU to GPU copy iff the tensor is pinned and the copy is + # This operation can be executed during the CPU to GPU copy if and only if the tensor is pinned and the copy is # done in the other stream t3_cuda_mul = t3_cuda * t3_cuda * t3_cuda t1_h2d_event = torch.cuda.current_stream().record_event() From 33236eca68edd59692cc18ed67d64b76d0d0e41d Mon Sep 17 00:00:00 2001 From: Vincent Moens Date: Mon, 29 Jul 2024 18:10:07 -0400 Subject: [PATCH 37/54] amend --- .ci/docker/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci/docker/requirements.txt b/.ci/docker/requirements.txt index c87982f266..9dfdf6bb93 100644 --- a/.ci/docker/requirements.txt +++ b/.ci/docker/requirements.txt @@ -28,8 +28,8 @@ tensorboard jinja2==3.1.3 pytorch-lightning torchx -torchrl==0.4.0 # TODO: use stable 0.5 when released +-e git+https://github.com/pytorch/rl.git#egg=torchrl -e git+https://github.com/pytorch/tensordict.git#egg=tensordict ax-platform nbformat>==5.9.2 From 2fd193afa1772e08f660bcbc88bee43a4fc9fafa Mon Sep 17 00:00:00 2001 From: Vincent Moens Date: Mon, 29 Jul 2024 18:20:21 -0400 Subject: [PATCH 38/54] amend --- intermediate_source/pinmem_nonblock.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/intermediate_source/pinmem_nonblock.py b/intermediate_source/pinmem_nonblock.py index 6960d0eb95..e46833b8b4 100644 --- a/intermediate_source/pinmem_nonblock.py +++ b/intermediate_source/pinmem_nonblock.py @@ -103,7 +103,7 @@ # before making the transfer. # # Asynchronous vs. Synchronous Operations with ``non_blocking=True`` (CUDA ``cudaMemcpyAsync``) -# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # # .. _pinned_memory_async_sync: # @@ -131,7 +131,7 @@ # # 3. The source data must be in pinned memory. # -# We demonstrate this by running profiles on the following script. +# We demonstrate this by running profiles on the following script. # import contextlib From 392230a02bb1ff8f49c96e9edbf6083832ae6839 Mon Sep 17 00:00:00 2001 From: Vincent Moens Date: Mon, 29 Jul 2024 18:25:43 -0400 Subject: [PATCH 39/54] amend --- intermediate_source/pinmem_nonblock.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/intermediate_source/pinmem_nonblock.py b/intermediate_source/pinmem_nonblock.py index e46833b8b4..e1fd5b3cc4 100644 --- a/intermediate_source/pinmem_nonblock.py +++ b/intermediate_source/pinmem_nonblock.py @@ -592,7 +592,7 @@ def pin_copy_to_device_nonblocking(*tensors): # .. code-block:: bash # # # Install tensordict with the following command -# !pip3 install https://github.com/pytorch/tensordict +# !pip3 install tensordict # from tensordict import TensorDict From bff42d1d94642290ed28e5f4b4fef797d91385ac Mon Sep 17 00:00:00 2001 From: Vincent Moens Date: Mon, 29 Jul 2024 18:37:49 -0400 Subject: [PATCH 40/54] amend --- intermediate_source/pinmem_nonblock.py | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/intermediate_source/pinmem_nonblock.py b/intermediate_source/pinmem_nonblock.py index e1fd5b3cc4..1a20a5e894 100644 --- a/intermediate_source/pinmem_nonblock.py +++ b/intermediate_source/pinmem_nonblock.py @@ -23,6 +23,13 @@ - While ``cpu_tensor.to("cuda", non_blocking=True).mean()`` executes correctly, attempting ``cuda_tensor.to("cpu", non_blocking=True).mean()`` will result in erroneous outputs. +Preamble +~~~~~~~~ + +The performance reported in this tutorial are conditioned on the system used to build the tutorial. +Although the conclusions should be applicable across different systems, the specific observations may vary slightly +depending on the hardware available. + """ import torch @@ -32,6 +39,14 @@ ###################################################################### # +# This tutorial requires tensordict to be installed. If you don't have tensordict in your environment yet, install it +# by running the following command in a separate cell: +# +# .. code-block:: bash +# +# # Install tensordict with the following command +# !pip3 install tensordict +# # We start by outlining the theory surrounding these concepts, and then move to concrete test examples of the features. # # - :ref:`Background ` @@ -136,7 +151,6 @@ import contextlib -import torch from torch.cuda import Stream @@ -589,10 +603,6 @@ def pin_copy_to_device_nonblocking(*tensors): # ``pin_memory()`` before proceeding with to ``to(device)``. # This approach can further accelerate data transfers, as demonstrated in the following example. # -# .. code-block:: bash -# -# # Install tensordict with the following command -# !pip3 install tensordict # from tensordict import TensorDict From e6b20b1cc49e245a96ba191c1dfae103b59423da Mon Sep 17 00:00:00 2001 From: Vincent Moens Date: Mon, 29 Jul 2024 18:44:07 -0400 Subject: [PATCH 41/54] amend --- intermediate_source/pinmem_nonblock.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/intermediate_source/pinmem_nonblock.py b/intermediate_source/pinmem_nonblock.py index 1a20a5e894..b862f1788e 100644 --- a/intermediate_source/pinmem_nonblock.py +++ b/intermediate_source/pinmem_nonblock.py @@ -29,6 +29,9 @@ The performance reported in this tutorial are conditioned on the system used to build the tutorial. Although the conclusions should be applicable across different systems, the specific observations may vary slightly depending on the hardware available. +The primary objective of this tutorial is to offer a theoretical framework for understanding CPU to GPU data transfers. +However, any design decisions should be tailored to individual cases and guided by benchmarked throughput measurements, +as well as the specific requirements of the task at hand. """ From d6318f77a4860874ff159016d180988f096c536f Mon Sep 17 00:00:00 2001 From: Vincent Moens Date: Mon, 29 Jul 2024 19:07:36 -0400 Subject: [PATCH 42/54] amend --- intermediate_source/pinmem_nonblock.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/intermediate_source/pinmem_nonblock.py b/intermediate_source/pinmem_nonblock.py index b862f1788e..969a65e192 100644 --- a/intermediate_source/pinmem_nonblock.py +++ b/intermediate_source/pinmem_nonblock.py @@ -27,8 +27,8 @@ ~~~~~~~~ The performance reported in this tutorial are conditioned on the system used to build the tutorial. -Although the conclusions should be applicable across different systems, the specific observations may vary slightly -depending on the hardware available. +Although the conclusions are applicable across different systems, the specific observations may vary slightly +depending on the hardware available, especially on older hardware. The primary objective of this tutorial is to offer a theoretical framework for understanding CPU to GPU data transfers. However, any design decisions should be tailored to individual cases and guided by benchmarked throughput measurements, as well as the specific requirements of the task at hand. From 83abe5f89563cfb73059fe3c464eef18004a4b99 Mon Sep 17 00:00:00 2001 From: Vincent Moens Date: Mon, 29 Jul 2024 19:16:24 -0400 Subject: [PATCH 43/54] amend --- intermediate_source/pinmem_nonblock.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/intermediate_source/pinmem_nonblock.py b/intermediate_source/pinmem_nonblock.py index 969a65e192..83f7a6215d 100644 --- a/intermediate_source/pinmem_nonblock.py +++ b/intermediate_source/pinmem_nonblock.py @@ -334,9 +334,14 @@ def timer(cmd): # the hood, a pageable tensor must be copied to pinned memory before being sent to GPU. # # However, contrary to a somewhat common belief, calling :meth:`~torch.Tensor.pin_memory()` on a pageable tensor before -# casting it to GPU should not bring any speed-up, on the contrary this call is usually slower than just executing -# the transfer. This makes sense, since we're actually asking python to execute an operation that CUDA will perform -# anyway before copying the data from host to device. +# casting it to GPU should not bring any significant speed-up, on the contrary this call is usually slower than just +# executing the transfer. This makes sense, since we're actually asking python to execute an operation that CUDA will +# perform anyway before copying the data from host to device. +# +# .. note:: Here too, the observation may vary depending on the available hardware. +# The pytorch implementation of +# `pin_memory `_ +# could be, in rare cases, faster than the corresponding CUDA version. # # ``non_blocking=True`` # ~~~~~~~~~~~~~~~~~~~~~ From 4fe4bde7787d91b65fa9bf3362a0905412182d15 Mon Sep 17 00:00:00 2001 From: Vincent Moens Date: Mon, 29 Jul 2024 19:16:51 -0400 Subject: [PATCH 44/54] amend --- intermediate_source/pinmem_nonblock.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/intermediate_source/pinmem_nonblock.py b/intermediate_source/pinmem_nonblock.py index 83f7a6215d..d2a6f25c5d 100644 --- a/intermediate_source/pinmem_nonblock.py +++ b/intermediate_source/pinmem_nonblock.py @@ -288,10 +288,10 @@ def timer(cmd): # A tensor in pageable memory -pageable_tensor = torch.randn(1_000_000) +pageable_tensor = torch.randn(1000) # A tensor in page-locked (pinned) memory -pinned_tensor = torch.randn(1_000_000, pin_memory=True) +pinned_tensor = torch.randn(1000, pin_memory=True) # Runtimes: pageable_to_device = timer("pageable_tensor.to('cuda:0')") From ea5320484462f3a4e30442b192cddf51602f5e32 Mon Sep 17 00:00:00 2001 From: Vincent Moens Date: Mon, 29 Jul 2024 19:18:53 -0400 Subject: [PATCH 45/54] amend --- intermediate_source/pinmem_nonblock.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/intermediate_source/pinmem_nonblock.py b/intermediate_source/pinmem_nonblock.py index d2a6f25c5d..83f7a6215d 100644 --- a/intermediate_source/pinmem_nonblock.py +++ b/intermediate_source/pinmem_nonblock.py @@ -288,10 +288,10 @@ def timer(cmd): # A tensor in pageable memory -pageable_tensor = torch.randn(1000) +pageable_tensor = torch.randn(1_000_000) # A tensor in page-locked (pinned) memory -pinned_tensor = torch.randn(1000, pin_memory=True) +pinned_tensor = torch.randn(1_000_000, pin_memory=True) # Runtimes: pageable_to_device = timer("pageable_tensor.to('cuda:0')") From 0d6cba7fa374e373fef5c28149c425246a08e401 Mon Sep 17 00:00:00 2001 From: Vincent Moens Date: Mon, 29 Jul 2024 19:42:03 -0400 Subject: [PATCH 46/54] amend --- intermediate_source/pinmem_nonblock.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/intermediate_source/pinmem_nonblock.py b/intermediate_source/pinmem_nonblock.py index 83f7a6215d..8904372d45 100644 --- a/intermediate_source/pinmem_nonblock.py +++ b/intermediate_source/pinmem_nonblock.py @@ -338,10 +338,12 @@ def timer(cmd): # executing the transfer. This makes sense, since we're actually asking python to execute an operation that CUDA will # perform anyway before copying the data from host to device. # -# .. note:: Here too, the observation may vary depending on the available hardware. -# The pytorch implementation of +# .. note:: The pytorch implementation of # `pin_memory `_ -# could be, in rare cases, faster than the corresponding CUDA version. +# which relies on creating a brand new storage in pinned memory through `cudaHostAlloc ` +# could be, in rare cases, faster than transitioning data in chunks as ``cudaMemcpy`` does. +# Here too, the observation may vary depending on the available hardware, the size of the tensors being sent or +# the amount of available RAM. # # ``non_blocking=True`` # ~~~~~~~~~~~~~~~~~~~~~ @@ -738,5 +740,6 @@ def pin_copy_to_device_nonblocking(*tensors): # # - `CUDA toolkit memory management doc `_ # - `CUDA pin-memory note `_ +# - `How to Optimize Data Transfers in CUDA C/C++ `_ # - tensordict :meth:`~tensordict.TensorDict.to` method; # From 69e98ea3420a22074527778dd7aa17710a4ce234 Mon Sep 17 00:00:00 2001 From: Vincent Moens Date: Tue, 30 Jul 2024 10:07:50 -0400 Subject: [PATCH 47/54] amend --- intermediate_source/pinmem_nonblock.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/intermediate_source/pinmem_nonblock.py b/intermediate_source/pinmem_nonblock.py index 8904372d45..ae8bc5da90 100644 --- a/intermediate_source/pinmem_nonblock.py +++ b/intermediate_source/pinmem_nonblock.py @@ -221,30 +221,30 @@ def benchmark_with_profiler( # .. figure:: /_static/img/pinmem/trace_streamed0_pinned0.png # :alt: # +# Using a pinned tensor doesn't change the trace much, both operations are still executed consecutively: benchmark_with_profiler(streamed=True, pinned=False) ###################################################################### -# Using a pinned tensor doesn't change the trace much, both operations are still executed consecutively: # # .. figure:: /_static/img/pinmem/trace_streamed0_pinned1.png # :alt: # +# Sending a pageable tensor to GPU on a separate stream is also a blocking operation: benchmark_with_profiler(streamed=False, pinned=True) ###################################################################### -# Sending a pageable tensor to GPU on a separate stream is also a blocking operation: # # .. figure:: /_static/img/pinmem/trace_streamed1_pinned0.png # :alt: # +# Only pinned tensors copies to GPU on a separate stream overlap with another cuda kernel executed on +# the main stream: benchmark_with_profiler(streamed=True, pinned=True) ###################################################################### -# Only pinned tensors copies to GPU on a separate stream overlap with another cuda kernel executed on -# the main stream: # # .. figure:: /_static/img/pinmem/trace_streamed1_pinned1.png # :alt: From ed465bd2e1a5061c84c49bc70c339ff28d7cbcfb Mon Sep 17 00:00:00 2001 From: Vincent Moens Date: Tue, 30 Jul 2024 11:48:19 -0400 Subject: [PATCH 48/54] amend --- intermediate_source/pinmem_nonblock.py | 23 +++-------------------- 1 file changed, 3 insertions(+), 20 deletions(-) diff --git a/intermediate_source/pinmem_nonblock.py b/intermediate_source/pinmem_nonblock.py index ae8bc5da90..1891e405cf 100644 --- a/intermediate_source/pinmem_nonblock.py +++ b/intermediate_source/pinmem_nonblock.py @@ -13,8 +13,9 @@ This tutorial examines two key methods for device-to-device data transfer in PyTorch: :meth:`~torch.Tensor.pin_memory` and :meth:`~torch.Tensor.to` with the ``non_blocking=True`` option. -Key Learnings -~~~~~~~~~~~~~ +What you will learn +~~~~~~~~~~~~~~~~~~~ + Optimizing the transfer of tensors from the CPU to the GPU can be achieved through asynchronous transfers and memory pinning. However, there are important considerations: @@ -52,24 +53,6 @@ # # We start by outlining the theory surrounding these concepts, and then move to concrete test examples of the features. # -# - :ref:`Background ` -# -# - :ref:`Memory management basics ` -# - :ref:`CUDA and (non-)pageable memory ` -# - :ref:`Asynchronous vs. Synchronous Operations with non_blocking=True ` -# -# - :ref:`A PyTorch perspective ` -# -# - :ref:`pin_memory ` -# - :ref:`non_blocking=True ` -# - :ref:`Synergies ` -# - :ref:`Other copy directions (GPU -> CPU) ` -# -# - :ref:`Practical recommendations ` -# - :ref:`Additional considerations ` -# - :ref:`Conclusion ` -# - :ref:`Additional resources ` -# # # Background # ---------- From d4169d4eb127271dc5b8a85652d74636577bf452 Mon Sep 17 00:00:00 2001 From: Vincent Moens Date: Tue, 30 Jul 2024 19:28:41 +0100 Subject: [PATCH 49/54] Update intermediate_source/pinmem_nonblock.py Co-authored-by: Svetlana Karslioglu --- intermediate_source/pinmem_nonblock.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/intermediate_source/pinmem_nonblock.py b/intermediate_source/pinmem_nonblock.py index 1891e405cf..25c1c0bf5a 100644 --- a/intermediate_source/pinmem_nonblock.py +++ b/intermediate_source/pinmem_nonblock.py @@ -693,7 +693,7 @@ def pin_copy_to_device_nonblocking(*tensors): # # - **System Architecture** # -# How is the system's architecture influencing data transfer speeds (e.g., bus speeds, network latency)? +# How is the system's architecture influencing data transfer speeds (for example, bus speeds, network latency)? # # Additionally, allocating a large number of tensors or sizable tensors in pinned memory can monopolize a substantial # portion of RAM. From 2f55eb8e78236c3135e4c5462e34cd7bd6b58bf6 Mon Sep 17 00:00:00 2001 From: Vincent Moens Date: Tue, 30 Jul 2024 19:29:26 +0100 Subject: [PATCH 50/54] Apply suggestions from code review Co-authored-by: Svetlana Karslioglu --- intermediate_source/pinmem_nonblock.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/intermediate_source/pinmem_nonblock.py b/intermediate_source/pinmem_nonblock.py index 25c1c0bf5a..1c6b838a10 100644 --- a/intermediate_source/pinmem_nonblock.py +++ b/intermediate_source/pinmem_nonblock.py @@ -125,7 +125,7 @@ # As the following example will show, three requirements must be met to enable this: # # 1. The device must have at least one free DMA (Direct Memory Access) engine. Modern GPU architectures such as Volterra, -# Tesla or H100 devices have more than one DMA engine. +# Tesla, or H100 devices have more than one DMA engine. # # 2. The transfer must be done on a separate, non-default cuda stream. In PyTorch, cuda streams can be handles using # :class:`~torch.cuda.Stream`. @@ -250,7 +250,7 @@ def benchmark_with_profiler( # New tensors can be directly created in pinned memory with functions like :func:`~torch.zeros`, :func:`~torch.ones` and other # constructors. # -# Let us check the speed of pinning memory and sending tensors to cuda: +# Let us check the speed of pinning memory and sending tensors to CUDA: import torch @@ -318,10 +318,10 @@ def timer(cmd): # # However, contrary to a somewhat common belief, calling :meth:`~torch.Tensor.pin_memory()` on a pageable tensor before # casting it to GPU should not bring any significant speed-up, on the contrary this call is usually slower than just -# executing the transfer. This makes sense, since we're actually asking python to execute an operation that CUDA will +# executing the transfer. This makes sense, since we're actually asking Python to execute an operation that CUDA will # perform anyway before copying the data from host to device. # -# .. note:: The pytorch implementation of +# .. note:: The PyTorch implementation of # `pin_memory `_ # which relies on creating a brand new storage in pinned memory through `cudaHostAlloc ` # could be, in rare cases, faster than transitioning data in chunks as ``cudaMemcpy`` does. @@ -505,7 +505,7 @@ def pin_copy_to_device_nonblocking(*tensors): ###################################################################### -# Other copy directions (GPU -> CPU, CPU -> MPS etc.) +# Other copy directions (GPU -> CPU, CPU -> MPS) # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # # .. _pinned_memory_other_direction: From 12d1b6952267a924184b6c6ea2d470edc2910b5c Mon Sep 17 00:00:00 2001 From: Vincent Moens Date: Tue, 30 Jul 2024 19:30:46 +0100 Subject: [PATCH 51/54] Update intermediate_source/pinmem_nonblock.py --- intermediate_source/pinmem_nonblock.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/intermediate_source/pinmem_nonblock.py b/intermediate_source/pinmem_nonblock.py index 1c6b838a10..3b3d3ba146 100644 --- a/intermediate_source/pinmem_nonblock.py +++ b/intermediate_source/pinmem_nonblock.py @@ -718,11 +718,11 @@ def pin_copy_to_device_nonblocking(*tensors): # # .. _pinned_memory_resources: # -# If you are dealing with issues with memory copies when using CUDA devices or want to learn more about -# what was discussed in this tutorial, check the following references: +# If you are dealing with issues with memory copies when using CUDA devices or want to learn more about +# what was discussed in this tutorial, check the following references: # -# - `CUDA toolkit memory management doc `_ -# - `CUDA pin-memory note `_ -# - `How to Optimize Data Transfers in CUDA C/C++ `_ -# - tensordict :meth:`~tensordict.TensorDict.to` method; +# - `CUDA toolkit memory management doc `_; +# - `CUDA pin-memory note `_; +# - `How to Optimize Data Transfers in CUDA C/C++ `_; +# - tensordict :meth:`~tensordict.TensorDict.to` method. # From 8f4d6d7cc9d79c1999d83e51eef99eddbcb6be8f Mon Sep 17 00:00:00 2001 From: Vincent Moens Date: Tue, 30 Jul 2024 16:59:29 -0400 Subject: [PATCH 52/54] edit index.rst --- index.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/index.rst b/index.rst index 5299f8c0c9..91517834fd 100644 --- a/index.rst +++ b/index.rst @@ -3,6 +3,7 @@ Welcome to PyTorch Tutorials **What's new in PyTorch tutorials?** +* `A guide on good usage of non_blocking and pin_memory() in PyTorch `__ * `Introduction to Distributed Pipeline Parallelism `__ * `Introduction to Libuv TCPStore Backend `__ * `Asynchronous Saving with Distributed Checkpoint (DCP) `__ @@ -949,7 +950,6 @@ Additional Resources beginner/basics/autogradqs_tutorial beginner/basics/optimization_tutorial beginner/basics/saveloadrun_tutorial - intermediate/pinmem_nonblock advanced/custom_ops_landing_page .. toctree:: @@ -977,6 +977,7 @@ Additional Resources beginner/pytorch_with_examples beginner/nn_tutorial intermediate/tensorboard_tutorial + intermediate/pinmem_nonblock .. toctree:: :maxdepth: 2 From 1dfe3151a286248d3d45e27cc9f324e4843af4be Mon Sep 17 00:00:00 2001 From: Vincent Moens Date: Tue, 30 Jul 2024 17:05:54 -0400 Subject: [PATCH 53/54] edit tensordict to() link --- intermediate_source/pinmem_nonblock.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/intermediate_source/pinmem_nonblock.py b/intermediate_source/pinmem_nonblock.py index 3b3d3ba146..616fb506e9 100644 --- a/intermediate_source/pinmem_nonblock.py +++ b/intermediate_source/pinmem_nonblock.py @@ -724,5 +724,5 @@ def pin_copy_to_device_nonblocking(*tensors): # - `CUDA toolkit memory management doc `_; # - `CUDA pin-memory note `_; # - `How to Optimize Data Transfers in CUDA C/C++ `_; -# - tensordict :meth:`~tensordict.TensorDict.to` method. +# - tensordict :meth:`~tensordict.TensorDictBase.to` method. # From 07f9932da3694d7984ee490fce153e390dabbfa4 Mon Sep 17 00:00:00 2001 From: Vincent Moens Date: Tue, 30 Jul 2024 18:47:03 -0400 Subject: [PATCH 54/54] address comments --- intermediate_source/pinmem_nonblock.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/intermediate_source/pinmem_nonblock.py b/intermediate_source/pinmem_nonblock.py index 616fb506e9..78e57b6cf1 100644 --- a/intermediate_source/pinmem_nonblock.py +++ b/intermediate_source/pinmem_nonblock.py @@ -66,7 +66,7 @@ # # When one creates a CPU tensor in PyTorch, the content of this tensor needs to be placed # in memory. The memory we talk about here is a rather complex concept worth looking at carefully. -# We distinguish two types of memory that are handled by the Memory Management Unit: the main memory (for simplicity) +# We distinguish two types of memory that are handled by the Memory Management Unit: the RAM (for simplicity) # and the swap space on disk (which may or may not be the hard drive). Together, the available space in disk and RAM (physical memory) # make up the virtual memory, which is an abstraction of the total resources available. # In short, the virtual memory makes it so that the available space is larger than what can be found on RAM in isolation @@ -78,9 +78,9 @@ # # Typically, when a program accesses a page that is not in RAM, a "page fault" occurs and the operating system (OS) then brings # back this page into RAM ("swap in" or "page in"). -# In turn, the OS may have to _swap out_ (or _page out_) another page to make room for the new page. +# In turn, the OS may have to swap out (or "page out") another page to make room for the new page. # -# In contrast to pageable memory, a _pinned_ (or _page-locked_ or _non-pageable_) memory is a type of memory that cannot +# In contrast to pageable memory, a pinned (or page-locked or non-pageable) memory is a type of memory that cannot # be swapped out to disk. # It allows for faster and more predictable access times, but has the downside that it is more limited than the # pageable memory (aka the main memory). @@ -158,13 +158,13 @@ def inner(pinned: bool, streamed: bool): t1_cuda = t1_cpu_pinned.to(device, non_blocking=True) else: t2_cuda = t2_cpu_paged.to(device, non_blocking=True) - t2_h2d_event = s.record_event() + t_star_cuda_h2d_event = s.record_event() # This operation can be executed during the CPU to GPU copy if and only if the tensor is pinned and the copy is # done in the other stream t3_cuda_mul = t3_cuda * t3_cuda * t3_cuda - t1_h2d_event = torch.cuda.current_stream().record_event() - t1_h2d_event.synchronize() - t2_h2d_event.synchronize() + t3_cuda_h2d_event = torch.cuda.current_stream().record_event() + t_star_cuda_h2d_event.synchronize() + t3_cuda_h2d_event.synchronize() # Our profiler: profiles the `inner` function and stores the results in a .json file @@ -206,7 +206,7 @@ def benchmark_with_profiler( # # Using a pinned tensor doesn't change the trace much, both operations are still executed consecutively: -benchmark_with_profiler(streamed=True, pinned=False) +benchmark_with_profiler(streamed=False, pinned=True) ###################################################################### # @@ -215,7 +215,7 @@ def benchmark_with_profiler( # # Sending a pageable tensor to GPU on a separate stream is also a blocking operation: -benchmark_with_profiler(streamed=False, pinned=True) +benchmark_with_profiler(streamed=True, pinned=False) ###################################################################### # @@ -323,7 +323,7 @@ def timer(cmd): # # .. note:: The PyTorch implementation of # `pin_memory `_ -# which relies on creating a brand new storage in pinned memory through `cudaHostAlloc ` +# which relies on creating a brand new storage in pinned memory through `cudaHostAlloc `_ # could be, in rare cases, faster than transitioning data in chunks as ``cudaMemcpy`` does. # Here too, the observation may vary depending on the available hardware, the size of the tensors being sent or # the amount of available RAM. @@ -724,5 +724,5 @@ def pin_copy_to_device_nonblocking(*tensors): # - `CUDA toolkit memory management doc `_; # - `CUDA pin-memory note `_; # - `How to Optimize Data Transfers in CUDA C/C++ `_; -# - tensordict :meth:`~tensordict.TensorDictBase.to` method. +# - `tensordict doc `_ and `repo `_. #