Banyak pengguna PyTorch mengimplementasikan bagian post-processing model deteksi menggunakan Plugin TensorRT, sehingga memungkinkan seluruh model diekspor ke TensorRT. Blade menawarkan skalabilitas yang baik, dan Anda dapat menggabungkan Custom TensorRT Plugin yang telah ada dengan Blade untuk optimisasi kolaboratif. Topik ini menjelaskan cara menggunakan Blade guna mengoptimalkan model deteksi yang telah menggunakan Plugin TensorRT.
Informasi latar belakang
TensorRT adalah alat andal untuk optimisasi inferensi pada GPU NVIDIA. Optimisasi dasar Blade terintegrasi secara mendalam dengan metode TensorRT dan mencakup berbagai teknik optimisasi, seperti optimisasi graf komputasi, pustaka vendor (misalnya TensorRT dan oneDNN), optimisasi kompilator AI, pustaka operator Blade yang dioptimalkan secara manual, presisi campuran Blade, serta Blade EasyCompression.
RetinaNet adalah jaringan deteksi RCNN satu-tahap yang terdiri dari jaringan tulang punggung (backbone), beberapa subnet, dan post-processing Non-Maximum Suppression (NMS). Banyak framework pelatihan mendukung implementasi RetinaNet, dengan Detectron2 sebagai contoh khasnya. Sebelumnya, dokumen ini menjelaskan cara mengekspor model RetinaNet (Detectron2) menggunakan metode scripting_with_instances dan mengoptimalkannya secara cepat dengan Blade. Untuk informasi lebih lanjut, lihat Studi kasus optimisasi RetinaNet 1: Optimalkan model RetinaNet (Detectron2) menggunakan Blade.
Meskipun banyak pengguna PyTorch mengekspor model ke ONNX lalu menerapkannya dengan TensorRT, proses ini memiliki keterbatasan. Dukungan ekspor ONNX dan opset ONNX di TensorRT terbatas, sehingga proses optimisasi bisa menjadi tidak andal—terutama dalam mengekspor bagian post-processing jaringan deteksi ke ONNX untuk optimisasi TensorRT. Selain itu, kode post-processing tersebut sering kali tidak efisien. Oleh karena itu, banyak pengguna mengimplementasikan bagian post-processing menggunakan mekanisme Plugin TensorRT agar seluruh model dapat diekspor ke TensorRT.
Sebagai perbandingan, optimisasi dengan Blade dan TorchScript Custom C++ Operators lebih sederhana daripada mengimplementasikan bagian post-processing menggunakan mekanisme Plugin TensorRT. Untuk informasi lebih lanjut, lihat Studi kasus optimisasi RetinaNet 2: Optimalkan model menggunakan Blade dan Custom C++ Operator. Blade juga memiliki skalabilitas yang baik. Jika Anda telah mengimplementasikan Custom TensorRT Plugin, Anda dapat menggunakannya bersama Blade untuk optimisasi kolaboratif.
Batasan
Lingkungan yang digunakan dalam topik ini harus memenuhi persyaratan berikut:
-
Lingkungan sistem: Linux dengan Python 3.6 atau versi lebih baru, GCC 5.4 atau versi lebih baru, Nvidia Tesla T4, CUDA 10.2, CuDNN 8.0.5.39, dan TensorRT 7.2.2.3.
-
Framework: PyTorch 1.8.1 atau versi lebih baru dan Detectron2 0.4.1 atau versi lebih baru.
-
Tool optimisasi inferensi: Blade 3.16.0 atau versi lebih baru (terhubung secara dinamis dengan TensorRT).
Prosedur
Langkah-langkah berikut menjelaskan cara mengoptimalkan model menggunakan Blade dan Plugin TensorRT:
-
Langkah 1: Buat model PyTorch dengan Plugin TensorRT
Implementasikan bagian post-processing RetinaNet menggunakan Plugin TensorRT.
-
Langkah 2: Panggil Blade untuk mengoptimalkan model
Panggil antarmuka
blade.optimizeuntuk mengoptimalkan model dan menyimpan model yang telah dioptimalkan. -
Langkah 3: Muat dan jalankan model yang telah dioptimalkan
Setelah melakukan pengujian performa pada model asli dan model yang telah dioptimalkan, Anda dapat memuat model yang telah dioptimalkan untuk inferensi jika hasilnya memuaskan.
Langkah 1: Buat model PyTorch dengan Plugin TensorRT
Blade dapat bekerja dengan mekanisme ekstensi TensorRT untuk optimisasi kolaboratif. Bagian ini menjelaskan cara menggunakan ekstensi TensorRT guna mengimplementasikan bagian post-processing RetinaNet. Untuk informasi lebih lanjut tentang pengembangan dan kompilasi Plugin TensorRT, lihat Dokumentasi NVIDIA Deep Learning TensorRT. Logika program untuk bagian post-processing RetinaNet dalam topik ini berasal dari komunitas open source NVIDIA. Untuk informasi lebih lanjut, lihat Retinanet-Examples. Topik ini mengekstraksi kode inti untuk menjelaskan proses pengembangan dan implementasi Custom Operator.
-
Unduh dan ekstrak kode contoh.
wget -nv https://pai-blade.oss-cn-zhangjiakou.aliyuncs.com/tutorials/retinanet_example/retinanet-examples.tar.gz -O retinanet-examples.tar.gz tar xvfz retinanet-examples.tar.gz 1>/dev/null -
Kompilasi Plugin TensorRT.
Kode contoh mencakup implementasi dan registrasi Plugin TensorRT untuk post-processing
decodedannmsRetinaNet. Dokumentasi resmi PyTorch menyediakan tiga cara untuk mengompilasi Custom Operators: Membangun dengan CMake, kompilasi Just-in-Time (JIT), dan Membangun dengan Setuptools. Untuk informasi lebih lanjut, lihat MEMPERLUAS TORCHSCRIPT DENGAN CUSTOM C++ OPERATORS. Ketiga metode kompilasi ini cocok untuk skenario berbeda; Anda dapat memilih salah satu sesuai kebutuhan. Untuk kesederhanaan, topik ini menggunakan metode kompilasi JIT. Berikut adalah kode contohnya.CatatanSebelum dikompilasi, Anda harus mengonfigurasi pustaka dependensi seperti TensorRT, CUDA, dan CuDNN.
import torch.utils.cpp_extension import os codebase="retinanet-examples" sources=['csrc/plugins/plugin.cpp', 'csrc/cuda/decode.cu', 'csrc/cuda/nms.cu',] sources = [os.path.join(codebase,src) for src in sources] torch.utils.cpp_extension.load( name="plugin", sources=sources, build_directory=codebase, extra_include_paths=['/usr/local/TensorRT/include/', '/usr/local/cuda/include/', '/usr/local/cuda/include/thrust/system/cuda/detail'], extra_cflags=['-std=c++14', '-O2', '-Wall'], extra_ldflags=['-L/usr/local/TensorRT/lib/', '-lnvinfer'], extra_cuda_cflags=[ '-std=c++14', '--expt-extended-lambda', '--use_fast_math', '-Xcompiler', '-Wall,-fno-gnu-unique', '-gencode=arch=compute_75,code=sm_75',], is_python_module=False, with_cuda=True, verbose=False, ) -
Enkapsulasi bagian model konvolusi RetinaNet.
Enkapsulasi bagian model RetinaNet ke dalam modul
RetinaNetBackboneAndHeadsyang terpisah.import torch from typing import List from torch import Tensor from torch.testing import assert_allclose from detectron2 import model_zoo # Kelas ini mengenkapsulasi backbone dan RPN heads RetinaNet. class RetinaNetBackboneAndHeads(torch.nn.Module): def __init__(self, model): super().__init__() self.model = model def preprocess(self, img): batched_inputs = [{"image": img}] images = self.model.preprocess_image(batched_inputs) return images.tensor def forward(self, images): features = self.model.backbone(images) features = [features[f] for f in self.model.head_in_features] cls_heads, box_heads = self.model.head(features) cls_heads = [cls.sigmoid() for cls in cls_heads] box_heads = [b.contiguous() for b in box_heads] return cls_heads, box_heads retinanet_model = model_zoo.get("COCO-Detection/retinanet_R_50_FPN_3x.yaml", trained=True).eval() retinanet_bacbone_heads = RetinaNetBackboneAndHeads(retinanet_model) -
Bangun jaringan post-processing RetinaNet menggunakan Plugin TensorRT. Jika Anda telah membuat TensorRT Engine, Anda dapat melewati langkah ini.
-
Buat TensorRT Engine.
Agar Plugin TensorRT berfungsi, Anda harus mengimplementasikan fitur-fitur berikut:
-
Memuat file plugin.so yang telah dikompilasi secara dinamis menggunakan
ctypes.cdll.LoadLibrary. -
Fungsi
build_retinanet_decodemenggunakan API Pythontensorrtuntuk membangun jaringan post-processing, lalu mengompilasinya menjadi Engine.
Berikut adalah kode contohnya.
import os import numpy as np import tensorrt as trt import ctypes # Muat pustaka dinamis Plugin TensorRT. codebase="retinanet-examples" ctypes.cdll.LoadLibrary(os.path.join(codebase, 'plugin.so')) TRT_LOGGER = trt.Logger() trt.init_libnvinfer_plugins(TRT_LOGGER, "") PLUGIN_CREATORS = trt.get_plugin_registry().plugin_creator_list # Dapatkan fungsi Plugin TensorRT. def get_trt_plugin(plugin_name, field_collection): plugin = None for plugin_creator in PLUGIN_CREATORS: if plugin_creator.name != plugin_name: continue if plugin_name == "RetinaNetDecode": plugin = plugin_creator.create_plugin( name=plugin_name, field_collection=field_collection ) if plugin_name == "RetinaNetNMS": plugin = plugin_creator.create_plugin( name=plugin_name, field_collection=field_collection ) assert plugin is not None, "plugin tidak ditemukan" return plugin # Fungsi untuk membangun jaringan TensorRT. def build_retinanet_decode(example_outputs, input_image_shape, anchors_list, test_score_thresh = 0.05, test_nms_thresh = 0.5, test_topk_candidates = 1000, max_detections_per_image = 100, ): builder = trt.Builder(TRT_LOGGER) EXPLICIT_BATCH = 1 << (int)(trt.NetworkDefinitionCreationFlag.EXPLICIT_BATCH) network = builder.create_network(EXPLICIT_BATCH) config = builder.create_builder_config() config.max_workspace_size = 3 ** 20 cls_heads, box_heads = example_outputs profile = builder.create_optimization_profile() decode_scores = [] decode_boxes = [] decode_class = [] input_blob_names = [] input_blob_types = [] def _add_input(head_tensor, head_name): input_blob_names.append(head_name) input_blob_types.append("Float") head_shape = list(head_tensor.shape)[-3:] profile.set_shape( head_name, [1] + head_shape, [20] + head_shape, [1000] + head_shape) return network.add_input( name=head_name, dtype=trt.float32, shape=[-1] + head_shape ) # Bangun input jaringan. cls_head_inputs = [] cls_head_strides = [input_image_shape[-1] // cls_head.shape[-1] for cls_head in cls_heads] for idx, cls_head in enumerate(cls_heads): cls_head_name = "cls_head" + str(idx) cls_head_inputs.append(_add_input(cls_head, cls_head_name)) box_head_inputs = [] for idx, box_head in enumerate(box_heads): box_head_name = "box_head" + str(idx) box_head_inputs.append(_add_input(box_head, box_head_name)) output_blob_names = [] output_blob_types = [] # Bangun jaringan decode. for idx, anchors in enumerate(anchors_list): field_coll = trt.PluginFieldCollection([ trt.PluginField("topk_candidates", np.array([test_topk_candidates], dtype=np.int32), trt.PluginFieldType.INT32), trt.PluginField("score_thresh", np.array([test_score_thresh], dtype=np.float32), trt.PluginFieldType.FLOAT32), trt.PluginField("stride", np.array([cls_head_strides[idx]], dtype=np.int32), trt.PluginFieldType.INT32), trt.PluginField("num_anchors", np.array([anchors.numel()], dtype=np.int32), trt.PluginFieldType.INT32), trt.PluginField("anchors", anchors.contiguous().cpu().numpy().astype(np.float32), trt.PluginFieldType.FLOAT32),] ) decode_layer = network.add_plugin_v2( inputs=[cls_head_inputs[idx], box_head_inputs[idx]], plugin=get_trt_plugin("RetinaNetDecode", field_coll), ) decode_scores.append(decode_layer.get_output(0)) decode_boxes.append(decode_layer.get_output(1)) decode_class.append(decode_layer.get_output(2)) # Bangun jaringan NMS. scores_layer = network.add_concatenation(decode_scores) boxes_layer = network.add_concatenation(decode_boxes) class_layer = network.add_concatenation(decode_class) field_coll = trt.PluginFieldCollection([ trt.PluginField("nms_thresh", np.array([test_nms_thresh], dtype=np.float32), trt.PluginFieldType.FLOAT32), trt.PluginField("max_detections_per_image", np.array([max_detections_per_image], dtype=np.int32), trt.PluginFieldType.INT32),] ) nms_layer = network.add_plugin_v2( inputs=[scores_layer.get_output(0), boxes_layer.get_output(0), class_layer.get_output(0)], plugin=get_trt_plugin("RetinaNetNMS", field_coll), ) nms_layer.get_output(0).name = "scores" nms_layer.get_output(1).name = "boxes" nms_layer.get_output(2).name = "classes" nms_outputs = [network.mark_output(nms_layer.get_output(k)) for k in range(3)] config.add_optimization_profile(profile) cuda_engine = builder.build_engine(network, config) assert cuda_engine is not None return cuda_engine -
-
Buat
cuda_engineberdasarkan jumlah aktual output, tipe output, dan bentuk output dariRetinaNetBackboneAndHeads.import numpy as np from detectron2.data.detection_utils import read_image !wget http://images.cocodataset.org/val2017/000000439715.jpg -q -O input.jpg img = read_image('./input.jpg') img = torch.from_numpy(np.ascontiguousarray(img.transpose(2, 0, 1))) example_inputs = retinanet_bacbone_heads.preprocess(img) example_outputs = retinanet_bacbone_heads(example_inputs) cell_anchors = [c.contiguous() for c in retinanet_model.anchor_generator.cell_anchors] cuda_engine = build_retinanet_decode( example_outputs, example_inputs.shape, cell_anchors)
-
-
Gunakan ekstensi Blade untuk mendukung model yang menggunakan PyTorch dan TensorRT Engine sekaligus.
Kode berikut menggabungkan kembali bagian Backbone, Heads, dan post-processing Plugin TensorRT menggunakan
RetinaNetWrapper,RetinaNetBackboneAndHeads, danRetinaNetPostProcess.import blade.torch # Bagian post-processing yang didukung oleh ekstensi TensorRT Blade. class RetinaNetPostProcess(torch.nn.Module): def __init__(self, cuda_engine): super().__init__() blob_names = [cuda_engine.get_binding_name(idx) for idx in range(cuda_engine.num_bindings)] input_blob_names = blob_names[:-3] input_blob_types = ["Float"] * len(input_blob_names) output_blob_names = blob_names[-3:] output_blob_types = ["Float"] * len(output_blob_names) self.trt_ext_plugin = torch.classes.torch_addons.TRTEngineExtension( bytes(cuda_engine.serialize()), (input_blob_names, output_blob_names, input_blob_types, output_blob_types), ) def forward(self, inputs: List[Tensor]): return self.trt_ext_plugin.forward(inputs) # Gunakan PyTorch dan TensorRT Engine bersamaan. class RetinaNetWrapper(torch.nn.Module): def __init__(self, model, trt_postproc): super().__init__() self.backbone_and_heads = model self.trt_postproc = torch.jit.script(trt_postproc) def forward(self, images): cls_heads, box_heads = self.backbone_and_heads(images) return self.trt_postproc(cls_heads + box_heads) trt_postproc = RetinaNetPostProcess(cuda_engine) retinanet_mix_trt = RetinaNetWrapper(retinanet_bacbone_heads, trt_postproc) # Ekspor dan simpan sebagai file TorchScript. retinanet_script = torch.jit.trace(retinanet_mix_trt, (example_inputs, ), check_trace=False) torch.jit.save(retinanet_script, 'retinanet_script.pt') torch.save(example_inputs, 'example_inputs.pth') outputs = retinanet_script(example_inputs)Modul
torch.nn.Moduleyang baru dirakit memiliki fitur-fitur berikut:-
Menggunakan antarmuka
torch.classes.torch_addons.TRTEngineExtension, yang didukung oleh ekstensi TensorRT Blade. -
Mendukung ekspor model TorchScript. Kode di atas menggunakan
torch.jit.traceuntuk mengekspor model. -
Mendukung penyimpanan model dalam format TorchScript.
-
Langkah 2: Panggil Blade untuk mengoptimalkan model
-
Panggil antarmuka optimisasi Blade.
Panggil antarmuka
blade.optimizeuntuk mengoptimalkan model. Berikut adalah kode contohnya. Untuk informasi lebih lanjut tentang antarmukablade.optimize, lihat Optimalkan model PyTorch.import blade import blade.torch import ctypes import torch import os codebase="retinanet-examples" ctypes.cdll.LoadLibrary(os.path.join(codebase, 'plugin.so')) blade_config = blade.Config() blade_config.gpu_config.disable_fp16_accuracy_check = True script_model = torch.jit.load('retinanet_script.pt') example_inputs = torch.load('example_inputs.pth') test_data = [(example_inputs,)] # Data input untuk PyTorch berupa daftar tupel. with blade_config: optimized_model, opt_spec, report = blade.optimize( script_model, # Model TorchScript yang diekspor pada langkah sebelumnya. 'o1', # Aktifkan optimisasi tingkat O1 Blade. device_type='gpu', # Perangkat target adalah GPU. test_data=test_data, # Sediakan satu set data uji untuk membantu optimisasi dan pengujian. ) -
Cetak laporan optimisasi dan simpan model.
Model yang dioptimalkan oleh Blade tetap berupa model TorchScript. Setelah optimisasi, Anda dapat menggunakan kode berikut untuk mencetak laporan optimisasi dan menyimpan model yang telah dioptimalkan.
# Cetak laporan optimisasi. print("Report: {}".format(report)) # Simpan model yang telah dioptimalkan. torch.jit.save(optimized_model, 'optimized.pt')Laporan optimisasi yang dicetak adalah sebagai berikut. Untuk informasi lebih lanjut tentang bidang-bidang dalam laporan optimisasi, lihat Laporan optimisasi.
Report: { "software_context": [ { "software": "pytorch", "version": "1.8.1+cu102" }, { "software": "cuda", "version": "10.2.0" } ], "hardware_context": { "device_type": "gpu", "microarchitecture": "T4" }, "user_config": "", "diagnosis": { "model": "unnamed.pt", "test_data_source": "user provided", "shape_variation": "undefined", "message": "Unable to deduce model inputs information (data type, shape, value range, etc.)", "test_data_info": "0 shape: (1, 3, 480, 640) data type: float32" }, "optimizations": [ { "name": "PtTrtPassFp16", "status": "effective", "speedup": "4.37", "pre_run": "40.59 ms", "post_run": "9.28 ms" } ], "overall": { "baseline": "40.02 ms", "optimized": "9.27 ms", "speedup": "4.32" }, "model_info": { "input_format": "torch_script" }, "compatibility_list": [ { "device_type": "gpu", "microarchitecture": "T4" } ], "model_sdk": {} } -
Lakukan pengujian performa pada model asli dan model yang telah dioptimalkan.
Berikut adalah kode contoh untuk pengujian performa.
import time @torch.no_grad() def benchmark(model, inp): for i in range(100): model(inp) torch.cuda.synchronize() start = time.time() for i in range(200): model(inp) torch.cuda.synchronize() elapsed_ms = (time.time() - start) * 1000 print("Latency: {:.2f}".format(elapsed_ms / 200)) # Uji performa model asli. benchmark(script_model, example_inputs) # Uji performa model yang telah dioptimalkan. benchmark(optimized_model, example_inputs)Berikut adalah hasil referensi pengujian ini.
Latency: 40.71 Latency: 9.35Hasil menunjukkan bahwa setelah 200 kali eksekusi, latensi rata-rata model asli dan model yang telah dioptimalkan masing-masing adalah 40,71 ms dan 9,35 ms.
Langkah 3: Muat dan jalankan model yang telah dioptimalkan
Opsional: Selama masa percobaan, tambahkan pengaturan variabel lingkungan berikut untuk mencegah program berhenti mendadak akibat kegagalan otentikasi:
export BLADE_AUTH_USE_COUNTING=1Lakukan otentikasi untuk menggunakan PAI-Blade.
export BLADE_REGION=<region> export BLADE_TOKEN=<token>Konfigurasikan parameter berikut sesuai kebutuhan bisnis Anda:
<region>: wilayah tempat Anda menggunakan PAI-Blade. Anda dapat bergabung dengan grup DingTalk pengguna PAI-Blade untuk memperoleh daftar wilayah tempat PAI-Blade tersedia. Informasi kode QR grup DingTalk tersedia di Instal PAI-Blade.
<token>: token otentikasi yang diperlukan untuk menggunakan PAI-Blade. Anda dapat bergabung dengan grup DingTalk pengguna PAI-Blade untuk memperoleh token otentikasi tersebut. Informasi kode QR grup DingTalk tersedia di Instal PAI-Blade.
-
Muat dan jalankan model yang telah dioptimalkan.
Model yang dioptimalkan oleh Blade tetap berupa model TorchScript. Anda dapat memuat model yang telah dioptimalkan tanpa perlu mengganti lingkungan.
import blade.runtime.torch import torch from torch.testing import assert_allclose import ctypes import os codebase="retinanet-examples" ctypes.cdll.LoadLibrary(os.path.join(codebase, 'plugin.so')) optimized_model = torch.jit.load('optimized.pt') example_inputs = torch.load('example_inputs.pth') with torch.no_grad(): pred = optimized_model(example_inputs)