百万卡架构解析:Nested BSP与Unified Bus如何实现超节点计算

📅 发布时间:2026/9/25 2:38:29
百万卡架构解析:Nested BSP与Unified Bus如何实现超节点计算
1. 从「百万卡」这个数字说起为什么规模本身就是一道架构题第一次看到「百万卡还算一台计算机」这个说法我的反应是这不就是堆机器吗但仔细想一下如果只是把一百万张加速卡插上电、连上网它大概率跑不了任何一个像样的训练任务。原因很简单——规模到了一定程度通信开销会吃掉一切。我做过一个粗略的估算。假设一个千亿参数级别的模型做一次全参梯度同步每张卡需要交换的梯度量在几十到上百MB量级。如果卡间走的是传统的层次化网络跨节点带宽按200Gbps算一百万张卡意味着任意一次全局同步要经过的跳数和汇聚层级会非常深。哪怕单跳延迟只有几微秒累积起来也是秒级的等待。而一次训练迭代的计算时间可能也就几百毫秒。通信占比一旦超过30%整个集群的算力利用率就会断崖式下跌。所以「百万卡是一台计算机」这句话本质上不是在描述硬件数量而是在描述一种编程模型和系统抽象让开发者写代码的时候不需要关心自己面对的是100万张卡还是8张卡就像写单机多线程程序不需要关心CPU有几个核心一样。这个抽象要成立必须同时解决三件事——计算怎么组织、通信怎么走、内存怎么统一编址。华为廖恒那篇论文的核心贡献恰好就是在这三个维度上各给了一套方案Nested BSP 解决计算组织Unified Bus 解决通信与内存而把冯·诺依曼结构往外扩解决的是「这套东西凭什么还能叫计算机」的理论合法性问题。下面我按自己的理解把这三块拆开讲。2. Nested BSP把「超步」这个概念嵌套起来用2.1 先搞清楚 BSP 到底约束了什么BSPBulk Synchronous Parallel这个模型不新上世纪90年代就有人提。它的核心是把并行计算切成一个个「超步」superstep每个超步内部包含三个阶段本地计算、全局通信、栅栏同步。所有参与的计算单元在每个超步结束时必须对齐然后才能进入下一个超步。这个模型的优点是编程简单——你不需要处理异步通信带来的竞态和死锁逻辑是确定性的。缺点也很明显栅栏同步是全局的规模一大最慢的那个节点会拖住所有人这就是所谓的「长尾效应」。传统 BSP 在几千卡的规模上还能用到了百万卡级别全局栅栏同步的代价会变得不可接受。因为一次全局同步意味着所有卡都要停下来等而百万卡里出现慢节点的概率几乎是100%。2.2 Nested BSP 的嵌套思路Nested BSP 的关键洞察是不是所有通信都需要全局同步。在典型的深度学习训练里梯度同步确实需要全局一致但很多中间计算、局部聚合、参数切分是可以分层处理的。它的做法是把计算单元组织成多层结构。最底层是一组紧耦合的卡比如通过 Unified Bus 直连的若干加速器它们之间做高频、低延迟的同步上一层是若干个这样的组组间做较低频的同步再往上继续嵌套。每一层都有自己的超步节奏层内的栅栏同步和层间的栅栏同步是解耦的。打个比方一个公司有一万个员工。如果每做一件事都要全员开会对齐那什么都干不成。实际的做法是小组内部快速对齐组长之间再对齐部门之间再对齐。Nested BSP 就是这个逻辑——把全局栅栏拆成多层局部栅栏让同步的代价和通信的局部性匹配起来。这里有个容易被忽略的细节嵌套层数不是越多越好。层数多了跨层同步的协调开销会上升层数少了又退化成全局同步。论文里给出的经验是嵌套层数应该和物理拓扑的层次对齐比如「卡内-板内-机柜内-机柜间」这种自然分层。这一点我在实际调优时深有体会——软件的分层如果和硬件的分层不一致性能会莫名其妙地差因为跨层通信会走到慢路径上。2.3 嵌套 BSP 对编程模型的影响对开发者来说Nested BSP 带来的最大变化是你写代码时需要显式地声明「这个通信的范围是哪一层」。这听起来是负担但其实是好事。因为一旦你声明了范围编译器/运行时就能把通信调度到对应的物理路径上避免不必要的全局流量。我见过不少团队在迁移到大规模集群时代码里全是全局 all-reduce结果跑到几千卡就跑不动了。改成分层 all-reduce 之后同样的硬件吞吐能提升百分之几十。Nested BSP 本质上就是把这种「分层通信」的最佳实践固化成了编程模型。注意嵌套 BSP 并不意味着你可以随便写异步代码。层内的同步仍然是强一致的只是同步的范围变小了。如果你在层内引入了异步那层间的栅栏就失去了意义。3. Unified Bus把内存和通信揉成一层3.1 传统互联为什么在百万卡级别失效传统的大规模集群互联是分层的卡内走 PCIe 或 NVLink节点间走 InfiniBand 或 RoCE机柜间走骨干网。每一层协议不同、地址空间不同、编程接口不同。开发者要处理「本地内存」和「远端内存」的区别要手动做 RDMA、要管理通信缓冲区。这套东西在几千卡时还能忍到了百万卡问题就放大了地址空间不统一意味着每一次跨节点访问都要经过一次「翻译」而翻译的代价随规模增长。更麻烦的是不同层级的带宽和延迟差异巨大调度器很难做出全局最优的决策。3.2 Unified Bus 的核心统一编址 统一语义Unified Bus 的思路是把整个集群的内存做成一个统一的地址空间所有加速器通过一条逻辑上的「总线」访问彼此的内存访问语义和访问本地内存一致。这听起来像是把 NUMA 做到了极致——不只是 CPU 和内存之间是 NUMA卡和卡之间也是 NUMA而且是对程序员透明的 NUMA。实现上它需要几个支撑全局地址映射每个物理内存页在整个集群里有唯一地址硬件负责路由。缓存一致性协议跨卡访问时缓存行的一致性由硬件维护不需要软件干预。带宽分层感知的调度虽然地址统一了但物理上还是有远近之分。运行时需要知道「访问这张卡的内存比访问那张卡快」并据此做数据放置。第三点特别关键。统一地址空间不等于统一性能。如果运行时不知道物理拓扑把所有内存访问都当成等价的那性能会比分层方案还差。所以 Unified Bus 实际上是一套「统一语义 分层物理」的组合语义上让你觉得是一台机器物理上仍然利用局部性。3.3 对昇腾生态的意义放到昇腾的语境里看Unified Bus 是让昇腾集群从「一堆加速卡」变成「一台超节点」的关键。昇腾本身有 HCCS 这样的高速互联Unified Bus 在它之上做了更高层的抽象。对于用 MindSpore 或 PyTorch 做训练的开发者来说最直接的好处是你不需要再手写通信原语了。张量的切分、聚合、广播运行时可以根据 Unified Bus 的拓扑自动选择路径。我实测过一个场景同样的模型在统一编址的集群上代码里去掉所有显式的通信调用只保留数据并行和模型并行的逻辑声明性能反而比手写通信更好。原因是运行时的调度能看到全局拓扑而人写的通信往往是局部的、保守的。4. 把冯·诺依曼往外扩为什么这仍然是一台「计算机」4.1 冯·诺依曼结构的本质是什么很多人把冯·诺依曼结构理解成「CPU 内存 总线」的物理形态这其实是误解。冯·诺依曼结构的本质是一种计算模型程序和数据都存在同一可寻址的存储器里处理器按顺序取指、译码、执行通过读写存储器与外界交互。这个模型的关键特征是存储程序、统一编址、顺序执行语义。只要满足这三条不管物理上是一块芯片还是一百万块芯片都可以被认为是冯·诺依曼结构的某种扩展。4.2 百万卡系统在哪些地方「超出了」经典冯·诺依曼经典冯·诺依曼假设存储器的访问延迟是均匀的或者至少差异不大。但百万卡系统里访问本地内存和访问远端内存的延迟可能差几个数量级。这是第一个超出点。第二个超出点是并行性。经典模型是单处理器的顺序执行而百万卡系统天然是海量并行的。虽然可以用 BSP 这样的模型来组织但并行语义和顺序语义在编程上是有本质区别的。第三个超出点是同步。经典模型里处理器和存储器之间的同步是隐式的、由硬件保证的。百万卡系统里同步是显式的、需要软件参与的。4.3 扩展后的冯·诺依曼统一地址 分层延迟 显式同步廖恒论文里的处理方式是保留冯·诺依曼的核心抽象统一编址、存储程序但把「均匀延迟」这个假设替换成「分层延迟」把「隐式同步」替换成「显式但可嵌套的同步」。这样得到的模型既保留了「一台计算机」的编程直觉又容纳了百万卡的物理现实。我觉得这个思路最妙的地方在于它没有试图消灭分层而是把分层「内化」成了模型的一部分。就像现代 CPU 有 L1/L2/L3 缓存和 NUMA程序员虽然知道有分层但写代码时仍然可以把它当成一台机器——只要性能调优时能感知到分层就行。百万卡系统也是这个逻辑默认视图是一台机器性能调优时可以下钻到分层细节。5. 这三块拼在一起实际开发时要注意什么5.1 编程模型的选择什么时候用嵌套什么时候用扁平如果你的任务通信模式是全局密集的比如全参梯度同步Nested BSP 的分层同步能帮上大忙。但如果你的任务本身通信就很少比如纯推理、或者参数服务器式的稀疏更新嵌套带来的协调开销可能反而得不偿失。我的经验是先看通信的「直径」。如果任意两个计算单元之间都需要通信那分层是必须的如果通信主要发生在局部那扁平结构可能更简单。5.2 内存放置统一地址不等于随便放Unified Bus 给了你统一地址空间但不代表你可以把数据随便放。物理局部性仍然存在数据放置策略直接影响性能。常见的做法是热数据放在访问最频繁的那一层冷数据可以放远一点。这需要你对任务的访问模式有清晰的认知。5.3 同步粒度栅栏不是越少越好Nested BSP 减少了全局栅栏但不代表栅栏越少越好。栅栏太少层间的数据一致性可能出问题栅栏太多又退化成全局同步。论文里建议的「和物理拓扑对齐」是一个很好的起点但实际调优时还需要根据任务的通信模式微调。6. 一个具体的调优案例从全局 all-reduce 到嵌套 all-reduce我拿一个中等规模的模型做过对比实验。基线是全局 all-reduce所有卡的梯度直接做全局聚合。改成嵌套之后先在卡组内做 all-reduce再在组间做 all-reduce最后做一次全局的 reduce-scatter。结果是在同样的硬件上嵌套版本的通信时间下降了约40%整体迭代时间下降了约15%。原因不复杂——全局 all-reduce 的通信量和卡数成正比而嵌套版本的通信量主要发生在局部跨组通信的数据量被压缩了。这个案例说明Nested BSP 不是一个理论概念而是有实际性能收益的工程方案。当然前提是你的物理拓扑支持分层通信如果所有卡都在同一个交换机下嵌套的意义就不大了。7. 我对这套架构的一点个人判断从工程角度看Nested BSP Unified Bus 扩展冯·诺依曼这套组合解决的是「规模上去了编程模型没跟上」的问题。它的价值不在于发明了某个全新的技术而在于把已有的技术BSP、NUMA、统一编址在百万卡这个尺度上重新组合并给出了理论上的合法性论证。我个人觉得最有启发的是「扩展冯·诺依曼」这个视角。很多时候我们做系统设计会不自觉地被经典模型的假设束缚住觉得「计算机就应该是均匀内存访问的」。但实际上只要保留核心抽象假设是可以替换的。统一编址 分层延迟 显式同步这个组合可能不只适用于百万卡也适用于其他大规模分布式系统。最后分享一个小技巧如果你在调优大规模集群时遇到性能瓶颈先别急着改代码先画一张物理拓扑图再画一张通信模式图把两张图叠在一起看。大部分性能问题都是因为通信模式和物理拓扑不匹配。Nested BSP 和 Unified Bus 本质上都是在帮你做这个匹配但前提是你自己得先看清楚这两张图。