ニュース

数千語の技術情報! LLM エンジニア必読の定量化ガイド。大規模なモデルを圧縮する方法を視覚的な図で示しています。

2024-07-31

한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina


新しい知恵のレポート

編集者:編集部

【新しい知恵の紹介】 LLM のパラメータスケールが徐々に拡大することに直面して、H100 を持たない開発者や研究者はそれを補うさまざまな方法を考え出しましたが、「定量化」テクノロジーもその 1 つです。このビジュアルガイドは、「定量化」の基本的な考え方や分岐方法を、さまざまなイラストを用いてわかりやすくまとめています。

大規模言語モデル (LLM) は、多くの場合、消費者向けハードウェアで実行するには大きすぎます。これらのモデルには数十億を超えるパラメーターが含まれる場合があり、多くの場合、推論プロセスを高速化するために大容量メモリを搭載した GPU が必要になります。

したがって、トレーニング方法の改善やアダプターの使用など、モデルを縮小する方法に焦点を当てた研究がますます増えています。この分野の主な技術の 1 つは量子化と呼ばれます。

ML エンジニアの Maarten Grootendorst は、言語モデリングのコンテキストで定量化テクノロジを具体的に紹介し、テクノロジの直感的な理解を助けるために視覚的な手法を通じて関連する概念を 1 つずつ探求するブログ投稿を書きました。


このブログ投稿では、Maarten がさまざまな方法、ユースケース、および定量化の背後にある原則を調査します。

記事の目次と内容を以下の図に示します。主に、トレーニング後の定量化 (PTQ) と量子化を意識したトレーニング (QAT) の 2 つの手法が紹介されています。AI の基礎知識がある読者はジャンプすることをお勧めします。対称量子化部分に直接接続します。


パート 1: LLM の「問題」

「大規模言語モデル」はモデル パラメーターの数が大きく、通常は規模 (主に重み) が数十億に達します。

これらのパラメータの保存コストが非常に高いだけでなく、推論段階での計算量も膨大になります。

推論中、アクティベーション値は入力と重みの積であるため、重みの数が増えるほどアクティベーション値も大きくなります。


したがって、数十億の値を可能な限り効率的に表現し、パラメーターの保存に必要なスペースを最小限に抑えたいと考えています。

最適化する前に、最初から値がどのように表現されるかを調べてみましょう。

数値の表し方

数値は通常、小数点を含む正または負の浮動小数点数 (または単に浮動小数点数) として保存されます。

これらの値は、各ビットの 2 進数で表されます。

IEEE-754 標準では、各桁の数字が特定の値を表す方法について説明しています。具体的には、符号、指数、小数 (仮数) の 3 つのマッピングがあります。


これら 3 つの部分を組み合わせて、一連のビット値に基づいて表現された値を計算できます。


使用する桁数が多いほど、数値の精度が高くなります。たとえば、FP32 形式は、FP16 よりも小数点以下の桁数まで正確になります。


メモリ制限

利用可能な桁数が多いほど、値がより正確になるだけでなく、表現できる値の範囲も広くなります。


ビット番号と表現が与えられたとき、表現できる値の範囲はダイナミック レンジと呼ばれ、2 つの隣接する値の間の距離は精度と呼ばれます。


この表現の優れた特徴は、特定の値を保存するためにデバイスが必要とするメモリ量を計算できることです。

メモリ内の各バイトには 8 ビットが含まれるため、ほとんどの形式の浮動小数点数に対する基本的な式を作成できます。


実際のアプリケーションでは、コンテキスト サイズやモデル アーキテクチャなど、推論中に必要なグラフィックス/メモリの量に影響を与える要因がさらに多くあります。

ここで、700 億個のパラメータを持つモデルがあると想像してください。ほとんどのモデル自体は 32 ビット浮動小数点数 (完全精度と呼ばれることが多い) を使用して表現されるため、モデルをロードするには 280GB のメモリが必要です。


しかし、すべてのパラメータを 16 ビット浮動小数点数として表現できれば、必要なメモリ サイズを直接 1 分の 1 に減らすことができます。

したがって、(推論だけでなくトレーニングにおいても) モデル パラメーターの表現の数を最小限に抑えることは非常に魅力的です。

ただし、このアプローチにはコストがかかります。通常、表現ビット数が減少するとモデルの精度が低下し、結果として精度が低下します。

精度を維持しながら、値を表すために使用される桁数を減らしたいと考えています。ここで量子化技術が役に立ちます。

パート 2: 定量化の概要

量子化の目的は、モデル パラメーターの精度を高いビット幅 (32 ビット浮動小数点数など) から低いビット幅 (8 ビット整数など) に下げることであることがわかりました。


元のパラメータを表すビット数を減らすと、通常、ある程度の精度 (粒度) が失われます。

この効果をより直感的にするために、写真の色を例えとして使用できます。たとえば、任意の画像 (左) を選択しますが、8 色のみを使用します (右)。


拡大されたクッキーは元のクッキーよりも「粗く」見えることに注意してください。

同様に、量子化の主な目的は、元のパラメータの精度を可能な限り維持しながら、元のパラメータを表すために必要なビット (色) の数を減らすことです。

一般的なデータ型

まず、一般的なデータ型と、32 ビット (完全精度または FP32 と呼ばれる) 表現の代わりにそれらを使用した場合の影響を見てみましょう。

FP16

最初は、32 ビットから 16 ビット (半精度または FP16 と呼ばれる) 浮動小数点に移行する例です。


FP16 の可能な値の範囲は、FP32 の値の範囲よりもはるかに小さくなります。

BF16

オリジナルの FP32 と同様の数値範囲を取得するために、bfloat 16 が「切り詰められた FP32」タイプとして導入されました。


BF16はFP16と同じビット数を使用しますが、指数ビットを追加するため、より広い範囲の値を取得でき、ディープラーニングの分野でよく使用されます。

INT8

ビット数をさらに減らすと、表現は浮動小数点数ではなく整数に近づきます。たとえば、FP32 から 8 ビットしかない INT8 に移行すると、元のビット数の 1/4 だけになります。


ビット数が削減されるたびに、マッピングが実行されて、元の FP32 表現がより少ないビットに「圧縮」されます。

ただし、実際の動作では、FP32 範囲 [-3.4e38, 3.4e38] 全体を INT8 にマッピングする必要はありません。実際のモデル パラメーターのデータ範囲を INT8 にマッピングする方法を見つける必要があるだけです。

一般的な圧縮/マッピング方法には対称量子化と非対称量子化があり、どちらも線形マッピングです。

次に説明するのは、FP32 から INT8 への量子化方法です。

対称量子化

対称量子化では、元の浮動小数点値の範囲は、量子化前後の範囲の中点としてゼロを使用して、量子化空間内のゼロを中心とする対称範囲にマッピングされます。

これは、浮動小数点空間の元のゼロが、量子化空間にマッピングされた後は正確にゼロになることを意味します。


対称量子化の典型的な例は、絶対最大 (absmax) 量子化です。

値のリストが与えられた場合、最も高い絶対値 (α) を範囲として取り、線形マッピングを実行します。


量子化方法に応じて、[-127, 127] は制限された範囲を表し、制限されていない範囲は [-128, 127] になります。

これはゼロを中心とする線形マップであるため、式は簡単です。

まず、次の式を使用してスケール係数を計算します。

- b は量子化するバイト数です (8)

- α は絶対値の最大値です

次に、 s を使用して入力 x を量子化します。


上図に示すように、FP32 を INT8 にマッピングすると、絶対値の最大値 α は 10.8 になります。


元の FP32 値を復元したい場合は、以前に計算したスケール係数を逆量子化に使用することもできます。


最初に量子化し、次に量子化を解除して元の値を復元します。プロセス全体は次のとおりです。


3.08 や 3.02 などの一部の値は、INT8 に量子化すると両方とも 36 になることがわかります。したがって、FP32 に逆量子化すると、精度がいくらか失われ、区別できなくなります。

元の値と逆量子化された値の間のこの差は、量子化誤差と呼ばれます。一般に、量子化結果のビット数が少ないほど誤差は大きくなります。


非対称量子化

対称量子化とは異なり、非対称量子化はゼロ中心対称ではありません。代わりに、浮動小数点範囲の最小値 (β) と最大値 (α) を量子化範囲の最小値と最大値にそれぞれマッピングします。

ここで検討する方法は、ゼロ点量子化と呼ばれます。


0 の位置がどのように移動したかに注目してください。これが非対称量子化と呼ばれる理由です。 [-7.59, 10.8] の範囲では、最大値と最小値は 0 からの距離が異なります。

ゼロ点位置のオフセットのため、線形マッピングを実行するには INT8 範囲でゼロ点を計算する必要があります。前と同様に、スケール係数も計算する必要がありますが、INT8 範囲 [-128, 127] の差を使用します。


ウェイトを移動するにはゼロ点 (z) を INT8 範囲で計算する必要があるため、これは少し複雑です。

前と同じように、数式を入力してみましょう。


量子化された値を INT8 から FP32 に逆量子化するには、以前に計算されたスケール ファクター (s) とゼロ点 (z) を使用する必要があります。

それ以外の場合、逆量子化は簡単です。


対称量子化と非対称量子化を並べてみると、2 つの方法の違いがすぐにわかります。


上の図では、対称量子化のゼロ中心特性と非対称量子化のオフセットがわかります。

範囲マッピングとクリッピング (Clipping)

前の例では、特定のベクトルの値の範囲を低ビット表現にマッピングする方法を検討しました。これによりベクトル値の全範囲をマッピングできますが、外れ値という大きな欠点が 1 つあります。

次の値を含むベクトルがあると想像してください。


他のすべての値よりもはるかに大きい値は、外れ値と見なすことができます。ベクトルの範囲全体をマッピングすると、すべての小さな値が同じ低次表現にマッピングされ、その区別性が失われます。


これは以前に使用された absmax メソッドです。クリッピングを行わない非対称量子化でも同じことが起こります。

代わりに、特定の値をクリップすることを選択できます。クリッピングとは、すべての外れ値が同じ値に設定されるように、元の値の異なるダイナミック レンジを設定することを指します。

以下の例では、ダイナミック レンジを手動で [-5, 5] に設定し、この範囲外のすべての値は、実際の値に関係なく、-127 または 127 にマッピングされます。


このアプローチの主な利点は、非外れ値の量子化誤差が大幅に減少することです。ただし、外れ値の量子化誤差が増加します。

較正

上の例では、ダイナミック レンジを [-5, 5] にランダムに設定していますが、量子化誤差を最小限に抑えながら、できるだけ多くの値を含む適切な範囲を見つけるために、「キャリブレーション」プロセスを通じて決定する必要があります。

キャリブレーション手順の具体的な実行は、パラメータの種類によって異なります。

重み (およびバイアス)

大規模言語モデル (LLM) の重みとバイアスは、モデルを実行する前にわかっているため、静的な値と考えることができます。たとえば、Llama 3 の約 20 GB のファイルは、そのほとんどが重みとバイアスで構成されています。

バイアス変数の数 (数百万) は重み (数十億) よりも大幅に少ないため、通常、バイアスはより高い精度 (INT16 など) を維持しますが、量子化の主な作業は重みに焦点を当てます。

既知の静的重量の場合、範囲を選択するための校正手法には次のものがあります。

- 入力範囲のパーセンタイルを手動で選択します

- 元の重みと量子化された重みの間の平均二乗誤差 (MSE) を最適化します。

- 元の値と量子化された値の間のエントロピー (KL 発散) を最小化します。


たとえば、パーセンタイルを選択すると、前に見たものと同様のクリッピング動作が発生します。

活性化値

大規模な言語モデル全体で常に更新される入力は、多くの場合アクティベーションと呼ばれます。


これらは通常、シグモイドやレルなどの何らかの活性化関数を通過するため、活性化値と呼ばれます。

重みとは異なり、アクティベーションは推論中の入力データに応じて変化するため、正確に定量化することが困難です。

これらの値は各隠れ層の後に更新されるため、入力データがモデルを通過するまで、推論段階ではその特定の値はわかりません。


一般に、重みとアクティベーションを調整するには 2 つの方法があり、モデルのさまざまな段階で適用されます。

- ポストトレーニング量子化 (PTQ)

- 名前が示すように、トレーニング後の数値化です。

- 量子化対応トレーニング (QAT)

- トレーニング/微調整中の量子化

パート 3: トレーニング後の定量化 (PTQ)

ポストトレーニング量子化 (PTQ) は、最も一般的な量子化手法の 1 つです。モデルのトレーニングが完了した後、モデルのパラメーター (重みとアクティベーション値を含む) を定量化します。

重みの量子化には、対称量子化または非対称量子化を使用できます。

ただし、活性化値の範囲が事前にわからないため、活性化値の定量化には、その基礎となる分布を取得するための推論段階が必要です。

アクティベーション値の量子化には 2 つの形式があります。

- 動的量子化

- 静的量子化

動的量子化

データが隠れ層を通過した後、その活性化値が収集され、各層の最大値 (α) と最小値 (β) が比較されます。


これらのアクティベーションの分布は、量子化された出力に必要なゼロポイント (z) とスケール ファクター (s) の値を計算するために使用されます。


このプロセスは、データが新しいネットワーク層を通過するたびに繰り返されます。したがって、各レイヤーには独自の独立した z 値と s 値があり、異なる量子化スキームが使用されます。

静的量子化

動的量子化とは異なり、静的量子化は推論中にゼロ点 (z) とスケール係数 (s) を計算しませんが、推論前にこれらの値を計算します。

これらの値を見つけるには、キャリブレーション データセットを使用し、それをモデルにフィードして、これらの基になる活性化値の分布を収集します。


これらの分布が収集されると、推論中の定量化に必要な s 値と z 値を計算できます。

実際の推論時には、s 値と z 値は再計算されませんが、量子化するためにすべてのアクティベーションにわたってグローバルに使用されます。

全体的に、各隠れ層の s 値と z 値を計算する動的量子化は、より正確になる傾向があります。ただし、これらの値は推論ごとに計算する必要があるため、計算時間が増加する可能性があります。

対照的に、静的量子化は、動的量子化ほど正確ではありませんが、量子化に使用される s 値と z 値が事前にわかっているため、高速になります。

4ビットの量的フィールド

ビットが失われるたびに量子化誤差が増加するため、8 ビット未満の量子化は常に課題でした。幸いなことに、ビット数を 6 ビット、4 ビット、さらには 2 ビットに減らす賢い方法がいくつかあります (ただし、ビット数を 4 ビット未満に減らすことは一般的に推奨されません)。

HuggingFace での 2 つの一般的な方法を見ていきます。

- GPTQ (完全なモデルは GPU 上で実行)

- GGUF (レイヤーを CPU にオフロードする可能性があります)

GPQ の

GPTQ は、実用化されている最も有名な 4 ビット量子化方式の 1 つと言えます。

非対称量子化を使用してレイヤーごとに処理し、次のレイヤーに進む前に各レイヤーを個別に処理します。


このレイヤーごとの量子化プロセスでは、最初にレイヤーの重みが逆ヘッセ行列に変換されます。逆ヘッセ行列はモデル損失関数の二次導関数であり、各重みの変化に対するモデル出力の感度を表します。

簡単に言うと、基本的に各レイヤーの重みの重要性 (逆重要性) を示します。

ヘッセ行列の重み値が小さいほど重要です。これらの重みのわずかな変化がモデルのパフォーマンスに大きな変化をもたらす可能性があるためです。


逆ヘシアン行列では、値が小さいほど「重要な」重みが高くなります。

次に、重み行列の最初の行を量子化および逆量子化します。


このプロセスにより、量子化誤差 (q) を計算でき、以前に計算した逆ヘシアン値 (h_1) を使用して重み付けできます。

基本的に、重みの重要性に基づいて重み付き量子化誤差を作成しています。


次に、この重み付けされた量子化誤差を行の他の重みに再分配します。これは、ネットワークの全体的な機能と出力を維持するのに役立ちます。

たとえば、これを 2 番目の重み (つまり、x_2=0.3) に対して行う場合、量子化誤差 (q) に 2 番目の重み (h_2) の逆ヘッセ行列を乗算し、それに加算します。


次に、特定の行の 3 番目の重みに対して同じ操作を続けます。


重み付けされた量子化誤差 q を再分配するこのプロセスは、すべての値が量子化されるまで繰り返されます。

通常、重みは相互に関連しているため、この方法が機能します。したがって、重みに量子化誤差がある場合、関連する重みは逆ヘッセ行列を通じてそれに応じて更新されます。

ググフ

GPTQ は、GPU 上で大規模言語モデル (LLM) 全体を実行するのに適した量子化方法ですが、対応するハードウェア条件がない場合は、LLM の任意の層を GGUF を通じて CPU にオフロードすることもできます。

これは、ビデオ メモリ (VRAM) の不足を補うために、CPU と GPU で同時にモデルを実行することに相当します。

量子化方式 GGUF は頻繁に更新され、具体的な量子化ビット数に依存しますが、基本原理は次のとおりです。

まず、特定のレイヤーの重みが「スーパーブロック」に分割され、各スーパーブロックには一連の「サブブロック」が含まれます。これらの「サブブロック」からスケール係数とアルファ値を計算します。


特定の「サブブロック」を量子化するには、前述の absmax 量子化を使用し、特定の重みにスケーリング係数 (s_sub) を乗算します。


スケール ファクター s_sub は、「サブブロック」の情報を使用して計算されますが、「スーパーブロック」の情報 s_super を使用して量子化されます。


要約すると、このブロックごとの量子化では、「スーパー ブロック」 (s_super) のスケール ファクターを使用して、「サブ ブロック」 (s_sub) のスケール ファクターを量子化します。

各スケールファクタの量子化レベルは異なる場合があり、「スーパーブロック」スケールファクタは一般に「サブブロック」スケールファクタよりも高い精度を持ちます。

これを説明するために、いくつかの量子化レベル (2 ビット、4 ビット、および 6 ビット) を調べてみましょう。


量子化タイプに応じて、ゼロ点を調整するには追加の最小値 (m) も必要です。これらはスケール係数 (s) と同様に量子化されます。

パート 4: 定量的意識トレーニング (QAT)

3 番目の部分では、トレーニング後にモデルを量子化する方法について説明します。この方法の欠点は、実際のトレーニング プロセスが考慮されていないことです。

ここで、定量的認識トレーニング (QAT) が役に立ちます。トレーニング後の定量化 (PTQ) とは異なり、QAT の目標は、トレーニング中に量子化プロセスを学習することです。


QAT は、トレーニング プロセス中に量子化がすでに考慮されているため、PTQ よりも正確になる傾向があります。仕組みは次のとおりです。

トレーニング プロセス中に、いわゆる「偽の」量子化が導入されます。たとえば、まず重みを INT4 に量子化し、次にそれらを逆量子化して FP32 に戻します。


このプロセスにより、モデルはトレーニング段階で損失を計算し重みを更新するときに量子化誤差を考慮できるようになります。

以下の図に示すように、「狭い」最小値はより大きな量子化誤差を引き起こすことが多いため、QAT は量子化誤差を減らすために「広い」最小値の場合の損失値を調査しようとします。


バックプロパゲーション中に量子化が考慮されないと仮定すると、勾配降下プロセスでは損失値が最小の重みが選択されます。ただし、「狭い」最小値にある場合は、より大きな量子化誤差が発生します。

対照的に、量子化を考慮すると、量子化誤差がはるかに小さい、「広い」最小値で異なる更新重みが選択されます。


したがって、PTQ 方式は高精度 (例: FP32) で低損失値を実現しますが、QAT も低精度 (例: INT4) で低損失値を追求しており、それを追求しています。

1ビット時代:BitNet

量子化精度を 4 ビットに下げるとすでにかなり小さいことが前にわかりましたが、さらに下げるとどうなるでしょうか?

ここで BitNet が登場します。BitNet はモデルの重みを単一ビット (-1 または 1) として表し、量子化プロセスを Transformer アーキテクチャに直接注入することでこれを行います。

Transformer アーキテクチャはほとんどの LLM の基礎であり、線形層を含む計算で構成されます。


これらの線形レイヤーは通常、FP16 などのより高い精度で表現され、ほとんどの重みが配置されます。

BitNet は、これらの線形層を BitLinear 層に置き換えます。


BitLinear レイヤーは通常の線形レイヤーと同じように機能し、重みとアクティベーション値を乗算して出力を計算します。

ただし、違いは、BitLinear レイヤーはモデルの重みを表すために 1 ビットのみを使用し、アクティベーション値を表すために INT8 を使用することです。


BitLinear レイヤーは、量子化対応トレーニング (QAT) と同様に、トレーニング中に一種の「偽の」量子化を実行して、重みとアクティベーションの量子化効果を分析します。


BitLinear を段階的に理解してみましょう。

重量の定量化

トレーニング中、重みは INT8 として保存され、signum 関数と呼ばれる基本的な戦略を使用して 1 ビットに量子化されます。

基本的に、重みの分布を 0 を中心に移動し、0 未満のすべての値を -1 に割り当て、0 より大きいすべての値を 1 に割り当てます。


さらに、値 β (平均絶対値) を追跡します。これは、後の逆量子化プロセスで使用します。

活性化量子化

アクティベーションを定量化するために、BitLinear は絶対最大値法 (absmax) を有効にして、アクティベーションを FP16 から INT8 に変換します。これは、より高精度の行列乗算 (×) が必要なためです。


さらに、値 α (最大絶対値) を追跡します。これは、後の逆量子化プロセスで使用します。

逆量子化

α (アクティベーションの最大絶対値) と β (重みの平均絶対値) を追跡します。これは、アクティベーションを FP16 まで逆量子化するのに役立ちます。

出力アクティベーションは、{α, γ} を使用して再スケーリングされ、元の精度に逆量子化されます。


このプロセスは比較的単純で、-1 または 1 の 2 つの値のみでモデルを表すことができます。

このアプローチにより、著者らは、モデルのサイズが大きくなるにつれて、1 ビット トレーニングと FP16 トレーニングの間のパフォーマンスのギャップがどんどん小さくなることを観察しました。

ただし、これは大規模なモデル (パラメータが 30B を超える) にのみ適用され、小規模なモデル間のギャップは依然として大きいです。

すべての LLM は 1.58 ビットです

前述のスケーラビリティの問題を改善するために、BitNet 1.58b が導入されました。

この新しいアプローチでは、モデルの各重みが -1 または 1 になるだけでなく、0 をとることもでき、各変数が 3 値になります。

興味深いことに、0 を追加するだけの簡単な操作で、BitNet が大幅に改善され、計算プロセスが高速化されます。

パワー0

0 を追加すると大幅な改善が得られるのはなぜですか?

これは行列の乗算に関係しています。

まず、行列乗算の基本的な仕組みを見てみましょう。

出力を計算するとき、重み行列と入力ベクトルを乗算します。以下は、重み行列の最初の層の最初の行の乗算を視覚化したものです。


この乗算には、単一の重みと入力を乗算し、それらをすべて加算する 2 つのアクションが含まれます。

対照的に、BitNet 1.58b では、3 値の重みによって基本的に次のことがわかるため、乗算の行為をなんとか回避しています。

- 1: この値を追加したい

- 0: この値は必要ありません

- -1: この値を減算したい

したがって、重みが 1.58 ビットに量子化されている場合は、加算を実行するだけで済みます。


これにより計算が大幅に高速化されるだけでなく、特徴のフィルタリングも可能になります。

特定の重みを 0 に設定することは、1 ビットのように入力値を加算または減算するのではなく、入力を無視することと同じです。

定量化する

重み量子化を実行するために、BitNet 1.58b は、以前に見た最大絶対値量子化 (absmax) の変形である平均絶対値量子化 (absmean) を使用します。

単純に重みの分布を圧縮し、絶対平均 (α) を使用して値を定量化します。次に、それらを -1、0、または 1 に丸めます。


BitNet と比較すると、アクティベーションの量子化は 1 つの点を除いて同じです。アクティベーションを [0, 2ᵇ⁻¹] の範囲にスケーリングする代わりに、最大絶対値法を使用して [-2ᵇ⁻¹, 2ᵇ⁻¹] にスケーリングします。

要約すると、1.58 ビット量子化には主に 2 つの手法が含まれます。

- 0 を追加して 3 値表現 [-1, 0, 1] を作成します。

- 重みの絶対平均定量化

BitNet の論文には、「13B BitNet b1.58 は、レイテンシ、メモリ使用量、エネルギー消費の点で 3B FP16 LLM よりも効率的である」という結論があります。


論文アドレス: https://arxiv.org/abs/2402.17764

わずか 1.58 ビットの計算効率で、軽量モデルが得られます。

参考文献:

https://newsletter.maartengrootendorst.com/p/a-visual-guide-to-quantization