在跑大模型分布式训练的时候,我们经常挂在嘴边的一句话是“通信掩盖计算”。但真正做过大规模训练的人都知道,张量并行(Tensor Parallelism)出现以后,网络就不再是“掩盖”能解决的问题了——它直接决定了一个 step 能不能跑起来、跑多快。最近我在调研业界怎么处理张量并行场景下的通信开销,注意到一个很有意思的方向:把 All-Reduce 这类计算下放到网络交换机里去做,而且交换机还要“感知计算”,也就是计算感知的交换机内计算框架(CAIS,Compute-Aware Switch In-network Computing Framework)。这篇文章就来聊聊这个方向解决了什么问题、怎么落地、有哪些坑。
CAIS 这类框架适合两类人:一类是被多机多卡训练收敛速度折磨的算法工程团队,另一类是对数据中心网络基础设施、可编程交换机感兴趣的架构师。它解决的核心问题非常明确——张量并行里每一层都少不了的集体通信(Collective Communication)太慢了,慢到瓶颈不在 GPU、不在 PCIe,而在整张网络上。与其让几十张卡“跨交换机”反复搬运同一批数据,不如让交换机顺手把数据加一下再传下去。这个思路听着离奇,但已经有真实硬件和框架在做了。
1. 张量并行为什么又爱又恨:先算清楚通信账
1.1 一个看似简单的切分方案
张量并行的基本玩法是把一个矩阵运算拆到多张卡上。比如 Transformer 里的 MLP 层,第一层线性变换按“列并行”切,每个 GPU 只算一部分输出通道;第二层线性变换按“行并行”切,每个 GPU 只保留一部分权重行。这样可以明显降低单卡显存。但代价是:第一层算完,各卡上的部分结果必须拼成一个完整结果才能喂给第二层;第二层算完,又得把各卡部分结果加在一起才能得到最终输出。这个“拼起来”和“加起来”的动作,就是 All-Reduce。
Attention 部分也一样。Q、K、V 的投影经常采用列并行,输出投影是行并行,中间照样躲不过 All-Reduce。说白了,一个典型的 Transformer 层,在张量并行模式下,前向至少要做两次 All-Reduce,反向还得再来至少两次。这个通信不是偶尔来一次,是每个 micro-batch、每一层、每步梯度更新都在重复。
1.2 一张表看懂并行策略的通信成本
很多新手分不清三种并行策略的通信差异。列个实战对照表,一目了然:
| 并行方式 | 切分粒度 | 主要通信原语 | 通信频率 | 典型瓶颈 |
|---|---|---|---|---|
| 数据并行 | 按 batch 切 | All-Reduce(梯度) | 每步一次 | 梯度同步带宽 |
| 流水线并行 | 按层切 | Point-to-Point(激活/梯度) | 每 step 数次,但量较小 | 微批切换延迟 |
| 张量并行 | 按权重矩阵切 | All-Reduce(激活/梯度) | 每层多次 | 单次通信量大、次数极多 |
数据并行看起来也在做 All-Reduce,但它同步的是梯度,一个梯度张量通常和一个层激活差不多大,频率远低于张量并行。流水线并行虽然跨节点通信,但传的是边界激活,量小得多。张量并行是三种方式里对网络最不友好的,因为它在计算热路径上插了大量全量数据的交换动作。
1.3 一次前向到底搬多少数据
我按一个常见规模算笔账。假设隐藏维度是 4096,序列长度 2048,FP16 精度,那么一个 All-Reduce 操作涉及的数据量大约是:
M = 序列长度 × 隐藏维度 × 字节数 = 2048 × 4096 × 2 = 16MB
在 p 个参与卡中做一个 All-Reduce,每张卡至少要承担 2×(p−1)/p×M 的通信流量。以 8 张卡为例,每张卡一次 All-Reduce 大约要搬运 28MB。一个 Transformer 层前向两次,就是 56MB;如果是 40 层的模型,前向一共就是 2.24GB 左右,反向还要再来一份。这只是单卡视角,整个机器组实际经过网络的数据量会放大好几倍。
这个量级放到单机内部的 NVLink 上还能接受,但一旦跨节点,走的是 InfiniBand 或 RoCE(RDMA over Converged Ethernet),带宽虽然是 200Gbps、400Gbps 级别,但延迟和链路竞争完全不同。更麻烦的是,All-Reduce 这类同步操作需要一个“集体通信屏障”,慢的那一路会直接拖住所有参与者。网络只要抖动一次,性能会肉眼可见地掉一个档次。
2. CAIS 的核心思路:让交换机从“搬运工”变成“计算节点”
2.1 传统交换机为什么处理不了 All-Reduce
普通交换机的工作很简单:看包头的目的地址,然后查表转发。它不关心包里面是什么,更不会把不同端口发来的数据包做运算。于是,All-Reduce 得由端点(GPU 侧)来做。GPU 把自己的数据块发给其他节点,同时接收其他节点的数据块,边收边算。
这里面有个很别扭的地方:一个 All-Reduce 在普通网络里往往要经过“网卡出去→交换机转发→对端网卡接收→CPU/GPU 计算→再发出去”的完整回路。数据在通信链路上来回折腾,网络设备的计算能力完全被浪费了。而真正有价值的计算,比如求和、求最大值,本来是很简单的向量操作,却要占用 GPU 的算力和 PCIe 带宽。
2.2 交换机内计算:把 Reducer 下沉到转发路径上
交换机内计算(In-network Computing)的想法是把某些运算直接放到交换机内部的数据包处理流水线里。业内其实早就有先例,比如高端 InfiniBand 交换机上的 SHARP 技术,就是让交换机在中间节点做数据聚合,减少最终端点的通信量。
CAIS 这样的框架走得更远:它不仅要让交换机做聚合,还要让交换机理解“这次聚合是哪一次张量并行的哪一层、哪个数据块、有几个参与方、下一跳该发给谁”。我把这种能力理解成“计算感知”——交换机不再是一个盲目的转发管道,而是能感知当前计算语义的基础设施节点。
2.3 计算感知到底“感知”什么
这里要细说一下。普通的交换机聚合方案只需要知道“把这些包加起来”,但 CAIS 需要知道更多:
- 会话维度的感知:当前数据包属于哪个训练任务、哪个模型副本、哪个并行组,不能把不同训练任务的数据混在一起聚合。
- 张量切分的感知:模型层不同、切分维度不同,数据块在交换机里的排列组合方式也不同。交换机要能根据包里的元数据,把属于同一个逻辑张量的分片准确对齐。
- 优先级的感知:梯度聚合的 All-Reduce 通常对延迟更敏感,而一些后台的模型分发、日志传输对带宽更敏感。交换机如果能区分这些流量,就可以在争用发生时优先满足张量并行的通信。
- 故障状态的感知:某个参与卡掉线、某个聚合树分支失效时,交换机要能重新收敛聚合路径,而不是继续做无意义的累加。
光这一点,就让交换机从“傻快”变成“聪明快”。
2.4 节省的不只是带宽,还有延迟预算
数据经由交换机聚合到底省了什么?以最典型的树形(Tree)All-Reduce 为例。假设 8 张卡分布在两台机器上,上面挂着一台顶交换机(Spine/TOR)。传统方式下,8 张卡先分成两组各自归约,然后两组的 partial 结果要穿过上行链路到顶交换机,再到对方机器。期间上行链路要承受相当大的瞬时流量。
如果交换机本身能算,那两台机器之间的数据可以先在交换机里做聚合,再往更上层发或者直接广播给目标卡。实际效果是跨机链路上跑的数据量从“8 份”变小到“聚合后的 1 到 2 份”,而且端点侧需要等待的往返次数变少了。对于纯延迟敏感的小消息,甚至可以把两次跨机上链的通讯压缩成一次。用通俗点的话讲,以前一个快递要从 A 城各发货点分别运到 B 城再汇总,现在快递到了转运中心就直接合并成一个包裹再送下去。
3. 设计一个 CAIS 样式的计算感知交换机系统
3.1 总体架构:控制面、数据面与端点代理
要把 CAIS 落地,不能只在交换机里写死一个聚合算法,需要一台“能感知任务”的外部控制器和配套的端点接入层。我把它拆成三个角色:
- 控制平面(Controller):负责注册训练任务、维护并行组信息、分配聚合树、下发流表规则。它通常跑在独立服务器上,和分布式训练框架的通信库对等通信。
- 数据平面(Switch Data Plane):可编程交换机(比如 P4 可编程硬件)上实现的聚合处理流水线。它根据控制面下发的规则,识别匹配某个 All-Reduce 会话的数据包,在交换芯片内部完成累加、转发。
- 端点代理(Endpoint Agent):跑在 GPU 服务器上,向上对接训练框架(类似 NCCL 的通信 API),向下封装特殊协议头,让交换机可以快速识别数据包属于哪个聚合任务。
这样的三层结构在真实部署时很常见。关键是,端点代理要懂得把一次大 All-Reduce 切分成多个分块(Chunk)再发出去,既要匹配交换机的片上缓存容量,也要避免链路拥塞。
3.2 交换机数据面上的 All-Reduce 怎么跑
这里提供一个简化但可落地的实现路径。假设有 4 个 GPU rank 需要做 All-Reduce,采用树形聚合:
- 端点代理把本地张量切成 N 个 chunk。
- 每个 chunk 包加自定义头,包含会话 ID、Rank ID、Chunk ID、聚合树层数。
- 交换机把来自不同端口、具有相同(会话 ID、Chunk ID)的多个包识别为“同一批次聚合”,对数据负载做向量加法。
- 聚合结果(如果还有上一层)继续转发到上层交换机或根节点,如果到了最后一层则多播/单播回各 rank。
- 各 rank 拿到最终 partial 结果后,在本地完成收尾(比如和自己的本地副本相加)。
可以看到,普通网络里的“逐跳转发”,在这里变成了“边转发边算”。副作用是数据包不再是字节不变的,交换机必须有能力修改包体,同时更新校验和。这在老式交换芯片上做不到,但在可编程交换芯片上是可以编程实现的。
3.3 关键参数与计算逻辑:chunk 大小、超时、容错
我做过类似实验,几个参数必须重点把控:
- Chunk 大小:可编程交换机的片上缓存通常在几十 MB 量级(常见 8MB 到 22MB),而前面算过,一次 All-Reduce 数据量动辄 16MB 以上。必须把张量切成几百 KB 级别的 chunk,才能避免交换机缓存溢出。切小了会让包头占比上升、处理单元开销变大,切大了又容易撞缓存上限。我一般从 256KB 起步,压测到 1MB 之间调优。
- 聚合超时:交换机在等同一批次的其他分片时,会有一个超时机制。设得太短,网络乱序会导致频繁超时重传;设得太长,又成为新的延迟来源。基本思路是参考网络往返时延 RTT,把超时定在 1.5 倍 RTT 附近。
- 数据包大小和 MTU:大模型训练通信普遍使用巨型帧,交换机聚合时必须按固定粒度对齐负载。建议强制端点代理把 chunk 再均匀切成固定大小的 MTU 包,避免最后一个包尾部不足导致聚合错位。
- 优先级队列:给张量并行的 All-Reduce 流量分配高优先级队列,和普通 TCP/存储流量分开。我用的一招是把聚合会话的 DSCP 标记统一设成一个专用值,交换机的调度器看到这个标记就走严格优先队列。
3.4 和 NCCL 风格框架对接的思路
端点上最现实的问题是,训练框架用的是 NCCL、MPI 这类成熟通信库。CAIS 要想真正被用起来,不是让框架重写通信逻辑,而是做兼容替换。
实际操作中,比较稳的一种做法是仿照 NCCL 插件的思路,做一个 proxy 层。端点代理截获框架发起的 All-Reduce 调用,判断当前通信组是否在 CAIS 的聚合树覆盖范围内。如果在,就通过专用通道把数据送去聚合;如果不在,就直接落到传统的 NCCL 协议上。
这套“双模”方案的好处是安全:任何一次交换机聚合失败,都可以回退到传统路径,训练不会中断。在集群维护、升级可编程交换机规则时,也可以主动把流量切回 NC CL 直通模式,避免流量黑洞。
4. 落地会踩的坑:从硬件选型到问题定位
4.1 硬件选型:可编程交换机不是万能的
现在市面上能承载交换机内计算的主要是 P4/开放可编程交换机,还有部分高端 IB 交换机。做选型时要分清两种需求:如果只要链路聚合(All-Reduce、Reduce),IB 交换机的硬件加速方案最省心;如果要自定义计算语义、同时兼容 RoCE 网络,那就得选可编程芯片方案。
我个人踩过的坑是低估了“可编程”的代价。P4 交换机的头空间、表项深度、ALU 流水线资源都是有限的。你原本想写的“复杂感知逻辑”很可能一编译就是用掉了大半资源,导致转发吞吐上不去。第一次做实验时,想把会话表、聚合缓存、优先级调度全放在一级流水线上,结果编译后 line-rate 达不到,只有限速一半。后来把会话表挪到外部搜索模块,流水线里只放核心计算规则,性能才恢复。
4.2 跟 NCCL 抢设备资源会导致训练抖动
交换机内计算虽然把聚合放到网络侧,但端点代理依然要占用 GPU 显存和 CPU 资源。尤其是使用 NVIDIA 的通信库时,通信算子经常绑定到一个专用 GPU 流。如果多个作业共享同一台 GPU 服务器,而代理进程的显存分配又不做限制,就会出现显存不足或通信流排队。
建议给代理进程设置显存上限,同时把通信专用流的优先级调低。因为 IO 管线一旦抖动,最容易受影响的反而是小包延时敏感的张量并行通信。
4.3 调试观测:在交换机里看不见是最大的问题
传统网络出问题,可以在交换机上抓包、查看计数器。可一旦做了交换机内聚合,数据包的内容会在中间节点被修改,常规抓包没有办法还原聚合是否正确。我在联调时吃过很大亏:训练跑着跑着 loss 出现 NaN,怀疑交换机聚合错位,但就是差不到证据。
后来总结出一套观测方法:
- 在端点代理里记录每个 chunk 的摘要哈希(比如 XXHash),发给控制器。
- 控制器把同一会话的摘要哈希汇总,和交换机上报的聚合结果比对。
- 任何不一致立刻定位到具体交换机、具体端口、具体 chunk。
成本不高,但排查问题的效率大幅提升。强烈建议上交换机内计算方案时,把数据面审计做成默认开启。
4.4 常见问题速查表
| 现象 | 可能原因 | 排查步骤 |
|---|---|---|
| 训练速度反而变慢 | chunk 太小,包头开销大;或聚合树层级过深 | 调大 chunk,简化聚合树,检查交换机流水线吞吐 |
| 偶发丢包导致卡住 | 聚合超时设置过短,或交换机队列溢出 | 增大超时倍数,缩小 chunk 以降低缓存压力 |
| 不同任务结果串扰 | 会话 ID 冲突或表项陈旧 | 检查控制面的会话分配与回收逻辑,强制清理旧规则 |
| loss 出现 NaN/异常 | 聚合分组错位、校验和失效 | 开启摘要哈希审计,比对端点和交换机结果 |
| 端口线速只有一半 | 流水线资源使用过度、匹配范围太宽 | 简化 P4 规则,把低频查询外置,复查 slice 分配 |
4.5 一个小经验:优先保证聚合树是“等分”的
在设计聚合树时,最容易忽略的是叶子节点的数据量要均衡。交换机内的累加操作本质是“多进一出”,如果某个分支送来的是一个过早聚合的大块数据,而另一个分支还是碎包,交换机内部的队列很容易在合并点打结。我后来直接把数据量均衡纳入聚合树生成阶段,由控制器按参与 rank 的实时状态动态调整树形,而不是固定一棵静态树。效果很明显,P95 尾延迟降低了约两成。
CAIS 这个方向能不能大规模取代传统 NCCL,还取决于可编程交换机的成本、运维复杂度以及生态兼容性。但至少在当前这个大模型集群规模暴涨、网络带宽捉襟见肘的时期,让交换机在转发同时承担一部分计算负担,确实是打破通信墙的一个可行思路。我个人在实际操作中的体会是:不要一上来就想着全量替换通信库,先拿一个非关键训练任务、一个并行组做灰度验证,把 chunk 参数、树形、优先级策略调明白,再逐步扩大范围。最终你会发现,真正的收益不只是单次 All-Reduce 变快,而是整张网在应对大规模张量并行时,有了更从容的流量调度余地。