> ## Documentation Index
> Fetch the complete documentation index at: https://dragonwingdocs.qualcomm.com/llms.txt
> Use this file to discover all available pages before exploring further.

# 量化是被低估的迁移步骤（第 4 部分，共 7 部分）

> 使用代表性数据、PTQ 和一个可度量的重试梯度，把 FP32 模型变成一个准确、高效的 HTP/NPU 工件。

<hr style={{ border: "none", borderTop: "1px solid #eee", margin: "0 0 2rem" }} />

<div style={{ display: "flex", justifyContent: "space-between", gap: "1rem", marginBottom: "2rem", flexWrap: "wrap" }}>
  <a href="/zh/tutorials/from-tensorrt-engine-to-qairt-qnn-context-binary" style={{ color: "#31017D", fontWeight: 600, textDecoration: "none" }}>← 上一篇：第 3 部分</a>
  <a href="/zh/tutorials/porting-llm-vlm-audio-and-vla-workloads-to-qualcomm" style={{ color: "#31017D", fontWeight: 600, textDecoration: "none" }}>下一篇：第 5 部分 →</a>
</div>

让模型在边缘 AI 加速器上跑得慢的最快方法就是把量化当成一个导出复选框。

在 Jetson 上，许多团队会先选 TensorRT FP16，等需要更高吞吐量时再添加 INT8。在 Qualcomm Dragonwing 上，HTP/NPU 路径从一开始就更常是整数路径。这会改变迁移计划。

快乐路径仍从你已经训练过的同一个模型开始：

```text theme={null}
best.pt
  -> 静态 ONNX
  -> FP32 验证
  -> QAIRT/QNN 转换
  -> 使用代表性数据的量化
  -> 目标 SoC 的上下文二进制文件
  -> 与 FP32 基线的验证
```

危险的捷径是这样的：

```text theme={null}
TensorRT INT8 校准缓存
  -> 复制到 Qualcomm
```

这不能迁移过来。TensorRT 校准缓存是 TensorRT 工件。更安全的路径是使用来自你产品领域的真实校准样本，从源模型或 FP32 中间体重新量化。对于贯穿的 YOLO 智能摄像头案例，这意味着使用来自目标摄像头路径的真实帧，而不是通用图像文件夹。

在开始之前：

```text theme={null}
[ ] FP32 源模型或已验证的 ONNX
[ ] 具代表性的校准集
[ ] 带产品指标的独立验证集
[ ] 目标 SoC/HTP 架构和 SDK 版本
[ ] 可接受的准确率损失和延迟/FPS 目标
```

***

## 为什么这比导出更重要

把 PyTorch 导出到 ONNX 让你在通往可移植图的路上走了大部分。量化决定了那张图在目标加速器上是否准确且快速。

一次迁移可以以三种看起来都像"模型不好"的方式失败：

```text theme={null}
模型被错误地导出了。
模型是用不具代表性的数据量化的。
运行时工件是为错误的目标/运行时组合构建的。
```

这就是我们的最佳实践为什么是在每个边界处验证：

| 边界                        | 你比较的对象                        |
| ------------------------- | ----------------------------- |
| PyTorch -> ONNX           | PyTorch 输出 vs ONNX Runtime 输出 |
| ONNX -> FP32 QAIRT/QNN 工件 | ONNX Runtime 输出 vs 转换后工件输出    |
| FP32 -> 量化工件              | ONNX Runtime 输出 vs 量化输出       |
| 工件 -> 应用                  | 源应用指标 vs 设备应用指标               |

对于分类器，这可能是 top-1/top-5 准确率。对于 YOLO，可能是 mAP、固定置信度阈值下的召回率以及少量手工检查的边缘案例。对于语音，可能是 WER。对于嵌入，可能是余弦相似度和检索质量。

具体指标取决于产品。重要的是在调优之前先选定它。

用失败的边界来避免指责错误的阶段：

| 失败的比较              | 可能的问题                 |
| ------------------ | --------------------- |
| PyTorch vs ONNX    | 导出、布局、预处理或不受支持的图重写    |
| ONNX vs FP32 QNN   | 转换器行为、算子支持、张量名或布局     |
| FP32 QNN vs 量化 QNN | 校准数据、量化设置或精度选择        |
| 张量匹配但应用不同          | 摄像头格式、颜色转换、后处理、阈值或时间戳 |

***

## 先 PTQ，必要时再 QAT

有两条实用的量化路径：

| 路径  | 含义     | 何时使用             |
| --- | ------ | ---------------- |
| PTQ | 训练后量化  | 大多数模型的首次尝试       |
| QAT | 量化感知训练 | 当 PTQ 没能达到准确率目标时 |

PTQ 是最简单的好路径：没有重训练循环、快速迭代，对于算子干净且有代表性校准数据的视觉模型往往就够了。

QAT 是更重的路径。AIMET 可以帮助在训练循环中带量化效果地训练模型，但这会增加训练基础设施、模型所有者时间以及新的验证周期。我们建议把 QAT 留给 PTQ 无法通过真实产品关口的情形。

一条好的决策规则：

```text theme={null}
PTQ 通过准确率和延迟关口 -> 交付候选
PTQ 未通过准确率但 FP32 工件正确 -> 尝试量化修复
量化修复失败 -> 使用 AIMET QAT
FP32 工件是错的 -> 回到导出/转换，而不是量化
```

***

## 从真实校准数据开始

校准数据不是形式。它教量化器该模型在设备上会看到的激活范围。

使用与生产匹配的样本：

```text theme={null}
真实摄像头
真实光照
真实压缩
真实裁剪
真实分辨率
真实类别不平衡
真实空场景
真实困难负样本
```

对于摄像头检测器，一个更好的校准集通常是 100-500 张无聊的产品帧，而不是 5,000 张通用互联网图像。包括暗帧、运动模糊、眩光、拥挤场景、仅背景场景，以及通常会触发误报的边缘案例。

对于 LLM/VLM/音频模型，校准更依赖运行时和模型，但同样的原则适用：提示词、上下文长度、图像或音频片段都应类似于产品工作负载。

***

## 选择一个精度通道

Qualcomm 迁移中常见的精度包括：

| 精度       | 用途                     |
| -------- | ---------------------- |
| `a8w8`   | 许多 HTP 视觉模型的默认首选       |
| `a16w8`  | 当激活量化伤害准确率时有用          |
| `a16w16` | 更大/更慢，但能保留更多准确率        |
| `fp16`   | 在受支持的地方对 GPU 路径或回退验证有用 |
| `w4a16`  | 常见于 GenAI/LLM NPU 优化工件 |

对于一个标准的目标检测器，我们的最佳实践是从 `a8w8` 开始、验证，并且只在产品指标要求时才提升精度。

这让搜索空间保持很小：

```text theme={null}
a8w8
  -> 如果激活敏感则 a16w8
  -> 如果准确率比大小或吞吐量更重要则 a16w16 / fp16
  -> 如果训练后选项无法通过关口则 QAT
```

***

## 一个实用的 QAIRT 量化流程

假设你已经导出并验证了一个静态 ONNX 模型。这里是工件流程的紧凑回顾，好让量化决策有上下文。公共 Dragonwing 文档常展示带 `qnn-onnx-converter` 和 `qnn-model-lib-generator` 的本地 QNN 模型库通道；DLC 风格的 QAIRT/AI Hub 流程使用 DLC 工件。请对照你已安装的 SDK 验证工具名。

本地 QNN 模型库流程：

```bash theme={null}
$QAIRT_SDK_ROOT/bin/x86_64-linux-clang/qnn-onnx-converter \
  --input_network model.optimized.onnx \
  --output_path out/model.cpp \
  --input_dim "images" 1,3,640,640

$QAIRT_SDK_ROOT/bin/x86_64-linux-clang/qnn-model-lib-generator \
  -c out/model.cpp \
  -b out/model.bin \
  -o out/libs \
  -t x86_64-linux-clang
```

创建一个包含与模型输入张量匹配的原始输入的校准输入列表：

```text theme={null}
calibration/frame_0001.raw
calibration/frame_0002.raw
calibration/frame_0003.raw
...
```

然后通过在转换期间传入校准列表来进行量化：

```bash theme={null}
$QAIRT_SDK_ROOT/bin/x86_64-linux-clang/qnn-onnx-converter \
  --input_network model.optimized.onnx \
  --output_path out/model_a8w8.cpp \
  --input_list calibration_input_list.txt \
  --input_dim "images" 1,3,640,640

$QAIRT_SDK_ROOT/bin/x86_64-linux-clang/qnn-model-lib-generator \
  -c out/model_a8w8.cpp \
  -b out/model_a8w8.bin \
  -o out/libs \
  -t x86_64-linux-clang
```

如果 AI Hub 或你的 QAIRT SDK 给你的是 DLC，请使用该 SDK 版本中等价的 DLC 量化路径，并在工件清单中记录工具版本。

只有在量化通过了你的功能检查之后，再构建目标上下文二进制文件：

```bash theme={null}
$QAIRT_SDK_ROOT/bin/x86_64-linux-clang/qnn-context-binary-generator \
  --model out/libs/x86_64-linux-clang/libmodel_a8w8.so \
  --backend $QAIRT_SDK_ROOT/lib/x86_64-linux-clang/libQnnHtp.so \
  --output_dir out/context_binary \
  --binary_file model_ctx \
  --config_file context_config.json
```

对于 DLC 工件，使用 SDK 的 DLC 模型加载路径，通常是 `--model libQnnModelDlc.so --dlc_path out/model_a8w8.dlc`。

对于 IQ-9/QCS9075，围绕 HTP v73 规划。对于 IQ-8275/QCS8275，围绕 HTP v75 规划。通过为你的开发板经过测试的 SDK 示例配置来设置该架构，而不是靠猜。上下文二进制文件对目标敏感，因此当目标 SoC、SDK、BSP 或运行时包变化时都要重建。

***

## 重试梯度

当量化损害了准确率时，避免随意寻找标志。使用一个小梯度，并采用第一个通过指标的选项。

| 步骤 | 尝试                 | 原因               |
| -- | ------------------ | ---------------- |
| 1  | 更有代表性的校准数据         | 最常见的修复，代码最少      |
| 2  | 非对称激活量化            | 当激活范围不居中时有用      |
| 3  | 逐通道权重量化            | 对卷积密集型模型有用       |
| 4  | 增强或百分位校准           | 帮助拉伸范围的异常值       |
| 5  | 对敏感层使用混合精度         | 让脆弱层保持更宽         |
| 6  | `a16w8` 或 `a16w16` | 用大小/性能换准确率       |
| 7  | AIMET QAT          | 更重，但通常是顽固模型的正确答案 |

当模型所有者可以重训练或微调，并且 PTQ 损失是真实存在的而不是预处理 bug 时，AIMET 就变得有吸引力。

***

## 验证应用，而不仅是张量

张量指标有用，但产品指标才是胜负手。

对于检测器，比较：

```text theme={null}
mAP / 召回率 / 精确率
每小时误报数
关键类别的漏检
帧间的框抖动
NMS 行为
在摄像头分辨率下的延迟和 FPS
持续流下的功耗
```

对于 LLM，比较：

```text theme={null}
TTFT
prefill tokens/sec
decode tokens/sec
上下文长度
内存变化
在产品提示上的答案质量
长会话下的功耗
```

对于音频，比较：

```text theme={null}
WER 或字符错误率
流式分块延迟
首个 token / 首个音频延迟
长时间运行下的内存增长
```

一个量化张量可以看起来很接近，而应用仍然失败，因为预处理、输出解码、阈值或时间逻辑变了。

对于贯穿的 YOLO 案例研究，把量化报告保持小而具体：

| 候选       | 校准集             | 精度                | 张量关口                  | 产品关口       | 决策           |
| -------- | --------------- | ----------------- | --------------------- | ---------- | ------------ |
| FP32 基线  | 验证集             | FP32              | 参考                    | 参考 mAP/召回率 | 基线           |
| PTQ 尝试 1 | 目标摄像头帧 v1       | a8w8              | 相对 FP32 的 cosine/SQNR | mAP/召回率变化  | 保留或重试        |
| PTQ 重试   | 目标摄像头帧 v2 或更宽精度 | 如需要的 a16w8/a16w16 | 相对 FP32 的 cosine/SQNR | mAP/召回率变化  | 交付候选或升级到 QAT |

关键字段是最终决策：哪个工件通过、哪个指标失败、以及哪个重试步骤改变了结果。

***

## 最小可用验收关口

对于第一次迁移，让关口保持简单：

```text theme={null}
[ ] ONNX Runtime 匹配 PyTorch 或训练框架基线
[ ] FP32 转换后的工件与 ONNX Runtime 紧密匹配
[ ] 量化后的工件通过张量相似度阈值
[ ] 产品指标在约定的容差内
[ ] HTP 运行时验证通过
[ ] 在正确性通过之后测量延迟/FPS/功耗
[ ] 校准集与模型构建一起归档
[ ] 在工件清单中记录 SDK、BSP、SoC 和精度
```

建议的张量关口：

| 阶段          | 关口                                  |
| ----------- | ----------------------------------- |
| ONNX 简化     | cosine >= 0.9999                    |
| FP32 转换后的工件 | cosine >= 0.999，SQNR >= 30 dB       |
| 量化工件        | cosine >= 0.99，SQNR >= 20 dB，加上产品指标 |

这些是起点，不是普世真理。检测器、分割器、嵌入模型或语音模型可能需要不同容差。最好的关口是能预测现场行为的最小那一个。

***

## 结论

训练好的模型通常是可移植的。部署工件则不是。量化是可移植模型变成 Qualcomm 就绪模型的地方。

从 PTQ 开始，使用真实校准数据，对照 FP32 ONNX 基线进行比较，并让重试梯度保持简短。如果简单路径通过就采用它。如果不通过，AIMET QAT 是下一个严肃工具，而不是一堆随机的导出标志。
