1
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?

prefill SSDストリーミング実装に向けたソースリーディングと改造計画

1
Posted at

本記事の調査には 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 の内訳は後述の改造計画を参照してください)。

image.png

発見 1: プリフィルはすでに「チャンク内層主導」でした

前回記事でいちばん強調した「チャンク主導 vs 層主導」について、ds4 の現状は中間形でした。README によると、チャンク化された Metal プリフィルは、チャンクごとに同一の range-capable layer-major graph を再利用します。つまりチャンクの内側は層主導で、compressor / indexer の絶対位置境界を保ったまま任意のトークン範囲を 1 グラフで処理できる作りです。チャンク長は既定 4096 トークンで、--prefill-chunk あるいは環境変数 DS4_METAL_PREFILL_CHUNK で変更でき、0 を指定するとメモリが許す限りプロンプト全体を 1 バッチで処理します。

image.png

含意は 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 バイパス経路が必要です。現行のデコード向け経路と、新設するプリフィル経路の違いを図にすると次の通りです。

image.png

なお 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 は層内で離れています。

image.png

ここで一つ、前回記事の設計を精緻化できます。層単位で全 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 週

全体の流れと合否ゲートを図にすると次の通りです。

image.png

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 秒です。

image.png

つまり前回記事の損益分岐の式の通り、この構成は最初から計算律速側にあり、層主導 + 全読みが正しく動けば、ストリーミングでも常駐時とほぼ同速のプリフィルになるはずです。実測記事のチャンク主導ストリーミング(約 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 点は追加のファクトチェックで決着しました。

  1. DSpark(--mtp)との排他は一時的な制約で、現在は解消されています。実測記事の時点(ds4f-mxfp4、8 月 2 日)ではエンジン初期化で無条件排他(エラー文は --ssd-streaming is not compatible with --mtp yet)でしたが、現在の main の README は、本体モデルは常駐でも --ssd-streaming でも DSpark 支援モデルを併用できる(支援モデル分の重みとランタイム状態はメモリ所要に加算される)と明記しています。エラー文の yet が示唆していた通りの経過です。
  2. 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)での実挙動は前述の通り未検証です。
  3. 残る要確認は内部シンボルです。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 ストリーミング試作)
1
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
1
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?