- vLLM の OpenAI 互換 API は後付けの互換層ではなく本体そのもので、1 つのサーバが 1 つのモデルだけを持つこと
--gpu-memory-utilization(既定0.92)が VRAM の何を予約し、その枠が重み・KV キャッシュ・実行時オーバーヘッドにどう割れるか--max-model-lenを伸ばすと同時に裁けるリクエストが減る理由と、その本数を数える式- vast.ai の vLLM テンプレートを
VLLM_MODELとVLLM_ARGSの 2 本だけで設定して立てる手順 - 手元のコードの
base_url/api_key/modelに何を書くか。そして--api-keyが守ってくれない口があること
前章の Ollama は「1 コマンドで会話まで」の最短ルートでした。この章はその次の段、手元のエディタや自作スクリプトから叩ける API サーバーを立てます。使うのは vLLM です。設定する項目は増えますが、増えた分はほぼ全部「32 GB の VRAM をどう配分するか」に費やされます。そこさえ掴めば、あとは同じ形の繰り返しです。
お金の目安を先に置きます。この章の作業は合計 40 分ほどで、1 枚構成の RTX 5090 なら 0.27 USD ほど(2026-08-24 時点の実測中央値 0.4037 USD/時 で計算。1 USD = 150 円として約 41 円)。ここに、確保したディスクの代金(同時点の中央値 0.200 USD/GB/月)とモデルを落とすときの下り帯域(同 0.0026 USD/GB)が乗ります。
vLLM は「OpenAI 互換 API そのもの」
vLLM の実体は 2 つあります。Python ライブラリ(from vllm import LLM)と、それをラップした HTTP サーバ(vllm serve)です。この章で使うのは後者です。
ここが Ollama といちばん違うところです。Ollama は /api/chat という独自の API を本体に持ち、その横に OpenAI 互換の口を生やしていました。vLLM には独自 API 層がありません。/v1/completions や /v1/chat/completions が最初から本体です。だから「OpenAI の SDK を使っている既存のコードの、接続先だけを差し替える」という使い方が素直にできます。
| 観点 | Ollama(第 12 章) | vLLM(この章) |
|---|---|---|
| API の形 | 独自 API + OpenAI 互換の口 | OpenAI 互換 API が本体 |
| 既定のポート | 11434 | 8000 |
| 1 サーバが持つモデル数 | 複数を同時に VRAM へ載せられる | 1 つだけ(公式: 「The server currently hosts one model at a time」) |
| モデルの取得元 | Ollama のライブラリ | 既定で Hugging Face Hub |
| モデルの指定 | ollama run gemma4 | vllm serve Qwen/Qwen3-8B(位置引数 = フラグ名を付けず並び順で渡す) |
| メモリの配分 | 自動。溢れたら黙って CPU へ | 起動時に自分で決めて予約する |
最後の行がこの章の主題です。Ollama は VRAM に収まらなければ勝手に CPU へ逃がして「動くけれど遅い」状態になりました。vLLM はそうしません。起動時に必要なメモリを計算し、足りなければその場でエラーを出して止まります。うるさいようですが、こちらのほうが原因が分かります。
前提条件も先に押さえます。vLLM が動く OS は Linux、Python は 3.10 から 3.13 です。Windows のネイティブ対応はありません(WSL かコミュニティ管理の fork を使うことになります)。macOS 向けには MLX をバックエンドにした vLLM-Metal がありますが、これは PyTorch ベースではない別物です。つまりこの章の話は、借りた Linux マシンの上でしか成立しません。手元の Mac に入れて試そうとしないでください。
32 GB は 3 つに割れる
RTX 5090 の VRAM は、vast.ai の offer が申告する値で 32607 MB(2026-08-24 時点、取得した 64 件すべて同一)でした。これは 2 進で数えると 約 31.84 GiB です。カタログの「32 GB」ではなく、この 31.84 GiB を分母にして計算します。
そして vLLM は、この 31.84 GiB を丸ごと使うわけではありません。--gpu-memory-utilization という引数が、GPU メモリのうち何割をモデル実行用に使うかを決めます。公式の説明はこうです。「The fraction of GPU memory to be used for the model executor, which can range from 0 to 1. ... If unspecified, will use the default value of 0.92.」既定は 0.92、つまり 92 % です。
--gpu-memory-utilization で決まり、そこから重みが引かれ、KV キャッシュは最後に「残った分」を受け取るだけ。だから重みが太ると KV は自動的に痩せます。必要 VRAM ≒ モデルの重み + KV キャッシュ + 実行時オーバーヘッド
3 項目を順に見ます。
① モデルの重みは事前に計算できます。vast.ai 公式の目安は「モデルパラメータ数 × 2 バイト(BF16 精度の場合)」。BF16 とは数値 1 つを 2 バイトで持つ形式で、量子化していない配布状態の代表的な精度です。8B のモデルなら 8 × 2 = 16 GB、2 進に直して 14.9 GiB です。量子化済みのモデルを選べばここが縮みます。静的に量子化されたモデル(Hugging Face 上の RedHatAI のリポジトリなど)は、公式によれば追加設定なしでそのまま読み込めます。
② 実行時オーバーヘッドは、CUDA コンテキストやアクティベーション、そして CUDA グラフ(= 毎回同じ順序で GPU に投げる一連の処理をひとまとまりに記録しておき、次回からは記録を再生して組み立て直しを省く仕組み)が占めます。vLLM は既定で CUDA グラフを使って推論を最適化しており、公式はこれが「追加の GPU メモリを食う」と明記しています。実数値は公式ドキュメントに書かれていませんので、ここは「読めない項」として扱ってください。どうしても削りたいときは --enforce-eager で CUDA グラフを無効化できます(起動は最速になりますが、定常時のデコード性能は落ちます)。
③ KV キャッシュ(= 会話の途中経過を GPU 上に置いておく作業領域。次節で詳しく見ます)は、①と②を引いた残り全部です。ここに何トークン分入るかが、次節のトレードオフの正体になります。実際にいくら確保されたかは起動ログに出ます。公式によれば、その値を次の起動で --kv-cache-memory-bytes に渡し直すと、メモリの実測(プロファイリング)と CUDA グラフのメモリ推定を丸ごと省いて起動を早められます。ただしこの値は同じ GPU・同じ初期空きメモリでしか通用しません。別のホストを借りて OOM したら、まずこのフラグを外して測り直します。
自動生成される Engine Arguments のリファレンスには --kv-cache-memory-bytes(説明は「Size of KV Cache per GPU in bytes」)と載っており、Optimization ページの本文は --kv-cache-memory と書いています。どちらが通るかは、借りたマシンで vllm serve --help を実行して確かめてください。綴りをドキュメントから写して動かない場合、まず疑うのはバージョン差です。
--gpu-memory-utilization は「1 インスタンスあたり」の上限
公式は「This is a per-instance limit, and only applies to the current vLLM instance. It does not matter if you have another vLLM instance running on the same GPU.」と書いています。つまり同じ GPU で vLLM を 2 つ動かしたいなら、両方に 0.5 を渡すという設計になります。逆に言うと、vLLM は他のプロセスが GPU をどれだけ使っているかを見てくれません。同じインスタンスで Ollama を立てたまま vLLM を起動すると、合計が VRAM を超えた時点で落ちます。
コンテキストを伸ばすと、同時に裁ける本数が減る
KV キャッシュには、1 リクエストが 1 トークン進むごとに決まった量が積み上がります。1 トークンあたりの量は、vast.ai の公式ドキュメントが式で示しています。
KV キャッシュ / トークン = 2 × 層数 × KV ヘッド数 × head_dim × 1 要素のバイト数
公式が挙げている計算例は 2 つです。Qwen3-30B-A3B(GQA = Grouped Query Attention。KV ヘッドを複数の Query ヘッドで共有して数を減らす構造)は 2 × 48 × 4 × 128 × 2 バイト で 約 96 KiB/token。GLM-4.7-Flash(MHA = Multi-Head Attention。共有せずヘッドの数だけ KV を持つ構造)は 2 × 47 × 20 × 256 × 2 バイト で 約 940 KiB/token。同じ「1 トークン」でも 約 10 倍違います。公式はこの差の原因を「KV ヘッドが 4 ではなく 20 あるため」と説明しています。パラメータ数が同じなら同じ VRAM で済む、という思い込みはここで崩れます。
公式ドキュメント自身の丸めが緩く、上の 2 つの例は KB(10 進)と KiB(2 進)の扱いが揃っていません。2 × 48 × 4 × 128 × 2 は正確には 98,304 バイトで、これは 96 KiB であり 98.3 KB です。まずバイトで通しで計算し、最後に一度だけ GiB へ直すのが安全です。
ここからが本題です。KV キャッシュの総量は起動時に固定されます。一方、1 本のリクエストが最大で何トークン分を占有するかは --max-model-len(プロンプトと出力を合わせたコンテキスト長の上限)が決めます。したがって、同時に何本裁けるかは割り算で決まります。
同時に処理できる本数 ≒ KV キャッシュの容量 ÷ (max-model-len × KV / トークン)
この割り算が出すのは「全部のリクエストが上限いっぱいまで伸びても詰まらない本数」、つまり最悪値です。実際の会話は上限まで使い切らないことのほうが多く、そのぶん同時に走れる本数は増えます。逆に、走らせている途中で KV が尽きたときに起きるのが後述の preemption です。見積もりはこの最悪値でやってください。楽観側で見積もると、混んだときだけ遅くなる不安定なサーバーになります。
実際にそのエラーを踏んだ例が vast.ai の公式ドキュメントに載っています。MiniMax-M2 を 4 枚構成で立てようとしたときのものです。
ValueError: To serve at least one request with max seq len (196608), 11.62 GiB KV cache is needed
公式の解決策は「フルの 196K コンテキストは 4×80GB GPU に収まらない。--max-model-len 131072 を使う」でした。読者がこの章で最初に踏むのもこのエラーです。犯人はほぼ常に --max-model-len です。
もう 1 つ、KV が足りないときに起きるのが preemption です。こちらは起動には成功していて、走らせている最中に性能が落ちます。vLLM は KV が枯渇するとリクエストを一旦追い出し、あとで計算し直します。そのときログにこう出ます。
WARNING 05-09 00:49:33 scheduler.py:1057 Sequence group 0 is preempted by PreemptionMode.RECOMPUTE mode because there is not enough KV cache space. This can affect the end-to-end performance. Increase gpu_memory_utilization or tensor_parallel_size to provide more KV cache memory. total_cumulative_preemption_cnt=1
公式が挙げる対処は 4 つで、優先順に ① gpu_memory_utilization を上げる ② max_num_seqs または max_num_batched_tokens を下げる ③ tensor_parallel_size を上げる ④ pipeline_parallel_size を上げるです。1 枚しか借りていないなら③と④は使えないので、実質は①と②の 2 択になります。
| 引数 | 何を決めるか | 上げると | 下げると |
|---|---|---|---|
--gpu-memory-utilization | VRAM のうち vLLM が予約する割合(既定 0.92) | KV が増える | 他プロセスと同居できる |
--max-model-len | 1 本あたりのコンテキスト長の上限。未指定ならモデルの設定から自動導出 | 長い文脈を扱える/同時本数が減る | 同時本数が増える |
--max-num-seqs | 1 回のイテレーションで処理する最大シーケンス数(=同時本数の上限) | スループットが伸びる | 必要な KV が減る |
--max-num-batched-tokens | chunked prefill(= 長いプロンプトの下処理を小分けにして、生成中のリクエストと同じバッチに混ぜる方式)のトークン予算 | TTFT(初回トークンまで)が良くなる | ITL(トークン間)が良くなる |
--enforce-eager | CUDA グラフを使わない | 起動が最速・メモリを節約/定常デコード性能は落ちる | |
--served-model-name | API 上のモデル名。未指定なら渡したモデル名がそのまま名前になる | 手元のコードに書く model の値を短くできる | |
スループットを最優先するなら、公式は max_num_batched_tokens を 8192 より大きくすることを勧めています(特に大きい GPU で小さいモデルを動かす場合)。逆に応答が途切れがちな体感を嫌うなら 2048 程度に下げます。なお vLLM V1 では chunked prefill は可能な限り既定で有効なので、この値を意識するのはチューニング段階で構いません。
vast.ai のテンプレートで立てる
立て方は 2 通りあります。① vast.ai の vLLM テンプレートを使う道と、② 素のインスタンスに自分で入れる道です。この章は①を通します。vllm serve の起動をテンプレートがやってくれるので、読者が触るのは環境変数 2 本だけで済むからです。
| 環境変数 | 役割 | テンプレートの既定値 |
|---|---|---|
VLLM_MODEL | サーブするモデル(Hugging Face のリポジトリ名) | deepseek-ai/DeepSeek-R1-Distill-Llama-8B |
VLLM_ARGS | vllm serve に渡す引数をそのまま並べる | テンプレート既定 |
既定モデルが選ばれている理由も公式に書かれています。「it can run on a single GPU with only 21GB VRAM」(コンテキストウィンドウは制限付き)。31.84 GiB の RTX 5090 なら余裕があります。ディスクはモデルサイズ以上を確保します。公式はこのモデルについて 17 GB 超をスライダーで割り当てるよう書いています。本書ではここに、あとで別のモデルを試したときの置き場と torch.compile のキャッシュ分を上乗せして 40 GB を例に使います(この 40 という数字は本書の判断で、公式の推奨値ではありません)。ディスクのサイズはインスタンスを作ったあとは変更できません。
ディスクの月額 = 確保 GB × storage_cost(USD/GB/月)
計算例: 40 GB を 1 か月確保すると 40 × 0.200 = 月 8 USD(2026-08-24 時点の中央値で計算)。GPU を 1 時間動かすのが 0.40 USD ですから、使い終わったインスタンスを放置するほうが高くつきます。
RTX 5090(Blackwell 世代)は旧世代ほどの後方互換性を持ちません。vast.ai 公式は「テンプレートと Docker イメージが CUDA 12.8 および PyTorch 2.7 以上を使うように構成されていることが重要」と明記し、レンタル手順の中で「Docker イメージを変更してはいけない」と警告しています。[Automatic] タグのテンプレートは対応済みの最新イメージを自動で引きます。イメージ名の欄には触らないでください。
テンプレートの編集からインスタンス起動、そして /v1/models が答えるところまで。所要 20 分(大半はモデルのダウンロード待ち)、課金は約 0.13 USD(2026-08-24 時点の実測中央値 0.4037 USD/時 で計算)。
- cloud.vast.ai/templates で推奨の vLLM テンプレートを開き、鉛筆ボタンを押してテンプレートエディタに入る。
VLLM_MODELとVLLM_ARGSを確認する。まずは既定のまま保存して構わない。保存したものはMy Templatesセクションに入る。- play アイコンを押して検索画面に移り、ディスクスライダーを 40 GB に合わせる(スライダーはテンプレートのアイコンの下、左の列にある)。ここは作成後に変更できない。
- 1× RTX 5090 の offer を選んで
Rentを押す。この瞬間から時間課金が始まる。 - インスタンスカードのログボタンでモデルのダウンロード進捗を見る。API がすぐ応答しないのは正常。
- SSH で入って、インスタンスの中から自分自身の 8000 番に問い合わせる。
curl http://localhost:8000/v1/models
サーブ中のモデル ID を含む JSON が返れば成功です。Connection refused が返るうちは、まだ起動シーケンスの途中です。認証エラー(401 など)が返ったときは、テンプレート側で API キーが設定されています。その場合は -H "Authorization: Bearer <OPEN_BUTTON_TOKEN>" を足して叩き直してください(トークンはインスタンスカードの Open ボタンや Instance Portal のリンクから読めます)。起動が終わると、インスタンスカードの Open ボタンから開く Instance Portal に、対話的な vLLM API ドキュメントと Ray コントロールパネルへのリンクが並びます。
1 回で狙いどおりに立つことは稀です。落ちたら VLLM_ARGS を直して立て直す、を繰り返します。次はその 1 周目です。
テンプレートエディタに戻り、VLLM_ARGS を明示します。所要 10 分、課金は約 0.07 USD。モデルは 2 回目以降ディスクに残っているので、ダウンロードは繰り返されません。
- テンプレートエディタで
VLLM_ARGSに次の 3 つを並べる。 - 保存して起動し直し、ログで KV キャッシュに何 GiB 割り当てられたかを読む。
- その値と、使うモデルの KV / トークンから、同時に何本裁けるかを検算する。
--max-model-len 32768 --gpu-memory-utilization 0.90 --max-num-seqs 8
--max-model-len 32768 は「1 本あたり最大 32,768 トークン」。--gpu-memory-utilization 0.90 は既定の 0.92 よりわずかに控えめで、同じ GPU に他のプロセスが少し居ても落ちにくくなります。--max-num-seqs 8 は同時本数の上限を 8 本に切る指定です。ここで落ちるなら、下げるのはまず --max-model-len です。
テンプレートを使わない道も 1 度は見ておく価値があります。素の CUDA 環境に自分で入れる場合、公式が第一手に挙げているのは uv で、uv pip install vllm --torch-backend=auto の --torch-backend=auto が肝です。これはインストール済みの CUDA ドライバのバージョンを見て、適切な PyTorch のインデックスを自動で選びます。Blackwell 世代のドライバ要件を自分で調べなくて済みます。Docker なら公式イメージは vllm/vllm-openai:latest で、モデル名はイメージ名の後ろに位置引数として渡します。
docker run --rm --gpus all \
-v ~/.cache/huggingface:/root/.cache/huggingface \
-v vllm-cache:/root/.cache/vllm \
-p 8000:8000 \
vllm/vllm-openai:latest \
Qwen/Qwen3-8B
2 つのボリュームには別々の役割があります。~/.cache/huggingface はモデルの重みを残すため。~/.cache/vllm は torch.compile の成果物を残すためで、これを付けないとコンテナを作り直すたびに再コンパイルが走ります。なお gated なモデル(Llama 系など)は Hugging Face 上でアクセス申請して承認を得たうえで、HF_TOKEN を渡す必要があります。
手元のコードから叩く
ここまでで API は立っています。あとは手元のコードの向き先を変えるだけです。OpenAI の Python クライアントを使う場合、変えるのは base_url と api_key の 2 つだけです。
base_url だけで、api_key は経路に関係なく vLLM の中まで運ばれてから照合されます。右下がこの章でいちばん危険な箱で、同じ HTTP サーバの上にありながら --api-key の守備範囲の外にあります(詳細はこの章のつまずきポイント)。3 つの値をどう埋めるかを順に決めます。
base_url— サーバのアドレスに/v1まで含めます。トンネル経由ならhttp://localhost:8000/v1。末尾の/v1を忘れるのが最頻の失敗ですapi_key—vllm serveに--api-key(または環境変数VLLM_API_KEY)を渡していれば、その文字列。渡していないサーバにはapi_key="EMPTY"と書きます(公式例の書き方。省略はできません)model— サーブしているモデルの ID、つまり Hugging Face のリポジトリ名をそのまま。長すぎて扱いにくいなら、起動時に--served-model-nameで別名を付けられます(未指定なら渡したモデル名が API 上の名前になります)
| 経路 | base_url に書く値 | 認証 | 証明書 |
|---|---|---|---|
| ① SSH トンネル(推奨) | http://localhost:8000/v1 | vLLM の --api-key だけ | 不要(平文が SSH の中を通る) |
| ② 公開ポート | https://<IP>:<外部ポート>/v1 | vast.ai の OPEN_BUTTON_TOKEN を Bearer で | 自己署名。curl なら -k が要る |
初心者に勧めるのは経路 ① です。SSH トンネルとは、SSH の接続の中に別の通信を通して、手元の localhost:8000 を借りたマシンの 8000 番にそのまま繋げる仕組みのことです。理由は 2 つあります。1 つは第 9 章で作った ssh -L の型がそのまま使えること。もう 1 つは、vast.ai が自己署名証明書を使うため、証明書を検証する普通のクライアント(Python の SDK を含む)は経路 ② をそのままでは通れないことです。curl -k に相当する回避をライブラリごとに調べるより、トンネルを 1 本張るほうが早く済みます。
手元の Mac / PC から、いま立てた vLLM に接続します。所要 10 分、課金は約 0.07 USD。インスタンスは起動したままにしておきます。
- 手元のターミナルでトンネル付きで接続する。
<ポート>と<ホスト>はインスタンスカードの SSH ボタンから写します。そのウィンドウは繋いだまま放置します。 - 手元の別のターミナルで、まず
curlでモデル ID を確かめる。ここで返ってきたidが、次のコードのmodelに書く値です。 - OpenAI の Python クライアントを入れて(
pip install openai)、接続先だけ差し替えたコードを走らせる。
ssh -p <ポート> root@<ホスト> -L 8000:localhost:8000
curl http://localhost:8000/v1/models
from openai import OpenAI
client = OpenAI(
base_url="http://localhost:8000/v1",
api_key="EMPTY",
)
completion = client.chat.completions.create(
model="deepseek-ai/DeepSeek-R1-Distill-Llama-8B",
messages=[{"role": "user", "content": "自己紹介を 1 文で"}],
)
print(completion.choices[0].message)
応答が表示されれば、手元のコードから借りた GPU を呼べている状態です。OpenAI にしかないパラメータではなく vLLM 独自の値(top_k など)を渡したいときは、extra_body={"top_k": 50} のように extra_body に入れます。
経路 ② を使うのは「テンプレートで借りて、そのまま外から叩きたい」場合です。URL は https://INSTANCE_IP:PORT_8000 の形で、Authorization ヘッダに OPEN_BUTTON_TOKEN の値を Bearer トークンとして入れます。外部ポート番号はインスタンスカードの IP ボタンから内部 8000 に対応するものを読みます。
curl -k https://<インスタンスの IP>:<外部ポート>/v1/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <OPEN_BUTTON_TOKEN>" \
-d '{
"model": "deepseek-ai/DeepSeek-R1-Distill-Llama-8B",
"prompt": "San Francisco is a",
"max_tokens": 128,
"temperature": 0.6
}'
つまずきポイント
vast.ai 公式も「初回起動時はモデルのダウンロードのため API がすぐには使えない」と明記しています。curl が Connection refused を返す間は、まずインスタンスカードのログボタンで進捗を見てください。壊れているかどうかの判断はそのあとです。公式が挙げている別の大型モデルの例では、初回デプロイに合計 30 分ほどかかり、「45 分以上止まっていたらログのエラーを確認」という目安が示されています。待っている時間も 1 秒単位で課金されているので、ダウンロードが長引きそうなモデルは借りる前にサイズを調べておきます。
--api-key を付けたから公開しても安全だと思った
安全ではありません。公式が明示的に警告しています。--api-key(および VLLM_API_KEY)が認証するのは /v1・/v2・/inference の接頭辞を持つエンドポイントだけで、/invocations は認証されないまま同じ推論機能を晒します。原文は「Do not rely on --api-key alone to secure vLLM.」。対策として公式が挙げているのはリバースプロキシ(= 外からの通信をいったん受け止め、認証してから後ろのサーバへ中継する別のサーバ)配下への配置です。この本の読者に勧められるのはそもそも外に開けないこと、つまり SSH トンネル経由で使うことです。
vLLM は N 個の GPU に対して「API サーバ 1 + エンジンコア 1 + GPU ワーカー N」の合計 2 + N プロセスが常に CPU を取り合います。公式は必要な物理コア数の最小値を 2 + N、ハイパースレッディング有効なら最低 2 × (2 + N) vCPU としており、「エンジンコアのプロセスはビジーループで動くので CPU 飢餓に特に弱い」と書いています。1 枚借りるなら物理 3 コア / 6 vCPU が下限です。vast.ai では 8 枚挿しマシンの 1 枚だけを借りると CPU も 8 分の 1 しか付いてきます(offer の gpu_frac が 0.125 のような値になっているのがその状態)。GPU が遊んでいたら、offer の CPU 割り当てを疑ってください。
vllm serve ではなく Python ライブラリとして使うときの罠です。公式は「The llm.generate method does not automatically apply the model's chat template to the input prompt.」と明記しています。Instruct / Chat モデルに llm.generate を使うと、チャットテンプレートが適用されないまま素のテキスト補完になります。メッセージ配列を渡す llm.chat を使ってください。この章で立てた HTTP サーバの /v1/chat/completions は、テンプレートを適用する側の口なのでこの問題は起きません。
理解度チェック
Q1. --gpu-memory-utilization の既定値はいくつで、その値は何に対する割合ですか?
0.92 です。GPU メモリのうちモデル実行用(model executor)に使う割合を 0 から 1 で指定します。RTX 5090 の実効 VRAM 31.84 GiB なら約 29.3 GiB を予約する計算です。これは 1 つの vLLM インスタンスに対する上限で、同じ GPU で他のプロセスが何を使っているかは考慮されません。
Q2. KV キャッシュに 9 GiB 割り当てられ、モデルの KV が 96 KiB/token でした。--max-model-len を 32768 にすると同時に何本裁けますか?
3 本です。まずバイトで通します。9 GiB = 9,663,676,416 バイト、96 KiB = 98,304 バイトなので、合計 98,304 トークン分の KV があります。これを 1 本あたり 32,768 トークンで割ると 3 本。--max-model-len を 8192 に下げれば 12 本になり、131072 に上げると1 本も入らず起動時にエラーで止まります。
Q3. パラメータ数が同じ 2 つのモデルで、KV キャッシュの必要量が 10 倍違うことがあります。何が原因ですか?
アテンションの構造(KV ヘッドの数)です。KV / トークンの式は 2 × 層数 × KV ヘッド数 × head_dim × 要素のバイト数 なので、KV ヘッドが多いほど比例して重くなります。vast.ai 公式の 2 例では、GQA の Qwen3-30B-A3B が約 96 KiB/token(KV ヘッド 4)、MHA の GLM-4.7-Flash が約 940 KiB/token(KV ヘッド 20)でした。「パラメータ数が同じなら同じ VRAM」は成り立ちません。
Q4. --api-key を設定した vLLM を、グローバル IP で直接公開してもよいですか?
いけません。--api-key が認証するのは /v1・/v2・/inference 接頭辞のエンドポイントだけで、/invocations は同じ推論機能を認証なしで晒します。公式も「Do not rely on --api-key alone to secure vLLM.」と書き、リバースプロキシ配下に置くよう案内しています。手元から使うだけなら ssh -L 8000:localhost:8000 でトンネルを張り、外にポートを開けないのが最も安全です。
Q5. OpenAI の Python クライアントで vLLM に繋ぐとき、base_url と api_key にはそれぞれ何を書きますか?
base_url はサーバのアドレスに /v1 まで含めた値(トンネル経由なら http://localhost:8000/v1)。api_key は --api-key に渡した文字列で、渡していないサーバには "EMPTY" と書きます(省略はできません)。model にはサーブ中のモデル ID、つまり Hugging Face のリポジトリ名をそのまま入れます。実際の値は curl http://localhost:8000/v1/models で確認できます。
まとめ
- vLLM は OpenAI 互換 API がそのまま本体。既定ポートは
8000、モデルは位置引数(vllm serve Qwen/Qwen3-8B)、1 サーバ 1 モデル - VRAM の分母は 32 GB ではなく 31.84 GiB。そこに
--gpu-memory-utilization(既定0.92)を掛けた枠を vLLM が予約する - 予約枠は重み → 実行時オーバーヘッド → KV キャッシュの順に配られ、KV は残り物。重みが太れば KV は自動的に痩せる
- 同時に裁ける本数は KV 容量 ÷ (max-model-len × KV / トークン)。足りなければ起動時にエラー、ぎりぎりなら
PreemptionMode.RECOMPUTEの警告が出て遅くなる - vast.ai のテンプレートで触るのは
VLLM_MODELとVLLM_ARGSの 2 本だけ。Docker イメージは差し替えない(CUDA 12.8 / PyTorch 2.7 以上が前提) - 手元からは
ssh -L 8000:localhost:8000+base_url="http://localhost:8000/v1"が既定の作法。API キー未設定のサーバにはapi_key="EMPTY" --api-keyはサーバ全体を守らない(/invocationsが素通り)。素のまま外に開けない
次章では同じ 32 GB の制約を、llama.cpp と GGUF という別の道具で攻めます。vLLM が「bf16 の重みをそのまま載せて配分を調整する」道具だったのに対し、あちらは「重みを最初から小さくして載せる」道具です。Q4_K_M のような量子化タイプの読み方から始めます。
参考リンク
- vLLM — Quickstart(
vllm serve・既定ポート 8000・1 サーバ 1 モデル) - vLLM — Engine Arguments(
--gpu-memory-utilizationの既定 0.92・--max-model-len・--served-model-name) - vLLM — Conserving Memory(コンテキスト長とバッチを削る・CUDA グラフ・量子化)
- vLLM — Optimization(preemption の警告文・chunked prefill・必要な CPU コア数)
- vLLM — OpenAI-Compatible Server(エンドポイント一覧・
--api-keyの警告・extra_body) - vLLM — Offline Inference(
llm.generateはチャットテンプレートを適用しない) - vLLM — Server Arguments(YAML 設定と引数の優先順位)
- vLLM — Using Docker(
vllm/vllm-openai:latest・2 つのキャッシュボリューム) - vLLM — GPU Installation(uv と
--torch-backend=auto・対応 OS と Python) - vLLM — Supported Models(対応アーキテクチャ・
HF_HOME) - vast.ai — vLLM(
VLLM_MODEL/VLLM_ARGS・ディスクの目安・Bearer トークン) - vast.ai — GLM-4.7-Flash(KV キャッシュの計算式と 2 つの実例)
- vast.ai — MiniMax-M2(
--max-model-lenで直す起動エラー・デプロイ所要時間) - vast.ai — RTX 5 series(CUDA 12.8 / PyTorch 2.7 以上・イメージを変えない)
- Hugging Face — CLI(
hf auth login --token・gated リポジトリとトークン)