🎉

ONNXによる前処理の影響の調査

2024/02/18に公開

onnxruntime.quantization.preprocessの挙動をresnet18モデルを例に検証。

onnxruntime.quantization.shape_inferenceについて

前処理
前処理は、float32モデルを変換して、量子化に備えることです。次の3つのオプションステップで構成されています:
シンボリック形状の推論。これは変圧器モデルに最適です。
モデルの最適化:このステップでは、ONNXランタイムネイティブライブラリを使用して、計算ノードのマージを含む計算グラフを書き直し、冗長性を排除してランタイムの効率を向上させます。
ONNX形状推論。
https://onnxruntime.ai/docs/performance/model-optimizations/quantization.html

検証

resnet18を作成

import torchvision.models as models

model = models.resnet18(pretrained=False)
model.eval()
dummy_input = torch.randn(1, 3, 224, 224)
torch.onnx.export(model, dummy_input, "resnet18.onnx", verbose=True, input_names=['input'], output_names=['output'])

前処理

!python -m onnxruntime.quantization.preprocess --input resnet18.onnx --output resnet18-infer.onnx
model = onnx.load('resnet18.onnx')

num_layers = len(model.graph.node)
print(f"ブロックの数: {num_layers}")

total_params = sum(np.prod(tensor.dims) for tensor in model.graph.initializer)
print(f"パラメータ数: {total_params}")

model = onnx.load('resnet18.onnx')

num_layers = len(model.graph.node)
print(f"ブロックの数: {num_layers}")

total_params = sum(np.prod(tensor.dims) for tensor in model.graph.initializer)
print(f"パラメータ数: {total_params}")

> ブロックの数: 65
パラメータ数: 11680872
> ブロックの数: 49
パラメータ数: 11680872

前処理を行った結果、ブロック数が65から49に減少

model_1 = onnx.load('resnet18.onnx')
model_2 = onnx.load('resnet18-infer.onnx')

# ノード(ブロック)の名前とタイプを取得
model_1_nodes = {(node.name, node.op_type) for node in model_1.graph.node}
model_2_nodes = {(node.name, node.op_type) for node in model_2.graph.node}

diff_1_to_2 = model_1_nodes - model_2_nodes
diff_2_to_1 = model_2_nodes - model_1_nodes

print("モデル1にはあるがモデル2にはないブロック:")
for node in diff_1_to_2:
    print(f"block: {node[0]}, type: {node[1]}")

print("\nモデル2にはあるがモデル1にはないブロック:")
for node in diff_2_to_1:
    print(f"block: {node[0]}, type: {node[1]}")

> モデル1にはあるがモデル2にはない層:
block: Identity_2, type: Identity
block: Identity_15, type: Identity
block: Identity_11, type: Identity
block: Identity_6, type: Identity
block: Identity_9, type: Identity
block: Identity_14, type: Identity
block: Identity_12, type: Identity
block: Identity_0, type: Identity
block: Identity_8, type: Identity
block: Identity_7, type: Identity
block: Identity_5, type: Identity
block: Identity_4, type: Identity
block: Identity_1, type: Identity
block: Identity_13, type: Identity
block: Identity_10, type: Identity
block: Identity_3, type: Identity

モデル2にはあるがモデル1にはないブロック:

Identity ブロックは入力をそのまま出力に渡す恒等写像なので、前処理の最適化プロセスによって不要と判断されたらしい。

モデルにIdentityブロックが含まれているかどうかをtorchvizで確認したところ。特にそのようなレイヤーはなかった。ONNX変換時に冗長なブロックが挿入された可能性がある。

import torchvision.models as models
from torchviz import make_dot

model = models.resnet18(pretrained=False)
dummy_input = torch.randn(1, 3, 224, 224)
output = model(dummy_input)
dot = make_dot(output, params=dict(list(model.named_parameters()) + [('input', dummy_input)]))
dot.render('resnet18_visualization', format='png')

追記:
モデル構造の分析に長けたPINTOさんお手製のツールがあるらしく、レイヤーの数やモデルサイズはこちらを使うとより簡単に分析できる
https://github.com/PINTO0309/ssc4onnx

Discussion