ONNX(Open Neural Network Exchange)は、PyTorchやTensorFlowで作った機械学習モデルを、フレームワークに依存しない共通形式で保存するためのオープンな仕様です。拡張子は .onnx で、中身は計算グラフと重みをProtocol Buffersで書き出したファイルです。学習はPyTorchで行い、推論はONNX Runtimeを使ってC#やJava、ブラウザ、スマートフォンで動かす、という分業を可能にします。
この記事では、2026年9月時点の最新版であるonnx 1.23.0とONNX Runtime 1.30.0を前提に、.onnxファイルの構造、PyTorch・Kerasからの変換手順、推論の書き方を説明します。あわせて、新しいonnxパッケージで作ったモデルを古いONNX Runtimeで読み込むと失敗する問題を、手元で再現したエラーメッセージ付きで取り上げます。
まとめ:ONNXの役割と使うときの要点
- ONNXは「モデルの交換形式」の仕様で、それ自体は推論エンジンではありません。実行はONNX Runtimeなどのランタイムが担います。
- .onnxファイルには、ノード(演算)の並び・重み(initializer)・入出力の型と形状・IR version・opsetの2つの版番号が入っています。
- PyTorchは2.9から
torch.onnx.exportの既定がdynamo=True(torch.exportベース)に変わりました。Keras 3はmodel.export(path, format="onnx")で直接書き出せます。 - onnx 1.23.0で作ったモデルは既定でIR 14・opset 28になり、ONNX Runtime 1.23.2では「Unsupported model IR version: 14」で読めません。配布先のランタイム版に合わせてIRとopsetを固定します。
- CUDAを指定しても、利用可能なEP一覧に無ければ警告だけ出てCPUで実行されることがあります。
get_providers()でセッションに登録されたEPを確認し、各ノードの実際の実行先はプロファイリングで確認します。
ONNXの定義と、ONNX Runtimeとの違い
ONNXは2017年9月にMicrosoftとFacebook(現Meta)が発表したプロジェクトで、2019年11月14日にLF AI Foundation(現LF AI & Data Foundation)のgraduate level projectとして移管されました。起源はFacebookのPyTorchチームが開発していたモデルエクスポーターで、当初の名称は「Toffee」でした。読み方は英語版Wikipediaが公式アカウント(@onnxai)の2018年3月の投稿を出典に、宝石のonyxと同じ発音(IPA: ˈɒnɪks)としており、日本語では「オニキス」「オニックス」と表記されます。
検索すると「ONNX」「onnx」「ONNX Runtime」が混ざって出てきますが、指しているものは別です。
| 名称 | 正体 | 現行版(2026-09-27時点) |
|---|---|---|
| ONNX | モデル形式と演算子の仕様 | IR 14/opset 28 |
| onnx(PyPI) | .onnxの作成・検証用Pythonライブラリ | 1.23.0(2026-09-18) |
| onnxruntime(PyPI) | Microsoftの推論エンジン | 1.30.0(2026-09-10) |
| Netron | .onnxのグラフ可視化ツール | 9.3.0(2026-09-25) |
pip install onnx で入るonnxパッケージには参照実装のReferenceEvaluatorがあり、動作確認用の推論もできます。本番の推論には、速度を重視して作られた onnxruntime(またはOpenVINO、TensorRTなど)を使います。
.onnxファイルの中身:グラフ・initializer・2つの版番号
ノードとinitializerで構成される計算グラフ
.onnxファイルは ModelProto というProtocol Buffersのメッセージで、その中の graph に「どの演算をどの順でつなぐか」が入っています。各ノードは MatMul や Relu などの演算子(operator)を1つ呼び出し、学習済みの重みは initializer として定数で埋め込まれます。次は、後述のコードで作った3ノードのモデルを onnx.printer.to_text() で文字列化したものです。
<
ir_version: 10,
opset_import: ["" : 21],
producer_name: "issoh-demo"
>
tiny (float[batch,3] x) => (float[batch,2] y)
<float[3,2] W = {1,2,3,4,5,6}, float[2] B = {0.5,-0.5}>
{
h = MatMul (x, W)
z = Add (h, B)
y = Relu (z)
}
入力 x の1次元目が batch という名前付きの次元になっている点に注意してください。ここを数値で固定するとバッチサイズを変えられないモデルになります。
IR versionとopsetの違い
ONNXモデルには版番号が2つあります。IR versionはファイル形式そのもの(どのフィールドを持てるか)の版、opsetは演算子セットの版で、たとえば同じ Resize でもopsetによって受け付ける属性が違います。onnxパッケージの版ごとの対応は、ONNX公式のVersioningドキュメントで次のように定められています。
| onnx | IR version | opset(ai.onnx) |
|---|---|---|
| 1.18.0 | 11 | 23 |
| 1.19.0 | 12 | 24 |
| 1.20.0 | 13 | 25 |
| 1.21.0 | 13 | 26 |
| 1.22.0 | 13 | 27 |
| 1.23.0 | 14 | 28 |
ランタイムは「自分が知っている最大のIR versionとopset」までしか読めません。この2つの番号が、後半で扱う版ズレ問題の原因になります。
大容量モデルのexternal data保存と2GiB制限
Protocol Buffersの直列化済みメッセージは2GiB未満でなければならないため、大きなモデルは重みを別ファイルに逃がす「external data」形式で保存します。512×512のfloat32行列を持つモデルで試すと、.onnx 本体は153バイト、重みファイルは1,048,576バイトに分かれました。
import numpy as np
import onnx
from onnx import helper, TensorProto, numpy_helper
W_big = numpy_helper.from_array(np.ones((512, 512), dtype=np.float32), name="W")
graph_big = helper.make_graph(
[helper.make_node("MatMul", ["x", "W"], ["y"])],
"big",
[helper.make_tensor_value_info("x", TensorProto.FLOAT, [1, 512])],
[helper.make_tensor_value_info("y", TensorProto.FLOAT, [1, 512])],
initializer=[W_big],
)
model_big = helper.make_model(
graph_big, opset_imports=[helper.make_opsetid("", 21)]
)
model_big.ir_version = 10 # このモデルはIR 10の機能だけを使用
onnx.checker.check_model(model_big, full_check=True)
onnx.save_model(
model_big, "big.onnx",
save_as_external_data=True,
all_tensors_to_one_file=True,
location="big.onnx.data",
size_threshold=1024,
)
この形式では .onnx と .onnx.data を同じディレクトリに置いたまま配布する必要があります。.onnxだけをコピーすると、読み込み時に重みファイルが見つからずに失敗します。
PythonでONNXモデルを作り、ONNX Runtimeで推論する手順
フレームワークを使わずにonnxパッケージだけでモデルを組み立て、ONNX Runtimeで実行する最小例です。onnx 1.23.0とonnxruntime 1.23.2(Python 3.12)で動作を確認しました。
pip install onnx==1.23.0 onnxruntime==1.23.2 numpy
import numpy as np
import onnx
import onnxruntime as ort
from onnx import helper, TensorProto, numpy_helper
W = numpy_helper.from_array(np.array([[1, 2], [3, 4], [5, 6]], dtype=np.float32), name="W")
B = numpy_helper.from_array(np.array([0.5, -0.5], dtype=np.float32), name="B")
x = helper.make_tensor_value_info("x", TensorProto.FLOAT, ["batch", 3])
y = helper.make_tensor_value_info("y", TensorProto.FLOAT, ["batch", 2])
nodes = [
helper.make_node("MatMul", ["x", "W"], ["h"]),
helper.make_node("Add", ["h", "B"], ["z"]),
helper.make_node("Relu", ["z"], ["y"]),
]
graph = helper.make_graph(nodes, "tiny", [x], [y], initializer=[W, B])
model = helper.make_model(
graph,
opset_imports=[helper.make_opsetid("", 21)],
producer_name="issoh-demo",
)
model.ir_version = 10 # 配布先のランタイムに合わせて固定する
onnx.checker.check_model(model, full_check=True)
onnx.save(model, "tiny.onnx")
sess = ort.InferenceSession("tiny.onnx", providers=["CPUExecutionProvider"])
for i in sess.get_inputs():
print(i.name, i.shape, i.type)
print(sess.run(None, {"x": np.array([[1, 0, 0], [0, 1, -1]], dtype=np.float32)}))
実行結果は次のとおりです。get_inputs() で入力名・形状・型が取れるので、受け取った.onnxファイルでも、この3つから入力テンソルの要件を確認できます。ただし、正規化係数、RGB・BGRの順序、トークナイズなどの前処理は、モデルの仕様や学習時の設定を別途確認する必要があります。
x ['batch', 3] tensor(float)
[array([[1.5, 1.5],
[0. , 0. ]], dtype=float32)]
sess.run の第1引数に None を渡すとすべての出力を返します。入力は型と形状を合わせたNumPy配列で渡します。np.ones((1, 3)) のようにfloat64の配列を渡すと、「Unexpected input data type. Actual: (tensor(double)) , expected: (tensor(float))」で止まります。
PyTorch・Keras・TensorFlowからのONNX変換
PyTorch:2.9以降はdynamo=Trueが既定
PyTorchの torch.onnx.export は、2.9で既定の変換方式が従来のTorchScriptベースから torch.export ベース(dynamo=True)に切り替わりました(PyTorch公式のONNXエクスポート解説)。2.14.0のソースでは、dynamo=False を指定すると「You are using the legacy TorchScript-based ONNX export」というDeprecationWarningが出ます。新方式はONNX Scriptを使うため、onnx と onnxscript を先に入れておきます。
import torch
model = MyModel().eval() # 学習済みのnn.Module
example = torch.randn(1, 3, 224, 224)
onnx_program = torch.onnx.export(
model,
(example,),
dynamo=True,
input_names=["input"],
output_names=["logits"],
dynamic_shapes=({0: "batch"},),
)
onnx_program.save("model.onnx")
旧方式からの移行で引っかかるのは3点です。可変バッチの指定は dynamic_axes ではなく dynamic_shapes を使う、戻り値が ONNXProgram になり .save() で書き出す、opset_version を省略すると2.14.0では20が使われる、の3つです。fallback 引数は2.11で削除されたため、古いサンプルをそのまま動かすとエラーになります。
Keras 3・TensorFlow:model.exportとtf2onnxの対応範囲
Keras 3(2026-07-29公開の3.15.1)は model.export("model.onnx", format="onnx") で直接ONNXを書き出せます。TensorFlowバックエンドでは内部でtf2onnxを呼び、JAX・PyTorchバックエンドではそれぞれの経路で変換します。
tf2onnxを単体で使う場合は、対応範囲を確認してください。tf2onnx 1.17.0のREADMEが検証対象としているのはTensorFlow 2.13〜2.15とPython 3.10〜3.12で、TensorFlowの最新版2.21.0は検証表にありません。既定のopsetは15で、READMEでは新しいメンテナーを募集しています。Keras側のソースにも「tf2onnxがNumPy 2に対応するまでの暫定パッチ」を当てる処理があり、tf2onnx単体で使う場合は、TensorFlow・NumPyの版と対象モデルを固定して変換を確認する必要があります。TensorFlowモデルを新規にONNX化するなら、Keras 3の model.export を先に試すほうが無難です。
scikit-learnのONNX変換と前処理パイプライン
scikit-learnのモデルは skl2onnx(1.20.0)で変換できます。各ステップに対応するコンバーターがある場合は、前処理のパイプラインごとONNXにでき、Python以外の言語での再実装を減らせます。独自の変換器などは、追加のコンバーター実装が必要です。
ONNX RuntimeでGPU・NPUを使う設定(Execution Provider)
ONNX Runtimeは、演算をどのハードウェアで実行するかを「Execution Provider(EP)」として切り替えます。providers に並べた順が優先順位で、公式ドキュメントは ['CUDAExecutionProvider', 'CPUExecutionProvider'] を「CUDAで実行できるノードはCUDA、できなければCPU」と説明しています。
| 実行先 | 主なEP | 入れるパッケージ |
|---|---|---|
| CPU | CPUExecutionProvider | onnxruntime |
| NVIDIA GPU | CUDA/TensorRT | onnxruntime-gpu |
| Intel CPU・GPU・NPU | OpenVINO | onnxruntime-openvino |
| Windows GPU | DirectML | onnxruntime-directml |
| AMD GPU | MIGraphX | onnxruntime_migraphx(AMD配布) |
| Apple | CoreML(preview) | onnxruntime |
| Qualcomm NPU | QNN | onnxruntime-qnn |
AMD GPU向けのROCm EPは1.23で削除され、公式ドキュメントはMIGraphX EPへの移行を求めています。古い記事の ROCMExecutionProvider を使うコードは現行版では動きません。
指定したEPが利用可能なEP一覧にない場合、警告を出してCPUで実行を続けることがあります。ただし、EPの初期化やCPUへのフォールバックも失敗すれば例外になります。CUDAの無い環境で providers=["CUDAExecutionProvider", "CPUExecutionProvider"] を指定すると、「Specified provider ‘CUDAExecutionProvider’ is not in available provider names」という警告が出るだけで、sess.get_providers() は ['CPUExecutionProvider'] を返しました。本番環境で「GPUのはずなのに遅い」ときは、まずこの戻り値を確認します。Intel環境でOpenVINOを直接使う選択肢はOpenVINOのライセンスと対応ハードウェアの解説にまとめています。
インストールでは、Intel Macに注意が必要です。onnxruntime 1.30.0がPyPIで配布しているmacOS向けwheelはarm64(macOS 14以降)だけで、Intel MacのPython 3.12で pip install onnxruntime を実行すると1.23.2が入りました。最新版の機能や対応opsetを前提にするなら、LinuxコンテナかApple Silicon機で動かします。
版の組み合わせで「読めないモデル」になる問題
ONNXでは、変換は成功しても推論側で読み込めないことがあります。原因の一つは、モデルに刻まれたIR version・opsetがランタイムの対応範囲を超えていることです。ほかに、未対応の演算子や外部重みファイルの欠落なども確認します。
onnx 1.23.0の既定値とONNX Runtimeの上限
onnx 1.23.0の helper.make_model で版を指定せずにモデルを作ると、IR 14・opset 28になります。これをONNX Runtime 1.23.2で読み込んだ結果が次のエラーです。
Unsupported model IR version: 14, max supported IR version: 11
IRだけを11に下げてopset 24にすると、今度は次のエラーで止まりました。opset 23以下にしたモデルは正常に推論できました。
ONNX Runtime only *guarantees* support for models stamped with official released onnx opset versions.
ONNX Runtimeは、ビルド時に取り込んだonnxのIR_VERSIONを上限として読み込みを拒否します。依存定義 cmake/deps.txt(v1.30.0のdeps.txt)を見ると、1.23.2はonnx 1.18.0(IR 11・opset 23)、最新の1.30.0はonnx 1.22.0(IR 13・opset 27)を取り込んでいます。つまり最新のONNX Runtime 1.30.0でも、onnx 1.23.0の既定値(IR 14・opset 28)のモデルは上限を超えます。onnxパッケージとランタイムは別々に更新されるため、新しいonnxの既定値がランタイムの対応範囲を超えることがあります。
互換性エラーの回避策:版の指定とモデル変換
- 作成時に固定する:
helper.make_opsetid("", 21)でopsetを、model.ir_version = 10でIRを指定します。 - 既存モデルのopsetを下げる:
onnx.version_converter.convert_version(model, 21)で変換できます。この例では変換後もIRは11のままですが、ONNX Runtime 1.23.2はIR 11に対応するため、変更は不要です。IR番号の書き換え自体は形式変換ではありません。下げる場合は、新しいIR固有のフィールドや型を使っていないことを確認し、検証と推論結果の比較を行います。 - PyTorchから書き出す場合:
opset_versionを省略すると20になるので、この問題は起きにくい側です。明示するときは配布先ランタイムの上限以下にします。
ONNX Runtimeの公式互換表(onnxruntime.aiのCompatibilityページ)は2026年9月時点で1.20までしか載っていません。新しい組み合わせを確認するときは、互換表ではなく対象バージョンの cmake/deps.txt を見るか、実際に読み込んで確かめるのが確実です。
Netronでの可視化と、量子化による軽量化
受け取った.onnxファイルの構造を確認するなら、Netron(9.3.0)が定番です。ブラウザ版かデスクトップ版でファイルを開くと、ノードのつながり、各ノードの属性、入出力の形状を図で確認できます。変換後に、元のモデルに無かった形状計算用のノードが大量に挟まっていないかを見るのにも使えます。
サイズと速度を詰めるなら、ONNX Runtimeの量子化機能を使います。上のexternal dataのモデル(重み1,048,576バイト)に quantize_dynamic でINT8の重み量子化をかけると、ファイルは262,732バイトになりました。
from onnxruntime.quantization import quantize_dynamic, QuantType
quantize_dynamic("big.onnx", "big_q.onnx", weight_type=QuantType.QInt8)
動的量子化は活性値のスケールとゼロ点を推論時に計算する方式で、公式ドキュメントはRNNやTransformer系に向くとしています。校正データで事前に計算する静的量子化(quantize_static)はCNN向けとされ、どちらも対象モデルはopset 10以上が必要です。精度評価の方法は量子化(モデル量子化)の仕組みとPTQ・QATの違いで扱っています。
ONNXを使わない方がよい場面
ONNXは「学習した環境と違う場所で推論したい」ときに効く道具です。次の条件に当てはまるなら、ONNX化を前提にしない方が工数を抑えられます。
- 学習も推論もPyTorchのサーバーで完結する:別環境への移植が不要なら、変換と検証の追加工数に見合う性能改善があるかで判断します。
torch.compileなどPyTorch側の最適化で足りるかを先に確認します。 - 大規模LLMをGPUサーバーで配信する:KVキャッシュの管理やバッチングはvLLM、TensorRT-LLMなどLLM専用の推論エンジンが担っています。構成の選び方はモデルサービングの構成と選定基準を参照してください。
- 自作の演算子(カスタムop)に依存している:自作演算をONNX標準演算子の組み合わせへ変換できない場合は、ランタイム側にもカスタム実装が必要となり、配布先ごとの対応が増えます。
逆に、C#・Java・JavaScriptのアプリへ組み込む、エッジ端末やブラウザで動かす、NPUや複数ベンダーのGPUに載せ分ける、といった用途ではONNXを中間形式にする価値があります。
よくある質問
ONNXの読み方は?
英語版Wikipediaは公式アカウント(@onnxai)の投稿を出典に、宝石のonyxと同じ発音としています。日本語では「オニキス」「オニックス」と書かれます。
.onnxファイルは何で開けますか?
構造を見るだけならNetronで開けます。中身をPythonで調べる場合は onnx.load("model.onnx") で読み込み、onnx.printer.to_text() で文字列化できます。推論に使うならONNX Runtimeの InferenceSession で読み込みます。
ONNXとONNX Runtimeの違いは?
ONNXはモデル形式の仕様、ONNX RuntimeはMicrosoftが開発する推論エンジンです。.onnxファイルはONNX Runtime以外にもOpenVINOやTensorRTなどで実行できます。
opsetはいくつを指定すればよいですか?
配布先のランタイムが対応する上限以下で指定します。ONNX Runtime 1.30.0が取り込んでいるのはonnx 1.22.0(opset 27)で、1.23.2ならopset 23が上限です。複数の環境に配る場合は、モデルに必要な演算子の版と、すべての配布先が対応するopset・IR・演算子実装を照合し、共通して使える版を明示指定して検証します。
ONNX Runtimeのインストール方法は?
CPUだけなら pip install onnxruntime、NVIDIA GPUを使うなら pip install onnxruntime-gpu です。PyPIのonnxruntime-gpuは1.27以降、CUDA 13.0向けが既定です。1.30.xもCUDA 13.0・cuDNN 9.x向けで、別途提供されるCUDA 12.x向けパッケージにはCUDA 12.8以上が必要です。1.30.0はPython 3.11以上が必要です。