ステータス: ドラフト(2026-10-07 更新)、一部実測値を反映。 公式ドキュメントで確認した記述をベースにしており、第 7 節の KV オフロード実測(8GB 級 GPU 1枚)と「外部的根拠」節の論文との突合が追加されています。ストレージの性能(VAST、3FS、GDS)は直接測定できておらず、ベンダー資料や論文の条件を確認した程度です。記事の終わりの「確認したバージョンと日付」および「確認できなかったこと」を先にお読みください。作成ルール: 実測データを基本とし、測定できなかったものは「未測定」と表示します。
目次
- 1. なぜ読み込みがボトルネックなのか
- 2. 読み込みパスの階層
- 3. モデルファイル形式と読み込みの最適化
- 4. 高性能 AI ストレージ: VAST Data を中心に
- 5. Kubernetes におけるストレージ
- 6. KV キャッシュ オフロード
- 7. 実測とよくある落とし穴
- 8. 実際に試してみる
- 9. 面接で聞かれそうな質問
- 10. よくある誤解
- 参考文献(実際に参照したもの)
- 確認したバージョンと日付(2026-10-05)
大規模モデルサービングにおいて、GPU の計算と同じくらい頻繁に問題になるのが次の 2 つです。数百 GB のウェイトをストレージから GPU メモリまで転送する時間と、GPU メモリに収まりきらない KV(Key-Value)キャッシュをどこに配置するかです。この記事では、これら 2 つの問題をストレージ階層(オブジェクトストレージ、ファイルシステム、ローカル NVMe、ページキャッシュ、GPU メモリ)でまとめて解説します。筆者の実測値は小規模な環境でのものであるため、その限界についても併記しています。
この記事を読むことで面接で説明できるようになること
- 数百個のポッドが同時に起動する際に読み込みがボトルネックになる理由と、ローダーのアクセスパターン、ファイルシステムの配置、キャッシュ階層のうちどれから修正すべきか。
- VAST Data のような AI ストレージが解決する課題、GPUDirect Storage(GDS)の正体、そしてベンダーの数値のうち「主張」として切り分けるべき部分。
- KV キャッシュ オフロードがメリットを生む条件を数式で立て、筆者の実測値を代入して損益分岐点となるヒット率を計算する方法。
1. なぜ読み込みがボトルネックなのか
ウェイトのサイズは、パラメータ数×データ型のバイト数になります。700億(70B)パラメータを bf16(2バイト)とすると 140GB になります(計算値)。ポッドごとに同じファイルを読み込むため、100個が同時に起動する場合、読み込むべき総量は 14TB になります。ストレージの集計帯域幅が 10GB/s と仮定すると、読み込みだけで約 1,400秒(23分)かかります(どちらも説明用の例であり実測値ではありません)。
オートスケーリングにおいては、この時間がそのまま応答遅延となります。ポッドがスケジューリングされ、イメージを取得し、ウェイトをロードし、コンパイルとウォームアップを完了して初めて readiness プローブが通過します。読み込みに 10分かかる場合、負荷が上昇した後に増加した 10分間のポッドはリクエストを受け付けられないため、読み込み時間は事前スケール、余裕容量、読み込みパスの改善というすべての選択肢を左右します。
NVIDIA Dynamo のドキュメントには実測の事例があります。554GiB のモデルを 2基の 8×H100 ノード(テンソル平行 16)上で Azure Managed Lustre(OST 4つ、集計約 8GB/s)からロードしたところ、デフォルトローダーの実効スループットは約 124MB/s であり、ロードに約 76分かかりました。ローダーを変更し、ファイルをすべての OST(Object Storage Target)に分散配置(ストライピング)したところ、約 5.3分になりました。ある環境におけるベンダーの測定値です。要点は、ストレージが高い帯域幅を持っていても、読み込み側のアクセスパターンが悪いとそれを活用できないということです。
2. 読み込みパスの階層
객체 저장소 (S3 호환) ─┐
병렬/분산 파일 시스템 ─┼─▶ 노드 로컬 NVMe ─▶ 페이지 캐시 ─▶ CPU 버퍼 ─▶ GPU HBM
(Lustre, VAST, WEKA 등) ─┘ (hostPath/로컬PV) (DRAM, mmap) (핀드 메모리) (샤드별 적재)
용량 큼, 공유, 느림 ◀──────────────────────────────────────▶ 작고 빠름, 노드/GPU 전용
| 階層 | 特性 | よくある失敗 |
|---|---|---|
| オブジェクトストレージ | 容量が大きく、すべてのノードで共有されます。リクエストごとの遅延が大きいため、大きなレンジを並列で読み込む必要があります。 | 数百個のポッドからの同時 GET リクエストが 1つのエンドポイントに集中します。 |
| 並列 / 分散ファイルシステム | データを複数のストレージノードに分散させます。ストライピングに応じて、1つのファイルの読み込み上限が決まります。 | ファイルが 1つのターゲットにしか配置されず、集計帯域幅を活かせません。 |
| ノードローカル NVMe キャッシュ | ネットワークの競合がありません。ノードごとにコピーが 1つずつ必要です。 | ノードが変更されると、再度取得することになります。 |
| ページキャッシュ | 同一ノード上のポッドが同じファイルを DRAM から読み込みます(OS の一般的な動作)。 | コンテナのメモリ制限にダーティページが含まれ、OOM が発生します(第 7 節)。 |
| GPU HBM(High Bandwidth Memory) | 最終目的地です。テンソル平行の場合、各 GPU は自分のシャードのみをロードします。 | ロード中に CPU バッファが不足します。 |
3. モデルファイル形式と読み込みの最適化
safetensors 形式。 最初の 8バイト(リトルエンディアンの符号なし 64ビット)がヘッダーサイズ N であり、次の Nバイトがテンサー名、データ型、形状、データオフセットを格納した JSON ヘッダーであり、残りがテンサーの生バイトです。pickle と異なりコードを実行しないため安全であり、ヘッダーを読むだけで特定のテンサーの位置がわかるため、一部分だけを読み込む遅延ローディングが可能です。テンソル平行では、 get_slice により自分の担当分の断片だけを読み込むことができます。ただしプロジェクトの README でも明記されているように、CPU ではキャッシュされたファイルに限りコピーなしで利用できるものの、GPU に送るには必ず一度コピーが必要です。「安全である」というのはコードの実行に関してであり、テンサーの値(NaN など)が正常であるかどうパースまで検証するわけではありません。
デフォルトローダーの限界。 Dynamo のドキュメントによると、vLLM のデフォルトローダー(--load-format auto)は各 safetensors シャードを mmap し、単一のストリームとしてコピーします。リモートストレージ環境では、小さなリクエストが浅いキュー深度で発行されるため、遅延に阻まれます。同じドキュメントの Lustre の測定では、単一ターゲットの読み込みはブロックサイズ 128KiB で約 228MB/s、1MiB で約 854MB/s、16MiB で約 1.6GB/s でした。
ストリーミングローダー。 vLLM ドキュメントの Run:ai Model Streamer は、 pip install vllm[runai] の後に --load-format runai_streamer を指定して有効にします。 --model-loader-extra-config により concurrency(読み込みスレッド数)、 memory_limit(CPU バッファサイズ)、 distributed(オブジェクトストレージからの分散ストリーミング)を調整し、ローカルパス、S3、GCS、Azure Blob から読み込みます。モデルが model-rank-{rank}-part-{part}.safetensors によりあらかじめ分割されている場合、 runai_streamer_sharded により各ワーカーが自分のシャードのみを読み込みます。このプロジェクトは 2026-09-13 に Model Streamer に名称が変更され、 dsx-ai-factory/model-streamer に移行しましたが、PyPI のパッケージ名はそのままです。
そのほか、vLLM ドキュメントには GDS を使用する fastsafetensors、CoreWeave Tensorizer、再起動時に GPU に残ったウェイトを CUDA IPC で再マッピングする vllm preloadがあります。Dynamo の ModelExpress は、すでにサービング中のレプリカの GPU から RDMA(Remote Direct Memory Access)でウェイトを受け取り、README では 8×B200 ノード、vLLM 0.23.0、テンソル平行 8 の条件下で、コールドプールの 8分 53秒に対して 11秒(48倍)になると主張しています(ベンダーの数値)。
4. 高性能 AI ストレージ: VAST Data を中心に
VAST のホワイトペーパーによると、アーキテクチャは DASE(Disaggregated Shared-Everything)です。ステートレスな CNode(コンテナとして動作するプロトコルおよびロジックサーバー)がすべての SSD を NVMe-oF(NVMe over Fabrics)経由でマウントし、データは DNode/DBox の SCM(書き込みバッファ)と QLC フラッシュに配置されます。アクセスプロトコルは NFS(RDMA 上の NFS を含む)、S3、SMB、ブロックなどです。NVIDIA GDS のドキュメントでは、GDS が利用可能なネットワークファイルシステムとして DDN-EXAScaler、VAST-NFS、WekaFS を挙げており、Lustre もサポート対象としています。
GDS は、ストレージから CPU のバンスバッファを経由せずに DMA(Direct Memory Access)で直接 GPU メモリへ転送するパスです。条件が一致しない場合(nvidia-fs ドライバーの不在、ファイルシステムの非サポート、O_DIRECT が利用不可)、互換モードとして CPU メモリを経由するパスにフォールバックします。したがって、「GDS サポート」とは有効になっているという意味ではなく、有効にできるという意味であり、実際のパスは確認する必要があります。
ベンダーの主張として切り分けるべき数値
- VAST ブログ(Dynamo 連携、CoreWeave 協業): NFS over TCP(VAST NFS マルチパスドライバー)を使用し、200Gbps リンクでの KV 読み込みにおいてラインレートの 90% 以上(181Gbps)を維持し、Llama 3.1 405B(AWQ Int4)と 127,188 トークンのプロンプトにおいて、TTFT(Time To First Token)がプレフィル計算の 62秒から KV ロードの 3秒(20倍)になると主張しています。記事のトークンあたり 504KiB という計算に基づくと約 65.6GB(61.1GiB、原文の 61GB は GiB の値)であり、181Gbps(約 22.6GB/s)で約 2.9秒となるため、3秒という数値と前後のじつまが合います(筆者の検算、単位が GB と GiB で混在しないよう修正)。
- VAST・Lablup ブログ(2026-06-24): 8×H100、Mistral Medium 3.5 128B、140K トークンのコンテキスト、100Gbps ネットワーク、GDS の条件下で、平均 TTFT が 22,104ms から 10,573ms(2.09倍)になり、読み込みスループットは最高 10,693MB/s、平均読み込み遅延は 8.265ms であったと報告しています。
- 同記事では、通常の NFS 共有においてはオフロードされたブロックを呼び戻すコストが GPU の再計算コストよりも大きくなる場合があることを認めています。これは、ストレージが十分に高速である場合にのみメリットが成立するということを意味します。
- VAST の KV ストレージライブラリである VUA ストレージは 2026-07-28 にアーカイブされ、コネクタ機能は LMCache の GDS バックエンドに統合されたと README に記載されています。
代替案の比較(簡潔に)。 Lustre はオープンソースの並列ファイルシステムであり、ストライピング(stripe_count)が性能を左右します。DeepSeek の 3FS は NVMe と RDMA をベースにした分散ファイルシステム(MIT ライセンス)であり、メタデータを FoundationDB に配置します。README では、180ノードで約 6.6TiB/s の読み込み性能が主張されています(ストレージノード 180台とクライアント 500台以上、バックグラウンドの学習トラフィックが存在する読み込みストレステストであり、ストレージノードの NIC 合計の約 81% という条件が README の原文に記載されています)。WEKA および DDN については、GDS ドキュメントのリスト以外で公式資料を開いて確認できていません。
5. Kubernetes におけるストレージ
- CSI と ReadWriteMany(RWX)。 複数のポッドでモデルキャッシュを共有するには、PVC(PersistentVolumeClaim)のアクセスモードが ReadWriteMany であるストレージクラスが必要です(CSI は Container Storage Interface)。Dynamo のドキュメントでは、RWX キャッシュ PVC をあらかじめ埋めておくダウンロードジョブが推奨されており、「拡張試験は単一のポッドではなく複数のポッドで行え」と記載されています。
- プリローダーパターン。 DaemonSet や Job がモデルをノードローカルの NVMe や共有 PVC に事前にダウンロードしておき、サービングポッドは読み取り専用でマウントします。公式標準ではなく広く使われている設計手法であり、ノード固定やノードごとのコピーというコストが伴います。
- OCI イメージボリューム。
volumes[].image.referenceにより、OCI イメージやアーティファクトを読み取り専用ボリュームとしてマウントします。Kubernetes のドキュメント基準では 1.31 で初めて導入され、1.36 から stable(デフォルト有効)になっており、コンテナランタイムのサポートが必要です。イメージレイヤーキャッシュとレジストリを再利用できる利点があります。 - KServe の LLMInferenceService は、モデルの配置場所として HuggingFace、S3、PVC をサポートしています。
6. KV キャッシュオフロード
GPU HBM (G1) ◀──────────────▶ CPU 핀드 메모리 (G2)
활성 KV, 가장 빠름 DMA, 비동기 │ ▲
▼ │ 선인출(prefetch)
로컬 NVMe/디스크 (G3) ◀──▶ 원격/객체 저장소 (G4)
적중 시 위로 올림(promote), 넘치면 아래로 내림(LRU 등), GPU 와 직접 닿는 층은 CPU 층뿐
階層名は Dynamo KVBM のドキュメント(G1 GPU HBM、G2 ピン留め DRAM、G3 NVMe/SSD、G4 S3/MinIO)に従いました。vLLM の OffloadingConnector も、CPU 層のみが GPU と直接接続し、補助層は CPU を経由する同様の構造になっています。
LMCache の構造。 LMCache は KV キャッシュ管理レイヤーであり、現在のドキュメントでは 2 つのモードが区別されています。従来の方式であるプロセス内(in-process)モードは vLLM 内で LMCacheConnectorV1 として動作し、ドキュメントでは deprecated と表示されています。新規デプロイではマルチプロセス(MP)モードが推奨され、独立した lmcache server がノードごとに 1 つ(DaemonSet)起動し、複数の vLLM ポッドが LMCacheMPConnector を介して共有します。サーバーは L1(CPU DRAM または GDS NVMe スラブ)と L2(ファイルシステム、NIXL、S3、Redis など)を備え、L1 から L2 へは非同期でプッシュし、キャッシュミス時には L2 から L1 へ昇格させます。ドキュメントで挙げられている利点は、vLLM プロセスにおける GIL 競合の解消と障害隔離です。Kubernetes オペレーターは LMCacheEngine カスタムリソース(lmcache.lmcache.ai/v1alpha1) 1 つで DaemonSet、Service、ConfigMap を管理します。
インスタンス間のプレフィックス共有。 方法は 4 つあります。同一ノードのポッドが MP サーバーの L1 を共有するか、L2 を共有ファイルシステムや S3 にするか(アダプターの "shared": true)、vLLM 自体の fs ティアを共有 PVC の同じ root_dir に指定するか(ハッシュシードが固定されているため同じファイル名になり、一部のハッシュアルゴリズムは PYTHONHASHSEED を合わせる必要があります)、RDMA P2P 共有(コーディネーターと RDMA ネットワークが必要であり、GDS L1 とは併用不可)を使用します。リクエストがキャッシュを持つインスタンスにルーティングされる必要があるため、KServe ドキュメントのスケジューラーは vLLM から送信される BlockStored/BlockRemoved イベントに基づいてインデックスを作成し、プレフィックスヒットスコア(重み 2.0)と負荷スコア(1.0)でルーティングを行います。
コスト対効果。 トークンあたりの KV サイズは 2 × 층 수 × KV 헤드 수 × head_dim × 자료형 바이트です。筆者の実験モデルである Qwen2.5-1.5B(Hugging Face config: 28 層、KV ヘッド 2、head_dim 1536/12=128、fp16)はトークンあたり 28,672 バイトであり、4,864 トークンの場合は 139,460,608 バイト(0.1299 GiB)になります。LMCache のログにある 0.1299 と一致します。70B 級の Llama(80 層、KV ヘッド 8、head_dim 128、bf16)はトークンあたり 327,680 バイトであるため、3万2千トークンであれば約 10.7GB になります(計算値)。1 回のヒットによる利得は T_재계산 − (S/B + 지연) であり、ヒット確率 p の平均利得 p×이득 − (1−p)×저장비용 が正の値でなければなりません。ストレージ層の帯域幅 B が低い場合、S/B が再計算のコストを上回ってしまいます。
7. 実測値とよくある落とし穴
KV オフロードの実測。 8GB 級のノート PC 向け GPU 環境で Qwen2.5-1.5B-Instruct(fp16)を vLLM 0.29.0 と LMCache 0.5.5 で稼働させ、GPU KV キャッシュサイズはいずれの設定も 21,520 トークンでした。10 個のドキュメント(1 ドキュメントあたり約 5,011 トークン、合計約 5万トークン)を、初回読み込み、同一順序での再読み込み、逆順での読み込みという形で 1 リクエストずつ送信しました。LMCache はチャンクサイズ 256、CPU 上限 1.5GB、プロセス内モード(LMCacheConnectorV1)でした。
| 設定 | パス | TTFT p50 (ms) |
|---|---|---|
| vLLM のみ | 初回 | 575.5 |
| vLLM のみ | 同一順序で再読み込み | 576.2 |
| LMCache | 初回 | 597.8 |
| LMCache | 同一順序で再読み込み | 48.6 |
- 同一順序で再読み込み時の TTFT は 576ms から 48.6ms へ、約 11.9 倍に高速化しました。LRU(Least Recently Used)は巡回アクセスにおいて容量が少しでも不足するとヒット数が 0 になる最悪のアクセスパターンであるため、差が大きくなります。
- 初回読み込み時は保存コストによって約 4% の性能低下(575.5ms から 597.8ms)が見られました。外部の報告と比較すると、この値は小規模構成におけるものです。VAST と Lablup の共同ブログでは、140K トークンのコンテキストにおいて初回ターンの TTFT が約 22.3 秒から約 26.3 秒に増加したと(約 18% 増、原因は LMCache の実装に起因すると推測)記載されています。
- ログから、リクエストごとに 4,864 トークン(0.13GB)を CPU からロードするのに 8〜9ms かかり、同一分量の再計算は約 560ms かかりました。8〜9ms という数値はログ行のタイムスタンプの差であり、専用のタイマーで測定したものではありません。換算すると約 15GB/s となりますが、この GPU の PCIe リンク(Gen 4、幅 x8、理論値約 15.75GB/s)を個別に測定したところ、固定メモリの 1GiB 転送はホストから GPU へ 13.4GB/s、GPU からホストへ 13.2GB/s であり(3 回の試行すべて同値、理論値の約 85%)、通常の(固定化していない)メモリでは 11.3GB/s でした。ログの 8〜9ms という概算値は実測したリンクの最大値(13.4GB/s)よりも高速な値であるため、ログのタイムスタンプの差は精密な概算ではなく、実際のロード処理は最大 13.4GB/s を基準として 0.13GB あたり 10ms 以上かかるはずです(計算値)。上述の詳細な実測における長さごとの値(6,414 トークン、KV 約 0.18GB)において、ロード処理が GPU ヒットよりも約 9ms 遅いという結果は、この帯域幅で転送する計算値(約 14ms)と同程度の桁数にあります。一部が計算処理とオーバーラップしているため完全に一致するわけではありません(推測)。ロードに必要な平均帯域幅は、リクエストあたり 0.13GB を 576ms 以内に転送すればよいため約 0.23GB/s であり、損益分岐を決めるのは転送帯域幅ではなく再計算の時間とヒット率です(計算値)。
- 上記の数値から損益分岐ヒット率を計算してみます。ヒットによる利得は 576.2−48.6=527.6ms、ミスによるコストは 597.8−575.5=22.3ms であるため、p* = 22.3/(22.3+527.6) ≈ 4% となります(両方の値を本実験の p50 に代入して計算したものであり、一般的な定数ではありません。同じ数式に VAST および Lablup の資料の値を代入すると約 21% になり、構成によって大きく変動します)。
実験の限界。 同時実行数が 1 であり、モデルが 1.5B と小さく、ドキュメントは乱数の単語から生成した合成ドキュメントであり、GPU は 1 基のみです。1 回しか測定していないため標準偏差は求めておらず、イメージタグが latest であるため、再現時にバージョンが異なる可能性があります。プロセス内モードは現在の LMCache のドキュメントでは deprecated であるため、MP モードの数値は再測定していません。大規模モデルにおいて性能差がさらに拡大するという予測は、今回の測定結果によって示されているわけではありません。
詳細実測: 容量カーブ、長さ別の利得、ディスク階層、チャンクサイズ
先ほどの実験を同一環境(8GB 級 GPU 1 基、vLLM 0.29.0、LMCache 0.5.5、Qwen2.5-1.5B-Instruct)で条件を変更して再度行いました。今回は GPU KV プールを 21,520 トークンに固定し(サーバーを再起動しても同値)、ドキュメントは 10 個(1 ドキュメントあたり 5,189 トークン、合計 52,025 トークン)としました。このワーキングセットの KV サイズは、トークンあたり 28,672 バイト(28 層 × KV ヘッド 2 × ヘッド 128 × キーと値 2 × 2 バイト)として計算すると約 1.49GB です。
CPU キャッシュ容量カーブ。 同一順序で再読み込みを行った場合(LRU の最悪ケース)の TTFT 中央値です。
| LMCache CPU 上限 | 再読み込み TTFT | 外部ヒットトークン / クエリトークン |
|---|---|---|
| なし(vLLM のみ) | 600ms | 該当なし |
| 0.5GB | 620ms | 0 / 52,025 |
| 1.0GB | 620ms | 0 / 52,025 |
| 1.5GB | 39ms | 51,200 / 52,025 (98%) |
| 2.0GB | 39ms | 51,200 / 52,025 (98%) |
- ワーキングセット(約 1.49GB)よりも上限値が小さい場合、利得は 0 になります。 0.5GB および 1.0GB の設定では、ヒットしたトークンが 1 つもありませんでした。巡回アクセスにおいて LRU は、容量が少しでも不足すると最も古いものから破棄されてしまうため、次に使用するはずのアイテムがすでに消失しているためです。利得は容量に応じて段階的に増加するのではなく、崖のように急激に現れます。
- 同様の理由により、ドキュメント 1 つ分しか格納できない上限(0.5GB)では、vLLM のみを使用する場合よりもわずかに遅くなりました(初回読み込みが 599ms から 618ms へ約 3% 増加し、保存コストのみが発生しました)。
実際のアクセスのように偏りがある場合(ドキュメント 60 個、ジップf分布 s=1、リクエスト 60 個)。 一部のドキュメントが頻繁に読み込まれる場合、小さなキャッシュでも効果があります。
| 設定 | TTFT 平均 | 外部ヒットトークン比率 |
|---|---|---|
| vLLM のみ | 278ms | 該当なし |
| CPU 0.5GB | 289ms | 0% |
| CPU 1.0GB | 174ms | 44% |
| CPU 1.5GB | 121ms | 64% |
| CPU 2.0GB | 63ms | 87% |
| CPU 0.2GB + ディスク 10GB | 47ms | 98% |
中央値は大部分が 30ms 前後であるため(頻繁に読み込まれるドキュメントは GPU で直接ヒットするため)、平均値と p95 の方が容量の効果をより正確に表します。
長さ別の利得(CPU 2GB、GPU から追い出された後に同じドキュメントを再リクエスト)。
| 入力トークン | 再計算(vLLM のみ) | LMCache からロード | 倍率 |
|---|---|---|---|
| 554 | 66ms | 21ms | 3.1倍 |
| 1,077 | 119ms | 24ms | 5.0倍 |
| 2,144 | 231ms | 29ms | 8.0倍 |
| 4,273 | 481ms | 45ms | 10.7倍 |
| 6,414 | 768ms | 41ms(36~43) | 約 19 倍 |
再計算時間は長さにほぼ比例して増加しますが、ロード時間はそれよりもはるかに緩やかに増加するため、コンテキストが長いほど利得が大きくなります。同じ長さの GPU ヒット(20〜32ms)と比較して、ロード処理の方が 1〜17ms ほど余分にかかる水準です。
ディスク階層。 CPU 0.2GB とローカルディスク 10GB を使用した場合、再読み込みは 57ms(p95 は 255ms)であり、CPU のみを使用する場合(39ms)よりは遅かったものの、再計算(600ms)に比べれば圧倒的に高速でした。ファイルが書き込まれたばかりのものであるため OS のページキャッシュの恩恵を受けている可能性があり、コールド状態のディスクの値として捉えるべきではありません(未分離)。
チャンクサイズ(64、256、1,024)。 共通接頭辞2,000トークンに末尾1,000トークンを付けたドキュメントで接頭辞の再利用を測定したところ、3つのチャンクサイズすべてで外部ヒットが2,048トークン、TTFTが140~144msで同じでした。このテストではチャンクサイズの影響は見られませんでした。ただし、このテストの接頭辞はすでにGPU接頭辞キャッシュにも残っており、vLLMのみを使用した値(138ms)とも同じであったため、チャンク整列の効果を切り分けるテストとしては不十分です(未分離)。
限界。 サーバーの状態を変えながら1回ずつ測定し(チャンクと容量の曲線は繰り返さない)、同時実行数1であり、合成ドキュメントと1.5Bモデルを使用しています。同じ条件で上記第7節の最初の実験を再度行ったところ、初回読み込みが598から618ms、再読み込みが48.6から39msへとわずかに異なったため、一桁msの差には意味を持たせるべきではありません。
よくある罠:ダーティページとメモリ制限。 ネットワークファイルシステム上に大きなオブジェクトをアップロードする際、まだストレージにフラッシュされていないダーティページキャッシュがコンテナメモリとして計算され、メモリ制限を超えてOOMKilledされる場合があります。同時アップロード数を制限し、メモリ制限を増やすことで解決した事例を経験しました。ページキャッシュ層がメモリ制限と絡み合う例です。
経験のないこと。 私はVAST、WEKA、GDSを運用したことがありません。第4節は公式資料とベンダー文書を読んで整理したものです。
外部根拠:論文と公式資料で確認した内容
KVオフロードとストレージの記述を1次資料と突き合わせました。外部の数値はモデル、GPU、負荷が異なるため直接比較せず、方向性と桁数の合致のみを判定しました。
| トピック | 資料と確認した箇所 | 確認した内容と条件 | この記事との関係 |
|---|---|---|---|
| LMCacheの利得 | Liu他、「LMCache: An Efficient KV Cache Layer for Enterprise-Scale LLM Inference」、arXiv 2510.09665(2025-12改訂)、8.2・8.4・8.7・9節 | 8×H100、Llama-3.1-8Bなど8B~480Bモデル、CPU 500GB。マルチターン文書Q&AにおいてQPS 1でのTTFTが1.9~8.1倍減少、同じTTFTでのスループットが2.3~14倍(抄録の「最大15倍」は本文では14倍でのみ確認される)。リモートストレージはCPUよりも帯域幅が低いため、入力が短い場合は読み込みがプリフィルよりも遅くなる可能性がある | 限定的に支持。 方向性と桁数は同じ。指標(負荷下のTTFT対同時実行数1のヒット対ミスマッチ)と基準線が異なるため、我々の12倍と直接比較できない。ストレージコストは論文では別数値として報告されていない |
| 近い独立測定 | py-kvcache論文(arXiv 2609.11744) | 同じドキュメントを繰り返して出力1トークンでヒットTTFTのみを測定した実験において、再計算と比較して1kトークンで2.2倍、80kトークンで32.8倍(論文が使用したワークステーション級GPU 1枚、PCIe 4.0 x16、vLLM 0.16、CPU階層) | 支持。 我々の5千トークン約12倍はこの範囲内 |
| KV圧縮転送 | Liu他、CacheGen、SIGCOMM 2024。Yao他、CacheBlend、EuroSys 2025 | CacheGen:KVサイズが3.5~4.3倍減少、精度低下2%以下、低帯域幅では読み込みが再計算と同じくらい遅くなることを認めている。CacheBlend:接頭辞ではなく再利用によりTTFTが2.2~3.3倍、品質低下が0.02以内 | CacheGen 限定的に支持(損失条件)、CacheBlendは 関連なし(別問題) |
| 損益分岐点 | 数式そのままを記載した資料は見つからなかった。CacheGen付録E(月間リクエスト数の損益分岐点、原文の算術が合わないため引用しない)、VAST・Lablup共同ブログ(2026-06-24) | 損益分岐点が長さ、ストレージ帯域幅、ヒット率に依存するという記述。VAST・Lablupの数値では4%ではなく約18~21% | 限定的に支持。 4%は一般的な定数ではない(記事で既に説明済み) |
| PCIe理論帯域幅 | NVIDIA製品資料とRambus解説(PCI-SIG仕様書はアクセスがブロックされており開けない) | 4.0 x16は約29.3GiB/s、x8は約14.7GiB/s、3.0 x16は約14.7GiB/s | 条件付き。 リンク幅によって制限内であることも、超えていることもあるため、上記の記述を「理論限界と同等レベル」に修正 |
| 3FS | DeepSeek 3FS README | 180ストレージノード、500以上のクライアント、バックグラウンド学習トラフィック、約6.6TiB/s(NIC合計の約81%) | 支持。 条件を本文に補足 |
| GDS | NVIDIA GPUDirect Storage公式ドキュメント | 一部のシステムで最大帯域幅が少なくとも2倍であるとだけ記載されており、残りの数値はパートナー提供 | 支持(定性的)。 DDNのプレスリリースには条件がなく、WEKAの最近の数値は原文が開けないため使用しない |
| 重み読み込み時間 | Fu他、ServerlessLLM、OSDI 2024 | PyTorchでOPT-30Bをロードするのに34秒、LLaMA-2-70Bは84秒(図ベース)。最小のモデルは2.7B | 限定的に支持。 1.7Bの4~6秒と突合せられる数値は本文にない |
| safetensors | safetensors README、Python pickle ドキュメントとPyTorch torch.load 警告 |
ヘッダー構造と、GPUに移行するにはコピーが1回必要であるという記述が一致、pickleは任意コード実行のリスクがある | 支持。 値(NaNなど)を検証しないという制限事項を本文に追加 |
8. 実際に試してみる
すべて読み取り専用であるか、一時的なポッドで終了します。
# (1) 모델 설정만으로 토큰당 KV 크기 계산: 로컬 실행, 환경 변경 없음
python3 - <<'PY'
import json, urllib.request
u = "https://huggingface.co/Qwen/Qwen2.5-1.5B-Instruct/resolve/main/config.json"
c = json.load(urllib.request.urlopen(u))
hd = c.get("head_dim") or c["hidden_size"] // c["num_attention_heads"]
b = 2 * c["num_hidden_layers"] * c["num_key_value_heads"] * hd * 2 # fp16/bf16
print(b, "B/token;", b * 4864 / 2**30, "GiB per 4864 tokens")
PY
# (2) 블록 크기에 따른 읽기 처리량: 기존 큰 파일을 읽기만 합니다(쓰기 없음)
for bs in 128k 1m 16m; do
fio --name=r$bs --filename=/mnt/models/큰파일.safetensors --rw=read \
--bs=$bs --direct=1 --size=4g --readonly | grep -E "READ:"
done
(3)実験の再現。 GPUを使用する1つの一時ポッドでvLLMとLMCacheを実行し、終了したらポッドを削除します。ノードメモリを他のワークロードと共有している場合は、ポッドのメモリ制限を勝手に増やさないでください。
9. 面接で出そうな質問
- 数百のポッドが同時に起動する場合、モデルのロードをどのように設計しますか? 総読み込み量(ポッド数×サイズ)を事前に計算し、読み込み側はストリーミングローダーで大きなブロックを同時に読み込ませ、ストレージ側はファイル配置、ノードローカルキャッシュ、レプリカ間転送によって共有ストレージの負荷を軽減します。検証は単一ポッドではなく、実スケールでのスケールアウトテストで行います。
- テンソル並列においてシャードごとのロードが可能な理由は何ですか? safetensorsのヘッダーにテンソルごとのオフセットがあるため、必要なバイト範囲のみを読み取ることができ、事前にランクごとに分割されたファイルであれば、各ワーカーが自分のファイルのみを読み込むため、全体の読み込み量が減るためです。
- VASTを導入すると、ロードとKVオフロードが自動的に高速化されますか? いいえ。GDSは条件が合致する場合に使用され、そうでなければ互換モードにフォールバックし、ローダーのアクセスパターンが悪いと帯域幅を使い切れません。ベンダーのベンチマークは条件(GPU、モデル、ネットワーク)を確認し、自分の環境で再測定する必要があります。
- KVキャッシュオフロードが損失になるケースは? ヒット率が損益分岐点を下回っている場合、ストレージ階層の帯域幅が低くて読み込み時間が再計算よりも長い場合、または接頭辞が短く毎回異なる場合です。筆者の実測では、初回読み込みで約4%の損失、損益分岐点ヒット率は約4%でした。
- 複数のインスタンスが接頭辞を共有するには? 共有L1(ノードごとのMPサーバー)または共有L2(NFS/S3)、P2P RDMAでKVを共有し、リクエストがキャッシュを持つインスタンスにルーティングされるように接頭辞認識ルーターを配置します。ハッシュシードを一致させないと、同じ接頭辞が異なるキーになってしまいます。
10. よくある誤解
- ストレージが早ければロードも早い:アクセスパターンとファイルの配置が先です。Dynamo事例の改善も、その大部分はローダーに起因していたとドキュメントに記載されています。
- GDSは有効化するだけで高速になる:フォールバックやドライバー、ファイルシステムの条件があります。
- KVオフロードは常に有利である:ヒット率に依存するトレードオフです。
- LMCacheはvLLM内のライブラリである:現在の推奨は別サーバー(MPモード)です。
- ベンダーの倍率は自分の環境でも再現できる:モデル、コンテキスト長、ネットワーク帯域幅が異なれば結果も変わります。
参考文献(実際に開いたもの)
- https://docs.lmcache.ai/ 、 /developer_guide/architecture.html 、 /mp/index.html 、 /mp/deployment.html 、 /mp/operator.html 、 /mp/p2p.html 、 /mp/l2_storage/index.html 、 /legacy/index.html
- https://github.com/LMCache/LMCache
- https://docs.vllm.ai/en/latest/features/kv_offloading_usage/ 、 /features/preload/ 、 /features/disagg_prefill/ 、 /models/extensions/runai_model_streamer/ 、 /models/extensions/fastsafetensor/
- https://github.com/run-ai/runai-model-streamer (READMEの以前の通知)
- https://github.com/huggingface/safetensors 、 https://huggingface.co/docs/safetensors/index
- https://github.com/ai-dynamo/dynamo/blob/v1.5.0/docs/fern/pages/kubernetes/model-deployment/model-loading/fast-model-loading.mdx
- https://github.com/ai-dynamo/dynamo/tree/v1.5.0/lib/kvbm-engine 、 https://github.com/ai-dynamo/modelexpress
- https://www.vastdata.com/whitepaper 、 https://www.vastdata.com/blog/vastdata-lablup-kvcache-offload-benchmark 、 https://www.vastdata.com/blog/nvidia-dynamo-vast-scalable-optimized-inference 、 https://github.com/vast-data/VUA
- https://docs.nvidia.com/gpudirect-storage/overview-guide/index.html
- https://github.com/deepseek-ai/3FS
- https://kubernetes.io/docs/concepts/storage/volumes/ 、 https://kubernetes.io/docs/concepts/storage/persistent-volumes/
- https://kserve.github.io/website/docs/model-serving/generative-inference/llmisvc/llmisvc-overview 、 /concepts/architecture/control-plane-llmisvc
確認したバージョンと日付(2026-10-05)
LMCache v0.5.5(2026-09-12)、vLLM v0.30.0(2026-09-22、 docs.vllm.ai/en/latest は開発版ドキュメント)、Dynamo v1.5.0、Kubernetes v1.37.1、Model Streamerの以前の通知2026-09-13。
確認できなかったこと。 WEKAとDDNの公式ドキュメント、LustreのGDS詳細条件、コンテナランタイム別のイメージボリューム対応バージョン、VAST製品の現在の正式名称と価格、MPモードの性能(再測定せず)、vLLM安定版ドキュメントと開発版ドキュメントの違い。