バッチ処理とマルチユーザー
一度に1リクエストは、すべてのデスクトップセッションの始まり方であり、GPUを無駄にする。サービングのゲームのすべてはバッチ処理。重みを通る1パスで多数のリクエストを同時にさばくこと。このページは、なぜ動くか、コティニュアスバッチングとページドアテンションの正体、本物のサービングエンジンが必要なとき、そしてサイズの決め方を扱う。
直列サービングがGPUを無駄にする理由
速度の式を思い出すこと。デコードされた各トークンは、本質的にアクティブな重みをすべて一度読む(速度)。1ストリームをサーブするとき、GPUは1ユーザーの1トークンのためにその読み取り全体を行う。8ストリームを同じフォワードパスにバッチ化すれば、重みは1回読まれ8シーケンスに使われる——余分な算術はほぼ無料。デコードは計算律速ではなく帯域律速だから。ストリームごとの速度はほとんど落ちず、合計スループットは帯域幅かKVメモリが飽和するまでほぼ線形で登る。この単一の事実がローカルサービングの経済のすべて。
スタティック対コティニュアスバッチング
素朴な版——スタティックバッチング——は、同時に到着したリクエストをグループ化し、全員が終わるまでバッチを走らせる。動くが、ひどく。
| スタティックバッチング | コティニュアスバッチング | |
|---|---|---|
| バッチ形成 | グループが揃ったときに1回 | デコードステップごとに再評価 |
| 途中合流 | いいえ——次のバッチを待つ | はい、リクエスト到着に応じてスロットが埋まる |
| 長いものの中の短いリクエスト | 最長が終わるまでブロック | 終了して直ちにスロットを解放 |
| テールレイテンシ | 悪い(先頭行ブロッキング) | 良い |
コティニュアス(イテレーション単位)バッチング——vLLMとSGLangのデフォルト——こそが、素朴なサーバが崩壊する中であのエンジンが本番トラフィックに耐える理由。
ページドアテンション:KV断片化の修正
アクティブな各リクエストはKVキャッシュメモリを必要とする(KVキャッシュを参照)。旧来のアプローチは各リクエストの最大コンテキストを前もって予約した——2kトークンを使うチャットが、ロックされて他の誰も使えない32kブロックの予約を保持することもあった。KVプールの大半は遊んでいた。ページドアテンションは、OSがメモリをページングするのと同じように、KVをオンデマンドで確保される固定サイズブロックに格納する。無駄は過半(vLLMの著者は論文で60〜80%と報告)から数%に下がり、数倍の同時シーケンスが収まり、スループットが追従する。同じアイデアが、今やサービングエンジン全体の標準。
あなたへのコスト:ユーザーに見えるものはない。アロケータの変更であり、同じGPUでより高い同時容量が見えるだけ。
本物のサービングエンジンが必要なとき
| ワークロード | エンジン |
|---|---|
| あなたと世帯、数個の順次チャット | Ollama / LM Studio——すでに十分 |
| 数人の開発者がのんびり同時利用 | 並列スロット付きllama-server、またはOllamaの並列環境変数 |
| プロダクトトラフィック、チームチャット、本物のユーザー | vLLMまたはSGLang |
| 長いシステムプロンプトを共有するエージェント艦隊 | SGLang(プレフィックスキャッシュ)またはvLLM |
このフォーラムのゲームコーナーは最終行の正統な例。チェスとチャンギの対局は複数のエージェントを走らせ、各エージェントが1秒に1回程度の指し手を要求する——小さいプロンプト、多数のストリーム、共有システムプロンプト。これはまさに、バッチ処理付きサービングエンジンを欲しがるワークロードであって、ループ内のollama runではない(エージェントを参照)。
スループット対レイテンシ
すべてのノブが片方をもう片方と交換する。大きいバッチは合計tok/sを上げながらストリームごとの速度をわずかに下げ、キューイングは負荷下でTTFTを上げる。1人用のコーディングアシスタントは同時実行を1〜2に制限して人間のストリームが並ばないようにしたい。30人用のチャットサービスはバッチを詰めたい。チューニング前にどちらか決めること(TTFTとデコードを別測定として扱う話はサービング)。
タダ飯に最も近いのはプレフィックスキャッシュ(SGLang)。繰り返される長いプロンプトがほぼゼロのプリフィルになり、これは大半のエージェントワークロードだ。
実用ノブ
# llama.cpp: 並列デコードスロット(コンテキストはスロット間で分割)
llama-server -m model.gguf -c 32768 -np 4
# Ollama: モデルあたりの並列リクエスト数(設定後にサービス再起動)
export OLLAMA_NUM_PARALLEL=4
# vLLM: 重要な2つ
vllm serve model --max-num-seqs 32 --gpu-memory-utilization 0.90
キューイングはこれらすべての上流で起きる。全スロットが忙しいとき、新しいリクエストは待つ。雰囲気ではなくキュー深度とTTFTを見ること。
上流にも上限を設定すること。接続上限付きリバースプロキシは速く正直に失敗し、深いキューはゆっくり神秘的に失敗する。
容量計画、おおよそ
重みを超えてリクエストが要求するのはKVキャッシュ。コンテキスト全体にわたり、トークンごとに。
KVバイト/トークン ≈ 2 × レイヤー数 × kv_heads × head_dim × bytes_per_value
実例、典型的な8B密モデル(32レイヤー、8 KVヘッド、ヘッド次元128、fp16値)。2 × 32 × 8 × 128 × 2 B ≈ トークンあたり131 KB → 2kトークンの会話 ≈ KV 256 MiB → そのようなチャット10件 ≈ 重み約5 GBに加えて2.5 GB。
(2はKとV。KVキャッシュを参照)。ゆえに長コンテキストのエージェントは、チャットよりリクエストあたりはるかに高くつく。大まかな快適表、強めにヘッジ済み——チャット長のコンテキスト、オーダーのみ。
| GPU | モデル | 同時軽チャットの目安 |
|---|---|---|
| 8 GB | 7–8B Q4 | 1〜3 |
| 24 GB | 8B Q4 | 約10〜30 |
| 24 GB | 32B Q4 | 約3〜8 |
| 48 GB | 70B Q4 | 約5〜15 |
実際のプロンプトで測ること。コンテキスト長は、他のどの変数よりもこれらの数値を動かす。
飽和のシグナル
- 日が進むにつれTTFTがミリ秒から秒へ登る。
- キュー深度が継続的にゼロ超。リクエストが最初のトークンの前に待つ。
- バッチが詰まるにつれストリームごとのtok/sが垂れる。
- vLLMログにプリエンプションやKV再計算——モデルの遅さではなくメモリ圧力。
どれか1つでも:コンテキストに上限を、KVキャッシュを量子化、GPU追加——この安さの順で——ユーザーが大声で不満を言う前に。
1日のTTFTとキュー深度をグラフ化——安定した上昇は容量不足を、のこぎり歯はキューで吸収できるバーストを意味する。
レンタル対購入、スケッチ
執筆時点で、レンタルの24GB級GPUは概ね時給0.50〜2ドル、中古24GBカードは数百ドル——損益分岐は実際の生成時間でおよそ300〜1,000時間のどこか。ローカルはプライバシー、レイテンシ、レート制限なしで勝つ(なぜローカルかの枠組み)。レンタルはバースト的な需要と、コンシューマカードに載らない80GB級モデルで勝つ(何がどこに収まるかはVRAM計算)。大半の人は自分の時間を過大評価する——決める前に実際の1週間を計測すること。
関連: 速度 · KVキャッシュ · エンドポイントのサービング · VRAM計算