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

AIダイエット失敗事件

較正データなしで — 動的量子化がグラフに施すこと

TT Labで続きを見る

一言でいうと

動的量子化は、重みだけを先にint8に固定し、活性化値の範囲は推論のたびにその場で測ります。そのためキャリブレーションデータがなくてもよく、その代わりにバッチごとに別の物差しを使います。

なぜこの方式が必要なのか

静的量子化を行うには、代表的な入力が必要です。活性化値がおおよそどの範囲にあるかを先に見て、スケールを固定する必要があるからです。ところが、現場ではそのデータを手に入れる作業が最も時間がかかります。顧客データは受け取れず、合成データは分布が違い、受け取ったデータは評価用と混ざっています。

動的量子化は、その問題をまるごと避けて通ります。活性化値の範囲を事前に決めず、実行時にそのテンソルを見て決めます。変換に必要なのはモデルファイル1つだけで、コマンド1行で終わります。そのため「とりあえずこれでどれくらい出るか見てみよう」ということができ、静的量子化を行うかどうかを決めるベースラインになります。

どう動くのか

ONNX Runtimeの量子化ドキュメントが、2つの方式を並べて説明しています。動的のほうは、重みだけをオフラインで整数に変え、活性化値のスケールとゼロポイントは実行中に計算します。

グラフを開いてみると、何が起きたかがすぐにわかります。MatMul1つが、このように変わります。

바뀌기 전   X(float32) ──▶ MatMul(W float32) ──▶ h(float32)

바뀐 뒤     X(float32) ──▶ DynamicQuantizeLinear ──▶ (q uint8, scale, zero_point)
                                 │
                                 ▼
                           MatMulInteger(W int8) ──▶ (int32)
                                 │
                            Cast ──▶ Mul(scale 들) ──▶ h(float32)

MatMulは消え、その場所にDynamicQuantizeLinear・MatMulInteger・Cast・Mulが入ります。最初のノードが活性化値をその場でuint8に下げながらスケールとゼロポイントも一緒に出し、整数カーネルが掛け算をしたあと、2つのスケールを掛けて実数に戻ります。

DynamicQuantizeLinearの定義は短いものです。データの最小値と最大値に0を加えて範囲を決め、その幅を255で割ってスケールを作り、0.0が収まる整数の位置をゼロポイントとします。この計算が推論のたびにもう一度行われるということが、この方式のすべてです。

ファイルサイズが減る場所も決まっています。initializerを1つずつ数えてみると、行列だけが減り、バイアスはそのままfloat32で残ります。そして、減った行列の隣には、スケールとゼロポイントが小さなinitializerとして付いてきます。「量子化すれば4分の1」という通念は、行列がファイルの大部分を占める場合にだけ当てはまる言葉です。

現場での姿

1つ目は、入力の大きさが変わっても耐えることです。入力を1000倍大きくしても、相対誤差がほとんどそのままです。バッチごとに物差しを作り直すからです。静的量子化は、同じ状況ではキャリブレーションのときに見た範囲の外に出て、まるごと切り捨てられます。キャリブレーションデータが実際の分布と違うリスクがないことが、この方式の最大の長所です。

2つ目は、同じバッチに混ざった隣の行が、互いを台無しにすることです。スケールがそのテンソル全体の最大値で決まるため、1行だけ極端に大きな値が入ってくると、残りの行が使える目盛りが減ります。単独で入れたときは問題なかった入力が、バッチに混ざると答えが悪くなります。これは、同じ入力なのに結果がバッチの構成によって変わるという意味であり、再現が必要な場面では、すぐに問題になります。

3つ目は、小さなモデルではファイルがかえって大きくなることです。重みが数百バイトしかないモデルに動的量子化をかけると、新しく入ったノードとスケール・ゼロポイントのinitializerのほうが、減った量より大きくなります。測らずに「小さくなっただろう」と済ませると、そのままデプロイされます。

4つ目は、範囲を測る作業が推論のたびに起きることです。スケールを求めるには、活性化テンソルを最初から最後までなめて、最小値と最大値を探さなければなりません。グラフにDynamicQuantizeLinearがいくつあるかが、そのなめる作業が推論のたびに何回行われるかです。時間を測る前に、ノード数を先に数えてみれば、どこにコストがかかるかがわかります。

実務で本当に大切なこと

次のラボですること

dyntool.pyを作って自分のモデルを動的量子化し、グラフの変化とinitializerごとのバイト数を数え、DynamicQuantizeLinearが推論のたびに行う計算を手で実装し、入力の倍率を1000倍まで振りながら誤差を測り、外れ値1行がバッチをどう揺らすかを測り、ごく小さなモデルでファイルが大きくなることを確認します。採点ツールは、毎回異なるシードとシェイプでモデルとバッチを新しく用意し、皆さんのツールを実際に動かして、同じ計算をやり直して照合します。