HCCL通信库的时间都花在哪——大模型训练中的通信性能数据拆解:从AllReduce到拓扑感知的链路利用率全链路分析
前言
在昇腾NPU集群上跑大模型分布式训练,最让人头疼的事情之一不是算力不够,而是通信吃掉了太多时间。CANN架构体系里,HCCL作为集合通信库负责多卡之间的数据同步和规约,它本身不参与计算,但每一次AllReduce、每一次AllGather都在消耗宝贵的训练周期。这篇文章要做的事情很明确:把HCCL通信过程拆开,看看时间到底花在了哪些环节,以及不同的算法选择和拓扑配置会对通信效率造成什么影响。
HCCL在CANN五层架构中位于第四层——昇腾计算执行层,与Runtime运行时、Graph Executor图执行器并列。它为上层框架(PyTorch、MindSpore等)提供集合通信原语,底层则依赖HCCS链路和RoCE网络完成实际的数据搬运。理解HCCL的性能特征,是优化大模型分布式训练吞吐的前提条件。
从一张Profiling图说起
在典型的多NPU大模型训练场景下,用CANN自带的profiling工具抓一次训练迭代的耗时分布,结果往往让人警醒。以一个常见的千卡规模训练任务为例,每个训练迭代的总耗时中,纯计算(前向+反向)和通信(梯度同步+参数同步)的比例并不是人们直觉中的"九比一"。在模型并行度较高、序列长度较大的场景下,通信占比往往会攀升到一个不可忽视的水平。
具体来看profiling数据中的几个关键区间:前向传播的矩阵乘法和注意力计算占据一部分时间,反向传播的梯度计算占据另一部分,而穿插在反向传播过程中的梯度AllReduce操作、以及模型并行引入的AllGather和ReduceScatter操作,它们的时间加在一起,在某些配置下甚至可以占到单步迭代总耗时的三成以上。这还只是平均情况——当某些通信操作因为拓扑不优或链路拥塞而拖长时,整体训练的步进速度就会被这些"通信气泡"卡住。
通信气泡的来源并不单一。一个训练步里,不同类型的通信操作穿插进行,有的需要等前一步计算完成才能启动,有的可以和计算重叠执行。如果重叠做得不好,计算单元就会空闲等待通信完成,造成所谓的"气泡"。这个气泡的大小,直接取决于HCCL内部的算法选择、拓扑路径和链路利用率。理解了这一点,就能明白为什么单纯提升链路带宽并不能等比例地降低通信耗时——拓扑路径上的跳数、每跳的启动延迟、以及算法本身的流水线效率,都在起作用。
HCCL的核心通信模式
大模型训练中用到的集合通信操作,核心就三种:AllReduce、AllGather和ReduceScatter。它们各自负责不同的同步需求,理解它们的语义和数据流向,是分析通信时间拆解的基础。
AllReduce是最常见的梯度同步操作。每张NPU上计算出本地梯度后,需要把所有卡上的梯度做规约(求和或求平均),然后把结果广播回所有参与者。在数据并行训练中,每个参数的梯度都要经历一次AllReduce。AllReduce的数据量取决于参数规模——一个数十亿参数的模型,梯度同步的数据量就达到数十GB量级。
AllGather的操作语义更简单:每张卡持有一部分数据,AllGather把所有卡的数据收集到一起,每张卡都得到完整数据。在张量并行(即模型并行中的列并行或行并行)中,前向传播时每张卡只持有权重矩阵的一个分片,需要通过AllGather拼出完整矩阵才能做矩阵乘法。这意味着每个Transformer层的前向和反向都可能各触发一次AllGather。
ReduceScatter是AllReduce的"半程"版本:每张卡持有一部分数据,规约后每张卡只拿到属于自己的那一块结果,不需要广播完整结果。在张量并行的反向传播中,或者在使用序列并行时,ReduceScatter被频繁调用来分发规约后的梯度片段。
这三种操作的共同点是:它们都涉及多张NPU之间的数据交换,交换的数据量与模型规模和并行策略直接相关。不同之处在于数据流向和每张卡的输入输出大小。AllReduce是全进全出,AllGather是分进全出,ReduceScatter是分进分出。这个区别决定了它们在不同算法和拓扑下的表现差异。
从时间拆解的角度看,一次集合通信操作的总耗时由以下几个部分叠加:算法逻辑带来的多次同步点(比如Ring算法要N-1步才能走完)、每一步的数据传输时间(取决于链路带宽和数据量)、同步等待时间(最快的卡要等最慢的卡到达同步点)、以及协议和软件栈的开销(HCCL内部的状态管理和调度逻辑)。这些因素中,链路带宽只是其中一个维度,而且往往不是瓶颈所在。
通信时间的量化拆解
把一次通信操作的时间拆开来看,最直观的分解方式是:启动延迟加上数据传输时间。启动延迟是发出通信指令到第一个字节上链路的时间,数据传输时间是所有字节穿过链路的时间。但这个分解太粗了,无法解释为什么同样的数据量在不同的拓扑和算法下,耗时差异会很大。
更细的拆解需要考虑以下几个维度。
链路带宽和延迟本身。昇腾NPU之间的互连链路分为机内和机间两种。机内链路走HCCS(Huawei Cache Coherence System),带宽高、延迟低,直接连接同一台服务器内的NPU。机间链路走RoCE网络,经过交换机,带宽和延迟都不如机内链路。链路的物理特性决定了传输时间的理论下限——数据量除以带宽就是传输时间的最小值,但实际远不止于此。
数据量的影响是线性的。更大的模型参数、更大的批量、更长的序列,都意味着每次通信要搬运更多数据。在AllReduce场景下,数据量就是梯度的总字节数;在AllGather场景下,数据量是每张卡持有的分片大小乘以参与卡数。数据量翻倍,传输时间翻倍,这很直观。
拓扑路径的影响是非线性的,也是最容易被人忽视的。拓扑决定了数据要从源卡到目的卡需要经过几跳、经过哪些链路、会不会经过拥塞节点。Ring拓扑中数据要沿着环依次传递,虽然每次只用一条链路,但总步数等于参与方数减一。Tree拓扑可以减少步数,但中间节点的带宽会成为瓶颈。Mesh拓扑看起来路径最短,但链路数量和路由复杂度都更高。选择不同的算法,本质上就是在步数、带宽利用率和同步复杂度之间做权衡。
把这些因素放在一起,一次集合通信的实际耗时可以用一个简化模型来近似:
# 通信耗时拆解的简化模型
steps = algo_steps(n) # 算法需要的步数,取决于算法类型和参与方数量n
per_step_time = data_size / effective_bw # 每步传输时间
sync_overhead = barrier_cost(n, topo) # 同步等待开销
total = steps * per_step_time + sync_overhead
这个模型把通信耗时拆成三个独立可调的因子——步数、单步传输时间和同步开销。实际调优时,减少步数靠换算法,提升单步传输时间靠优化带宽利用率,降低同步开销靠改善拓扑和减少straggler。三个维度独立分析,才能定位到真正的瓶颈,而不是笼统地说"通信太慢"。
effective_bw(有效带宽)这个概念值得展开说一下。链路的理论带宽和实际可用的有效带宽之间往往有很大差距。理论带宽是物理链路在理想条件下的最大吞吐,但实际传输中,协议开销、链路争用、对头阻塞、不均衡的路由分配都会吃掉一部分带宽。在多卡同时通信的场景下,多条流共享同一条物理链路时,每条流分到的带宽只是链路带宽的一个分数。这就是为什么拓扑利用率——也就是物理链路被有效使用的比例——比理论带宽更能决定实际性能。
同步等待开销也不容小觑。在分布式训练中,所有参与方必须到达同步点后才能继续。如果有一张卡因为计算负载不均或调度抖动而慢了一拍,其他所有卡都要等它。这种等待时间会随着参与方数量增加而放大——卡越多,出现慢卡的概率越高,同步等待的期望时间也越长。这也是为什么大规模训练中,通信性能往往比小规模测试时差得多的原因之一。
Ring vs Tree vs Mesh:三种算法的实际表现
HCCL支持多种集合通信算法,最基础的三种是Ring、Tree和Mesh。它们在步数、带宽利用率和适用场景上各有特点,选择哪种算法对通信性能有直接影响。
Ring算法是最经典的集合通信实现。在Ring AllReduce中,参与方排成一个逻辑环,数据沿环依次传递。AllReduce被分为ReduceScatter和AllGather两个阶段:ReduceScatter阶段每一步每张卡把自己的一部分数据发给下一张卡,下一张卡收到后做规约再往下传,N-1步后每张卡持有一块完整的规约结果;AllGather阶段反过来,每张卡把自己持有的那一块规约结果沿环广播出去,再N-1步后每张卡都拿到完整结果。Ring算法的优点是每步只使用一条链路,不会争用带宽;缺点是总步数是2(N-1),参与方越多越慢。
Tree算法把参与方组织成一棵树,根节点负责规约和广播。在Reduce阶段,叶子节点把数据发给父节点,逐层规约到根;在Broadcast阶段,根节点把结果逐层向下广播。Tree算法的步数是树深度的两倍,对于二叉树就是2*log2(N),比Ring算法少很多。但Tree算法的缺点是中间节点的带宽压力大——根节点的入带宽要接收所有子节点的数据,出带宽要向所有子节点广播,容易成为瓶颈。
Mesh算法尝试让每张卡直接和其他卡通信,减少中间跳转。在理想情况下,Mesh可以一步完成所有数据交换,但实际上受限于物理链路数量和路由能力,通常需要多步才能完成。Mesh的优势是链路利用率高,多条链路可以并行工作;劣势是调度复杂,且对底层网络的拓扑感知要求高。
实际性能对比中,三种算法没有绝对的赢家。在小规模(比如8卡机内)场景下,Ring算法因为实现简单、链路利用率均匀,往往是默认选择。在中等规模(几十到上百卡)场景下,Tree算法因为步数少而表现更好,尤其是当数据量较大时,步数的节省带来的收益超过带宽争用的损失。在大规模(数百到数千卡)场景下,Mesh算法如果配合良好的拓扑感知和路由调度,可以获得最高的有效带宽利用率,但实现难度也最大。
HCCL内部有算法自动选择机制,会根据参与方数量、数据量和拓扑信息自动切换算法。但在某些边界条件下,自动选择的策略可能不是最优的,这时就需要手动指定算法。
# HCCL算法选择的配置示例
import hccl
# 创建通信域时指定算法偏好
config = {
"algorithm": "tree", # 可选 ring / tree / mesh
"net_type": "roce", # 机间通信用RoCE
}
# 更常见的做法是通过环境变量控制
# HCCL_ALGO=Tree 优先使用Tree算法
# HCCL_ALGO=Ring 优先使用Ring算法
没有一种算法在所有场景下都是最优的,所以HCCL提供了算法选择机制。环境变量方式比代码内配置更灵活,因为不需要修改训练脚本就能切换算法,方便在调优阶段快速试验不同策略。实际工程中,调优人员往往通过批量试验不同算法组合来找到特定集群配置下的最优解。
从步数的角度看,N=8时Ring需要14步,Tree只需要6步;N=64时Ring需要126步,Tree只需要12步。步数差异在大规模时非常显著。但步数少不代表总时间少——Tree算法每步的数据量可能更大,根节点的带宽瓶颈可能让每步的实际耗时远高于Ring。所以最终的判断必须落在实际测试数据上,不能只看理论分析。
另一个容易被忽略的维度是算法对计算和通信重叠的友好程度。Ring算法因为每步数据量小,更容易和前一步的计算重叠;Tree算法因为每步数据量大,重叠窗口更短。在大模型训练中,通信和计算的重叠是提升训练吞吐的关键手段之一——如果通信可以完全隐藏在计算时间内,那通信耗时对训练步进速度的影响就是零。但完全重叠在现实中很难做到,尤其当通信耗时超过计算耗时的时候。
跨机通信与机内通信的鸿沟
一台服务器内部,多张NPU之间通过HCCS链路互连,带宽充裕、延迟极低。一旦通信跨越服务器边界,数据就要走RoCE网络,经过PCIe交换、网卡、光纤、交换机,再反向进入对端服务器。这条路径上的每一个环节都会引入额外的延迟和带宽损耗。
机内通信和机间通信的差距是数量级的。以常见的8卡服务器为例,机内任意两张NPU之间的HCCS链路带宽可以达到数十GB/s级别,延迟在微秒级别。而机间RoCE链路的可用带宽受限于网卡速率和交换机端口带宽,典型配置下每条链路的可用带宽通常在几GB/s到十几GB/s之间,延迟也从微秒级跳到十微秒甚至百微秒级。
这个差距在通信时间上的体现是:同样一次AllReduce操作,如果参与方全部在同一台服务器内,通信耗时相对较短;如果参与方分布在多台服务器上,通信耗时会大幅增加,增加的幅度远超参与方数量的线性增长。原因在于跨机通信引入了额外的协议开销(RoCE的拥塞控制和重传机制)、交换机的排队延迟、以及跨机链路带宽的争用。
HCCL在处理跨机通信时,会采用分层策略:先在机内完成ReduceScatter,把每台服务器内8张卡的数据规约为一份中间结果,然后只在服务器之间做跨机规约,最后在机内做AllGather分发。这种分层方式可以减少跨机链路上的数据量——如果直接让每张卡都参与跨机通信,跨机链路上的数据量是8倍,而分层后只需要1倍。
# HCCL分层通信的配置示意
# 通过环境变量启用分层通信
# HCCL_MULTI_MODE=enable 启用多机分层
# HCCL_INTRA_NIC=enable 机内通信走HCCS
# HCCL_INTER_NIC=roce 机间通信走RoCE
# 在训练脚本中初始化通信域
import torch
import torch_npu
# 初始化分布式环境
torch.distributed.init_process_group(backend="hccl")
# HCCL自动感知拓扑并应用分层策略
# 但在非标准拓扑下可能需要手动指定
rank = torch.distributed.get_rank()
world_size = torch.distributed.get_world_size()
local_rank = rank % 8 # 每台服务器8张卡
分层通信的核心思路是"减少跨机数据量"。跨机链路是最稀缺的资源——带宽低、延迟高、争用多。把尽可能多的数据交换留在机内完成,只在不得已的时候才走跨机链路,这是利用拓扑信息来优化通信的基本策略。HCCL的自动拓扑感知可以处理标准配置,但在非标准拓扑(比如跨机链路不均匀、某些节点间带宽异常)下,手动干预可能更有效。
跨机通信的另一个挑战是straggler效应的放大。在机内,8张卡的时钟频率和负载通常比较均衡,同步等待时间较短。但在跨机场景下,不同服务器的计算速度可能因为CPU负载、内存带宽争用、PCIe带宽波动等原因出现差异,导致某些服务器总是比其他服务器慢一拍。这种差异在大规模集群中几乎不可避免,而且难以通过软件手段完全消除。HCCL内部的同步机制会等待所有参与方到达同步点,所以最慢的那个节点决定了整体速度。
实际调优中,减少跨机通信的占比是提升训练吞吐最有效的手段之一。这包括:尽可能增大数据并行的组内卡数(让AllReduce在机内完成)、使用流水线并行来减少跨机AllGather的频率、以及在拓扑感知调度时把通信密集的组放在同一台服务器内。这些策略的共同点都是"把通信留在带宽充裕的地方做"。
通信策略调整带来的吞吐提升
理论分析讲了这么多,最终还是要落在实际效果上。在大模型训练的工程实践中,调整HCCL的通信策略确实可以带来训练吞吐的显著提升,但前提是调对了方向。
调整通信策略的核心思路有三个方向:减少通信次数、减少每次通信的数据量、提高通信和计算的重叠度。这三个方向不是互斥的,往往需要同时发力。
减少通信次数的典型手段是梯度累积。不每一步都做AllReduce,而是累积若干步的梯度后统一做一次。这样通信频率降低了,但等价的批量大小不变。代价是增加了显存占用(需要保存累积的梯度),而且累积步数过多会影响训练收敛性。在实际工程中,梯度累积步数的选择需要在通信开销和训练效果之间权衡。
减少每次通信的数据量的手段更多。一种是使用梯度压缩——在AllReduce之前把梯度从FP32压缩到FP16甚至更低精度,数据量直接减半或更多。HCCL支持FP16和BF16的集合通信,可以在传输时使用低精度格式,减少链路上的数据量。另一种是利用模型并行的结构,让每张卡只同步自己负责的那部分参数,而不是全量同步。张量并行和序列并行就是这种思路的体现。
提高通信和计算的重叠度是最精巧也最有效的手段。基本想法是:在反向传播过程中,一层一层的梯度是逐步产生的,不需要等所有层的梯度都算完再做AllReduce,而是算完一层的梯度就立刻发起这一层的AllReduce,同时继续计算下一层的梯度。这样通信和计算在时间上重叠,通信的额外耗时被"隐藏"在计算时间内。
# 梯度异步AllReduce的重叠策略示意
import torch
import torch_npu
# 反向传播中逐层发起AllReduce
for name, param in model.named_parameters():
if param.grad is not None:
# 不等所有层算完,算完一层就发
grad = param.grad
# 把梯度切分成小块流水线发送
chunk_size = grad.numel() // pipeline_depth
for i in range(pipeline_depth):
chunk = grad.flatten()[i*chunk_size : (i+1)*chunk_size]
handle = torch.distributed.all_reduce(
chunk, async_op=True
)
handles.append(handle)
# 等所有异步操作完成
for h in handles:
h.wait()
异步all_reduce配合流水线切分,目的是让通信和计算在时间线上交错进行。把梯度切成小块流水线发送,比一次性发送整块梯度更容易和计算重叠——大块发送时,通信要独占链路很长时间,计算单元在这段时间只能空等;小块发送时,每块通信时间短,计算单元的等待间隙也短,重叠效率更高。pipeline_depth的选择需要根据实际的计算时间和通信时间比例来调——太大了管理开销高,太小了重叠效果不明显。
综合这些策略调整后,训练吞吐的实际提升效果可以通过对比表格来呈现。
使用前后的效率对比
| 对比维度 | 使用前(默认配置) | 使用后(优化策略) |
|---|---|---|
| 通信算法 | Ring(所有规模统一使用) | 按规模自动选择Ring/Tree/Mesh |
| 通信与计算重叠 | 无重叠,先算后传 | 逐层异步AllReduce,计算通信流水线化 |
| 跨机通信策略 | 所有卡直接参与跨机AllReduce | 分层策略,机内规约后跨机,减少跨机数据量 |
| 梯度精度 | FP32全量传输 | FP16传输,数据量减半 |
| 拓扑感知 | 未配置,默认逻辑拓扑 | 根据物理拓扑手动配置,减少跨机跳数 |
| 训练吞吐 | 基线水平,通信占比较高 | 吞吐获得显著提升,通信占比大幅下降 |
| 通信耗时占比 | 在大规模场景下可能超过三成 | 优化后通常降至可接受范围 |
这张表里的对比不是理论推演的结果,而是实际调优过程中观察到的变化趋势。具体数值因集群配置、模型规模和并行策略而异,但方向是确定的:算法选择、分层通信、精度压缩和计算通信重叠,这四招组合使用,通信耗时占比的下降幅度是实质性的。
仓库链接:https://atomgit.com/cann/hccl
更多推荐


所有评论(0)