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

AIダイエット失敗事件

ノードが6個から2個へ — 最適化が変えたものを数える

TT Labで続きを見る

目標

最適化が手を加える材料を集めた/root/onnxq-optimize/graph.onnxを自分で組み立て、ORTの4つの最適化レベルを実行してグラフをファイルに取り出し、ノードを数えるツール/root/onnxq-optimize/optlevel.pyを作ります。レベルごとに何が消えて何ができるかを書き、答えが同じかどうかを測り、取り出したファイルをほかのマシンに移してはいけない理由を、ドメイン名で証明します。

なぜ重要なのか

onnxruntimeは、セッションを開くときにグラフを書き直します。定数だけで計算できる部分を先に畳み込み、なくてもよいノードを削除し、よく出てくる形を1つにまとめます。何がどこまで変わるかは、最適化レベルが決めます。 量子化の前後で速度を比べるときに数字が合わない原因が、ここにある場合が多くあります。2つの測定の最適化レベルが違うと、その比較は量子化を測ったのではなく、最適化を測ったことになります。ベースラインをORT_DISABLE_ALLで固定しておけば、この揺れはなくなります。 もっと高くつく事故は、最適化されたファイルを保存してデプロイすることです。そのファイルには標準ではないドメインのノードが入っているので、ほかのランタイムが開けません。ところが、onnx.checkerはそのファイルを通します。チェッカーは知らないドメインを通り過ぎるからです。そのため、移植可能かどうかの根拠は、チェッカーではなく、ノードのドメインのリストです。 このラボは時間を測りません。このマシンはエミュレーションなので、同じ作業でも時間が2倍まで揺れます。そのため、構造がどう変わったかと、答えが同じかどうかだけを判定します。 採点ツールは、皆さんが書き出した文言を信じません。一時ディレクトリに採点ツールが自分で作ったグラフを用意し、皆さんのツールを実際に実行して、同じファイルを採点ツールが4つのレベルで最適化して得た値と照合します。シェイプ・重み・乱数シードは、実行のたびに変わります。

ステップ

  1. /root/onnxq-optimize/build_graph.pyを作成して実行し、ファイルを作ってください(出力先: /root/onnxq-optimize/graph.onnx)。
  2. /root/onnxq-optimize/optlevel.pyにnodesを作り、現在のグラフのノードと個数を読み取らせてください。
  3. optimizeを追加し、指定されたレベルで最適化したグラフをファイルに取り出させてください。
  4. levelsを追加し、4つのレベルをすべて実行して、ノードを数えて並べさせてください。
  5. fusionを追加し、レベルごとに何が消えて何ができたかを書かせてください。
  6. equalを追加し、指定されたシードで作った同じ入力を4つのレベルに入れて、答えが同じかどうかを測らせてください。
  7. portableを追加し、取り出したファイルが標準の演算子だけを使っているかどうかを判定させてください。
  8. レポートを作成してください(出力先: /root/onnxq-optimize/opt_report.json、/root/onnxq-optimize/opt_report.md)。

参考

最適化が手を加える材料を集める

/root/onnxq-optimize/build_graph.pyを作成して実行し、ファイルを作ってください(出力先: /root/onnxq-optimize/graph.onnx)。入力がすべてinitializerであるノードを1つ、Identityを1つ、そしてMatMul・Add・Relu・Mulを入れる必要があります。

定数畳み込みを見るには、2つのinitializerを足すノードを入れて、その結果をあとで使えばよいです。Identityは何もしないので、なくてもよいノードの例としてぴったりです。MatMulのあとにAddを置き、その次にReluを置くと、融合される形になります。保存する前に、onnx.checker.check_model(model, full_check=True)でふるいにかけてみてください。

現在のグラフを数えておく

/root/onnxq-optimize/optlevel.pyにnodes <모델>(プレースホルダーはモデルです)を作り、{"nodes", "count", "initializers"}を出力させてください。nodesの各項目には、op_type・domain・nameを入れます。

ノードのdomainは、標準の演算子なら空文字列です。今はすべて空文字列でしょうが、最適化されたファイルを開くと、そうではないものが出てきます。そのときに比べるために、今のものを先に数えておく必要があります。順序は、ファイルに書かれたままにしてください。

最適化されたグラフをファイルに取り出す

optimize <모델> <단계> <출력>を追加し、そのレベルで最適化したグラフを出力パスに保存して、保存されたファイルを読み直してノードを数えて出力させてください(プレースホルダーは、モデル、レベル、出力です)。

SessionOptions.optimized_model_filepathにパスを渡すと、セッションを開く間に書き直したグラフが、そのパスに保存されます。セッションを開かなければ、ファイルもできません。ORT_DISABLE_ALLで取り出したものがベースラインで、元のファイルと同じノードが出るのが正常です。

4つのレベルを並べる

levels <모델>(プレースホルダーはモデルです)を追加し、4つのレベルをすべて実行して、レベルごとに{"nodes", "count"}を入れたオブジェクトを出力させてください。

一時ディレクトリにレベルごとに1つずつ取り出して、読めばよいです。終わったら削除してください。4行を並べると、どのレベルで何が起きるかが一目でわかります。ドキュメントを読むよりこちらのほうが速いです。

何が消えて何ができたか

fusion <모델>(プレースホルダーはモデルです)を追加し、レベルごとに{"removed", "added", "total"}を出力させてください。removedは元のグラフにあったのに消えたop_type、addedは新しくできたop_typeです。

元のop_typeの集合と、各レベルのop_typeの集合を比べればよいです。定数だけで計算されていたノードは消え、その結果がinitializerとして収まります。MatMulのあとのAddは、Gemm 1つにまとめられ、次のレベルでは活性化まで飲み込んだ名前が出てきます。その名前のドメインが何かにも、注目してください。

グラフは変わっても、答えは同じか

equal <모델> <씨앗> <행수>を追加し、そのシードで作った同じ入力を4つのレベルすべてに入れて、{"seed", "rows", "levels", "max_abs_diff"}を出力させてください(プレースホルダーは、モデル、シード、行数です)。

入力は、numpy.random.RandomState(씨앗).standard_normal((행수, 입력너비))をfloat32にして作ります(プレースホルダーは、シード、行数、入力の幅です)。入力の幅は、モデルの宣言の軸1から読んでください。レベルごとに、出力全体の合計と絶対値の最大を書き、ベースラインとの最大絶対差も書きます。正確に同じになることを期待せず、許容誤差を決めておいてください。float32の有効桁は7桁しかありません。

このファイルを移してよいか

portable <모델>(プレースホルダーはモデルです)を追加し、ORT_ENABLE_ALLで取り出したファイルを読んで、{"nondefault_domains", "opset_domains", "checker", "portable"}を出力させてください。

標準のドメインは、空文字列とai.onnxだけです。それ以外のドメインを持つノードが1つでもあれば、そのファイルはその環境専用です。onnx.checkerはそういうファイルも通すので、チェッカーの結果とドメインのリストを一緒に書いておいてください。2つが食い違うことが、このステップの核心です。log_severity_levelを2にすると、ORTが保存しながら出す警告も見えます。

4つのレベルを1枚で報告する

/root/onnxq-optimize/opt_report.jsonにlevels・nondefault_domains・portable・equalityを書き、## 무엇이 줄었나、## 어느 단계에서 무엇이 바뀌나、## 답은 같은가、## 이 파일을 옮겨도 되나の4つのセクションでMarkdownのレポートを書いてください(出力先: /root/onnxq-optimize/opt_report.md。韓国語の見出しは、順に「何が減ったか」「どのレベルで何が変わるか」「答えは同じか」「このファイルを移してよいか」という意味です)。

採点ツールが同じグラフを自分で4つのレベルで最適化してノードを数え、equalityに書かれたシードと行数でもう一度動かして、差を測ります。そのため、実際に測って書いてください。レポートには、最適化前のノード数と、残ったドメイン名を、数字と名前のままで書く必要があります。そうすれば、受け取る側が判断できます。