【PyTorch】ONNXエクスポートとTensorRT変換の完全ガイド:よくあるエラーと解決策

問題の概要:PyTorchモデルのONNXエクスポートとTensorRT変換における課題

PyTorchで学習したモデルを本番環境で高速に推論するためには、ONNX形式へのエクスポートと、それをNVIDIAのTensorRTエンジンに変換するプロセスが一般的です。しかし、この一連の作業では、初心者から中級者までが頻繁に遭遇する特有のエラーや課題が数多く存在します。具体的には、ONNXエクスポート時の「シンボリックサイズ」に関するエラー、サポートされていないPyTorch演算子のエラー、動的サイズ(バッチサイズや解像度)の扱い、そしてTensorRT変換時の精度(FP32/FP16/INT8)に関する問題などが挙げられます。これらの問題は、単にコマンドを実行するだけでは解決が難しく、モデル構造や変換プロセスの理解が必要となります。

原因の解説:なぜエラーが発生するのか?

主な原因は以下の3つに大別できます。

1. 演算子の互換性問題

PyTorchの一部の演算子(特にカスタムレイヤーや新しいバージョンで追加された関数)は、ONNX標準で完全にサポートされていない場合があります。ONNXは共通の演算子セットを定義していますが、全てのPyTorchの機能をカバーしているわけではありません。

2. 動的サイズと静的サイズの不一致

PyTorchモデルは推論時に動的な入力サイズ(可変バッチサイズ、可変画像サイズ)を扱えることが多いですが、ONNXエクスポート時やTensorRT変換時には、これらの次元をどのように扱うかを明示的に指定する必要があります。指定が不適切だと、次元の不一致エラーが発生します。

3. バージョン間の非互換性

PyTorch、torch.onnx、onnx、TensorRTの各ライブラリのバージョン組み合わせによって、サポートされる機能や挙動が異なります。特に、CUDAやcuDNNのバージョンも深く関与するため、環境構築の段階で問題が潜在するケースが少なくありません。

解決方法:ステップバイステップのトラブルシューティング

ステップ1: 環境のセットアップとバージョン確認

まず、互換性のあるバージョンを揃えることが最も重要です。以下のコマンドで環境を確認し、推奨組み合わせを目指しましょう。

# 現在の環境確認
import torch
print(f"PyTorch version: {torch.__version__}")
print(f"CUDA available: {torch.cuda.is_available()}")
print(f"CUDA version: {torch.version.cuda}")

# 推奨バージョンの一例 (2024年現在)
# PyTorch 2.0+ / torch.onnx
# ONNX 1.14+
# TensorRT 8.6+
# CUDA 11.8
# cuDNN 8.9+

ステップ2: PyTorchモデルのONNXエクスポート(基本形)

最もシンプルな静的サイズでのエクスポートから始めます。ここで発生する典型的なエラーメッセージとその対応策を以下に示します。

import torch
import torchvision

# サンプルモデルの準備(例: ResNet18)
model = torchvision.models.resnet18(pretrained=True)
model.eval()

# ダミー入力(静的サイズ: バッチサイズ1, 3チャネル, 224x224)
dummy_input = torch.randn(1, 3, 224, 224)

# ONNXエクスポートの試行
try:
    torch.onnx.export(
        model,
        dummy_input,
        "resnet18_static.onnx",
        input_names=["input"],
        output_names=["output"],
        opset_version=14  # 互換性の高いバージョンを指定
    )
    print("ONNX export succeeded!")
except Exception as e:
    print(f"ONNX export failed: {e}")

よくあるエラー1: Exporting the operator 'aten::...' to ONNX opset version 14 is not supported.
解決策: サポートされていない演算子を使用しています。`opset_version`を下げてみる(例: 13, 12)、またはモデル内の該当演算子をONNXサポートの別の演算子で置き換える必要があります。複雑なカスタムレイヤーは、ONNXのカスタムオペレーターとして定義する必要がある場合もあります。

ステップ3: 動的サイズを考慮したONNXエクスポート

本番環境では可変バッチサイズや解像度を扱うことが一般的です。`dynamic_axes`パラメータを使用して、動的に変化する次元を指定します。

try:
    torch.onnx.export(
        model,
        dummy_input,
        "resnet18_dynamic.onnx",
        input_names=["input"],
        output_names=["output"],
        opset_version=14,
        # 動的軸の定義: バッチサイズと画像の高さ・幅を動的に
        dynamic_axes={
            'input': {0: 'batch_size', 2: 'height', 3: 'width'},
            'output': {0: 'batch_size'}
        }
    )
    print("Dynamic ONNX export succeeded!")
except Exception as e:
    print(f"Dynamic ONNX export failed: {e}")

よくあるエラー2: Symbolic size ... is not statically inferrable.
解決策: モデル内に、動的サイズではシンボリックに推論できない計算(例: テンソルの形状に依存する特定の操作)が含まれています。モデル内の全演算子が動的形状をサポートしているか確認し、サポートしていない部分(例: view操作で-1を使用する代わりに固定サイズを計算する)を修正する必要があります。

ステップ4: ONNXモデルの検証と最適化

エクスポートしたONNXモデルが有効か検証し、必要に応じて最適化(グラフの簡略化)を行います。

import onnx
import onnxruntime as ort

# 1. ONNXモデルのロードと形式チェック
onnx_model = onnx.load("resnet18_dynamic.onnx")
onnx.checker.check_model(onnx_model)
print("ONNX model is valid!")

# 2. ONNX Runtimeで推論テスト(検証)
ort_session = ort.InferenceSession("resnet18_dynamic.onnx", providers=['CPUExecutionProvider'])
outputs = ort_session.run(None, {'input': dummy_input.numpy()})
print(f"ONNX Runtime inference output shape: {outputs[0].shape}")

# 3. (オプション) ONNX Simplifierでモデルを最適化
# コマンドラインで実行: python -m onnxsim input.onnx output_simplified.onnx

ステップ5: TensorRTへの変換(trtexecを使用)

ONNXモデルをTensorRTエンジン(.planファイル)に変換します。TensorRTがインストールされている環境で、コマンドラインツール`trtexec`を使用する方法が一般的です。

# 基本的な変換コマンド (FP32精度)
# trtexec --onnx=resnet18_dynamic.onnx --saveEngine=resnet18_fp32.plan --workspace=1024

# 動的形状を明示的に指定する場合(重要!)
trtexec --onnx=resnet18_dynamic.onnx 
        --saveEngine=resnet18_dynamic.plan 
        --minShapes=input:1x3x224x224 
        --optShapes=input:8x3x224x224 
        --maxShapes=input:32x3x512x512 
        --workspace=2048

# FP16精度で変換(推論速度向上)
trtexec --onnx=resnet18_dynamic.onnx 
        --saveEngine=resnet18_fp16.plan 
        --minShapes=input:1x3x224x224 
        --optShapes=input:8x3x224x224 
        --maxShapes=input:32x3x512x512 
        --fp16 
        --workspace=2048

よくあるエラー3: [TRT] [E] INVALID_ARGUMENT: getPluginCreator could not find plugin ... version 1
解決策: ONNXモデル内にTensorRTがネイティブサポートしていない演算子(プラグインが必要な演算子)が含まれています。該当するTensorRTプラグインライブラリをリンクする(`–plugins`オプション)、またはONNXモデルを変換する前に、その演算子をサポートされている別の演算子の組み合わせで表現するようにモデルを修正する必要があります。

ステップ6: PythonからTensorRTエンジンのロードと推論

変換した.planファイルをPythonでロードして推論を実行します。

import tensorrt as trt
import pycuda.driver as cuda
import pycuda.autoinit
import numpy as np

TRT_LOGGER = trt.Logger(trt.Logger.WARNING)

def load_engine(engine_file_path):
    with open(engine_file_path, "rb") as f, trt.Runtime(TRT_LOGGER) as runtime:
        return runtime.deserialize_cuda_engine(f.read())

engine = load_engine("resnet18_fp16.plan")
context = engine.create_execution_context()

# 入力/出力バッファの準備
# 動的形状の場合、推論実行前に具体的な形状をセットする必要がある
batch_size = 4
height, width = 256, 256
context.set_binding_shape(0, (batch_size, 3, height, width)) # 入力バインディングの形状を設定

# メモリ割り当てとデータ転送、推論実行...
# (詳細なコードは割愛しますが、ホスト/デバイス間のメモリ転送が必要)

コード例・コマンド例のまとめ

上記のステップで使用した主要なコードとコマンドを再掲します。

# 核心となるONNXエクスポートコマンド(動的軸付き)
torch.onnx.export(model, dummy_input, "model.onnx",
                  input_names=["input"], output_names=["output"],
                  opset_version=14,
                  dynamic_axes={'input': {0: 'batch_size', 2: 'height', 3: 'width'},
                                'output': {0: 'batch_size'}})

# 核心となるTensorRT変換コマンド(動的形状指定付き)
# trtexec --onnx=model.onnx --saveEngine=model.plan 
#         --minShapes=input:1x3x224x224 
#         --optShapes=input:8x3x224x224 
#         --maxShapes=input:32x3x512x512 
#         --fp16 --workspace=2048

まとめ・補足情報

PyTorchモデルからONNXを経由してTensorRTエンジンを作成するプロセスは、単なる形式変換ではなく、推論環境に最適化するための重要な工程です。成功の鍵は以下の点にあります。

  1. 段階的なアプローチ: まず静的サイズで成功させ、その後で動的サイズに拡張する。
  2. 環境の厳密な管理: PyTorch、ONNX、TensorRT、CUDAのバージョン互換性を常に確認する。
  3. エラーメッセージの詳細な解読: エラーメッセージは、問題の演算子や次元を特定する重要な手がかりとなる。
  4. 検証の徹底: ONNXエクスポート後はONNX Runtimeで、TensorRT変換後は小さなデータで推論結果を比較し、精度が大きく劣化していないか確認する。

最後に、非常に複雑なモデル(Transformer系アーキテクチャなど)では、ONNX/TensorRTのサポートが限定的な場合があります。そのような場合は、NVIDIAが提供するPolygraphyなどのデバッグツールを活用したり、モデルをサポートされているサブモジュールに分割して変換するなどの戦略が必要になることも覚えておきましょう。このガイドが、高速な推論環境構築の一助となれば幸いです。

この記事は役に立ちましたか?