TT Lab
ブログ

한국어 English 日本語 中文

はじめる
ブログ

ブログ

ビット数を減らす:FP16・BF16・INT8 量子化、事後量子化と量子化認識学習、較正データ

状態:ドラフト(2026-10-16)、一部実測値。 GPUの測定は、ホームラボの8GBクラスのノートPC用GPU1枚で使い捨てPodを使用して行いました(ウォームアップ後の繰り返しの中央値)。精度比較は公開データセット OpenTTGames のテストマッチ1試合(マーカー付き60fpsフレーム388枚)です。INT8は同PodのCPU(4コア制限)でPyTorchの事後量子化を用いて行いました。形式と較正の原理はノートPCのCPUの例(code/02_quant_error.py、1秒前後)で確認しました。 シリーズ「ビジョンモデルを読む 3」の第2部。レイテンシーバジェットは 第1部にあります。

目次

1. なぜビット数を減らすと高速になるのか

ニューラルネットワークの推論にかかる時間は、大半が2つの要素に起因します。メモリから数値を読み込む時間と、乗算および加算を行う時間です。数値を1つ、32ビットから16ビットに減らすと、読み込むバイト数が半分になります。さらに、最近のGPUやモバイルチップには、16ビットや8ビットの乗算を一度に大量に行える専用プロセッサが搭載されています。NVIDIAの2018年世代のGPU仕様では、INT8の演算スループットはFP32の16倍、FP16は8倍です(Wu他 2020、表1 — 仕様上の比率であり実測値ではありません)。

その代償となるのが精度です。数値を粗く表現すると丸め誤差が生じ、範囲が狭いと大きな数値がオーバーフローします。

숫자 형식과 INT8 눈금 비트 나눔(가로 길이 = 비트 수) FP32 지수 8 가수 23 FP16 지수 5 가수 10 최대 65,504 BF16 지수 8 가수 7 범위 = FP32 INT8 정수 8 −128 … 127 s = 부호. 지수는 범위를, 가수는 촘촘함을 정한다 INT8 양자화: 실수 r 을 0–255 눈금 q 에 맞춘다 q=0 q=255 잘림 최솟값 상한 가장 가까운 눈금으로 반올림 → 255 r = S · (q − Z), S = (상한 − 최솟값) ÷ 255 범위를 넓게 잡으면 잘림은 줄지만 눈금 간격 S 가 커진다 범위를 좁게 잡으면 눈금은 촘촘하지만 큰 값이 잘린다
図:FP32・FP16・BF16・INT8のビット配分と、INT8量子化が実数を0〜255の目盛りに合わせ、上限を超える値を切り捨てる様子。範囲を広く取ると切り捨ては減るが、目盛りが荒くなる

2. 16ビットの2種類:FP16とBF16

浮動小数点数は、符号・指数・仮数に分かれます。指数は「どれほど大きまたは小さな数値まで表せるか」、仮数は「どれほど細かく表せるか」を決定します。

形式 符号・指数・仮数 最大値 1の次の値との間隔(eps)
FP32 1 · 8 · 23 3.4×10³⁸ 1.19×10⁻⁷
FP16 1 · 5 · 10 65,504 9.77×10⁻⁴
BF16 1 · 8 · 7 3.4×10³⁸ 7.81×10⁻³

(code/02_quant_error.out.txt におけるPyTorchの値。)FP16は精度が高いものの範囲が狭く、BF16はFP32と範囲が同じである代わりに精度が粗いです。実際に代入してみると、70,000はFP16ではinfになり、BF16では70,144になり、1e-8はFP16では0になり、BF16では1.00117e-8になります。256×256の行列乗算の相対誤差は、FP16が3.6×10⁻⁴、BF16が2.9×10⁻³でした。

学習においては、この範囲の問題が大きくなります。Micikevicius他による「Mixed Precision Training」(2018)では、FP16において2⁻²⁴より小さい勾配が0になってしまう問題を、損失に大きな数を掛けた後に割り算を行う ロススケーリングによって解決し、重みのFP32の複製を別途保持しました。これにより、ResNet-50のImageNet top-1精度は、FP32の75.92と混合精度での76.04で同等でした(表1)。BF16は範囲がFP32と同じであるためロススケーリングなしで使用でき、Kalamkar他(2019)はResNet-50をBF16で学習させ、FP32と同等の74.7を達成しました(同一の評価手法同士での比較、4.1.2節)。

推論ではすでに学習済みの重みを使用するため、範囲の問題は小さくなります。活性化値が65,504を超えない限り、多くの場合FP16で十分です。

3. INT8:実数を目盛りに合わせる

8ビット整数には256個の枠しかありません。Jacob他(2018)のアフィン量子化は、実数 r を次のように記述します。

r = S · (q − Z)
S: 눈금 한 칸의 크기(실수),  Z: 실수 0 에 해당하는 정수(영점),  q: 0–255 의 정수
범위 [최솟값, 상한] 을 정하면  S = (상한 − 최솟값) / 255,  Z = round(−최솟값 / S)

実数の0が正確に1つの整数として表されるように Z を配置することがポイントです。0で埋めるパディングやReLUの背後にある0が誤差なく表現されます。乗算は uint8 × uint8 を int32 で加算した後に最後に一度だけ倍率を掛け直して再び8ビットに縮小するため、推論全体を整数のみで実行できます。同論文では、バッチ正規化を畳み込みにあらかじめ畳み込んだ(第3部)グラフを量子化しています。

結果(Jacob他 2018) 実数 INT8 条件
ResNet-50 ImageNet top-1 76.4 74.9 表4.1
MobileNet SSD(幅50%) COCO mAP · レイテンシー 16.7 · 121ms 16.6 · 61ms 2017年のモバイル向け大コア、単一スレッド(表4.4)
顔検出(幅25%)レイテンシー 44ms(約23fps) 28ms(約36fps) 同一のモバイル向け大コア1つ(表4.6)

同じ33msのバジェット内であれば、INT8モデルの方が実数モデルよりも約10%精度が高かったというのがこの論文の要点です(4.2.1節、モバイルの小さなコア)。精度を下げて浮いた時間で、より大きなモデルを実行できるためです。

目盛りをいくつ設けるか:テンソル全体かチャンネルごとルか

重みテンソル全体に S を1つだけ使用すると、特定のチャンネルの値が大きいだけで目盛りがそのチャンネルに合わせて広がり、残りが潰れてしまいます。出力チャンネルごとに S を個別に設けると(per-channel)、この問題が解消されます。実際に試してみたところ(256×256行列乗算、重みのみINT8)、

重み テンソル全体 チャンネルごと
均一な重み 1.06×10⁻² 7.13×10⁻³
1つのチャンネルだけ20倍 7.25×10⁻²(その他のチャンネル 1.13×10⁻¹) 6.32×10⁻³

となります。実際のモデルでも同様の現象が発生します。Krishnamoorthi(2018)のホワイトペーパーでは、MobileNet-v1を学習なしで8ビットに変更した場合、テンソル全体の目盛りではtop-1が0.001(完全に崩壊)、チャンネルごとの目盛りでは0.703(実数は0.709)でした(表3)。深度方向別畳み込み(第4部)はチャンネルごとに値の大きさが大きく異なるため、この問題に特に脆弱です。

4. 事後量子化(PTQ)と較正データ

事後量子化は、学習が完了したモデルに代表的な入力データを数百枚流し込み、レイヤーごとに活性化値の範囲を測定し(較正、calibration)、その範囲から S と Z を決定します。学習は不要です。Nagel他(2021)のホワイトペーパーでは、ラベルなし画像が500〜1,000枚あれば通常は十分であると述べられており、標準的な手順に従えば W8A8(重み・活性化値ともに8ビット)で扱ったすべてのモデルにおいてFP32の0.7%の範囲内であったと報告されています。

難しいのは 範囲をどこまで設定するかです。最小値と最大値をそのまま使用すると、稀にある1つの大きな値のせいで目盛りが粗くなってしまいます。上部を少し切り詰める(例:99.99パーセンタイル)ことで範囲を狭めると、目盛りは細かくなりますが、切り捨てられた値が不正確になります。2種類の活性化値を用いて実際に試してみました(較正用サンプルとテスト用サンプルは別途抽出)。

活性化値 較正バッチ 最小値〜最大値 99.99パーセンタイル 99.9パーセンタイル
(ア)裾野の長い分布 1 上限 23.1、誤差 0.057 21.0, 0.067 11.0, 0.158
(ア) 64 上限 215.1、誤差 0.144 22.7, 0.059 10.3, 0.170
(イ)稀にある大きな値がシグナルとなる場合 64 上限 109.4、誤差 0.076 51.0, 0.180 4.1, 0.707

そのため、較正方法はモデルごとに検証する必要があります。Wu他(2020)の表によると、EfficientNet-B0に対してPTQを適用した場合、最小値〜最大値が22.3%、エントロピーが72.1%、99.9パーセンタイルが70.9%であり(FP32は76.9%)、Inception v4では最小値〜最大値が0.1%、99.99パーセンタイルが79.6%でした(FP32は79.7%)(ImageNet top-1、較正画像1,024枚、表5)。

5. 量子化認識学習(QAT)

PTQでは不十分な場合、学習の段階から量子化を模倣します。順伝播において、重みと活性化値を「量子化してから元に戻した」値(偽量子化値)として計算し、逆伝播では丸め処理が存在しないかのように勾配をそのまま流します。モデルが目盛りに合わせて重みを調整するように学習させる仕組みです。Krishnamoorthiのホワイトペーパーでは、テンソル全体の目盛りで崩壊していたMobileNet-v1(0.001)が、QATによって0.70まで回復し(表4)、Wu他ではEfficientNet-B0がQATにより76.95%となり、PTQの最良値(72.1%)を上回りました(元の学習スケジュールの約10%分をファインチューニング、表7)。その代償として、学習パイプラインとデータが必要になる点があげられます。

6. 自前の実測:ボールモデルのFP16

速度

TT Scoreのボールモデル(BlurBall、3フレームウィンドウ、512×288)を実験用GPUで測定しました(バッチ1、ウォームアップ20回後の250回の中央値)。

精度 単一ウィンドウ
FP32(運用方式) 15.09ms
FP16、autocast(演算ごとに自動で16ビット化) 9.77ms(1.54倍)
FP16、モデル全体をhalf化 9.49ms(1.59倍)

同じGPU上で、トランスフォーマーエンコーダー(EfficientTAM-S、第4部)は3.1倍でした。ボールモデルは解像度を最後まで維持する畳み込みネットワークであるため、16ビット専用プロセッサの恩恵が相対的に小さく、バッチ正規化やReLUといった要素ごとの演算が残っているためです。第3部でこれらをコンパイラによって統合したところ、4.3msまで低下しました。

精度:ボールの位置が変わるか

OpenTTGamesのテストマッチ1試合において、ボールの位置マーカーが付いた60fpsフレーム388枚(すべてボールが存在)を対象に、運用時と同じ方式(最後のフレームのヒートマップ、閾値0.5でのブロブ検出)でFP32とFP16を同じ入力に対して実行しました。正解から5px(512×288基準)の範囲内であれば正解とみなします。同じフレームをJPEG品質70で圧縮・展開した入力(ブラウザが送信するものと同一の条件)に対してもFP32を実行しました。

入力・精度 正解 / 誤検知 / 検出漏れ 精度 · 再現率 FP32と検出有無が同じフレーム ヒートマップの最大値位置が動いたフレーム
オリジナル · FP32 384 / 2 / 4 0.995 · 0.990 — —
オリジナル · FP16(autocast) 384 / 2 / 4 0.995 · 0.990 100% 4.6% (最大2px)
オリジナル · FP16(half) 384 / 2 / 4 0.995 · 0.990 100% 0.8% (最大1px)
JPEG 70 · FP32 377 / 1 / 11 0.997 · 0.972 97.9% 34.3%

FP16はこの388枚の中で判定を1つも変えませんでした。 ヒートマップの値の差は中央値で0.0001水準でした。一方、JPEG圧縮は検出漏れのボールを4個から11個に増やし、最大値の位置をフレームの3分の1ほど動かしました。精度を半分に減らすことよりも、 入力を圧縮する方がはるかに大きな誤差でした。運用環境ではすでにJPEGを受け取っているため、FP16を有効にしても現在と大きく変わることはないと考えています。ただし、これは1つの試合・条件における結果であり、劣化条件(暗い・かすんでいる)や運用GPUでは測定していません(未計測)。

7. 実測結果:ボールモデルのINT8事後量子化(CPU)

GPUでのINT8はTensorRTのようなランタイムが必要となるため、今回は測定できませんでした(パート3)。代わりにPyTorchの事後量子化(FXグラフ方式、x86整数エンジン)を用いて、同じモデルをCPU上でINT8に変更しました。キャリブレーションフレームは試合の前半、評価フレームは後半から重複しないように選択しました。

評価フレーム220枚(ボールあり215枚)、キャリブレーション候補146枚、CPU 4コア制限の条件で、4枚ずつまとめて処理した際の時間の中央値です。

方式 キャリブレーションフレーム 正解 / 誤検知 / 検出漏れ 再現率 ヒートマップの最大値の差(対FP32、中央値) CPU処理時間/枚
FP32 — 208 / 3 / 7 0.967 — 519.9ms
INT8、デフォルト(アクティベーションヒストグラム、重みはチャネルごと) 1 206 / 3 / 9 0.958 0.064 72.2ms
同等 8 207 / 3 / 8 0.963 0.056 71.3ms
同等 64 208 / 3 / 7 0.967 0.057 63.1ms
INT8、最小値-最大値、重みテンソルごと 8 207 / 3 / 8 0.963 0.086 71.4ms
INT8、最小値-最大値、重みはチャネルごと 8 208 / 2 / 7 0.967 0.084 72.5ms

8. 制限事項

9. 本プロジェクトとの関連性

次の記事では、精度を維持したまま 実行方式を変更するコンパイラとランタイムについて解説します。

TTの一言。 数値を半分に減らしてもボールの位置はそのままでした。むしろ写真を圧縮した時の方が大きくブレました。どこで誤差が生じるのか実際に測定してみるまで分かりません!

参考文献

ログインするといいねできます

コメント

まだコメントはありません。

ログインするとコメントできます