趋近智
将计算负担和模型参数 (parameter)分布到多个设备上对于训练大型模型来说是必要的,但这会带来一个重要的性能考量:通信开销。每当数据需要在设备间交换时,无论是梯度、激活值还是权重 (weight)分片,时间都会花在通信上而不是计算上。将这种开销降到最低对于实现高效扩展和缩短训练时间来说十分重要。
本节分析了我们讨论过的不同并行策略所关联的通信模式和成本。了解这些成本有助于为给定的模型架构和硬件配置选择最合适的策略或策略组合。
分布式训练依赖于通信集合操作,这些操作涉及多个进程协同交换数据。LLM训练中最常用的基本操作包括:
broadcast): 将数据从一个进程发送到所有其他进程。reduce): 使用指定的运算(如求和、求平均)将所有进程的数据合并到一个进程上。all_reduce): 合并所有进程的数据,并将结果分发回所有进程。这实际上是先进行规约再进行广播的操作。它在数据并行中大量用于同步梯度。scatter): 将数据块从一个进程分散到所有其他进程。gather): 将所有进程的数据块收集到一个进程上。all_gather): 收集所有进程的数据块,并将完整的、拼接后的数据分发回所有进程。用于某些张量并行实现。reduce_scatter): 使用规约运算合并所有进程的数据,然后分散结果,使得每个进程接收最终规约张量的一个数据块。也用于张量并行。send/recv): 直接通信,一个进程向另一个特定进程发送数据,后者接收数据。这是流水线并行的主要机制。在PyTorch中,这些操作通常通过torch.distributed包来访问。例如,对一个组中所有进程的张量t执行All-Reduce操作可能看起来像这样:
import torch
import torch.distributed as dist
# 假设 't' 是当前设备上的一个张量
# 假设分布式环境已初始化
# 执行异步All-Reduce(默认求和)
dist.all_reduce(t, op=dist.ReduceOp.SUM, async_op=True)
# 稍后,如果需要则同步,或者链接计算
# ...
流水线并行的点对点通信涉及发送和接收进程对:
# 阶段 'i' 的进程将激活值发送到阶段 'i+1'
if rank == i:
# 假设 'activations' 是要发送的张量
dist.send(tensor=activations, dst=i+1)
# 阶段 'i+1' 的进程从阶段 'i' 接收激活值
elif rank == i+1:
# 为接收激活值分配缓冲区
received_activations = torch.zeros_like(expected_activations_shape)
dist.recv(tensor=received_activations, src=i)
通信所需的时间取决于几个因素:
一个常见且简单的用于表示大小为 的单个消息的通信时间 的模型是alpha-beta模型:
这里, 代表延迟部分, 是传输时实现的有效带宽。对于涉及 个设备的集合操作,模型会变得更复杂,通常会涉及与 的对数或线性相关的项,具体取决于所用算法。
让我们分析每种主要策略固有的通信成本:
数据并行 (DP):
all_reduce操作,以聚合所有 个副本的梯度。张量并行 (TP):
all_reduce、all_gather或reduce_scatter操作,这些层在设备间进行拆分。digraph G { rankdir=LR;
// 设置全局字体大小
fontsize=12;
// 节点设置,带有明确的字体大小
node [shape=circle, style=filled, color="#a5d8ff", fontname="sans-serif", fontsize=12];
// 边设置,带有明确的字体大小
edge [color="#495057", fontname="sans-serif", fontsize=12];
GPU0 -> GPU1 [label="前向/反向"];
GPU1 -> GPU2 [label="前向/反向"];
GPU2 -> GPU3 [label="前向/反向"];
GPU3 -> GPU0 [label="前向/反向"];
} ``` > 4-GPU环形All-Reduce中数据流的简化视图,常用于数据并行或张量并行集合操作。每个GPU都从其相邻设备发送和接收数据块。
send/recv操作。阶段 在前向传播期间将其输出激活值发送到阶段 ,而阶段 在反向传播期间将激活值的梯度发送回阶段 。| 策略 | 主要操作 | 消息大小 | 频率 | 敏感度 | 瓶颈 |
|---|---|---|---|---|---|
| 数据并行 | all_reduce |
模型梯度 (大) | 每 (累积) 步一次 | 带宽 | All-Reduce时间 |
| 张量并行 | all_reduce、all_gather、reduce_scatter |
层激活值/梯度 (小/中) | 每层多次 | 延迟、带宽 | 频繁的集合调用 |
| 流水线并行 | send/recv |
边界激活值/梯度 (中/大) | 每个微批量/阶段一次 | 延迟、带宽 | 流水线气泡、阶段间通信 |
表:不同并行策略通信特点的定性比较。
混合方法将这些策略结合起来,带来更复杂的通信模式。例如,将数据并行与张量并行结合使用意味着每个数据并行组(应用了张量并行的地方)都会执行梯度的All-Reduce操作。同时使用张量并行和流水线并行则涉及阶段内张量并行通信和阶段间流水线并行通信。
虽然理论分析提供了直觉,但特定训练运行中的实际通信开销在很大程度上取决于实现细节、硬件、网络配置和软件堆栈(例如PyTorch版本、NCCL版本)。因此,性能分析是必要的。torch.profiler、NVIDIA Nsight Systems (nsys) 或框架特有的日志记录等工具可以帮助衡量在不同通信操作(nccl:all_reduce、nccl:send等)与计算内核上花费的时间。分析这些性能文件对于找出瓶颈并优化分布式训练配置十分重要。
# 使用 torch.profiler 捕获 CPU 和 GPU 活动的示例
# 包括分布式通信调用(如果使用 NCCL 后端)
with torch.profiler.profile(
activities=[
torch.profiler.ProfilerActivity.CPU,
torch.profiler.ProfilerActivity.CUDA
],
record_shapes=True,
profile_memory=True,
with_stack=True
) as prof:
with torch.profiler.record_function("model_training_step"):
# 在这里执行模型的正向、反向传播和优化器步骤
outputs = model(inputs)
loss = criterion(outputs, targets)
loss.backward()
optimizer.step()
# 打印聚合统计信息
print(prof.key_averages().table(sort_by="cuda_time_total", row_limit=10))
# 导出跟踪文件,以便在 Perfetto UI 或 Chrome Trace Viewer 等工具中进行详细分析
# prof.export_chrome_trace("trace.json")
"通过了解与每种并行策略相关的基本通信模式和成本,并使用性能分析工具来衡量表现,您可以做出明智的决定,了解如何最好地分配您的LLM训练工作负载,以最大程度地提高效率并缩短训练时间。"
这部分内容有帮助吗?
© 2026 ApX Machine LearningAI伦理与透明度•