OE Agent“天工”新版本发布,请前往论坛顶部查看,欢迎试用并反馈~
查看OE-Skills
专栏算法工具链J6P ConvTranspose3d BPU 适配实践

J6P ConvTranspose3d BPU 适配实践

HuangHui2026-06-29
29
0

J6P 上 ConvTranspose3d 编译落 CPU 的问题分析与等价改写优化实践

1. 背景

在模型适配 J6P 平台的过程中,我们遇到一个典型问题:模型中存在 ConvTranspose3d,ONNX 中该节点可以正常导出,但经过工具链转换和编译后,在编译图中出现了 hbtl_call_native_Conv 节点。
由于 hbtl_ 开头的节点表示运行在 CPU 上,没有落到 BPU,因此该问题会带来明显的性能风险。
本案例围绕一个真实的 ConvTranspose3d 编译落 CPU 问题,分析其原因,并给出两种等价改写方案:
  1. ConvTranspose3d -> Conv3D 1x1x1 + Reshape/Transpose

  2. ConvTranspose3d -> ConvTranspose2D + Reshape/Transpose

最终实测表明,两种方案都可以使算子落到 BPU,但性能差异明显,其中 ConvTranspose2D + Reshape/Transpose 方案性能更优。

2. 问题现象

客户原始 PyTorch 模型中存在如下结构(示例):

客户模型中的配置如下:

也就是说,该模型中包含 3 个 ConvTranspose3d:

输入通道

输出通道

kernel_size

stride

deblock_seg_v2.0

128

64

(5,2,2)

(5,2,2)

deblock_seg_v2.3

64

48

(2,2,2)

(2,2,2)

deblock_seg_v2.6

48

32

(2,2,2)

(2,2,2)


3. 编译图现象:kernel 为什么从 (5,2,2) 变成 1x1x1

在 ONNX 中,原始节点仍然是 ConvTranspose,并且属性中可以看到:

但在编译后的图中,相关节点变成了:

这容易造成误解:是不是工具链把 kernel 改错了?

答案是否定的。

这里并不是 kernel 丢失,而是工具链对一种特殊的 ConvTranspose3d 做了等价 lowering。

该层满足以下条件:

这类 ConvTranspose3d 属于无重叠上采样。每个输入点只负责生成一个固定大小的输出块,不同输入点生成的输出块之间没有重叠累加。

以第一层为例:

每个输入点会生成一个:

大小的空间块。由于 stride 和 kernel 相同,这些空间块之间不会相互重叠。

因此,可以把原始空间展开先折叠到 channel 维度中:

也就是说:

原来需要生成到 D/H/W 空间维度上的内容,先被放到了 channel 维度里,然后再通过 Reshape/Transpose 还原回原来的 5D 输出形态。

建议配图:


4. 为什么最终落到 CPU

J6P BPU 对 3D ConvTranspose 并不是任意形态都支持,而是存在约束。

结合算子支持约束,可以看到关键限制包括:

而当前模型的 3 个 ConvTranspose3d 分别为:

第一层中:

超过了 BPU 对 3D ConvTranspose 中 kernel D 维 [1,2] 的限制。
第二、三层虽然 kernel 是 (2,2,2),但 stride 也是 (2,2,2),不满足:
因此,原始 ConvTranspose3d 不能直接按照 BPU 3D ConvTranspose 算子下发执行。

工具链为了保证模型语义正确,会对该结构做自动 lowering。但自动 lowering 生成的是:

该节点属于 CPU fallback 路径。

这里需要注意一个重要区别:

自动 lowering 是在原始 ConvTranspose3d 已经被判断为无法直接落 BPU 后,由工具链生成的内部 fallback 实现,因此最终表现为 hbtl_call_native_Conv。
而如果我们在 ONNX 层提前手动改写成标准 Conv + Reshape/Transpose 子图,则该子图会重新进入工具链前端、量化、图优化和 BPU 算子匹配流程,可能最终落到 BPU。

5. 方案一:改写为 Conv3D 1x1x1 + Reshape/Transpose

5.1 原理

对于满足以下条件的 ConvTranspose3d:

可以将其等价改写为:

原始形式:

等价改写形式:

该方式与工具链自动 lowering 的数学逻辑类似,但区别在于:手动改写后,ONNX 中已经显式存在标准 Conv 节点,工具链会重新对该标准子图做 BPU 匹配。

5.2 PyTorch 侧修改示例

可以封装一个模块:

客户原始构图处可以替换为:

5.3 ONNX 直接改图脚本

如果暂时不改 PyTorch 源码,也可以在 ONNX 层直接改图。脚本功能是将满足条件的 ConvTranspose3d 改写为:

示例命令:


6. 方案二:改写为 ConvTranspose2D + Reshape/Transpose

6.1 原理

方案二是将 3D 反卷积降维为 2D 反卷积。

原始输入:

先把 D 维并入 batch:

然后使用 2D ConvTranspose 处理 H/W 方向上采样:

输出:

最后再通过 Reshape/Transpose/Reshape 还原为:

以第一层为例:

6.2 PyTorch 侧修改示例

客户原始构图处可以替换为:

6.3 ONNX 直接改图脚本

ONNX 改图脚本会将满足条件的 ConvTranspose3d 改写为:

示例命令:


7. 两种方案的等效性验证

两种改写都必须先验证 ONNX 输出等效性。

示例命令:

对于本案例,修改前后 ONNX 输出验证结果为:

说明修改前后模型输出完全一致,改写没有改变模型语义。


8. 板端性能对比

两种方案都可以使模型完整落到 BPU,但性能并不相同。

板端使用 hrt_model_exec perf 测试结果如下:

方案

平均延迟

FPS

说明

Conv3D 1x1x1 + Reshape/Transpose

36.860 ms

27.065 FPS

可落 BPU,但性能一般

ConvTranspose2D + Reshape/Transpose

23.733 ms

41.972 FPS

可落 BPU,性能更优

从延迟看:

从速度比看:

也就是说,ConvTranspose2D + Reshape/Transpose 方案相比 Conv3D 1x1x1 + Reshape/Transpose 方案约快 1.55 倍,延迟降低约 35.6%。

8.1 为什么 2D 方案更快

虽然两种方式在数学上等价,并且都能落 BPU,但执行形态不同。

Conv3D 1x1x1 方案会将 KD*KH*KW 全部折叠到 channel 维度:

Conv3D 1x1x1 中间 channel

(64, KD=5, KH=2, KW=2)

64*5*2*2 = 1280

(48, KD=2, KH=2, KW=2)

48*2*2*2 = 384

(32, KD=2, KH=2, KW=2)

32*2*2*2 = 256

而 ConvTranspose2D 方案只需要将 KD 暂存到 channel 维度,H/W 方向由 2D ConvTranspose 直接展开:

ConvTranspose2D 中间 channel

(64, KD=5)

64*5 = 320

(48, KD=2)

48*2 = 96

(32, KD=2)

32*2 = 64

因此,2D 方案具有以下优势:

  1. 核心计算是 2D ConvTranspose,更接近 BPU 常规高效路径。

  2. 中间 channel 更小,数据搬运和 layout 压力更低。

  3. H/W 方向直接由 2D 反卷积展开,不需要把 KH/KW 也折叠到 channel 后再搬回空间。

  4. 整体 reshape/transpose 的数据重排压力更小。


9. 推荐结论

针对当前模型,建议采用:

作为最终部署改写方案。

原因如下:

  1. 该方案与原始 ConvTranspose3d 数学等价。
  2. ONNX 修改前后输出完全一致,等效性验证通过。

  3. 编译后所有算子可以落到 BPU,规避原始 CPU fallback。

  4. 板端实测性能明显优于 Conv3D 1x1x1 方案。
  5. 该方案更符合 BPU 对 2D 卷积/反卷积的高效执行形态。

Conv3D 1x1x1 + Reshape/Transpose 方案可以作为备选方案或用于验证工具链自动 lowering 逻辑,但不建议作为最终性能优先方案。


本文章用到的脚本:https://pan.baidu.com/s/1oCE-RmT7LMI2W9sPMERqGA?pwd=8erf
算法工具链
技术深度解析官方教程征程6
评论0
0/600