工程筆記本 / NVIDIA

NVIDIA

NVIDIA CUDA核心GPU實做:Jetson Nano 運用TensorRT加速引擎 – 下篇

鄭 建彥📅 2021-07-21👁 33
*本文由RS components 贊助發表,轉載自DesignSpark部落格原文連結

使用TensorRT運行ONNX

要來運行TensorRT了,來複習一下TensorRT的流程:
  1. ONNX parser:將模型轉換成ONNX的模型格式。
  2. Builder:將模型導入TensorRT並且建構TensorRT的引擎。
  3. Engine:引擎會接收輸入值並且進行Inference跟輸出。
  4. Logger:負責記錄用的,會接收各種引擎在Inference時的訊息。
  第一個我們已經完成了,接下來的部分要建構TensorRT引擎,這個部分可以參考於NVIDIA的官網文件,主要程式如下,總共三個副函式build_engine、save_engine、load_engine,就如字面上的意思一樣是建置、儲存、載入,而log函式庫是我自己寫的用來顯示狀態以及計時: [pastacode lang="python" manual="import%20tensorrt%20as%20trt%0Afrom%20log%20import%20timer%2C%20logger%0A%0ATRT_LOGGER%20%3D%20trt.Logger(trt.Logger.WARNING)%0Atrt_runtime%20%3D%20trt.Runtime(TRT_LOGGER)%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20%0A%20%20%20%20onnx_path%20%3D%20'alexnet.onnx'%0A%20%20%20%20trt_path%20%3D%20'alexnet.trt'%0A%20%20%20%20input_shape%20%3D%20%5B1%2C%20224%2C%20224%2C%203%5D%0A%20%20%20%20%0A%20%20%20%20build_trt%20%3D%20timer('Parser%20ONNX%20%26%20Build%20TensorRT%20Engine')%0A%20%20%20%20engine%20%3D%20build_engine(onnx_path%2C%20input_shape)%0A%20%20%20%20build_trt.end()%0A%20%20%20%20%0A%20%20%20%20save_trt%20%3D%20timer('Save%20TensorRT%20Engine')%0A%20%20%20%20save_engine(engine%2C%20trt_path)%0A%20%20%20%20save_trt.end()" message="" highlight="" provider="manual"/] build_engine的程式碼,max_workspace是指GPU的使用暫存最大可到多少,因為TensorRT可以支援到Float Point 16,這次模式選擇fp16,在建構引擎之前需要先解析 (Parser) ONNX模型,接著再使用build_cuda_engine來建構: [pastacode lang="python" manual="def%20build_engine(onnx_path%2C%20shape%20%3D%20%5B1%2C224%2C224%2C3%5D)%3A%0A%20%20%20%20%0A%20%20%20%20with%20trt.Builder(TRT_LOGGER)%20as%20builder%2C%20builder.create_network(1)%20as%20network%2C%20trt.OnnxParser(network%2C%20TRT_LOGGER)%20as%20parser%3A%0A%20%20%20%20%20%20%20%20%0A%20%20%20%20%20%20%20%20builder.max_workspace_size%20%3D%20(256%20%3C%3C%2020)%20%20%20%20%0A%23%20256MiB%0A%0A%20%20%20%20%20%20%20%20builder.fp16_mode%20%3D%20True%20%0A%23%20fp32_mode%20-%3E%20False%0A%0A%20%20%20%20%20%20%20%20%0A%20%20%20%20%20%20%20%20with%20open(onnx_path%2C%20'rb')%20as%20model%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20parser.parse(model.read())%0A%0A%20%20%20%20%20%20%20%20engine%20%3D%20builder.build_cuda_engine(network)%0A%0A%20%20%20%20%20%20%20%20return%20engine" message="" highlight="" provider="manual"/] save_engine的程式碼,儲存的時候需要將引擎給序列化以供儲存以及載入: [pastacode lang="python" manual="def%20save_engine(engine%2C%20engine_path)%3A%0A%20%20%20%20%0A%20%20%20%20buf%20%3D%20engine.serialize()%0A%20%20%20%20with%20open(engine_path%2C%20'wb')%20as%20f%3A%0A%20%20%20%20%20%20%20%20f.write(buf)" message="" highlight="" provider="manual"/] load_engine的程式碼,主要在於要反序列化獲得可運行的模型架構: [pastacode lang="python" manual="def%20load_engine(trt_runtime%2C%20engine_path)%3A%0A%20%20%20%20%0A%20%20%20%20with%20open(engine_path%2C%20'rb')%20as%20f%3A%0A%20%20%20%20%20%20%20%20engine_data%20%3D%20f.read()%0A%20%20%20%20engine%20%3D%20trt_runtime.deserialize_cuda_engine(engine_data)%0A%0A%20%20%20%20return%20engine" message="" highlight="" provider="manual"/] 執行的結果如下,其實沒有耗費很多時間: 可以看到已經有一個 “ Alexnet.trt “ 生成出來了,或許因為經過序列化處理,所以檔案少了非常多的容量: 接下來就是重頭戲了,使用TensorRT進行Inference,先導入函式庫,這邊要注意common是從 /usr/src/tensorrt/samples/python/common.py複製出來的,engine是剛剛建構引擎的程式,log是我另外寫的用來計時跟顯示,其中trt的logger跟runtime也都先定義好了方便之後的呼叫: [pastacode lang="python" manual="import%20tensorrt%20as%20trt%0Afrom%20PIL%20import%20Image%0Aimport%20torchvision.transforms%20as%20T%0Aimport%20numpy%20as%20np%0A%0Aimport%20common%0Afrom%20engine%20import%20load_engine%0Afrom%20log%20import%20timer%2C%20logger%0A%0ATRT_LOGGER%20%3D%20trt.Logger(trt.Logger.WARNING)%0Atrt_runtime%20%3D%20trt.Runtime(TRT_LOGGER)" message="" highlight="" provider="manual"/] 載入資料的副函式,這邊需要轉成numpy格式,因為trt的引擎只吃numpy: [pastacode lang="python" manual="def%20load_data(path)%3A%0A%20%20%20%20trans%20%3D%20T.Compose(%5B%0A%20%20%20%20%20%20%20%20T.Resize(256)%2C%20T.CenterCrop(224)%2C%20T.ToTensor()%0A%20%20%20%20%5D)%0A%0A%20%20%20%20img%20%3D%20Image.open(path)%0A%20%20%20%20img_tensor%20%3D%20trans(img).unsqueeze(0)%0A%20%20%20%20return%20np.array(img_tensor)" message="" highlight="" provider="manual"/] 接著載入引擎並且分配內存,binding是存放input、output所要佔用的空間大小,stream則是pycuda的東西 ( cuda.Stream() ),是cuda計算缺一不可的成員;這邊將inputs的內容替換成我要inference的照片,.host的意思是input的內存空間。 [pastacode lang="python" manual="%23%20load%20trt%20engine%0A%0Aload_trt%20%3D%20timer(%22Load%20TRT%20Engine%22)%0Atrt_path%20%3D%20'alexnet.trt'%0Aengine%20%3D%20load_engine(trt_runtime%2C%20trt_path)%0Aload_trt.end()%0A%0A%23%20allocate%20buffers%0A%0Ainputs%2C%20outputs%2C%20bindings%2C%20stream%20%3D%20common.allocate_buffers(engine)%0A%23%20load%20data%0A%0Ainputs%5B0%5D.host%20%3D%20load_data('..%2Ftest_photo.jpg')" message="" highlight="" provider="manual"/] 推論的部分則是透過common.do_inference來進行,create_execution_context是必要的,但沒有開源所以不太清楚裡面的內容: [pastacode lang="python" manual="%23%20inference%0A%0Ainfer_trt%20%3D%20timer(%22TRT%20Inference%22)%0Awith%20engine.create_execution_context()%20as%20context%3A%0A%20%20%20%20trt_outputs%20%3D%20common.do_inference(context%2C%20bindings%3Dbindings%2C%20inputs%3Dinputs%2C%20outputs%3Doutputs%2C%20stream%3Dstream)%0Apreds%20%3D%20trt_outputs%5B0%5D%0Ainfer_trt.end()" message="" highlight="" provider="manual"/] 最後取得標籤以及對應的信心指數: [pastacode lang="python" manual="%23%20Get%20Labels%0A%0Af%20%3D%20open('..%2Fimagenet_classes.txt')%0At%20%3D%20%5B%20i.replace('%5Cn'%2C'')%20for%20i%20in%20f.readlines()%5D%0Alogger(f%22Result%20%3A%20%7Bt%5Bnp.argmax(preds)%5D%7D%22)" message="" highlight="" provider="manual"/] 接著稍微比較了所有的框架,但可能因為同時進行三種不同的框架,Jetson Nano負荷不來所以時間都被拉長了,但我們仍然可以從比例看的出來彼此之間的差距,PyTorch:ORT:TRT大約是7:2:1,代表運行PyTorch所耗費的時間是開啟TRT引擎的7倍! 從下圖可以看到蠻有趣的一點是理論上精度最高的是PyTorch的版本,結果在信心指數的部分卻是最低的。

PyTorch使用TensorRT最簡單的方式

 

torch2trt套件

接下來來介紹一下這個套件,可以只用少少的程式碼將PyTorch的模型轉換成trt的模型,對於torch的愛好者來說實在是太棒了: 安裝方法如下,這邊我是採用我的映像檔並且開啟torch虛擬環境: [pastacode lang="python" manual="%24%20git%20clone%20https%3A%2F%2Fgithub.com%2FNVIDIA-AI-IOT%2Ftorch2trt%0A%24%20cd%20torch2trt%0A%24%20workon%20torch%0A(torch)%20%24%20python3%20setup.py%20install" message="" highlight="" provider="manual"/]

torch2trt 將 PyTorch的模型轉換成tensorrt

使用方法如下,透過torch2trt就可以直接轉換,一樣需要宣告輸入的維度大小;接著也可以使用pytorch的方式進行儲存;導入則需要使用TRTModule: [pastacode lang="python" manual="import%20torch%0Afrom%20torch2trt%20import%20torch2trt%0Afrom%20torchvision.models.alexnet%20import%20alexnet%0A%0A%23%20Load%20Alexnet%20Model%0A%0Amodel%20%3D%20alexnet(pretrained%3DTrue).eval().cuda()%0A%0A%23%20TRT%20Model%0A%0Ax%20%3D%20torch.ones((1%2C%203%2C%20224%2C%20224)).cuda()%0Amodel_trt%20%3D%20torch2trt(model%2C%20%5Bx%5D)%0A%0A%23%20Save%20Model%0A%0Aalexnet_trt_pth%20%3D%20'alexnet_trt.pth'%0Atorch.save(model_trt.state_dict()%2C%20alexnet_trt_pth)%0A%0A%23%20Load%20Model%0A%0A%0Afrom%20torch2trt%20import%20TRTModule%0Amodel_trt%20%3D%20TRTModule()%0Amodel_trt.load_state_dict(%20torch.load('alexnet_trt.pth'))" message="" highlight="" provider="manual"/] 完整程式碼如下: [pastacode lang="python" manual="import%20torch%0Afrom%20torch2trt%20import%20torch2trt%0Afrom%20torchvision%20import%20transforms%20as%20T%0Afrom%20torchvision.models.alexnet%20import%20alexnet%0Aimport%20time%0A%0A%23%20Use%20to%20print%20info%20and%20timing%0A%0Afrom%20print_log%20import%20log%0A%0A%23%20Load%20Model%0A%0Aalexnet_pth%20%3D%20'alexnet.pth'%0Aload_model%20%3D%20log(%22Load%20Model...%22)%0Amodel%20%3D%20alexnet(pretrained%3DTrue).eval().cuda()%0Atorch.save(model.state_dict()%2C%20alexnet_pth%2C%20_use_new_zipfile_serialization%3DFalse)%0Aload_model.end()%0A%0A%23%20TRT%20Model%0A%0Aconvert_model%20%3D%20log(%22Convert%20Model...%22)%0Ax%20%3D%20torch.ones((1%2C%203%2C%20224%2C%20224)).cuda()%0Amodel_trt%20%3D%20torch2trt(model%2C%20%5Bx%5D)%0Aconvert_model.end()%0A%0A%23%20Save%20Model%0A%0Aalexnet_trt_pth%20%3D%20'alexnet_trt.pth'%0Asave_model%20%3D%20log(%22Saving%20TRT...%22)%0Atorch.save(model_trt.state_dict()%2C%20alexnet_trt_pth)%0Asave_model.end()" message="" highlight="" provider="manual"/] 在 JetsonNano上的運作結果如下,轉換的時間為127秒左右,儲存的時間為160秒左右,基本上時間會因各個裝置不同而改變,執行完之後就會看到多了兩個檔案 alexnet以及alexnet_trt: 接著我稍微改動了Github的範例,使用自己的貓咪照片來當作預測資料,首先載入模型、資料、標籤檔: [pastacode lang="python" manual="import%20torch%0Aimport%20torch.nn%20as%20nn%0Afrom%20torchvision%20import%20transforms%20as%20T%0Afrom%20torchvision.models.alexnet%20import%20alexnet%0A%0Afrom%20torch2trt%20import%20torch2trt%0Afrom%20torch2trt%20import%20TRTModule%0A%0Aimport%20os%0Aimport%20cv2%0Aimport%20PIL.Image%20as%20Image%0Aimport%20time%0A%0A%23%20Use%20to%20print%20info%20and%20timing%0A%0Afrom%20print_log%20import%20log%0A%0Adef%20load_model()%3A%0A%0A%20%20%20%20model_log%20%3D%20log('Load%20%7B%7D%20...%20'.format('alexnet%20%26%20tensorrt'))%0A%20%20%20%20%0A%20%20%20%20model%20%3D%20alexnet().eval().cuda()%0A%20%20%20%20model.load_state_dict(torch.load('alexnet.pth'))%0A%20%20%20%20model_trt%20%3D%20TRTModule()%0A%20%20%20%20model_trt.load_state_dict(torch.load('alexnet_trt.pth'))%0A%20%20%20%20%0A%20%20%20%20model_log.end()%0A%0A%20%20%20%20return%20(model%2C%20model_trt)%0A%0Adef%20load_data(img_path)%3A%0A%20%20%20%20%0A%20%20%20%20data_log%20%3D%20log('Load%20data%20...')%0A%20%20%20%20%0A%20%20%20%20img_pil%20%3D%20Image.open(img_path)%0A%20%20%20%20trans%20%3D%20T.Compose(%5BT.Resize(256)%2CT.CenterCrop(224)%2C%20T.ToTensor()%5D)%0A%20%20%20%20%0A%20%20%20%20data_log.end()%0A%0A%20%20%20%20return%20trans(img_pil).unsqueeze(0).cuda()%0A%0Adef%20load_label(label_path)%3A%0A%20%20%20%20f%20%3D%20open(%20label_path%2C%20'r')%0A%20%20%20%20return%20f.readlines()%20%20%20" message="" highlight="" provider="manual"/] 接著先寫好了inference的過程: [pastacode lang="python" manual="def%20infer(trg_model%2C%20trg_label%2C%20trg_tensor%2C%20info%20%3D%20'Normal%20Model')%3A%0A%20%20%20%20%0A%20%20%20%20softmax%20%3D%20nn.Softmax(dim%3D0)%0A%20%20%20%20infer_log%20%3D%20log('%5B%7B%7D%5D%20Start%20Inference%20...'.format(info))%0A%0A%20%20%20%20with%20torch.no_grad()%3A%0A%20%20%20%20%20%20%20%20predict%20%3D%20trg_model(trg_tensor)%5B0%5D%0A%20%20%20%20%20%20%20%20predict_softmax%20%3D%20softmax(predict)%0A%20%20%20%20%0A%20%20%20%20infer_log.end()%0A%20%20%20%20label%20%3D%20trg_label%5Btorch.argmax(predict_softmax)%5D.replace('%5Cn'%2C'%20')%0A%20%20%20%20value%20%3D%20torch.max(predict_softmax)%0A%20%20%20%20return%20(%20label%2C%20value)" message="" highlight="" provider="manual"/] 最後就是整個運作的流程了: [pastacode lang="python" manual="if%20__name__%20%3D%3D%20%22__main__%22%3A%0A%0A%20%20%20%20%0A%23%20Load%20Model%0A%0A%20%20%20%20model%2C%20model_trt%20%3D%20load_model()%0A%0A%20%20%20%20%0A%23%20Input%20Data%0A%0A%20%20%20%20img_path%20%3D%20'test_photo.jpg'%0A%20%20%20%20img_tensor%20%3D%20load_data(img_path)%0A%0A%20%20%20%20%0A%23%20Label%0A%0A%20%20%20%20label_path%20%3D%20'imagenet_classes.txt'%0A%20%20%20%20labels%20%3D%20load_label(label_path)%0A%0A%20%20%20%20%0A%23%20Predict%20%3A%20Normal%0A%0A%20%20%20%20label%2C%20val%20%3D%20infer(model%2C%20labels%2C%20img_tensor%2C%20%22Normal%20AlexNet%22)%0A%20%20%20%20print('%5CnResult%3A%20%7B%7D%20%20%7B%7D%5Cn'.format(label%2C%20val))%0A%0A%20%20%20%20%0A%23%20Predict%20%3A%20TensorRT%0A%0A%20%20%20%20label_trt%2C%20val_trt%20%3D%20infer(model_trt%2C%20labels%2C%20img_tensor%2C%20%22TensorRT%20AlexNet%22)%0A%20%20%20%20print('%5CnResult%3A%20%7B%7D%20%20%7B%7D%5Cn'.format(label_trt%2C%20val_trt))" message="" highlight="" provider="manual"/] 獲得的結果如下: 接著我們來嘗試運作物件辨識的範例吧!我第一個想到的就是torchvision中常見的物件辨識fasterrcnn,但是他轉換成trt過程問題很多,從這點也看的出來trt的支援度還沒有非常高,常常因為一些沒支援的層而無法轉換,這時候你就要自己去重新定義,對於新手而言實在是非常辛苦,所以Fasterrcnn這部分我就先跳過了,轉戰YOLOv5去嘗試運行TensorRT看看。  

YOLOv5使用TensorRT引擎方式

其實排除上述介紹的簡單方式,正規的方式應該是先轉成ONNX再轉成TensorRT,其中yolov5就有提供轉換成ONNX的方式  

1.  轉換成 ONNX格式再導入TensorRT ( 僅到匯出)

這邊我們直接使用YOLOv5來跑範例,先下載YOLOv5的Github並且開啟虛擬環境,如果你是使用自己的環境則可以透過安裝YOLOv5相依套件來完成: [pastacode lang="python" manual="%24%20git%20clone%20https%3A%2F%2Fgithub.com%2Fultralytics%2Fyolov5%0A%24%20cd%20yolov5%0A%24%20workon%20yolov5" message="" highlight="" provider="manual"/] Nano 上安裝ONNX、coremltools: [pastacode lang="python" manual="%24%20sudo%20apt-get%20install%20protobuf-compiler%20libprotoc-div%0A%24%20pip%20install%20onnx%0A%24%20pip%20install%20coremltools%3D%3D4.0" message="" highlight="" provider="manual"/] 進行ONNX的轉換: [pastacode lang="python" manual="%24%20source%20~%2F.bashrc%0A%24%20workon%20yolov5%0A(yolov5)%20%24%20python%20models%2Fexport.py" message="" highlight="" provider="manual"/] 預設是yolov5s.pt,執行完之後可以發現多了yolov5s.onnx以及yolov5.torchscript.pt,接著就可以使用ONNX的方法去導入使用,不過在yolo系列很少人會這樣做,主要是因為yolo有自定義的層,可能會導致trt無法轉換,但是也因為yolo已經很出名了,所以轉換的部分已經有人整合得很好,可以直接拿來使用。

1.  使用tensorrtx直接轉換( 可執行)

YOLOv5有提供直接從.pt轉成trt的方式,這時候就要參考另外一個github了 https://github.com/wang-xinyu/tensorrtx,裡面有個yolov5的資料夾,按照ReadMe進行即可完成轉換並且進行inferece: 1.複製兩個Github並複製py到yolov5資料夾,運行gen_wts.py來生成yolov5s.wpt,這個是讓TensorRT引擎運行的權重檔。 [pastacode lang="python" manual="(yolov5)%20%24%20git%20clone%20https%3A%2F%2Fgithub.com%2Fwang-xinyu%2Ftensorrtx.git%0A(yolov5)%20%24%20git%20clone%20https%3A%2F%2Fgithub.com%2Fultralytics%2Fyolov5.git%0A(yolov5)%20%24%20cd%20yolov5%20%0A(yolov5)%20%24%20cp%20yolov5s.pt%20weights%2F%0A(yolov5)%20%24%20cp%20~%2Ftensorrtx%2Fyolov5%2Fgen_wts.py%20.%0A(yolov5)%20%24%20python%20gen_wts.py" message="" highlight="" provider="manual"/] 2.由於tensorrt引擎是基於C++,所以常常會使用cmake來建構成執行檔。 [pastacode lang="python" manual="(yolov5)%20%24%20cd%20~%2Ftensorrtx%2Fyolov5%0A(yolov5)%20%24%20cp%20~%2Fyolov5%2Fyolov5s.wts%20.%0A(yolov5)%20%24%20mkdir%20build%0A(yolov5)%20%24%20cd%20build%0A(yolov5)%20%24%20cmake%20..%0A(yolov5)%20%24%20make%0A(yolov5)%20%24%20sudo%20.%2Fyolov5%20-s%20%20%20%20%20%20%20%20%20%20%20%20%20%2F%2F%20serialize%20model%20to%20plan%20file%20i.e.%20'yolov5s.engine'%20" message="" highlight="" provider="manual"/] 建構完之後就可以直接透過下列指令執行,會輸出圖檔 _bus.jpg、_zidane.jpg: [pastacode lang="python" manual="(yolov5)%20%24%20sudo%20.%2Fyolov5%20-d%20%20~yolov5%2Fdata%2Fimages%20%20" message="" highlight="" provider="manual"/] 接下來還可以使用Python來進行Inference,需安裝tensorrt跟pycuda: [pastacode lang="python" manual="(yolov5)%20%24%20pip%20install%20pycuda%0A(yolov5)%20%24%20python%20yolov5_trt.py" message="" highlight="" provider="manual"/] 執行結果如下: 接著使用原生的yolov5進行inference,耗費時間約為0.549s、0.244s: 不曉得為什麼跑了那麼多範例,yolov5的速度沒有提升反而下降,這個部分還需要研究一下…如果廣大讀者們知道的話麻煩再留言告訴我~

結語

從圖片分類的範例來看的話,TensorRT還是非常的厲害的!但是目前支援度還是不太高,入門的難易度很高,所以如果有要使用自己的模型要好好研究一番,但如果是用GPU模型並且是直接使用常見模型的話TensorRT絕對一個大趨勢,畢竟加速的效果真的不錯。  

參考文章

  *本文由RS components 贊助發表,轉載自DesignSpark部落格原文連結

相關文章 📎

留言 💬 (0)

還沒有留言,來當第一個。