<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>量子化</title><link>https://blog.ast.moe/tags/%E9%87%8F%E5%AD%90%E5%8C%96/</link><description>Recent posts from 五里霧中</description><generator>Hugo</generator><item><title>FP6から考えるLLM量子化: 限られたbitをどこに配るか</title><link>https://blog.ast.moe/blog/2026-09-24/</link><pubDate>Thu, 24 Sep 2026 13:43:00 +0900</pubDate><guid>https://blog.ast.moe/blog/2026-09-24/</guid><description>&lt;p&gt;LLMの量子化について調べながら、そもそもFP8を使うのはどういう場面だろう、と考えていた。さらに調べるとFP6やFP4もある。6bitという半端な幅はどう保存するのか、そこまでbit数を減らした浮動小数点はどのくらいの精度で値を表せるのかが気になった。&lt;/p&gt;
&lt;p&gt;FP6の表現可能な値を並べてみると、普段使う浮動小数点ではあまり意識しない、隣り合う値の間隔がはっきり見える。同じ6bitでも指数と仮数への配分によってその間隔が変わる。ならば、重みの分布に合わせて表現を選べないだろうか。&lt;/p&gt;
&lt;p&gt;最初は単に「6bitはどう保存するのか」という疑問だったのに、表現可能な値を見ているうちに、どの値を正確に残すかという話になった。そこから、情報の偏りに合わせて少ないbitを配分するという点で、画像圧縮のことを思い出した。さらに、圧縮に時間をかけて結果をよくするZopfliのことも連想した。&lt;/p&gt;
&lt;p&gt;この記事で考えたい問いは、&lt;strong&gt;限られた保存bit数をどこに、どんな表現で割り当てれば、モデルの挙動を壊しにくいか&lt;/strong&gt;というものだ。まずFP6で失われる値を具体的に見て、そこから立てた仮説をJPEGやZopfliの発想、既存の量子化研究と照らす。最後に、何を改善とみなして測るべきかを考える。&lt;/p&gt;
&lt;p&gt;量子化方式を実装・評価した結果ではない。以下は、数値表現の観察を出発点にした仮説と、先行研究から分かる範囲を分けて書いたものになる。&lt;/p&gt;
&lt;h2 id="背景-6bitへの疑問"&gt;背景: 6bitへの疑問&lt;/h2&gt;
&lt;h3 id="量子化と6bitの保存"&gt;量子化と6bitの保存&lt;/h3&gt;
&lt;p&gt;LLMの重みはFP16やBF16などで保持されることが多い。量子化は、これをINT8、INT4、FP8、FP6などの少ないbit数で近似し、モデルサイズや重みを読み出すメモリ帯域を減らす方法だ。対応する演算器やkernelがあれば計算の高速化にもつながるが、bit数を減らすだけで必ず速くなるわけではない。&lt;/p&gt;
&lt;p&gt;30B、つまり300億個の重みをすべて16bitで持つなら、重みだけで約60GBになる。8bitなら単純計算で約30GB、6bitなら約22.5GB、4bitなら約15GBだ。GPUが計算するたびに大量の重みをメモリから運ぶことを考えると、この差は保存容量だけの話ではない。ただし、実際にはscaleなどのメタデータが付くし、実行時にはactivationやKV cacheなど重み以外のメモリも必要なので、この数字がそのまま必要容量になるわけではない。&lt;/p&gt;
&lt;p&gt;コンピュータでは8bit、16bit、32bit、64bitのような幅をよく見る。6bitの値は8bitの箱へ入れて残り2bitを捨てるのかと思ったが、そうすると容量面では8bitと変わらない。実際には複数の値をbit単位で詰められる。6bitの値が4個なら24bitなので、3byteへ収まる。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;6bit × 4個 = 24bit = 3byte
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;DRAMは物理的にはbitを記憶し、CPUからは通常byte単位のアドレス空間として扱う。6bitごとにアドレスを割り当てる必要はなく、読み出したbyte列から目的の値を展開すればよい。例えば3byteをまとめて読み、そこから4個の6bit値を取り出す。&lt;/p&gt;
&lt;p&gt;ただし、byteの境界をまたぐ値は取り出すときにshiftやmaskが要る。値を計算用の形式へ展開し、scaleを適用する処理も入る。大量の重みをSIMDやGPUのkernelでまとめて処理するとしても、packingとunpackingは無料ではない。メモリから運ぶ量を25%減らせても、展開が遅ければ8bitより速いとは限らない。「6bitなら8bitより25%小さい」と「6bitなら8bitより速い」は別の主張だ。&lt;/p&gt;
&lt;p&gt;この区別は後でZopfliの話につながる。モデルを作るときの計算を重くすることは許容できても、推論のたびに複雑な展開をするなら、その費用はずっと払い続けることになる。&lt;/p&gt;
&lt;h3 id="fp6で隣り合う値の間隔を見てみる"&gt;FP6で隣り合う値の間隔を見てみる&lt;/h3&gt;
&lt;p&gt;FP8は、値の範囲と精度をある程度保ちながらデータ量や転送量を減らす選択肢になる。では、さらに少ないFP6やFP4ではどうなるのか。
&lt;span&gt;
&lt;a target="_blank" href="https://www.opencompute.org/documents/ocp-microscaling-formats-mx-v1-0-spec-final-pdf"&gt;
OCPのMX形式仕様
&lt;/a&gt;
&lt;img class="external-link-icon" src="https://blog.ast.moe/images/outer-link.png" width="12" height="12" alt="" aria-hidden="true" loading="lazy"&gt;
&lt;/span&gt;
には、FP8、FP6、FP4を使う形式が載っている。ここではFP6のE3M2とE2M3を見てみる。名前は符号以外の指数bit数と仮数bit数を表す。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;形式&lt;/th&gt;
&lt;th style="text-align: right"&gt;符号&lt;/th&gt;
&lt;th style="text-align: right"&gt;指数&lt;/th&gt;
&lt;th style="text-align: right"&gt;仮数&lt;/th&gt;
&lt;th&gt;傾向&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;E3M2&lt;/td&gt;
&lt;td style="text-align: right"&gt;1bit&lt;/td&gt;
&lt;td style="text-align: right"&gt;3bit&lt;/td&gt;
&lt;td style="text-align: right"&gt;2bit&lt;/td&gt;
&lt;td&gt;広い範囲を表せるが値の間隔は粗い&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;E2M3&lt;/td&gt;
&lt;td style="text-align: right"&gt;1bit&lt;/td&gt;
&lt;td style="text-align: right"&gt;2bit&lt;/td&gt;
&lt;td style="text-align: right"&gt;3bit&lt;/td&gt;
&lt;td&gt;範囲は狭いが間隔は細かい&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;スケールを掛ける前の正の値だけを見ると、隣り合う表現可能値の距離は次のようになる。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;形式&lt;/th&gt;
&lt;th&gt;0付近の最小間隔&lt;/th&gt;
&lt;th&gt;大きい値の側の最大間隔&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;E3M2&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0, 0.0625, 0.125&lt;/code&gt; の間は &lt;code&gt;0.0625&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;16, 20, 24, 28&lt;/code&gt; の間は &lt;code&gt;4&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;E2M3&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0, 0.125, 0.25&lt;/code&gt; の間は &lt;code&gt;0.125&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;4, 4.5, 5, ..., 7.5&lt;/code&gt; の間は &lt;code&gt;0.5&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;例えばE3M2で8から14までがどうできるかを考える。指数部によって基準になる大きさが8になり、仮数の2bitで&lt;code&gt;1.00&lt;/code&gt;、&lt;code&gt;1.01&lt;/code&gt;、&lt;code&gt;1.10&lt;/code&gt;、&lt;code&gt;1.11&lt;/code&gt;の4通りを表す。それぞれ8、10、12、14だ。次の指数帯に入ると基準が16になり、16、20、24、28と並ぶ。保存している仮数の刻みは同じでも、指数が一つ上がれば絶対的な距離は倍になる。&lt;/p&gt;</description></item></channel></rss>