0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

2bit 量子化はなぜ「冗談ではない」のか: DwarfStar の非対称量子化と imatrix を読む【第2回/全8回】

本シリーズは、DeepSeek V4 Flash / Pro 専用推論エンジン DwarfStards4)のコードを読み解く連載です。
第2回は、128GB クラスの個人マシンで巨大 MoE モデルを動かすための要である、2bit 量子化と imatrix を扱います。

対象リポジトリ: https://github.com/antirez/ds4
主な参照箇所: README.md, gguf-tools/deepseek4-quantize.c, gguf-tools/quants.c, gguf-tools/imatrix/README.md, gguf-tools/mixed/README.md

連載「DwarfStar(ds4) を読む」全8回

  • 第1回 なぜ専用エンジンを書くのか
  • 第2回 非対称2bit量子化とimatrix(本記事)
  • 第3回 Metalグラフと圧縮KV
  • 第4回 ディスクKVキャッシュ
  • 第5回 サーバとDSMLツール呼び出し
  • 第6回 TCPパイプライン分散推論
  • 第7回 ネイティブエージェント
  • 第8回 ステアリング・MTP・評価基盤

TL;DR

  • DS4 の 2bit GGUF は、モデル全体を一律に 2bit 化しているわけではない。README は ルーテッド MoE エキスパートだけを量子化し、共有エキスパート・アテンション射影・ルーティングなどは品質維持のため残すと説明している。
  • 2bit の主役はルーテッドエキスパートの gate/up/down。代表的な q2 レシピでは gate / upIQ2_XXSdownQ2_K にする。
  • Q2_K は 256 要素を 84 バイトに詰める。16 要素ごとのスケール/最小値と、2bit コードを持つ K-quant 系レイアウト。
  • IQ2_XXS は 256 要素を 66 バイトに詰める。8 値単位のグリッド探索、符号マスク、グループスケールを使い、実装上 imatrix を要求する。
  • imatrix は「重みそのものの大きさ」ではなく、DS4 の実推論グラフでルーテッドエキスパートの入力列がどれだけ使われたかを集める活性の重要度。量子化誤差を均等に扱わず、重要な列を優先するための統計である。

1. なぜ「非対称」なのか

README の Model Weights 節には、2bit 量子化についてかなり強い表現があります。

The 2 bit quantizations provided here are not a joke

この主張の前提は、「すべてを 2bit にする」ではありません。DS4 の 2bit は非常に非対称です。README は、2bit 量子化について次のように説明しています。

  • ルーテッド MoE エキスパートだけを主に量子化する
  • up/gateIQ2_XXS
  • downQ2_K
  • ルーテッドエキスパートはモデルサイズの大半を占める
  • 共有エキスパート、射影、ルーティングなどは品質確保のため残す

MoE モデルでは、全パラメータのうち推論時に毎トークン使われる部分と、ルーターに選ばれたエキスパートだけが使う部分が分かれます。DeepSeek V4 Flash の形状は ds4.c で固定値として定義され、Flash は 43 層、各層 256 ルーテッドエキスパート、各トークンで 6 エキスパートを使います。

つまり、ファイルサイズの大半を占めるが毎回すべてが発火するわけではないルーテッドエキスパートを強く圧縮し、アテンションや共有エキスパートなど品質に効きやすい部分はより保守的に扱う、という設計です。

どれくらい「大半」なのか概算してみます。各ルーテッドエキスパートは gate/up/down の 3 行列で 4096 × 2048 × 3 ≈ 25.2M パラメータ。これが 256 エキスパート × 43 層なので 25.2M × 256 × 43 ≈ 277B。Flash の総パラメータ 284B 級のうち、ルーテッドエキスパートだけで約 97% を占める計算です。残り 3%(アテンション・共有エキスパート・ルーティング・埋め込み・出力ヘッド)は品質のため保守的に残します。

パラメータ規模は DeepSeek 公式モデルカード(HuggingFace、2026-06-01 取得時点) に基づきます。公式は Flash を 284B / アクティブ 13B、Pro を 1.6T / アクティブ 49B、コンテキスト 1M、FP4+FP8 Mixed と記載。モデルカードは更新され得るため、引用時は取得日を併記すると後からズレを追えます。97% は上記の概算で、レシピにより端数は前後します。

この割り切りが「284B 級の MoE を 96/128GB マシンに載せる」ための現実的なポイントです。


2. 量子化ポリシーはテンソル名で決まる

量子化ツールの中心は gguf-tools/deepseek4-quantize.c です。これは HF safetensors から DS4 専用 GGUF を作るための C 実装で、GGML にはリンクしていません。

量子化対象の振り分けは policy_type() が担っています。重要なのは、テンソル名を見てルーテッドエキスパートかどうか、さらに gate/down/up のどれかを判定している点です。

typedef struct {
    ds4q_type routed_w1, routed_w2, routed_w3;
    ds4q_type attention_proj, attention, shared, embedding, output, dense;
    type_override *overrides;
    int n_overrides;
} quant_policy;

parse_expert_tensor()ffn_gate_exps, ffn_down_exps, ffn_up_exps のような GGUF テンソル名を解釈し、EXP_W1, EXP_W2, EXP_W3 に分類します。policy_type() はこの分類に応じて、CLI で指定された --routed-w1, --routed-w2, --routed-w3 の型を返します。

--routed-w1 TYPE       routed gate expert tensor type
--routed-w2 TYPE       routed down expert tensor type
--routed-w3 TYPE       routed up expert tensor type
--attention-proj TYPE  attn_q/kv/output projection type
--shared TYPE          shared expert tensor type

gguf-tools/README.md の例では、q2 ルーテッドエキスパートの代表設定として次のようなオーバーライドが示されています。

--experts iq2_xxs
--routed-w2 q2_k
--attention-proj q8_0
--shared q8_0
--output q8_0

ここで --experts iq2_xxs はルーテッドエキスパート全体の既定を IQ2_XXS にし、--routed-w2 q2_k で down 射影だけ Q2_K に戻す形です。DeepSeek 系の FFN 命名では w1 = gate, w2 = down, w3 = up なので、README の「up/gate at IQ2_XXS, down at Q2_K」と対応します(コード内の分類名は EXP_W1/W2/W3 なので、policy_type() を読むときは「W1=gate, W2=down, W3=up」の対応を取り違えないよう注意)。

policy_type() の判定優先順位を図にすると次の通りです。

量子化は、単なる「圧縮率」の問題ではなく、どのテンソルをどの精度に落とすかというポリシーの問題です。DS4 はこのポリシーをコードで明示し、テンソル名ベースで再現可能にしています。


3. Q2_K: 256 要素を 84 バイトにする K-quant

gguf-tools/quants.c の型特性(type trait)を見ると、DS4 が実際に扱う量子化型とブロックサイズがまとまっています。

#define QK_K 256

[DS4Q_TYPE_Q2_K]    = { "q2_K",    QK_K,  84, true,  false },
[DS4Q_TYPE_Q4_K]    = { "q4_K",    QK_K, 144, true,  false },
[DS4Q_TYPE_IQ2_XXS] = { "iq2_xxs", QK_K,  66, true,  true  },

Q2_K は 256 要素を 84 バイトにする形式です。生の f16 なら 256 要素で 512 バイトなので、単純なバイト比では約 6.1 倍小さくなります。

ds4q_write_q2_k_block_ref() のレイアウトは以下の通りです。

enum { scales_off = 0, qs_off = 16, d_off = 80, dmin_off = 82 };

意味としては次の構成です。

領域 バイト 役割
scales 16 16 要素サブブロックごとのスケール/最小値のニブル
qs 64 256 個の 2bit コード
d 2 スケールの f16 基準値
dmin 2 最小値の f16 基準値
合計 84 256 要素ブロック

量子化コードは、16 要素ごとにスケールと最小値を推定し、値を 0..3 の 2bit コードに丸めます。その後、4 つの 2bit コードを 1 バイトにパックします。

qs_out[j / 4 + l] = L[j + l] |
                    (L[j + l + 32] << 2) |
                    (L[j + l + 64] << 4) |
                    (L[j + l + 96] << 6);

Q2_Krequires_imatrix = false ですが、imatrix があれば重み付き版の ds4q_write_q2_k_block_weighted() が使われます。重み付き版では、列ごとの重要度を量子化誤差の重みとして使い、スケールとコードの選び方を変えます。

if (quant_weights) {
    ds4q_write_q2_k_block_weighted(x, block, quant_weights + b * QK_K);
} else {
    ds4q_write_q2_k_block_ref(x, block);
}

ここが imatrix の効きどころです。型は同じ Q2_K のままでも、どの誤差を重く見るかが変わります。


4. IQ2_XXS: 小さいが、imatrix 前提の 2bit

IQ2_XXS は 256 要素を 66 バイトに詰める、さらに攻めた形式です。Q2_K よりも小さい一方で、quants.c の型特性では requires_imatrix = true になっています。

[DS4Q_TYPE_IQ2_XXS] = { "iq2_xxs", QK_K, 66, true, true }

実装コメントは、IQ2_XXS の構造を次のように説明しています。

  • 256 値のブロックを 8 個の 32 値グループとして扱う
  • 各 32 値グループは、4 つの 8 値グリッドインデックスと 4 つの 7bit 符号マスクを持つ
  • ブロックスケールは f16
  • グループごとの 4bit スケールニブルで微調整する
  • すべての 2bit 8 タプルを許すのではなく、256 個の小さなグリッドを使う
  • グリッドにないタプルは最近傍グリッドリストから探索する

コード上も grid_size = 256, map_size = 43692, neighbour_shells = 2 として、許されたグリッドと近傍探索表を初期化しています。

enum { grid_size = 256, map_size = 43692, neighbour_shells = 2 };

IQ2_XXS の量子化は ds4q_write_iq2_xxs_block() で行われ、冒頭で重要度ベクトルの存在を要求します。

static void ds4q_write_iq2_xxs_block(
        const float *x,
        uint8_t *y,
        const float *quant_weights) {
    assert(quant_weights);
    ...
}

これは設計上かなり重要です。IQ2_XXS は単に「2bit だから小さい」形式ではなく、「どの列の誤差を優先して小さくするか」という重みがあってはじめて成立する形式です。


5. imatrix は「重みの統計」ではなく「実行時の活性の統計」

gguf-tools/imatrix/README.md は、DS4 の imatrix の対象を明確に説明しています。

現在の imatrix の対象はルーテッド MoE 経路です。Flash では 43 層 × 256 ルーテッドエキスパート、Pro では 61 層 × 384 ルーテッドエキスパートが対象になります。各層には以下の 3 種類のルーテッドエキスパートテンソルがあります。

blk.N.ffn_gate_exps.weight
blk.N.ffn_up_exps.weight
blk.N.ffn_down_exps.weight

収集される値は次の通りです。

  • gate/up テンソル: FFN 正規化後の入力活性の二乗
  • down テンソル: ルーティング重み適用後のルーテッド SwiGLU 行の二乗

つまり imatrix は、重み行列を眺めて「この列が大きい」と判断するものではありません。DS4 の実際の Metal prefill グラフを走らせ、MoE 入力として実体化された活性を読んで、列ごとに sum(x[column]^2) を積算します。

importance[column] = sum(x[column]^2)

この統計は、「実際の DS4 推論でその列がどれだけ効いているか」に近い信号です。量子化時には、重要度の高い列ほど誤差が重く評価されます。


6. 較正データセットは DS4 の実ユースに寄せている

imatrix の品質は、何を較正データとして流すかに強く依存します。DS4 のデータセットは gguf-tools/imatrix/dataset/ にあり、再生成スクリプトは以下です。

python3 gguf-tools/imatrix/dataset/build_ds4_imatrix_dataset.py

README によると、現在追跡しているデータセットは 4682 個のレンダリング済みプロンプト、概算 2.91M トークンです。中身は単なる Wikipedia 断片ではなく、DS4 の想定ユースにかなり寄せられています。

  • このリポジトリ自身の C / Metal ソースレビュー
  • 長文脈スニペット
  • DSML 構文を使ったエージェント / ツール呼び出しプロンプト
  • 書き換え、要約、抽出、翻訳
  • ds4-eval の GPQA Diamond, SuperGPQA, AIME2025 プロンプト
  • thinking / non-thinking のアシスタントプレフィックス

この構成は、DwarfStar の設計思想そのものと一致しています。DS4 は「ローカルでコーディングエージェントとして使えるか」を重視しているため、量子化の較正もその用途に合わせています。

単にパープレキシティを下げるための一般コーパスではなく、「DS4 が実際に解かせたい種類の仕事」を使って MoE 活性の重要度を測っている点が面白いところです。


7. imatrix ファイル形式とエキスパートごとのパッキング

deepseek4-quantize.c は、llama.cpp の旧来のバイナリ .dat imatrix 形式を読みます。ただし DS4 ではルーテッドエキスパートが大量にあるため、エントリの持ち方が DS4 固有です。

gguf-tools/imatrix/README.md は次のように説明しています。

entry length = n_expert * n_columns

1 テンソルにつき 1 エキスパートベクトルではなく、1 ルーテッドエキスパートテンソルのエントリに、全エキスパート分の重要度ベクトルを連結して入れます。Flash なら 256 エキスパート、Pro なら 384 エキスパートです。

deepseek4-quantize.cimatrix_find() は、エキスパート ID が指定されている場合、該当エキスパートのセグメントを切り出して返します。

if (expert_id >= 0 && n_experts > 0 &&
    e->n_values == ncols * n_experts) {
    return e->values + expert_id * ncols;
}

このため、ファイル形式は llama.cpp の旧来 imatrix と互換でも、意味づけは DS4 のテンソル命名とエキスパートパッキングを理解した量子化ツールでないと正しく使えません。


8. imatrix がない場合のフォールバック

IQ2_XXS は重要度ベクトルを要求します。しかし初期の 2bit GGUF やテスト用途では、外部 imatrix がないケースもあります。その場合、deepseek4-quantize.c は合成フォールバックを作ります。

gguf-tools/README.md は次の式で説明しています。

importance[column] = sum(row[column]^2) over all rows

これは「実行時の活性」ではなく「重みの列エネルギー」です。安定した列重みにはなりますが、DS4 の実推論でその列がどれだけ使われるかを測ったものではありません。

README が imatrix 版を推奨している理由はここにあります。フォールバックは動くが、実活性から測った imatrix のほうが DS4 の用途に近い。


9. 評価は公式 DeepSeek 継続生成に対する NLL

量子化の良し悪しは、最終的にはモデル出力で見る必要があります。DS4 には gguf-tools/quality-testing/ があり、公式 DeepSeek API から継続生成を集め、ローカル GGUF の正解トークンの負の対数尤度(NLL)を測る流れが用意されています。

python3 gguf-tools/quality-testing/collect_official.py
make -C gguf-tools quality-score
gguf-tools/quality-testing/score_official MODEL.gguf \
  gguf-tools/quality-testing/data/manifest.tsv \
  /tmp/model.tsv \
  4096
python3 gguf-tools/quality-testing/compare_scores.py /tmp/old.tsv /tmp/new.tsv

gguf-tools/imatrix/README.md には、Q4 imatrix ファイルの評価結果が載っています。

old Q4 avg NLL:         0.177357819
Q4 imatrix avg NLL:     0.173895148
relative NLL change:   -1.95%
case wins:              54 imatrix / 46 old
first-token matches:    83 imatrix / 81 old
avg greedy LCP:         12.21 imatrix / 11.94 old

改善幅だけを見ると小さく見えるかもしれません。しかし、巨大モデルの低ビット量子化では「同じ型、同じサイズで、NLL が一貫して少し下がる」こと自体が価値です。特に DS4 のようにコーディングエージェント・長文脈・ツール呼び出しを狙う場合、単発ベンチよりも破綻しにくさが重要になります。


10. q2-q4-imatrix: 最後の 6 層だけ Q4 にする実験

DS4 には、さらに実用的な中間解があります。gguf-tools/mixed/ のスプライサは、2 つの互換 GGUF を読み、選択したルーテッドエキスパートテンソルだけをドナー GGUF からコピーします。

重要なのは、再量子化しないことです。テンソルのペイロードをバイト単位で差し替えるだけです。

gguf-tools/mixed/README.md によると、これまでの最良の混合 Flash 実験は、固定 imatrix の 2bit GGUF をベースにし、最後の 6 層 37-42 のルーテッドエキスパートだけを固定 imatrix の Q4 GGUF から持ってくる構成です。

python3 gguf-tools/mixed/splice_mixed_expert_layers_gguf.py \
  --base /path/to/DeepSeek-V4-Flash-IQ2XXS-w2Q2K-...-imatrix-fixed.gguf \
  --donor /path/to/DeepSeek-V4-Flash-Q4KExperts-...-imatrix-fixed.gguf \
  --q4-layers 37-42 \
  --out /path/to/DeepSeek-V4-Flash-Layers37-42Q4KExperts-...

README の記述では、この混合ファイルは約 91GB、フル Q4 は約 153GB です。出力一致チェックでは素の 2bit よりもフル Q4 に近い挙動を示したとされています。

これは「量子化は一枚岩ではない」という良い例です。最後の層ほど出力分布への影響が大きいなら、そこだけ高精度に戻すことで、サイズ増加を限定しながら品質を寄せられる可能性があります。


11. この記事の要点

DwarfStar の量子化で面白いのは、単なる「2bit で動いた」という話ではありません。

  1. モデル構造を理解して、サイズの大半を占めるルーテッド MoE エキスパートに狙いを絞る
  2. gate/up/down で型を変える
  3. IQ2_XXSQ2_K のブロック形式をローカル C 実装として持つ
  4. 実際の DS4 推論グラフから活性の重要度を集める
  5. 公式継続生成に対する NLL で検証する
  6. 必要なら最後の層だけ Q4 に戻す混合 GGUF も作る

この一連の流れは、DwarfStar が「推論エンジン」だけではなく「そのエンジン専用の GGUF をどう作るか」まで含めて設計されていることを示しています。

次回は、エンジンの心臓部である Metal グラフと圧縮 KV 実装を読みます。第1回で概観した raw SWA、compressor、indexer、Hyper-Connection、tail-only RoPE が、実際にはどのようにグラフとキャッシュに落ちているのかを追います。


本記事は Quick Iterate のローカル LLM 研究の一環として、公開リポジトリ antirez/ds4 のコードを読み解いたものです。行番号・定数・ベンチ値は閲覧コミット ba00a8a(2026-05-30)/README 取得日 2026-06-01 時点のものです。ds4-agent は alpha、エンジン本体は beta 品質で活発に変化するため、引用箇所は各自で最新の README / ソースに当たって再確認してください。

クイックイタレート株式会社 ― IoT / 電力監視 / AI / 衛星・無線通信 / システムインテグレーション
ローカル LLM・エージェント基盤に関するお問い合わせはお気軽にどうぞ。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?