当前位置: 首页 > news >正文

PVN3D Custom ONNX Op / TensorRT Plugin 设计说明

1. 目标

本文用于给 PVN3D 的PointNet2自定义 CUDA 算子定义一套可落地的部署侧设计,目标是:

  • 支持导出完整ONNX
  • 支持构建完整TensorRT engine
  • 部署运行时不再依赖PyTorchpvn3d.lib.pointnet2_utils._ext

本文不是泛泛而谈的 PointNet2 设计文档,而是直接结合当前仓库里的真实实现:

  • Python 封装:
    • pointnet2_utils.py
    • pointnet2_modules.py
  • C++/CUDA 扩展:
    • bindings.cpp
    • sampling.cpp
    • sampling_gpu.cu
    • ball_query.cpp
    • ball_query_gpu.cu
    • group_points.cpp
    • group_points_gpu.cu
    • interpolate.cpp
    • interpolate_gpu.cu

2. 版本基线

本文对应的部署基线沿用当前容器文档中的已核验版本:

  • Python3.8.20
  • PyTorch1.10.0+cu113
  • ONNX1.14.1
  • ONNX Runtime1.16.3
  • TensorRT8.6.1

这里的ONNX -> TRT默认指:

  • TensorRT8.6.1自带 ONNX parser
  • trtexec或 TensorRT builder API

而不是依赖单独的onnx-tensorrtPython 包。

3. 现有 CUDA 扩展暴露出的算子集合

从 bindings.cpp 可以确认,当前_ext实际导出的是:

  • gather_points
  • gather_points_grad
  • furthest_point_sampling
  • three_nn
  • three_interpolate
  • three_interpolate_grad
  • ball_query
  • group_points
  • group_points_grad

对部署前向链真正有意义的是:

  • furthest_point_sampling
  • gather_points
  • ball_query
  • group_points
  • three_nn
  • three_interpolate

*_grad相关接口只服务训练反向,对部署态完整 ONNX / TRT engine 不必实现。

4. 场景描述:这些算子在 PVN3D 里到底干什么

4.1 场景 A:Set Abstraction 路径

对应模块:

  • pointnet2_modules.py

PointnetSAModuleMSG.forward()里,PointNet2 做的是:

  1. 从原始点云xyz[B,N,3]中选出npoint个中心点
  2. 以这些中心点为球心,在半径radius内搜邻域点
  3. 对每个中心点收集固定数量nsample的邻域索引
  4. 用这些索引把xyzfeatures分组为局部 patch
  5. 对局部 patch 做 MLP 和池化

也就是说,这条链不是普通 CNN 的卷积 receptive field,而是点云上的“显式邻域构造”:

  1. furthest_point_sample
  2. gather_operation
  3. ball_query
  4. grouping_operation

这是第一类场景。

4.2 场景 B:Feature Propagation 路径

对应模块:

  • pointnet2_modules.py

PointnetFPModule.forward()里,PointNet2 做的是:

  1. 对稀疏点集和稠密点集做 3 近邻搜索
  2. 计算三近邻的距离倒数权重
  3. 按三近邻索引把稀疏特征插值回稠密点集

对应链路是:

  1. three_nn
  2. three_interpolate

这是第二类场景。

4.3 为什么这两类场景必须单独描述

因为它们决定了 custom op 的接口边界:

  • SA路径主要是“离散索引生成 + batched gather”
  • FP路径主要是“近邻搜索 + 加权插值”

如果不先按场景分开,后面容易把 plugin 设计做成一堆彼此耦合、难以调试的杂合层。

5. 从 CUDA 源码提炼出来的真实语义

这部分非常关键,因为 custom op 的行为必须和现有 CUDA kernel 保持一致。

5.1furthest_point_sampling

从 sampling_gpu.cu 可以确认:

  • 输入是dataset[b, n, 3]
  • 输出是idxs[b, m]
  • 首个采样点固定从old = 0开始
  • temp[b, n]初始化为大数1e10
  • 迭代过程中使用“当前点到已选点集合的最小距离”的最大值作为下一个点
  • 对模长mag <= 1e-3的点直接跳过

这意味着第一版 custom op 设计里要明确:

  • 采样起点不是随机的,而是固定0
  • 存在“近零点跳过”行为
  • 算子行为依赖固定的距离更新逻辑,而不是任意 FPS 变体

5.2ball_query

从 ball_query_gpu.cu 可以确认:

  • 输入是:
    • new_xyz[b, m, 3]
    • xyz[b, n, 3]
  • 输出是idx[b, m, nsample]
  • 判定条件是d2 < radius^2
  • 当找到第一个命中点k时,会先把整个idx[j, :]全部填成k
  • 后续继续找到命中点时,再按cnt位置覆写前面的槽位

这意味着:

  • 邻域不足nsample时,不是填-1
  • 而是重复第一个命中的索引

这是非常重要的行为约定,custom op 和 TRT plugin 必须保持一致。

5.3group_points

从 group_points_gpu.cu 可以确认:

  • 输入:
    • points[b, c, n]
    • idx[b, npoints, nsample]
  • 输出:
    • out[b, c, npoints, nsample]

本质就是按idxpoints上取值并重排。

它本身没有搜索语义,依赖上游idx的定义是否一致。

5.4gather_points

从 sampling_gpu.cu 可以确认:

  • 输入:
    • points[b, c, n]
    • idx[b, m]
  • 输出:
    • out[b, c, m]

它对应的是单索引 gather,而不是group_points那种二维索引 gather。

5.5three_nn

从 interpolate_gpu.cu 可以确认:

  • 输入:
    • unknown[b, n, 3]
    • known[b, m, 3]
  • 输出:
    • dist2[b, n, 3]
    • idx[b, n, 3]
  • kernel 内输出的是平方距离dist2
  • Python 封装层再对dist2torch.sqrt()

这意味着部署设计里有两个选择:

  1. custom op 直接输出sqrt(dist2),对齐 Python 最终语义
  2. custom op 输出dist2,再在图里补一个Sqrt

推荐第一版选择第 2 种:

  • plugin 更贴近现有 CUDA kernel
  • 方便先保持实现简单

5.6three_interpolate

从 interpolate_gpu.cu 可以确认:

  • 输入:
    • points[b, c, m]
    • idx[b, n, 3]
    • weight[b, n, 3]
  • 输出:
    • out[b, c, n]

其语义严格就是:

  • 三个索引位置的特征值乘对应权重后求和

这意味着如果上游three_nn已经产出稳定的idx,那么three_interpolate比搜索类算子更规则。

6. Custom ONNX op 域与命名

建议统一使用固定 domain:

  • com.pvn3d.pointnet2

建议的 op 名如下:

  • PVN3D_FurthestPointSample
  • PVN3D_GatherPoints
  • PVN3D_BallQuery
  • PVN3D_GroupPoints
  • PVN3D_ThreeNN
  • PVN3D_ThreeInterpolate

这样做的目的:

  • 和 TensorRT plugin 名一一对应
  • 后续图检查时容易定位
  • 不和其他第三方 PointNet2 实现混淆

7. 每个 custom op 的接口设计

7.1PVN3D_FurthestPointSample

场景:

  • SA路径选中心点

输入:

  • xyz:float[B, N, 3]

属性:

  • npoint: int

输出:

  • idx:int32[B, npoint]

约束:

  • 第一版只支持 static shape
  • 第一版只支持B=1
  • 第一版只支持float32输入

语义约定:

  • 起始索引固定为0
  • 保持现有 kernel 的“近零点跳过”行为

TensorRT plugin 建议名:

  • PVN3DFurthestPointSample_TRT

7.2PVN3D_GatherPoints

场景:

  • SA路径根据 FPS 输出索引取中心点坐标

输入:

  • points:float[B, C, N]
  • idx:int32[B, S]

输出:

  • out:float[B, C, S]

第一版建议:

  • 先尝试标准 ONNXGather改写
  • 只有在 TRT parser 或运行期不稳定时才升级成 plugin

如果最终也做 plugin,plugin 名建议:

  • PVN3DGatherPoints_TRT

7.3PVN3D_BallQuery

场景:

  • SA路径在每个中心点周围搜固定邻域

输入:

  • new_xyz:float[B, S, 3]
  • xyz:float[B, N, 3]

属性:

  • radius: float
  • nsample: int

输出:

  • idx:int32[B, S, nsample]

语义约定:

  • 距离判定条件是d2 < radius^2
  • 邻域不足时重复第一个命中索引
  • 不使用-1作为 padding

TensorRT plugin 建议名:

  • PVN3DBallQuery_TRT

7.4PVN3D_GroupPoints

场景:

  • SA路径把邻域索引转换成局部 patch 特征张量

输入:

  • points:float[B, C, N]
  • idx:int32[B, S, K]

输出:

  • out:float[B, C, S, K]

第一版建议:

  • 先尝试标准图改写
  • 如果失败,再做 plugin

如果最终 plugin 化,plugin 名建议:

  • PVN3DGroupPoints_TRT

7.5PVN3D_ThreeNN

场景:

  • FP路径从稀疏点集向稠密点集回传特征前,先找 3 近邻

输入:

  • unknown:float[B, N, 3]
  • known:float[B, M, 3]

输出:

  • dist2:float[B, N, 3]
  • idx:int32[B, N, 3]

说明:

  • 推荐 custom op 直接输出dist2
  • 后续图里补Sqrt
  • 这样最贴近当前 CUDA kernel

TensorRT plugin 建议名:

  • PVN3DThreeNN_TRT

7.6PVN3D_ThreeInterpolate

场景:

  • FP路径根据三近邻和权重做特征插值

输入:

  • points:float[B, C, M]
  • idx:int32[B, N, 3]
  • weight:float[B, N, 3]

输出:

  • out:float[B, C, N]

第一版建议:

  • 优先尝试标准图改写
  • 若在 TRT 8.6.1 下不稳定,再实现 plugin

如果最终 plugin 化,plugin 名建议:

  • PVN3DThreeInterpolate_TRT

8. 第一版实现边界

为了降低复杂度,第一版 custom op / plugin 明确建议限制为:

  • static shape
  • batch=1
  • num_points=4096
  • 输入特征只支持float32
  • 索引统一int32
  • 先不考虑反向

不要第一版就追求:

  • dynamic shape
  • batch>1
  • fp16kernel 内部全支持
  • 完整训练反向

这些优化都应该放在完整链路先打通之后。

9. ONNX symbolic 设计建议

PyTorch 侧建议为每个算子注册独立 symbolic,直接映射到 custom domain 节点。

例如:

  • furthest_point_sample(xyz, npoint)
    ->com.pvn3d.pointnet2::PVN3D_FurthestPointSample
  • ball_query(radius, nsample, xyz, new_xyz)
    ->com.pvn3d.pointnet2::PVN3D_BallQuery
  • three_nn(unknown, known)
    ->com.pvn3d.pointnet2::PVN3D_ThreeNN

原则:

  • 属性显式传递
  • 不在 symbolic 里藏 shape 假设
  • shape 假设写在 export 脚本和 manifest 里

10. TensorRT plugin 设计建议

每个 plugin 至少要定义清楚:

  • plugin name
  • plugin version
  • 输入输出数目
  • dtype 支持
  • shape 推导
  • serialization 字段
  • enqueue 所需参数

建议统一 version:

  • 1

建议 plugin namespace:

  • pvn3d.pointnet2

这样后续 parser 注册和日志定位会更清楚。

11. 推荐的落地顺序

按当前 PVN3D 的实际风险排序,建议:

  1. 先实现PVN3D_ThreeNN
  2. 再实现PVN3D_BallQuery
  3. 再实现PVN3D_FurthestPointSample
  4. 之后视情况决定GatherPointsGroupPointsThreeInterpolate是否也 plugin 化

原因:

  • ThreeNN逻辑闭合,最适合做第一批原型
  • BallQuery能把“邻域补位策略”这类关键行为尽早固定
  • FurthestPointSample是 SA 路径里最强约束的离散算子

12. 与现有混合部署的关系

当前混合部署路径的价值仍然存在:

  • 它是数值对齐基线
  • 它能验证rgb_backbonefusion_head已经没问题
  • 它让我们只需专注PointNet2场景链

也就是说,后续 custom op / plugin 的测试应始终对齐当前这条链:

  • 原生_extPointNet2 输出
  • custom ONNX / TRT PointNet2 输出
http://www.jsqmd.com/news/579044/

相关文章:

  • Svelte 现实世界指南(四)
  • 为什么你的PyTorch 3.0静态图训练成本比竞品高2.6倍?——基于17家头部AI Lab真实Trace数据的成本效能诊断矩阵(含可执行checklist)
  • 2026年市场口碑好的GEO优化排名企业,究竟有哪些独特优势?
  • 成为数据科学家之路,第一部分:数学
  • Figma Make 提示词工程化:构建从布局、组件、交互到风格的稳定设计系统
  • 【STM32MP135】实战指南:利用HAL库构建Bare Metal裸机应用并实现SD卡启动全流程
  • OpenClaw学习助手:千问3.5-27B驱动错题本自动整理与解析
  • OpenClaw智能截图:千问3.5-27B识别界面元素自动操作
  • C语言标准流实战:stdin/stdout/stderr重定向的5个常见场景与代码示例
  • 边缘检测算法选型指南:从Sobel到Canny的5个实际场景对比(含医疗/自动驾驶案例)
  • Vivado Linux版安装空间不足?手把手教你如何优化磁盘空间分配
  • OpenClaw监控方案:Qwen3.5-9B任务执行日志与告警配置
  • OpenClaw外设控制探索:百川2-13B-4bits量化版驱动智能家居联动
  • OpenClaw安全实践:Qwen2.5-VL-7B本地化处理敏感图片数据
  • OpenClaw技能扩展实战:Qwen3-14b_int4_awq驱动Markdown文章自动发布
  • OpenClaw跨平台控制:Qwen3.5-9B管理多台电脑
  • ROI 崇拜可能对商业有害
  • 从UI稿到上线:我是如何把‘滑动标尺’这个需求在微信小程序里落地的(踩坑复盘)
  • PVN3D ORT Custom Ops 实现记录
  • hashid使用教程
  • Windows 11上保姆级教程:用Ollama本地部署DeepSeek-R1 8B,再也不用担心API费用和网络延迟了
  • 保姆级教程:从原始counts矩阵到Seurat对象,手把手完成单细胞RNA-seq质控与过滤(附R代码避坑点)
  • STM32标准库开发入门与实战指南
  • 从ASCII码到Word模板:深入理解Apache POI中(char)11这个‘竖直制表符’的妙用
  • 央企技术岗职场生态与转型指南
  • PCB拼板工艺:提升SMT效率与成本优化的关键技术
  • 语言驱动的视觉追踪新范式:深入解析RMOT核心框架与实战应用
  • 杰理之关机DAC未进入高阻【篇】
  • 职场选择与职业发展:半导体工程师的路径规划
  • 卷起袖子:9 个你应该探索的数据与机器学习项目示范