- 浮動小数点数が「符号・指数・仮数」の 3 欄でできていること。FP32 / FP16 / BF16 の実際のビット配分
- 指数ビットが「届く範囲」を、仮数ビットが「刻みの細かさ」を決める理由。BF16 が学習で好まれるのに FP16 の上位互換ではない理由
- FP8 に E4M3 と E5M2 の 2 種類がある理由。FP4(E2M1)が表せる 16 個の値の実体
- 整数の等間隔な目盛りと、浮動小数点の「0 付近が密」な目盛りの違い。NF4 がそのどちらでもない第 3 の置き方であること
- MXFP4 の実効 4.25 bpw がどこから来るのか。以降の章で共通語彙として使う bpw(bits per weight)の定義
「有効数字と桁」を 2 進でやっているだけ
コンピュータは実数をそのまま持てません。0.1 と 0.2 のあいだにも実数は無限にあるのに、使えるのは有限個のビットだけです。だから必ず近似して、どこかで打ち切ります。その打ち切り方の設計図が「数値形式」です。
10 進で考えると見通しが良くなります。光の速さ 299,792,458 m/s を「約 3.00 × 108 m/s」と書くとき、私たちは情報を 2 つに分けています。有効数字 3.00(どれくらい細かいか)と指数 108(どれくらいの桁か)です。有効数字が 3 桁なら 2.99 × 108 と 3.00 × 108 のあいだは表せません。もっと細かくしたければ有効数字の欄を増やす。10-30 のような極端な桁まで書きたければ指数の欄を増やす。書ける文字数が決まっているなら、この 2 つは取り合いになります。
浮動小数点数(floating point number = 小数点の位置が値によって動く数値形式)は、これを 2 進でやっているだけです。ビット列を 3 つの欄に区切ります。
- 符号(sign) — 正か負か。常に 1 ビット
- 指数(exponent) — 2 の何乗か。10 進の「× 108」に当たる欄
- 仮数(mantissa) — 有効数字に当たる欄。fraction とも呼ばれる
値 = (-1)符号 × 1.仮数 × 2(指数 − バイアス)
「1.仮数」は「1 に続く小数部だけを仮数欄が持つ」という意味です。先頭の 1 は必ず 1 なので保存しません(暗黙の 1)。バイアス(bias)は指数欄に負の数を入れるための下駄で、FP32 では 127 です。指数欄に入るのは 0 以上の整数なので、そこから 127 を引くことで負の指数まで表せます。
数値を入れてみます。FP32 で 0.375 を表す計算例です。0.375 = 1.5 × 2-2 なので、符号 = 0、仮数の小数部 = 0.5、指数欄 = -2 + 127 = 125。ビット列にすると 0 01111101 10000000000000000000000 になります。この 3 欄への割り振り方を変えたものが FP16、BF16、FP8、FP4 です。
3 欄の幅 — FP32・FP16・BF16 の実際のビット数
PyTorch の公式ドキュメントは、扱える dtype を S-E-M 記法(符号 - 指数 - 仮数)で明記しています。表に出てくる正規化数とは「暗黙の 1 が立っている普通の表現」のことです(指数欄が最小のときだけ暗黙の 1 が無い非正規化数になり、もっと小さい値を粗く表せます。以降この本では正規化数の範囲だけを扱います)。範囲の値は FP32 / FP16 が numpy.finfo の実測、FP8 が FP8 論文(arXiv:2209.05433)の Table 1、FP4 は OCP Microscaling のリファレンス実装(microsoft/microxcaling)の式を評価した値、BF16 は 1-8-7 のビット配分から計算した値です。
| 形式 | S-E-M | 最大の正規化数 | 最小の正規化数 | PyTorch の型名 |
|---|---|---|---|---|
| FP32 | 1-8-23 | 3.4028235e+38 | 1.1754944e-38 | torch.float32 |
| FP16 | 1-5-10 | 65504 | 6.104e-05(= 2-14) | torch.float16 |
| BF16 | 1-8-7 | 約 3.3895e+38 | 約 1.1755e-38(= 2-126) | torch.bfloat16 |
| FP8 E4M3 | 1-4-3 | 448(= 1.75 × 28) | 0.015625(= 2-6) | torch.float8_e4m3fn |
| FP8 E5M2 | 1-5-2 | 57344(= 1.75 × 215) | 6.104e-05(= 2-14) | torch.float8_e5m2 |
| FP4 E2M1 | 1-2-1 | 6.0 | 1.0 | torch.float4_e2m1fn_x2 |
| E8M0 | 0-8-0 | 符号も仮数も持たず、2 の冪だけを表す(後述の共有スケール専用) | torch.float8_e8m0fnu | |
型名の接尾辞にも意味があります。PyTorch のドキュメントによれば f は有限値のみ(無限大を持たない)、n は NaN の符号化が IEEE 754 と異なる、uz は負のゼロを持たない、という意味です。torch.float8_e4m3fn という名前が「無限大を持たず NaN の表し方が独自の E4M3」だと読めるようになります。
同じ 16 ビットでも、FP16 は指数 5・仮数 10、BF16 は指数 8・仮数 7 です。この差が実際にどう出るかは、1 つの値を各形式で表してみると分かります。NVIDIA Transformer Engine のドキュメントが 0.3952 という値の最近傍表現を並べています(差は引き算した計算例です)。
| 形式 | 0.3952 に最も近い表現値 | 元の値との差 |
|---|---|---|
| FP16(仮数 10bit) | 0.395264 | +0.000064 |
| BF16(仮数 7bit) | 0.394531 | -0.000669 |
| FP8 E4M3(仮数 3bit) | 0.40625 | +0.01105 |
| FP8 E5M2(仮数 2bit) | 0.375 | -0.0202 |
表の差の列を割り算してみる計算例です。仮数 10 ビットの FP16(0.000064)に対し、仮数 7 ビットの BF16(0.000669)はずれが約 10 倍。仮数 3 ビットの FP8 E4M3(0.01105)は FP16 の約 170 倍で、桁が 2 つ上がります。仮数 2 ビットの FP8 E5M2(0.0202)はさらにその約 2 倍。並べると規則が見えます。仮数を 1 ビット削るごとに刻みの幅が 2 倍粗くなるので、3 ビット削れば誤差は約 8 倍(≒ 1 桁)、7 ビット削れば約 128 倍(≒ 2 桁)になる。仮数ビットの数は、そのまま「1 つの値をどれだけ正確に写せるか」です。
指数は範囲、仮数は精度
指数の欄を広げると、届く大きさの範囲(ダイナミックレンジ)が広がります。仮数の欄を広げると、刻みが細かくなります。総ビット数が決まっているなら、この 2 つは必ずトレードオフです。NVIDIA Transformer Engine のドキュメントはこれを一文で書いています。「増大したダイナミックレンジのトレードオフは、格納される値の精度の低下である」。
BF16 が学習で好まれる理由がここにあります。BF16 の指数は 8 ビットで、FP32 とまったく同じ幅です。だから表せる大きさの範囲(約 1.18 × 10-38 〜 3.39 × 1038)が FP32 とほぼ一致します。FP16 は 6.10 × 10-5 〜 65504 しかありません。
学習で問題になるのは下側です。ここで出てくる勾配(gradient)は「重みを次にどちらへどれだけ動かすかを示す小さな数値」で、学習中だけ流れるものです(推論では出てきません)。Transformer Engine のドキュメントは「FP16 のダイナミックレンジは勾配値の分布を格納するには十分だが、その分布が FP16 で扱える範囲より高すぎる/低すぎる位置に中心を持つことがある」と述べ、その対策として loss scaling(2 の冪だけを掛けて分布を FP16 の表現可能範囲へ移す操作)を説明しています。つまり FP16 での学習は、放っておくと小さい勾配が 6.10 × 10-5 を下回ってゼロに潰れる(アンダーフローする)ので、追加の仕掛けが要ります。BF16 は範囲が広いのでその仕掛けが不要です。運用が楽なほうが選ばれた、というだけの話です。
代償は精度です。BF16 は FP16 の上位互換ではありません。仮数が 7 ビットしかないので刻みは FP16 より粗く、先の 0.3952 の表では誤差が 10 倍大きくなっていました。「新しいから良い」ではなく「範囲を買って精度を売った」形式だと理解してください。なお 16 ビットである以上、重み 1 個あたりのバイト数は FP16 と BF16 で同じ 2 バイトです(第 1 章のサイズ計算はどちらでも変わりません)。
FP8 — 2 種類あるのは、前方と勾配で要求が逆だから
形式名の読み方をここで固定します。E は指数ビット数、M は仮数ビット数で、符号ビットは名前に出ません。だから E4M3 は 1 + 4 + 3 = 8 ビット、E5M2 も 1 + 5 + 2 = 8 ビット、E2M1 は 1 + 2 + 1 = 4 ビットです。E8M0 だけは符号を持たない特殊な形式で 0 + 8 + 0 = 8 ビットです。
8 ビットの浮動小数点には規格が 2 つあります。FP8 論文(NVIDIA・Arm・Intel の共著)の Table 1 がその中身です。
- E4M3 — 指数バイアス 7、最大 448(= 1.75 × 28)、最小の正規化数 2-6。無限大を持たず、NaN も
S.1111.111の 1 パターンだけ - E5M2 — 指数バイアス 15、最大 57344(= 1.75 × 215)、最小の正規化数 2-14。無限大と NaN は IEEE 754 準拠
論文はこの設計を「E5M2 は IEEE 754 の慣習に従い、FP16 から仮数ビットを削ったものとみなせる。対して E4M3 は、特殊値に使われるビットパターンをほぼ回収してダイナミックレンジを広げた」と説明しています。E5M2 が FP16 とほぼ同じ範囲を持つのは指数が 5 ビットで同じだからで、図 2-2 で 2 本の棒が同じ長さになっているのがそれです。
推奨される使い分けも論文が明示しています。E4M3 を重みと活性化に、E5M2 を勾配に。前方計算に出てくる重みと活性化は値の幅が比較的狭いので、範囲より精度が欲しい。逆に勾配は桁が大きく散らばるので範囲が要る。同じ 8 ビットを逆向きに配分した 2 つを、用途で使い分けているわけです。
ハードウェアの話も一次情報で押さえます。Transformer Engine のドキュメントは「FP8 データ型は Hopper アーキテクチャで導入された」「対応デバイスは Ada 以降(SM 8.9+)」と書いています。ただし vLLM のドキュメントによれば compute capability 7.5(Turing)でも FP8 のモデルは読み込めて、その場合は重みだけ 8 ビットの weight-only として動きます。「対応している」と「その速度が出る」が別だという話は第 7 章で扱います。
FP4(E2M1)— 表せる値は 16 個しかない
指数 2 ビット・仮数 1 ビットまで削ると、表せる値を全部書き出せます。NVIDIA の公式ブログが列挙しているとおり、大きさは 0, 0.5, 1.0, 1.5, 2, 3, 4, 6 の 8 通り。これに符号が付いて合計 16 コードです。無限大も NaN も持ちません。最大値は 6.0、最小の正規化数は 1.0 です。
言い換えると、FP4 に「7」は無いし「0.3」も無い。0.3952 を FP4 で表すと 0.5 になります(列挙した値からの帰結です)。この粗さで重みを保存できるはずがない、というのが素直な感想で、それは正しい。FP4 は単体では使えません。使えるようにする仕掛けが、この章の最後に出てくる共有スケールです。
もう一つ実装上の事実として、4 ビットをそのまま扱えるハードウェアはほとんどありません。だから 2 個を 1 バイトに詰めます。PyTorch の型名が torch.float4_e2m1fn_x2(x2 = 2 個パック)になっているのはそのためです。Hugging Face のドキュメントは INT4 についてパック方法を式で書いています。
packed_byte = (val1 & 0x0F) | (val2 << 4)
ネイティブな 4 ビット演算器が無くても得はあります。同ドキュメントの説明どおり「主な利益はメモリ帯域の削減から来る。パックされた int4 重みをメモリから読むのは int8 の 2 倍速い」からで、大きいモデルではメモリアクセスがボトルネックなので、アンパックと逆量子化の計算コストを上回ることが多いのです。
整数の等間隔と、浮動小数点の不均一
ここまでは浮動小数点の話でした。量子化の実務では整数形式も同じくらい使われます。INT8 は 256 段(符号付きで -128 〜 127)、INT4 は 16 段(符号付きで -8 〜 7)です。浮動小数点との決定的な違いは、整数の目盛りが等間隔だということ。指数の欄が無いので、刻みは全域で一定になります。これを uniform(均一)量子化と呼びます。
同じ 16 通りでも置き場所が違う。この違いが効くかどうかは、保存したいデータの分布で決まります。FP8 論文は「その非線形なサンプリングにより、FP8 は推論において int8 より有利になることがある」と書いています。LLM の重みはゼロ中心の釣鐘型に近い(QLoRA 論文が Shapiro-Wilk 検定で確認しています)ので、ゼロ付近が密な目盛りのほうが当たりやすい。なぜ 4 ビットでも文章が書けるのかという本題は第 4 章で扱います。
ただし非均一な形式には代償があります。量子化サーベイ(arXiv:2103.13630)は「非均一量子化は固定ビット幅でより高い精度を達成できることがある」と認めた上で、「GPU や CPU のような汎用の計算ハードウェアで効率的にデプロイするのは一般に難しく、単純さとハードウェアへの効率的な写像のおかげで均一量子化が事実上の標準になっている」と述べています。目盛りが不規則だと、掛け算の前に表を引く手間が増えるからです。
NF4 — 分布から目盛りを決める第 3 の置き方
QLoRA 論文(arXiv:2305.14314)が導入した NF4(4-bit NormalFloat)は、整数でも浮動小数点でもありません。作り方の鍵は分位点(quantile)で、これは「分布を等しい確率で区切る位置」のことです。たとえば 4 分位点なら、値全体の 25%・50%・75% がそこより下に来る 3 点を指します。手順は 3 ステップです。(1) 標準正規分布 N(0,1) の 2k+1 個の分位点を推定して k ビットのデータ型を作る、(2) その値を [-1, 1] に正規化する、(3) 入力の重みテンソルを絶対最大値で [-1, 1] に正規化してから量子化する。つまり 16 個の代表値が正規分布の分位点として決まった実数の表であって、符号・指数・仮数への分解を持ちません。
設計上の工夫として、ゼロを厳密に表せるように非対称な作り方をしています(負側 2k-1 個・正側 2k-1+1 個の分位点を推定し、重複するゼロを 1 個削る)。パディングなどのゼロを誤差なしで量子化できることを、論文は「重要な性質」と呼んでいます。
主張は正確に受け取る必要があります。論文は NF4 を「正規分布データに対して情報理論的に最適」と呼びましたが、後続論文(arXiv:2306.06965)が「量子化される値の分布はブロックサイズに依存するので、その最適性は厳密には成り立たない」と指摘し、期待 L1 再構成誤差の最小化から導いた AF4 を提案しています。ただし同論文の結論は「大きいブロックサイズでは AF4 のほうが良く、小さいブロックサイズでは NF4 とほぼ同等」で、NF4 の既定ブロックサイズ 64 では両者はほぼ一致します。実務上 NF4 が十分に良い、という結論は変わりません。NF4 の使いどころと double quantization は第 5 章で扱います。
ブロック共有スケールと bpw — 4.25 の 0.25 はどこから来るか
FP4 が単体で使えない理由を整理すると、範囲の問題です。最大 6.0 までしか届かないのに、実際の重みは 0.001 のことも 0.8 のこともある。ではテンソル全体を 1 つの倍率で割ってから 4 ビットに落とせばいいか。MX 論文(arXiv:2310.10537、Microsoft・AMD・Intel・Meta・NVIDIA・Qualcomm の共著)はここを否定します。「FP8 や INT8 のような最も狭い形式は、各テンソルのダイナミックレンジに合わせるためテンソル単位のスケーリングファクタを必要とする。しかし 8 ビット未満の形式では、限られたダイナミックレンジのためテンソル単位のスケーリングでは不十分であることが示されている」。
そこで出てきたのが Microscaling(MX)です。範囲の問題を 4 ビットの外へ出します。連続する 32 要素をひとまとめ(MX ブロック)にして、そのブロックだけで共有する 8 ビットのスケールを 1 個持たせる。論文の定義では、ブロックは共有スケール X 1 個と k 個の要素 Pi から成り、実際の値は vi = X · Pi で復元されます。こうすると 4 ビットは「そのブロック内での相対値」だけを表せばよくなる。ブロック単位のスケールという考え方そのものは第 3 章で原理から扱います。
共有スケールに使われるのが E8M0 です。指数 8 ビット・仮数 0 ビットなので、2 の冪だけを表します。Transformer Engine のドキュメントによれば E8M0 のバイアスは FP32 と同じ 127 で、変換は FP32 表現から 8 ビットの指数を抜き出すだけで済みます。MX 論文は「すべての具体的な MX 形式は共有スケールの形式として E8M0 を使う。これらの形式が表せる指数の集合は FP32 が表せる指数の上位集合である」と書いています。
ここで、この本を通して使う指標を定義します。bpw(bits per weight)= 重み 1 個を保存するのに実際に使うビット数の平均です。スケールなどの付帯情報を要素数で割り勘して足したものが実効値になります。
実効 bpw = 要素のビット幅 + スケールのビット数 ÷ ブロックサイズ
MXFP4 に数値を入れる計算例です。要素は FP4 で 4 ビット、スケールは E8M0 で 8 ビット、ブロックは 32 要素。4 + 8 ÷ 32 = 4 + 0.25 = 4.25 bpw。「4 ビット量子化なのに 4.25」の 0.25 は、8 ビットのスケールを 32 要素で割り勘した分です。OpenAI の gpt-oss モデルカードが「重みは 1 パラメータあたり 4.25 ビット」と書いているのは、この算術のことです。
| 形式 | 要素 | 共有スケール | ブロック | 実効 bpw |
|---|---|---|---|---|
| MXFP8 | FP8(8bit) | E8M0(8bit) | 32 | 8 + 8÷32 = 8.25 |
| MXFP6 | FP6(6bit) | E8M0(8bit) | 32 | 6 + 8÷32 = 6.25 |
| MXINT8 | INT8(8bit) | E8M0(8bit) | 32 | 8 + 8÷32 = 8.25 |
| MXFP4 | FP4 E2M1(4bit) | E8M0(8bit) | 32 | 4 + 8÷32 = 4.25 |
| NVFP4 | FP4 E2M1(4bit) | FP8 E4M3(8bit)+ テンソル単位の FP32 1 個 | 16 | 4 + 8÷16 = 4.5 |
| NF4(DQ なし) | NF4(4bit) | FP32(32bit) | 64 | 4 + 32÷64 = 4.5 |
| NF4 + DQ | NF4(4bit) | スケール自身を再量子化 | 64 | 4 + 0.127 = 約 4.127 |
出典の切り分けをしておきます。MX の 4 形式のブロックサイズ・スケール形式・要素のビット幅は MX 論文の Table 1 に載っている値ですが、bpw 列は Table 1 には無く、上の式に当てはめた計算例です。MXFP4 の 4.25 だけは OpenAI の gpt-oss モデルカードが「4.25 bits per parameter」と明記しています。NVFP4 の 4.5 は NVIDIA 公式ブログの「4.5 bits per value」、NF4 の 2 行は QLoRA 論文の「32/64 = 0.5 bits per parameter」と「8/64 + 32/(64·256) = 0.127 bits」がそれぞれ出典です。
NVFP4 は NVIDIA Blackwell 世代で導入された 4 ビット形式で、要素は MXFP4 と同じ E2M1 です。違うのは 3 点。(1) ブロックが 16 要素、(2) 共有スケールが FP8 E4M3、(3) さらにテンソル全体に FP32 のグローバルスケールが 1 個付く二段構え。Transformer Engine のドキュメントによれば値は x = xe2m1 · sblock · sglobal で復元されます。
なぜ手間を増やすのか。NVIDIA の公式ブログが両方の言い分を書いています。E8M0 は「スケールファクタを最近傍の 2n にスナップするので、ブロックの最大値に対して大きな量子化誤差を生じることがあり、しばしばブロック全体の量子化誤差を大きくする」。対して E4M3 は「16 個の値全体で誤差の総和が小さくなるような小数スケールを 1 つ選ぶ」。さらにブロックを 32 から 16 に半減させることで「局所的なダイナミックレンジに合わせる機会が 2 倍」になります。代償が bpw で、4.25 から 4.5 に増える。それでも E8M0 が残っている理由も同ブログが書いています。「単純さが最優先のとき」— テンソル単位の追加スケールが要らず、計算がわずかに単純だからです。
bpw の効き方を実物で確かめられます。例に使う gpt-oss は MoE(Mixture of Experts、= 層の中に小さな専門家ネットワークを何十個も置き、トークンごとに数個だけ使う構造)のモデルです。gpt-oss はモデルカードで「MoE 重みを MXFP4 形式に量子化した」「重みは 1 パラメータあたり 4.25 ビット」「MoE 重みは総パラメータ数の 90% 以上を占める」と述べていますが、それ以外のテンソルは BF16 のままです。モデルカードの Table 1(20b は MLP が 19.12B、Attention 0.64B、Embed + Unembed 1.16B、チェックポイント 12.8 GiB)に当てはめる計算例を出すと、19.12 × 109 × 4.25 + (0.64 + 1.16) × 109 × 16 = 1.101 × 1011 ビット = 12.81 GiB。公式値とほぼ一致し、チェックポイント全体の平均は 5.26 bpw になります。「4.25 ビットのモデル」でも、モデル全体の平均 bpw は 4.25 ではありません。
もう一点。実務で「MXFP4 のモデル」と言われるものは、たいてい重みだけ 4 ビットで活性化は BF16 です。MX 論文の Table 6 がその理由で、LLaMA-7B の wikitext perplexity(= 次の単語をどれだけ当てにくかったかの指標。低いほど良い。詳しくは第 9 章)は FP32 で 9.488、重みも活性化も MXFP4 にすると 27.201 まで崩れます。重みだけ MXFP4・活性化を MXFP6 にすれば 11.147 に収まる。gpt-oss の README も活性化の精度は BF16 推奨と明記しています。どのハードでどの形式が実際に速く動くかは第 7 章、この bpw から必要メモリを出す手順は第 8 章です。
つまずきポイント
「E4M3 は 4 ビットだと思っていた」
名前に符号ビットが出ないので、総ビット数は E + M + 1 です。E4M3 = 8 ビット、E5M2 = 8 ビット、E2M1 = 4 ビット。E8M0 だけは符号を持たないので 0 + 8 + 0 = 8 ビットです。形式の表を読むたびに足し算する癖を付けてください。この読み方が身に付かないと、MXFP4 の bpw が計算できません。
「BF16 のほうが新しいから FP16 より正確」
逆です。BF16 は範囲を FP32 並みに広げる代わりに仮数を 7 ビットに削っています。0.3952 の最近傍表現は FP16 が 0.395264、BF16 が 0.394531 で、誤差は BF16 のほうが 10 倍大きい。BF16 が学習で選ばれるのは精度ではなく範囲(と loss scaling が不要になる運用の楽さ)のためです。
「4 ビットにすればファイルは 1/8 になる」
なりません。共有スケールの分だけ増えます(MXFP4 で +0.25、NVFP4 と NF4 で +0.5 ビット/重み。NF4 はスケール自身を再量子化する double quantization を掛ければ +0.127 まで下がります)。さらに埋め込み・出力層・attention は量子化されないことが多く、gpt-oss-20b は「4.25 ビットのモデル」でありながらチェックポイント平均は 5.26 bpw です。見積りは形式名の数字ではなく実効 bpw で行ってください。
「MXFP4 と NVFP4 は名前が違うだけ」
要素形式は同じ E2M1 ですが、ブロックが 32 対 16、共有スケールが E8M0(2 の冪のみ)対 FP8 E4M3(小数スケール可)、そして NVFP4 だけテンソル単位の FP32 スケールを追加で持ちます。実効 bpw も 4.25 対 4.5 で違うので、メモリ見積りの数字も変わります。
理解度チェック
Q1. BF16 が FP16 より学習に向くと言われる理由を、ビット配分から説明してください。
BF16 の指数は 8 ビットで FP32 と同じ幅なので、表せる範囲が約 1.18e-38 〜 3.39e+38 と FP32 にほぼ一致します。FP16 の下限は 6.10e-05 で、小さい勾配がここを下回るとゼロに潰れるため loss scaling という追加の仕掛けが必要になります。BF16 はそれが不要です。代償は仮数 7 ビットの粗さで、精度では FP16 に負けます。
Q2. torch.float8_e5m2 は何ビットの形式で、表せる最大値はいくつですか。
1(符号)+ 5(指数)+ 2(仮数)= 8 ビットです。最大の正規化数は 1.75 × 215 = 57344。指数が FP16 と同じ 5 ビットなので範囲もほぼ同じで、FP8 論文は E5M2 を「FP16 から仮数ビットを削ったもの」と説明しています。用途の推奨は勾配です。
Q3. MXFP4 の実効 bpw が 4.25 になる理由を式で説明してください。
要素が FP4 で 4 ビット、共有スケール(E8M0)が 8 ビット、ブロックが 32 要素なので、4 + 8 ÷ 32 = 4.25 です。0.25 は 8 ビットのスケールを 32 要素で割り勘した分。NVFP4 はブロックが 16 要素なので 4 + 8 ÷ 16 = 4.5 になります。
Q4. INT4 と FP4 はどちらも 4 ビット、つまり 16 通りのビットパターンしか持てません。LLM の重みを保存するなら、どちらの目盛りの置き方が有利になりやすいですか。
0 付近が密な FP4 側です。LLM の重みはゼロ中心の釣鐘型に近いので、値が集まる場所に目盛りが多いほうが当たりやすい。FP8 論文も「非線形なサンプリングにより FP8 は推論において int8 より有利になることがある」と書いています。ただし目盛りをさらに分布へ寄せた NF4 のような非均一形式には、汎用ハードウェアで速く動かしにくいという別の代償があります。
Q5. あるモデルが「4.25 ビット量子化」と書かれています。パラメータ数 × 4.25 ÷ 8 でファイルサイズを見積もって良いですか。
良くありません。4.25 bpw が適用されるのは量子化されたテンソルだけで、埋め込み・出力層・attention は高精度のまま残ることが多いからです。gpt-oss-20b は MoE 重み(総パラメータの 90% 以上)が MXFP4 の 4.25 bpw ですが、残りが BF16 なのでチェックポイント平均は 5.26 bpw、実サイズは 12.8 GiB です。見積りの正しい手順は第 8 章で扱います。
まとめ
- 浮動小数点は符号 1 ビット + 指数 + 仮数の 3 欄。指数が届く範囲、仮数が刻みの細かさを決める。総ビット数が決まっていれば必ずトレードオフになる
- FP32 = 1-8-23、FP16 = 1-5-10、BF16 = 1-8-7。BF16 は FP32 と同じ指数幅で範囲を保ち、仮数を 7 ビットに削った形式で、FP16 の上位互換ではない
- 形式名の E と M は指数と仮数のビット数で、符号は名前に出ない。E4M3 と E5M2 は同じ 8 ビットを逆向きに配分した 2 択(重み・活性化 / 勾配)
- FP4(E2M1)が表せる大きさは 0, 0.5, 1, 1.5, 2, 3, 4, 6 の 8 通りだけ。単体では使えず、共有スケールと組んで初めて成立する
- 整数は等間隔、浮動小数点は 0 付近が密、NF4 は正規分布の分位点。同じ 16 通りでも置き場所が違い、置き場所が分布に合うほど有利になる
- bpw = 要素のビット幅 + スケールのビット数 ÷ ブロックサイズ。MXFP4 は 4 + 8÷32 = 4.25、NVFP4 は 4 + 8÷16 = 4.5。ブロックを小さくすると精度は上がるがビットは増える
ここまでで「箱の形」が分かりました。次は中身を詰める話です。実際の重みをその 16 個の目盛りにどう割り当てるのか — scale と zero-point の決め方、そしてブロックという粒度の効き方を第 3 章で扱います。
参考リンク
- PyTorch: dtype 一覧(S-E-M 記法と接尾辞の意味)
- FP8 Formats for Deep Learning(E4M3 / E5M2 の Table 1)
- Microscaling Data Formats for Deep Learning(MX の定義と Table 1・Table 6)
- QLoRA: Efficient Finetuning of Quantized LLMs(NF4 と block-wise absmax)
- NF4 Isn't Information Theoretically Optimal (and that's Good)(AF4 の指摘)
- A Survey of Quantization Methods for Efficient Neural Network Inference(uniform / non-uniform)
- gpt-oss-120b & gpt-oss-20b Model Card(4.25 bits per parameter)
- NVIDIA Transformer Engine: Using FP8 and FP4(形式ごとの最近傍表現と loss scaling)
- NVIDIA Transformer Engine: MXFP8(E8M0 の定義とスケール計算)
- NVIDIA Transformer Engine: NVFP4(二段スケーリングの式)
- NVIDIA: Introducing NVFP4(FP4 の表現値一覧と E8M0 対 E4M3)
- Hugging Face: Quantization concepts(int4 packing とメモリ帯域)
- OpenAI gpt-oss README(MXFP4 の格納形式と BF16 活性化)