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

AIダイエット失敗事件

握ったものを数える — 実行プロバイダと誤差の蓄積

TT Labで続きを見る

目標

要求した実行プロバイダーと、セッションが実際に手にしたプロバイダーを測り、サポートする演算子のリストに応じてグラフが何個の断片に分かれるかを数え、層の境界ごとに量子化誤差がどう育つかを表にするツールeprun.pyを作成します。

なぜ重要なのか

実行プロバイダーは、要求するものであって、保証されるものではありません。この環境で実際に測ると、存在しないプロバイダーをリストに入れても、例外は出ず、そのままCPUに落ちて動きます。そのため、「アクセラレーターを使っている」という思い込みが、ログに何の痕跡も残さないまま、長く続きます。確認する方法は1つだけです。セッションに尋ねることです。 プロバイダーは、モデル全体を担当するのではなく、ノードごとに担当できるものだけを引き取ります。そのため、サポートのリストがグラフの順序の上で途切れると、モデルが複数の断片に分かれ、断片の間ごとに値を受け渡します。断片の分かれ方が違えば、計算の順序が変わり、同じモデルに同じ入力を入れても、マシンごとに違う数が出ることがあります。 誤差の側にも、同じ姿勢が必要です。量子化誤差は、層ごとに新しく生まれ、前の層の誤差が次の層に入ります。ただし、層ごとに均等に育つわけではないので、話で済ませず、層の境界ごとに測って表にして初めて、どの層が問題なのかが見えます。 このPodにはプロバイダーが2つしかありません(AzureExecutionProvider・CPUExecutionProvider)。存在しないアクセラレーターをあるかのように扱わず、ここで観察できることだけを扱います。採点ツールは、毎回違うモデル・シード・サポートリストで皆さんのツールを実際に実行し、同じ計算をやり直して照合します。

ステップ

  1. /root/ep/gen_ep.pyを作成して実行し、モデルを2つ作ってください(出力先: /root/ep/fp32.onnx、/root/ep/int8.onnx)。層は5層以上です。
  2. /root/ep/eprun.pyにprovidersを作成し、要求リストごとの結果を書いてください(出力先: /root/ep/providers.json)。
  3. partitionを追加して、サポートする演算子のリストが与えられたとき、グラフが何個の断片に分かれるかを数えさせてください。
  4. exposeを追加して、層の境界のテンソルをグラフの出力として取り出したバージョンを作り、保存してください(出力先: /root/ep/fp32_exposed.onnx、/root/ep/int8_exposed.onnx)。
  5. layersを追加して、層の境界ごとに、最大絶対誤差・相対誤差・最小コサインを出力させてください。
  6. growthを追加して、層を重ねるにつれて誤差が育つ比率を出力させ、皆さんのモデルの結果を書いてください(出力先: /root/ep/growth.json)。
  7. settingsを追加して、同じモデル・同じ入力を、設定だけ変えて動かした結果を出力させ、書いてください(出力先: /root/ep/settings.json)。
  8. /root/ep/ep_report.mdを4つのセクションで書いてください。

参考

層を何重にも積んだモデルと、その整数バージョンを作る

/root/ep/gen_ep.pyを作成して実行し、モデルを2つ作ってください(出力先: /root/ep/fp32.onnx、/root/ep/int8.onnx)。線形演算子が5つ以上必要です。

層が2つや3つだと、誤差が育つ様子が見えません。5層以上積んでください。静的量子化にはCalibrationDataReaderが必要で、キャリブレーションデータは、テスト入力と同じ分布から取ればよく、同じサンプルである必要はありません。

要求したものと手にしたものを並べる

/root/ep/eprun.pyにproviders <model> <목록> [...]を作成し(プレースホルダーはリストです)、存在するプロバイダーだけを書いた要求と、存在しないプロバイダーを混ぜた要求の結果を書いてください(出力先: /root/ep/providers.json)。

存在しないプロバイダーを入れると例外が出そうですが、この環境ではそうなりません。自分で入れてみて、何が起きるかを測ってください。session.get_providers()が答えで、要求リストは答えではありません。例外を出すバージョンがあるかもしれないので、例外名も入れておいてください。そして、フォールバックの案内が標準出力に出るので、答えが混ざらないように止めてください。contextlib.redirect_stdoutが使えます。

グラフが何個の断片に分かれるかを数える

partition <model> <지원연산자목록>を追加して(プレースホルダーは、サポートする演算子のリストです)、その演算子だけをサポートするプロバイダーがあるとき、グラフがどう分かれるかをシミュレーションさせてください。断片の数と受け渡しの数を一緒に出力します。

実際の割り当てを真似るのではなく、サポートのリストがグラフの順序の上で途切れる場所を数えるのです。同じ担当者が続く区間が、1つの断片です。整数バージョンでMatMulだけをサポートすると置けば、Q/DQのせいで断片がどれだけ増えるかがすぐに見えます。

層の境界のテンソルを取り出す

expose <model> <out.onnx>を追加して、層の境界のテンソルをグラフの出力として付け足したバージョンを保存させてください(出力先: /root/ep/fp32_exposed.onnx、/root/ep/int8_exposed.onnx)。

整数バージョンでは、QuantizeLinearの出力はint8です。それをfloatとして取り出すと書けば、セッションが開きません。線形演算子が受け取る側のテンソルは、両方のグラフともfloatなので安全です。保存したバージョンを実際に開いて動かしてみてから、先に進んでください。

層の境界ごとに誤差を測る

layers <fp32> <int8> <seed>を追加して、シードで作った同じ入力を2つのバージョンに入れ、層の境界ごとに最大絶対誤差・大きさ・相対誤差・最小コサインを出力させてください。

絶対誤差だけを見ると、後ろの層が常に悪く見えます。値そのものが大きくなるからです。そのため、その層のテンソルの最大絶対値で割った相対誤差も一緒に出します。2つのグラフの境界の名前は違うので、位置の順で対応させてください。

育つ比率を表にする

growth <fp32> <int8> <seed>を追加して、層ごとの相対誤差とその比率、単調かどうか、全体の増加倍率、最も大きく跳ねた層を出力させ、皆さんのモデルの結果を書いてください(出力先: /root/ep/growth.json)。

測ってみると、比率が1より小さい区間も出てきます。活性化関数が一部を切り捨て、層ごとにスケールが新しく決まるからです。単調ではないこと自体が結果なので、そのまま書いてください。前の層の相対誤差が0なら割れないので、nullにします。

設定だけ変えて同じ入力を動かしてみる

settings <model> <seed>を追加して、同じモデル・同じ入力を、最適化レベルとスレッド数だけ変えて動かし、基準との差を出力させてください。皆さんのモデルの結果を書いてください(出力先: /root/ep/settings.json)。

このPodでは、4つのケースがすべて同じ値を出しました。その結果をそのまま書いてください。違わなければならないと取り繕いません。大事なのは、同じだったという事実ではなく、同じか違うかを測る手順が整っていることです。ほかのマシンでは、違う答えが出るかもしれません。

何がこのマシンでだけ真なのかを書く

/root/ep/ep_report.mdを、## 무엇을 요청했고 무엇을 쥐었나 ## 그래프가 쪼개지는 자리 ## 층을 거듭하며 자란 오차 ## 이 기계에서만 참인 것の4つのセクションで書いてください(韓国語の見出しは、順に「何を要求して何を手にしたか」「グラフが分かれる場所」「層を重ねて育った誤差」「このマシンでだけ真であること」という意味です)。使えるプロバイダーの数と、全体の誤差の増加倍率が、数字で入っている必要があります。

最後のセクションが、このレポートの核心です。ここで測った値のうち、どれがこのPodのプロバイダー構成とこの設定に結びついているかを書いてください。時間を測っていないことも書きます。