两条分别接向不同硬件的适配管线汇入同一段共享内核层

一、这条新闻该读成成本,不是性能

据公开报道,AMD 与英特尔在 PyTorch 的 x86 CPU 深度学习优化方面展开合作,相关工作整合进开源深度学习库 oneDNN,开发者得到的是一条跨两家 CPU 的软件路径。消息本身不含性能数字,任何”快多少倍”的说法都不是这条报道给的,本文也不补。

对我们这类做交付的团队,它改变的是一个具体成本项:以前要在两条优化路径上各维护一遍。同一份模型,两家平台各自有算子实现、各自的线程与指令集假设、各自的版本节奏,而这三样都会在升级时以不同方式出问题。合流之后,省掉的首先是要维护两遍的适配层,其次才谈得上推理本身的开销。这两件事必须分开记账,否则很容易把”少维护一套胶水”的确定性收益,错报成”性能提升”的不确定收益。

我们的判断标准很直接:如果一套 CPU 推理栈的价值来自”少踩两遍坑”,那它可以立刻采用;如果来自”更快”,那它必须先在自有算例上重新测过,测完才允许进容量规划。

二、CPU 在企业里承担的到底是哪四个位置

CPU 推理最常见的错判来自拿 GPU 的直觉去看它:单看峰值算力当然不够,但企业场景里 CPU 的价值不来自峰值,来自它已经在服务器上、不占额外预算、冷启动可控、且不需要为它单独设计故障域。

位置 为什么是 CPU 适合判据 常见错判
小模型常驻 模型本来就不大,显存收益抵不上运维成本 参数量与激活规模让权重整体能驻留共享缓存之外不成为瓶颈 为了”统一架构”把小服务也塞进 GPU 池
低并发在线 请求稀疏,排队不发生在算力上 峰值并发下尾延迟仍达标 只看平均延迟,忽略突发窗口
批量离线 夜间跑,吞吐优先,不挑硬件 单位时间处理条数满足批窗口 用在线服务余量跑批,白天互相拖累
显存不足兜底 溢出到大内存或降级到 CPU 切换后仍能给出可接受结果 兜底链路从未演练,出事时切不过去

四项里只有第四项是”降级位置”,前三项是常态位置。把它们混成一句”CPU 也能跑”,就会在容量规划时漏掉一个关键区分:常态位置要按成本优化,兜底位置要按行为一致性优化。

三、一次选型要测的四个量,以及为什么厂商倍数换不出你的数字

我们要求 CPU 推理的选型报告必须包含四个量,缺一个就打回去重测。

  1. 首包延迟:从请求进入到第一个输出单元可用。流式场景里它决定体验,且它对线程启动、内存分配与冷启动极敏感。
  2. 吞吐:单位时间完成条数,且必须同时记录批大小与线程数。只报吞吐不报配置的测试等于没测。
  3. 绑核与满核的差异:同一负载分别在”限制到固定核数”和”放任使用全部逻辑核”下跑。跨 NUMA 访存和超线程争用常常在这里现形,很多”CPU 慢”实际是”线程铺太开”。
  4. 精度损失:量化或低精度路径与基线的差异,用自有任务集而不是通用样例衡量,并记录在长上下文或边界输入上的表现。

这四个量解释了为什么”厂商给出的倍数”不能直接换算到自家环境:倍数依附于算例形状(矩阵尺寸、是否访存受限)、线程数配置、批大小、以及是否启用量化。同一份模型在批大小为 1 和批大小为 32 时是两种瓶颈,一个优化包在一种下有效在另一种下可能反向。把外部倍数当作输入去做容量规划,风险是线性的:你以为省了一张卡,实际省下的时间被批处理窗口吃掉。

还有一层必须在部署侧固定,否则所有基准都不成立:容器内的 CPU 拓扑与配额会改变结论。cgroup 限额下运行时看到的核数与真实可用核数不一致,默认线程数按宿主核数铺开就会被限流;缓存与内存带宽是共享的,同宿主的其他负载会污染尾延迟。可复现的做法是把”核数、绑核策略、是否限流、同宿主邻居、频率策略”写进基准记录,缺一不可比。

四、两套后端并存时最容易出的三类不一致

即便有统一路径,过渡期通常还是两套后端并存:一家平台走新内核,另一家走旧实现,或 CPU 与 GPU 同时留着。三类不一致按发现难度递增:

  • 结果数值不一致。不同实现会有不同的累加顺序与融合方式,浮点结果本就不该逐位相同。风险在阈值判断——分类概率卡在决策边界、贪心解码在第若干步选了另一条路径,看起来像”模型抽风”,实际是后端换了。对策不是要求逐位相等,而是设定与任务匹配的容差,并对关键中间量做逐层比对;同时把决策边界附近的样本单独列成必测集。
  • 缓存与线程行为不一致。一侧的内存池复用策略、工作线程数或环境变量语义不同,同一份配置在另一侧要么过订阅要么饥饿。对策是把线程与内存配置从应用里抽出来,由统一的启动层负责,并记录实际生效值而不是期望值。
  • 版本升级只在一侧验证。最隐蔽。发布流水线只在主用后端跑回归,另一侧的依赖升级就带着未验证的风险上线。对策是双后端跑同一套断言,缺任何一侧的绿色就不发版;这条是纯流程约束,技术上没有替代品。

统一路径的价值在这里最清楚:三类不一致里有两类会随实现收敛而减少,第三类只会被暴露得更早。

五、把 CPU 与 GPU 放进同一条降级链路

降级链路的设计原则是按排队与超时切,不按错误码切。GPU 侧显存吃紧时最常见的表现不是报错,是请求开始排队,错误码一切正常而延迟一路走高。

触发条件 动作 超时预算 回切条件
GPU 队列等待超阈值 同模型 CPU 路径接管 单请求预算放宽一档,上限仍需硬约束 队列等待连续若干窗口回到阈值下
GPU 节点不可用 切 CPU 或切小模型 明确放弃一部分质量目标 健康检查恢复且灰度验证通过
全链路饱和 只保留核心场景,其余拒绝 快速失败,不排队 配额恢复
精度不达业务门槛 拒答或转人工 不设降级 人工确认

三件事必须写在设计里。第一,CPU 兜底路径要在正常日子里就跑,哪怕只承担一小部分流量:从未运行过的降级路径第一次启用时必然暴露问题,而那时是最不该暴露问题的时候。第二,降级后的质量差要显式记账,把”这条回答走了降级”落到日志字段,事后才能判断某些投诉是不是降级带来的。第三,切换要有滞回,避免在阈值附近来回抖动,抖动会同时打满两侧。

预算分配上我们按”最慢依赖定并发”来算:CPU 路径的单请求耗时通常长于加速卡路径,同一份入口并发如果照搬,会把 CPU 侧排满,最后两边都不达标。因此降级档位的并发上限单独设,且和回切阈值一起评审。

六、验收步骤与真实踩过的坑

一份可交给客户的 CPU 推理验收,按顺序做六步:固定算例与配置矩阵、在目标容器规格下测四个量、双后端跑一致性断言、跑一次真实降级演练并记录字段、把配置生效值写入部署产物、最后出一份”结论依附于哪些条件”的说明而不是一个倍数。

踩坑清单里最常复发的四条:把默认线程数当最优(默认值常常按宿主核数算,与配额无关);只在批大小 1 上测过就上生产,批量场景失去融合收益;冷启动没测,缩容后重新拉起的实例在第一批请求上明显慢;只在一条样例上比精度,边界样本的差异被漏掉。

洹拓在给客户写这类方案时,报告首页只会放一句话:本文所有数字在什么配置下成立。选型真正省下来的从来不是算力账单,是那些需要在两套实现里各踩一遍的坑。