当前位置: 首页 > news >正文

CANN ops-transformer:AllReduce 与 AllGather 在分布式推理中的选型


个人主页:ujainu

文章目录

    • 前言
    • 分布式推理的通信需求
    • AllReduce 原理
      • Ring AllReduce
      • Tree AllReduce
      • 带宽优化
    • AllGather 原理
      • Gather 语义
      • 内存开销
      • 适用场景
    • ops-transformer 中的选型策略
      • KV Cache 广播用 AllGather
      • 梯度同步用 AllReduce
    • 性能对比
      • 延迟对比
      • 带宽利用率
      • 卡数扩展性
    • 关键警告
      • 警告 1:AllGather 的内存爆炸
      • 警告 2:AllReduce 的死锁风险
    • 结尾行动指引

前言

在大模型推理部署中,昇腾CANN 作为昇腾NPU 的算子公共平台中间件,通过 ops-transformer 组件提供高效的分布式推理能力。随着模型规模突破千亿参数,单卡显存已无法承载完整的模型权重和KV Cache,分布式推理成为必然选择。ops-transformer 作为昇腾CANN 的核心算子加速库,在分布式推理场景下需要精心选择集合通信原语,以平衡通信开销与计算效率。本文将深入剖析 AllReduce 与 AllGather 在 ops-transformer 分布式推理中的选型策略,帮助开发者理解其设计理念、架构实现和性能优化要点。

分布式推理的通信需求

分布式推理通过模型并行策略将计算图切分到多卡上执行,不同并行策略产生差异化的通信模式:

张量并行(TP)将模型的每一层切分到多卡,前向和反向传播需要在每层结束时进行激活值的规约或广播。TP 的通信特点是高频次、小消息,对延迟敏感。

流水线并行(PP)将模型的不同层分配到不同卡上,形成流水线。PP 的通信集中在相邻层之间的激活值和梯度传递,通信量中等,但对时序要求严格。

专家并行(EP)用于 MoE 模型,将不同的专家网络部署在不同卡上。EP 的通信特点是稀疏且动态,需要根据路由结果进行 All-to-All 通信。

ops-transformer 在设计时充分考虑了这些通信模式的差异。对于 TP 中的层间同步,通常选择 AllReduce;对于 KV Cache 的跨卡广播,则采用 AllGather。这种差异化选型源于两种原语在语义和性能上的本质区别。

AllReduce 原理

AllReduce 是集合通信中的规约操作,将所有进程的数据进行聚合(如求和、求平均)后再分发到每个进程。在分布式推理中,AllReduce 主要用于梯度同步或激活值规约。

Ring AllReduce

Ring AllReduce 通过构建逻辑环实现高效通信,分为 Scatter-Reduce 和 AllGather 两个阶段。在 Scatter-Reduce 阶段,每个节点将部分数据规约到环上的下一个节点;在 AllGather 阶段,将规约结果广播到所有节点。

# Ring AllReduce 伪代码示例defring_allreduce(tensor,rank,world_size):# Scatter-Reduce 阶段forstepinrange(world_size-1):send_idx=(rank+step)%world_size recv_idx=(rank+step+1)%world_size send_data=tensor[send_idx::world_size]recv_data=tensor[recv_idx::world_size]# 执行规约操作tensor[recv_idx::world_size]=reduce(send_data,recv_data)# AllGather 阶段forstepinrange(world_size-1):send_idx=(rank-step+world_size)%world_size recv_idx=(rank-step-1+world_size)%world_size send_data=tensor[send_idx::world_size]recv_data=tensor[recv_idx::world_size]# 广播规约结果tensor[recv_idx::world_size]=recv_data

Ring AllReduce 的通信复杂度为 O(N),其中 N 是数据量,与卡数无关。这使得它在大规模集群中表现出优异的带宽利用率。

Tree AllReduce

Tree AllReduce 采用树形拓扑进行规约,分为 Reduce-Scatter 和 Broadcast 两个阶段。在 Reduce-Scatter 阶段,叶子节点向父节点发送数据并规约;在 Broadcast 阶段,根节点的结果向下广播。

// Tree AllReduce C++ 实现片段voidhcclAllReduce(constvoid*sendbuf,void*recvbuf,size_t count,hcclDataType_t datatype,hcclRedOp_t op,hcclComm_t comm){// 构建逻辑树拓扑intrank=hcclGetRank(comm);intnranks=hcclGetNumRanks(comm);// Reduce-Scatter 阶段treeReduceScatter(sendbuf,recvbuf,count,datatype,op,comm);// Broadcast 阶段treeBroadcast(recvbuf,count,datatype,comm);}

Tree AllReduce 的通信复杂度为 O(log N),其中 N 是卡数。在小规模集群中,Tree 算法通常比 Ring 更快;但在大规模场景下,Ring 的带宽优势更为明显。

带宽优化

hccl(Huawei Collective Communications Library)通过以下策略优化 AllReduce 的带宽利用率:

  1. 流水线化:将大数据切分为多个 chunk,在不同 chunk 间实现计算与通信的流水线
  2. 拓扑感知:根据昇腾NPU 的物理拓扑(如 HCCS 互联)选择最优的 Ring 或 Tree 构建方式
  3. 数据类型优化:针对 FP16、BF16 等低精度数据类型,使用专门的 kernel 实现
# 启动分布式推理时配置 AllReduce 算法exportHCCL_ALGO=Ring# 或 TreeexportHCCL_BUFFSIZE=2048# 通信缓冲区大小(MB)exportHCCL_RDMA_TC=96# RDMA 流量控制# 启动推理服务python-mops_transformer.inference\--model-path /path/to/model\--tp-size8\--comm-algo ring

AllGather 原理

AllGather 是集合通信中的收集操作,将每个进程的数据片段收集到一起,形成完整的数据视图。在分布式推理中,AllGather 主要用于 KV Cache 的跨卡广播或模型权重的分布式加载。

Gather 语义

AllGather 的语义是:每个进程 i 持有数据块 D_i,操作后所有进程都拥有 [D_0, D_1, …, D_{N-1}] 的完整数据。这与 AllReduce 的规约语义有本质区别:AllGather 不做计算,只做数据重排。

# AllGather 的直观理解# 初始状态:# Rank 0: [A]# Rank 1: [B]# Rank 2: [C]# Rank 3: [D]# AllGather 后:# Rank 0: [A, B, C, D]# Rank 1: [A, B, C, D]# Rank 2: [A, B, C, D]# Rank 3: [A, B, C, D]

内存开销

AllGather 的内存开销与参与进程数和单个数据块大小成正比。在 KV Cache 广播场景中,假设序列长度为 S,头数为 H,头维度为 D,卡数为 N,则每个卡需要存储 N × S × H × D 的 KV Cache。这在长序列推理时会成为显存瓶颈。

ops-transformer 通过以下方式优化 AllGather 的内存开销:

  1. 分块 AllGather:将 KV Cache 按层或按头分块,按需拉取,减少峰值显存
  2. KV Cache 共享:在多轮对话中,已计算的 KV Cache 可以跨请求共享,避免重复 AllGather
  3. 量化压缩:对 KV Cache 进行 INT8 或 INT4 量化,减少通信量和显存占用
// Ascend C 实现的量化 AllGather 内核__global__voidquantized_allgather_kernel(constint8_t*input,// INT8 量化后的输入float*output,// FP32 反量化输出constfloat*scale,// 量化缩放因子inttotal_size,intrank,intworld_size){inttid=blockIdx.x*blockDim.x+threadIdx.x;intchunk_size=total_size/world_size;intstart=rank*chunk_size;intend=start+chunk_size;for(inti=start+tid;i<end;i+=blockDim.x*gridDim.x){// AllGather 通信(省略 hccl 调用)// 反量化:INT8 -> FP32output[i]=(float)input[i]*scale[i/chunk_size];}}

适用场景

AllGather 在以下场景中优于 AllReduce:

  1. 数据分发:需要将每卡的数据片段聚合为完整视图(如 KV Cache 广播)
  2. 只读数据:聚合后的数据不会被修改,无需规约计算
  3. 内存带宽受限:当计算瓶颈在内存带宽而非计算能力时,AllGather 的简洁语义可以减少 kernel 启动开销

ops-transformer 中的选型策略

ops-transformer 在分布式推理中采用差异化的通信原语选型策略,核心原则是:规约操作用 AllReduce,数据广播用 AllGather

KV Cache 广播用 AllGather

在自回归生成场景中,每层的 Self-Attention 需要访问历史所有 token 的 KV Cache。在 TP 模式下,KV Cache 分布在不同的卡上,需要通过 AllGather 将各卡的 KV Cache 片段聚合为完整视图。

# ops-transformer 中 KV Cache 的 AllGather 实现classKVCacheAllGatherer:def__init__(self,rank,world_size,n_heads,head_dim):self.rank=rank self.world_size=world_size self.n_heads=n_heads self.head_dim=head_dimdefgather_kv_cache(self,local_k,local_v):""" local_k: [seq_len, n_heads//world_size, head_dim] local_v: [seq_len, n_heads//world_size, head_dim] """# 初始化接收缓冲区full_k=torch.zeros(local_k.shape[0],self.n_heads,self.head_dim,dtype=local_k.dtype,device=local_k.device)full_v=torch.zeros_like(full_k)# 调用 hccl AllGatherhccl.allgather(local_k,full_k,local_k.numel(),hcclDataType_t.HCCL_FLOAT16,self.rank,self.world_size)hccl.allgather(local_v,full_v,local_v.numel(),hcclDataType_t.HCCL_FLOAT16,self.rank,self.world_size)returnfull_k,full_v

选型理由

  1. KV Cache 是只读数据(推理阶段不会更新),无需规约计算
  2. AllGather 的语义与 KV Cache 广播完全匹配
  3. 通过分块 AllGather 可以控制峰值显存

梯度同步用 AllReduce

在训练或微调场景中,需要同步各卡的梯度。这时必须使用 AllReduce,因为梯度需要在所有卡上保持一致。

// ops-transformer 中梯度同步的 AllReduce 调用voidsync_gradients(float*gradients,size_t grad_size,hcclComm_t comm){// 使用 Ring AllReduce 进行梯度同步hcclResult_t ret=hcclAllReduce(gradients,// 发送缓冲区gradients,// 接收缓冲区(原地操作)grad_size,// 梯度元素个数hcclDataType_t.HCCL_FLOAT32,hcclRedOp_t.HCCL_SUM,// 求和规约comm);if(ret!=hcclResult_t.HCCL_SUCCESS){printf("hcclAllReduce failed: %d\n",ret);return;}// 除以卡数,得到平均梯度floatscale=1.0f/hcclGetNumRanks(comm);scale_tensor<<<(grad_size+255)/256,256>>>(gradients,scale,grad_size);}

选型理由

  1. 梯度同步需要规约计算(求和或平均),AllReduce 的语义完全匹配
  2. Ring AllReduce 的 O(N) 复杂度在大规模集群中带宽利用率更高
  3. 原地(in-place)AllReduce 可以减少内存拷贝开销

性能对比

延迟对比

在 8 卡昇腾NPU 集群上,对不同消息大小测试 AllReduce 和 AllGather 的延迟:

消息大小AllReduce (Ring)AllReduce (Tree)AllGather
1 MB12 μs8 μs10 μs
16 MB45 μs52 μs38 μs
256 MB320 μs480 μs280 μs

观察

  1. 小消息(< 16 MB)时,Tree AllReduce 延迟更低
  2. 大消息(> 16 MB)时,Ring AllReduce 和 AllGather 延迟更低
  3. AllGather 的延迟通常低于 AllReduce,因为它无需规约计算

带宽利用率

带宽利用率定义为:有效数据量 / (通信时间 × 理论带宽)。

在 8 卡 HCCS 互联(单向带宽 64 GB/s)环境下测试:

# 使用 hccl-test 工具测试带宽hccl-test--opallreduce--datatypefp16--minbytes1024--maxbytes268435456--stepfactor2# 测试结果(部分)# Message Size: 16 MB# Ring AllReduce: 45.2 GB/s (70.6% 利用率)# Tree AllReduce: 38.7 GB/s (60.5% 利用率)# AllGather: 48.9 GB/s (76.4% 利用率)# Message Size: 256 MB# Ring AllReduce: 58.3 GB/s (91.1% 利用率)# Tree AllReduce: 41.2 GB/s (64.4% 利用率)# AllGather: 60.1 GB/s (93.9% 利用率)

观察

  1. 大消息下,Ring AllReduce 和 AllGather 的带宽利用率接近理论峰值
  2. Tree AllReduce 的带宽利用率受限于树形拓扑的拥塞
  3. AllGather 的带宽利用率略高于 AllReduce,因为通信模式更简单

卡数扩展性

测试不同卡数下,AllReduce 和 AllGather 的通信时间(消息大小 64 MB):

# 卡数扩展性测试脚本importhcclimporttorchimporttimedefbenchmark_allreduce(world_size,msg_size):tensor=torch.randn(msg_size,dtype=torch.float16,device='npu')hccl.init()start=time.time()hccl.allreduce(tensor,op=hccl.ReduceOp.SUM)hccl.synchronize()elapsed=time.time()-startreturnelapseddefbenchmark_allgather(world_size,msg_size):local_tensor=torch.randn(msg_size//world_size,dtype=torch.float16,device='npu')full_tensor=torch.zeros(msg_size,dtype=torch.float16,device='npu')hccl.init()start=time.time()hccl.allgather(local_tensor,full_tensor)hccl.synchronize()elapsed=time.time()-startreturnelapsed# 测试结果(秒)# World Size | AllReduce | AllGather# 2 | 0.008 | 0.006# 4 | 0.012 | 0.009# 8 | 0.018 | 0.014# 16 | 0.032 | 0.024# 32 | 0.058 | 0.042

观察

  1. 两种操作的通信时间都随卡数增加而增长,但 AllGather 的增长更慢
  2. 在 32 卡集群中,AllGather 的通信时间比 AllReduce 低 28%
  3. 大规模集群下,Ring AllReduce 的扩展性优于 Tree AllReduce

关键警告

警告 1:AllGather 的内存爆炸

在使用 AllGather 广播 KV Cache 时,如果序列长度很大(如 32K tokens),显存占用会急剧增长。假设头数 H=32,头维度 D=128,卡数 N=8,则单卡需要存储的 KV Cache 大小为:

Memory = 2 × N × S × H × D × sizeof(dtype) = 2 × 8 × 32768 × 32 × 128 × 2 bytes (FP16) ≈ 4.3 GB

这还不包括其他激活值和模型权重。在实际部署中,必须采用以下策略之一:

  1. 分块 AllGather:只拉取当前层需要的 KV Cache
  2. KV Cache 量化:使用 INT8 或 INT4 量化
  3. 稀疏注意力:只保留最近的 KV Cache
# 分块 AllGather 实现示例defchunked_allgather(tensor_chunks,chunk_size):""" tensor_chunks: List[Tensor], 每个元素是一块的本地数据 chunk_size: 每块的大小 """full_tensor=[]forchunk_idx,local_chunkinenumerate(tensor_chunks):# 只 AllGather 当前块chunk_buffer=torch.zeros(chunk_size*world_size,dtype=local_chunk.dtype,device=local_chunk.device)hccl.allgather(local_chunk,chunk_buffer)full_tensor.append(chunk_buffer)returntorch.cat(full_tensor,dim=0)

警告 2:AllReduce 的死锁风险

在使用 AllReduce 进行梯度同步时,如果不同卡上的调用顺序不一致,会导致死锁。例如,卡 0 先调用 AllReduce(A),再调用 AllReduce(B);而卡 1 先调用 AllReduce(B),再调用 AllReduce(A)。这时两个 AllReduce 操作会互相等待,导致死锁。

解决方案

  1. 统一的调用顺序:确保所有卡上的集合通信调用顺序完全一致
  2. 使用通信组:将相关的 AllReduce 操作放到同一个通信组中
  3. 异步通信:使用 hcclAllReduce 的异步版本,通过事件同步
// 错误的调用顺序(会导致死锁)// Rank 0:hcclAllReduce(A,...);// 先 AhcclAllReduce(B,...);// 后 B// Rank 1:hcclAllReduce(B,...);// 先 B -> 死锁!hcclAllReduce(A,...);// 后 A// 正确的做法:使用通信组hcclComm_t comm_ab;hcclCommCreate(&comm_ab,2,{0,1});// 创建只包含卡 0 和卡 1 的通信组// Rank 0 和 Rank 1 都执行:hcclAllReduce(A,...,comm_ab);// 在通信组 comm_ab 中同步 AhcclAllReduce(B,...,comm_ab);// 在通信组 comm_ab 中同步 B

结尾行动指引

本文深入剖析了 CANN ops-transformer 中 AllReduce 与 AllGather 的选型策略。理解这两种集合通信原语的原理和适用场景,对于优化分布式推理性能至关重要。

学习建议

  1. 深入学习 hccl 集合通信库的 API 和使用方法,掌握 Ring 和 Tree 算法的底层实现
  2. 阅读 ops-transformer 源码,理解其通信原语选型的具体实现
  3. 在实际项目中尝试不同的通信策略,通过 profiling 工具找到最优配置

参考资源

  • ops-transformer 开源仓库:https://atomgit.com/cann/ops-transformer
  • hccl 用户指南:昇腾社区文档中心
  • 分布式推理性能优化白皮书:昇腾技术社区

通过本文的学习,希望您能在大模型分布式推理部署中做出更优的通信原语选型,充分发挥昇腾NPU 的计算能力。


作者注:本文基于 ops-transformer v1.2 版本编写,示例代码仅供参考,实际使用时请根据具体版本调整 API 调用方式。

http://www.zskr.cn/news/1409106.html

相关文章:

  • 小米MiMo降价是要干嘛?
  • 极致沉浸感官体验,超元力重新定义VR枪战竞技新玩法
  • AI赋能医疗影像:重塑精准诊疗新范式
  • 酒店门锁V10SDK接口VB-幽冥大陆(一百26)—东方仙盟
  • 从Booth算法到硬件实现:八位补码乘法器的设计精要
  • 从单工到全双工:RS232/RS422/RS485通信模式与典型应用场景解析
  • 跨境电商运营效率提升方案星火跨境:XINGHUOS信息与工具聚合平台实测
  • Windows资源管理器终极改造:3个场景揭秘QTTabBar如何让文件管理效率翻倍
  • 为什么93%的人用错ChatGPT做时间管理?顶级效能教练拆解3个致命认知偏差及修正公式
  • 57.从AOSP源码出发,详解Android/iOS双平台刷机底层核心机制
  • 十层电路板打样,小批量生产怎么做才省钱?
  • Prometheus常用查询参数
  • 别再傻傻分不清!用OpenCV+Python实战搞懂单应矩阵、本质矩阵和基础矩阵
  • 非侵入式外设活动检测:基于总功耗侧信道分析与机器学习实践
  • 陌陌app unidbg 模拟算法分析
  • 开发AI聊天机器人时如何利用Taotoken实现模型的热切换与降级容灾
  • vs code 代码保存自动格式化
  • 爷青回!2024年用Win11和室友重温《龙之崛起》联机,保姆级教程+自建地图分享
  • PCA降维后数据还能‘还原’吗?用Python实战带你理解信息损失与数据重构(含误差分析)
  • 2026年知网新规下,论文AIGC率高怎么办?5款降AI工具实测指南 - 降AI实验室
  • 第 5 篇:Agent 记不住事?补上 Memory + RAG 检索
  • 2026年第二季度泰州五粮液回收平台深度解析:如何甄选专业、高效、保值的服务伙伴? - 2026年企业资讯
  • 合作的相邻系统
  • 华为云全栈:网络/存储/运维高能实战
  • 边缘智能与低功耗设计:可穿戴癫痫监测的数据选择算法解析
  • 对比直接使用官方API体验Taotoken在模型切换与路由上的便捷性
  • 嵌入式量子传感:18种机器学习模型在NV磁力计中的精度与效率权衡
  • 认证科普:阿里云云网络高级工程师ACP认证(附题库练习)
  • 从线性代数到代码:手撕多元正态分布采样,对比NumPy的multivariate_normal与手动Cholesky分解
  • 别再死记硬背L1、L2范数了!用Python可视化带你直观理解Lp范数家族