博客算法工具链【模型部署那些事】QAT 实战:从 PyTorch 模型到 J6E 实板精度与延时验证

【模型部署那些事】QAT 实战:从 PyTorch 模型到 J6E 实板精度与延时验证

金属货机2026-09-30
30
0
本文从 CIFAR-10 浮点模型开始,依次完成校准、QAT 微调、BC 导出、HBM 编译,最后通过 UCP 在 J6E 开发板上验证精度和推理延时。整个流程基于 OpenExplorer 3.8.1,目标架构为 nash-e。

1. 实验环境与结果

实验使用的主要环境如下:

项目

配置

开发板

Horizon Robotics J6E MatrixB

BPU 架构

nash-e

BPU 核心

Core 0

开发环境

OpenExplorer 3.8.1 GPU Docker

PyTorch

2.8.0+cu128

horizon-plugin-pytorch

3.2.4

HBDK4 Compiler

4.9.7

部署方式

x86 端通过 hbm_infer 调用板端 BPU

各阶段精度如下:

阶段

Top-1

精度变化

Float PyTorch

84.40%

基线

Calibration

84.26%

-0.14pp

QAT PyTorch

86.03%

相对 Float +1.63pp

QAT BC

85.91%

相对 QAT -0.12pp

Quantized BC

85.89%

-0.02pp

J6E HBM

85.89%

相对 Quantized BC 0.00pp

延时测试使用固定输入,预热 100 帧后正式测量 1,000 帧:

指标

Mean

P50

P95

P99

x86 UCP 端到端

2.133 ms

2.135 ms

2.320 ms

2.503 ms

gRPC 发送到接收

1.765 ms

1.762 ms

1.928 ms

2.130 ms

板端总处理

0.633 ms

0.616 ms

0.762 ms

0.947 ms

BPU 纯推理

0.445 ms

0.430 ms

0.491 ms

0.740 ms

2. 整体流程

本次实践的主线如下:

3. 数据预处理必须保持一致

本次使用 CIFAR-10 数据集:

  • 训练集 50,000 张

  • 测试集 10,000 张

  • 图像尺寸为 32×32
  • 共 10 个类别

模型使用的输入预处理为:

对应的数据变化是:

训练、BC 验证和实板验证必须执行完全相同的预处理。预处理不一致时,模型通常仍能正常运行,但输出精度会明显异常。

这类问题很容易被误判为量化误差或板端计算误差,因此应在部署前固定预处理流程,并在每个验证阶段复用同一份实现。

4. 模型的 QAT 适配

实验模型选择 MobileNetV2。它规模适中,同时包含普通卷积、深度可分离卷积和残差连接,适合作为 QAT 入门模型。

4.1 设置目标架构

构建模型、执行 prepare、导出和编译之前,都需要设置正确的 march:

march 必须与目标板卡的 BPU 架构一致。

4.2 添加量化边界

模型输入和输出位置分别加入 QuantStub 和 DeQuantStub:
QuantStub 标记浮点输入进入量化计算图的位置,DeQuantStub 标记输出恢复为浮点的位置。
后续执行 remove_io_op 后,这两个边界算子会被移除,最终 HBM 直接接收 INT8 输入并返回 INT32 输出。

4.3 处理 ReLU6 兼容性

torchvision 的 MobileNetV2 默认包含 ReLU6(inplace=True)。当前使用的 Horizon QAT 模块不支持这种形式,prepare 时会报错:
可以在 prepare 前将其改为非原地操作:
ReLU6 没有可训练参数,因此这项修改不会改变 checkpoint 的 state_dict 结构。

5. QConfig、Template 和 prepare

在 Horizon QAT 流程中,可以这样理解三个概念:

QConfig 决定 Observer 类型、数据类型、量化粒度和对称方式。例如:

Template 则负责按模块名或输入输出位置匹配量化配置。本次实验使用全局双 INT8 配置,没有加入 INT16 或 FP16 混合精度策略。

prepare 会分析浮点计算图,并完成以下工作:
  1. 根据 Template 计算各位置的最终 QConfig。

  2. 将浮点模块替换为 QAT 兼容模块。

  3. 插入 Observer 和 FakeQuantize 节点。

  4. 检查量化边界、模块执行次数和异常配置。

  5. 生成能够在校准、QAT 和验证状态之间切换的模型。

prepare 后得到的仍然是 PyTorch 模型,但前向计算已经可以模拟定点量化误差。

常见状态包括:

  • CALIBRATION:收集激活统计信息。
  • QAT:训练过程中启用 FakeQuant。
  • VALIDATION:冻结统计信息并验证当前量化精度。
  • FLOAT:关闭 FakeQuant,用于浮点行为检查。

执行后应检查 QConfig 变更日志和模型检查报告,确认没有异常配置或非预期量化边界。

6. 浮点训练、校准与 QAT

6.1 浮点模型

浮点模型使用 Adam 训练 100 个 epoch:

最终 Top-1 为:

QAT 不应替代正常的浮点训练。更稳妥的做法是先得到精度可用的浮点模型,再使用更小的学习率进行量化微调。

6.2 校准

校准阶段使用 HistogramObserver,共执行 100 个 step,覆盖 25,600 张图像。

结果如下:

校准阶段不通过梯度更新模型权重,主要作用是收集激活分布并确定量化范围。0.14 个百分点的下降处于可接受范围内,说明当前 INT8 配置可以继续进入 QAT。

6.3 QAT 微调

QAT 阶段切换到 MinMaxObserver,使用较小学习率训练 20 个 epoch:

最终结果:

QAT 精度高于浮点基线不代表量化天然能够提升模型精度。本次浮点模型在第 100 个 epoch 仍然取得最佳结果,说明训练尚未完全进入平台期。QAT 又增加了 20 个 epoch,同时改变了优化器和训练噪声,因此精度提升更可能来自额外微调和 FakeQuant 带来的正则化效果。

7. 从 QAT 模型导出 BC

QAT 模型导出后得到:

导出过程中曾遇到一个值得记录的问题:文件保存成功后,进程在退出阶段出现原生库段错误。

仅凭这条日志无法判断 BC 是否损坏。随后我在一个全新的进程中重新加载 BC,并检查函数数量和输入输出签名:

函数签名为:

因此该 BC 可以继续使用。

这里的经验是:产物已经生成,不等于产物一定有效;进程异常退出,也不等于产物一定损坏。更可靠的方法是在独立进程中重新加载产物并执行结构检查。

8. Quantized BC 与整数 IO

QAT BC 经过 convert(..., march="nash-e") 后得到 Quantized BC。转换前后精度如下:

转换带来的精度变化很小。

接着使用 remove_io_op 删除输入 Quantize 和输出 DeQuantize,使模型直接使用整数输入输出。处理后的函数签名为:
算子统计中没有发现 hbtl CPU 算子,主体计算均落在 BPU 上。
执行 remove_io_op 后,输入量化和输出反量化不再由模型内部完成,而是交给应用侧处理。

9. 编译 HBM

使用 HBDK4 编译面向 nash-e 的 HBM:

编译结果:

生成的 HBM 为:

编译完成后,建议保存 HBM 的文件大小和 SHA256。这样可以确认实板测试使用的模型是否与编译产物一致。

10. 通过 hbm_infer 部署到 J6E

本次使用 HbmRpcSession 从 x86 端自动部署 HBM,并通过 gRPC 调用板端 BPU:
hbm_infer 会自动完成:
  1. 通过 SSH 连接开发板。

  2. 部署 HBM、板端服务和运行库。

  3. 启动推理服务。

  4. 通过 gRPC 发送输入并接收结果。

  5. 关闭服务并清理临时文件。

HBM 不需要提前手工复制到开发板。

临时目录不要放在 NFS 上

最初我将 remote_root 放在 NFS 目录中。推理能够正常完成,但 close_server() 清理文件时出现:

将临时目录改到板端本地文件系统后,问题消失:

对于频繁创建和删除临时文件的推理服务,板端本地 /tmp 比 NFS 目录更合适。
无论推理是否成功,都应在 finally 中调用 close_server(),避免板端残留服务进程和临时文件。

11. 整数输入输出的量化处理

从 HBM 中读取到的输入信息如下:

应用侧需要按照模型的量化参数将浮点输入转换为 INT8:

输出信息如下:

10 个类别分别使用独立的 scale,因此输出需要逐通道反量化:

这里有一个容易遗漏的问题:不能直接对原始 INT32 输出执行 argmax。因为每个类别使用的 scale 不同,整数值之间不能直接比较。必须先逐通道反量化,再计算分类结果。

12. 实板精度验证

正式评测前,可以先用少量样本检查整个链路:

小样本结果只用于确认预处理、输入量化、推理和输出解析是否正常。

在 CIFAR-10 的 10,000 张测试图像上,最终结果为:

HBM 与 Quantized BC 的精度完全一致,说明以下环节在 x86 和实板之间保持一致:

  • 图像预处理

  • INT8 输入量化

  • HBM 计算

  • INT32 输出反量化

  • Top-1 计算

13. 独立测试推理延时

完整精度测试包含数据读取、图像预处理和输入量化。延时测试使用一份提前完成预处理和量化的固定输入:

测试时需要区分几种延时:

  • host_call:x86 Python 调用一次 session(...) 的总耗时。
  • sd2rv_duration:请求经 gRPC 发出到响应返回的耗时。
  • board_duration:板端预处理、推理和后处理总耗时。
  • infer_duration:BPU 纯推理耗时。
本次 BPU 纯推理平均耗时为 0.445 ms,板端总处理平均耗时为 0.633 ms,而 x86 侧看到的端到端平均耗时为 2.133 ms。
三个数字描述的范围不同。评估 BPU 性能时应关注 infer_duration;评估真实应用调用体验时,则需要关注端到端耗时。
算法工具链
技术深度解析征程6
评论0
0/600