注意
单击此处下载完整的示例代码
PyTorch 中 和 的良好使用指南non_blocking
pin_memory()
¶
创建时间: Jul 31, 2024 |上次更新时间: 2024-8-01 |上次验证: Nov 05, 2024
作者: Vincent Moens
介绍¶
将数据从 CPU 传输到 GPU 是许多 PyTorch 应用程序的基础。
用户了解可用于在设备之间移动数据的最有效工具和选项至关重要。
本教程介绍了 PyTorch 中设备到设备数据传输的两种关键方法:和
non_blocking=True
您将学到什么¶
优化张量从 CPU 到 GPU 的传输可以通过异步传输和内存来实现 寄托。但是,有一些重要的注意事项:
使用速度可能比 Straightforward 慢两倍。
tensor.pin_memory().to(device, non_blocking=True)
tensor.to(device)
通常,是提高传输速度的有效选择。
tensor.to(device, non_blocking=True)
虽然执行正确,但 attempt 将导致错误的输出。
cpu_tensor.to("cuda", non_blocking=True).mean()
cuda_tensor.to("cpu", non_blocking=True).mean()
序言¶
本教程中报告的性能取决于用于构建本教程的系统。 尽管结论适用于不同的系统,但具体的观察结果可能略有不同 取决于可用的硬件,尤其是较旧的硬件。 本教程的主要目标是为理解 CPU 到 GPU 的数据传输提供一个理论框架。 但是,任何设计决策都应针对具体情况进行定制,并以基准吞吐量测量为指导。 以及手头任务的具体要求。
import torch
assert torch.cuda.is_available(), "A cuda device is required to run this tutorial"
本教程需要安装 tensordict。如果您的环境中还没有 tensordict,请安装它 通过在单独的单元格中运行以下命令:
# Install tensordict with the following command
!pip3 install tensordict
我们首先概述围绕这些概念的理论,然后转向特性的具体测试示例。
背景¶
内存管理基础知识¶
在 PyTorch 中创建 CPU 张量时,需要放置该张量的内容 在内存中。我们这里讨论的内存是一个相当复杂的概念,值得仔细研究。 我们区分了两种由内存管理单元处理的内存类型:RAM(为简单起见) 以及磁盘上的交换空间(可能是也可能不是硬盘驱动器)。磁盘和 RAM 中的可用空间(物理内存)合计 构成虚拟内存,它是可用总资源的抽象。 简而言之,虚拟内存使可用空间大于 RAM 上孤立的空间 并产生主内存比实际更大的错觉。
在正常情况下,常规 CPU 张量是可分页的,这意味着它被划分为称为页面的块,这些块 可以位于虚拟内存中的任何位置(在 RAM 或磁盘上)。如前所述,这样做的好处是 内存似乎比主内存的实际大小大。
通常,当程序访问不在 RAM 中的页面时,会发生“页面错误”,然后操作系统 (OS) 会带来 将此页面返回到 RAM 中(“换入”或“页面输入”)。 反过来,操作系统可能必须换出(或“分页”)另一个页面,以便为新页面腾出空间。
与可分页内存相反,固定 (或分页锁定或不可分页) 内存是一种不能 换出到磁盘。 它允许更快、更可预测的访问时间,但缺点是它比 分页内存(又名主内存)。
![](https://pytorch.org/tutorials/_images/pinmem.png)
CUDA 和(非)可分页内存¶
要了解 CUDA 如何将张量从 CPU 复制到 CUDA,让我们考虑上述两种情况:
如果存储器是 page-locked,则器件可以直接访问主存储器中的存储器。内存地址良好 defined 和需要读取这些数据的函数可以显著加速。
如果内存是可分页的,则必须先将所有页面发送到主内存,然后再发送到 GPU。 此操作可能需要一些时间,并且比在页面锁定张量上执行时更难预测。
更准确地说,当 CUDA 将可分页数据从 CPU 发送到 GPU 时,它必须首先创建该数据的页面锁定副本 在进行转移之前。
异步操作与同步操作 (CUDAnon_blocking=True
cudaMemcpyAsync
)¶
在执行从主机(例如 CPU)到设备(例如 GPU)的副本时, CUDA 工具包提供了执行这些操作的方法 操作。
在实践中,在调用 PyTorch 时,始终会调用 cudaMemcpyAsync。
如果 (默认),则在每个 和 之后调用 a ,使
在主线程中调用
blocking 。
如果 ,则不会触发同步,并且主机上的主线程未被阻塞。
因此,从主机的角度来看,可以同时将多个 Tensor 发送到设备,
因为线程不需要等待一个传输完成即可启动另一个传输。
non_blocking=False
cudaStreamSynchronize
cudaMemcpyAsync
non_blocking=True
注意
通常,传输在设备端被阻塞(即使它不在主机端): 执行其他操作时,无法在设备上进行复制。 但是,在某些高级方案中,可以在 GPU 端同时完成复制和内核执行。 如以下示例所示,必须满足三个要求才能启用此功能:
设备必须至少有一个空闲的 DMA (Direct Memory Access) 引擎。现代 GPU 架构,如 Volterra、 Tesla 或 H100 设备具有多个 DMA 引擎。
源数据必须位于固定内存中。
我们通过在以下脚本上运行配置文件来演示这一点。
import contextlib
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())
# The function we want to profile
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)
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
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
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")
在 chrome () 中加载这些配置文件跟踪显示以下结果:首先,让我们看看
如果在将可分页张量发送到 GPU 后执行 arithmetic operation on 会发生什么情况
在主流中:chrome://tracing
t3_cuda
benchmark_with_profiler(streamed=False, pinned=False)
![](https://pytorch.org/tutorials/_images/trace_streamed0_pinned0.png)
使用固定张量不会对跟踪发生太大变化,这两个操作仍然连续执行:
benchmark_with_profiler(streamed=False, pinned=True)
![](https://pytorch.org/tutorials/_images/trace_streamed0_pinned1.png)
在单独的流上将可分页张量发送到 GPU 也是一种阻塞操作:
benchmark_with_profiler(streamed=True, pinned=False)
![](https://pytorch.org/tutorials/_images/trace_streamed1_pinned0.png)
只有固定的张量副本到单独流上的 GPU 与 在 主流:
benchmark_with_profiler(streamed=True, pinned=True)
![](https://pytorch.org/tutorials/_images/trace_streamed1_pinned1.png)
PyTorch 透视图¶
pin_memory()
¶
PyTorch 提供了通过 method 和 constructor 参数创建张量并将其发送到页面锁定内存的可能性。
初始化 CUDA 的机器上的 CPU 张量可以通过该方法
转换为固定内存。重要的是,is 阻塞在主机的主线程上:它将等待张量被复制到
page-locked memory。
可以使用函数(如
,
和其他
构造 函数。
pin_memory
让我们检查一下固定内存和将张量发送到 CUDA 的速度:
import torch
import gc
from torch.utils.benchmark import Timer
import matplotlib.pyplot as plt
def timer(cmd):
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
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]
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"\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"]
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()
# Clear tensors
del pageable_tensor, pinned_tensor
_ = gc.collect()
![设备转换运行时 (pin-memory)](https://pytorch.org/tutorials/_images/sphx_glr_pinmem_nonblock_001.png)
pageable_tensor.to('cuda:0'): 0.4667 ms
pinned_tensor.to('cuda:0'): 0.3703 ms
pageable_tensor.pin_memory(): 0.3607 ms
pageable_tensor.pin_memory().to('cuda:0'): 0.7238 ms
我们可以观察到,将固定内存张量转换为 GPU 确实比可分页张量快得多,因为在 ,则必须先将可分页张量复制到固定内存,然后再将其发送到 GPU。
然而,与一些普遍的看法相反,调用
将其转换为 GPU 应该不会带来任何显着的加速,相反,此调用通常比
执行传输。这是有道理的,因为我们实际上是在要求 Python 执行 CUDA 将执行的操作
在将数据从 Host 复制到 Device 之前执行 anyway。
注意
在极少数情况下,依赖于通过 cudaHostAlloc 在固定内存中创建全新存储的 pin_memory 的 PyTorch 实现可能比像那样以块形式转换数据更快。
在这里,观察结果也可能因可用硬件、发送的张量大小或
可用 RAM 的数量。cudaMemcpy
non_blocking=True
¶
如前所述,许多 PyTorch 操作都可以选择相对于主机异步执行
通过参数。non_blocking
在这里,为了准确说明使用 的好处,我们将设计一个稍微复杂一点的
实验,因为我们想要评估在调用和不调用 的情况下将多个张量发送到 GPU 的速度有多快。non_blocking
non_blocking
# A simple loop that copies all tensors to cuda
def copy_to_device(*tensors):
result = []
for tensor in tensors:
result.append(tensor.to("cuda:0"))
return result
# A loop that copies all tensors to cuda asynchronously
def copy_to_device_nonblocking(*tensors):
result = []
for tensor in tensors:
result.append(tensor.to("cuda:0", non_blocking=True))
# We need to synchronize
torch.cuda.synchronize()
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]
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, color=colors)
ax.set_ylabel("Runtime (ms)")
ax.set_title("Device casting runtime (non-blocking)")
ax.set_xticks([])
ax.legend()
plt.show()
![设备强制转换运行时(非阻塞)](https://pytorch.org/tutorials/_images/sphx_glr_pinmem_nonblock_002.png)
copy_to_device(*tensors): 25.1154 ms
copy_to_device_nonblocking(*tensors): 19.2452 ms
为了更好地了解这里发生的事情,让我们分析一下这两个函数:
from torch.profiler import profile, ProfilerActivity
def profile_mem(cmd):
with profile(activities=[ProfilerActivity.CPU]) as prof:
exec(cmd)
print(cmd)
print(prof.key_averages().table(row_limit=10))
让我们看看具有常规 first 的调用堆栈:to(device)
print("Call to `to(device)`", profile_mem("copy_to_device(*tensors)"))
copy_to_device(*tensors)
------------------------- ------------ ------------ ------------ ------------ ------------ ------------
Name Self CPU % Self CPU CPU total % CPU total CPU time avg # of Calls
------------------------- ------------ ------------ ------------ ------------ ------------ ------------
aten::to 3.75% 1.167ms 100.00% 31.089ms 31.089us 1000
aten::_to_copy 13.71% 4.262ms 96.25% 29.922ms 29.922us 1000
aten::empty_strided 25.21% 7.838ms 25.21% 7.838ms 7.838us 1000
aten::copy_ 19.17% 5.960ms 57.33% 17.822ms 17.822us 1000
cudaMemcpyAsync 19.18% 5.962ms 19.18% 5.962ms 5.962us 1000
cudaStreamSynchronize 18.98% 5.901ms 18.98% 5.901ms 5.901us 1000
------------------------- ------------ ------------ ------------ ------------ ------------ ------------
Self CPU time total: 31.089ms
Call to `to(device)` None
现在是版本:non_blocking
print(
"Call to `to(device, non_blocking=True)`",
profile_mem("copy_to_device_nonblocking(*tensors)"),
)
copy_to_device_nonblocking(*tensors)
------------------------- ------------ ------------ ------------ ------------ ------------ ------------
Name Self CPU % Self CPU CPU total % CPU total CPU time avg # of Calls
------------------------- ------------ ------------ ------------ ------------ ------------ ------------
aten::to 4.47% 1.023ms 99.90% 22.875ms 22.875us 1000
aten::_to_copy 16.00% 3.664ms 95.43% 21.851ms 21.851us 1000
aten::empty_strided 32.26% 7.386ms 32.26% 7.386ms 7.386us 1000
aten::copy_ 22.86% 5.235ms 47.17% 10.801ms 10.801us 1000
cudaMemcpyAsync 24.31% 5.566ms 24.31% 5.566ms 5.566us 1000
cudaDeviceSynchronize 0.10% 23.144us 0.10% 23.144us 23.144us 1
------------------------- ------------ ------------ ------------ ------------ ------------ ------------
Self CPU time total: 22.898ms
Call to `to(device, non_blocking=True)` None
使用 时,结果无疑会更好,因为所有传输都是同时启动的
在主机端,并且只完成一次同步。non_blocking=True
好处会因张量的数量和大小以及硬件而异 使用。
注意
有趣的是,阻塞实际上执行相同的异步设备强制转换操作
() 作为每个副本后具有同步点的 URL。to("cuda")
cudaMemcpyAsync
non_blocking=True
协同 效应¶
现在我们已经指出,固定内存中已有的张量到 GPU 的数据传输比从
分页内存,并且我们知道异步执行这些传输也比同步更快,因此我们可以
这些方法的基准组合。首先,让我们编写几个新函数,它们将在每个张量上调用 and:pin_memory
to(device)
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
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 = {
"blocking": [page_copy, pinned_copy, pin_and_copy],
"non-blocking": [page_copy_nb, pinned_copy_nb, pin_and_copy_nb],
}
x = torch.arange(3)
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, 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([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()
del tensors, tensors_pinned
_ = gc.collect()
![运行时(pin-mem 和非阻塞)](https://pytorch.org/tutorials/_images/sphx_glr_pinmem_nonblock_003.png)
copy_to_device(*tensors): 616.7664 ms
copy_to_device_nonblocking(*tensors): 544.0807 ms
copy_to_device(*tensors_pinned): 371.0937 ms
copy_to_device_nonblocking(*tensors_pinned): 345.8835 ms
pin_copy_to_device(*tensors): 996.5177 ms
pin_copy_to_device_nonblocking(*tensors): 677.9741 ms
其他复制方向(GPU -> CPU、CPU -> MPS)¶
到目前为止,我们一直假设从 CPU 到 GPU 的异步复制是安全的。 这通常是正确的,因为 CUDA 会自动处理同步以确保正在访问的数据是 在读取时有效。 但是,此保证不适用于从 GPU 到 CPU 的相反方向的传输。 如果没有显式同步,这些传输无法保证副本在 数据访问。因此,主机上的数据可能不完整或不正确,从而有效地使其成为垃圾:
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"{i}th test failed with non_blocking. Skipping remaining tests")
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!")
0th test failed with non_blocking. Skipping remaining tests
No test failed with synchronize
同样的注意事项也适用于从 CPU 到非 CUDA 设备(如 MPS)的副本。 通常,只有当目标为 支持 CUDA 的设备。
总之,使用 时,将数据从 CPU 复制到 GPU 是安全的,但对于任何其他方向,仍然可以使用,但用户必须确保在执行设备同步之前执行
数据被访问。non_blocking=True
non_blocking=True
实用建议¶
现在,我们可以根据我们的观察总结一些早期建议:
通常,将提供良好的吞吐量,无论原始张量是 还是
不在固定内存中。
如果张量已经在固定内存中,则可以加速传输,但将其发送到
从 Python 主线程手动固定内存是主机上的阻塞操作,因此会湮灭大部分
使用的好处(因为 CUDA 无论如何都会进行pin_memory传输)。non_blocking=True
non_blocking=True
其他注意事项¶
众所周知,PyTorch 提供了一个类,其构造函数接受一个参数。
考虑到我们之前对 的讨论,您可能想知道 是如何设法
如果内存固定本身是阻塞的,则加快数据传输速度。
pin_memory
pin_memory
DataLoader
关键在于 DataLoader 使用单独的线程来处理从可分页到固定的数据传输 memory,从而防止主线程中的任何阻塞。
为了说明这一点,我们将使用同名库中的 TensorDict 原语。
调用 时,默认行为是将张量异步发送到设备,
后跟对 after.to()
torch.device.synchronize()
此外,还包括一个选项,该选项在继续执行之前启动多个线程以执行。
此方法可以进一步加快数据传输速度,如以下示例所示。TensorDict.to()
non_blocking_pin
pin_memory()
to(device)
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)})
# 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]
bar_labels = [
"Blocking copy (1x)",
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, color=colors)
ax.set_ylabel("Runtime (ms)")
ax.set_title("Device casting runtime")
ax.set_xticks([])
ax.legend()
plt.show()
![设备转换运行时](https://pytorch.org/tutorials/_images/sphx_glr_pinmem_nonblock_004.png)
td.to('cuda:0', non_blocking=False): 619.8487 ms
td.to('cuda:0'): 542.9433 ms
td.to('cuda:0', non_blocking_pin=True, num_threads=0): 658.6780 ms
td.to('cuda:0', non_blocking_pin=True, num_threads=4): 360.2737 ms
在此示例中,我们将许多大型张量从 CPU 传输到 GPU。
此方案非常适合利用 多线程 ,这可以显著提高性能。
但是,如果张量较小,则与多线程相关的开销可能会超过好处。
同样,如果只有几个张量,则将张量固定在单独线程上的优势就会受到限制。pin_memory()
作为附加说明,虽然在固定内存中创建永久缓冲区以穿梭似乎是有利的 张量,则此策略不一定会加速 计算。将数据复制到固定内存而导致的固有瓶颈仍然是一个限制因素。
此外,将驻留在磁盘上的数据(无论是在共享内存还是文件中)传输到 GPU 通常需要 将数据复制到 pinned memory (位于 RAM) 的中间步骤。 在这种情况下,利用 non_blocking 进行大量数据传输会显著增加 RAM 消耗。 可能导致不良反应。
在实践中,没有放之四海而皆准的解决方案。
将多线程与传输结合使用的有效性取决于
各种因素,包括特定系统、操作系统、硬件和任务的性质
正在执行。
以下是尝试加快 CPU 和 GPU 之间的数据传输或比较时要检查的因素列表
跨方案的吞吐量:pin_memory
non_blocking
可用内核数
有多少个 CPU 内核可用?系统是否与可能竞争的其他用户或进程共享 资源?
核心利用率
CPU 内核是否被其他进程严重占用?应用程序是否执行其他 CPU 密集型任务 与数据传输同时进行?
内存利用率
当前使用了多少可分页和页面锁定内存?是否有足够的可用内存进行分配 额外的固定内存而不影响系统性能?请记住,没有什么是免费的,例如会消耗 RAM 并可能影响其他任务。
pin_memory
CUDA 设备功能
GPU 是否支持多个 DMA 引擎进行并发数据传输?具体功能有哪些 正在使用的 CUDA 设备的局限性?
要发送的 Tensor 数量
在典型操作中传输了多少张量?
要发送的 Tensor 的大小
正在传输的张量的大小是多少?一些大张量或许多小张量可能无法从 相同的转学计划。
系统架构
系统架构如何影响数据传输速度(例如,总线速度、网络延迟)?
此外,在固定内存中分配大量张量或相当大的张量可能会垄断大量 部分的 RAM 中。 这会减少其他关键操作(如分页)的可用内存,这可能会对 算法的整体性能。
结论¶
在本教程中,我们探讨了影响传输速度和内存的几个关键因素
management 时。我们已经了解到,通常使用
加快数据传输速度,如果实施,还可以提高性能
正确。但是,这些技术需要仔细设计和校准才能有效。
non_blocking=True
请记住,分析代码并密切关注内存消耗对于优化资源至关重要 使用情况并实现最佳性能。
其他资源¶
如果您在使用 CUDA 设备时处理内存副本问题,或者想要详细了解 本教程中讨论的内容,请查看以下参考资料:
tensordict doc 和 repo 的 Doc 和 Repo 中。
脚本总运行时间:(1 分 17.533 秒)