Transformers documentation

量子化の概念

Hugging Face's logo
Join the Hugging Face community

and get access to the augmented documentation experience

to get started

量子化の概念

量子化された数値は、int8 の場合は 256 個、int4 の場合は 16 個という少数の値のいずれかしか取れないため、どの量子化手法でも丸め誤差が生じます。手法間の違いは、その誤差がモデルの出力に悪影響を与えないようにする方法にあります。このページでは、これらの違いの背景にある選択事項を説明します。具体的には、数値をその少数の値にどのようにマッピングするか、どのテンソルを量子化するか、どのデータ型を使用するか、そして量子化がいつ行われるかです。

Transformersでの量子化が初めての場合は、概要から始めてください。

値をより少ないビット数にマッピングする

Quantizationは、高精度の値の範囲を、低ビット型が表現できる少数の整数の集合にマッピングします。int8は[-128, 127]の256個の整数を、int4は16個の整数を表現できます。

Affine quantizationは最も一般的なマッピングです。テンソルの最小値と最大値を取得し、その範囲に整数の範囲を伸張して対応させます。以下の例では、値が-1.8から2.4の範囲にあるテンソルをint8にマッピングし、0.0は-19に対応します。

bf16  -1.8             0.0                    2.4
       ├────────────────┼──────────────────────┤
       ▼                ▼                      ▼
int8  -128             -19                    127

2つのパラメータがマッピングを定義します。

  • S は S 元の単位における 1 ステップのサイズです。 S=valmax−valminqmax−qmin S = \frac{val_{max} - val_{min}}{q_{max} - q_{min}}

  • ゼロポイント は Z を整数で表す値です。 0.0. Z=qmin−round(valminS) Z = q_{min} - round\left(\frac{val_{min}}{S}\right)

値 x は、スケールで割り、ゼロポイントでシフトすることにより、整数 q に量子化されます。 q=round(xS+Z) q = round\left(\frac{x}{S} + Z\right)

Dequantizing はマッピングを逆転させ、 x. の近似値を返します。 x と、その近似値の差が丸め誤差です。 x≈S⋅(q−Z) x \approx S \cdot (q - Z)

上記の例では、S は約 0.0165、Z は -19 です。値 0.5 は 11 に量子化され、11 は約 0.494 に逆量子化されます。これにより、約 0.006 の丸め誤差が生じます。

┌─────┐  quantize   ┌────┐ dequantize   ┌───────┐
│ 0.5 │ ──────────▶ │ 11 │ ───────────▶ │ 0.494 │
└─────┘             └────┘              └───────┘

Affine量子化には2つの形式があります。

  • 非対称量子化は、上記の式を使用します。正確な範囲をマッピングするため、重みがゼロを中心にない場合でも、表現可能な値が無駄になることはありません。
  • 対称量子化は、範囲が[-a, a]でありZが0に固定されていると仮定するため、スケールのみを保存します。計算コストは低くなりますが、重みがゼロの一方に偏っている場合、表現可能な値の一部が未使用になります。

粒度を選択します

テンソル全体で単一のスケールを使用する場合は保存コストが低くなりますが、1つの大きな外れ値があると範囲が広がり、他のすべての重みにおける表現可能な値の間隔が広がってしまいます。より小さなグループに分割することで、各スケールがカバーする範囲を狭めることができます。

  • Per-tensor は、テンソル全体に対して 1 つのスケーリング係数と 1 つのゼロポイントを適用します。
  • Per-channel は、重み行列の各行である各出力チャネルに対して1つのスケールを使用します。
  • グループごと、またはブロックごとの方式では、128個の重みなどの連続する値の各グループに対して1つのスケールを使用します。各グループのスケールには、追加のストレージコストが発生します。

下の図は、3行の重み行列における3つのオプションを示しています。各sは1つのスケールを表します。

per-tensor        per-channel           per-group
┌────────────┐    ┌────────────┐        ┌──────┬──────┐
│            │    │            │ s0     │  s0  │  s1  │
│            │    ├────────────┤        ├──────┼──────┤
│     s      │    │            │ s1     │  s2  │  s3  │
│            │    ├────────────┤        ├──────┼──────┤
│            │    │            │ s2     │  s4  │  s5  │
└────────────┘    └────────────┘        └──────┴──────┘

ほとんどの低ビット方式はグループごとに量子化を行います。GPTQはデフォルトで128個の重みからなるグループを使用し、Fine-grained FP8はデフォルトで128x128のブロックを使用します。Data typesのFP4形式では、ブロックサイズが形式自体に組み込まれています。

重みまたは活性化を量子化

Methodsは、量子化の対象とするテンソルが異なり、それが行列積の計算がどこで実行されるかを決定します。

  • Weight-only方式では、重みを低精度で保存し、アクティベーションは bf16 または fp16 のままにします。各 matmul の前に、カーネルが重みをより高精度な計算用 dtype にデクォアンタイズします。bitsandbytes 4-bit、AWQ、GPTQ、HQQ はこの方式で動作します。bitsandbytes では、bnb_4bit_compute_dtype がその計算用 dtype を設定します。
  • 重みと活性化方式では、活性化値も量子化され、matmulが低精度で直接実行されます。これには、最近のGPUのFP8のように低精度matmulをサポートするハードウェアが必要です。この方式は、8ビットの重みと活性化を指してW8A8と表記されることがよくあります。

Weight-only 方式はメモリを節約できますが、デクアンタイズがすべての matmul に追加の計算負荷をもたらすため、必ずしも高速になるとは限りません。モデルがメモリ制約されている場合、生成が高速化されます。トークンを1つずつ生成する際、GPU は各ステップの大部分を計算ではなく、メモリからの重みの読み取りに費やします。4-bit 重みの読み取りは、bf16 重みの場合のデータ量の4分の1しか移動しないため、この節約効果がデクアンタイズのコストを上回る可能性があります。

重みと活性化の量子化手法は、プリフィルや大規模バッチ処理のようにモデルが計算律速になっている場合に有効です。これは、低精度の行列積そのものが高速であるためです。一部の手法は、これら2つのモードを切り替えます。NVFP4は、プリフィル時に4ビット重み・4ビット活性化の行列積を実行し、デコーディング時には4ビット重み・16ビット活性化の行列積を実行します。

データ型

データ型は、量子化された数値が表現できる値の範囲を決定します。

int8 と int4

int8 と int4 は等間隔の整数を表し、上記のアフィン変換を使用します。int4 は 16 値しか表現できないため、int8 よりも精度の低下が大きくなります。int4 を使用する手法では、通常、グループごとに量子化したり、キャリブレーションを用いたりして精度を維持します。

ほとんどのハードウェアには4ビットのストレージ型が存在しないため、2つのint4値が1バイトにパックされます。例えば、最初の値が下位4ビット、2番目の値が上位4ビットを占有します。カーネルは、これらの値で計算を行う前に、値をアンパックします。

┌──────┐  ┌──────┐        ┌──────┬──────┐
│ val1 │  │ val2 │        │ val2 │ val1 │
│ 0101 │  │ 0011 │   →    │ 0011 │ 0101 │  1 byte
└──────┘  └──────┘        └──────┴──────┘
  int4      int4            high   low

一部の4ビット型は、重みの分布に合わせて、表現可能な値の間隔を均等にせず、非均等に配置しています。bitsandbytesのNF4は、多くの重みが存在するゼロ付近に、より多くの値を配置しています。

FP8

FP8は、符号・指数部・仮数部からなる浮動小数点のレイアウトを8ビットで保持します。その表現可能な値はゼロ付近では互いに近くなり、絶対値が大きいほど間隔が広くなるため、重みと活性化値の値の分布に適しています。

FP8には2つのバリアントがあります。

  • E4M3は指数部が4ビット、仮数部が3ビットです。より高精度ですが、カバーできる範囲は狭くなります。推論時の重みと活性化には、通常この形式が選択されます。
  • E5M2は指数部が5ビット、仮数部が2ビットです。精度は低くなりますが、より広い範囲をカバーできます。
E4M3   ┌─┬───────┬─────┐
       │S│E E E E│M M M│
       └─┴───────┴─────┘
E5M2   ┌─┬─────────┬───┐
       │S│E E E E E│M M│
       └─┴─────────┴───┘
  S sign   E exponent   M mantissa

FP8 matmulには、compute capability 8.9以上のNVIDIA GPU(Ada、Hopper、Blackwell)、AMD Instinct MI300、Intel XPUなどのハードウェアサポートが必要です。Transformersは、Fine-grained FP8、FBGEMM FP8、compressed-tensorsを通じてFP8をサポートしています。

ブロックスケール付きFP4

FP4は符号ビット1ビット、指数ビット2ビット、仮数ビット1ビット(E2M1)で構成されています。表現できる値は、正負を問わず0、0.5、1、1.5、2、3、4、6のみです。表現可能な値がこれほど少ないため、FP4形式ではテンソル全体に1つのスケールを適用することはできません。そのため、値の小さなブロックごとにスケールを付与し、そのスケールを形式の一部として保存します。

形式ブロックサイズブロックごとのスケーリング追加のスケール
MXFP432個の値8ビットの2の累乗(E8M0)None
NVFP416個の値FP8テンソルごとに1つのFP32スケール

MXFP4はマイクロスケーリング形式です。2の累乗のスケーリングは指数をシフトするだけなので適用コストは低いですが、2の累乗の値にのみ収束します。一方、NVFP4はより小さなブロックと、2の累乗の間の値を取りうるFP8スケーリングを使用します。これにより精度をより保てますが、FP8ブロックスケーリングを範囲内に保つためのテンソルごとのスケーリングが必要になります。Four Over SixおよびFP-Quantも、NVFP4またはMXFP4の重みを生成します。

同じブロックの考え方は8ビットでも見られます。MXFP8は2の累乗のブロックスケールを持つFP8値を保存し、Fine-grained FP8がそれをロードします。

コードブック

4ビット未満では、一部の手法は各重みを最も近い表現可能な値に丸めることをやめます。代わりに、ベクトルとして重みのグループをまとめて量子化します。コードブックには学習されたベクトルの集合が保持され、各重みグループは最も近いベクトルのインデックスとして保存されます。AQLMでは、デフォルトで8つの重みを1グループとし、16ビットのインデックスを使用します。これにより、1つの重みあたり2ビットになります。VPTQもコードブックを使用します。良いコードブックを見つけるには、キャリブレーションデータに対する長時間の最適化が必要であるため、これらのモデルは事前に量子化され、チェックポイントとして読み込まれます。

量子化が発生するタイミング

モデルの量子化は、学習後または学習中に実行されます。Transformersのほとんどの手法は、学習後に量子化を行います。

ポストトレーニング量子化

Post-training quantization (PTQ) は、学習済みのモデルを、そのモデルが学習した内容を変更せずに量子化します。PTQ の手法は、データが必要かどうかによって異なり、これがドキュメントの目次内でその手法がどのグループに分類されるかを決定します。

  • キャリブレーションデータなしの場合、スケールは重みのみから導出され、モデルの読み込み中に実行できるほど量子化が高速です。bitsandbytes、HQQ、torchaoはこの方式で動作します。これらの手法は読み込み時の量子化のセクションにリストされています。
  • キャリブレーションデータを用いる場合、この手法は小さなデータセットをモデルに通し、活性化値に基づいて各層の量子化方法を決定します。キャリブレーションは4ビット以下で通常、より高い精度をもたらしますが、数分から数時間かかります。

キャリブレーション手法は、アクティベーションを異なる方法で利用します。GPTQはレイヤーを1列ずつ量子化し、各列の丸め誤差を補うために残りの列を調整します。一方、AWQは最も大きなアクティベーションを持つ重みチャネルを特定し、丸める前にそれらをスケールアップして保護します。

calibration runsの実行状況によって、Transformersでのメソッドの使い方が決まります。

  • dataset を GPTQConfig に渡すと、GPTQ は from_pretrained の内部でキャリブレーションを行うため、読み込み時に量子化 の項目に分類されています。
  • AWQ、AutoRound、AQLM、VPTQなどの他のキャリブレーション手法は、それぞれのライブラリ内でキャリブレーションを行います。Transformersは結果を読み込むため、これらは事前量子化済みチェックポイントの読み込みに分類されています。

量子化認識トレーニング

Quantization-aware training (QAT)は、学習中に量子化をシミュレートします。フォワードパスでは量子化と同様に値を丸め、勾配によってフルプレシションの重みを更新するため、モデルは丸め誤差に耐えられるように学習します。QATは非常に低いビット幅で最も高い精度を達成する傾向がありますが、モデルの学習が必要です。

BitNetモデルは、1.58ビットの重みをループ内に含めた形でこの方式で学習され、Transformersは学習済みチェックポイントをロードします。

リソース

Update on GitHub