自由課題

学んだり、考えたり、試したりしたこと。

Jetson Nanoで自律走行ラジコンを作る(5)

今回は、TensorRTというDNNの推論エンジンを利用して前回作成したモデルをJetson Nano上で実行する。

「Jetson Nanoで自律走行ラジコンを作る」シリーズのまとめ記事は以下。

kimitok.hateblo.jp

本題に入る前にモデルを少し変更することにした。
前回作成したモデルでは、入力画像のリサイズや正規化といった画像に対する前処理はあらかじめ行われている前提としていた。

しかし、処理時間や実機側の実装の手間を考えて前処理もモデルに組み込んでしまうことにした。
具体的には以下のようにした。

preprocessing = tf.keras.Sequential([
    tf.keras.layers.experimental.preprocessing.Resizing(224, 224),
    tf.keras.layers.experimental.preprocessing.Rescaling(scale=1./255.) 
])

num_classes = 2
model = tf.keras.Sequential([
    tf.keras.layers.InputLayer(input_shape=[240, 360, 3], name='input'),
    preprocessing,
    hub.KerasLayer("https://tfhub.dev/tensorflow/efficientnet/b0/feature-vector/1", trainable=False),
    tf.keras.layers.Dense(640),
    ...

ということで本題に入る。
TensorRTはNVIDIAが開発したDNNの推論エンジンで、 NVIDIAのデバイスを使用して高速に推論を行うことができる(らしい)。
JetPackにはTensorRTがプレインストールされているのでビルドする必要はない。

ONNXモデルをTensorRTで実行するためにtrtexecというコマンドを使用するのだが、どうもJetPackの初期状態ではパスが通っていないらしい。

jetson-nano:~/lunchjet/build$ dpkg -S trtexec
libnvinfer-samples: /usr/src/tensorrt/samples/trtexec/prn_utils.py
libnvinfer-samples: /usr/src/tensorrt/samples/trtexec/profiler.py
libnvinfer-samples: /usr/src/tensorrt/samples/trtexec/trtexec.cpp
libnvinfer-samples: /usr/src/tensorrt/samples/trtexec
libnvinfer-bin: /usr/src/tensorrt/bin/trtexec
libnvinfer-samples: /usr/src/tensorrt/samples/trtexec/README.md
libnvinfer-samples: /usr/src/tensorrt/samples/trtexec/tracer.py
libnvinfer-samples: /usr/src/tensorrt/samples/trtexec/Makefile

ONNXファイルをTensorRTのエンジンファイルに変換するにはtrtexecを使用する。

/usr/src/tensorrt/bin/trtexec --onnx=efficentnet_b0_640_2.onnx --saveEngine=efficentnet_b0_640_2.trt --fp16 --verbose

上記を実行するとエラーが発生する。

いろいろ調べてみたがパッと根本対策するのが難しそうに見えたので、とりあえずGoogle Colab側でtf2onnxに指定するopsetのバージョンを13から10に下げてみたところ(以下のコマンド)成功した。

!python3 -m tf2onnx.convert --saved-model 'models/efficentnet_b0_640_2' --opset 10 --inputs input:0[1,240,360,3] --output 'models/efficentnet_b0_640_2.onnx'

trtexecの実行に成功すると、以下のようなメッセージが表示される。 かなり時間がかかる。

jetson-nano:~/lunchjet/build$ time /usr/src/tensorrt/bin/trtexec --onnx=efficentnet_b0_640_2.onnx --saveEngine=efficentnet_b0_640_2.trt --fp16 --workspace=1024
&&&& RUNNING TensorRT.trtexec # /usr/src/tensorrt/bin/trtexec --onnx=efficentnet_b0_640_2.onnx --saveEngine=efficentnet_b0_640_2.trt --fp16 --workspace=1024
[06/23/2021-04:13:21] [I] === Model Options ===
[06/23/2021-04:13:21] [I] Format: ONNX
[06/23/2021-04:13:21] [I] Model: efficentnet_b0_640_2.onnx
[06/23/2021-04:13:21] [I] Output:
[06/23/2021-04:13:21] [I] === Build Options ===
[06/23/2021-04:13:21] [I] Max batch: 1
[06/23/2021-04:13:21] [I] Workspace: 1024 MB
[06/23/2021-04:13:21] [I] minTiming: 1
[06/23/2021-04:13:21] [I] avgTiming: 8
[06/23/2021-04:13:21] [I] Precision: FP32+FP16
[06/23/2021-04:13:21] [I] Calibration: 
[06/23/2021-04:13:21] [I] Safe mode: Disabled
[06/23/2021-04:13:21] [I] Save engine: efficentnet_b0_640_2.trt
...
----------------------------------------------------------------
Input filename:   efficentnet_b0_640_2.onnx
ONNX IR version:  0.0.5
Opset version:    10
Producer name:    tf2onnx
Producer version: 1.8.5
Domain:           
Model version:    0
Doc string:       
----------------------------------------------------------------
[06/23/2021-04:13:26] [W] [TRT] onnx2trt_utils.cpp:220: Your ONNX model has been generated with INT64 weights, while TensorRT does not natively support INT64. Attempting to cast down to INT32.
[06/23/2021-04:30:21] [I] [TRT] Detected 1 inputs and 1 output network tensors.
[06/23/2021-04:30:22] [I] Starting inference threads
[06/23/2021-04:30:26] [I] Warmup completed 5 queries over 200 ms
[06/23/2021-04:30:26] [I] Timing trace has 78 queries over 3.10927 s
[06/23/2021-04:30:26] [I] Trace averages of 10 runs:
[06/23/2021-04:30:26] [I] Average on 10 runs - GPU latency: 39.7039 ms - Host latency: 39.8319 ms (end to end 39.8424 ms, enqueue 18.7935 ms)
[06/23/2021-04:30:26] [I] Average on 10 runs - GPU latency: 39.7146 ms - Host latency: 39.8426 ms (end to end 39.8532 ms, enqueue 17.8146 ms)
[06/23/2021-04:30:26] [I] Average on 10 runs - GPU latency: 39.7396 ms - Host latency: 39.8685 ms (end to end 39.879 ms, enqueue 17.7146 ms)
[06/23/2021-04:30:26] [I] Average on 10 runs - GPU latency: 39.7206 ms - Host latency: 39.8507 ms (end to end 39.8614 ms, enqueue 17.8423 ms)
[06/23/2021-04:30:26] [I] Average on 10 runs - GPU latency: 39.7114 ms - Host latency: 39.8394 ms (end to end 39.85 ms, enqueue 17.6247 ms)
[06/23/2021-04:30:26] [I] Average on 10 runs - GPU latency: 39.7409 ms - Host latency: 39.8684 ms (end to end 39.8789 ms, enqueue 16.6851 ms)
[06/23/2021-04:30:26] [I] Average on 10 runs - GPU latency: 39.727 ms - Host latency: 39.8599 ms (end to end 39.8703 ms, enqueue 17.4027 ms)
[06/23/2021-04:30:26] [I] Host Latency
[06/23/2021-04:30:26] [I] min: 39.7101 ms (end to end 39.7203 ms)
[06/23/2021-04:30:26] [I] max: 40.1162 ms (end to end 40.1218 ms)
[06/23/2021-04:30:26] [I] mean: 39.8512 ms (end to end 39.8617 ms)
[06/23/2021-04:30:26] [I] median: 39.8544 ms (end to end 39.8645 ms)
[06/23/2021-04:30:26] [I] percentile: 40.1162 ms at 99% (end to end 40.1218 ms at 99%)
[06/23/2021-04:30:26] [I] throughput: 25.0863 qps
[06/23/2021-04:30:26] [I] walltime: 3.10927 s
[06/23/2021-04:30:26] [I] Enqueue Time
[06/23/2021-04:30:26] [I] min: 13.5795 ms
[06/23/2021-04:30:26] [I] max: 26.8156 ms
[06/23/2021-04:30:26] [I] median: 16.7192 ms
[06/23/2021-04:30:26] [I] GPU Compute
[06/23/2021-04:30:26] [I] min: 39.5806 ms
[06/23/2021-04:30:26] [I] max: 39.9905 ms
[06/23/2021-04:30:26] [I] mean: 39.7223 ms
[06/23/2021-04:30:26] [I] median: 39.7251 ms
[06/23/2021-04:30:26] [I] percentile: 39.9905 ms at 99%
[06/23/2021-04:30:26] [I] total compute time: 3.09834 s
&&&& PASSED TensorRT.trtexec # /usr/src/tensorrt/bin/trtexec --onnx=efficentnet_b0_640_2.onnx --saveEngine=efficentnet_b0_640_2.trt --fp16 --workspace=1024

real    17m6.273s
user    7m27.964s
sys     2m2.556s

推論時にFP16を使用するオプション(--fp16)を付与したのでモデルファイルがオリジナルの半分になっている。
ただ、FP16にして処理時間が段違いに速くなるかというとそうでもなさそうだ。

このモデルファイル*1を使用して推論するためのコードを書く。
TensorRTのC++ APIには高水準APIのようなものはないらしく*2、細かい設定を延々と記述する形になる。
特に工夫の余地もなくほぼサンプルコードのコピペなのでここにコードは載せない。

今回書いたソースは以下。

github.com

あえて特記する点があるとすると、OpenCV経由で得た画像を入力する際に以下の処理が必要だった。

DriveParameter DriveDetector::detect(const cv::Mat &image)
{
    cv::Mat image_rgb, image_rgb_float;
    cv::cvtColor(image, image_rgb, cv::COLOR_BGR2RGB);
    image_rgb.convertTo(image_rgb_float, CV_32F);

    // Copy image data to input binding memory
    if (cudaMemcpyAsync(input_mem, image_rgb_float.data, input_mem_size, cudaMemcpyHostToDevice, stream) != cudaSuccess)
    {
...

作成したモデルは入力がfloat32のRGB(NHWC)であるのに対し、OpenCVを使って取得した画像はuint8のBGR(NHWC)なので事前に変換が必要である。
特にモデルのデータ型を変換するのを忘れていてしばらくはまってしまった。

ということで、適当な画像を使って推論させてみると無事結果が出力された。
処理時間を確認するために、同じ画像を使って10回推論させている。

jetson-nano:~/lunchjet/build$ ./test/drive_detector_test ./efficentnet_b0_640_2.trt ./2021_05_22_11_53_13_819.jpg 
input index: 0
output index: 1
params: steer=-0.0366211 throtle=0.776367 1281ms
params: steer=-0.0366211 throtle=0.776367 50ms
params: steer=-0.0366211 throtle=0.776367 48ms
params: steer=-0.0366211 throtle=0.776367 43ms
params: steer=-0.0366211 throtle=0.776367 43ms
params: steer=-0.0366211 throtle=0.776367 43ms
params: steer=-0.0366211 throtle=0.776367 43ms
params: steer=-0.0366211 throtle=0.776367 42ms
params: steer=-0.0366211 throtle=0.776367 41ms
params: steer=-0.0366211 throtle=0.776367 43ms

今はカメラのフレームレートを15FPSにしているが、定常状態では一応20FPSでも処理できそうではある。

今回はここまで。

参考資料

前処理レイヤーを使用する  |  TensorFlow Core

NVIDIA Deep Learning TensorRT Documentation

TensorRT: Main Page

TensorRT/quickstart at master · NVIDIA/TensorRT · GitHub

*1:TensorRTではエンジンファイルというらしい

*2:Python APIには多少ある模様