Unweight:品質を犠牲にせずにLLMを22%圧縮した方法
著者: Mari Galicer, Ivan Nikulin, Chris Branch
公開日: 2026-04-17
所要時間: 12 分
50ms以内に世界の95%のインターネット接続人口に対して推論を提供するには、GPUメモリを徹底的に効率化する必要があります。昨年は Rust ベースの推論エンジン Infire でメモリ利用率を改善し、モデルスケジューリングプラットフォーム Omni でコールドスタートを解消しました。次の大きなボトルネックはモデルの重み(weights)です。
1トークンを生成する際、LLM はすべてのモデル重みを GPU メモリから読み出します。多くのデータセンターで使用している NVIDIA H100 GPU では tensor cores がメモリよりも最大で約600倍速く処理できるため、計算ではなくメモリ帯域がボトルネックになります。メモリバスを越えるバイト数が少なければ、より速くトークンを生成できます。
そこで我々は Unweight を構築しました。これはロスレス圧縮システムで、ハードウェアに特殊な依存をせずにモデル重みを最大で15–22%小さくし、ビット単位で同一の出力を保持できます。コアのブレークスルーは、重みをオンチップの高速共有メモリ(shared memory)で復号(decompress)してそのまま tensor cores に渡すことで、遅いメインメモリへの往復を避ける点にあります。
Unweight のランタイムはワークロードに応じて複数の実行戦略から選択します。単純さを優先する戦略もあれば、メモリトラフィックを最小化する戦略もあり、オートチューナーが重み行列とバッチサイズごとに最適なものを選びます。本稿では Unweight の仕組みを詳述します。合わせて技術報告と GPU カーネルの OSS 化も行っています。
初期結果として、Llama-3.1-8B では MLP(Multi-Layer Perceptron)重みのみで約30%の圧縮を確認しました。デコードに関係するパラメータに選択的に適用するため、モデル全体では15–22%のサイズ削減と約3 GBのVRAM節約につながります。これにより GPU の効率が向上し、より多くのモデルをより多くの場所で動かせるようになり、Cloudflare のネットワーク上での推論が安価かつ高速になります。
圧縮が思ったより難しい理由
モデル重みを創造的に圧縮して推論を高速化したり、小さいGPUで動かしたりする研究は増えています。最も一般的なのは量子化(quantization)で、32/16ビット浮動小数点を8/4ビット整数に変換してサイズを削減します。これはロッシー(非可逆)圧縮で、異なる16ビット浮動小数点が同じ4ビット値に写像され得るため、精度低下が品質に予測不可能な影響を与えます。多様なユースケースを扱う本番推論では、モデルの振る舞いを正確に保存するロスレスな手法を求めていました。
最近のシステム(Huff-LLM, ZipNN, ZipServ)は LLM 重みをかなり圧縮できることを示していますが、対象が異なります。ZipNN は配布や保管向けで CPU 側での復号を想定し、Huff-LLM は専用 FPGA ハードウェアによるデコードを提案し、ZipServ は消費者向けGPUを対象としており当社の H100 と互換しません。これらはいずれも我々が必要とする「Hopper(H100)上でのロスレスな推論時復号」と「Rust ベース推論エンジンへの統合」を満たしませんでした。
課題は単なる圧縮そのものではありません。BF16 の指数バイト(exponent)は冗長性が高く、エントロピー符号化は良く効きます。しかし復号が十分速くないと推論が遅くなります。H100 上では tensor cores が多くの時間メモリ待ちでアイドルになっていますが、その余剰を復号処理に単純に転用できません。GPU 内の各計算ユニットは共有メモリ制約のため復号カーネルか行列乗算カーネルのどちらかしか同時に実行できず、復号遅延が行列乗算と完全に重ならないとトークン遅延に直接加算されます。
Unweight の解は、重みをオンチップの高速共有メモリで復号して tensor cores に直接渡すことです。ただし、これを異なるバッチサイズや重み形状に対して効率的に動作させるための工学的工夫が必要でした。
モデル重みを効果的に圧縮する方法
AIモデル内の数値は基本的に16ビットの "brain float"(BF16)で表現されます。各 BF16 値は次の3つの部分から成ります:
- Sign(符号): 1 bit — 正/負
- Exponent(指数): 8 bits — 大きさ(オーダー)
- Mantissa(仮数): 7 bits — そのオーダー内の精度
符号と仮数は重み間でほとんど予測不能に変動し、圧縮に適しません。一方で指数は別の特性を示します。
複数の先行研究は、学習済みLLMにおいて256通りの指数値のうちごく少数が支配的であることを示しています。典型的な層では上位16の指数値が全重みの99%以上を占めます。情報理論的にはこの分布を表現するのに約2.6ビットしか必要ないため、8ビットは冗長です。
Unweight はこの冗長性を利用します。符号と仮数はそのままにして、指数バイトだけをハフマン符号化(Huffman coding)で圧縮します。ハフマンは頻出値に短いコード、稀な値に長いコードを割り当てる古典的手法で、指数分布が強く偏っているため指数ストリームで約30%の圧縮が得られます。
この手法は MLP(gate, up, down 投影)重み行列に選択的に適用します。これらはモデルパラメータの約2/3 を占め、トークン生成時のメモリトラフィックを支配します。Attention 重み、埋め込み(embeddings)、LayerNorm は非圧縮のままです。全体として最適化は MLP 重みサイズの約20%削減に相当し、技術報告で詳細に説明しています。
まれな指数値を持つ少数の重みについては別処理を行います。具体的には、64要素の行の中に上位16パレット外の指数が1つでもあれば、その行全体をそのまま(verbatim)保存します。これによりホットパスで要素ごとの分岐を排し、ホットコードでは行単位で一度だけ判定を行います。
GPU メモリのボトルネック
NVIDIA H100 には主に2種類のメモリがあります:
- High Bandwidth Memory(HBM): 大容量だがアクセスは相対的に遅い。モデル重みはここに置かれる。
- Shared memory(SMEM): 極めて高速だが小さい。計算直前にデータをステージングする場所。
推論では各トークン生成において重み行列を HBM から読み出す必要があり、HBM と SMEM の間のメモリバスが性能上のボトルネックです。H100 の tensor cores は HBM が供給するデータよりずっと速く数値を処理できます。圧縮によりバスを横断するバイト数が減れば、トークン生成は速くなります。ただし GPU は圧縮データのまま計算できないため、復号が必要です。
多くの既存手法は重み行列全体を復号して HBM に戻し、通常の行列乗算を行います。これは容量面では有益ですが、帯域幅面では効果が薄く、毎トークンごとに未圧縮行列を HBM から読み出し続けるため帯域幅削減に寄与しません。
圧縮重みを利用する4つの方法
推論時に圧縮重みを使う最適解は一つではなく、ワークロード(バッチサイズ、重み行列の形、復号に利用できる GPU 時間)によって変わります。Unweight は次の4つの実行パイプラインを提供します:
- フルハフマン復号(full Huffman decode): 元の BF16 を完全復元して
cuBLAS に渡す。最も単純だが前処理で多くのバイトを HBM に書き戻す。バッチサイズが小さく行列乗算が短い場合に有利。
- 指数のみ復号(exponent-only decode): 前処理トラフィックを半分にする。指数バイトだけ復元して残りは組み合わせる。
- パレットトランスコード(palette transcode): 実行時に4ビットのパレットインデックスへトランスコードし、前処理トラフィックを4分の1にする。再構築型行列乗算(reconstructive matmul)を用いる。
- 直接パレット(direct palette): モデルロード時に事前トランスコードしておき、前処理を完全にスキップ。行列乗算カーネルがインデックスからオンザフライで BF16 を再構築する。前処理コストはゼロだがカーネル側の作業が増える。
これらはスペクトラムを形成します。端点は「フル復号+cuBLAS」と「直接パレット」です。中間に「指数のみ復号」と「ランタイムパレット変換」があり、両者は再構築型行列乗算(共有メモリで復号して tensor cores に直接渡すカスタムカーネル)を使います。
なぜ単一のパイプラインが勝たないのか
前処理を減らすと HBM への書き戻しが少なくなりメモリバスを早く解放できますが、再構築の仕事を行列乗算カーネルに押し付けることになります。このトレードオフが有効かどうかは状況によります。
- 小さなバッチサイズ(例: 1–64 トークン)では行列乗算が非常に短いためカーネル内部で重い再構築を行う余裕がなく、カスタムカーネルの固定コストが支配的になります。
cuBLAS によるフル復号が有利な場合が多いです。
- 大きなバッチサイズ(例: 256+ トークン)では行列乗算が長時間走り、追加の再構築を吸収できます。軽い前処理の方が早く終わり、解放されたメモリバス帯域と計算のオーバーラップが有利になります。ここではパレットや指数パイプラインが優位になります。
同一層内の別個の重み行列でも、gate や up と down のように次元が異なれば行列乗算内で行われる操作の順序が変わり、最適なトレードオフも変化します。
そのため Unweight は単一戦略をハードコードせず、ランタイムが各重み行列とバッチサイズに最適なパイプラインを選びます。オートチューナーはターゲットハードウェア上でのエンドツーエンドのスループットを実測して最適解を判断します。
再構築型(reconstructive)行列乗算の仕組み
4つのパイプラインのうち3つは、復号と計算を融合したカスタム行列乗算カーネルを使います。カーネルは HBM から圧縮データを読み出して共有メモリで BF16 を再構築し、そのまま tensor cores に供給します。復元済みの重みがメインメモリに現れることはありません。
内部では GPU のスレッドグループを2種類の役割に分けます:
- Producer グループ: TMA(専用のメモリコピーハードウェア)を使って HBM から圧縮データを共有メモリにロードします。符号+仮数バイト、指数データ(またはパレットインデックス)、そして稀な指数を持つ行の verbatim データをステージします。Circular buffer を満たすようにコンシューマより先行して動き、必要になる前にデータを用意します。
- Consumer グループ: 共有メモリ内で指数と符号+仮数を組み合わせて BF16 値を再構築し、そのまま Hopper の
WGMMA テンソルコア命令に渡します。再構築された重みは共有メモリから直接計算に入るためメインメモリへ出ることがありません。
この方式により、MLP 重み行列については従来より約30%少ないバイトがメモリバスを越えることになり、トークン生成の帯域幅ボトルネックを軽減できます。
以上は本文の主要点の要約です。詳細な実装、ベンチマーク、オートチューニングの手法や技術報告は別途公開している技術資料および GPU カーネルの OSS を参照してください。