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

AIダイエット失敗事件

float の島を数える — 演算子ごとの対応と置き換え

TT Labで続きを見る

目標

同じモデルを5通りで出力したあと、グラフの中でint8に包まれた演算子と、floatのまま残った島を数える分析ツールgraphscan.pyを作成します。対象範囲を絞る2つのオプションの効果を数字で確認し、モデルをGemmに直して、Q/DQの往復を減らします。

なぜ重要なのか

量子化ツールは、自分が知っている演算子だけをint8の形に変えます。QDQ形式では、演算子名がそのまま残り、テンソルごとにQuantizeLinearとDequantizeLinearが挟まれるだけです。そのため、「量子化した」と「int8で動く」は別の話で、両者を分けるには、グラフを数える必要があります。 このラボは、判定の規則を次のように固定します。すべての入力がDequantizeLinearから来て、すべての出力がQuantizeLinearにだけ行く演算子を、int8に包まれたものと見なします。残りはfloatのまま残り、つながったものどうしをまとめると、float島になります。 島は、数より境界が問題です。島ごとに、入ってくるDQと出ていくQが付き、境界ごとに、値を詰め替える作業と丸めが1回ずつ増えます。島を減らす道は2つです。対象範囲を絞って、外れるノードをそもそも除くか、モデルを直して、量子化される形にするかです。 採点ツールは、皆さんが書いた数字を信じません。毎回違う層の数・形・除外ノードで自分のモデルを一時ディレクトリに作り、皆さんの分析ツールを実際に実行して、採点ツールが同じ規則で数えた値と照合します。ステップ6・7は、皆さんが作ったモデルファイルを採点ツールが直接読んで、数え直します。

ステップ

  1. /root/ops/gen_models.pyを作成して実行し、/root/opsの下にモデルを5つ作ってください。
  2. /root/ops/graphscan.pyにnodesを作成して、演算子のcensusと、初期値のデータ型を数えさせてください。
  3. qdqを追加して、QuantizeLinear・DequantizeLinearと、Q/DQの往復を数えさせてください。
  4. islandsを追加して、int8に包まれた演算子とfloat島を分けさせてください。
  5. boundaryを追加して、島ごとに出入りする境界を数えさせてください。
  6. diffを追加して、2つのモデルの差を出力させ、対象範囲を絞った結果を書いてください(出力先: /root/ops/scope.json)。
  7. MatMulとAddをGemmにまとめたモデル(/root/ops/gemm.onnx)と、それを量子化したモデル(/root/ops/gemm_full.onnx)を作り、結果を書いてください(出力先: /root/ops/fuse.json)。
  8. /root/ops/ops_report.mdを4つのセクションで書いてください。

参考

同じモデルを5通りで出力する

/root/ops/gen_models.pyを作成して実行し、/root/opsの下にfp32.onnx・full.onnx・matmul_only.onnx・excluded.onnx・dynamic.onnxを作ってください。

onnx.helperでMatMul・Add・Reluをつなげ、ノードごとに名前を付けてください。名前があってこそ、あとでnodes_to_excludeで1つを除けます。静的量子化にはCalibrationDataReaderが必要で、同じキャリブレーションデータを何度も使うときは、リーダーをそのたびに新しく作ってください。一度読み切ったリーダーは、もう一度は返してくれません。

何が入っているかから数える

/root/ops/graphscan.pyにnodes <model>を作成して、演算子のcensusと、初期値のデータ型のcensusを出力させてください。

初期値のデータ型は、onnx.TensorProto.DataType.Name(init.data_type)で名前が得られます。静的QDQバージョンと動的バージョンを並べて数えると、形式がどれほど違うかがすぐにわかります。動的バージョンには、MatMulがそもそもありません。

Q/DQの往復を数える

qdq <model>を追加して、quantize・dequantize・round_trips・weight_dequantizeを数えさせてください。

重みに付くDequantizeLinearには、対になるQuantizeLinearがありません。重みはすでにint8でファイルに入っているからです。そのため、DequantizeLinearの数からその分を引いて初めて、活性化値の往復数と合います。

floatのまま残った島を見分ける

islands <model>を追加して、int8に包まれた演算子とfloatのまま残った演算子を分け、つながったものどうしをまとめて、島として出力させてください。

先に、生産者・消費者の表を作っておくと、残りが楽になります。包まれているかの判定は、参考のセクションの規則をそのまま使ってください。島をまとめるときは、floatノードの間のつながりだけをたどります。QやDQを通ったら、別の島です。

島ごとに境界を数える

boundary <model>を追加して、島ごとに入ってくるDequantizeLinearの数と、出ていくQuantizeLinearの数を数え、全体の境界の数を出力させてください。

同じDequantizeLinearが、1つの島の2つのノードに入ることがあります。ノードごとに数えず、集合にまとめて数えてください。島の出力がグラフの出力にそのまま出ていく場合、出ていくQuantizeLinearが0になることがあります。

対象範囲を絞ると何が変わるのか

diff <a> <b>を追加して、2つのモデルの差を出力させ、3つのバージョンの数値と2つのdiffを、参考のセクションの形で書いてください(出力先: /root/ops/scope.json)。

オプションを渡したからといって、そのとおりになったと信じないでください。ノード名で除いたバージョンでは、そのノードの重みがfloatの初期値として残っていて、演算子の種類で絞ったバージョンでは、その種類以外の演算子が丸ごと島になります。2つの絞り込みは、グラフに残す痕跡が違います。

モデルを直して往復を減らす

MatMulとAddをGemm1つにまとめて、モデルを作ってください(出力先: /root/ops/gemm.onnx)。それを同じ設定で量子化したモデルも作り(出力先: /root/ops/gemm_full.onnx)、2つのバージョンの数値と最大絶対誤差を書いてください(出力先: /root/ops/fuse.json)。

まとめる条件を守ってください。MatMulの出力が、そのAddでだけ使われていて、Addのもう一方の入力が初期値のときだけ、まとめます。まとめたあとは、onnx.checker.check_modelを通る必要があり、オリジナルと同じ入力で動かして、答えが同じかどうかを必ず確認してください。値が変わったなら、まとめ方が間違っています。

グラフで答えられるように書く

/root/ops/ops_report.mdを、## 무엇이 int8 로 갔나 ## float 로 남은 섬 ## 범위를 좁히면 무엇이 달라지나 ## 모델을 고쳐 얻은 것の4つのセクションで書いてください(韓国語の見出しは、順に「何がint8に行ったか」「floatのまま残った島」「対象範囲を絞ると何が変わるか」「モデルを直して得たもの」という意味です)。島の数と、消えたQ/DQの数が、数字で入っている必要があります。

このレポートは、「なぜ期待したほど減らなかったのか」という質問に答える文章です。数えてみた5つの数字(演算子のcensus・Q/DQの往復・島の数・島のサイズ・境界の数)をそのまま書けば、答えになります。時間を測っていないことも書いてください。