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?

Redis 作者が DeepSeek V4 専用の推論エンジンを C で書いた ― DwarfStar(ds4.c) のアーキテクチャを読む【第1回/全8回】

本シリーズは、antirez(Salvatore Sanfilippo、Redis 作者)が公開した DeepSeek V4 Flash 専用推論エンジン DwarfStar(リポジトリ名 ds4)のコードを読み解く連載です。第1回は「なぜ専用エンジンを書くのか」という設計思想と、それを支える DeepSeek V4 の特異なアテンション構造の全体像を扱います。

リポジトリ: https://github.com/antirez/ds4
※コードの行番号・定数は執筆時点(閲覧コミット ba00a8a)のものです。実装は beta 品質(ds4-agent のみ alpha)で活発に変化するため、引用箇所は各自で再確認してください。

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

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

TL;DR

  • DwarfStar は 汎用 GGUF ランナーではない。DeepSeek V4 Flash(および高メモリ機向けの Pro)1モデルだけを、ロード・プロンプト整形・ツール呼び出し・KV 永続化・HTTP API・コーディングエージェントまでエンドツーエンドで"完成"させることを狙った C 実装。
  • GGML/llama.cpp にリンクしない完全自己完結。ただし量子化レイアウト・カーネルの知見は llama.cpp から借用しており、敬意を込めて LICENSE に著作権表示を残している。
  • DeepSeek V4 のアテンションは MLA の発展形で、raw sliding-window KV + 学習済み圧縮器(compressor) + 疎選択器(indexer) の三層構造。これが「1M トークンコンテキストをローカル機で」を可能にする。
  • 設計を貫く一文:「KV キャッシュは RAM のものという発想を捨てよ。圧縮 KV と高速 SSD の時代、KV キャッシュはディスクの一級市民だ」

1. 「1モデル特化」という賭け

ローカル推論の世界には優れたプロジェクトが無数にありますが、新モデルが出るたびに注目はそちらへ移っていきます。DwarfStar の README はこの状況に対して、意図的に狭い賭けをすると宣言します。

This project takes a deliberately narrow bet: one model at a time, official-vector validation (logits obtained with the official implementation), long-context tests, and enough agent integration to know if it really works.

つまり、

  1. 一度に1モデルだけを相手にする
  2. 公式実装で得た logits(official vector)に対して正しさを検証する
  3. 長文脈テストを回す
  4. 「本当に使えるか」を確かめられる程度にはコーディングエージェント統合まで作り込む

汎用ランナーが「広く・浅く」なりがちなのに対し、DwarfStar は「狭く・深く」を選びます。著者のビジョンは明快で、ローカル推論とは次の3つが箱から出してすぐに噛み合う状態を指す、というものです。

  • A) HTTP API 付きの推論エンジン
  • B) そのエンジンの前提に合わせて特別に作られた GGUF
  • C) コーディングエージェント実装による検証

この A/B/C 一体という思想が、後の回で扱う「専用 GGUF 量子化」「DSML ツール呼び出し」「分散 KV 永続化」のすべての前提になっています。

もう一つ、コミュニティ的に正直で面白いのは、このコードが GPT-5.5 の強力な支援で書かれたと公言している点です。

バージョン表記「GPT-5.5」は README 原文(strong assistance from GPT 5.5)のままです。読者が誤記と取り違えやすい箇所なので、引用時は原文どおりである旨を添えると安全です。

This software is developed with strong assistance from GPT 5.5 ... If you are not happy with AI-developed code, this software is not for you.

「AI が書いたコードが嫌なら使うな」と言い切りつつ、人間がアイデア・テスト・デバッグを主導したと明記する。AI 時代の OSS のあり方としても読みどころです。


2. 対象モデル: DeepSeek V4 Flash / Pro の素性

エンジンが対象とするモデルの形状(shape)はコード内にハードコードされています。ds4.cDS4_SHAPE_FLASH を見ると、モデルの全体像が一望できます。

// ds4.c
static const ds4_shape DS4_SHAPE_FLASH = {
    .name = "DeepSeek V4 Flash",
    .n_layer = 43,
    .n_embd = 4096,
    .n_vocab = 129280,
    .n_head = 64,
    .n_head_kv = 1,
    .n_head_dim = 512,
    .n_rot = 64,             // RoPE は末尾 64 次元だけに適用
    .n_expert = 256,         // MoE: 256 エキスパート
    .n_expert_used = 6,      //       うち 6 個をトークンごとにアクティブ
    .n_expert_shared = 1,    //       + 共有エキスパート 1
    .n_ff_exp = 2048,
    .n_swa = 128,            // raw sliding window = 128 トークン
    .n_indexer_head = 64,
    .n_indexer_head_dim = 128,
    .n_indexer_top_k = 512,  // 圧縮行から 512 行だけを選んで attend
    .n_hc = 4,               // Hyper-Connection: 4 本の残差ストリーム
    .n_hc_sinkhorn_iter = 20,
    .expert_weight_scale = 1.5f,
    ...
};

Pro 版(DS4_SHAPE_PRO)は 61 層 / embd 7168 / 128 ヘッド / 384 エキスパート / indexer top_k 1024 と、すべてがひと回り大きくなります。

注目すべきは MoE の構造です。256 個のルーテッドエキスパートのうち、1トークンあたり 6 個しか発火しないn_expert_used = 6)。総パラメータは巨大(Flash で 284B 級)でも、トークンごとの実効計算量は小さい。これが「284B が 96/128GB の個人機で動くのに、27B/35B の dense モデルよりずっと"大きく感じる"」という README の主張の根拠です。

パラメータ規模は DeepSeek 公式モデルカード(HuggingFace、2026-06-01 取得時点) に基づきます。公式は Flash を 284B / アクティブ 13B、Pro を 1.6T / アクティブ 49B、コンテキスト 1M、FP4+FP8 Mixed と記載しています。モデルカードは更新され得るため、引用時は取得日を併記すると後からズレを追えます。本記事の n_expert_used 等は ds4.c のハードコード形状から確認した値です。

Being so large, Flash knows more things if you go sampling at the edge of knowledge.


3. アテンションの三層構造 ― MLA の発展形

DwarfStar の最大の見どころは、DeepSeek V4 の 圧縮アテンションです。一般的な Transformer は「全過去トークンの K/V を保持して attend する」ため、コンテキストが伸びると KV キャッシュが線形に膨張します。DeepSeek V4 はこれを三層に分けて解きます。

「直近は高精度のまま・古いものは圧縮・圧縮行は indexer で間引く」の三段構えが一目で分かります。以下、各層を順に見ます。

3-1. raw sliding-window KV(直近 128 トークン)

直近 DS4_N_SWA = 128 トークンの K/V を高精度のままリングバッファに保持します。ds4.ckv_cache_push_raw() は、満杯になると 1 行スライドする素直な実装です。

// ds4.c: 直近 128 トークンの raw KV。満杯になると 1 行ずつスライド
static void kv_cache_push_raw(ds4_layer_cache *cache, const float *kv) {
    if (cache->n_raw < cache->cap_raw) {
        float *dst = cache->raw_kv + (uint64_t)cache->n_raw * DS4_N_HEAD_DIM;
        for (uint32_t i = 0; i < DS4_N_HEAD_DIM; i++)
            dst[i] = f16_to_f32(f32_to_f16(kv[i]));   // F16 相当に丸める
        cache->n_raw++;
        return;
    }
    memmove(cache->raw_kv, cache->raw_kv + DS4_N_HEAD_DIM, ...);  // スライド
    ...
}

3-2. compressor(学習済み圧縮器)

128 トークンより古い情報は捨てるのではなく、圧縮して保持します。ここが核心です。圧縮率は層ごとに異なり、層のパリティで 4 と 128 を交互に切り替えます

// ds4.c: 層ごとの期待圧縮率
static uint32_t ds4_expected_layer_compress_ratio(uint32_t il) {
    switch (DS4_MODEL_VARIANT) {
    case DS4_VARIANT_FLASH:
        if (il < 2) return 0;                       // 最初の2層: 圧縮なし(局所のみ)
        return (il & 1u) == 0 ? 4u : 128u;          // 偶数層: 4:1 / 奇数層: 128:1
    case DS4_VARIANT_PRO:
        if (il < 2) return 128u;
        return (il & 1u) == 0 ? 4u : 128u;
    ...
    }
}
  • ratio 0(Flash の最初の2層): 圧縮器なし。raw sliding window だけ=純粋な局所アテンション。
  • ratio 4(偶数層): 4トークンを1行に畳む軽い圧縮。indexer 付き
  • ratio 128(奇数層): 128トークンを1行に畳む極めて重い圧縮。indexer なし(行が少ないので全部見られる)。

「軽い圧縮で細かく見る層」と「重い圧縮で大局を見る層」を交互に積むことで、局所と大局の両方を低コストで扱う、という設計です。層ごとの圧縮率を並べると、偶奇で 4 と 128 が交互に並ぶのが分かります。

層     :  0    1    2    3    4    5    6   ...  41   42
ratio  :  0    0    4   128   4   128   4   ...  128   4
            └ 局所のみ  │    └──────────────────────────┘
                       └ ratio 4  = 軽圧縮 + indexer(top-512)
                         ratio 128 = 重圧縮 (indexer なし / 行が少ない)

compressor は単なる平均プーリングではありません。スコアによる softmax 加重プーリング + 位置バイアスという学習済み機構です。compressor_decode_one() の本体を追うと、

// ds4.c: 1トークン分の圧縮器更新(抜粋)
// 1) 層出力 x から「KV 行」と「スコア行」を同時に射影
matvec_q8_0_pair_prequant(kv_cur, sc_cur, model, wkv, wgate, xq, xscale);

// 2) スコアに位置依存の加法バイアス(APE: additive positional encoding)を足す
for (uint32_t j = 0; j < width; j++)
    sc_cur[j] += tensor_2d_value(model, ape, j, pos_mod);

// 3) ロール状態(frontier)に書き込む
memcpy(state_kv    + (uint64_t)row * width, kv_cur, ...);
memcpy(state_score + (uint64_t)row * width, sc_cur, ...);

// 4) ratio 境界(4 or 128 トークンごと)で 1 圧縮行を吐く
if (should_compress) {
    compressor_pool_decode_state(pooled, state_kv, state_score, head_dim, ratio);
    // RMS 正規化 → tail RoPE → FP8 量子化(往復) ...
}

compressor_pool_decode_state() の中身は、スコアを logit とみなした softmax 加重平均です。

// out[j] = Σ_r softmax(score[r][j]) * kv[r][j]
for (uint32_t r = 0; r < compress_ratio; r++) {
    const float w = expf(state_score[r*width + j] - max_score);
    denom += w;
    sum   += w * state_kv[r*width + j];
}
out[j] = denom > 0.0f ? sum / denom : 0.0f;

つまり compressor は「直近 N トークンのうち、どれをどれだけ残すか」を学習されたスコアで重み付けして 1 行に畳む小さなアテンションなのです。位置バイアス(APE)により、N トークンの中の相対位置も考慮されます。

3-3. indexer(疎選択器)

ratio-4 の層では、圧縮行が大量に貯まります(1M コンテキストなら 25 万行)。全部に attend するのは重いので、indexer が「いま重要な top-k 行」だけを選びます。

indexer は本体アテンションとは独立した、細い(head_dim 128)クエリ系を持ちます。スコア上位 n_indexer_top_k = 512(Pro は 1024)行だけを本体アテンションに渡すことで、計算量を実質一定に抑えます。

// ds4.c: indexer は ratio==4 の層にだけ確保される
if (ratio == 4) {
    cache->layer[il].index_comp_kv    = xmalloc_zeroed(comp_cap * DS4_N_INDEXER_HEAD_DIM, ...);
    cache->layer[il].index_state_kv   = ...;   // indexer 用の frontier
    cache->layer[il].index_state_score = ...;
}

4. 「KV キャッシュはディスクの一級市民」

三層構造の帰結として、DeepSeek V4 の KV キャッシュは異常に小さいです。README の数値では、フル 1M トークンコンテキストでも約 26GB、しかもその大半(約22GB)が indexer です。

この圧縮率が効くと、世界観が変わります。従来「KV キャッシュは RAM に置くもの、コンテキストを使い切ったら捨てるもの」でした。しかし圧縮 KV と現代の高速 SSD を組み合わせれば、KV キャッシュをディスクに永続化して、セッションを跨いで再利用できる

The KV cache is actually a first-class disk citizen.

第3回で扱う Metal グラフ上の圧縮 KV、第4回で扱う DSV4 ペイロードと SHA1 ディスク KV キャッシュ、第5回で扱うサーバのプレフィックス再利用、第7回で扱うエージェントの「セッション = オンディスク KV」は、すべてこの一文から派生しています。


5. もう一つの柱: thinking が「短い」

性能面で DwarfStar が DeepSeek V4 Flash を推す理由として、thinking(思考)セクションの短さが挙げられています。

In thinking mode, if you avoid max thinking, Flash produces a thinking section ... even 1/5 of other models in many cases, and crucially, the thinking section length is proportional to the problem complexity.

思考の長さが問題の複雑さに比例するため、簡単な問いには短く、難しい問いには長く考える。これにより、他モデルでは thinking を有効にすると実用に耐えない状況でも、Flash は thinking を有効にしたまま使える、という主張です。エンジン側も DS4_THINK_NONE / HIGH / MAX の3モードを持ち、コンテキスト長に応じて Think Max を自動的にフォールバックさせる作りになっています(ds4_think_mode_for_context())。

この「1/5」「複雑さに比例」は README 著者の定性的な観察であり、原文に比較対象・プロンプト数・temperature・コンテキスト長・平均/中央値といった測定条件は示されていません。本記事でも「作者が自プロジェクトで観察した主張」として読んでください。定量比較が必要なら、同一条件(同一プロンプト集合・同一サンプリング・同一コンテキスト)で thinking トークン数を実測する必要があります。


6. バックエンドとビルド

production パスは Apple Silicon の Metal が主ターゲット、次いで Linux の CUDA(特に DGX Spark / GB10 に配慮)。CPU パスは「参照・デバッグ用」で、しかも macOS の VM 実装のバグでカーネルクラッシュを起こすため使うなと強く警告されています。

この macOS カーネルクラッシュの記述は README 著者の警告そのままで、原文には対象 macOS バージョン・機種・再現条件・issue/radar 番号が示されていません。実害の大きい警告なので、実際に CPU パスを試す前に最新 README と issue を確認してください。本番は Metal/CUDA を使う前提なので、通常の利用では踏まない経路です。

make                  # macOS Metal
make cuda-spark       # Linux CUDA, DGX Spark / GB10
make cuda-generic     # その他の CUDA GPU
make cpu              # CPU 専用(診断用)

主要バイナリは ds4(CLI)と ds4-server(HTTP API)、加えて ds4-agent / ds4-eval / ds4-bench


次回予告

第2回は、128GB の Mac で 284B モデルを動かす核心 ―「非対称 2bit 量子化」と「imatrix(重要度行列)」を扱います。「ルーテッド MoE エキスパートだけを 2bit に落とし、それ以外は触らない」という割り切りが、なぜ品質を保ったまま巨大なサイズ削減を実現するのか。gguf-tools/ のコードを読みながら、IQ2_XXS / Q2_K のブロックレイアウトまで降りていきます。


参考: 第1回で触れた主なコード

項目 場所
モデル形状定義 ds4.c DS4_SHAPE_FLASH / DS4_SHAPE_PRO
層ごとの圧縮率 ds4.c ds4_expected_layer_compress_ratio()
KV キャッシュ確保 ds4.c kv_cache_init()
圧縮器本体 ds4.c compressor_decode_one() / compressor_pool_decode_state()
設計思想 README.md "Motivations" 節

本記事は 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?