本記事の調査には Anthropic の Claude とそのリサーチ機能を使用しています。本記事はソースコードと公開ドキュメントの机上調査であり、提案する改造はまだ実装も実測もしていません。また一部のソースは取得の制約で中身を確認できておらず、推測を含む箇所はその都度明記します。
はじめに
前回、プリフィル特化の重み SSD ストリーミングについて、層主導実行・全 expert 先読み・シーケンシャルリード化という設計と成立条件を机上で詰めました。
また、ds4 の既存 --ssd-streaming(デコード向けの expert キャッシュ方式)については、ロスレス MXFP4 版を実際に動かした実測記事があります。
今回はその続編として、「では実際に ds4 をどう改造すれば、前回設計した層主導のプリフィルストリーミングが動くのか」を、antirez/ds4 のソースコード・README・AGENT.md・issue を読んで調べました。前回の「わかっていないこと」に挙げた「ds4 本体のストリーミングが層主導・シーケンシャル設計になっているかは未確認」への答え合わせでもあります。
結論を先に書くと、ds4 は前回設計の中核をすでに半分持っていました。プリフィルはチャンク内では層主導で実行されており、SSD ストリーミングには層 2 枚分のバッファ予約と「現在層の計算中に次層をロードして隠す」という設計目標が明記されています。足りないのは大きく 3 つ、チャンク跨ぎのモデル 1 周化、router を待たない全 expert 先読み、そして expert 群粒度でのシーケンシャル読みです。本記事ではこの差分を 4 フェーズの改造計画に落とします。
結論サマリ
| 前回設計の要素 | ds4 の現状 | 必要な差分 |
|---|---|---|
| 層主導実行(全 N トークンを層単位で処理) | チャンク内は層主導。チャンク=0 指定で全体 1 バッチも可 | ストリーミングとの両立とメモリ管理 |
| SSD 読み出し=モデル 1 周 | チャンク跨ぎで再読(既定 4096 トークンごと) | 1 周固定の新経路 |
| router 非依存の全 expert 先読み | top-k 選択に駆動される LRU キャッシュ | 層開始時の全 256 expert 予約読み(LRU バイパス) |
| expert ID 昇順の grouped GEMM + シーケンシャルリード | 順序は router 依存、レイアウトはテンソル種別ごと連続 | 並べ替え固定と GGUF 再パック(最適化) |
| 層 2 枚分のダブルバッファ | キャッシュ予算が 2 層分を予約する実装あり | 群粒度への拡張 |
| KV の層単位退避 | 分散モードに層順の KV シリアライズあり | プリフィル層完了フックからの流用 |
| SSD → pinned RAM → VRAM の 3 段 | Metal は mmap/UMA で実質 2 段 | CUDA 移植時に pinned バッファで実装 |
ds4 のコード構成
改造対象の土地勘です。ds4 は C の単一巨大ファイル ds4.c(約 6.5 万行)を中心に、SSD ストリーミングが ds4_ssd.c / ds4_ssd.h(SSD streaming expert cache)として分離され、バックエンドは ds4_metal.m + metal/*.metal、ds4_cuda.cu、ds4_rocm.cu に分かれています。公開 API は ds4_engine と ds4_session の 2 型だけという簡潔な構成です(ds4.h)。モデルロードは mmap ベースで、テンソルデータは推論が触るまでカーネルのページキャッシュに留まり、Metal では mmap のスライスをノーコピーの MTLBuffer として包む、という方式がソース冒頭のコメントに書かれています。量子化・変換ツール群は gguf-tools/ 配下(deepseek4-quantize、quants.[ch])にあり、GGML には依存していません。改造で触る場所をフェーズ別に示すと次の通りです(P0〜P3 の内訳は後述の改造計画を参照してください)。
発見 1: プリフィルはすでに「チャンク内層主導」でした
前回記事でいちばん強調した「チャンク主導 vs 層主導」について、ds4 の現状は中間形でした。README によると、チャンク化された Metal プリフィルは、チャンクごとに同一の range-capable layer-major graph を再利用します。つまりチャンクの内側は層主導で、compressor / indexer の絶対位置境界を保ったまま任意のトークン範囲を 1 グラフで処理できる作りです。チャンク長は既定 4096 トークンで、--prefill-chunk あるいは環境変数 DS4_METAL_PREFILL_CHUNK で変更でき、0 を指定するとメモリが許す限りプロンプト全体を 1 バッチで処理します。
含意は 2 つあります。第一に、前回記事の「チャンク主導だと SSD 流量が ceil(N/C) × W に膨らむ」という問題は、ds4 ではチャンク跨ぎにだけ残っています。既定の 4096 のままストリーミングすると、N=32K で 8 周(FP4 系 147GB × 8 ≈ 1.18TB)ですが、チャンク=0 相当にできればモデル 1 周に収束します。第二に、AGENT.md には SSD ストリーミング時の設計目標として「現在層の推論時間で次層のロードを隠すこと」「キャッシュに無い routed expert のロードを、shared expert とキャッシュ済み expert の計算で隠すこと」が明記されており、キャッシュ予算の自動計算も「オーバーラップとプリフィル用に routed 2 層分(two full routed layers)を予約」してから動的 expert 数に換算します。前回設計の層単位ダブルバッファに相当する骨組みが、既に存在するということです。
発見 2: 既存ストリーミングは router 駆動の LRU で、全 expert 先読みではありません
実測記事で確認した通り、--ssd-streaming は非 routed(約 8.2GiB)を常駐させ、routed expert をメモリ上のキャッシュ経由で供給します。I/O は mmap のページフォルト任せではなく、確保済みバッファへの明示的な高速リードです。関連フラグは --ssd-streaming-cache-experts(個数または GB 指定)、--ssd-streaming-cold、--ssd-streaming-preload-experts などがあります。なおこのストリーミング機構は Metal 専用で(GLM 系は ROCm も対応)、CUDA には存在しません。
参考になるのが issue #437 で、コントリビュータが Metal ストリーミングの I/O を検証し、F_NOCACHE を付けた pread のダブルバッファ、エンコード位置の 2 層後方への MADV_DONTNEED、F_RDAHEAD の無効化、F_NOCACHE にはページ整列 I/O が必要、といった具体的な知見を残しています。なお改善案の試作コミット(residency-lru の 11f20b1、nommap の cae5e95)は報告者のフォーク側にあり、antirez 本体の main には取り込まれていません(比較のベースラインは上流の cafc134)。本改造の I/O 層はここを直接の下敷きにできます。
ギャップは選択の駆動源です。現行キャッシュは router の top-k 出力に駆動されますが、前回記事で示した通り、長いプリフィルではほぼ全 expert が活性するので、LRU の判断自体が無意味になります。層開始時に全 256 expert を予約読みする LRU バイパス経路が必要です。現行のデコード向け経路と、新設するプリフィル経路の違いを図にすると次の通りです。
なお ds4f-mxfp4 ブランチの先頭コミット 4893e0c(プレフィル済み層からストリーミング用キャッシュをシード)は方向性の近い機能で、今回の追加調査で、プリフィルで活性化した expert をそのままデコード用ストリーミングキャッシュのシードとして残す最適化であることが確認できました。実測記事で触れた三段構え(静的ホットリスト、LRU、プレフィルシード)の三段目に当たります。
発見 3: GGUF は expert 連続ではありません
前回記事では「1 expert = [gate|up|down] の連続領域」というファイル配置を提案しましたが、ds4 レイアウトの実際は違いました。routed expert は層あたり 3 本の 3 次元テンソル(ffn_gate_exps / ffn_up_exps / ffn_down_exps、expert が最外次元)で、ローダは tensor_expect_routed_expert がこの形状(DS4_N_EMBD=4096、DS4_N_FF_EXP=2048、DS4_N_EXPERT=256)を検証します。つまりテンソル種別ごとの連続配置で、同一 expert の gate/up/down は層内で離れています。
ここで一つ、前回記事の設計を精緻化できます。層単位で全 expert を読むだけなら、現行レイアウトでも各層は大きなシーケンシャルリード 3 本で済み、致命的ではありません。expert 連続への再パックが効くのは、8〜16 expert 束の群粒度でダブルバッファを回すときです。現行配置だと 1 束の読みが 3 レンジに分かれるのに対し、再パック後は 1 本の 100〜200MB 級リードになります。したがって再パックは Phase 0〜1 の必須要件ではなく、Phase 2 の最適化と位置づけるのが正確です。再パック自体は gguf-tools/deepseek4-quantize(ロスレス MXFP4 変換を追加したコミット 725b084 の系列)にレイアウトを追加すればよいのですが、ローダと各バックエンドの GEMM オフセット計算が現行の並びを前提にしているため、両方を同時に改修する必要があります。
改造で触るフラグ・環境変数の整理
本文に散在するフラグ類をここで一覧にしておきます。
| 名称 | 種別 | 挙動 | 確認状況 |
|---|---|---|---|
| --prefill-chunk / DS4_METAL_PREFILL_CHUNK | フラグ / 環境変数 | プリフィルのチャンク長。既定 4096、0 でプロンプト全体を 1 バッチ処理 | README で確認 |
| --ssd-streaming | フラグ | 非 routed を常駐させ、routed expert をキャッシュ経由で SSD から供給 | README / AGENT.md で確認 |
| --ssd-streaming-cache-experts | フラグ | expert キャッシュ容量を個数または GB で指定 | README で確認 |
| --ssd-streaming-cold | フラグ | コールド状態での開始(事前ロードを抑止する挙動と推定) | フラグの存在は README で確認 |
| --ssd-streaming-preload-experts N | フラグ | 起動時に N 個の expert を事前ロード | README で確認 |
| --batched-session N | フラグ | 複数セッションの同時実行。長いプリフィルは有界チャンクで交互処理 | README で確認 |
| DS4_METAL_STREAMING_EXPERT_HOTLIST | 環境変数 | 静的ホットリストの差し替え | 実測記事の挙動由来。ソース上は未確認 |
| DS4_SSD_AUTO_CACHE_PCT | 環境変数 | 自動キャッシュ予算の係数(50〜95%) | 実測記事の挙動由来。ソース上は未確認 |
| --prefill-layer-major | 新設案 | 層主導 + モデル 1 周ストリーミング | 本稿 Phase 0 の提案 |
改造計画: 4 フェーズ
以上を踏まえた改造ロードマップです。まず Metal(M5 Max 128GB)で通し、CUDA は後述の方針で移植します。
| フェーズ | 内容 | 主に触る場所 | 検証方法 | 目安 |
|---|---|---|---|---|
| 0 | --prefill-layer-major 追加。チャンク=0 相当 + --ssd-streaming の両立、層境界活性化(32KB/トークン)の保持 | ds4_cli.c、ds4.c のプリフィル分岐 | SSD 読み出し総量がモデル 1 周に一致するか(fs_usage 等で計測) | 1〜2 週 |
| 1 | router 非依存の全 expert 先読み(LRU バイパス)。ハッシュ層 0–2 は開始前に必要集合を確定 | ds4_ssd.c/.h、層ループ | 無駄読み expert 数(N>256 でほぼ 0 のはず)、先読みの隠蔽率 | 2〜3 週 |
| 2 | GGUF の expert 連続再パック + 8〜16 expert 束ね読み + 群ダブルバッファ | deepseek4-quantize、ds4_ssd.c、GEMM オフセット | 束 read 時間 vs 計算時間の損益分岐(前回試算の N* と突合) | 3〜4 週 |
| 3 | KV の層単位退避と disaggregated 接続 | 分散モードの KV シリアライズ、ds4_server.c | VRAM 上の KV が常に 1 層分 + バッファに収まるか | 2 週 |
全体の流れと合否ゲートを図にすると次の通りです。
Phase 0 の検証仮説を具体的に置いておきます。M5 Max の常駐プリフィルは README 値で 2048 トークン 790 t/s、16K で 572 t/s、32K で 557 t/s です。32K なら計算だけで約 59 秒かかる一方、routed 約 137GiB(12.75MiB × 256 expert × 43 層の概算。非 routed 分を足すと README 記載の MXFP4 版全体約 156GB とおおむね整合します)を 1 周読む時間は、内蔵 SSD 実効 6.5GB/s の仮定で約 21 秒です。
つまり前回記事の損益分岐の式の通り、この構成は最初から計算律速側にあり、層主導 + 全読みが正しく動けば、ストリーミングでも常駐時とほぼ同速のプリフィルになるはずです。実測記事のチャンク主導ストリーミング(約 194 t/s、常駐比 48%)からどこまで戻せるかが、設計の妥当性をいちばん直接に示す数字になります。合否判定を整理すると次の通りです。
| 指標 | 現状(チャンク主導 + ストリーミング) | Phase 0 の目標 |
|---|---|---|
| SSD 読み出し総量(N=32K) | 8 周 ≈ 1.18TB | 1 周 ≈ 147GB |
| プリフィル速度(M5 Max) | 約 194 t/s(実測記事、約 19K トークンのコールド測定) | 常駐同等の 550〜570 t/s 級(16〜32K の README 値) |
2 つとも満たせば、層主導 + 全読みの設計が実機で成立したと言えます。
Phase 3 は既存資産の流用です。ds4 の分散モードは coordinator / worker 間で層順のテンソルストリームとして KV をやり取りする機構を持っており、プリフィル中の「層 L 完了時にその層の KV をホストへ退避」というフックは、このシリアライズをほぼそのまま使えると考えられます。前回記事で述べた KV の層単位退避と disaggregated 構成(プリフィル専用ノードからデコード側ノードへの転送)の実装経路がここにあります。
CUDA 側の壁は二重です
主目的であるプリフィル専用ノード(RTX 5060 Ti 16GB ×2、SM120)には、独立した壁が 2 つあります。第一に、SSD ストリーミング機構そのものが CUDA バックエンドに存在しません。第二に、SM120(コンシューマ Blackwell)の FP4 MoE grouped GEMM は、汎用スタック側で現状壊れています。cutlass issue #3096 と FlashInfer #2723 / #2577 が報告している通り、これらのライブラリのネイティブ FP4 MoE バックエンドは SM120 でゴミ出力かクラッシュになり、vLLM 側の報告では attention のみのモデルや非 MoE モデルの FP4 は正しく動くのに、MoE 層を持つモデルだけが壊れる、と切り分けられています。原因は grouped GEMM の TMA Warp-Specialized テンプレートが sm_100a(データセンタ系)専用で、autotuner が試す tactic がすべて失敗して遅い代替カーネルに落ちるためとされます。パッチを当てて動かした報告でも 14.6〜39 tok/s 級(別モデル・デコード時)で、実用速度には遠い状況です。一方で ds4 はこれらのライブラリに依存しない自前カーネルで、README は Blackwell CUDA でのネイティブ FP4 実行(バッチ expert 計算に FP4 活性化、デコードは Q8 活性化)を記載しています。この不具合の影響を直接は受けない可能性がありますが、GeForce SM120(5060 Ti)での実挙動は未検証です。
そこで CUDA 移植は 3 案を比較しました。
| 案 | 内容 | リスク | 工数 | 備考 |
|---|---|---|---|---|
| A | main に取り込まれた CUDA MXFP4 カーネル(Blackwell ネイティブ FP4)をそのまま利用 | 中(GeForce SM120 での実挙動が未検証) | 小 | 動けば最短。まず実測 |
| B | cutlass の SM120 block-scaled grouped GEMM を利用 | 中(既知バグの解決待ち、追従コスト大) | 中 | GGML 非依存・自前実装という ds4 の方針とも相性が悪い |
| C | ロード時に FP16/FP8 へ dequant して既存カーネルで計算 | 低 | 低〜中 | 確実に動くフォールバック。束バッファ単位で dequant すれば常駐増なし |
推奨は、まず案 A を実測し、問題があれば案 C にフォールバックする二段構えです。main に CUDA MXFP4 カーネルが入った現在、当初想定していた Metal カーネルの移植は不要になりました。案 C はプリフィルが演算律速であることを利用した保険で、dequant の追加コストは大きなバッチに償却されやすく、SM120 の地雷を全部回避できます。参考フォークにも先例があり、Entrpi/ds4-on-spark はレジスタ上で dequant する D2R 方式、Trosfy/spark-ds4f は mmq Q4_K 経路を使っています。cutlass 系(案 B)は #3096 の解決を待ってからで十分と考えられます。
環境面の注意も 2 点あります。WSL2 では cudaHostRegister が使えない(cudaHostAlloc は可)とされており、その場合 mmap 済み GGUF をそのまま pinned 登録するゼロコピーはできず、pinned バウンスバッファを明示確保する必要があります。O_DIRECT / io_uring も WSL2 では挙動が不安定という報告があるため、本番はネイティブ Linux + ext4/xfs を推奨します(この 2 点は独立の一次確認が取れていない伝聞情報です)。また前回記事の通り GeForce は GPUDirect Storage の対象外なので、経路は SSD → pinned ホスト RAM → VRAM で設計します。
Metal 先行 がよいかも
CUDA 直行は「ストリーミング基盤の新規実装」と「GeForce SM120 での FP4 実挙動の確認」という 2 つの不確定を同時に背負うことになります。一方 Metal 側には、ds4_ssd.c のキャッシュ基盤、2 層分予約、層オーバーラップの設計目標、issue #437 の I/O 実測知見と、必要な部品がすべて揃っています。前回記事の「わかっていないこと」に挙げた最大の不確定要素、すなわち 3 段パイプラインの実効オーバーラップ率・SSD の持続帯域・grouped GEMM 効率は、実装しないと分からない類のものなので、部品の揃った Metal で先に潰し、成立条件を実測で固めてから案 C で CUDA へ持っていく、という順序が合理的と判断します。
追加調査で決着した 2 点と、着手前に確認すべき点
初稿の時点で情報源どうしが食い違っていた 3 点のうち、2 点は追加のファクトチェックで決着しました。
- DSpark(--mtp)との排他は一時的な制約で、現在は解消されています。実測記事の時点(ds4f-mxfp4、8 月 2 日)ではエンジン初期化で無条件排他(エラー文は --ssd-streaming is not compatible with --mtp yet)でしたが、現在の main の README は、本体モデルは常駐でも --ssd-streaming でも DSpark 支援モデルを併用できる(支援モデル分の重みとランタイム状態はメモリ所要に加算される)と明記しています。エラー文の yet が示唆していた通りの経過です。
- MXFP4 は main に取り込み済みです。issue #641(GGUF type 39 が読めずクラッシュ)は解消され、download_model.sh には MXFP4 ターゲット(約 156GB)が追加されています。README は MXFP4 GGUF が Metal と CUDA で動作し、Blackwell CUDA ではネイティブ FP4 行列命令と FP4 活性化をバッチ expert 計算に使う(デコードとその他の CUDA デバイスは Q8 活性化)と記載しています。ただし GeForce SM120(5060 Ti)での実挙動は前述の通り未検証です。
- 残る要確認は内部シンボルです。ds4_ssd.c のキャッシュ構造体名、ホットリスト関連の環境変数名(DS4_METAL_STREAMING_EXPERT_HOTLIST、DS4_SSD_AUTO_CACHE_PCT)などは、取得制約でソース上の確認が取れていません。
確認用の grep をまとめておきます。
git grep -n "ssd_streaming\|expert_cache\|prefill_chunk\|hotlist"
git grep -n "tensor_expect_routed_expert\|metal_graph\|layer_major"
git grep -n "mtp\|dspark" ds4.c # 排他チェックの現行実体
git grep -n "two full routed layers\|preload"
git log --oneline --all -- ds4_ssd.c # ストリーミング実装の変遷
わかっていないこと
- 本稿は全編ソースとドキュメントの机上調査で、提案した改造は未実装・未実測です。特に層主導 + 全読みが Metal のグラフ実行とストリーミングの同期でどこまで素直に書けるかは、書いてみないと分かりません
- ds4_ssd.c の内部構造(キャッシュ構造体名や環境変数名)は未確認で、該当箇所は推測を含みます。WSL2 の cudaHostRegister / O_DIRECT の挙動も独立の一次確認が取れていません
- 層主導化はサーバの連続バッチング(--batched-session、長いプリフィルをチャンクで交互処理する前提)と衝突しやすく、プリフィル専用ノードでは連続バッチングを無効化するのが素直ですが、共存させる場合の設計は詰めていません
- 上流へのパッチ提案かフォーク運用か。AGENT.md には「フラグの裏に恒久的な意味論の分岐を増やさない」「C++ を導入しない」という方針があり、層主導プリフィルは診断スイッチではなく恒久機能なので、フォークですすめるのが現実的と考えています
- WSL2 と各 SSD の実効持続帯域、SM120 の FP4 事情の今後の推移
まとめ
- ds4 のプリフィルはすでにチャンク内層主導(range-capable layer-major graph)で、SSD ストリーミングにも 2 層分予約と層オーバーラップの設計目標がありました。前回記事の設計は新規実装ではなく、既存機構への差分 3 点(チャンク跨ぎの 1 周化、全 expert 先読み、expert 群粒度のシーケンシャル化)に整理できます
- GGUF はテンソル種別ごとの連続配置で expert 連続ではありませんが、層単位読みなら現行のまま成立します。expert 連続への再パックは群ダブルバッファのための最適化(Phase 2)です
- 改造は 4 フェーズ、合計 2〜3 か月規模の見立てです。Phase 0 の合否は「SSD 読み出し総量がモデル 1 周に収束するか」「32K プリフィルが常駐時とほぼ同速(557 t/s 級)に戻るか」で判定できます
- CUDA は、main に取り込まれた MXFP4 カーネル(Blackwell ネイティブ FP4)の GeForce SM120 での実挙動をまず確認し、問題があれば dequant → FP16 の案 C にフォールバックします。cutlass / FlashInfer 系の SM120 不具合は、これらに依存しない ds4 の自前カーネルには直接は及ばない可能性があります。実装は Metal(M5 Max)先行を推奨します
- KV の層単位退避は分散モードの層順シリアライズを流用でき、前回記事の disaggregated 構成へ実装上も接続できる見通しです
参考資料
- 前回記事: プリフィルならウエイトはSSDに置けるかも(https://qiita.com/sukimaengineer/items/5f204f0a256a563ff8a4)
- 実測記事: DeepSeek V4 Flash 0731 のロスレスMXFP4版をSSDストリーミングで動かしてみた(https://qiita.com/sukimaengineer/items/c97f3e6aafdc63b7ac17)
- 前々回記事: MoEは1トークンをこう計算しています(https://qiita.com/sukimaengineer/items/cd76fec76d49a098abed)
- antirez/ds4(github.com/antirez/ds4): README.md、AGENT.md、QA_BEFORE_RELEASES.md、ds4.h、ds4.c、ds4_ssd.c/.h、gguf-tools/(deepseek4-quantize、quants.[ch])
- antirez/ds4 issue #437(Metal SSD ストリーミングの F_NOCACHE / MADV_DONTNEED / F_RDAHEAD 検証。試作 commits 11f20b1・cae5e95 は報告者フォーク側、上流ベースラインは cafc134)、#641(MXFP4 type 39 未対応クラッシュ、その後 main で解消)、#172(metal_graph_encode_decode_layer 等の内部シンボル)、#431・#78(DS4_METAL_PREFILL_CHUNK 既定 4096)
- ds4f-mxfp4 ブランチ(commits 725b084 / 1f862bd / 7bec128 / 4893e0c、2026-08-01 push)
- NVIDIA/cutlass issue #3096、flashinfer-ai/flashinfer issue #2723・#2577、vLLM issue #38718(SM120 の NVFP4 MoE grouped GEMM 不具合の切り分け)
- 参考フォーク: Entrpi/ds4-on-spark(D2R dequant-to-register、METAL_VS_CUDA.md)、Trosfy/spark-ds4f(mmq Q4_K プリフィル経路)、ssd-moe/deepseek-v4-flash-mlx(MLX での expert ストリーミング試作)





