Files
blackbird/docs/reports/actpred_hailo_preprocess_optimization_plan_20260817.md
T
qing 8a5162c912 docs: 修订 Hailo 预处理优化方案——查表法实测证伪,改合并乘加为主
原型实测:numpy 高级索引查表无加速(26.6 vs 28.5ms),预期 18-20ms 不成立。
主方案改为合并系数乘加(-5ms,0 LSB 等价),cv2.LUT 列为可选激进路径;
预期收益修正为 ~47ms(纯 numpy)/ ~25-30ms(cv2)。
2026-08-17 14:48:26 +08:00

7.4 KiB
Raw Blame History

actpred Hailo 推理预处理优化方案(修订版):合并归一化与量化

文档类型:开发方案 模块:actpred(部署链 hailo_infer.py) 作者:自动生成 日期:2026-08-17(原型实测修订) 前置:actpred_inference_speed_benchmark_20260813.md(速度基准)、 2026-08-17 新 640×480 数据集重测(本方案 §2 数据)

⚠️ 修订说明(2026-08-17) 初版以"numpy 查表法"为核心方案,预期端到端降至 18-20ms。 原型实测(§3.4)证明 numpy 高级索引查表在 Pi5 上无加速(fancy indexing 的 gather 开销与逐元素 float 运算相当),该预期不成立。 本修订版改以合并系数乘加(纯 numpy,-5ms)为主方案, cv2.LUT(需引入 opencv 依赖)列为可选的激进路径。


1. 背景与动机

2026-08-17 用新录制的 640×480 数据集(免 resize 训练样本)重测 HEF 推理速度:

阶段 640×480 输入 224×224 输入(旧数据)
端到端 predict 52.4 ms 60.0 ms
预处理 35.9 ms 43.1 ms
NPU run 12.5 ms 12.9 ms
后处理 0.6 ms 0.6 ms

输入尺寸已与模型一致(resize 变 no-op),预处理仍是端到端延迟的主导项(68%)。 旧基准报告(2026-08-13)已记录同类问题:1080p 采集的预处理缩放约 45ms 主导端到端延迟, 当时通过把默认分辨率改回 640×480 缓解;本次分析证明 640×480 下 Python 端预处理 仍有约 36ms 的优化空间——但空间比初版估计小(见 §3.4 实测)。

2. 瓶颈分析(100 次均值分段计时)

子步骤 耗时 说明
_frame_float(3 帧) 27.2 ms PIL fromarray/convert + resize(同尺寸 no-op 仍有开销)+ (x/255-mean)/std float 运算(92 万像素×3 通道)
create_bindings + 帧量化绑定 9.1 ms _bind_frames:float 归一化帧 → round(x/q.scale+q.zp) 再量化回 uint8
history/pred 绑定 0.07 ms 可忽略

关键冗余:当前流程先做 float 归一化(_frame_float),随后又量化回 uint8(_bind_frames)—— 中间 float 帧是纯中间态,归一化与量化可以合并为一步。

3. 优化方案

3.1 主方案:合并系数乘加(纯 numpy,无新依赖)

HEF 帧输入是 uint8 量化值:q = round(((x/255 - mean)/std) / q_scale + q_zp)。 把 4 步运算合并为一次乘加(系数预计算,按通道):

q = round(x * A[c] + B[c])
A[c] = 1 / (255 * std[c] * q_scale)
B[c] = -mean[c] / (std[c] * q_scale) + q_zp
  • 现流程 4 次全数组运算(/255、-mean、/std、/q_scale+q_zp)→ 1 次乘加
  • 数值完全等价(实测 max_diff = 0 LSB,见 §3.4)
  • 直通模式(_normalize_frames=False)同理:A=1/q_scale, B=q_zp

3.2 附带优化(与主方案正交)

  1. 免 PIL 路径:输入已是 np.ndarray 时直接运算,跳过 Image.fromarray/convert
  2. resize no-op 跳过:image.size == (W,H) 时不调用 resize()
  3. 缓冲复用:np.multiply/np.add 带 out= 参数,减少临时数组分配

3.3 可选激进路径:cv2.LUT(需引入 opencv 依赖)

cv2.LUT(img, lut) 是 C 实现的 uint8→uint8 查表,单次遍历,无 float 中间态—— 理论上可把帧预处理降到 ~3ms。但当前环境 cv2 未安装;Pi5 arm64 上 opencv-python 安装包较重,是否引入需权衡(见 §7)。

3.4 原型实测评估(2026-08-17,100 次均值,640×480×3 帧)

方案 3 帧耗时 对比现 float 28.5ms 数值差异
现 float 路径 28.5 ms 基线 —
numpy 高级索引查表 26.6 ms 无加速(1.0x) 0 LSB
合并系数乘加 23.2 ms 快 19%(-5ms) 0 LSB
cv2.LUT —(cv2 未安装) 理论 ~3ms —

结论:

  • numpy fancy indexing 的 gather 开销与逐元素 float 运算相当,查表法无收益
  • 纯 numpy 可达的最佳是合并乘加(-5ms),受限于 92 万像素 float32 数组的内存吞吐
  • 要数量级提升只能靠 cv2.LUT(C 查表),代价是引入 opencv 依赖

3.5 预期收益(修订)

路径 帧预处理 端到端 predict 依赖
现状 35.9 ms 52.4 ms —
主方案(合并乘加) ~31 ms ~47 ms 无
激进路径(cv2.LUT) ~13 ms ~25-30 ms opencv-python

闭环 10Hz(100ms 预算)下,主方案占用率 52%→47%;激进路径降至 ~28%,为 20Hz 留出空间。

4. 一致性约束(关键)

  • 训练侧不动:dataset.py 的 Resize+ToTensor+Normalize 保持 float 路径—— 合并乘加只是推理侧对"归一化+量化"的合并实现,数值等价(实测 0 LSB)
  • 三后端口径:predict.py(PyTorch,float 输入不量化)、onnx_infer.py(ORT,float) 不受影响;优化仅作用于 hailo_infer.py 的 uint8 量化输入路径
  • 帧输入判定不变:_normalize_frames(量化网格负零偏判定)决定走 归一化还是直通,逻辑与现状一致
  • 旧 5 输入 / 旧版单图像 HEF 布局同样适用(按各自 quant_info 算系数)

5. 实施步骤

步骤 内容 产出
1 hailo_infer.py:加载时按 quant_info 预计算通道系数 A/B(归一化/直通两种) 系数构建 + 单测(与 float 路径逐点比对 = 0 LSB)
2 _frame_float / _bind_frames 改为合并乘加路径(ndarray 直通、免 PIL、resize no-op 跳过) 推理链路改造 + 单测(逐帧输出比对)
3 NPU 实测:新 640×480 数据集 100 次均值 + 分段计时 基准对比(52.4 → ~47ms)
4 _npu_test.py 4 角 + HEF vs torch 对比复验 量化误差不回归(≤1°)
5 全套单测(TestHailoPredictor 等) 188 tests OK
6(可选) 引入 cv2.LUT 路径(配置开关,默认关闭) 激进路径 + 依赖权衡记录

6. 风险与对策

风险 对策
乘加系数浮点误差 实测 0 LSB(系数用 float32 与现路径同精度);NPU 输出 vs torch 误差预算不变(≤1°)
主方案收益有限(仅 -5ms) 如实记录(§3.4/§3.5);若需数量级提升走 cv2.LUT,否则可接受现状不优化
cv2 依赖体积/安装 激进路径默认关闭、配置开关;Pi arm64 opencv 安装成本单独评估
未来输入尺寸变更 系数与尺寸无关(逐像素映射);resize 仍需走 PIL,仅影响附带优化 2

7. 决策记录

  • 查表法(初版核心)实测证伪:numpy 高级索引 gather 在 Pi5 上与逐元素 float 运算同速(26.6 vs 28.5ms),"查表降为内存索引即快"的假设不成立——教训: 性能方案须原型实测,不能凭直觉估计
  • 主方案选合并乘加:纯 numpy 无新依赖、数值 0 LSB 等价、实施简单;-5ms 收益 虽小但零风险。激进 cv2.LUT 留作可选(需权衡 opencv 依赖)
  • 是否优化的取舍:现状 52.4ms 已满足 10Hz 闭环;若控制率需求不上探 20Hz, 可仅做主方案(低成本)甚至不优化。激进路径仅在明确需要更高控制率时启用
  • 训练侧不改:训练用 float 归一化(增强/数值精度),推理侧合并实现——两侧 口径在"归一化值"层面一致,量化差异由部署链固有预算吸收(HEF vs torch 0.5° 余量充足)