专栏算法工具链J6m 芯片上 GridSample 双 int16 输入方案与性能优化实践

J6m 芯片上 GridSample 双 int16 输入方案与性能优化实践

Huanghui2025-09-02
141
0

一、背景与问题提出

在 J6m 芯片平台,grid_sample 算子原生只支持 int8 输入
然而,许多模型在训练时对特征输入精度要求较高,int8 精度不足以支撑推理效果,必须要 int16 输入 才能保证精度。

如果不解决:

  • 模型精度显著下降;

  • 结果分布与训练预期不一致;

  • 后续部署和优化步骤复杂化。

因此,需要找到一种方法在算子限制下支持 int16 输入。


二、算子限制与拆分方案

2.1 拆分思路

  • 将 int16 数据拆分为 高位和低位 两份 int8 数据;
  • 分别输入两个 int8 grid_sample;
  • 通过 Add 合并高低位输出,得到最终 int16 精度结果。

2.2 拆分前后对比

方案

Sub

GridSample

Add

原版

/

1

/

拆分

1

2

1

表1 单个 grid_sample 算子在原版与拆分方案下的算子数量对比

问题总结:

  • 算子数量显著增加;

  • 芯片需加载两份数据 → 内存带宽压力翻倍
  • 当输入数据较大时,内存屏障成为性能瓶颈

三、QAT 双 int16 支持方案

为了避免在推理阶段再做复杂适配,可以在 量化训练(QAT)阶段 开启双 int16 支持:
  • 插件版本要求:horizon_plugin_pytorch ≥ 2.6.7
  • 修改方法:在
    horizon_plugin_pytorch/quantization/fx/graph_optimizers.py
    Description

原Int8版 与 双 Int16 图结构对比

Int8原版

双int16

这样一来,从训练到部署全链路都能保持一致的 int16 输入支持,避免了精度损失。


四、性能优化实践

4.1 实验数据维度信息

Description

4.2 初始实现

在最初实现中,采用 for 循环多次处理输入数据
  • 每次循环都会构建新的计算图;

  • 数据被重复加载多次,算子冗余严重;

  • 性能明显下降。

图3 初始实现计算图
图3 初始实现下的计算图结构

4.3 优化思路与方法

  • 去除 for 循环,一次性处理所有输入数据;
  • 避免冗余计算图与重复 IO;

  • 充分利用芯片算力,减少数据搬运开销;

  • 提升算子融合效果,降低整体延迟。

图4 优化后计算图
图4 优化后的计算图结构

五、性能对比实验(OE 3.2.0 + HBDK 4.4.5)

我们在 bilinear 与 nearest 两种插值模式下进行了测试:

模式

初始性能

优化性能

带宽消耗

bilinear

Description
Description
Description 优化后--> Description

nearest

Description
Description
Description 优化后--> Description
表2 在不同插值模式下的性能与带宽对比

实验表明:

  • 优化后整体性能有明显提升;

  • 内存带宽占用下降,系统稳定性更强;

  • 在大规模输入场景下,优势更加显著。


六、更多思考与经验

  1. 算子拆分是无奈之举:虽然解决了精度问题,但不可避免地增加了算力和带宽开销。
  2. 图优化与数据优化必不可少:通过消除冗余循环、减少数据搬运,可以在一定程度上抵消性能损失。
  3. 编译器优化差异:不同 HBDK 版本对计算图优化策略不同,建议多版本尝试,寻找最佳解。
  4. 全链路一致性:在 QAT 阶段开启双 int16,可以从训练到部署保持一致性,减少调试时间和适配风险。
  5. 工程经验总结:算子拆分 + 编译器优化 + 带宽控制,三者结合才能实现真正的可落地方案。

七、总结

  • 在 J6m 平台上,grid_sample 原生只支持 int8;
  • 通过算子拆分,可以实现双 int16 输入支持;

  • 在 QAT 阶段开启双 int16,能有效避免精度损失;

  • 通过优化计算图结构,性能损失得到弥补;

  • 实践表明,性能优化与带宽控制相结合,才能真正发挥 J6m 芯片的潜力。

希望本文的分享能为正在适配和优化 J6m 平台的开发者提供一些启发。
欢迎大家在社区留言交流更多经验!
算法工具链
征程6官方教程
评论0
0/1000