PyTorch 用 PyTorch 编写分布式应用程序

2025-06-19 10:16 更新

在大规模深度学习模型训练和高效推理过程中,分布式计算技术发挥着至关重要的作用。PyTorch 作为当前主流的深度学习框架之一,提供了功能强大的分布式软件包(torch.distributed),助力开发者轻松实现跨多进程、多机器集群的并行计算。本文将深入剖析 PyTorch 分布式应用开发的关键技术点,并通过丰富的代码示例引导您快速上手,实现高效的分布式训练和推理。

一、分布式环境搭建

(一)初始化进程组

在 PyTorch 分布式应用中,首先需要初始化进程组,这是实现分布式通信的基础。每个进程通过指定的后端(如 Gloo、NCCL 等)进行通信,以下是使用 Gloo 后端进行初始化的示例代码:

import os
import torch
import torch.distributed as dist
from torch.multiprocessing import Process


def init_process(rank, size, backend='gloo'):
    """ 初始化分布式环境 """
    os.environ['MASTER_ADDR'] = '127.0.0.1'  # 主节点 IP 地址
    os.environ['MASTER_PORT'] = '29500'       # 主节点端口号
    dist.init_process_group(backend, rank=rank, world_size=size)
    print(f"进程 {rank} 初始化完成")


def main():
    size = 4  # 进程总数
    processes = []
    for rank in range(size):
        p = Process(target=init_process, args=(rank, size))
        p.start()
        processes.append(p)
    for p in processes:
        p.join()


if __name__ == "__main__":
    main()

(二)环境变量配置

为了确保分布式进程之间的正常通信,需要配置以下环境变量:

  • MASTER_ADDR:主节点的 IP 地址,用于其他进程连接。
  • MASTER_PORT:主节点的端口号,用于进程间通信。
  • WORLD_SIZE:进程总数,表示整个分布式环境中的进程数量。
  • RANK:当前进程的排名,唯一标识每个进程。

这些环境变量可以在代码中直接设置,也可以通过命令行参数传递。

二、点对点通信

点对点通信是分布式计算中的基本通信模式,允许数据在两个进程之间直接传输。PyTorch 提供了阻塞式和非阻塞式的点对点通信方法。

(一)阻塞式通信

阻塞式通信方法包括 sendrecv,它们会在数据传输完成之前阻塞当前进程。以下是阻塞式通信的代码示例:

def run(rank, size):
    tensor = torch.zeros(1)
    if rank == 0:
        tensor += 1
        dist.send(tensor=tensor, dst=1)
    else:
        dist.recv(tensor=tensor, src=0)
    print(f"进程 {rank} 的数据:{tensor[0]}")


def main():
    size = 2
    processes = []
    for rank in range(size):
        p = Process(target=init_process, args=(rank, size, run))
        p.start()
        processes.append(p)
    for p in processes:
        p.join()


if __name__ == "__main__":
    main()

(二)非阻塞式通信

非阻塞式通信方法包括 isendirecv,它们允许进程在数据传输的同时继续执行其他任务。以下是非阻塞式通信的代码示例:

def run(rank, size):
    tensor = torch.zeros(1)
    req = None
    if rank == 0:
        tensor += 1
        req = dist.isend(tensor=tensor, dst=1)
        print(f"进程 0 开始发送数据")
    else:
        req = dist.irecv(tensor=tensor, src=0)
        print(f"进程 1 开始接收数据")
    req.wait()
    print(f"进程 {rank} 的数据:{tensor[0]}")


def main():
    size = 2
    processes = []
    for rank in range(size):
        p = Process(target=init_process, args=(rank, size, run))
        p.start()
        processes.append(p)
    for p in processes:
        p.join()


if __name__ == "__main__":
    main()

三、集体通信

集体通信允许在进程组内进行高效的通信操作,常见的集体通信操作包括:

(一)广播(Broadcast)

广播操作将一个进程的数据分发到其他所有进程:

def run(rank, size):
    tensor = torch.ones(1)
    if rank == 0:
        dist.broadcast(tensor=tensor, src=0)
    else:
        dist.broadcast(tensor=tensor, src=0)
    print(f"进程 {rank} 的数据:{tensor[0]}")


def main():
    size = 2
    processes = []
    for rank in range(size):
        p = Process(target=init_process, args=(rank, size, run))
        p.start()
        processes.append(p)
    for p in processes:
        p.join()


if __name__ == "__main__":
    main()

(二)归约(Reduce)

归约操作将所有进程的数据汇总到一个指定的进程:

def run(rank, size):
    tensor = torch.ones(1)
    dist.reduce(tensor=tensor, dst=0, op=dist.ReduceOp.SUM)
    if rank == 0:
        print(f"进程 0 收到的总和:{tensor[0]}")


def main():
    size = 2
    processes = []
    for rank in range(size):
        p = Process(target=init_process, args=(rank, size, run))
        p.start()
        processes.append(p)
    for p in processes:
        p.join()


if __name__ == "__main__":
    main()

(三)全归约(All-Reduce)

全归约操作将所有进程的数据汇总后,再将结果分发到所有进程:

def run(rank, size):
    tensor = torch.ones(1)
    dist.all_reduce(tensor=tensor, op=dist.ReduceOp.SUM)
    print(f"进程 {rank} 的数据:{tensor[0]}")


def main():
    size = 2
    processes = []
    for rank in range(size):
        p = Process(target=init_process, args=(rank, size, run))
        p.start()
        processes.append(p)
    for p in processes:
        p.join()


if __name__ == "__main__":
    main()

四、分布式训练实践

(一)数据分区

在分布式训练中,需要将数据集分区,使每个进程处理不同的数据子集。以下是数据分区的代码示例:

from torch.utils.data import DataLoader, Dataset
import torch
import torch.distributed as dist
from torch.utils.data.distributed import DistributedSampler


class MyDataset(Dataset):
    def __init__(self):
        self.data = list(range(100))  # 示例数据


    def __len__(self):
        return len(self.data)


    def __getitem__(self, idx):
        return self.data[idx]


def partition_dataset(rank, world_size):
    dataset = MyDataset()
    sampler = DistributedSampler(dataset, num_replicas=world_size, rank=rank)
    dataloader = DataLoader(dataset, batch_size=2, sampler=sampler)
    return dataloader


def main():
    rank = 0
    world_size = 2
    dataloader = partition_dataset(rank, world_size)
    for batch in dataloader:
        print(f"进程 {rank} 的批次数据:{batch}")


if __name__ == "__main__":
    main()

(二)分布式同步 SGD

实现分布式同步随机梯度下降(SGD)是分布式训练的核心任务之一。以下是分布式同步 SGD 的代码示例:

import torch
import torch.nn as nn
import torch.optim as optim
import torch.distributed as dist
from torch.multiprocessing import Process


class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        self.fc = nn.Linear(10, 2)


    def forward(self, x):
        return self.fc(x)


def average_gradients(model):
    """ 平均模型梯度 """
    size = float(dist.get_world_size())
    for param in model.parameters():
        dist.all_reduce(param.grad.data, op=dist.ReduceOp.SUM)
        param.grad.data /= size


def run(rank, size):
    torch.manual_seed(1234)
    model = Net()
    optimizer = optim.SGD(model.parameters(), lr=0.01)
    criterion = nn.CrossEntropyLoss()


    # 模拟数据
    inputs = torch.randn(20, 10)
    labels = torch.randint(0, 2, (20,))


    # 分区数据
    inputs = inputs.chunk(size)[rank]
    labels = labels.chunk(size)[rank]


    for epoch in range(10):
        optimizer.zero_grad()
        outputs = model(inputs)
        loss = criterion(outputs, labels)
        loss.backward()
        average_gradients(model)
        optimizer.step()
        print(f"进程 {rank} - Epoch {epoch} - Loss: {loss.item()}")


def init_process(rank, size, fn):
    dist.init_process_group('gloo', rank=rank, world_size=size)
    fn(rank, size)


def main():
    size = 2
    processes = []
    for rank in range(size):
        p = Process(target=init_process, args=(rank, size, run))
        p.start()
        processes.append(p)
    for p in processes:
        p.join()


if __name__ == "__main__":
    main()

五、进阶主题

(一)通信后端选择

PyTorch 提供了多种通信后端,包括 Gloo、NCCL 和 MPI。每种后端都有其适用场景和性能特点:

  • Gloo:适用于 CPU 和 GPU 通信,支持多种集体通信操作,易于使用。
  • NCCL:专为 NVIDIA GPU 设计,提供高性能的 GPU 集体通信操作。
  • MPI:适用于大规模分布式计算环境,具有高度优化的通信性能。

(二)初始化方法

根据实际应用场景,可以选择不同的初始化方法来设置分布式环境:

  • 环境变量初始化:通过设置环境变量 MASTER_ADDRMASTER_PORTWORLD_SIZERANK 来初始化进程组。
  • 共享文件系统初始化:进程组通过共享文件系统进行初始化,适用于具有共享存储的集群环境。
  • TCP 初始化:通过指定主节点的 IP 地址和端口号进行初始化,适用于没有共享存储的环境。

## 环境变量初始化示例
dist.init_process_group(
    backend='gloo',
    init_method='env://'
)


## 共享文件系统初始化示例
dist.init_process_group(
    init_method='file:///mnt/nfs/sharedfile',
    rank=args.rank,
    world_size=args.world_size
)


## TCP 初始化示例
dist.init_process_group(
    init_method='tcp://10.1.1.20:23456',
    rank=args.rank,
    world_size=args.world_size
)

通过本文的详细讲解和代码示例,您已经掌握了 PyTorch 分布式应用开发的关键技术点,包括分布式环境搭建、点对点通信、集体通信以及分布式训练实践等内容。PyTorch 的分布式软件包为开发高效的分布式深度学习应用提供了强大的支持。未来,您可以进一步探索分布式模型并行、异构计算环境下的分布式训练等高级主题,以应对更大规模的模型和数据集挑战。编程狮将持续为您提供更多深度学习分布式计算的优质教程,助力您的技术成长与项目实践。

以上内容是否对您有帮助:
在线笔记
App下载
App下载

扫描二维码

下载编程狮App

公众号
微信公众号

编程狮公众号