3. Working With TensorRT Using The Python API

以下部分將重點介紹使用 Python API 可以執(zhí)行 TensorRT 用戶的目標和任務。這些部分主要討論在沒有任何框架的情況下使用 Python API。示例部分提供了進一步的詳細信息,并在適當情況下鏈接到下面。

假設你從一個訓練過的模型開始。本章將介紹以下使用TensorRT的必要步驟:

  • 從模型中創(chuàng)建 TensorRT 網(wǎng)絡定義
  • 調用 TensorRT builder 從網(wǎng)絡創(chuàng)建優(yōu)化的運行時引擎
  • 序列化和反序列化引擎,以便在運行時快速重新創(chuàng)建引擎
  • 向引擎提供數(shù)據(jù)以執(zhí)行推理

Python API vs C++ API

從本質上說,C++ API 和 Python API 在支持您的需求方面應該接近相同。Python API 的主要優(yōu)點是數(shù)據(jù)預處理和后處理很容易使用,因為您可以使用各種庫,如 NumPy 和 SciPy。

C++ API 應該用于安全非常重要的場合,例如在汽車中。有關 C++ API 的更多信息,請參見使用 C++ API 處理 TensorRT

有關如何使用 Python 優(yōu)化性能的更多信息,請參見如何優(yōu)化我的 Python 性能?來自最佳實踐指南。

3.1. Importing TensorRT Into Python

  1. 導入 TensorRT:
import tensorrt as trt
  1. 實現(xiàn)一個日志接口,通過該接口 TensorRT 報告錯誤、警告和信息消息。下面的代碼展示了如何實現(xiàn)日志記錄接口。在這種情況下,我們抑制了信息消息,只報告警告和錯誤。TensorRT Python 綁定中包含了一個簡單的日志記錄器。
TRT_LOGGER = trt.Logger(trt.Logger.WARNING)

3.2. Creating A Network Definition In Python

使用 TensorRT 執(zhí)行推理的第一步是從您的模型創(chuàng)建一個 TensorRT 網(wǎng)絡。最簡單的方法是使用 TensorRT 解析器庫導入模型(請參閱使用 Python 中的解析器導入模型、使用 Python 從 Caffe 導入模型、使用 Python 從 TensorFlow 導入模型和使用 Python 從 ONNX 導入模型),該庫支持以下格式的序列化模型:

  • Caffe (both BVLC and NVCaffe)
  • ONNX 1.0 and 1.1, and
  • UFF (used for TensorFlow)

另一種選擇是直接使用 TensorRT 網(wǎng)絡 API 定義模型(請參閱使用Python API從頭創(chuàng)建網(wǎng)絡定義)。這需要您進行少量的 API 調用來定義網(wǎng)絡圖中的每一層,并為模型的訓練參數(shù)實現(xiàn)您自己的導入機制。

注意: TensorRT Python API 僅適用于 x86_64 平臺。更多信息請參見深度學習 SDK 文檔- TensorRT 工作流。

3.2.1. Creating A Network Definition From Scratch Using The Python API

在創(chuàng)建網(wǎng)絡時,必須首先定義引擎并創(chuàng)建用于推理的構建器對象。Python API 用于從網(wǎng)絡 API 創(chuàng)建網(wǎng)絡和引擎。網(wǎng)絡定義引用用于向網(wǎng)絡添加各種層。有關使用 Python API 創(chuàng)建網(wǎng)絡和引擎的更多信息,請參見 network_api_pytorch_mnist 示例。

下面的代碼演示了如何創(chuàng)建一個具有輸入、卷積、池、完全連接、激活和 SoftMax 層的簡單網(wǎng)絡。

# Create the builder and network
with trt.Builder(TRT_LOGGER) as builder, builder.create_network() as network:
    # Configure the network layers based on the weights provided. In this case, the weights are imported from a pytorch model. 
    # Add an input layer. The name is a string, dtype is a TensorRT dtype, and the shape can be provided as either a list or tuple.
    input_tensor = network.add_input(name=INPUT_NAME, dtype=trt.float32, shape=INPUT_SHAPE)

    # Add a convolution layer
    conv1_w = weights['conv1.weight'].numpy()
    conv1_b = weights['conv1.bias'].numpy()
    conv1 = network.add_convolution(input=input_tensor, num_output_maps=20, kernel_shape=(5, 5), kernel=conv1_w, bias=conv1_b)
    conv1.stride = (1, 1)

    pool1 = network.add_pooling(input=conv1.get_output(0), type=trt.PoolingType.MAX, window_size=(2, 2))
    pool1.stride = (2, 2)
    conv2_w = weights['conv2.weight'].numpy()
    conv2_b = weights['conv2.bias'].numpy()
    conv2 = network.add_convolution(pool1.get_output(0), 50, (5, 5), conv2_w, conv2_b)
    conv2.stride = (1, 1)

    pool2 = network.add_pooling(conv2.get_output(0), trt.PoolingType.MAX, (2, 2))
    pool2.stride = (2, 2)

    fc1_w = weights['fc1.weight'].numpy()
    fc1_b = weights['fc1.bias'].numpy()
    fc1 = network.add_fully_connected(input=pool2.get_output(0), num_outputs=500, kernel=fc1_w, bias=fc1_b)

    relu1 = network.add_activation(fc1.get_output(0), trt.ActivationType.RELU)

    fc2_w = weights['fc2.weight'].numpy()
    fc2_b = weights['fc2.bias'].numpy()
    fc2 = network.add_fully_connected(relu1.get_output(0), OUTPUT_SIZE, fc2_w, fc2_b)

    fc2.get_output(0).name =OUTPUT_NAME
    network.mark_output(fc2.get_output(0))

3.2.2. Importing A Model Using A Parser In Python

要使用解析器導入模型,需要執(zhí)行以下高級步驟:

  • 創(chuàng)建TensorRTbuilder和網(wǎng)絡。
  • 為特定格式創(chuàng)建TensorRT解析器。
  • 使用解析器解析導入的模型并填充網(wǎng)絡。

有關這些步驟和示例代碼的示例,請參見使用 Python 從 Caffe 導入、使用 Python 從 TensorFlow 導入和使用 Python 從 ONNX 導入。
構建器必須在網(wǎng)絡之前創(chuàng)建,因為它是網(wǎng)絡的工廠。不同的解析器有不同的網(wǎng)絡輸出標記機制。有關更多信息,請參見 UFF 解析器 API、Caffe 解析器 APIONNX 解析器 API。

3.2.3. Importing From Caffe Using Python

下面的步驟說明了如何使用 Caffe 解析器和 Python API 直接導入 Caffe 模型。有關更多信息,請參閱 introductory_parser_samples 示例。

  1. import TensorRT:
import tensorrt as trt
  1. 定義數(shù)據(jù)類型。在本例中,我們將使用float32。
datatype = trt.float32
  1. 另外,定義一些路徑。更改以下路徑,以反映您在示例中所包含的模型中所放置的位置:
deploy_file = 'data/mnist/mnist.prototxt'
model_file = 'data/mnist/mnist.caffemodel'
  1. 創(chuàng)建 builder, network, 和 parser:
with trt.Builder(TRT_LOGGER) as builder, builder.create_network() as network, trt.CaffeParser() as parser:
model_tensors = parser.parse(deploy=deploy_file, model=model_file, network=network, dtype=datatype)

解析器返回 model_tensors,它是一個表,包含從張量名稱到 ITensor 對象的映射。

3.2.4. Importing From TensorFlow Using Python

下面的步驟說明了如何使用 UffParser 和 Python API 直接導入 TensorFlow 模型。這個示例可以在 <site-packages>/tensorrt/samples/python/end_to_end_tensorflow_mnist 目錄中找到。有關更多信息,請參見 end_to_end_tensorflow_mnist Python 示例。

  1. Import TensorRT:
import tensorrt as trt
  1. 為TensorFlow模型創(chuàng)建一個凍結的TensorFlow模型。 關于將TensorFlow模型凍結到流中的說明可以在 Freezing A TensorFlow Graph 中找到。

  2. 使用 UFF 轉換器將凍結的 tensorflow 模型轉換為 UFF 文件。通常,這很簡單:

convert-to-uff frozen_inference_graph.pb

根據(jù)您如何安裝 TensorRT,轉換到 uff 實用程序可能不會安裝在您的系統(tǒng)路徑中。在這種情況下,直接調用底層 Python 腳本。它應該位于 UFF 模塊的 bin 目錄中;例如, ~/.local/lib/python2.7/site-packages/uff/bin/convert_to_uff.py 。

要找到 UFF 模塊的位置,運行如下命令 python -c "import uff; print(uff.__path__)"

或者,您可以使用 UFF Paser API 并直接轉換 TensorFlow GraphDef。

  1. 定義一些路徑。更改以下路徑,以反映您將示例中包含的模型放置在何處:
model_file = '/data/mnist/minist.uff'
  1. 創(chuàng)建 builder, network, 和 parser:
with builder = trt.Builder(TRT_LOGGER) as builder, builder.create_network() as network, trt.UffParser() as parser:
        parser.register_input("Placeholder", (1, 28, 28))
        parser.register_output("fc2/Relu")
        parser.parse(model_file, network)

3.2.5. Importing From ONNX Using Python

限制:由于 ONNX 格式發(fā)展很快,您可能會遇到模型版本和解析器版本之間的版本不匹配。TensorRT 5.0.0 附帶的 ONNX 解析器支持 ONNX IR (Intermediate Representation)版本 0.0.3,opset 版本 7。

一般來說,ONNX 解析器的新版本是向后兼容的,因此,遇到由早期版本的 ONNX 導出器生成的模型文件不會造成問題。當更改不向后兼容時,可能會有一些例外。在這種情況下,將早期的 ONNX 模型文件轉換為稍后支持的版本。有關此主題的更多信息,請參見 ONNX Model Opset Version Converter。

用戶模型也有可能是由一個導出工具生成的,該工具支持比 TensorRT 附帶的 ONNX 解析器支持的更晚的 opset。在這種情況下,檢查發(fā)布到 GitHub onnx-tensorrt 的最新版本是否支持所需的版本。有關更多信息,請參見 yolov3_onnx。

支持的版本由 onnx_trt_backend.cpp 中的 BACKEND_OPSET_VERSION 變量定義。從 GitHub 下載并構建 ONNX TensorRT 解析器的最新版本。構建的說明可以在這里找到: TensorRT backend for ONNX.

下面的步驟說明如何使用 OnnxParser 和 Python API 直接導入 ONNX 模型。有關更多信息,請參見 introductory_parser_samples Python示例。

  1. import TensorRT:
import tensorrt as trt
  1. 創(chuàng)建 build, network, and parser:
with builder = trt.Builder(TRT_LOGGER) as builder, builder.create_network() as network, trt.OnnxParser(network, TRT_LOGGER) as parser:
with open(model_path, 'rb') as model:
parser.parse(model.read())

3.2.6. Importing From PyTorch And Other Frameworks

在 PyTorch 中使用 TensorRT (或任何其他具有 NumPy 兼容權重的框架)涉及到使用 TensorRT API 復制網(wǎng)絡體系結構(請參閱 Creating A Network Definition From Scratch Using The Python API),然后從 PyTorch 復制權重。有關更多信息,請參見 Working With PyTorch And Other Frameworks。

注意:在 ubuntu14.04 和 CentOS 上,同時加載 torch 模塊和 TensorRT 可能會導致 segmentation faults。

要執(zhí)行推理,請遵循 Performing Inference In Python 的概述說明。

3.3. Building An Engine In Python

構建器的一個功能是搜索其 CUDA 內核目錄以獲得最快的可用實現(xiàn),因此必須使用相同的 GPU 來構建優(yōu)化引擎將運行的 GPU。

構建器具有許多屬性,您可以設置這些屬性以控制網(wǎng)絡應運行的精度,以及自動調整參數(shù),例如TensorRT在確定哪個最快時(多次迭代會導致更長的運行時間,但是對噪聲的敏感性較?。摓槊總€內核計時多少次。您還可以查詢構建器以找出硬件本機支持的混合精度類型。

兩個特別重要的屬性是最大批處理大小(maximum batch size )和最大工作區(qū)大小(maximum workspace size)。

最大批大小指定 TensorRT 要優(yōu)化的批大小。在運行時,可以選擇較小的批處理大小。

層算法通常需要臨時工作區(qū)。此參數(shù)限制網(wǎng)絡中任何層可以使用的最大大小。如果提供的劃痕不夠,TensorRT 可能無法找到給定層的實現(xiàn)。

有關在Python中構建引擎的更多信息,請參見 introductory_parser_samples 示例。

  1. 使用構建對象構建 engine:
builder.max_batch_size = max_batch_size
builder.max_workspace_size = 1 <<  20 # This determines the amount of memory available to the builder when building an optimized engine and should generally be set as high as possible.
with trt.Builder(TRT_LOGGER) as builder:
with builder.build_cuda_engine(network) as engine:
# Do inference here.
  1. 執(zhí)行推理。要執(zhí)行推理,請遵循在 Performing Inference In Python 中概述的說明。

3.4. Serializing A Model In Python

當您序列化時,您將引擎轉換為一種格式,以便稍后存儲并用于推理。要用于推理,只需對引擎進行反序列化即可。序列化和反序列化是可選的。由于從網(wǎng)絡定義創(chuàng)建引擎可能會耗費大量時間,因此您可以避免在每次應用程序重新運行時重新構建引擎,方法是對引擎進行一次序列化,并在進行推斷時對其進行反序列化。因此,在構建引擎之后,用戶通常希望對其進行序列化,以便以后使用。

從這里開始,您可以序列化引擎,也可以直接使用引擎進行推理。在將模型用于推理之前,序列化和反序列化是一個可選步驟——如果需要,引擎對象可以直接用于推理。

注意:序列化引擎不能跨平臺或 TensorRT 版本移植。引擎特定于它們所構建的 GPU 模型(除了平臺和 TensorRT 版本之外)。

  1. 將模型序列化為 modelstream:
serialized_engine = engine.serialize()
  1. 反序列化 modelstream 以執(zhí)行推理。反序列化需要創(chuàng)建一個運行時對象:
with trt.Runtime(TRT_LOGGER) as runtime:
engine = runtime.deserialize_cuda_engine(serialized_engine)

最后一個參數(shù)是使用定制層應用程序的插件層工廠,它是可選的。可以在 Extending TensorRT With Custom Layers 中找到更多細節(jié)。

也可以將序列化引擎保存到文件中,并從文件中讀回:

  1. 序列化引擎并寫入文件:
with open(“sample.engine”, “wb”) as f:
        f.write(engine.serialize())
  1. 從文件中讀取引擎并反序列化:
with open(“sample.engine”, “rb”) as f, trt.Runtime(TRT_LOGGER) as runtime:
        engine = runtime.deserialize_cuda_engine(f.read())

3.5. Performing Inference In Python

下面的步驟說明了如何在Python中執(zhí)行推斷,現(xiàn)在您已經(jīng)有了一個引擎。

  1. 為輸入和輸出分配一些主機和設備緩沖區(qū):
# Determine dimensions and create page-locked memory buffers (i.e. won't be swapped to disk) to hold host inputs/outputs.
        h_input = cuda.pagelocked_empty(engine.get_binding_shape(0).volume(), dtype=np.float32)
        h_output = cuda.pagelocked_empty(engine.get_binding_shape(1).volume(), dtype=np.float32)
        # Allocate device memory for inputs and outputs.
        d_input = cuda.mem_alloc(h_input.nbytes)
        d_output = cuda.mem_alloc(h_output.nbytes)
        # Create a stream in which to copy inputs/outputs and run inference.
        stream = cuda.Stream()
  1. 創(chuàng)建一些空間來存儲中間激活值。由于引擎包含網(wǎng)絡定義和經(jīng)過訓練的參數(shù),因此需要額外的空間。這些是在執(zhí)行上下文中持有的:
with engine.create_execution_context() as context:
        # Transfer input data to the GPU.
        cuda.memcpy_htod_async(d_input, h_input, stream)
        # Run inference.
        context.execute_async(bindings=[int(d_input), int(d_output)], stream_handle=stream.handle)
        # Transfer predictions back from the GPU.
        cuda.memcpy_dtoh_async(h_output, d_output, stream)
        # Synchronize the stream
        stream.synchronize()
        # Return the host output. 
return h_output

引擎可以有多個執(zhí)行上下文,允許一組權重用于多個重疊的推理任務。例如,您可以在并行 CUDA 流中處理圖像,每個流使用一個引擎和一個上下文。每個上下文將在與引擎相同的 GPU 上創(chuàng)建。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容