TT Lab
はじめる
学ぶ 学習パス コース

AIダイエット失敗事件

このファイルは何を約束しているのか — エクスポートで固まるもの

TT Labで続きを見る

目標

onnx.helperでMLPを自分で組み立てて/root/onnxq-export/mlp.onnxを作り、ONNXファイルに固定されているものを読み取るツール/root/onnxq-export/modelmeta.pyを作ります。バージョン番号を差し替えながらonnx.checkerとonnxruntimeに順に尋ね、チェッカーは通るのにランタイムが拒否する箇所を自分で見つけて、文書に残します。

なぜ重要なのか

ONNXファイルはグラフだけを持っているのではありません。どの演算子セットのバージョンで書いたか、IRバージョンはいくつか、誰が作ったか、何が入力で何がすでに決まった重みかが、エクスポートした瞬間に一緒に固定されます。受け取る側はその決定を変えられず、変えるには、もう一度エクスポートし直す必要があります。 そのため、「私たちのサーバーで開かない」という報告の原因は、たいてい変換オプションではなく、ファイルの冒頭にあります。バージョン番号を下げて保存し直しても、演算子の定義がそれに合わせて下がるわけではないので、チェッカーは通すのにランタイムだけが拒否するファイルができてしまいます。エラーメッセージが「バージョンが低い」ではなく「この演算子の実装がない」と出るため、原因がさらに見えにくくなります。 onnx.checkerも1層ではありません。デフォルトの検査は構造だけを見て、full_check=Trueを渡して初めてシェイプ推論まで実行されます。行列積が成り立たないMatMulは、デフォルトの検査をそのまま通過します。そして、2つの検査のどちらも、登録されていないドメインの演算子を防げません。 採点ツールは、皆さんが書き出した文言を信じません。一時ディレクトリに採点ツールが自分で作ったONNXファイルを用意し、皆さんのツールを実際に実行して、同じファイルを採点ツールが読んで得た答えと照合します。シェイプ・名前・バージョン番号・活性化関数は、実行のたびに変わります。

ステップ

  1. /root/onnxq-export/build_mlp.pyを作成して実行し、ファイルを作ってください(出力先: /root/onnxq-export/mlp.onnx)。
  2. /root/onnxq-export/modelmeta.pyにinfoを作り、バージョン番号・producer・入出力・initializer・ノードを読み取らせてください。
  3. infoにinput_overridesとruntime_inputsを追加し、initializerと入力の境界が見えるようにしてください。
  4. checkを追加し、onnx.checkerをデフォルトとfull_check=Trueの2回実行して、判定を別々に書かせてください。
  5. loadを追加し、onnxruntimeがセッションを開けるか、開けないならどんな例外かを書かせてください。
  6. stampを追加し、演算子はそのままにして、バージョン番号だけを差し替えて保存し直させてください。
  7. scanを追加し、このランタイムが開けるバージョンの範囲を実際に測らせ、自分のモデルの範囲を書き込んでください(書き込み先: /root/onnxq-export/opset_range.json)。
  8. 引き継ぎ文書を作成してください(出力先: /root/onnxq-export/export_report.json、/root/onnxq-export/export_report.md)。

参考

モデルを自分で組み立てる

/root/onnxq-export/build_mlp.pyを作成して実行し、ファイルを作ってください(出力先: /root/onnxq-export/mlp.onnx)。入力xは[シンボル, 8]、出力yは[シンボル, 4]で、MatMul・Add・Reluで2層を積みます。重みはinitializerとして固定します。

helper.make_tensor_value_infoのシェイプのリストに文字列を入れるとその軸はシンボル名になり、整数を入れると固定されます。重みはnumpy_helper.from_array(배열, 이름)で作り(プレースホルダーは配列と名前です)、make_graphの5番目の引数に入れます。その名前をノードの入力に使い、make_graphの入力リストには入れないでください。保存する前に、onnx.checker.check_model(model, full_check=True)で一度ふるいにかけてみてください。

ファイルに固定されているものを読み取る

/root/onnxq-export/modelmeta.pyにinfo <모델>(プレースホルダーはモデルです)を作り、ir_version・producer_name・opsets・inputs・outputs・initializers・nodesをJSONで出力させてください。

onnx.load(path)がModelProtoを返します。model.opset_importはドメインとバージョン番号のリストで、デフォルトのドメインは空文字列です。軸は、dim_paramがあればシンボル、dim_valueがあれば整数、どちらもなければnullとして書きます。inputsからは、initializerの名前を除いてください。その値は、ファイルの中にすでに入っています。

重みは入力ではない

infoの応答に、input_overrides(graph.inputとinitializerの両方にある名前)とruntime_inputs(セッションが実際に求める入力名)を追加してください。セッションを開けなければ、runtime_inputsはnullです。

IR 4より前は、initializerを必ずgraph.inputにも宣言する必要がありました。そのため、古いツールが作ったファイルには、重みが入力リストにも一緒に書かれていて、その名前は「デフォルト値のある入力」という意味です。ランタイムに尋ねると、その名前を必須入力として要求しないことがすぐにわかります。セッションを開くコードは、例外を握りつぶしてnullを返してください。開けないファイルも、infoでは読み取れる必要があります。

チェッカーは1層ではない

check <모델>(プレースホルダーはモデルです)を追加し、onnx.checkerをデフォルトとfull_check=Trueでそれぞれ実行して、{"checker", "full_check", "message"}を出力させてください。失敗した場合は、捕まえた例外の最初の1行をそのままmessageに入れてください。

デフォルトの検査は構造だけを見ます。full_check=Trueを渡すとシェイプ推論まで実行され、行列積が成り立たないMatMulや、宣言した出力シェイプと推論されたシェイプが違う場合を捕まえます。例外のメッセージを作り上げずに、str(exc)の最初の1行をそのまま写してください。採点ツールが、自分で仕込んだ名前がその中にあるかどうかを見ます。

ランタイムに直接尋ねる

load <모델>(プレースホルダーはモデルです)を追加し、onnxruntimeのセッションを開いてみて、{"load", "error_type", "message"}を出力させてください。error_typeは捕まえた例外のクラス名で、開けたらnullです。

チェッカーを通ったファイルでも、ランタイムは拒否することがあります。登録されていないドメインの演算子、バージョン番号が低すぎてそのバージョンに定義がない演算子、ランタイムがまだ開けない高いバージョンがそうです。拒否の種類がそれぞれ違うので、例外のクラス名まで書いておけば、次の人が原因をすぐに切り分けられます。

バージョン番号だけを差し替える

stamp <모델> <opset> <ir> <출력>を追加し、デフォルトドメインのopsetとir_versionだけを変えて、別のファイルに保存させてください(プレースホルダーはモデルと出力です)。ノード・重み・producer_nameはそのままにしておく必要があります。

model.opset_importを順に見て、ドメインが空文字列の項目のversionを書き換え、なければ新しく追加します。これは変換ではなく、スタンプを押す作業です。演算子の定義が、それに合わせて下がることも、上がることもありません。その事実を、次のステップで目で確かめることになります。

このランタイムが開けるバージョンの範囲を測る

scan <모델>(プレースホルダーはモデルです)を追加し、バージョン番号を1から27まで差し替えながら、セッションが開くかどうかを測って、{"min_ok", "max_ok", "ok", "failed"}を出力させてください。そして、自分のモデルの範囲をmin_ok・max_okとして書き込んでください(書き込み先: /root/onnxq-export/opset_range.json)。

前のステップのstampとloadを、そのままつなげればできます。一時ディレクトリに出力して開いてみるだけでよいので、元のファイルには手を触れないでください。下限は、モデルが使う演算子がどのバージョンから定義されたかによって決まり、上限は、ランタイムがどこまで開けるかによって決まります。2つの数字の出どころが違うという点が、このステップの核心です。

引き継ぎを1枚に残す

/root/onnxq-export/export_report.jsonに、モデルから読み取った値とバージョンの範囲、そしてチェッカーは通るのにランタイムが拒否するバージョンを入れたchecker_ok_runtime_errorを書き、Markdownのレポートを4つのセクションで書いてください(出力先: /root/onnxq-export/export_report.md)。

checker_ok_runtime_errorのopsetは、開ける下限より低いバージョンであればかまいません。そのバージョンで出力したファイルをcheckとloadにそれぞれ尋ねて、実際に出た判定を書いてください。採点ツールも、同じファイルを作って確認し直します。レポートには下限と上限を数字で書く必要があります。そうすれば、受け取る側が自分のランタイムと見比べられます。