大模型推理瓶颈:RDU数据流架构如何用编译器减少数据搬运
发布时间:2026/10/26 6:09:04 锦皓数字建站

大模型推理做到一定规模你会撞上一个有点反常识的现象加卡不一定变快扩显存反而更省钱。卡在 128K 上下文、低并发、专家混合结构这类场景里瓶颈往往不在峰值算力而在权重和 KV cache 的搬运量上。SambaNova 的 RDUReconfigurable Dataflow Unit走的就是另一条路——它不追求做一个什么都能算的通用加速器而是把整张计算图交给编译器让硬件在运行前就被编排成专用流水线。这篇文章拆的是它的架构逻辑和高性能来源数据流执行省掉了哪些开销、SRAM/HBM/DDR 三级内存怎么分工、可重构阵列在编译器眼里长什么样、以及这套设计在真实部署里会付出什么代价。适合正在做推理集群选型、想搞清楚非 GPU 路线边界在哪的工程师也适合对编译器驱动硬件感兴趣、想理解空间计算架构的读者。我不会只讲好话编译时间、动态形状、算子覆盖这些坑一样要摊开说因为能不能用好这类芯片八成取决于你愿不愿意接受它的约束。1. 这块芯片解决的不是算得快而是搬得少1.1 一个被内存带宽卡住的推理现场先算一笔最朴素的账。一个 70B 参数的稠密模型用 bf16 存权重就是 140GB。单卡 HBM 容量常见的量级是 64GB 到 80GB这意味着模型天生就要切开放在多张卡上而每切一刀层与层之间就要过一次互连。更麻烦的是解码阶段每生成一个 token都要把参与计算的权重完整读一遍。此时算术强度大概等于 batch size 的量级——batch 为 1 的时候你搬了 140GB 的数据只换来大约 140 GFLOP 的计算量加速器绝大部分算力都在等数据。这就是为什么很多团队在压测时会发现把并发从 1 提到 8吞吐几乎线性涨提到 32 之后曲线开始变平再往上加涨的是显存占用和尾延迟不是吞吐。硬件的机器平衡点峰值算力除以内存带宽通常在一两百 FLOP/Byte 这个区间而低并发解码的算术强度只有个位数两者差了一到两个数量级。按这个思路往下推优化方向其实只有三条把 batch 做大、把数据搬得更少、把容量做大以减少切分。前两条是所有加速器都在做的事第三条是 SambaNova 这套架构最鲜明的取向——宁可牺牲一点峰值算力密度也要让一台机器装得下更大的模型、更长的上下文。1.2 数据流执行到底省掉了哪几层开销通用处理器和 GPU 的执行模型本质是指令流取指、译码、读寄存器、计算、写回每个操作都要走一遍这套流程中间结果先进寄存器堆或共享内存再由下一条指令读出来。为了掩盖访存延迟还要堆多级缓存、做乱序、做大量线程切换。这套机制的好处是通用代价是每一层抽象都要吃功耗和面积。数据流模型换了个前提既然计算图的依赖关系在编译期就完全已知那就不必在运行时再动态决定下一步算什么。编译器把每个算子映射成一组固定的计算单元和片上缓冲区数据从上个单元的输出端口直接流进下个单元的输入端口中间不落 HBM、不进 cache、不需要指令发射。省掉的东西可以列成一张清单取指译码的功耗、cache tag 比对和 miss 的不确定性、寄存器堆的反复读写、kernel launch 的调度间隙、以及最关键的——算子之间来回搬 tensor 的带宽消耗。这里要说清楚一个容易混淆的点数据流并不等于没有控制。循环、条件分支在编译器里照样存在只是被展开、折叠或者转成了计数器和谓词。真正的差别在于控制逻辑在编译期就被固化进配置而不是在运行时靠指令流逐条驱动。这带来的直接收益是延迟可预测——没有 cache miss 抖动没有调度器抢占同一张图跑一万次的耗时应高度一致。对在线服务来说尾延迟的稳定性有时候比平均吞吐更值钱。代价也很直接硬件形态在加载配置的那一刻就固定了图变了就得重新配置。这就注定了它对动态形状、动态控制流的容忍度远低于 GPU后面第 4 章会专门讲这个坑。1.3 粗粒度可重构阵列把算子焊成电路RDU 的底层是一种粗粒度可重构阵列结构。所谓粗粒度是相对于 FPGA 那种细粒度门级可编程而言它的可重构单元不是查找表和触发器而是整块的乘加阵列、向量运算单元和小容量便签存储器scratchpad。阵列之间靠一张可配置的片上网络连接连接拓扑、方向、位宽都能按配置切换。这种设计处在 ASIC 和 FPGA 之间。ASIC 最快最省电但一个算子流片一次改不动FPGA 最灵活但门级布线的面积和功耗开销巨大跑矩阵乘这种规整计算非常不划算。粗粒度阵列找的是中间点粒度足够粗乘法器和 SRAM 都是硬的效率接近 ASIC连接足够可配不同模型的算子组合能在同一块硅片上换着跑。从编译器的视角看这块阵列更像一张可编辑的数据流图每个乘加块是节点每条片上通路是边配置的过程就是给节点分配功能、给边分配带宽和缓冲深度。实际做映射的时候编译器要解的是一道带约束的装箱问题——算子怎么切分才能填满阵列、片上缓冲够不够放下一个 tile、相邻算子能不能对同一个数据块做流水而不重读。这些决策在 GPU 上由程序员通过 tile size、shared memory 用量手动调在这里被编译器接管了一大半。我个人的判断是这种架构的胜负手不在硬件本身而在编译器成熟度。阵列再漂亮编译器映射不出高效流水线实测性能就是纸面数字的零头。这也是评估这类平台时最该做的一件事拿你自己的模型而不是官方 demo去跑端到端。2. RDU 内部怎么排布算力、片上存储与三级内存2.1 计算阵列的组织与数据复用RDU 的计算资源由若干计算簇组成簇内是向量化的乘加单元簇间通过片上网络互连。和 GPU 的 SM 架构相比最明显的差别是片上存储的占比。在 GPU 里一个 SM 的 shared memory 通常是百 KB 量级靠大量常驻线程来掩盖访存延迟RDU 走的是另一条路——给每个计算单元配更大的本地便签存储让数据在片上停留更久用空间换时间而不是用并发换时间。这个取向对矩阵乘的影响很实在。做 GEMM 的时候经典优化是把输出分块让 A 的一块和 B 的一块在片上反复复用减少对下层存储的访问次数。片上 SRAM 越大能留住的块越大复用次数越多理论带宽需求越低。SambaNova 公开资料里反复强调的一点就是memory-rich——按公开报道的量级几代 RDU 的片上 SRAM 从数百 MB 起步这个数字比同期通用 GPU 的片上存储高出一个量级以上。但要提醒一句片上 SRAM 大不等于有效带宽高。真正决定性能的是编译器能不能把数据流排成读一次、用多次的形态。如果图切得太碎每个算子都独立读写一遍片上缓冲大 SRAM 就退化成了一个昂贵的中间仓库收益立刻归零。评估的时候可以看一个指标单位 token 实际产生的片上读写次数和理论最小值差多少。2.2 SRAM / HBM / DDR 三级结构的分工这是整套架构里我觉得最值得琢磨的部分。三代 RDU 的内存层次大致可以概括成三级层级典型量级放什么关键指标片上 SRAM数百 MB当前 tile 的输入输出、融合算子的中间结果带宽最高、容量最小、无访问延迟抖动HBM数十 GB激活值、KV cache、热点权重带宽高、容量中等DDR每节点 TB 级冷权重、长上下文 KV 的溢出部分、多模型共存容量极大、带宽低一个数量级这个分层的动机很明确大模型推理是容量敏感 带宽敏感的双重负载。权重 140GB 放不进 HBM但每生成一个 token 又要把用到的权重过一遍。分层之后容量问题被 DDR 兜住带宽问题靠不是所有层每一刻都在被访问来缓解——MoE 路由、分层预取、权重常驻热度分级都能让实际访问集中在 HBM 和 SRAM 上。KV cache 的账更能说明问题。以常见的 70B GQA 模型为例80 层、8 个 KV 头、head dim 128每个 token 的 KV 占用是 80 × 8 × 128 × 2K 和 V× 2 字节约 320KB。8K 上下文就是 2.6GB32K 上下文是 10.5GB128K 上下文直接干到 42GB。一个 32 并发的 32K 上下文服务光 KV cache 就要 336GB。这个量级下如果只能放 HBM节点数量会被容量而不是算力决定。提示评估这类分层内存架构时别只看总容量要问清楚每一层的带宽和谁能决定数据放哪一层。如果放置策略对用户不透明、也不能干预那大容量很可能只在特定模型上兑现。2.3 卡间互连与整机形态单卡放不下就横向扩这是所有路线的共同选择差别在扩展的粒度。RDU 的整机思路是把多颗芯片通过交换结构连成一个更大的逻辑单元对外呈现为一个大内存池模型并行在机内完成跨机再用常规网络做流水或数据并行。这个设计的意图是把张量并行这种通信最密集的切分方式关在机箱里跨机只走通信量小的路径。工程上最需要关注三件事。第一是机内互连的实际带宽和收敛比标称数字和满配下的可用带宽经常不是一回事。第二是内存池化后的访问语义——是统一地址空间还是显式拷贝前者写代码舒服但容易踩伪共享后者可控但要手工管理。第三是故障域机内耦合越紧单点故障影响面越大做高可用方案时要按机而不是按卡来设计冗余。按我的经验这类紧耦合整机在单模型超大、并发不高的场景里优势明显在很多小模型、请求碎片化的场景里反而不好摆布因为大内存池的调度粒度较粗容易一边闲置一边排队。3. 高性能的真正来源四个杠杆的定量拆解3.1 杠杆一全图融合把中间结果留在片上传统框架执行模型一个 Transformer 层会被拆成几十个 kernelLayerNorm、QKV 投影、RoPE、注意力打分、softmax、输出投影、残差、FFN 两层……每个 kernel 都要把结果写回 HBM下个 kernel 再读出来。对 70B 模型来说这些中间 tensor 的单次读写量并不夸张但层数乘上去就非常可观。全图编译的价值就在于把这些边界抹掉。编译器看到的是完整的依赖图可以把一连串逐元素算子融合成一段流水中间结果只在片上缓冲里转一圈不落外部存储。省下来的带宽直接转化为吞吐——在解码这种受带宽限制的阶段带宽省 30%吞吐大致就涨 30%。但融合不是无条件越多越好。融合的边界受片上缓冲容量约束一个 tile 放不下就得回退到分层存储反而多了一次无谓的搬运。所以编译器需要做代价模型驱动的切分决策而这一步的准确性恰恰是不同编译器水平拉开差距的地方。我见过的情况是同一个模型在不同编译配置下端到端差距能到 40% 以上而差异主要来自融合粒度和 tile 大小。3.2 杠杆二大容量内存撑住低 batch 长上下文这一条是这套架构最独特的价值也最容易被误读。大内存不等于高吞吐它换来的是低并发下的可行性。设想一个企业内部的知识库问答QPS 只有个位数但上下文动辄 32K还要求首 token 延迟可控。这种负载的特点是并发低、单请求重、对延迟敏感。用通用 GPU 集群做你要么把上下文截断要么承担巨大的 KV cache 复制和调度开销要么把模型切到很多卡上、用通信换容量。而在大内存节点上模型和 KV 都能装下不需要切分通信开销趋近于零batch 可以开得很小。代价在带宽。DDR 的带宽比 HBM 低一个数量级如果热点权重没有落在 HBM 上每 token 的解码时间会被 DDR 带宽直接卡死。一个粗略的估算方法假设某层权重 10GB 且全部落在 DDRDDR 有效带宽 300GB/s那么这一层每 token 至少要 33ms。这个数字足以让任何在线服务不可接受。所以真正决定体验的是权重热度分级做得好不好而不是总容量有多大。3.3 杠杆三MoE 与稀疏结构下的性价比专家混合模型天然契合一件事参数量很大但每次前向只激活其中一小部分。对稠密架构来说参数量就是访存量对 MoE 来说参数量是容量需求访存量由激活专家数决定。这两件事被拆开之后大容量 中等带宽的硬件就有了用武之地。举个量化的例子。一个总参数 100B、每 token 激活 10B 的 MoE 模型权重总量约 200GBbf16单 token 实际读取约 20GB。如果全部权重常驻在容量足够的节点上解码时只需读取被路由到的那部分加上路由分发本身的开销。相比把所有专家都摊在多卡上、每个 token 都触发一次跨卡 all-to-all这种专家就在本地内存里的布局省掉了最贵的那段通信。现实里的坑也不少。路由不均衡时热门专家会被反复读取冷门专家常驻却从不用实际带宽消耗可能远高于理论值batch 很小时路由的负载均衡更差某些专家的访存会形成尖峰。做容量规划时我通常按最热门的 20% 专家贡献 80% 的访存来估而不是用平均值。3.4 杠杆四确定性调度带来的延迟可预测性这一条在技术分享里常被忽略但在生产环境里价值很高。GPU 上的延迟抖动来源很多kernel launch 排队、cache 命中率波动、多租户共享时的抢占、动态电压频率调整。你很难给出一个严格的 P99 承诺只能靠压测经验加冗余。数据流架构因为没有 cache、没有运行时调度决策同一张图、同一形状、同一 batch执行时间几乎是常数。这让你可以按最坏情况来做容量规划而不是按平均情况加安全系数。对一个需要 SLA 的推理服务来说冗余度可以从 2 倍降到 1.3 倍这部分省下来的机器成本相当可观。前提是形状固定、输入分布稳定。一旦请求的序列长度在很大区间内波动每次换形状都可能触发重新配置确定性就断了。这也是为什么实际部署里几乎都会做形状分桶。4. 编译器驱动的代价形状、编译时间与算子覆盖4.1 动态形状为什么是硬伤GPU 上换个序列长度无非是 kernel 里的循环次数变了顶多多几次分支判断。数据流架构上形状决定了 tiling 方案、片上缓冲的分配、流水线的级数甚至决定了阵列怎么布。形状一变这些全部要重算重算的产物是一份新的硬件配置。这带来两个直接后果。第一编译产物是形状特化的你不能指望编译一次到处跑。第二编译本身有成本整图编译从几分钟到几十分钟都属正常模型大的时候更久。如果服务在运行时才发现遇到了没编译过的形状用户就要等一次编译——这在在线场景里是不可接受的。业界的通行解法是形状分桶把连续的形状离散成若干个桶比如 128、256、512、1024、2048、4096、8192、16384、32768请求进来先归到最近的桶不足的部分补 padding。桶越多浪费越少编译产物越多冷启动越慢。桶的密度怎么选取决于你的流量分布——如果 90% 的请求都在 1K 以下那 32K 以上的桶可以稀疏一些甚至只留一个兜底。padding 的浪费也要算。一个 1100 token 的请求归到 2048 桶计算量翻了近一倍。分桶设计得不好纸面吞吐会凭空蒸发三成。我的建议是按真实流量分布做直方图用累计覆盖率来定桶边界而不是凭感觉取 2 的幂。4.2 编译缓存与形状分桶的工程做法分桶的落地关键在预编译和缓存共享两件事上。预编译要把线上可能出现的形状在 CI 阶段全部编好产物存进版本化的缓存服务启动时直接加载而不是现场编译。缓存要按模型版本、编译器版本、形状三元组做键任一变化都要失效重建。下面这段是用来做容量和桶规划的估算脚本思路是把参数、KV 和冗余一并算进去我一般拿它做第一轮筛选def kv_bytes_per_token(n_layers, n_kv_heads, head_dim, dtype_bytes2): # K 和 V 各一份 return n_layers * n_kv_heads * head_dim * 2 * dtype_bytes def plan(n_params_b, n_layers, n_kv_heads, head_dim, ctx_len, concurrency, weight_bytes2, safety1.2): weights n_params_b * 1e9 * weight_bytes kv kv_bytes_per_token(n_layers, n_kv_heads, head_dim) \ * ctx_len * concurrency total (weights kv) * safety return { weights_GB: round(weights / 1e9, 1), kv_GB: round(kv / 1e9, 1), total_GB: round(total / 1e9, 1), fits_hbm_only: total 64e9, } print(plan(70, 80, 8, 128, ctx_len32768, concurrency8))跑出来你会发现70B 模型加 8 路 32K 上下文的 KV总需求接近 190GB早就越过了单卡 HBM 的边界。这个数字直接告诉你为什么需要分层内存也告诉你哪些权重必须常驻高速层。4.3 算子回退与数值精度全图编译最怕的不是性能差而是悄悄回退。某些算子编译器不认识就退回主机侧执行一次回退可能把整条流水线打断H2D/D2H 拷贝加上主机计算的延迟足够让吞吐掉一个数量级。而且这种回退往往不报错只在性能剖析里露出一小段异常耗时。我的做法是上线前必做两件事。一是把编译日志里的未支持算子清单拉出来逐条确认能改写成等价组合的就改写不能改的就评估回退比例超过 5% 直接判定不可用。二是做数值对齐测试用同一批输入分别跑参考实现和目标平台逐层比对的相对误差特别注意 softmax、LayerNorm、attention 累加这些对精度敏感的位置。精度上还有一个细节累加器位宽。矩阵乘用 bf16 输入、fp32 累加是常规做法但有些平台为了省面积会降累加精度短序列看不出问题长序列的误差会累积到肉眼可见。压测时一定要把上下文拉到目标长度再比别用 128 token 的短样本下结论。5. 落地部署的实操链路5.1 上车前的容量与带宽估算评估任何新硬件我都会先做一遍纸面三算算不过就不用浪费时间去搭环境了。第一算是容量权重 KV 框架开销再乘 1.2 的安全系数看能不能装进单节点的内存池。装不下就要考虑切分一切分就要引入通信通信一引入前面省下的带宽优势就开始打折。第二算是带宽按目标 batch 和上下文长度估出每 token 需要搬运的字节数除以对应存储层的有效带宽得到理论单 token 延迟。这个数字如果已经超过你的 SLA 要求那无论怎么优化都没戏。要特别注意用有效带宽而不是标称带宽实测经验是标称值的 60% 到 75% 比较现实。第三算是形状分布把线上请求的长度做成直方图看有多少比例落在桶内、多少要靠 padding。padding 比例超过 30% 就要重新设计桶边界。三算都过了再谈搭环境。顺序反了的话很容易在一个根本不适合的场景里折腾两周。5.2 模型改写的检查清单从通用框架迁到编译型平台模型侧要动的通常是这几处我按影响从大到小排控制流外提模型里的 if/else、动态循环、变长循环尽量提到图外由主机侧决定图内只保留规整的张量运算。算子等价替换不支持的算子换成支持的组合比如某些特殊归一化用基础算子拼出来注意数值等价性要验证。数据布局统一卷积类模型要确认 NHWC / NCHW 的偏好避免图里插入大量转置。常量折叠能提前算的都在图外算好减少图内节点数量。静态化 shape 参数把 batch、序列长度这类维度从运行时变量改成编译期常量。改写过程中有一个原则值得坚持先保正确再谈性能。我见过太多人一上来就手工调 tile、改融合策略结果数值对不上排查一周才发现是某个转置写错了方向。5.3 性能剖析的先后顺序剖析要有顺序乱看指标只会浪费时间。我通常按这个链路走先看端到端耗时分解区分预处理、模型前向、后处理各占多少。预处理里的 tokenize 和图像解码经常被忽略实际能占到 20%。再看前向内部的时间分布找出最耗时的几个算子。如果分布很均匀说明融合没做好如果某一两个算子独大说明那里有回退或 tile 太小。然后看各层存储的带宽利用率。SRAM 利用率低说明数据复用差HBM 打满说明容量规划有问题DDR 打满说明权重热度分级没生效。最后看形状命中率统计有多少请求触发了重新编译或 padding 浪费。这个顺序的好处是每一步都能排除掉一大类可能不会在错误的方向上深挖。实测下来八成的问题在前两步就能定位。5.4 故障排查对照表现象最可能的原因先查什么处理方向吞吐只有预期的三成算子回退到主机执行编译日志里的未支持算子清单改写算子或换等价实现首 token 正常、后续极慢权重未常驻高速层DDR 带宽利用率与权重放置策略调热度分级把热点层钉在 HBM延迟抖动大、P99 离谱形状未命中运行时编译形状命中率统计扩充预编译桶加兜底桶长上下文下显存溢出KV cache 估算偏低每 token KV 字节数与时并数降并发或开启 KV 分层多模型共存时互相拖慢内存池调度粒度太粗各模型的实际驻留量拆节点或做时间片隔离数值对不上累加精度或转置方向错误逐层误差对比提累加位宽核对布局这张表是我自己排查时用的骨架实际用的时候建议按你的模型补充两列具体的日志关键字和责任人否则出了问题还是得从头想。注意任何性能突然下降的问题先确认版本再确认形状分布最后才动代码。这三步能挡掉一半的无效排查。6. 选型判断什么时候该用它什么时候别硬上6.1 与通用 GPU 路线的正面对比把两条路线放在一张表里看差异会清楚很多。注意这里说的是典型取向不是绝对结论具体型号之间差别也不小。维度数据流 RDU 路线通用 GPU 路线单节点模型容量优势明显TB 级内存池受单卡显存约束靠切分扩展低并发长上下文优势明显无需跨卡切分KV 复制与调度开销大动态形状适应性弱依赖形状分桶强运行时自适应生态与算子覆盖相对窄需要适配极成熟几乎无死角编译/启动成本高需预编译与缓存低即时编译为主延迟确定性高近似常数中受调度与缓存影响训练场景不是主战场主流选择小模型高并发一般调度粒度粗优势明显从这个对比能看出它不是更好的 GPU而是另一种取舍。把它放在 GPU 集群旁边做特定负载的补充往往比试图整体替换要现实得多。6.2 适合与不适合的清单适合的场景我总结成四条模型大到单卡装不下、切分后通信开销吃掉大部分收益比如百 B 级稠密模型或总参数更大的 MoE。上下文很长、并发很低比如文档理解、代码库问答、长会议纪要分析。对延迟稳定性有硬性要求、需要给出严格 SLA 的在线服务。多模型共存、需要在一台机器上同时驻留好几个模型的场景大内存池的利用率优势能体现出来。不适合的场景同样清楚请求形状高度发散、长尾极长形状分桶怎么设计都浪费严重。小模型、超高并发、追求极致单位成本吞吐的场景通用路线更划算。需要频繁微调、改结构、试新算子的研发阶段编译成本会让你痛不欲生。团队没有编译器侧的人力遇到算子不支持就只能等厂商支持。我个人在实际操作中的体会是这类平台的引入成本不在硬件采购而在把模型变成编译器喜欢的样子这件事上。预算里要留出至少两到三个人的适配工作量以及一轮完整的精度回归测试周期。如果这部分没算进去上线时间大概率会翻倍。最后分享一个判断小技巧拿你线上流量里最长的那 1% 请求去估算它在目标平台上的单次成本。如果这个成本可以接受说明大内存路线对你是真优势如果这 1% 的请求把成本拉得离谱那你的负载其实更适合弹性调度而不是大内存常驻。这个测试比任何 benchmark 都更接近真实决策。
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。