1. 目的本文单独记录当前pvn3d-dev容器内的 ONNX 转换流程、测试方法以及已经确认的导出问题。这篇文档只关注两件事如何把当前deploy/里的可导出子图转成 ONNX导出过程中实际遇到了什么问题如何判断下一步怎么处理不在本文展开的内容TensorRT 安装容器创建PointNet2 插件化这里先说明一个边界当前可以用 ONNX Runtime 测试整条 PVN3D 推理链但不是“整网纯 ONNX Runtime”当前可行的是“ONNX Runtime PointNet2 Native CUDA”的混合推理流程原因很简单rgb_backbone可以走 ONNX Runtimefusion_head可以走 ONNX RuntimePointNet2 仍然保留 PyTorch CUDA 扩展相关文档pvn3d_tensorrt_env_setup_guide.mdtensorrt_install_in_pvn3d_dev.md2. 当前容器环境本文基于当前实测容器环境编写容器pvn3d-dev仓库路径/workspace/workflow/self/PVN3DConda 环境pvn3dPython3.8.20PyTorch1.10.0cu113CUDA Toolkit11.3ONNX1.14.1ONNX Runtime1.16.3TensorRT Python8.6.1trtexec/opt/tensorrt/TensorRT-8.6.1.6/bin/trtexec当前环境已经满足 ONNX 导出和 ONNX 基础校验的前提。3. 当前导出目标当前deploy/scripts/export_onnx.py只导出两个子图rgb_backbonefusion_headPointNet2 不在 ONNX 导出范围内原因是它依赖自定义 CUDA 扩展。3.1 为什么不是把原始 graph 一次性导成一个 ONNX当前 PVN3D 的推理路径不是一个“纯标准算子”的网络而是混合结构RGB 主干网络PointNet2 点云分支RGB 与点云融合头这里真正的问题不在 CNN也不在最终的融合头而在 PointNet2。PointNet2 当前依赖本仓库里的自定义 CUDA 扩展包括这类算子furthest_point_samplegather_pointsball_querygroup_pointsthree_nnthree_interpolate这些算子当前是通过本地编译的扩展模块执行的不是标准 ONNX 算子。因此如果直接把整个 PVN3D 原始 graph 一次性导出会立刻遇到两个工程问题PyTorch 到 ONNX 这一层无法稳定表达 PointNet2 的自定义 CUDA 算子即使勉强导出后面的 TensorRT 也不能直接解析这些自定义算子除非单独开发 TensorRT plugin这就是当前部署路线不把整网直接导成单个 ONNX 的原因。3.2 为什么最终变成“2 个 ONNX 1 段 CUDA”当前拆分后的部署结构是rgb_backbone.onnxPointNet2 Native CUDAfusion_head.onnx也就是图像分支导成 ONNX点云分支继续保留原生 CUDA融合与预测头再导成 ONNX这样拆的目的很明确把能稳定导出的部分先导出来把最难处理的 PointNet2 自定义算子留在现有 CUDA 路径先跑通第一阶段部署再决定是否值得做 TensorRT plugin换句话说当前不是“原始 graph 被随意拆开”而是按算子可导出性做的工程拆分。3.3 为什么 PointNet2 继续保留 CUDA而不是也转成 ONNX原因不是 PointNet2 不重要恰恰相反是因为它最特殊。当前 PointNet2 这一路已经在容器里验证通过扩展模块能导入CUDA 可用原生推理链可工作这里有一个验证细节需要单独说明当前容器里验证_ext时应先import torch如果直接裸导入_ext可能报ImportError: libc10.so: cannot open shared object file这属于 PyTorch 共享库的动态加载顺序问题不表示扩展没有编译成功在这种前提下继续保留 PointNet2 的原生 CUDA 路径收益很直接不需要先做 ONNX 自定义算子适配不需要先做 TensorRT plugin可以先让部署链往前走这条路线的代价也很明确最终不是单一 engine推理链会变成混合执行但对当前阶段来说这是合理代价因为它显著降低了第一版部署风险。相关脚本export_onnx.pymodel_wrappers.pyrun_linemod_hybrid_ort.py4. 进入环境dockerexec-itpvn3d-devbashsource/opt/conda/etc/profile.d/conda.sh conda activate pvn3dcd/workspace/workflow/self/PVN3D建议先做一轮快速检查python--versionpython -PY import torch import onnx import onnxruntime as ort print(torch:, torch.__version__) print(torch_cuda:, torch.version.cuda) print(cuda_available:, torch.cuda.is_available()) print(onnx:, onnx.__version__) print(onnxruntime:, ort.__version__) PY5. ONNX 导出脚本说明导出脚本在export_onnx.py当前关键参数--checkpoint--output-dir--target--num-classes--num-points--height--width--device--opset当前脚本已经调整过默认opset默认值13原因不是偏好问题而是当前容器里的torch 1.10.0不支持opset 17导出。6. 推荐导出命令当前已经验证通过的一组导出尺寸是height480width624下面的命令统一按这组尺寸记录。之前的480x640是问题定位阶段的测试尺寸不再作为当前推荐命令。6.0 用于 LINEMODape混合测试的推荐导出命令如果后续要用 ONNX Runtime 测试 LINEMODape的整条混合推理链建议先导出与ape权重匹配的 ONNXpython deploy/scripts/export_onnx.py\--checkpointweights/ape_pvn3d_best.pth.tar\--output-dir deploy/models/onnx_ape\--targetall\--num-classes2\--num-points4096\--height480\--width624\--devicecuda\--opset13这里不要继续使用ycb_pvn3d_best.pth.tar导出的 ONNX 去测 LINEMODape。原因YCB 是22类LINEMOD 单类配置是2类checkpoint 和输出头维度必须一致6.1 先只测rgb_backbonepython deploy/scripts/export_onnx.py\--checkpointweights/ycb_pvn3d_best.pth.tar\--output-dir deploy/models/onnx\--targetrgb_backbone\--num-classes22\--num-points4096\--height480\--width624\--devicecuda\--opset136.2 只测fusion_headpython deploy/scripts/export_onnx.py\--checkpointweights/ycb_pvn3d_best.pth.tar\--output-dir deploy/models/onnx\--targetfusion_head\--num-classes22\--num-points4096\--height480\--width624\--devicecuda\--opset136.3 全量导出python deploy/scripts/export_onnx.py\--checkpointweights/ycb_pvn3d_best.pth.tar\--output-dir deploy/models/onnx\--targetall\--num-classes22\--num-points4096\--height480\--width624\--devicecuda\--opset13建议不要一开始就跑all。先拆开测试定位更快。7. 导出后检查如果导出成功先检查输出目录ls-lhdeploy/models/onnxcatdeploy/models/onnx/export_manifest.json预期文件rgb_backbone.onnxfusion_head.onnxexport_manifest.json8. ONNX 基础测试8.1 检查模型能否被 ONNX 解析python -PY import onnx model onnx.load(deploy/models/onnx/rgb_backbone.onnx) onnx.checker.check_model(model) print(rgb_backbone.onnx ok) PY如果是fusion_head.onnx把文件名替换掉即可。8.2 用 ONNX Runtime 测试能否创建会话python -PY import onnxruntime as ort sess ort.InferenceSession( deploy/models/onnx/rgb_backbone.onnx, providers[CPUExecutionProvider], ) print(inputs:, [i.name for i in sess.get_inputs()]) print(outputs:, [o.name for o in sess.get_outputs()]) PY这里先用 CPUExecutionProvider。原因当前目的只是检查 ONNX 图是否可加载不把 ORT GPU 执行问题混进导出验证阶段9. 混合推理全流程测试9.1 当前是否可以用 ONNX Runtime 测整条 PVN3D 流程可以但要明确是“混合推理”。当前能跑通的路径是rgb_backbone.onnx由 ONNX Runtime 执行PointNet2 由原生 PyTorch CUDA 扩展执行fusion_head.onnx由 ONNX Runtime 执行最后的 LINEMOD pose 求解仍走现有 PyTorch / numpy 逻辑这不是整网纯 ORT但已经覆盖了当前部署拆分后的主流程。9.2 测试脚本位置脚本放在run_linemod_hybrid_ort.py脚本做的事情读取一帧 LINEMOD 样本把 RGB 从640裁成624把点数从数据集原始的12288重采样到4096用 ONNX Runtime 跑rgb_backbone用原生 PointNet2 CUDA 跑点云特征用 ONNX Runtime 跑fusion_head可选对比整模型 PyTorch 输出调用 LINEMOD pose solver输出 pose、ADD、ADD-S9.3 运行命令先保证你已经导出了ape对应的 ONNXpython deploy/scripts/export_onnx.py\--checkpointweights/ape_pvn3d_best.pth.tar\--output-dir deploy/models/onnx_ape\--targetall\--num-classes2\--num-points4096\--height480\--width624\--devicecuda\--opset13然后执行测试脚本python onnx_test/run_linemod_hybrid_ort.py\--clsape\--checkpointweights/ape_pvn3d_best.pth.tar\--rgb-onnx deploy/models/onnx_ape/rgb_backbone.onnx\--fusion-onnx deploy/models/onnx_ape/fusion_head.onnx\--sample-index0\--num-points4096\--height480\--width624\--crop-left8\--outputonnx_test/outputs/linemod_ape_hybrid_summary.json9.4 输出内容脚本会输出并保存一份 JSON总结以下信息使用的 checkpoint使用的 ONNX 文件ONNX Runtime providers输入 shape点云 shape预测 poseADDADD-S如果未加--skip-torch-compare还会给出混合推理和整模型 PyTorch 结果的误差统计9.5 为什么脚本里还要裁剪和重采样这是当前导出配置决定的不是额外的设计复杂度。原因有两个当前已验证通过的 ONNX 导出尺寸是480x624LINEMOD 数据集当前默认采样点数是12288而导出使用的是4096所以脚本里必须做两步对齐RGB:640 - 624points:12288 - 40969.6 为什么需要crop_leftcrop_left不是多余参数它是用来定义 RGB 裁剪窗口起点的。当前背景是LINEMOD 原始 RGB 宽度是640当前导出的 ONNX 输入宽度是624所以测试脚本必须先把原图裁成624宽才能送进rgb_backbone.onnx。但这里不能只裁 RGB不处理choose。原因choose记录的是采样点在原始图像上的像素索引一旦 RGB 被裁剪像素坐标系就变了脚本必须知道裁剪窗口是从哪一列开始的才能把choose从原始640宽图正确映射到裁剪后的624宽图这就是crop_left的作用定义 RGB 裁剪起点让choose的索引同步完成重映射当前默认值是--crop-left8原因原始宽度640导出宽度624两者差值是16左边裁8、右边等价裁8也就是居中裁剪。如果以后导出宽度改了crop_left也要跟着一起调整。它不是固定常量而是和导出分辨率绑定的参数。10. 当前已经确认的问题10.1 问题一默认opset 17导致导出直接失败实际报错ValueError: Unsupported ONNX opset version: 17原因原脚本默认把--opset设成了17当前容器里的torch 1.10.0cu113不支持这个导出版本实际检查结果stable opset:7到13main opset:14解决当前环境统一使用--opset 13脚本默认值已经改成13相关代码位置export_onnx.py:81export_onnx.py:4210.2 问题二torch.onnx.symbolic_helper访问方式在torch 1.10下不稳在把导出脚本默认opset改为13之后又遇到过一轮脚本自身报错AttributeError: module torch.onnx has no attribute symbolic_helper原因在当前torch 1.10.0cu113环境里import torch.onnx.symbolic_helper as sh是可以成功的但torch.onnx.symbolic_helper不一定会自动挂成torch.onnx的属性也就是说这两种写法在当前环境里不是等价的错误写法torch.onnx.symbolic_helper正确写法importtorch.onnx.symbolic_helperasonnx_symbolic_helper解决已经把 export_onnx.py 改成显式导入torch.onnx.symbolic_helper后续 opset 检查统一使用局部模块变量不再通过torch.onnx.symbolic_helper访问这属于脚本兼容性问题不是模型结构问题。10.3 问题三rgb_backbone导出卡在adaptive_avg_pool2d实际报错RuntimeError: Unsupported: ONNX export of operator adaptive_avg_pool2d, since output size is not factor of input size这是当前真正的 ONNX 导出阻塞点。原因来自 PSPNet 的 PSP 模块。相关代码pspnet.py:20这里使用了AdaptiveAvgPool2d(output_size(1,1))AdaptiveAvgPool2d(output_size(2,2))AdaptiveAvgPool2d(output_size(3,3))AdaptiveAvgPool2d(output_size(6,6))前一轮失败时使用的测试输入是height480width640在torch 1.10的 ONNX 导出器里如果特征图尺寸相对于这些目标池化尺寸不是整倍数导出就会失败。这不是 ONNX 包没装好也不是 TensorRT 的问题而是 PyTorch 导出器本身的限制。10.4 问题四PointNet2 扩展编译成功但直接导入_ext报libc10.so当前容器里还确认过一个容易误判的问题。现象python setup.py build_ext --inplace已经成功_ext.cpython-38-*.so已经生成直接执行下面的验证代码时失败python -PY from pvn3d.lib.pointnet2_utils import _ext print(_ext) PY报错ImportError: libc10.so: cannot open shared object file: No such file or directory原因_ext.so依赖 PyTorch 的共享库例如libc10.so这些库在当前 conda 环境的torch/lib目录下如果当前 Python 进程还没有先加载torch直接导入_ext时动态链接器可能找不到这些依赖这说明问题不在编译结果本身而在动态库加载顺序。解决python -PY import torch from pvn3d.lib.pointnet2_utils import _ext print(_ext) PY这个顺序已经在pvn3d-dev容器里验证通过。结论build_ext --inplace成功说明扩展已经编译出来如果裸导入_ext报libc10.so优先先import torch这不是 ONNX 导出 bug而是当前环境下 PointNet2 扩展验证时的依赖加载问题10.5 问题五Invalid magic number实际上是 checkpoint 保存格式不同在用 LINEMODape权重导出 ONNX 时出现过下面的报错RuntimeError: Invalid magic number; corrupt file?对应命令python deploy/scripts/export_onnx.py\--checkpointweights/ape_pvn3d_best.pth.tar\--output-dir deploy/models/onnx_ape\--targetall\--num-classes2\--num-points4096\--height480\--width624\--devicecuda\--opset13这个报错看起来像文件损坏但当前容器里实际确认的原因不是损坏而是保存格式不同ycb_pvn3d_best.pth.tar可以直接用torch.loadape_pvn3d_best.pth.tar是普通picklecheckpoint其中仍然包含model_state所以问题不在权重内容而在加载器一开始只支持torch.load。解决已经更新 model_wrappers.py现在会先尝试torch.load如果遇到Invalid magic number自动回退到pickle.load结论Invalid magic number在这里不等于 checkpoint 损坏它表示当前 checkpoint 不是torch.save格式当前导出脚本已经兼容这两种 checkpoint10.6 问题六混合测试脚本找不到obj_01.ply在运行下面的混合测试命令时python onnx_test/run_linemod_hybrid_ort.py\--clsape\--checkpointweights/ape_pvn3d_best.pth.tar\--rgb-onnx deploy/models/onnx_ape/rgb_backbone.onnx\--fusion-onnx deploy/models/onnx_ape/fusion_head.onnx\--sample-index0\--num-points4096\--height480\--width624\--crop-left8\--outputonnx_test/outputs/linemod_ape_hybrid_summary.json出现过下面的报错FileNotFoundError: [Errno 2] No such file or directory: datasets/linemod/Linemod_preprocessed/models/obj_01.ply原因不是模型文件不存在。当前容器里实际目录是存在的/workspace/workflow/self/PVN3D/pvn3d/datasets/linemod/Linemod_preprocessed/models/obj_01.ply真正原因是basic_utils.py 里某些 LINEMOD 模型路径仍然写成了相对路径相对路径是按pvn3d/目录为当前工作目录来解析的但onnx_test/run_linemod_hybrid_ort.py一开始是在仓库根目录执行也就是说这是 legacy 工具代码里的路径假设问题不是数据集缺失。解决已经更新 run_linemod_hybrid_ort.py脚本启动后会自动切换当前工作目录到pvn3d/这样datasets/linemod/...这类相对路径就能正确解析结论报obj_01.ply找不到时不要先怀疑数据集没准备好先检查是不是工作目录不符合basic_utils.py的相对路径假设当前测试脚本已经做了自动修正11. 当前问题的处理建议11.1 先固定opset 13这是当前环境下的硬约束不要再尝试17。11.2 确保使用更新后的导出脚本当前 export_onnx.py 已经做了两处修正默认opset从17改为13symbolic_helper改成显式导入方式如果你本地容器里运行的还是旧版本脚本先同步这份脚本再测试。11.3 PointNet2 扩展验证时先导入torch如果需要验证 PointNet2 扩展是否可用使用下面的顺序python -PY import torch from pvn3d.lib.pointnet2_utils import _ext print(_ext) PY不要把下面这种“裸导入_ext”当成唯一验证方式python -PY from pvn3d.lib.pointnet2_utils import _ext print(_ext) PY在当前容器里这种写法可能因为libc10.so的动态加载顺序而失败。11.4 使用更新后的 checkpoint 加载逻辑如果导出 LINEMODape权重时遇到Invalid magic number; corrupt file?先不要判断为权重损坏。当前应先确认使用的是更新后的model_wrappers.py现在它已经兼容torch.savecheckpointpicklecheckpoint11.5 混合测试脚本应使用更新后的版本如果运行onnx_test/run_linemod_hybrid_ort.py时出现FileNotFoundError: ... obj_01.ply先确认使用的是更新后的脚本版本。当前脚本已经自动切到pvn3d/工作目录避免basic_utils.py的相对路径解析失败。11.6 优先尝试更适合 PSP 导出的固定分辨率当前已经验证通过的尺寸是--height480--width624原因PSP 模块的池化目标是1, 2, 3, 6这类结构更适合特征图宽高都能被1,2,3,6整除的输入480和624这一组尺寸已经在当前容器里完成过 ONNX 导出相比480x640这一组更容易满足 PSP pooling 的导出约束对当前容器这一步不再是“尝试”而是目前已经跑通的推荐值。11.7 如果尺寸不能改就做导出专用改写如果部署输入必须固定为480x640那就不要继续硬试默认 PSP 实现了。更直接的做法是为 ONNX 导出单独改写PSPModule把AdaptiveAvgPool2d换成更容易被torch 1.10导出的显式 pooling 方案这条路需要改模型代码但从工程上比升级整套 PyTorch 环境更稳。11.8 不建议优先升级 PyTorch原因会影响当前稳定的torch 1.10 cu113会影响 PointNet2 扩展会把问题从“局部导出兼容性”扩大成“整套训练/推理环境迁移”12. 当前建议的测试顺序建议按下面顺序执行python deploy/scripts/export_onnx.py --help用apecheckpoint 导出deploy/models/onnx_ape只测试fusion_head再测试rgb_backbonergb_backbone导出失败时先改输入尺寸导出成功后用onnx.checker检查再用 ONNX Runtime 建立会话再跑onnx_test/run_linemod_hybrid_ort.py最后再进入 TensorRT engine 构建这样做的原因fusion_head不包含 PSPNet 的自适应池化问题先拆开验证能更快判断问题到底在 RGB backbone 还是 fusion head13. 当前状态结论截至目前ONNX 导出与测试状态可以概括成下面几句当前可以测试整条 PVN3D 主流程但方式是“混合推理”不是整网纯 ORTrgb_backbone在480x640下主要卡在 PSP 模块的AdaptiveAvgPool2d导出限制在480x624下当前容器已经成功导出rgb_backbone.onnx和fusion_head.onnxPointNet2 继续保留原生 CUDA是当前这条测试链成立的前提当前阻塞点已经不再是 ONNX 缺包而是后续要继续验证混合推理结果与 TensorRT engine 构建补一条结构层面的结论当前部署路径本来就不是“整网一个 ONNX”而是“两个 ONNX PointNet2 原生 CUDA”下一步最合理的动作是用ape权重重新导出匹配 LINEMOD 的 ONNX跑onnx_test/run_linemod_hybrid_ort.py看混合推理和整模型 PyTorch 的误差再决定是否继续推进 TensorRT engine 构建