yolo11n-pose vs yolo11s-pose:W8A16 端侧量化对比

两个模型用同一套量化方案(W8A16,AI Hub QNN 量化器)、同一份校准集,切成同样三段: A 臂 backbone(0–10,8 路池化输出)、B 臂 backbone + neck(0–22,P3/P4/P5 特征图)、 C 臂 backbone + neck + Pose 头(0–23,(1,56,8400))。延迟和内存在同一台 S23(SM8550)上同一天实测, n 的三个臂也在今天重跑,排除 runtime 版本漂移。

结论

另外测了 yolo11s 混合精度(AI Hub --lite_mp 10% / 20%,以及纯 W8A8 参照), 效果不理想(20% 档只比 W8A16 快 2–10%、精度略差),单独放在 子页:混合精度 vs W8A16。 另有同一对比在 SM8850(8 Elite Gen 5)上的重测:子页:n vs s · W8A16 · SM8850。

实验设置

延迟(S23 实测)

AI Hub profile 报告的推理时间(单次 profile 内多次推理的最小值)。每个编译产物 profile 3 次(不同设备实例),条形取 3 次的中位数,表里给出 3 次的范围。每个臂编译两遍:I/O 为 fp32(默认),以及 --quantize_io(uint16 I/O)。

yolo11nyolo11s

内存

三个口径:推理峰值内存(profile 报告的区间,推理过程中 NPU 侧的工作内存)、 context binary(部署到端上的编译产物大小,决定加载时间和常驻占用)、int8 权重(参数量 × 1 字节,context binary 的主体)。

峰值内存的读法:AI Hub 报的推理峰值内存在同一个量化模型的多次编译 / profile 之间波动很大 (例:n 的 B 臂 9 月记录 9.27–13.76 MB,今天 1.01–5.35 MB),所以这里每个臂跑了 3 次、看范围。 context binary 大小是确定值,是最可靠的内存对比口径。
yolo11nyolo11s 峰值内存取区间上沿,3 次 profile 的中位;fp32 I/O

精度损失(各自相对自己的 FP32)

n 和 s 的特征空间不同,不能拿 n 的量化特征去比 s 的。能比的是各自损失了多少: 同一批图、同一个脚本,量化模型(AI Hub 量化器产出的 QDQ 图,本地 ONNX Runtime 执行)对各自的 FP32 ONNX。

A 臂 · backbone:特征误差与任务探针

任务探针在每个模型自己的特征上拟合,三种读法: FP32(探针在 FP32 特征上训、在 FP32 特征上测)、冻结(同一个探针,换成量化特征来测:只有特征在变)、 重训(探针直接在量化特征上训:以后在量化骨干上训的头能拿到的水平)。

8 个抽头块各自的误差

B 臂 · backbone + neck:特征图误差

C 臂 · backbone + neck + Pose 头:检测一致性

NMS(conf 0.25 / IoU 0.7)之后,按 IoU ≥ 0.5 把量化检测和 FP32 检测一一配对。cricket_only 700 张。 这张表是 AI Hub 原样产出的量化模型,含下面的框解码截断;修补后的数字见 修复。

⚠ 发现:Pose 头框解码被截断(n 和 s 都有,已交付的 n 全模型包也有)—— 已找到修复

现象:量化后,P3(stride 8,最小的人)上中心 x 或 y 超过 325 px 的框,坐标被钉在 325.5 px。 在 NMS 之后表现为凭空多出的框(错位的框没被合并)和少量真人漏检,而匹配上的框误差仍然很小 —— 所以此前按「配对率 + 匹配框 px 误差」的验收没有发现它。

机制

根因是量化器的取范围算法。AI Hub 默认 --range_scheme auto = mse_minimizer:按直方图选误差最小的范围,主动截尾。 这个张量的真实范围是 [-0.52, 80.5],大部分值在 40 以下,P3 远端坐标是「尾部」,于是被截在 40.69。

ultralytics 的 dist2bbox 先在网格单位里算框中心 c_xy=(x1y1+x2y2)/2(范围 0–81)和宽高 wh(范围 0–41), 拼接(/model.23/Concat_3)后再乘 stride。QNN 量化器给这个 concat 一个共享编码,取的是 wh 的范围 [-0.59, 40.69],并反向传给了 Div_1(c_xy)。

40.69 网格 × stride 8 = 325.5 px。stride 16/32 的锚点网格坐标最大只有 40/20,不受影响。

对已有结论的修正

  • 「concat 输入幅度比 > 约 3× 才要查」不成立:这个 concat 的幅度比只有约 2×,照样被截断。决定因素是直方图形状(长尾被 MSE 截掉),不是幅度比。v2 那次 8 路拼接的截断很可能是同一机制。
  • 交付包 release/cricket_quant_fullmodel_v1 的 encodings.json 里,/m.23/Div_1、Sub_1、Concat_3 是同一个编码(上沿 40.69)——已交付的 n 全模型有同样的问题。
  • 只影响 C 臂的框。A/B 臂和关键点都不经过这个 concat。

修复:只修补这 3 个张量的编码(已在 S23 验证)

scripts/quant_patch_decode_encoding.py 把量化模型里 Div_1 / Sub_1 / Concat_3 的 Q/DQ 编码改成覆盖 [-0.6, 80.6](uint16,每档 0.0012 网格 ≈ 0.01 px),三者保持同一编码,其余所有编码不动。 输出仍是单一 (1,56,8400),格式不变;打分/门控头、抽头都不受影响;不需要重新量化,只要重新编译。

备选(未采用)

遗留:单一输出共用一个编码,置信度分辨率仍是约 0.0117 一档(约 85 档),对 0.25 这类阈值影响很小。

口径与复现

python scripts/quant_export_onnx.py --variants pooled --weights weights/yolo11s-pose.pt --out results/quant_y11s/onnx
python scripts/quant_export_full_onnx.py --weights weights/yolo11s-pose.pt --out results/quant_y11s/onnx
python scripts/quant_aihub_submit.py --variant {pooled,full_neck,full_pose} --acts 16 \
       --onnx-dir results/quant_y11s/onnx --out results/quant_y11s/aihub --name-prefix y11s_
python scripts/quant_aihub_profile.py --variant <v> --out results/quant_y11s/aihub --only w8a16 \
       --devices "Samsung Galaxy S23" --tag "[r0923]"            # 再加 --extra="--quantize_io" --tag "[qio-r0923]"
python scripts/quant_aihub_profile.py --variant <v> --out results/quant_y11s/aihub --only w8a16 --collect
python scripts/quant_y11s_accuracy.py            # 本地 QDQ vs FP32
python scripts/quant_y11s_ondevice_pose.py       # C 臂端上对拍
python scripts/quant_y11s_build_viewer.py        # → 本页 data.json