ONNX模型在鲲鹏上推理慢一倍?先别急着换模型,查 NUMA 和算子后端
把一个在 x86 云服务器上跑得好好的 ONNX 推理服务迁到华为云鲲鹏 ARM 实例,预期是“架构换了但模型没变,性能应该差不多”。实际压测结果很尴尬:P99 延迟翻了一倍多,吞吐量掉了近四成,CPU 占用却上不去,像是在“等内存”。排查过程比预想的曲折,最终定位到两个 ARM 环境特有的隐形瓶颈。
一、 第一个瓶颈:NUMA 跨节点内存访问的“隐性税”
鲲鹏 920 采用多 NUMA 节点架构,每个 CPU Socket 有自己的本地内存控制器。ONNX Runtime 的默认构建版本没有启用 NUMA 感知的内存分配,模型加载和推理时的内存页可能落在远端 NUMA 节点上。单次跨节点内存访问的延迟比本地访问高出数十纳秒,推理服务的高并发线程持续触发跨节点访问时,延迟抖动和带宽浪费会被显著放大。实测数据也印证了这一点:绑定 NUMA 本地节点后,ONNX 推理服务的 P99 延迟比未绑定时低约 40%,吞吐量提升 25%。
我们的修复很简单,但效果立竿见影:用 numactl 把推理进程绑定到单个 NUMA 节点,确保 CPU 和内存都在本地。
bash
numactl --cpunodebind=0 --membind=0 python serve_onnx.py
同时关掉内核的 numa_balancing,避免 CFS 调度器在运行中把线程迁移到远端节点、把缓存全部打掉。
bash
sysctl -w kernel.numa_balancing=0
二、 第二个瓶颈:ONNX Runtime 在 ARM 上回退到了低效后端
NUMA 绑定之后延迟有改善,但距离 x86 基线仍有差距。检查 ONNX Runtime 的构建信息才发现,使用的 pip 安装版本没有启用 ACL 或 DNNL 等 ARM 优化后端,可用 provider 列表中只有 CPUExecutionProvider。在 ARM64 上,这意味着部分算子回退到了标量或低效的 NEON 实现,尤其是 GatherND、DynamicQuantizeLinear 这类在 ARM 上缺乏高效内核的算子,会被迫走通用解释器路径。
解决路径有两条:一是换用华为云或 openEuler 生态提供的、启用了 ACL 后端的 ONNX Runtime 构建;二是对热点算子做定向替换或融合。我们优先换了启用 ACL 的版本,同时把 intra_op_num_threads 设成物理核数(而不是逻辑核数),避免超线程带来的缓存竞争。
python
sess_options = ort.SessionOptions()
sess_options.intra_op_num_threads = 8 # 物理核数,非逻辑核
三、 量化要谨慎:INT8 不是无脑加速
过程中也尝试了 INT8 动态量化来进一步压延迟,但在 ARM CPU 上,动态量化的收益不稳定——部分算子有高效 INT8 内核时提升明显,没有的反而引入额外的量化/反量化开销。相比之下,静态 QDQ 量化配合校准数据在 ARM 上的表现更可预期。我们最终对卷积为主的视觉子模型做了静态 INT8,对包含大量非规则算子的 NLP 子模型保持 FP32,整体延迟再降了约 15%,而精度损失控制在可接受范围内。
四、 总结
ONNX 推理在 ARM 云服务器上的性能退化,很少是“ARM 本身不行”。先做 NUMA 绑定、再检查 ONNX Runtime 的 ARM 后端是否启用、最后有针对性地选择量化策略,三步走完,大部分场景的性能可以回到甚至超过 x86 基线。反过来,如果在 NUMA 和算子后端都没对齐的情况下急着换模型或加机器,钱花了,问题还在。
- 点赞
- 收藏
- 关注作者
评论(0)