2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

RunPod で DeepSeek V4 Flash を立てて、Mac の OpenCode から使う

2
Last updated at Posted at 2026-08-14

はじめに

RunPod という GPU クラウドの成り立ちを知って、少し使ってみたくなりました。

2026年1月の TechCrunch の記事によると、創業者の Zhen Lu 氏と Pardeep Singh 氏は Comcast の同僚で、2021年後半の時点では自宅で Ethereum のマイニングリグを回していたそうです。二人合わせて5万ドルほどをハードウェアに突っ込んでいたところで、Ethereum の PoS 移行が見えてきて、マイニングという趣味の先行きが怪しくなります。

そこで彼らがやったのが、マイニングリグを AI サーバに転用することでした。時期としては ChatGPT はもちろん DALL-E 2 よりも前です。転用作業をしている最中に、GPU まわりのソフトウェアスタックの体験がひどい、という問題に気づいたのが RunPod の出発点だと Lu 氏は語っています。GPU の上でソフトウェアを開発する体験そのものが劣悪だった、という趣旨の発言が引用されています。

個人的に面白いと感じたのは次の点です。

  • ハードウェアの遊休資産をどう活かすかという、極めて実務的な動機から始まっています
  • 最初の売り込みは Reddit の投稿でした。Dell Technologies Capital のパートナーがその投稿を見て $20M のシードにつながったとされています
  • Hugging Face 共同創業者の Julien Chaumond 氏は、ユーザとしてサポートチャットから連絡してきてエンジェル投資家になったそうです
  • 自宅のブレーカー工事から始まって、2026年1月時点で ARR $120M に到達しています

成り立ちからの流れと成長を図にすると次のようになります(2026年8月時点)。GPU 台数やサーバー数は公表されていないため、公表値のある登録開発者数を成長の代理指標にしています。売上の公式な確定値は 2026年1月の ARR $120M 超が最新です。シードラウンドの時期は 2023年末とする記述と 2024年5月とする記述があり、図では境界付近に置いています。

image.png

開発者数は 2024年5月の10万人から2年あまりで10倍の100万人超に達しています。2026年6月24日には Summit Partners 主導で $100M を調達し、評価額 10億ドルのユニコーンになりました。興味深いのは、この調達発表で ARR の数値が更新されていないことです。公式の確定値は 2026年1月の 120Mドル 超のままで、約 240Mドル に倍増したという数字は The Information の報道による推計です。図では公式値だけを採用しています。あわせて、5億ドル超の買収提案を複数断って独立を選んだ、という報道も出ています。

GPU の需給や メモリ不足 の話は日常的に目にしますが、その末端で「余った演算資源を誰がどう捌くか」というレイヤーの会社がこういう成り立ちだったのは知りませんでした。マイニングリグの再利用から始まった会社が、いまや RTX PRO 6000 Blackwell を時間貸ししているというのは、この業界の変化の速さを象徴している気もします。

ということで、実際に契約して DeepSeek V4 Flash を動かし、手元の Mac の OpenCode から使うところまでやってみました。結果として、GPU の選択画面でそれなりに詰まったので、その部分も含めて手順として残しておきます。なお創業経緯は報道ベースで、配信によってブートストラップ時の売上規模など数字にゆれがある点はご了承ください。

構成

image.png

役割分担は次のとおりです。

場所 動かすもの 理由
Mac OpenCode、編集対象のコード 編集対象と同じ場所にいるのが自然です。Pod を落としても設定と履歴が残ります
Pod vLLM(DeepSeek V4 Flash) GPU が必要なのはここだけです
Pod Claude Code(任意) vLLM の設定変更や CUDA コードの修正・再コンパイルなど、サーバ側の整備に使います

OpenCode は Pod 側と Mac 側のどちらで動かすべきか

最初に迷ったのがここでした。比較すると次のようになります。

観点 Mac 側で動かす Pod 側で動かす
編集対象のコード ローカルにあるものを直接編集できます git で往復する必要があります
Pod terminate 時 設定・履歴が残ります 消えます(退避が必要です)
レイテンシ RTT 150ms 程度が乗ります ほぼゼロです
バックエンド切り替え baseURL の書き換えだけです Pod ごと差し替えになります
GPU を使うビルド・実行 できません その場でできます

結論としては Mac 側にしています。OpenCode と vLLM のやり取りは HTTP API 数回だけなので、RTT 150ms 程度あっても decode にかかる時間に比べれば誤差の範囲だと考えています(推測で、実測はしていません)。逆に、CUDA カーネルをその場でビルドして回すような作業なら、Pod 内で完結させたほうが速いはずです。

そこで、Pod 側には Claude Code を置いて「サーバを整備する側」と「サーバを使う側」で分担させます。

ツール 場所 触る対象
Claude Code Pod vLLM の設定、CUDA コード、サーバ環境そのもの
OpenCode Mac ローカルのプロジェクトコード(推論は Pod の DS4 Flash に投げる)

vLLM の起動オプションを変えてログを見る、CUDA コードを直して nvcc で再コンパイルする、といった反復作業は、エラーが出る場所と直す場所が同じマシンにあるのが一番速いです。Mac 側から scp と ssh で往復するより、Pod 内の Claude Code に nvcc のエラーメッセージを直接読ませたほうが効率が上がります。

通信経路の選び方

Mac から vLLM への到達手段は2つあります。

経路 到達方法 認証 判断
SSH ローカルフォワード localhost:8000 SSH 鍵 今回採用
RunPod HTTP プロキシ https://{POD_ID}-8000.proxy.runpod.net なし(URL を知っていれば誰でも) 非推奨

プロキシ URL は開発用のエンドポイントを外部に晒す形になります。どうしても使うなら vLLM 側に --api-key を設定すべきだと思います。

手順

全体の流れは次のとおりです。順番を間違えると戻り作業が発生します。

image.png

点線が今回学んだ依存関係です。Volume は DC に紐づき、公開鍵の自動注入は Pod 起動時にしか行われません。この2つがドキュメントの手順の並びからは読み取りにくいところでした。

1. 契約

  1. https://console.runpod.io/ でサインアップします。GitHub アカウントでも可能です
  2. Billing からクレジットを事前チャージします。前払いのプリペイド方式で、カード登録だけでは Pod を起動できません
  3. この時点ではまだ Volume も Pod も作りません

クレジットまわりの注意点です。

  • Pod の起動には1時間分以上の残高が必要で、残高が尽きかけると自動停止します
  • チャージしたクレジットは返金できません。数ヶ月分を超える入金は避けたほうが無難です

2. GPU の在庫を先に確認する

今回一番詰まったのがここです。

GPU 選択画面では、各カードの右下に 1 max 8 max といった表示が出ます。これは 1 Pod あたりに割り当てられる最大 GPU 数です。

表示 意味
1 max この構成では 1 枚しか取れません
2 max 以上 複数枚構成が可能です
Incompatible 選択中のテンプレートと互換性がありません
Out of capacity 在庫切れです

私の場合、RTX PRO 6000 が 1 max と表示されて、2枚構成が組めない状態になっていました。max 数はデータセンターに実在するマシンの物理構成で決まるようで、1枚構成のマシンしか置いていない DC では、どう設定しても2枚にはなりません。

このとき Network volume のセレクタが設定済みになっていたのが原因でした。Network Volume は特定の DC に紐づくため、先に作ってしまうと候補がその DC 内に限定されます。

image.png

つまり「VRAM の合計が足りるか」の前に「その DC に何枚挿しのマシンが物理的に置いてあるか」で上限が決まります。

また、テンプレートを選んだ時点で「Advanced compatibility filters」というバナーが出て、互換性フィルタが自動でかかっている場合があります。同じ画面で AMD MI300X が Incompatible と表示されていたのは、CUDA 系のテンプレートを選んでいたためだと思われます。MI300X は 192GB 積んでいて 8 max なので一見魅力的ですが、ROCm 版 vLLM に乗り換える話になるので、今回は追いませんでした。

確認の手順としては次のようにしました。

  1. 検索欄に RTX PRO 6000 と入力します
  2. タブを All ではなく Available にします。All には今すぐ確保できないものも含まれます
  3. Network volume のセレクタは未選択にします
  4. 2 max 以上の RTX PRO 6000 がある DC を探して、DC 名を控えます

もうひとつ、Secure Cloud と Community Cloud の区別があります。

提供元 価格 信頼性
Secure Cloud Tier 3/4 データセンター(提携 DC 含む) 高め 高い。Enterprise 向けには SLA の提供もあります
Community Cloud 個人・第三者ホストのピア 安い ホストに依存し、可変です

なお「Community = 中断されるスポット」ではありません。中断可能(interruptible / spot)は Cloud 種別とは独立した指定で、Community でも通常のオンデマンド Pod は立てられます。とはいえ信頼性はホスト依存なので、167GB のダウンロードや長時間の vLLM サービングには Secure が向いています。

そして今回の構成では選択の余地は実質ありません。Pod 用の Network Volume は Secure Cloud 側で提供されているため、Network Volume を使う時点で Secure Cloud が前提になります。

補足: なぜ2枚必要か

DeepSeek V4 Flash は 284B 総パラメータ / 13B アクティブの MoE です。MoE は1トークンあたりの計算量が減るだけで、メモリは全エキスパート分が必要になります。ここは誤解しやすいところだと思います。

RTX PRO 6000 Blackwell は1枚 96GB、2枚で 192GB。正式版 0731 のバジェットは次のとおりです。

192GB のバジェット サイズ 備考
重み(ネイティブ FP4/FP8、DSpark 込み) ディスク上 167GB / ロード後 約157GiB preview の約160GB から +7GB。増分は新規追加の DSpark speculative module です
ランタイムオーバーヘッド 約10〜17GB CUDA コンテキスト、NCCL、CUDA graphs など
KV キャッシュに使える残り 約10〜17GB fp8 KV で約200K トークン分に相当します

パラメータ数(284B / 13B active)とアーキテクチャは preview から変わっていません。Hugging Face 上の表示が 304B になっているのは DSpark ドラフタを含むリポジトリ合計で、ベースモデルは 284B のままです。

重要な注意点として、preview 時代に言われていた「1M コンテキストでも KV キャッシュ約10GB」は 0731 + vLLM において現時点では成立しません。vLLM の実測報告(Issue #51041)では、0731 は preview の約8倍/トークンの KV を消費し、1M 分は約51GiB に達します。ハイブリッドアテンション(CSA + HCA)というアーキテクチャ自体は変わっておらず本来の設計値は小さいのですが、vLLM 側の KV 確保が過剰になる未解決の問題があり、現時点の実用上限は 192GB 構成で約200K です(--max-model-len 204800 での実稼働報告があり、報告者は「これ以上は上げるな」と明記しています)。目安として fp8 KV で 64K ≈ 3.3GiB、200K ≈ 10.4GiB です。

つまり 0731 + 2枚構成は「重みは収まる。コンテキストは約200K まで」という整理になります。コーディングエージェント用途では 200K あれば実用域ですが、1M を前提にした使い方はこの構成ではできません。vLLM 側の問題が解消されれば上限は再評価できます。

名前が紛らわしいのですが、RTX 6000 Ada とは別物です。

世代 VRAM/枚 2枚合計 FP4 テンソルコア V4-Flash ネイティブ
RTX 6000 Ada Ada (sm_89) 48GB 96GB なし 載りません
RTX PRO 6000 Blackwell Blackwell (sm_120) 96GB 192GB あり 載ります

V4-Flash のエキスパートはネイティブ FP4 で配布されており、FP4 テンソルコアを持つ Blackwell に載せられます。ただし相性が良いのは「載る」という意味であって「FP4 の演算性能をフルに使える」わけではありません。DeepGEMM の FP4 カーネルは sm_120(ワークステーション向け Blackwell)を実行時に拒否するため、実際には Marlin バックエンドへのフォールバックになり、FP4 の FLOPS 面の優位は現状使えません。データセンター向けの sm_100 系とはここが違います。

補足: WK と無印の違い

コンソールには2種類並んでいました。

VRAM ホスト RAM 価格
RTX PRO 6000 96GB 251GB $2.09/hr
RTX PRO 6000 WK 96GB 125GB $1.89/hr

RunPod 公式の GPU タイプ一覧によれば、WK は RTX PRO 6000 Blackwell の Workstation Edition、無印は Server Edition です。Max-Q Workstation Edition は RTX PRO 6000 MaxQ として別に載っています。NVIDIA の製品ラインとしてはこの3種があり、TDP と冷却設計が異なります。

GPU 側の VRAM は同じなので、実質的な差はホスト RAM と価格です。

ここで「重み 167GB > ホスト RAM 125GB の WK でロードできるのか」という疑問が出てきます。結論だけ言うと、vLLM の既定ローダーは mmap 経由でテンソルを逐次 GPU に流すため、原理的には 125GB でもロードできます。ただし RTX PRO 6000 + NVFP4 系の組み合わせで初期化時に固定 約52GiB の CPU RAM を確保する未解決の vLLM バグ(Issue #46268)が報告されています。報告事例は別モデル(Qwen 系 NVFP4)で、DeepSeek V4 で再現するか、TP=2 でランク分積み上がるかは未確認ですが、もし同様の確保が起きれば 125GB を圧迫しかねません。仕組みの詳細と条件は記事末尾の Appendix にまとめました。

今回はこの不確実性を避けて、ホスト RAM 251GB の無印を使いました。$0.20/hr の差額は保険としては安いという判断です。WK で試す場合は Appendix の監視手順で匿名メモリの実測を見ながら判断するのがよいと思います。

補足: 2枚構成が見つからない場合

代替案 VRAM 備考
H200 x2 282GB 余裕があります。単価は高くなります
A100 80GB x4 320GB FP4 ネイティブが効かない世代です
96GB 1枚 + Q4 量子化 + CPU/RAM オフロード 96GB + ホスト RAM 284B の4bitは重みだけで約142GB あり、VRAM 単体には載りません。オフロード前提で速度は大きく落ちます
96GB 1枚 + Q2 級量子化 96GB Q2 級でも約97GB という例があり、それでもぎりぎりです。品質劣化も大きく、予備検証の域を出ません

3. Network Volume を作る

手順2で 2 max 以上を確認した DC に作ります。

  • サイズ: 400GB 程度(重み 167GB + キャッシュ + 余裕)
  • マウント先: /workspace

167GB のダウンロードを Pod を作り直すたびに繰り返すのは、時間もコストも無駄です。Network Volume は Pod を terminate しても残ります。

4. SSH 鍵を登録する

Pod を起動する前に済ませておく必要があります。

ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519 -C "your@email.com"
cat ~/.ssh/id_ed25519.pub

出力を https://console.runpod.io/user/settings の SSH Public Keys 欄に貼り付けます。

Pod 起動前に登録しておけば、起動時に Pod 側の ~/.ssh/authorized_keys へ自動で注入されます。Pod が動き出してから登録した場合は注入されないため、Web Terminal から手動で追記するか、Pod を作り直すことになります。

5. Pod をデプロイ

項目 設定
GPU RTX PRO 6000 Blackwell(無印 / Server Edition、ホスト RAM 251GB)x2
テンプレート 公式 PyTorch、CUDA 12.8 以降、Ubuntu 24.04
SSH Terminal Access チェックを入れます
Container Disk 100GB 程度
Network Volume 手順3のものを /workspace にマウント
Expose TCP 22
環境変数 HF_HOME=/workspace/hf

HTTP ポートの Expose は不要です。vLLM は --host 127.0.0.1 で立てて SSH トンネル経由でのみ使うため、RunPod の HTTP プロキシに公開する意味がありません(プロキシを使う場合はサービス側が 0.0.0.0 で listen する必要があり、認証なしで外部に露出します)。

デプロイ前に、RunPod のストレージ3種の寿命を押さえておきます。ここを誤解すると stop 一発で環境が消えます。

種類 場所 stop 時 terminate 時 課金
Container Disk /(OS 領域、/root 含む) 消えます 消えます 起動中のみ
Volume Disk /workspace(Network Volume 未使用時) 残ります 消えます 起動中 $0.10、停止中 $0.20/GB/月
Network Volume /workspace(今回の構成) 残ります 残ります $0.07/GB/月(1TB 未満)

重要なのは、pip install した vLLM や、後述の Claude Code は既定では /root 配下、つまり Container Disk に入るという点です。stop するだけで消えます。terminate だけでなく stop でも消える、というのが直感に反するところです。対策は手順7でまとめます。

テンプレート選定の要点は3つです。

要件 理由 満たさない場合の症状
CUDA 12.8 以降 Blackwell は sm_120 で、12.4 以前のイメージにカーネルがありません nvidia-smi は通るのに PyTorch が GPU を認識しません
nvcc が入っていること vLLM や flash-attn が CUDA カーネルをその場でコンパイルします ビルドで詰まります
公式 PyTorch テンプレート SSH over exposed TCP にすべて対応しています VSCode から接続できません

タグの命名規則が途中で変わっているので、少し紛らわしくなっています。

世代 タグ例 OS
runpod/pytorch:2.8.0-py3.11-cuda12.8.1-cudnn-devel-ubuntu22.04 22.04
runpod/pytorch:1.0.2-cu1281-torch280-ubuntu2404 24.04

公式ドキュメントのカスタムテンプレート作成ガイドが base image として使っているのは新しいほうで、こちらは Ubuntu 24.04 です。新しい命名にはグレードを示す devel という文字列が含まれていないため、nvcc が入っているかどうかはタグから判別できません。Pod に入ってから実地で確認する必要があります。

なお devel は OS の種類ではなく、NVIDIA が配る CUDA イメージのグレードです。

グレード 入っているもの 用途
base CUDA ランタイムの最小限 実行だけ
runtime CUDA ライブラリ一式(cuBLAS 等) ビルド済みバイナリの実行
devel runtime + nvcc、ヘッダ、静的ライブラリ CUDA コードのコンパイル

Ubuntu 24.04 では Python 3.12 がデフォルトになり、PEP 668 の externally-managed-environment が効きます。pip install が拒否される場合は、venv を切るか --break-system-packages を付けることになります。

もうひとつ、こちらのほうが重要です。RunPod の containers リポジトリに、CUDA 12.8.1 + PyTorch 2.8.0 のテンプレートが実際には PyTorch 2.4.1 をインストールしてしまうという Issue が上がっています。PyTorch 2.8.0 の cu128 安定版 wheel が公開されておらず、pip が黙って 2.4.1+cu124 にフォールバックしていたためです。

これが残っていると Blackwell では致命的で、2.4.1+cu124 には sm_120 のカーネルが入っていません。テンプレート名に CUDA 12.8 と書いてあっても、中身の PyTorch が cu124 ということが起こり得るので、手順7で必ず実地確認します。

RunPod には vLLM テンプレートや Serverless もありますが、こちらはモデルをサーブする用途に最適化されており、SSH で入って開発する使い方には向きません。開発が主目的なら PyTorch テンプレートに自分で vLLM を入れるほうが素直だと思います。

6. Mac 側の SSH 設定

Pod のページで Connect → SSH タブを開き、2つ目の「SSH over exposed TCP」のコマンドを確認します。

ssh root@123.456.789.80 -p 12345 -i ~/.ssh/id_ed25519

この 123.456.789.8012345 は説明用のダミーです。実際には Connect 画面に表示された Pod のグローバル IP と割当ポートをそのままコピーして使います。

コマンドが1つしか表示されない場合、そのテンプレートは exposed TCP に対応していないため、VSCode からは接続できません。

~/.ssh/config に次を追記します。

Host runpod-ds4
    HostName 123.456.789.80
    User root
    Port 12345
    IdentityFile ~/.ssh/id_ed25519
    LocalForward 8000 127.0.0.1:8000
    ServerAliveInterval 60
    ServerAliveCountMax 3

LocalForward が要点で、これで Mac の localhost:8000 が Pod の vLLM に繋がります。VSCode の Remote-SSH もこの設定をそのまま読みます。

補足: なぜ 127.0.0.1 で Pod に届くのか

127.0.0.1 はループバックアドレスで「そのマシン自身」を指すのに、なぜ Mac の 127.0.0.1 で Pod 上の vLLM に届くのか。ここは分かりにくいので補足します。ポイントは、Mac の 127.0.0.1 と Pod の 127.0.0.1 は別物で、この設定行には両方が別の意味で登場していることです。

LocalForward 8000 127.0.0.1:8000
             └─①   └─────②
箇所 どのマシンの話か 意味
8000 Mac ssh クライアントが Mac の 127.0.0.1:8000 で待ち受けます
127.0.0.1:8000 Pod トンネルを抜けた先で、sshd が「Pod 自身の」127.0.0.1:8000 に接続します

流れを追うと次のようになります。

  1. OpenCode が Mac の 127.0.0.1:8000 に接続します。ここで受けるのは vLLM ではなく、Mac 上の ssh クライアントです
  2. ssh がそのデータを暗号化し、既存の SSH 接続(Pod のグローバル IP : ポート番号)に流します。インターネットを渡るのはこの経路だけです
  3. Pod 側の sshd がデータを受け取り、②に従って「Pod から見た 127.0.0.1:8000」、つまり同居している vLLM に接続します

image.png

OpenCode は「ローカルの 8000 番に話しかけているつもり」で、vLLM は「ローカルから接続が来たと思っている」。間の遠距離区間を SSH が肩代わりしている構図です。

この仕組みだからこそ、vLLM を --host 127.0.0.1 で立てるのが効きます。vLLM は「Pod 自身からの接続」しか受け付けませんが、トンネル経由の接続は sshd が Pod 内部から繋ぎに行くので通ります。一方、外部から Pod のグローバル IP :8000 を直接叩いても、vLLM はそのインターフェースで待ち受けていないため届きません。SSH 鍵を持っている人だけが到達できる、という認証が自然に付いてくる形です。

確認するには、ssh runpod-ds4 を張った状態と切った状態で curl http://127.0.0.1:8000/v1/models を叩き比べると、トンネルの有無で結果が変わるのが分かります。

まず素の SSH で疎通を確認します。

ssh runpod-ds4

VSCode から接続する場合は、拡張機能「Remote - SSH」(ms-vscode-remote) をインストールしたうえで、コマンドパレットから Remote-SSH: Connect to Host を選び、runpod-ds4 を選択します。プラットフォームを聞かれたら Linux を選び、接続後に Open Folder で /workspace を開きます。

7. Pod 側のセットアップ

まず環境を実地で確認します。テンプレート名を信用せず、ここで中身を見ます。

nvidia-smi
nvcc --version
python -c "import torch; print(torch.__version__, torch.version.cuda)"
python -c "import torch; print(torch.cuda.get_arch_list())"
python -c "import torch; print(torch.cuda.device_count())"

確認すべき点は4つです。

確認項目 期待する結果 外れていた場合
nvcc --version バージョンが表示される nvcc なし。devel 相当のイメージに変更します
torch.version.cuda 12.8 以降 cu124 などにフォールバックしています(後述の再インストール)
get_arch_list() sm_120 を含む Blackwell 非対応のビルドです
device_count() 2 Pod が1 GPU で立っています。手順2に戻ります

なお、ここでの確認は system 側の Python に対するものです。実際に vLLM が使うのは後で作る venv 側の PyTorch なので、venv 作成後にもう一度同じ確認を行い、そちらを最終判定とします(後述)。

system 側で sm_120 が含まれていない場合は、PyTorch を入れ直します。

pip install --force-reinstall torch --index-url https://download.pytorch.org/whl/cu128

Ubuntu 24.04 では PEP 668 により pip が拒否されることがあります。その場合は venv を切るか --break-system-packages を付けます。

環境が確認できたらモデルを取得します。vLLM のインストールは後述の venv(/workspace 側)で行うので、ここではダウンロードだけです。

tmux new -s dl
export HF_XET_HIGH_PERFORMANCE=1
pip install -U huggingface_hub --break-system-packages
hf download deepseek-ai/DeepSeek-V4-Flash-0731

高速ダウンロードの定番だった hf_transfer は、Hub の Xet ストレージ移行に伴い廃止されています。HF_HUB_ENABLE_HF_TRANSFER=1 は現在は何もしない非推奨フラグで、代わりに HF_XET_HIGH_PERFORMANCE=1 を使います(帯域と全 CPU コアを使い切る設定なので、共有マシンでは注意。今回の Pod は占有なので問題ありません)。古いチュートリアルの手順をコピーすると効いていないフラグを設定し続けることになるので、地味に引っかかりやすいところです。

モデルは正式版の 0731 を使います。初期の preview 版(deepseek-ai/DeepSeek-V4-Flash)は 0731 に置き換えられており、agentic / coding 系の評価は大きく改善しています(Terminal Bench 2.1 で 61.8 → 82.7 など)。コーディングエージェントに使う今回の用途では正式版一択です。ただし 82.7 は DeepSeek Harness で reasoning_effort=max を使った公式評価値で、vLLM 公式 recipe によれば Think Max には 384K 以上のコンテキストが必要とされます。本記事の 204800 トークン + OpenCode という構成と同一条件ではないので、この数字がそのまま手元で出るわけではない点は留意してください。重みはディスク上 167GB(preview 比 +7GB、増分は DSpark speculative module)で、safetensors 48 シャードです。

167GB のダウンロードになるので、SSH が切れても止まらないよう tmux の中で走らせます。

Pod 側でも Claude Code を使う場合は次のようにします。

curl -fsSL https://claude.ai/install.sh | bash
export PATH="$HOME/.local/bin:$PATH"
echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc
claude --version

公式ドキュメントによれば、ネイティブインストーラは Node.js を必要とせず、Ubuntu 20.04 以降、RAM 4GB 以上が要件です。Pro / Max / Team / Enterprise / Console のいずれかのアカウントが必要で、無料プランは対象外です。

リモートなのでブラウザは自動で開きません。claude を実行して表示された URL を手元のブラウザにコピーしてログインし、返ってきたコードを端末に貼り戻します。

設定は ~/.claude~/.claude.json(= /root 配下)に保存されます。/root は Container Disk なので stop でも消えます。永続側に逃がしておきます。

mkdir -p /workspace/claude-config
mv ~/.claude /workspace/claude-config/.claude 2>/dev/null
mv ~/.claude.json /workspace/claude-config/.claude.json 2>/dev/null
ln -sfn /workspace/claude-config/.claude ~/.claude
ln -sfn /workspace/claude-config/.claude.json ~/.claude.json

~/.claude(設定ディレクトリ)と ~/.claude.json(MCP 設定などを含むファイル)の両方を退避して、両方にシンボリックリンクを張ります。片方だけ退避すると、stop のたびに MCP 設定などが消えます。

ここで手順5のストレージ寿命の話が効いてきます。pip で入れた vLLM も、上でインストールした Claude Code 本体(~/.local/bin)も、~/.claude.json も、すべて Container Disk(/root 側)にあるため、stop するだけで消えます。シンボリックリンクも、リンク先の実体は残りますがリンク自体が /root にあるので消えます。

対策は「環境ごと /workspace に置き、resume 後に1本のスクリプトで復元する」ことです。

# Python 環境を Network Volume 側に作る(初回のみ)
python -m venv /workspace/venv
source /workspace/venv/bin/activate
pip install -U "vllm>=0.26,<0.27" huggingface_hub

# resume 後に流す復元スクリプトを /workspace に置いておく
cat > /workspace/setup.sh <<'SETUP'
#!/bin/bash
ln -sfn /workspace/claude-config/.claude ~/.claude
ln -sfn /workspace/claude-config/.claude.json ~/.claude.json
export PATH="/workspace/venv/bin:$HOME/.local/bin:$PATH"
command -v claude >/dev/null || curl -fsSL https://claude.ai/install.sh | bash
SETUP
chmod +x /workspace/setup.sh

vLLM を入れた venv 側で、PyTorch の確認をもう一度行います。vLLM の wheel は自前の PyTorch を venv に引き込むため、冒頭で確認した system 側の Python と、実際に vLLM が使う venv 側は別物です。最終判定はこちらの結果で行います。

source /workspace/venv/bin/activate
python -c "import torch; print(torch.__version__, torch.version.cuda)"
python -c "import torch; print(torch.cuda.get_arch_list())"
python -c "import torch; print(torch.cuda.device_count())"

venv 側で sm_120 が出ない場合は、venv 内で cu128 系に入れ直します(vLLM が要求する torch バージョンとの互換に注意してください)。

pip install --force-reinstall torch --index-url https://download.pytorch.org/whl/cu128

復元スクリプトの実行は source /workspace/setup.sh とします。/workspace/setup.sh のように普通に実行すると子シェルで走るため、中の export PATH=... がスクリプト終了とともに消えて、今いるシェルに反映されません。source なら現在のシェルに PATH が残ります。

venv を /workspace に置いておけば vLLM は stop を越えて残ります。Claude Code のネイティブインストーラは軽いので、消えていたら入れ直す方式にしています。頻繁に stop/resume するなら、必要なものを焼き込んだカスタム Docker イメージをテンプレートにするのが最終形です。

Pod 側で Claude Code を使う際の注意点です。

  • 作業ディレクトリは最初から /workspace 配下にします。生成・修正したコードを /root に置くと stop で消えます
  • vLLM 本体を再ビルドさせている間はサーバが落ちるため、その間 Mac 側の OpenCode は使えません。「応答が返らない」と思ったら自分で vLLM を止めていた、ということは起こりがちです
  • vllm serve 自体は Claude Code に実行させず、自分で tmux 内で起動・停止します。フォアグラウンドで走り続けるコマンドはセッションを塞ぐので、Claude Code には設定ファイルの編集・ビルド・単発のテスト実行を任せる分担が安定します

8. vLLM を起動する

2x RTX PRO 6000 での実稼働報告(Hugging Face discussion #22 ほか)に基づく構成です。vLLM は 0.26 系を使います。

tmux new -s vllm
source /workspace/venv/bin/activate
VLLM_WORKER_MULTIPROC_METHOD=spawn vllm serve deepseek-ai/DeepSeek-V4-Flash-0731 \
  --served-model-name ds4-flash \
  --tensor-parallel-size 2 \
  --enable-expert-parallel \
  --disable-custom-all-reduce \
  --kv-cache-dtype fp8 \
  --moe-backend marlin \
  --max-model-len 204800 \
  --max-num-batched-tokens 2048 \
  --max-num-seqs 4 \
  --block-size 256 \
  --gpu-memory-utilization 0.90 \
  --tokenizer-mode deepseek_v4 \
  --tool-call-parser deepseek_v4 \
  --reasoning-parser deepseek_v4 \
  --enable-auto-tool-choice \
  --trust-remote-code \
  --host 127.0.0.1 --port 8000

主要オプションの意図は次のとおりです。

オプション 意図
--tensor-parallel-size + --enable-expert-parallel 2 / 有効 実稼働報告のある組み合わせです。TP で重みを分割しつつ MoE 層はエキスパート並列にします
--disable-custom-all-reduce 有効 2枚構成の実稼働報告に合わせて NCCL 側へ固定するために付けています。PCIe だから必須というわけではありません。vLLM のカスタム all-reduce は P2P が通る PCIe 接続2枚にも対応しており、自動で無効化されるのは PCIe のみの GPU が3枚以上のときです。実機ではあり/なし両方を測って速い方を採るのがよいです
--kv-cache-dtype fp8 KV が逼迫する構成なので fp8 でほぼ必須です
--moe-backend marlin sm_120 では DeepGEMM の FP4 カーネルが使えないため Marlin を明示します
--max-model-len 204800 192GB での実用上限です。報告者も「これ以上は上げるな」としています
--gpu-memory-utilization 0.90 ロード後の重み約157GiB + オーバーヘッドを見込んだ値です
--max-num-seqs / --max-num-batched-tokens 4 / 2048 個人利用のエージェント用途向けに絞った値です
--block-size 256 DeepSeek 公式の vLLM 例と公式 recipe が指定している値に合わせています
--tokenizer-mode ほか deepseek_v4 系4つ deepseek_v4 / 有効 この記事の目的に直結する最重要オプションです。下で説明します
--host 127.0.0.1 SSH トンネル以外から届かないようにします

初回はまず --max-model-len 65536 あたりで立ち上がりを確認してから 204800 に上げると、切り分けが楽です。

deepseek_v4 系の4オプション(--tokenizer-mode / --tool-call-parser / --reasoning-parser / --enable-auto-tool-choice)は、この記事の目的にとって欠かせません。OpenCode はコーディングエージェントとして「ファイルを読む」「シェルを実行する」「ファイルを書き換える」といった tool call を多用しますが、これらのパーサ指定がないと、普通のチャットは通るのにツール呼び出しが機能せず、エージェントとしてまともに働かない状態になり得ます。また 0731 には通常の Jinja チャットテンプレートがなく DeepSeek V4 専用エンコーディングを使うため、--tokenizer-mode deepseek_v4 も必須です。vLLM 公式の DeepSeek V4 recipe でもこの4つが明示されています。

curl のチャットテストが通っただけでは tool calling の動作確認にはならない点に注意してください。OpenCode を繋いだあと、実際にファイル操作を指示して tool call が往復することを確認するのが確実です。

なお構成の検証状況について正直に書いておくと、vLLM 公式 recipe が RTX PRO 6000 で明示的に検証しているのは8枚構成です。2枚 + TP=2 は Hugging Face discussion のコミュニティ実稼働報告に基づくもので、筆者自身の長期運用実績はまだありません。

DSpark(speculative decoding)についての注意です。0731 の目玉である投機的デコードは、sm_120 では素の vLLM だと warmup 段階で abort して動きません(sm_120 用 sparse MLA カーネルのバッチサイズ制約が原因)。--speculative-config を付けなければ DSpark は読み込まれないので、この構成では付けずに運用します。DSpark を使いたい場合はコミュニティのパッチ済みフォークが必要になります。

速度の目安としては、同構成の報告で単発 110〜225 tok/s、並列5リクエストで 300 tok/s 程度という数字が出ています。プロンプト内容に強く依存するので参考値です。

Mac 側で疎通を確認します。

curl http://127.0.0.1:8000/v1/models

ds4-flash が返れば、トンネルとサーバの両方が通っています。実際に1トークン生成させておくと、OpenCode を繋ぐ前に推論経路まで確認できます。

curl http://127.0.0.1:8000/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{"model": "ds4-flash", "messages": [{"role": "user", "content": "1+1は?"}], "max_tokens": 32}'

なお初回は重み 167GB のロードがあるため、vLLM の起動完了までに数分かかります。ログに Application startup complete が出るまで待ってから叩きます。

9. Mac の OpenCode 設定

~/.local/share/opencode/auth.json を作成または編集します。

{
  "vllm": { "type": "api", "key": "sk-local" }
}

vLLM 自体は API キーを要求しませんが、OpenCode 側がキーの存在を期待するため、プレースホルダを入れておきます。

~/.config/opencode/opencode.json を作成または編集します。

{
  "$schema": "https://opencode.ai/config.json",
  "provider": {
    "vllm": {
      "npm": "@ai-sdk/openai-compatible",
      "name": "DS4-Flash @ RunPod",
      "options": { "baseURL": "http://127.0.0.1:8000/v1" },
      "models": {
        "ds4-flash": { "name": "DeepSeek V4 Flash" }
      }
    }
  },
  "model": "vllm/ds4-flash",
  "small_model": "vllm/ds4-flash"
}

models のキーは、バックエンドが公開するモデル ID と完全一致させる必要があります。手順8の --served-model-name と揃えます。

プロジェクトディレクトリで opencode を起動すれば、Mac のローカルファイルを編集しながら RunPod 上の DeepSeek V4 Flash が使えます。

ローカルの llama.cpp と切り替えたい場合は、provider を並べておいて model を書き換えるだけです。

{
  "provider": {
    "vllm":  { "npm": "@ai-sdk/openai-compatible", "options": { "baseURL": "http://127.0.0.1:8000/v1" }, "models": { "ds4-flash": {} } },
    "llama": { "npm": "@ai-sdk/openai-compatible", "options": { "baseURL": "http://127.0.0.1:8080/v1" }, "models": { "local": {} } }
  },
  "model": "vllm/ds4-flash"
}

運用とコスト

場面 対応
作業終了 Pod を stop します。起動中はずっと課金されます。長期中断なら terminate(後述のコスト概算参照)
再開 resume 後にポート番号が変わることがあるため ~/.ssh/config の Port を確認し、source /workspace/setup.sh で環境を復元します(Container Disk は stop で消えているため)
SSH が切れる ServerAliveInterval を設定済みです。それでも切れるなら autossh を挟みます
vLLM を落としたくない tmux 内で起動しておけば SSH が切れても生き残ります

コスト概算

この構成の月額イメージです。GPU 単価は変動するので、あくまで目安として見てください。

項目 単価
GPU(起動中のみ) 約 4.2ドル/hr(無印 2.09ドル/hr × 2枚) 平日 4h(月 84h)で約 350ドル/月
Network Volume 0.07ドル/GB/月(1TB 未満) 400GB で約 28ドル/月(Pod 停止中も terminate 後も課金)
Container Disk 起動中のみ 停止時にクリアされるため停止中の課金はありません
Volume Disk(使う場合) 起動中 0.10ドル、停止中 0.20ドル/GB/月 停止中は倍レートになります。今回の構成では Network Volume が /workspace を置き換えるため使いません
データ転送 無料 167GB の DL も egress も課金なし

見落としやすいのは次の2点です。

  • Network Volume は Pod を stop しても terminate しても残り、課金も続きます。プロジェクトを畳むときは Volume の削除まで忘れずに
  • Network Volume を使わず volume disk に頼る構成の場合、停止中のディスクは $0.10 から $0.20/GB/月へ倍になります。「stop で節約」のつもりが、ディスク側は逆に高くなる仕組みです

今回の構成は最初からモデルと環境を Network Volume($0.07/GB/月)に置いているので、Pod は使い捨てにできます。長期に中断する場合も、stop で温存するのではなく terminate してしまうのが安くつきます。

データ転送が無料なのは地味に効きます。ハイパースケーラーだと egress だけで無視できない額になるため、167GB 級のモデルを出し入れする用途では差が出ます。

GPU 課金は起動中ずっと発生するため、OpenCode で考えている時間もそのまま $4/hr 強に乗ってくることになります。ローカルで回せる環境がある場合、どちらが得かは稼働時間次第だと思います。

トラブルシュート

症状 確認すること
SSH で入れない 公開鍵の登録タイミング。Pod 起動後の登録では注入されません
Permission denied (publickey) ssh -v で使われている鍵を確認します
Connect に SSH コマンドが1つしかない テンプレートが exposed TCP 非対応です
device_count() が 1 Pod が1 GPU で立っています。手順2に戻ります
pip install が拒否される Ubuntu 24.04 の PEP 668。venv を切るか --break-system-packages を付けます
PyTorch が GPU を認識しない torch.cuda.get_arch_list()sm_120 があるか。テンプレート名が CUDA 12.8 でも中身が cu124 のことがあります
vLLM が OOM --max-model-len を下げます(204800 が実用上限)。--gpu-memory-utilization を下げるのは逆効果です(ロード後の重み約157GiB が固定費のため、下げると載らなくなります)
warmup で abort する --speculative-config を付けていないか確認します。DSpark は sm_120 の素の vLLM では動きません
KV キャッシュが想定より小さい 0731 は vLLM 上で preview の約8倍/トークンの KV を消費します(vLLM Issue #51041、未解決)。1M コンテキストはこの構成では張れません
curl 127.0.0.1:8000 が繋がらない SSH セッションが生きているか、LocalForward が config に入っているか(手順6の補足参照)
OpenCode にモデルが出ない models キーと --served-model-name の一致
Claude Code の挙動がおかしい claude doctor で診断します
VSCode Server が入らない Container Disk の空き容量。rm -rf ~/.vscode-server して再接続します
resume したら vLLM / claude が見つからない Container Disk は stop で消えます。venv を /workspace に置いているか、source /workspace/setup.sh を流したか

おわりに

手順としては素直なのですが、GPU の在庫と Network Volume の順序という、ドキュメントに明記されていない依存関係で足を取られました。クラウド GPU は「VRAM が何 GB あるか」だけを見がちですが、実際には「そのデータセンターに何枚挿しのマシンが置いてあるか」という物理の話に引き戻されます。

マイニングリグの再利用から始まった会社だという背景を知ってから触ると、この物理寄りの感触にも納得がいく気がします。抽象化されきっていないところに、かえって信頼できる部分があるのかもしれません。

調べていく中で、当初の想定のうち2つは机上で決着しました。並列方式は「TP=2 + エキスパート並列の併用」に実稼働報告が集まっており、比較実験の前に出発点が定まりました。1M コンテキストは vLLM の KV 確保の問題により現構成では張れないことが分かり、実用上限は約200K です。残る未検証項目は、自分のワークロードでのスループット実測と、コード生成品質の評価です。このあたりは測ってから別途書こうと思います。

Appendix: ホスト RAM 125GB で 167GB の重みはロードできるのか?

本文の WK(ホスト RAM 125GB)と無印(251GB)の選択に関わる疑問、「重みのほうがホスト RAM より大きいが大丈夫なのか」を掘り下げます。今回は無印を使ったので実害はありませんでしたが、WK を選ぶ判断材料として残しておきます。

結論

条件付きで足ります。「VRAM に素直に全部載せる」運用であれば、モデルサイズ > ホスト RAM でもロードは原理的に可能です。ただし後述の条件を守ること、そして未解決バグの不確実性が残ることが前提です。

なぜ足りるのか: mmap とページキャッシュ

vLLM の既定の safetensors ローダーは、モデル全体を一度 RAM に展開しません。動きは次のとおりです。

  1. 各シャード(0731 は48個)を mmap でプロセスの仮想アドレス空間にマップします
  2. テンソルを1つ取り出し、GPU 上に確保済みのパラメータ領域へコピーします
  3. ホスト側のテンソル参照はスコープを抜けて解放され、次のテンソルへ進みます

このとき RAM に載るのはファイルにバックされたページキャッシュであり、匿名メモリ(ヒープ)ではありません。ページキャッシュはメモリが逼迫すればカーネルが破棄して回収できるため、167GB のファイルを 125GB の RAM 上でマップしても、常駐するのは空きに収まる分だけです。つまり「ページキャッシュは回収可能なので RAM 総量より大きいモデルもロードできる」という理解で正しく、これは llama.cpp が RAM を超える GGUF を日常的に mmap でロードしているのと同じ原理です。

TP=2 でも事情は変わりません。各ランクが同じファイルを mmap しますが、同一ファイルのページキャッシュはカーネルが物理メモリ上で共有するため、匿名メモリが2倍になることはありません(ただし各ランクがファイル全体を走査するため、ロード時間は TP 数に比例して延びます)。

ダウンロード時(hf download で 167GB を書く)もストリーム書き込みなので RAM は問題になりません。

危ないのはどこか: 匿名メモリを要求する経路

RunPod の Pod のような swap がない(または極小の)コンテナ環境では、匿名メモリに逃げ場がありません。OOM killer に殺されるのはページキャッシュではなく匿名メモリが上限に達したときなので、次の経路が地雷になります。

経路 何が起きるか
--safetensors-load-strategy eager safetensors を mmap ではなく CPU メモリへ先読みする方式です。既定の lazy(mmap)より RAM 消費が大幅に増えるため、125GB 構成では使いません
--cpu-offload-gb 重みをピン留めホストメモリに常駐させます。指定 GiB がまるごと退避不能な RAM になります
--kv-offloading-size KV キャッシュを CPU RAM に退避します。同上です
NFS 等のネットワークファイルシステムに重みを置く vLLM の自動 prefetch 判定など挙動が読みにくくなります。なお今回の構成の /workspace は RunPod の Network Volume で、NVMe バックのネットワークストレージです。Pod ローカルの NVMe とは I/O 特性が異なりますが、既定の mmap 遅延ロードなら大きな問題にはなりにくいはずです。ロードが極端に遅い場合は、重みを Container Disk 側へコピーしてから起動する手があります
vLLM Issue #46268 RTX PRO 6000(sm_120)+ NVFP4 系チェックポイントで、エンジン初期化時に固定 約52GiB の CPU RAM を確保する未解決バグです。0.96GB のモデルでも 52GiB 確保されるという報告で、重みロードではなくカーネル初期化まわりが原因と推測されています。報告は Qwen 系モデルでの事例で、DeepSeek V4 での再現は未確認です

最後の #46268 が今回の構成にとって最大の不確実性です。ただし断定はできません。報告事例は Qwen 系の NVFP4 マルチモーダルモデルで、DeepSeek V4 で同じ確保が起きるか、TP=2 でランクごとに積み上がるかは検証されていません。条件(NVFP4 系量子化 + sm_120)が重なるため警戒している、という位置づけです。仮に 52GiB がランクごとに積まれると 104GiB となり 125GB ではほぼ限界ですが、無印(251GB)なら踏んでも耐えられます。

WK で試す場合の監視手順

判断材料は匿名メモリの実測です。vLLM 起動中に次を見ます。

free -h
# vLLM プロセスの匿名メモリを直接確認
grep -E '^(Rss|Pss|Pss_Anon|Pss_File|Anonymous|Swap):' \
  /proc/$(pgrep -f "vllm serve" | head -1)/smaps_rollup

見るべきは Anonymous:(または Pss_Anon:)です。smaps_rollup にはまさにこの目的のフィールドが用意されています。Rss から Shared_Clean を引く方法では、ファイルバックのプライベートページ(Private_Clean など)が混ざるため匿名メモリを正しく分離できません。Anonymous: が 100GiB を大きく下回って安定していれば、WK で問題なく運用できます。100〜110GiB に迫る、あるいは起動ログの FlashInferCutlassNvFp4LinearKernel 前後で RAM が急増して OOM kill されるなら #46268 を踏んでいる可能性が高く、無印に切り替えるか vLLM のバージョン変更を検討します。

まとめ

運用 WK(125GB)で足りるか
VRAM に全部載せる(今回の構成、mmap 既定、ローカル NVMe) 原理的には足ります。ただし #46268 の不確実性が残ります
CPU オフロード / KV オフロードを使う 足りません。無印一択です
eager ロード(--safetensors-load-strategy eager 危険です。既定の lazy のままにします

なお「167GB の safetensors を 125GB RAM で vLLM にロードした」というピンポイントの公開実例は見つけられませんでした。上記は vLLM のローダー実装と Linux のメモリ管理からの演繹であり、WK で本番運用する前には上の監視手順での実測を勧めます。差額 $0.20/hr(2枚で $0.40/hr)をどう見るか次第ですが、検証の手間を考えると最初から無印にしてしまうのが楽だというのが今回の判断です。

参考

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

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?