目录

多处理最佳实践

是 Python 模块的直接替代品。它支持完全相同的操作, 而是扩展它,以便通过 发送的所有张量都会将其数据移动到共享 内存,并且只会将句柄发送到另一个进程。

注意

当 发送到另一个进程时,数据将被共享。如果是 not ,它也是共享的。在 a 没有 一个字段被发送到另一个进程,则 创建一个特定于 Standard Process-specific 的 不会在所有进程之间自动共享,这与 数据的共享方式不同。None.grad

这允许实施各种训练方法,例如 Hogwild、A3C 或任何 其他需要异步操作的。

多进程中的 CUDA

CUDA 运行时不支持 start 方法;或 start 方法为 在子进程中使用 CUDA 所需的。forkspawnforkserver

注意

可以通过使用 或 直接使用 创建上下文来设置 start 方法 。multiprocessing.get_context(...)multiprocessing.set_start_method(...)

与 CPU 张量不同,发送过程需要保留原始张量 只要接收进程保留 Tensor 的副本即可。它已实现 在后台,但要求用户遵循该计划的最佳实践 才能正常运行。例如,只要 Consumer 进程具有对 Tensor 的引用,而 refCounting 不能 如果使用者进程通过 Fatal 信号异常退出,则 Save You。请参阅此部分

另请参阅:使用 nn.parallel.DistributedDataParallel 而不是 multiprocessing 或 nn。DataParallel 数据并行

最佳实践和提示

避免和对抗死锁

当生成新进程时,有很多事情可能会出错,其中 死锁的最常见原因是后台线程。如果有 线程,并且被调用,则非常 子进程可能处于损坏状态并死锁,或者 以不同的方式失败。请注意,即使你不这样做,Python 内置的 库可以 - 无需再深入研究实际上是一个非常复杂的类,则 生成多个用于序列化、发送和接收对象的线程,它们 也可能导致上述问题。如果您发现自己处于这种情况 尝试使用 , 它不会 使用任何其他线程。forkSimpleQueue

我们正在尽最大努力让您轻松,并确保这些死锁不会 但有些事情超出了我们的控制范围。如果您有任何问题,则无法 应付一段时间,尝试在论坛上联系,我们会看看它是否是一个 我们可以修复的问题。

重用通过 Queue 传递的缓冲区

请记住,每次将 a 放入 时,它都必须移动到共享内存中。 如果已经共享,则为 no-op,否则将产生额外的 内存复制可能会减慢整个过程。即使您有一个 进程将数据发送到单个 Broker 中,使其将缓冲区发送回去 - 这个 几乎是免费的,并且可以让您在发送下一批时避免复制。

异步多进程训练(例如 Hogwild)

使用 ,可以训练模型 异步,参数要么一直共享,要么 定期同步。在第一种情况下,我们建议将整个 model 对象,而在后者中,我们建议只发送 .

我们建议使用 for pass all kinds 的 PyTorch 对象。例如,可以继承张量 以及共享内存中已有的存储,当使用 start 方法时, 但是,它很容易出错,应谨慎使用,并且只能由 Advanced 用户。队列,即使它们有时是一个不那么优雅的解决方案,也会起作用 在所有情况下都是正确的。fork

警告

您应该小心使用不受保护的 global 语句 替换为 .如果使用的 start 方法不同,它们将在所有子进程中执行。if __name__ == '__main__'fork

霍格维尔德

具体的 Hogwild 实现可以在 examples 存储库中找到, 但为了展示代码的整体结构,还有一个 minimal 示例如下:

import torch.multiprocessing as mp
from model import MyModel

def train(model):
    # Construct data_loader, optimizer, etc.
    for data, labels in data_loader:
        optimizer.zero_grad()
        loss_fn(model(data), labels).backward()
        optimizer.step()  # This will update the shared parameters

if __name__ == '__main__':
    num_processes = 4
    model = MyModel()
    # NOTE: this is required for the ``fork`` method to work
    model.share_memory()
    processes = []
    for rank in range(num_processes):
        p = mp.Process(target=train, args=(model,))
        p.start()
        processes.append(p)
    for p in processes:
        p.join()

多进程中的 CPU

不适当的多处理会导致 CPU 超额订阅,从而导致 不同的进程争夺 CPU 资源,导致 效率。

本教程将解释什么是 CPU 超额订阅以及如何 避免它。

CPU 超额订阅

CPU 超额订阅是一个技术术语,指的是一种情况 其中分配给系统的 vCPU 总数超过总数 硬件上可用的 vCPU 数量。

这会导致对 CPU 资源的严重争用。在这种情况下,有 在进程之间频繁切换,这会增加进程数 切换开销并降低整体系统效率。

请参阅 CPU 超额订阅以及 Hogwild 中的代码示例 在示例中找到的实现 存储库

在 CPU 上使用以下命令运行训练示例时 使用 4 个进程:

python main.py --num-processes 4

假设计算机上有 N 个 vCPU 可用,执行上述操作 command 将生成 4 个子进程。每个子进程将分配 N vCPU,因此需要 4*N 个 vCPU。但是, 计算机只有 N 个 vCPU 可用。因此,不同的 进程会争夺资源,导致进程频繁 开关。

以下观察结果表明存在 CPU 超过 订阅:

  1. CPU 使用率高:使用命令可以观察 CPU 利用率始终较高,通常达到或 超过其最大容量。这表明对 CPU 资源超过可用的物理内核,导致 进程之间对 CPU 时间的争用和竞争。htop

  2. 频繁的上下文切换,系统效率低下:在 超额订阅的 CPU 方案,进程争夺 CPU 时间,并且 操作系统需要在不同进程之间快速切换 公平分配资源。这种频繁的上下文切换增加了 开销并降低整体系统效率。

避免 CPU 超额订阅

避免 CPU 超额订阅的一个好方法是适当的资源分配。 确保并发运行的进程或线程数 不超过可用的 CPU 资源。

在这种情况下,解决方案是指定适当数量的 子进程中的线程。这可以通过设置数字来实现 使用 subprocess 中的函数的每个进程的线程数。torch.set_num_threads(int)

假设计算机上有 N 个 vCPU,并且 M 个进程将为 生成,则每个进程使用的最大值将 是。为避免 CPU 超额订阅mnist_hogwild example,示例中的文件需要进行以下更改 存储库num_threadsfloor(N/M)train.py

def train(rank, args, model, device, dataset, dataloader_kwargs):
    torch.manual_seed(args.seed + rank)

    #### define the num threads used in current sub-processes
    torch.set_num_threads(floor(N/M))

    train_loader = torch.utils.data.DataLoader(dataset, **dataloader_kwargs)

    optimizer = optim.SGD(model.parameters(), lr=args.lr, momentum=args.momentum)
    for epoch in range(1, args.epochs + 1):
        train_epoch(epoch, args, model, device, train_loader, optimizer)

使用 为每个进程设置 。其中,将 N 替换为 可用 vCPU 的数量和 M 与所选的进程数。这 适当的值将根据具体 任务在手。但是,作为一般准则,的最大值应该是避免 CPU 超额订阅。 在 mnist_hogwild 训练示例中,在避免 CPU 超额 订阅,您可以实现 30 倍的性能提升。num_threadtorch.set_num_threads(floor(N/M))num_threadnum_threadfloor(N/M)

文档

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

查看文档

教程

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

查看教程

资源

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

查看资源