AI・データ●●●●○

量子化

Quantization ・ りょうしか

モデルの重みを少ないビット数の数値に変換して軽量化する技術。ローカルLLMの立役者。

✦ 30秒で試す — 航海数字の細かさを削ると、AIが家のノートPCに収まる4ビットに丸めた後、ふつうの重みはどうなる?予想してみる

概要

量子化(quantization)は、機械学習モデルの重み(パラメータ)を表す数値の精度を意図的に落とし、モデルのサイズと計算コストを減らす技術です。学習時にはふつう32ビットや16ビットの浮動小数点数で表されている重みを、8ビットや4ビットの整数など、より少ないビット数に置き換えます。ビット数を半分にすればモデルの容量もほぼ半分になり、メモリから読み出す量と計算量も減るので、推論が軽く・速くなります。

この技術が脚光を浴びたのは、LLMの巨大化と「手元で動かしたい」という需要がぶつかったからです。70億パラメータのモデルでも16ビットのままでは約14GBのメモリを要し、家庭用のGPUやノートPCには載りません。4ビットに量子化すれば約4GBまで縮み、普通のノートPCで動くようになります。Ollama や llama.cpp でローカルLLMを動かしたことがあるなら、そのモデルファイルはほぼ間違いなく量子化済みです。量子化は、ローカルLLM文化の文字どおりの立役者です。

なぜ生まれたか

量子化の考え方自体は、LLM以前からスマートフォンや組み込み機器向けの深層学習で育ってきました。端末での画像認識や音声認識では、メモリも電力も限られていて、ニューラルネットワークをそのままの大きさでは載せられなかったからです。そこで「学習は高精度で行い、推論は低精度で行っても精度はさほど落ちない」という経験則を土台に、8ビット整数(INT8)で推論する技術が2010年代後半に整えられました。

LLMの時代になると、この問題は桁違いに深刻になりました。パラメータ数が数十億〜数千億に達し、推論の足かせが計算そのものより「重みをメモリから読み出す速さ(メモリ帯域)」に移ったのです。文章を1トークン生成するたびに全パラメータを読み出す LLM の推論では、重みを小さくすることがそのまま速度の向上につながります。加えて、高性能なGPUの価格高騰と品薄が「手持ちの機械で動かす」動機を強めました。2022年に大規模モデルの8ビット化(LLM.int8())や4ビット化(GPTQ)の手法が相次いで発表され、2023年3月に llama.cpp が登場すると、コミュニティ主導で4ビット量子化が一気に実用化・普及しました。

詳細

仕組み — 実数の範囲を整数の目盛りに割り付ける

量子化の基本操作は、浮動小数点の重みが取る値の範囲を、少数の離散的な目盛りに割り付けることです。いちばん単純な対称量子化(absmax)では、重みの絶対値の最大 max|w| を整数の最大値に合わせます。INT8 なら scale = max|w| ÷ 127 とし、各重みを q = round(w ÷ scale) という −127〜127 の整数に置き換えます。使うときは q × scale と掛け戻すだけです。目盛りの間の細かな違いは失われ、この差が量子化誤差になります。ビット数を減らすほど目盛りは粗くなり、4ビットでは −7〜7 の15段しかありません。

対称量子化は 0 を中心に目盛りを振りますが、値の分布が片側に寄っている場合(ReLU の後の活性化など)は、最小値から最大値までを目盛りに割り付け、0 がどの目盛りに当たるかを「ゼロ点」として別に持つ非対称量子化のほうが目盛りを無駄なく使えます。どちらの方式でも、目盛りの細かさは「範囲の広さ ÷ 段数」で決まる、という点は同じです。

この「範囲の広さ」が量子化の急所です。重みの大半は 0 の近くに集まっているのに、ごく少数の外れ値があると、その1本に合わせて範囲全体が引き伸ばされ、ふつうの重みはみな同じ目盛り(特に 0)に潰れてしまいます。下の部品で、ビット数を下げたり外れ値を遠くへ引いたりして確かめてください。

⚡ 体験: 重みを整数の目盛りに吸着させる
分布ブロック1ブロック2ブロック3ブロック4-4-3-2-101234重みの値 / 目盛りの間隔 0.0083
ビット数
スケール
ふつうの重みの誤差(RMSE)0.0020 に潰れた重み0 / 631重みあたり8 bit

まず下の「ビット数」を 8 → 4 → 2 と下げてください。重みが最寄りの目盛りに吸着し、ずれ(朱の線)が伸びます。次に金の取っ手(外れ値)を右へ引いてみてください。

白い点が量子化後の重み、薄い輪が元の値、朱の線がそのずれ。縦の細線が目盛り(k × scale、k は −(2^(b−1)−1)〜+(2^(b−1)−1) の整数)。上の山は元の重みの分布(青)と、量子化後に各目盛りへ集まった本数(金の棘)。

実用的な手法は、この弱点にいくつかの方向から手を打っています。1つ目は、重み行列を32〜256個程度の小さなブロックに分けて、ブロックごとに別々のスケールを持たせることです。外れ値の被害はそのブロックの中に閉じ込められ、代わりにスケールを保存する分だけ容量が少し増えます。2つ目は、外れ値だけを16ビットのまま別扱いすることです。LLM.int8() は、大きなモデルの活性化に現れる少数の外れた次元だけを16ビットで計算し、残りを8ビットで計算しました。3つ目は、誤差の効き方を考えて丸め方を賢くすることで、GPTQ は丸めた誤差を残りの重みで打ち消すように1列ずつ量子化し、AWQ は活性化が大きく出力への影響が大きい重みを守るようにスケールを調整します。

各重みは4ビットの整数(−7〜7)で保存する3 −5 1 7−2 6 −7 00 0 7 04 −1 −6 2scale 0.05scale 0.04scale 0.70scale 0.06スケールは16ビットで各ブロックに1つ。値に戻すときは 整数 × そのブロックの scale外れ値(7 × 0.70 = 4.9)のいるブロックだけ目盛りが粗く、ほかの重みが 0 に潰れるほかのブロックは細かい目盛りのまま
ブロックごとのスケール。重みの列を小さなブロックに分け、ブロックごとに16ビットのスケールを1つ持つ。外れ値の影響はそのブロックの中だけに閉じる

精度と容量のトレードオフ

容量はほぼビット数に比例します。70億パラメータのモデルなら、32ビットで約28GB、16ビットで約14GB、8ビットで約7GB、4ビットで約3.5GB(ブロックごとのスケールなどを含めると約4GB)です。量子化するのはふつう重みだけで(重みのみ量子化、W4A16 などと書きます)、計算は重みを16ビットに戻しながら行います。重みと活性化の両方を8ビットにして整数演算器で計算する方式(W8A8)や、新しい GPU が対応する FP8・FP4 のような低ビットの浮動小数点形式もあり、どこまでを低精度にするかは、使う GPU と求める速度で選びます。

1パラメータあたりのビット数と、70億パラメータのモデルのおよその容量FP3232bit 浮動小数点約28GB — 学習時の標準FP16 / BF1616bit 浮動小数点約14GB — 配布時の標準INT88bit 整数約7GB劣化はほぼ見えない。多くの用途で十分INT44bit 整数約3.5GB(スケール込みで約4GB)家庭用GPUやノートPCで動く。劣化が現れ始めるが実用域ビット数が半分になるたびに容量も半分になる目安: 16ビット比で 8bit はほぼ無劣化、4bit は軽微な劣化、3bit 以下は用途を選ぶ
精度と容量のトレードオフ — 70億パラメータのモデルの場合。棒の長さは容量に比例する

いつ量子化するか — PTQ と QAT

量子化のタイミングには大きく2系統あります。学習済みモデルを後から変換する PTQ(Post-Training Quantization)と、量子化された状態を想定しながら学習する QAT(Quantization-Aware Training)です。PTQ は追加学習が要らず手軽なので、LLMの量子化はほぼこちらが主流です。少量の較正データ(代表的な入力)を流して各層の値の範囲や誤差の効き方を測ってから変換する GPTQ や AWQ、llama.cpp 系の K-quants などが代表的な手法です。QAT は学習時から量子化誤差を織り込むので低ビットでも精度を保ちやすい一方、学習のコストがかかるため、モデルの提供元が公式に低ビット版を出すような場面で使われます。

ファインチューニングの世界でも量子化は重要です。4ビットに量子化したモデルを凍結し、小さな追加行列(LoRA)だけを16ビットで学習する QLoRA(2023年)は、家庭用の GPU で LLM をカスタマイズできるようにした代表的な手法です。QLoRA が使う NF4 という4ビット形式は、重みが正規分布に近いことを利用して、値が密集する 0 の近くほど目盛りを細かく配っています。等間隔の目盛りより、分布の山に合わせた目盛りのほうが同じビット数で誤差が小さくなる、という工夫です。

GGUF とローカルLLM

ローカル実行の文脈で必ず出会うのが GGUF というファイル形式です。llama.cpp プロジェクトが2023年8月に、それまでの GGML 形式を置き換えるものとして定めた形式で、量子化済みの重みとトークナイザ・メタデータを1ファイルにまとめて配布できます。Ollama や LM Studio など主要なローカルLLMの道具が対応しています。Hugging Face で「Q4_K_M」「Q8_0」といった名前を見かけたら、それは量子化の方式とビット数を表す GGUF の型名です。数字がビット数、K は K-quants(ブロックをさらに束ねてスケール自体も量子化する方式)、M・S・L は重要な層に少し多めのビットを配る度合いを表します。おおまかには Q8 がほぼ無劣化、Q4_K_M が容量と品質の釣り合いのよい定番、Q2〜Q3 は大きく縮む代わりに劣化が目立つ、という使い分けになります。CPU だけでも動き、GPU があれば一部の層だけを GPU に載せる(オフロード)といった柔軟な構成が取れることも、ローカルLLMが広まった理由です。

精度劣化との付き合い方

量子化は不可逆な圧縮で、失った情報は戻りません。劣化の現れ方には傾向があり、一般常識的な応答は低ビットでも保たれやすい一方、数学的な推論・コード生成・多言語(特に学習データの少ない言語)といった繊細なタスクから先に崩れていきます。ベンチマークの平均点ではわずかな差でも、特定の入力で目に見えて挙動が変わることがあるので、「自分の用途のタスクで実測する」ことが実務上の鉄則です。

パラメータ数とビット数のどちらを取るかという選択では、「大きいモデルを強めに量子化するほうが、小さいモデルを高精度で使うより良い」傾向が知られています(例: 13Bの4bit ≧ 7Bの16bit)。ただし3ビットを切るあたりから劣化が急になるので、限られたメモリの使い方として「4ビット前後で、入る中でいちばん大きなモデル」を目安にするとよいでしょう。なお量子化の対象は重みだけではありません。長いコンテキストウィンドウで膨らむ KV キャッシュ(生成中に保持する注意の中間結果)も8ビットや4ビットに量子化でき、推論時のメモリを減らす手段として定着しつつあります。

試問

64個の重み(16個ずつ4ブロック)が数直線に並び、縦の細線が量子化の目盛りです。目盛りは「いちばん大きな絶対値 ÷ 段数」の間隔で刻まれ、各重みは最寄りの目盛りに丸められます。ビット数(8・4・3・2)と、スケールの持ち方(全体で1つ・16個のブロックごと・外れ値だけ16ビットで別に持つ)を切り替えられます。

✦ 命題挑戦中

値 3.6 の外れ値が1本まじった重みを、1重みあたり 5 ビット以下に縮めながら、0 に潰れるふつうの重みを 10 個以下に抑えよ

  • 1重みあたり 5 ビット以下(いま 8)
  • 0 に潰れたふつうの重みが 10 個以下(いま 0)
⚡ 体験: 重みを整数の目盛りに吸着させる
分布ブロック1ブロック2ブロック3ブロック4-4-3-2-101234重みの値 / 目盛りの間隔 0.028
ビット数
スケール
ふつうの重みの誤差(RMSE)0.0080 に潰れた重み0 / 631重みあたり8 bit

金の点は値 3.6 の外れ値で、この試問では動かせません。ビット数とスケールの持ち方を切り替えて、ふつうの重みがどれだけ 0 に潰れるかを見てください。

白い点が量子化後の重み、薄い輪が元の値、朱の線がそのずれ。縦の細線が目盛り(k × scale、k は −(2^(b−1)−1)〜+(2^(b−1)−1) の整数)。上の山は元の重みの分布(青)と、量子化後に各目盛りへ集まった本数(金の棘)。