目录

PyTorch 中 和 的良好使用指南non_blockingpin_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 中(“换入”或“页面输入”)。 反过来,操作系统可能必须换出(或“分页”)另一个页面,以便为新页面腾出空间。

与可分页内存相反,固定 (或分页锁定或不可分页) 内存是一种不能 换出到磁盘。 它允许更快、更可预测的访问时间,但缺点是它比 分页内存(又名主内存)。

CUDA 和(非)可分页内存

要了解 CUDA 如何将张量从 CPU 复制到 CUDA,让我们考虑上述两种情况:

  • 如果存储器是 page-locked,则器件可以直接访问主存储器中的存储器。内存地址良好 defined 和需要读取这些数据的函数可以显著加速。

  • 如果内存是可分页的,则必须先将所有页面发送到主内存,然后再发送到 GPU。 此操作可能需要一些时间,并且比在页面锁定张量上执行时更难预测。

更准确地说,当 CUDA 将可分页数据从 CPU 发送到 GPU 时,它必须首先创建该数据的页面锁定副本 在进行转移之前。

异步操作与同步操作 (CUDAnon_blocking=TruecudaMemcpyAsync)

在执行从主机(例如 CPU)到设备(例如 GPU)的副本时, CUDA 工具包提供了执行这些操作的方法 操作。

在实践中,在调用 PyTorch 时,始终会调用 cudaMemcpyAsync。 如果 (默认),则在每个 和 之后调用 a ,使 在主线程中调用 blocking 。 如果 ,则不会触发同步,并且主机上的主线程未被阻塞。 因此,从主机的角度来看,可以同时将多个 Tensor 发送到设备, 因为线程不需要等待一个传输完成即可启动另一个传输。non_blocking=FalsecudaStreamSynchronizecudaMemcpyAsyncnon_blocking=True

注意

通常,传输在设备端被阻塞(即使它不在主机端): 执行其他操作时,无法在设备上进行复制。 但是,在某些高级方案中,可以在 GPU 端同时完成复制和内核执行。 如以下示例所示,必须满足三个要求才能启用此功能:

  1. 设备必须至少有一个空闲的 DMA (Direct Memory Access) 引擎。现代 GPU 架构,如 Volterra、 Tesla 或 H100 设备具有多个 DMA 引擎。

  2. 传输必须在单独的非默认 cuda 流上完成。在 PyTorch 中,可以使用 .

  3. 源数据必须位于固定内存中。

我们通过在以下脚本上运行配置文件来演示这一点。

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://tracingt3_cuda

benchmark_with_profiler(streamed=False, pinned=False)

使用固定张量不会对跟踪发生太大变化,这两个操作仍然连续执行:

benchmark_with_profiler(streamed=False, pinned=True)

在单独的流上将可分页张量发送到 GPU 也是一种阻塞操作:

benchmark_with_profiler(streamed=True, pinned=False)

只有固定的张量副本到单独流上的 GPU 与 在 主流:

benchmark_with_profiler(streamed=True, pinned=True)

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)
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_blockingnon_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()
设备强制转换运行时(非阻塞)
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")cudaMemcpyAsyncnon_blocking=True

协同 效应

现在我们已经指出,固定内存中已有的张量到 GPU 的数据传输比从 分页内存,并且我们知道异步执行这些传输也比同步更快,因此我们可以 这些方法的基准组合。首先,让我们编写几个新函数,它们将在每个张量上调用 and:pin_memoryto(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 和非阻塞)
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=Truenon_blocking=True

实用建议

现在,我们可以根据我们的观察总结一些早期建议:

通常,将提供良好的吞吐量,无论原始张量是 还是 不在固定内存中。 如果张量已经在固定内存中,则可以加速传输,但将其发送到 从 Python 主线程手动固定内存是主机上的阻塞操作,因此会湮灭大部分 使用的好处(因为 CUDA 无论如何都会进行pin_memory传输)。non_blocking=Truenon_blocking=True

现在,人们可能会合理地问这种方法有什么用。 在下一节中,我们将进一步探讨如何使用它来进一步加速数据传输。

其他注意事项

众所周知,PyTorch 提供了一个类,其构造函数接受一个参数。 考虑到我们之前对 的讨论,您可能想知道 是如何设法 如果内存固定本身是阻塞的,则加快数据传输速度。pin_memorypin_memoryDataLoader

关键在于 DataLoader 使用单独的线程来处理从可分页到固定的数据传输 memory,从而防止主线程中的任何阻塞。

为了说明这一点,我们将使用同名库中的 TensorDict 原语。 调用 时,默认行为是将张量异步发送到设备, 后跟对 after.to()torch.device.synchronize()

此外,还包括一个选项,该选项在继续执行之前启动多个线程以执行。 此方法可以进一步加快数据传输速度,如以下示例所示。TensorDict.to()non_blocking_pinpin_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()
设备转换运行时
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_memorynon_blocking

  • 可用内核数

    有多少个 CPU 内核可用?系统是否与可能竞争的其他用户或进程共享 资源?

  • 核心利用率

    CPU 内核是否被其他进程严重占用?应用程序是否执行其他 CPU 密集型任务 与数据传输同时进行?

  • 内存利用率

    当前使用了多少可分页和页面锁定内存?是否有足够的可用内存进行分配 额外的固定内存而不影响系统性能?请记住,没有什么是免费的,例如会消耗 RAM 并可能影响其他任务。pin_memory

  • CUDA 设备功能

    GPU 是否支持多个 DMA 引擎进行并发数据传输?具体功能有哪些 正在使用的 CUDA 设备的局限性?

  • 要发送的 Tensor 数量

    在典型操作中传输了多少张量?

  • 要发送的 Tensor 的大小

    正在传输的张量的大小是多少?一些大张量或许多小张量可能无法从 相同的转学计划。

  • 系统架构

    系统架构如何影响数据传输速度(例如,总线速度、网络延迟)?

此外,在固定内存中分配大量张量或相当大的张量可能会垄断大量 部分的 RAM 中。 这会减少其他关键操作(如分页)的可用内存,这可能会对 算法的整体性能。

结论

在本教程中,我们探讨了影响传输速度和内存的几个关键因素 management 时。我们已经了解到,通常使用 加快数据传输速度,如果实施,还可以提高性能 正确。但是,这些技术需要仔细设计和校准才能有效。non_blocking=True

请记住,分析代码并密切关注内存消耗对于优化资源至关重要 使用情况并实现最佳性能。

其他资源

如果您在使用 CUDA 设备时处理内存副本问题,或者想要详细了解 本教程中讨论的内容,请查看以下参考资料:

脚本总运行时间:(1 分 17.533 秒)

由 Sphinx-Gallery 生成的图库

文档

访问 PyTorch 的全面开发人员文档

查看文档

教程

获取面向初学者和高级开发人员的深入教程

查看教程

资源

查找开发资源并解答您的问题

查看资源