目錄
- 前言
- 0. 簡述
- 1. camera.backbone.onnx(fp16)
- 2. camera.backbone.onnx(int8)
- 3. camera.vtransform.onnx(fp16)
- 4. fuser.onnx(fp16)
- 5. fuser.onnx(int8)
- 6. lidar.backbone.xyz.onnx
- 7. head.bbox.onnx(fp16)
- 總結
- 下載鏈接
- 參考
前言
自動駕駛之心推出的《CUDA與TensorRT部署實戰課程》,鏈接。記錄下個人學習筆記,僅供自己參考
本次課程我們來學習下課程第八章——實戰:CUDA-BEVFusion部署分析,一起來分析 BEVFusion 中各個 ONNX
課程大綱可以看下面的思維導圖
0. 簡述
本小節目標:分析 CUDA-BEVFusion 中各個 onnx 的輸入輸出,以及網絡架構
這節給大家講解第八章節第 7 小節,分析 BEVFusion 中的各個 onnx,這里我們拿 CUDA-BEVFusion 中導出好的 onnx 先看一看,一共有 5 個 onnx,對比看看 FP16 和 INT8 的 onnx 有什么區別,分析每個 onnx 的輸入輸出是什么以及它們之間是怎么連接的
1. camera.backbone.onnx(fp16)
我們先看 camera.backbone 部分,backbone 提供了兩個,一個是 resnet 另一個是 swin transformer,我們這里以 resnet50 為例來講解,我們主要看下輸入輸出就好了,主干部分是 resnet50 的結構,大家已經非常熟悉了
camera backbone 部分 fp16 的情況下需要看的東西并不是很多,需要注意的是 input 有兩個,一個是 camera,一個是 LiDAR 到 camera 的 1ch depth map,output 也是有兩個,一個是 camera feature(32ch),一個是 depth feature(118ch)
camera.backbone 的第一個輸入是環視相機圖像,以 nuscenes 數據集為例,相機個數為 6,高度為 256,寬度為 704,所以第一個輸入的維度就是 1x6x3x256x704,如下圖所示:
image(RGB)6 camera,3*256*704
通過變換矩陣將點云投影到相機上,這就是 camera.backbone 的第二個輸入即 depth map,如下圖所示:
(1ch)6 camera,1*256*704
對于輸出也有兩個,大家可以回顧下上節課講解的 BEVPool,它的輸入就對應于這里的輸出,如下圖所示。一個是 camera_feature,維度是 6x32x88x80,分別代表著 NxHxWxC,另一個是 camera_depth_weights 即 depth 的概率圖,維度是 6x118x32x88 分別代表著 NxDxHxW,這里的 D 表示對每一個特征像素 D 個 depth 的概率分布,圖中 D 是 118,說明我們為圖片上每個像素點估計 118 個深度值,之后對 118 個深度值做一個 softmax 看哪個點出現的可能性最大,這就是 camear backbone 的輸出,它和普通的圖像特征提取網絡如 resnet 有所不同,多了一個深度概率分支
camera feature:80chdepth feature:118ch
2. camera.backbone.onnx(int8)
接著我們再來看下 camera.backbone 的 INT8 的 onnx,camera backbone 部分如果是 int8 的話,我們可以看到在每一個 conv 前面都添加了 Q/DQ 節點,如下圖所示,每一個 Q/DQ 節點都有對應的 scale 和 zero_shift,我們可以知道這一部分是經過 QAT 學習的,其余的沒有變化(有關 QAT 學習如何添加這些 Q/DQ 節點有時間的話后面會介紹)
image(RGB)6 camera,3*256*704
可以看到每一個 Conv 前面都加了 Q/DQ 節點,每個 Conv 節點都有兩個輸入,一個是 activation value,一個是 weight,兩個輸入都需要加 Q/DQ,其實 Q/DQ 添加的過程并不是很復雜,通過 NVIDIA 提供的 pytorch_quantization 量化工具即可完成,這個我們在 TensorRT量化實戰課YOLOv7量化:YOLOv7-PTQ量化(一) 中有提到過,大家感興趣的可以看下
我們之前有講過 TensorRT 里面對輸入對 activation value 是 per-tensor 的量化粒度,每一個 tensor 只有一個 scale,這個大家可以從上圖中看出來,y_scale 和 y_zero_point 都只有一個值,也就是 6x3x256x704 這整個 tensor 共用這一個 scale 和 zero_point
對于 weight 而言是 per-channel 的量化粒度,也就是說每個通道共享一個 scale 和 zero_point,這個我們從上圖中也能看出來,可以看到 weight 的 Q 節點的 scale 有 64 個,對應的是 Conv 節點的 64 個通道
同時 zero_point 也是 64 個,只不過全為 0,如上圖所示,那為什么都是 0 呢?這個我們在第五章節的時候也講過,NVIDIA 將量化分為對稱量化和非對稱量化,NVIDIA 官方說如果要做非對稱量化在部署階段計算量比較大,也不好融合,所以 NVIDIA 在做量化時統一采用的是對稱量化,因此 zero_point 就是 0 了
camera feature:80chdepth feature:118ch
我們可以看其實并不是所有的節點都做了 INT8 量化,輸出部分像 softmax、Transpose 就沒有做 INT8 了,如上圖所示
以上就是 camera.backbone 的 INT8 的 onnx 的整個結構了,值得注意的是在 resnet50int8/build 文件夾下有各種層的信息以及輸出的日志文件,我們一起來看下,
在 camera.backbone.json 文件中我們可以看到每個 layer 都有關于 INT8 量化的一些描述,我們重點來看下 camera.backbone.log,來看下層融合之后的精度
我們可以看到 log 里面每個層每個節點的融合信息以及它們的精度的變化,大家可以打開簡單看下
3. camera.vtransform.onnx(fp16)
我們看完 camera.backbone 之后我們來看 camera.vtransform,camera vtransform 的部分只有 fp16 的 onnx,需要看的東西并不是很多,需要注意的是,這個 vtransform 是針對 backbone 中輸出進行的,三個 conv 將 360x360 大小的 input feature 進行特征學習 downsample 到 180x180
值得注意的是這里跨越了 BEVPool 這個部分,也就是說 camera.backbone 的兩個輸出經過 BEVPool 投影到 BEV 空間之后的輸出才是作為 camera.vtransform 的輸入
80*360*360->80*180*180
4. fuser.onnx(fp16)
我們繼續看,下一個是 fuser,fuser.onnx 的 fp16 模型比較簡單,相比于 BEVFormer 來說 BEVFusion 的融合部分整體上只有 convolution 而沒有像 BEVFormer 的 attention(spatial,temporal)。并且整體上相比于 backbone 而言,模型的深度也很淺,并且只有一個 BN,所有的 kernel 都是 3x3
投影在 BEV 空間的 feature mapcamera 是 80ch,lidar 是 256ch估計是因為點云是 sparse 的,所以需要更大的 channel size
輸入一個是 camera 一個是 lidar,camera 這邊是 BEVPool 處理過投影到 BEV 上的 camera feature,維度是 1x80x180x180,lidar 這邊是經過 SCN 網絡提取后的 lidar feature,維度是 1x256x180x180
通過多個 conv 將 camera feature 和 lidar feature 融合最終得到 180x180 Grid size 的 BEV 特征,ch 大小是 512
輸出是融合后的 BEV 特征,維度是 1x512x180x180
5. fuser.onnx(int8)
fuser.onnx 的 int8 模型會稍微復雜一點,跟 camera.backbone(int8) 一樣,每一個 conv 前都有 Q/DQ 節點,所有這里的 fuser 也是經過 QAT 進行學習到的,這里的權重已經能夠在某種程度上適應 fp32->int8 的量化誤差了
投影在 BEV 空間的 feature mapcamera 是 80ch,lidar 是 256ch估計是因為點云是 sparse 的,所以需要更大的 channel size
通過多個 conv 將 camera feature 和 lidar feature 融合最終得到 180x180 Grid size 的 BEV 特征,ch 大小是 512
6. lidar.backbone.xyz.onnx
我們再來看下 lidar.backbone.xyz.onnx 也就是點云特征提取網絡的 onnx,這個其實就是 CenterPoint 的 SCN 架構直接導出的 ONNX
值得注意的是 lidar.backbone.xyz 的 onnx 比較特殊,因為這里使用的是自定義 onnx 節點,有兩個自定義節點:
- SparseConvolution
- ScatterDense
所以在推理的時候會根據自定義 onnx 節點里的信息和輸入 tensor 的信息進行推理
BEV-Grid:256*180*180
輸入是經過處理后的點云 tensor 維度是 1x5,輸出是 lidar feature 維度是 1x256x180x180,這個會輸入到 fuser 模塊與 camera 部分做融合得到融合后的 BEV 特征
自定義 SparseConvolution 節點里面包含了許多信息,如上圖所示,這些信息將會在推理階段用到,包括 activation,kernel_size,padding,rulebook,stride 等等
7. head.bbox.onnx(fp16)
最后我們看 head,head.bbox 部分是 fp16 推理的,使用 fuser 后的 512x180x180 的 feature map 進行前向推理,這里的 forward 過程的 onnx 詳細部分沒有必要看,我們只需要知道輸出都有哪些:
- height:[dim,1,200](3D 目標框的高度即 z 方向上的大小)
- dim:[dim,3,200](3D 目標框的中心點即 center_x,center_y,center_z)
- rot:[dim,2,200](rotation 即 sin 和 cos)
- reg:[dim,2,200](3D 目標框的長寬即 x,y 方向上的大小)
- vel:[dim,2,200](速度即 vx、vy,用來表示在哪個方向移動)
- score:[dim,10,200](class confidence)
在 BEV 空間上生成的特征圖
輸出在 BEV 空間上的 3D BBox 的各種信息(高度、深度、坐標、得分等等)
輸入是 1x512x180x180,也就是融合后的 BEV 特征,輸出有 6 個,相關含義上面已經提到過了
以上就是 BEVFusion 中的各個 onnx 的分析,我們知道了每個 onnx 的輸入輸出以及如何銜接之后,再去閱讀代碼會相對簡單一些
總結
這節課程我們主要學習了 BEVFusion 中的各個 onnx,分析了每個 onnx 的輸入輸出以及它們之間是怎么銜接的,主要包括 camera.backbone、camera.vtransform、fuser、lidar.backbone.xyz、head 五個 onnx,我們還分析了不同精度下的 onnx 差異,主要對比了 FP16 和 INT8 兩種精度,INT8 下的 onnx 都插入了 Q/DQ 節點來做量化工作。
OK,以上就是第 7 小節有關 BEVFusion 中各個 onnx 分析的全部內容了,下節我們來學習 CUDA-BEVFusion 推理框架設計模式,敬請期待😄
下載鏈接
- 論文下載鏈接【提取碼:6463】
- 數據集下載鏈接【提取碼:data】
- 代碼和安裝包下載鏈接【提取碼:cuda】
參考
- TensorRT量化實戰課YOLOv7量化:YOLOv7-PTQ量化(一)