Skip to content
Published on

vLLM 内部構造 (6) — コンテキストウィンドウ、max_model_len、max_tokens 完全整理

シェア
Authors

なぜこの五つが紛らわしいのか

vLLMを運用していてもっとも多く出てくる質問は、性能ではなく長さについてだ。コンテキストウィンドウが128kというモデルを載せたのに、なぜ8192で引っかかるのか。max_tokensを4000にしたのに、なぜ拒否されるのか。max_num_batched_tokensとはそもそも何なのか。

紛らわしい理由ははっきりしている。名前がどれも似ていて、それぞれ違う場所で決まり、超えたときに現れる症状もばらばらだからだ。あるものはリクエストが拒否され、あるものは応答が静かに切り詰められ、あるものはサーバーがそもそも起動しない。

この記事はその五つを一度に整理する。順序はこうだ。まず比較表で全体の見取り図をつかみ、それぞれを説明したあと、実際によく起きる三つのシナリオを数字で追い、最後にエラーメッセージから原因を逆にたどる方法を整理する。

内容は 2026-08-12 に公式ドキュメントとソースで確認しました。vLLM は変化が速いため、設定値と挙動は使用中のバージョンのドキュメントで再確認してください。

一目でわかる比較表

名前決まる場所制限する対象超えたときに起きること
コンテキストウィンドウモデル自体(モデル設定ファイル)モデルが一度に見られるトークン数これより大きいエンジン設定を要求すると起動段階で問題になる
max_model_lenエンジン起動引数リクエスト一つの入力と出力を合わせた長さリクエストが拒否される
max_tokens(またはmax_completion_tokens)リクエスト本文そのリクエストが新たに生成するトークン数その時点で生成が打ち切られる
max_num_batched_tokensエンジン起動引数1ステップでバッチ全体が処理するトークン数リクエストが複数ステップに分割されるか、後回しになる
max_num_seqsエンジン起動引数1ステップに同時に詰め込めるシーケンス数待機キューで待つ

表でもっとも重要な行は二番目だ。max_model_lenが制限するのは入力ではなく、入力と出力の合計だ。公式のエンジン引数ドキュメントは、この値をプロンプトと出力を合わせたモデルのコンテキスト長だと明記している。長さに関する誤解の半分は、ここから生まれる。

コンテキストウィンドウとmax_model_len

この二つは、層が違う。

コンテキストウィンドウはモデルの性質だ。学習方法から生まれる構造的な上限であり、設定ファイルを書き換えて増やせる性質のものではない。モデルカードに128kと書かれている、あの数字のことだ。

max_model_lenはエンジンの設定だ。公式ドキュメントによれば、この値を指定しなければモデル設定から自動的に導出される。つまり何も与えなければ、たいていはモデルのコンテキストウィンドウに従う。しかし明示的に指定すれば、その値が実際の上限になる。

ここで最初の誤解が解ける。モデルが128kに対応しているからといって、あなたのサーバーが128kを受け付けるわけではない。実際に受け付けるのはmax_model_lenだ。そしてこの値を低めに設定しておくのはミスではなく、たいていは意図された選択だ。理由は後で扱う。

デフォルト値をここで具体的な数字として書かない理由も明らかにしておく。公式ドキュメントは、この引数について固定されたデフォルト値を提示しておらず、モデル設定から導出されるとだけ説明している。だから、自分のデプロイで実際にいくつになっているかは、起動ログで確認する。

max_tokensと名前が変わった話

max_tokensはリクエスト本文に入れる値で、そのリクエストが新たに生成するトークン数の上限だ。入力の長さはここに含まれない。だからこの値自体は「どれだけ長く答えるか」を決めるだけで、どれだけ長い質問を受け取るかとは無関係だ。

ここで最新の情報を一つ押さえておく。mainブランチのチャット補完リクエスト定義を見ると、max_tokensフィールドには非推奨の表示が付いており、max_completion_tokensを使うようにという案内が添えられている。そして両方の値が渡された場合はmax_completion_tokensが優先される。OpenAI互換APIの流れに沿った変化だ。新しくコードを書くならmax_completion_tokensを使うほうがよく、既存のコードがmax_tokensを使っているなら、今すぐ壊れることはないが、いずれ整理すべき負債として残しておけばよい。

この値をまったく与えないとどうなるか。残っている余裕の分だけ生成が続き、モデルが自分で終えるか上限に達するまで進む。サービスの観点では、上限を明示しておくほうがほぼ常に良い。上限がないと、ユーザー一人がKVキャッシュを長く占有することになり、それが第4回で見たプリエンプションにつながる。

max_num_batched_tokensとmax_num_seqs

この二つは名前のせいで長さの上限のように見えるが、性格はまったく違う。リクエスト一つに対する制限ではなく、1ステップでエンジンが処理する作業量に対する制限だ

max_num_batched_tokensは、今回のステップでバッチ全体が処理するトークン数の上限だ。リクエスト一つがこの値を超えたからといって拒否されるわけではない。チャンクドプリフィルが有効になっていれば、単に複数のステップに分かれて処理される。

max_num_seqsは、1ステップに同時に詰め込めるシーケンス数の上限だ。これを超えると待機するだけで、失敗はしない。

mainブランチのSchedulerConfigには、それぞれのクラスのデフォルト値が2048と128として宣言されている。ただし実際に適用される値は、利用状況や環境によって調整されることがあるため、起動ログで確認する。

この二つが長さの問題と交わる地点が一つだけある。公式の最適化ドキュメントは、チャンクドプリフィルを無効にした場合、max_num_batched_tokensmax_model_lenより大きくなければならないと明記している。分割処理ができないのに、最大長のプロンプトを1ステップに収められなければ、どうにもならないからだ。

シナリオ1 — 8kモデルに6k入力とmax_tokens 4000

もっとも多いケースから見る。コンテキスト8192のモデルを載せていて、ユーザーが6000トークンの文書を貼り付けながらmax_tokensを4000にした。

max_model_len = 8192  (入力 + 出力が共有する予算)

入力  6000 ██████████████████████████████
出力  4000 ████████████████████
合計 10000 ──────────────────────────────────────────  8192 超過

結果: 生成が始まる前にリクエストが拒否される。

ここで初心者がもっとも多くしてしまう予想は、「入力6000は8192より小さいから通って、答えは2192トークンあたりで切られるだろう」というものだ。そうはならない。vLLMはリクエストを受け取った時点で入力とmax_tokensを足して検査し、超えていれば拒否する。

実際に出るメッセージはこういう形だ。公開されているvLLMのIssueに報告された原文をそのまま引用する。

This model's maximum context length is 16384 tokens. However, you requested
122946 tokens (112946 in the messages, 10000 in the completion).
Please reduce the length of the messages or completion.

メッセージを分解してみると、構造がそのまま見える。かっこの中は入力と出力に分かれていて、二つの合計が前半の上限と比較されている。この一行こそが、「入力と出力は同じ予算を分け合う」という事実のもっとも確かな証拠だ。

だから解決策も三つのうちのどれかになる。入力を減らすか、max_tokensを減らすか、max_model_lenを増やすかだ。前の二つはリクエスト側の修正で、最後はサーバーの再起動が必要になる。ちなみに、自動的に切り詰めず拒否するこの挙動については、vLLMのリポジトリに改善要望のIssueが上がっている。つまりバージョンによって変わりうる領域なので、使用中のバージョンで直接確認する。

実践的なコツを一つ。アプリケーション側でmax_tokensを定数として固定しておくと、長い入力が来るたびにこのエラーに当たる。入力トークン数を数えたうえで、残っている余裕から安全マージンを引いた値を計算して入れるほうが安定する。

シナリオ2 — 128kモデルなのになぜ8192で引っかかるのか

モデルカードには128kと書かれているのに、サーバーはずっと小さい値でリクエストを拒否する。確認する順序はこうだ。

第一に、起動コマンドで--max-model-lenを直接指定したことがあるか見る。指定していたなら、その値が答えだ。モデルの能力とは無関係に、エンジンが定めた上限が優先される。

第二に、指定していなければ、エンジンがモデル設定から導出した値を見る。公式ドキュメントが明らかにしているとおり、指定しなければモデル設定から自動的に導出されるが、この値はモデルカードの宣伝文句とは異なる場合がある。拡張手法によって長いコンテキストに対応しているモデルは、設定ファイルにはより小さいデフォルトのウィンドウが書かれていることもある。

第三に、エラーメッセージが語る数字を信じる。先ほどのメッセージで「maximum context length is」の後に出てくる値が、今このエンジンで実際に効いている上限だ。モデルカードより、この数字のほうが事実だ。

第四に、それでも128kが必要なら、KVキャッシュがそれを支えられるかをまず計算する。そして、これが三番目のシナリオにつながる。

シナリオ3 — リクエストより前に起動で引っかかる場合

--max-model-lenを大きく指定したら、サーバーがそもそも起動しない場合だ。vLLMのIssueに報告された原文の形はこうだ。

ValueError: The model's max seq len (4096) is larger than the maximum number
of tokens that can be stored in KV cache (3664). Try increasing
gpu_memory_utilization or decreasing max_model_len when initializing the engine.

このメッセージこそが、この記事の中でもっとも重要かもしれない。なぜならここに、本当の構造が現れているからだ。エンジンは起動時に重みを載せ、残ったメモリでKVキャッシュを確保する。そして、そのキャッシュに何個のトークンを収められるかを計算する。もしその数がmax_model_lenより小さければ、最大長のリクエスト一つすら最後まで処理できないという意味になるため、起動を中断する。

つまり長さの上限を最終的に決めるのは、モデルでも設定でもなく、メモリだ。

本当の限界を決めるのはKVキャッシュメモリ

先の三つのシナリオを一般化すると、こうなる。

GPU全体のメモリ
  └─ gpu_memory_utilization の比率分をvLLMが使用
       ├─ モデルの重み            (モデルサイズと量子化が決める)
       ├─ アクティベーションと各種オーバーヘッド
       └─ 残り全部 = KVキャッシュ    ← ここが長さと同時実行数を一緒に食う

KVキャッシュは二つの要求を同時に受ける。一つは長さだ。リクエスト一つが長いほど、ブロックを多く使う。もう一つは同時実行数だ。リクエストが多いほど、ブロックを多く使う。だからmax_model_lenを二倍にすると、同じメモリで支えられる同時リクエスト数はおおよそ半分になる。

この関係は実務ではこう現れる。128kを開けておくと、実際に128kを使うユーザーがいなくても、同時処理能力は減る。エンジンは最悪のケースに耐えられて初めて起動するからだ。だからほとんどの本番デプロイは、モデルが対応する最大値ではなく、実際のワークロードの上位パーセンタイルに合わせてmax_model_lenを設定する。これが、先ほどこの値を低めにしておくのはミスではないと述べた理由だ。

エラーメッセージから原因を見つける

最後に、症状から逆にたどっていく表だ。

症状もっとも有力な原因まず確認すること
リクエストが400系で拒否され、メッセージにmessagesとcompletionの数字が見える入力とmax_tokensの合計がmax_model_lenを超過リクエストのmax_tokensの計算方法
サーバー起動中にKVキャッシュへ収められるトークン数が不足しているというエラーmax_model_lenが利用可能なメモリに対して大きい--gpu-memory-utilization--max-model-len
応答が文の途中で急に切れるリクエストの生成上限に到達レスポンスの終了理由フィールド
モデルカードより小さい値で引っかかるエンジンが導出したか、指定されたmax_model_len起動ログでの実際の適用値
同時リクエストが増えると急激に遅くなる長さの上限ではなくKVキャッシュ不足とプリエンプションプリエンプションの警告ログ(第4回参照)

まとめると、手順は一つだ。まずエラーメッセージがリクエスト段階で出たのか起動段階で出たのかを見分け、リクエスト段階なら入力と生成上限の合計を見て、起動段階ならメモリとmax_model_lenの関係を見る。この二つの枝分かれさえ区別できれば、長さの問題の大部分は十分で片付く。

試してみる

参考資料