1. 课程导入与工程全景
视觉推理模型在服务器上跑通,并不等于它能在真实业务里跑好。同一份 PyTorch 权重,放到 NVIDIA GPU、国产 AI 加速卡、移动端 NPU 或 CPU 上,往往出现三类结果:精度掉点、时延翻倍、甚至直接编译失败。多硬件平台视觉推理适配,就是解决"同一模型、多种芯片、一致可用"这一工程问题的系统性方法。它不是简单的模型转换,而是围绕算子支持、数值精度、内存布局、调度策略四条主线,把训练框架产出的模型,稳定地落到目标硬件并满足业务指标。
适配到底在适配什么
先明确对象。视觉推理的输入通常是图像张量,形状如 [1, 3, 640, 640],经过骨干网络、颈部、检测头,输出框、类别与掩码。适配工作发生在"模型"与"硬件"之间,核心是三层映射:
- 计算图映射:把框架算子(Conv、SiLU、Resize、NMS)映射到硬件支持的算子库或自定义算子。
- 数据布局映射:NCHW 与 NHWC、连续内存与分块内存、量化零点与缩放因子的排布差异。
- 执行调度映射:算子融合、流水并行、多核切分、动态 shape 处理。
这三层任何一层不匹配,都会以精度或性能问题的形式暴露出来。因此适配的工程本质是:在保持数值语义一致的前提下,让计算图、布局、调度同时满足目标硬件的约束。
典型挑战:为什么"一次编写、到处运行"很难
第一是算子覆盖缺口。某国产加速卡可能支持 Conv、Add、Mul,但不支持 GridSample 或可变形卷积,而视觉模型恰恰常用这些算子。第二是精度差异。FP16 在部分硬件上累加溢出,INT8 量化后小目标召回下降,同一阈值下 mAP 可能从 0.42 掉到 0.36。第三是性能特征差异。GPU 偏好大 batch 与高并行,NPU 偏好固定 shape 与算子融合,CPU 则受内存带宽限制。第四是工具链碎片化。ONNX、TensorRT、各家自研编译器对同一算子的语义解释并不完全一致。
完整工程流程
一个可复用的适配流程包含六个阶段,且允许回退迭代:
- 基线建立:在参考平台(如 GPU)上固定输入、权重、预处理,记录输出与精度指标,作为唯一比对基准。
- 模型导出:导出 ONNX 或中间表示,固定动态维度,记录算子清单与版本。
- 硬件编译:用目标平台工具链编译,处理不支持的算子(替换、拆分或自定义实现)。
- 数值校验:逐层比对与端到端比对,定位首个偏差超限的层。
- 性能调优:调整 batch、量化策略、算子融合与内存复用,测量时延与吞吐。
- 回归固化:把输入样本、阈值、脚本固化为回归用例,防止后续改动引入退化。
下面这张图给出该流程的工程全景,重点表达各阶段的输入输出、判定条件与回退路径,而不是简单罗列步骤。
典型应用场景
适配需求通常来自四类场景。其一是云端多卡混部:同一检测服务要同时跑在 A 卡与 B 卡上,需要统一接口与精度口径。其二是边缘盒子:算力受限,必须做 INT8 量化与算子融合,同时保证小目标不丢。其三是移动端:NPU 对动态 shape 支持差,需要固定分辨率并处理前后处理算子。其四是国产化替代:把已有 GPU 模型迁移到国产加速卡,重点在算子补齐与精度回归。
一个可执行的最小适配示例
以 YOLO 类检测模型迁移到某 NPU 为例,关键操作如下。先固定输入并导出:
# 固定 batch=1, 输入 640x640,导出 ONNX
torch.onnx.export(model, dummy, "yolo.onnx",
input_names=["images"], output_names=["output"],
opset_version=11, dynamic_axes=None)
随后用目标工具链编译,遇到不支持的算子时,优先用等价算子组合替换,例如把不支持的 Hardswish 拆为 HardSigmoid + Mul。编译完成后做数值校验:
# 逐层比对,定位首个偏差超限的层
max_abs_err = np.max(np.abs(ref_out - npu_out))
print("max_abs_err =", max_abs_err) # 示例:0.0031
若端到端误差在阈值内,再进入性能调优:调整 batch、开启算子融合、复用输入输出内存。最后把该输入样本、阈值与脚本写入回归用例,后续任何模型或工具链升级都自动跑一遍。这样,适配就从一次性的"调通"变成了可持续的工程能力。