Exynos NPU在三星移动SoC中承担卷积、矩阵乘等神经网络计算任务,但它并不像CPU那样直接暴露通用指令集。应用层要真正调用NPU,必须经过ONE Runtime和模型编译器这两层基础设施。ONE Runtime负责执行模型、管理内存、调度后端,编译器则把开发者熟悉的模型格式转化为NPU后端可运行的指令序列。只有把这两层理解清楚,才能解决端侧推理中的转换失败、算力利用率低、量化精度异常等问题。

一、Exynos NPU与ONE Runtime的职责边界
Exynos NPU内部有一组专用的乘加阵列和片上缓存,适合执行卷积、深度可分离卷积、全连接等算子。但NPU没有统一的用户态编程接口,不同SoC型号之间的寄存器配置和内存布局也存在差异。ONE Runtime的出现就是把这些差异封装到后端适配层中,对上提供统一会话、输入输出张量以及运行接口,对下根据可用硬件选择NPU、CPU或GPU后端。
在实际运行中,ONE Runtime会先解析nnpackage包,构建计算图,然后对每个算子询问NPU后端是否支持。如果某个算子NPU不支持,Runtime会将其切分到CPU执行,并在边界处插入数据搬运节点。这个切分过程对上层透明,但会带来额外的内存拷贝和同步开销,因此模型编译阶段能否让更多算子映射到NPU,直接影响最终性能。
与通过Android NNAPI间接调用NPU的方式相比,ONE Runtime可以更细粒度地控制输入输出布局、内存池和算子选择。NNAPI虽然通用,但部分厂商驱动对动态形状和自定义算子支持有限。ONE Runtime更贴近三星硬件,能利用NPU的私有特性,例如专用卷积模式和缓存驻留策略,降低推理延迟。
二、模型编译器怎样把ONNX变成NPU指令
模型编译器不是简单的格式转换工具。它从ONNX、TensorFlow Lite或PyTorch导出的模型中读取计算图,先做一轮与硬件无关的优化,例如常量折叠、冗余算子消除、算子合并等。然后根据目标NPU的能力,对计算图进行后端相关改写,比如把连续的卷积、批归一化和ReLU合并成一个融合算子,减少中间结果的写回和读取。
量化是编译器中非常关键的一步。Exynos NPU对INT8计算有专门的加速通路,编译器会分析模型每一层的数值范围,插入量化与反量化节点,或直接生成INT8版本的算子。常见的做法是用少量校准数据确定激活值的scale和zero point,再执行训练后量化。如果精度下降过多,可以结合量化感知训练,或者只对敏感层保留浮点计算,形成混合精度模型。
后端代码生成阶段,编译器会把优化后的图映射为NPU可执行的二进制指令和权重布局。这里会涉及内存对齐、张量排布、算子调度等底层细节。比如NPU偏好NHWC格式,而ONNX默认可能是NCHW,编译器需要插入转置,或直接在卷积参数中处理。最终产物通常是一个nnpackage目录,里面包含模型结构、权重和配置文件。下面是一个使用onecc命令行的示例:
onecc \ --input_model model.onnx \ --output_dir ./nnpackage \ --target npu \ --quantize int8 \ --calibration_data calibration.txt
执行成功后,output目录下会出现model.nnpkg文件,它就是ONE Runtime可以加载的模型包。
三、端侧部署流程与Runtime调用
端侧部署一般遵循导出、编译、加载、执行四个步骤。先在训练框架中把模型导出为ONNX或TensorFlow Lite格式,再使用模型编译器生成nnpackage。应用启动后,通过ONE Runtime加载该包,设置输入张量,运行推理并读取输出。由于移动端内存有限,Runtime通常允许复用输入输出缓冲区,避免每次推理都重新分配内存。
下面用Python接口展示一个最简推理流程,实际移动应用中会使用C++或Java接口,但逻辑类似:
import onert
import numpy as np
session = onert.Session("model.nnpkg")
input_data = np.random.rand(1, 224, 224, 3).astype(np.float32)
session.set_input(0, input_data)
session.run()
output_data = session.get_output(0)
print("output shape:", output_data.shape)
print(output_data)
常见的部署问题集中在算子不支持、量化后精度下降和内存超限。算子不支持通常发生在模型中包含NPU尚未实现的算子,例如某些变形的激活函数或动态控制流。解决办法是查看编译器输出的分区报告,将未支持的算子替换为等价算子,或者允许该子图回退到CPU。精度下降则要检查校准数据是否覆盖真实输入分布,必要时改用混合精度。内存超限多由大尺寸特征图或未合并的中间张量引起,可以通过调整输入分辨率、开启内存复用或减小批大小来缓解。
另外,Runtime的初始化有一定开销,端侧应用应避免在UI主线程中加载模型和执行推理。可以把模型加载放在后台线程,推理任务放入单独的串行队列,通过异步回调返回结果。对于连续视频流检测场景,还可以使用Runtime的双缓冲或多session并行能力,减少NPU空闲等待。
四、性能调优的几个实用方向
要让Exynos NPU跑满,只完成模型转换还不够。首先要关注算子融合情况。融合后的计算图可以减少DRAM读写,对移动端能效提升明显。通过编译器生成的可视化图或日志,可以看到Conv+BN+ReLU是否被合并。如果没有合并,需要检查BN是否已经被折叠,或者ReLU是否处于被融合支持的连接位置。
其次是数据布局。Exynos NPU处理NHWC格式通常比NCHW快,因为卷积核在通道维的访存更连续。编译器会自动插入转置,但转换过程会增加一次内存搬运。如果模型输入来自相机或图像处理管线,可以尽量让上游直接输出NHWC,省去中间的转置。若必须使用NCHW,建议在模型外部完成一次转置,而不是让Runtime在每个推理周期重复处理。
缓存和内存对齐也会影响性能。nnpackage中的权重经过重新排列后,如果文件加载到非对齐地址,可能触发额外的拷贝。移动端可以使用posix_memalign或平台提供的对齐分配器来创建输入输出张量。对于INT8模型,确保输入数据已经按照编译器预期的scale和zero point处理,避免在Runtime内部隐式转换。下表列出了调优手段与收益方向:
| 调优手段 | 主要收益 | 注意事项 |
|---|---|---|
| INT8量化 | 降低延迟与功耗 | 需要校准数据,敏感层可保留FP32 |
| 算子融合 | 减少中间读写 | 需确认NPU支持融合模式 |
| NHWC布局 | 提升访存效率 | 上游数据尽量直接输出NHWC |
| 内存复用 | 降低峰值内存 | 注意并发场景下生命周期 |
最后,不要忽略端侧性能测试。同一模型在不同Exynos芯片上的NPU频率、缓存大小和驱动版本都可能不同,建议在目标设备上分别测量NPU推理延迟、CPU回退比例和内存占用。只有结合编译器日志和运行时profiling数据,才能确定优化瓶颈到底在算子映射、内存带宽还是调度等待。

