vllm-project

Stars and mentions

Last 30 days, Oct 4 – Nov 2

GitHub stars2,799
mentions198
04896144192Oct 4Oct 18Nov 2
UTC days · repositories vllm-project owns11 repositories

Package downloads

Cumulative recorded downloads · since 2026-04-08

crates.io—
PyPI43.2M
010.8M21.6M32.4M43.2MOct 4Oct 18Nov 2
Nov 1crates.io: 0/1 packages observed · PyPI: 1/14 packages observed

Across 15 known packages in repositories vllm-project owns. Cumulative totals include recorded downloads before the selected range. Missing observations leave gaps; totals may be incomplete. PyPI excludes known mirrors.

Top repositories

vllm-project’s repositories ranked by new stars, as on trending.

Loading

Contributes to

Repositories outside vllm-project’s account where vllm-project opened or reviewed pull requests in the past 365 days.

Who noticed vllm-project

Everyone who starred or forked vllm-project’s repositories since the archive began in 2011.

Notable accounts

Ordered by the stars their own repositories have received.

Biggest fans

Accounts that starred the most of these repositories.

Breakout weeks

Each repository’s best week of new stars, with the release published around it.

Mentions

200posts
Nov 34 posts
  1. How to Stop a Long LLM Answer From Freezing When the Server Runs Out of Memory > Engineer's view. This is memory overcommit. Back every page up front and half your RAM sits idle. Overcommit, and an out-of-memory killer stops a process at peak. An LLM server faces the same choice, because each request's memory grows with its answer. You pay in idle GPU memory or in frozen streams. > > TL;DR. An LLM server must decide how much memory to hold for an answer before the model has written it. Hold too much and you waste capacity. Hold too little and a running answer gets paused to make room. Explain Like I'm 5 Picture a restaurant where nobody knows how long each family will stay. You could save a giant table for every family, just in case. Most families leave early, so half your seats sit empty while people wait outside. Or you could seat everyone at small tables and hope. When the room gets full, you ask the newest family to stand up and wait, even in the middle of dinner. They keep their food. They just can't eat for a while. An AI server runs that restaurant, and every answer is a family. The Problem You have shipped this bug already, in a system with no model in it. You ran a service on Kubernetes. You set its memory request to the worst case you had ever seen. Nothing crashed. But the cluster reported itself full while the real usage sat near 40%, and new pods stayed pending. So you lowered the request to the average. The cluster filled up nicely. Then a busy afternoon came, several pods grew at once, and the kernel killed one in the middle of its work. You were guessing a number before you knew the answer. An LLM server guesses that number for every request. The memory here is the KV cache: the model's saved work for every token so far. Without it, the model would reread the whole conversation for each new token. It grows by one token per token generated. The model only decides the length by stopping, so at the moment a request starts, nobody knows its final size. The caller sends max_tokens, but that is a ceiling, not a forecast. Under load, that guess shows up in two ways. Your GPU memory looks full while throughput sits far below what you paid for. Or a streaming answer stops mid-sentence for many seconds, then carries on as if nothing happened. Neither one logs an error. The fix: find out which rule your server uses, measure how long your answers really are, then set max_tokens and your concurrency limit from those lengths. New to the KV cache? Start at How to Work Out How Much GPU Memory One Request Really Needs. The Fix: Match the Limits to Your Server's Admission Rule Admission is the moment a server lets a waiting request start. Three popular servers give three different answers to one question: what must be free first? !One unknown length, three admission rules, two possible bills What does each server hold back? TGI counts the prompt plus the full max_new_tokens against its budget. It cannot run out mid-answer. SGLang counts max_new_tokens times a ratio, clipped at 4,096 tokens. The ratio starts at 0.7 and falls to about 0.1 while things go well. When memory runs out, it resets from what requests really used. vLLM counts only what exists now. Its --watermark setting can keep some memory free, but the default is zero. What happens when memory runs out? The server evicts a running request. It frees that request's cache and sends it to the front of the waiting line. vLLM picks the newest request. SGLang picks the one with the least output. The answer isn't lost. Tokens already generated are kept. The request must rebuild its cache by rereading the prompt and its own output, and only then does the stream move again. In vLLM, nothing new starts during a step that evicted. Is the redone work the expensive part? I expected it to be. It wasn't. My first simulation was built to show a server thrashing, and throughput never collapsed. Rereading is much faster than generating. !Long answers: tokens per second by admission rule (simulated, median of 9 runs) Eviction redid 80% of the prefill work and still delivered about five times the throughput. The price landed elsewhere: 26% of finished requests were evicted, and their median freeze was 58 seconds. Throughput hid that completely. What This Means for You When this matters. You run your own model server, or you are choosing one. It matters most when answers vary widely in length, as they do with reasoning models and agents. If you only call a hosted API, this is the provider's problem. You can still detect it from your side, and your max_tokens may still matter. How it affects you. The same GPU can give very different results depending on which rule it runs. On a reserving server, one team's careless limit costs everyone. In my chat simulation, moving max_tokens from 8,192 to 4,096 raised throughput 29%, from 351 to 454 tokens per second. Answers were short, so about 0.5% would have been cut. On an evicting server, average throughput looks healthy while a few users stare at a stopped stream. Your dashboard's mean hides them. What to do about it. Start with no setup. Log the largest gap between chunks on your streaming calls. A gap of many seconds in the middle of an answer is your first sign. Next, log how many tokens your answers really use. Compare that with the max_tokens you send. Most teams send a default nobody chose. Then cap concurrency from those lengths. On an evicting server, that cap removed more freezing than any other setting I tried. Finally, check the eviction counter and alert on it. Implementing It The change. Four jobs, and one person may do all of them. Job 1, whoever owns the client. Measure the freeze from the outside. This works against any server. In your streaming call: gap, last = 0.0, None for chunk in client.chat.completions.create(model=MODEL, messages=msgs, stream=True): now = time.monotonic() gap, last = max(gap, now - last) if last else 0.0, now metrics.observe("stream_max_gap_seconds", gap) Start timing after the first chunk, as this does, so that queue time isn't counted as a freeze. Job 2, whoever sets max_tokens. Choose it from logged lengths, not from the model's limit. Take a value a little above your longest normal answer, then count what it would cut: limit = sorted(lengths)[int(0.999 * len(lengths))] cut = sum(n > limit for n in lengths) / len(lengths) A wrong limit shows up as finish_reason: "length". Alert on that rate. Job 3, whoever runs the server. Work out two limits from the same logs. safe_concurrency() in code_example.py does this: never = pool / (mean_prompt + p95_answer) # almost no evictions, low throughput average = pool / (mean_prompt + mean_answer) # the memory is full on an average mix pool is your KV cache size in tokens. Those limits came out at 4 and 15 for the long-answer case. The upper limit is the useful one here. It gave up 17% of throughput, cut evicted requests from 26% to 7%, and cut the median freeze from 58 to 17 seconds. !Median freeze of an evicted request, by concurrency cap (seconds) Job 4, the setting for your server. On vLLM, lower admission without shrinking the batch buffers: vllm serve "$MODEL" --max-num-active-seqs 15 --max-num-seqs also works, and the docs recommend it. Be careful with --watermark: at 0.2 it cut redone work from 80% to 53%, but the median freeze rose to 247 seconds, because an evicted request then waits for extra room. On SGLang, watch for the pool-full warning. Its tuning guide says to raise --schedule-conservativeness to about 1.3 if the warning fires often. Lower it to about 0.3 if token usage stays low while requests queue. On TGI, set --max-total-tokens as low as your longest real request. TGI is in maintenance mode, so weigh that for a new deployment. How you know it worked. On vLLM, this should sit near zero: rate(vllm:num_preemptions_total[5m]) vllm:kv_cache_usage_perc should stay high, or your cap is too low. On any server, p99 of stream_max_gap_seconds should fall while total tokens per second holds. If usage drops and waiting requests climb, you set the cap too low. Raise it until evictions just reappear. When Tuning Admission Is the Wrong Tool When you never fill the pool. If vllm:kv_cache_usage_perc stays well under 1 at peak, no rule is making a difference. Fix something else. When batch work doesn't care. A nightly job nobody watches can freeze for a minute at no cost. Leave concurrency high, and take the throughput. When you are simply out of capacity. A cap moves waiting from the stream to the queue. It doesn't make more GPU. In my runs, the queue still grew, and your time to first token will show that. When a limit would cut real work. A low max_tokens on a reasoning model can end the answer before it starts. Check finish_reason first. Treat my numbers as a shape, not a measurement. They come from a simulation that follows each server's source code, with invented lengths and an assumed prefill speed. Single runs also varied widely: the same setup froze for 26 to 170 seconds across nine seeds. It ignores prefix caching, which can make a reread much cheaper. I did not run these servers on a GPU. If prefill is slow, or contexts are long, eviction costs more than it did here. Three questions before you change anything: - How much longer is your p95 answer than your mean answer? - Does your server hold memory for max_tokens, or only for what exists? - Would a user notice a ten-second pause, or only a slower batch? Glossary - KV cache — the model's saved work for every token so far, which grows as the answer grows. - admission — the moment a server lets a waiting request start using memory. - evicts — frees a running request's cache and sends it back to wait. - prefill — processing input tokens to build the cache, much faster than generating. - max_tokens — the most tokens a caller allows for one answer; a ceiling, not a forecast. - watermark — a share of memory vLLM can keep free when admitting requests. Further reading - Optimization and Tuning — vLLM documentation - scheduler.py in vLLM — vLLM repository - Hyperparameter Tuning — SGLang documentation - Launcher reference — Hugging Face TGI documentation --- Originally published at theaicommit.com, a daily AI engineering lab for software engineers. The original has an interactive version you can play with, and the full runnable code is on GitHub.

  2. Reddit · · r/LocalLLaMA · u/rmhubbert · vllm-project/vllm

    vLLM now has PLE disk offload support on nightly

    169 points · 21 comments
  3. vLLM・SGLang・TensorRT-LLMをクラウドGPUで動かしたら、それぞれの個性が見えてきた この記事で分かること Qwen3-8Bを、L4 24GB 1台の上でvLLM・SGLang・TensorRT-LLMに載せ、同じリクエストを送った。揃えたつもりの条件が、5か所で揃っていなかった。どれもベンチマークの数字に入り込む。 速さの順位は載せない(速さは自分の条件で測ってほしい)。すべてこの版・L4(sm_89)での話である。エンジンを選びたいだけなら「エンジン別の要点」と最後の「私の使い分け」へ。 エンジン別の要点 エンジン 知っておくこと vLLM 0.31.0 L4ではQwen/Qwen3-8B-FP8が重みだけFP8(W8A16)で動く SGLang 0.5.21 ツール定義に項目が足され、入力が増える。L4・8B bf16は--mem-fraction-staticの自動値では起動しない(0.87で動いた) TensorRT-LLM 1.2.1 L4ではQwen/Qwen3-8B-AWQ・-FP8をそのまま読めない。KVが足りないと黙って待たせる。キャッシュを消すAPIが無い 用語 用語 意味 KVキャッシュ 生成中のリクエストが、それまでのトークンの計算結果を置いておくGPUメモリ。容量をトークン数で数え、同時に処理できる本数の上限を決める プリフィル 入力(プロンプト)をまとめて読み込む最初の段階。入力が長いほど重い TTFT Time To First Token。送ってから最初の1トークンが返るまでの時間 W8A16/W8A8 重み(Weight)と計算時の値(Activation)のビット数。W8A16は重みだけ8bitで計算は16bit 追い出し(preemption/retraction) KVが足りなくなったとき、処理中のリクエストをいったん外すこと。後でやり直す RadixAttention SGLangのプレフィックスキャッシュ。木構造で、任意のトークン位置まで一致を使い回す(vLLMは決まった長さのブロック単位) sm_89/Ada/Hopper NVIDIA GPUの世代。L4はAda(sm_89)、H100はHopper(sm_90) ModelOpt NVIDIAの量子化ツール。TensorRT-LLM向けの量子化モデルを作る 条件 項目 値 モデル Qwen3-8B(bf16、AWQ 4bit、FP8) GPU NVIDIA L4 24GB 1台(sm_89、AWS g6.xlarge)。ドライバ 595.112.07、CUDA 13.2 エンジン vLLM 0.31.0 / SGLang 0.5.21 / TensorRT-LLM 1.2.1(trtllm-serve、PyTorchの経路) リクエスト /v1/chat/completions。ツール定義10個(ケースごとに40個から選ぶ)+日本語の質問。システムプロンプトなし、thinkingオフ、質問の末尾にリクエストごとの識別子 max_tokens/最大の文脈長 512/8,192 測った並行数 AWQ・FP8は1と16。bf16は1(16は載らない:3節)。SGLang bf16は容量の確かめで8も 送ったケース 200ケースのうち20ケースを繰り返し送った(並行数1で80件) プレフィックスキャッシュ 3エンジンとも既定で有効。区切り(エンジン×チェックポイント×並行数×測定の種類)ごとにサーバーを起動し直す エンジン別 vLLM SGLang TensorRT-LLM 入力長 最大/中央値(200ケース) 2,591/2,310 2,691/2,410 2,591/2,310 ツールパーサー hermes qwen(Qwen25Detector) qwen3 バッチの上限 max_num_seqs 256・max_num_batched_tokens 2048 max_running_requests 2048 max_batch_size 2048・max_num_tokens 8192 チャンク化プリフィル 有効 有効(4096) 無効 CUDA graph FULL_AND_PIECEWISE 有効 バッチ1〜32・64・128 バッチの上限はどれも並行数16より大きい。 1. 入力:SGLangだけトークン数に差が出た 同じリクエストでusage.prompt_tokensを比べると、SGLangだけ全ケースで+100トークンだった。 // 送ったもの({…}は省略) {"type": "function", "function": {"name": "search_master", "description": "…", "parameters": {…}}} // テンプレートに渡るもの {"type": "function", "function": {"description": "…", "name": "search_master", "parameters": {…}, "strict": false}, "defer_loading": null} エンジン toolsの扱い prompt_tokens vLLM 型に通すが、テンプレートに渡す長さは変わらない 送ったまま手元でテンプレートに通した長さと一致 SGLang model_dump()で項目を足し、キーの順が変わる +100 TensorRT-LLM — vLLMと一致 書き換えを再現する関数を書き、1,200件でSGLang自身のmodel_dump()と突き合わせて違いは0件だった。確かめるなら、テンプレート適用後の文字列(vLLMなら/tokenize)の差分を取る。 切り替えるオプションは無い。 揃えるならクライアントから足した形で送る(他エンジンの入力も変わる)。今回はそのまま測った。 効き方:プリフィルが重くなる/KVの見積もりがずれる(3節)/精度にも混ざる。ツールが多いほど差は大きい。パーサーも3つとも別の実装で、読み損ねは精度の差として出る。 2. 量子化:「FP8」が同じ意味ではない エンジン 起動時の警告 vLLM Weight-only FP8 compression will be used leveraging the Marlin kernel. SGLang Using default W8A8 Block FP8 kernel config. Performance might be sub-optimal! 「FP8で比べた」表は、重みだけ8bitと計算も8bitが混ざりうる。起動ログのカーネル名で確かめる。 vLLMがL4でW8A16になるのは、カーネルの候補を順に試し、最初に使えるものを選ぶため(vllm/model_executor/kernels/linear/init.pyのinit_fp8_linear_kernel)。 CUTLASSのブロックFP8のカーネルはsm_90・sm_100・sm_120向けしか無い(csrc/.../c3x/scaled_mm_blockwise_sm*_fp8.cu)。--linear-backendで変えられるはずだが試していない。 TensorRT-LLM 1.2.1・L4では、公開の次の2つを読めなかった。 チェックポイント 読めない理由 代わり(ModelOpt 0.37.0) Qwen/Qwen3-8B-AWQ(AutoAWQ形式) load_hf_quant_configにawqの分岐が無い --qformat int4_awq Qwen/Qwen3-8B-FP8(ブロック単位) 対応表で、ブロック単位FP8はAda(sm_89)が対応外(HopperとBlackwell sm100/103は対応)。モデル側の表でもQwen3は対応外なので、H100でも使えない --qformat fp8 --kv_cache_qformat none 2つで約56分かかった。nvidia/Qwen3-8B-FP8はKVまでFP8になるため使わなかった(KVの型の上書きは試していない)。TensorRT-LLMだけチェックポイントが別物なので、量子化した構成はエンジン間で比べず、各エンジン内でbf16との精度差だけ確かめた。 3. 容量:同じGPUでも載る量が違う 名前の似たメモリ設定が、何に対する割合かが違う(L4は約22.5GiB〔23,034MiB〕、Qwen3-8B bf16の重みは15.27GiB)。 | weights | KV | act | rest | GPU |===============|==============|=========|======| vLLM |<---------------- 0.92 ---------------->| SGLang |<----------- 0.87 ----------->| TRT-LLM |<-------- 0.9 x free --------->| act=活性化など計算中の作業領域(プリフィルの中間結果、CUDA graph)。rest=エンジンの外で使われる分の余白。模式図で、長さは比率どおりではない。 エンジン 割合の対象 割合に含むもの 値 KVの容量(トークン) 容量を直接指定するなら vLLM GPU全体 重み+KV+活性化 0.92(既定) 30,240 --kv-cache-memory-bytes(指定すると0.92は無視される) SGLang GPU全体 重み+KV(活性化は外) 0.87(手で決めた) 26,813 --max-total-tokens TensorRT-LLM 重みと活性化を確保した後の空き KVだけ 0.9(既定) 約32,800 kv_cache_config.max_tokens(割合と両方なら小さいほう) 揃えたいなら割合ではなくトークン数で直接指定する(オプション名は版で変わりやすい)。今回は既定に近い状態を測るため揃えていない。 KV 1トークン = 36層 × KVヘッド8 × 128次元 × K/V 2 × 2バイト = 144KiB 1本の最大 = 入力2,691(SGLang) + max_tokens 512 + 余裕1 = 3,204 並行数8 = 3,204 × 8 = 25,632(+100前は 3,104 × 8 = 24,832) 並行数16 ≒ 51,300 → bf16は3エンジンとも載らない 全リクエストが最大長まで伸びても入るかで数え、入らない組み合わせは測っていない。 SGLangは自動値(0.684)で起動しなかった。 自動値=(GPUの容量−作業領域の見積もり)÷GPUの容量(srt/arg_groups/memory_hook.py)。 作業領域の見積もり(L4) 大きさ 固定の分 512MiB プリフィル chunked_prefill_size 4,096トークン×1.5MiB=6GiB(係数1.5はソースで「経験的な値」) 並列化の分 128MiB(1GPU) CUDA graph デコードの最大バッチ48×2MiB+プリフィルの分 合計 約7.1GiB(23,034MiB×(1−0.684)) ValueError: Loaded weights leave no GPU memory for the KV cache under --mem-fraction-static=0.684. 並行数8が載る値を0.01刻みで探した(0.01あたり約1,590トークン)。 割合 KVの容量 並行数8(25,632) KV以外の残り 追い出し 0.684(自動) 起動しない — — — 0.84 22,047 載らない 1.82 GB — 0.86 25,224 載らない 1.40 GB — 0.87 26,813 載る 1.11 GB 0回 +100前なら0.86で足りたが、1節の+100で408トークン足りなくなり0.87にした。0.88以上は試していない。 4. 溢れたとき:追い出すか、黙って待たせるか 方式 エンジン 混んだとき遅れる場所 見える指標 追い出す vLLM・SGLang 生成の途中(ストリーミングが止まる) vllm:num_preemptions/sglang:num_retracted_requests_total 待たせる TensorRT-LLM 最初の1トークン(TTFT) 無い。numActiveRequestsで見る TensorRT-LLMは「入力長+max_tokens」ぶんを先に確保するので、max_tokensを大きく付けるほど同時に受け入れる本数が減る。並行数16で送っても中では8本しか走っていないことがある。空きに合わせて詰め込むMAX_UTILIZATIONもある(処理中のリクエストが一時停止しうる。試していない)。 今回は/metricsを2秒ごとに読み、numActiveRequestsの最大が送った本数に届いたことを確かめた。溢れる組み合わせは測っていない(3節)。 5. 観測:そのまま読むと誤判定になる エンジン 癖 対処 SGLang キャッシュヒットが0だとprompt_tokens_detailsごと出ない 無ければ0 SGLang num_retracted_requests_totalは初回の追い出しまで出ない ゲージnum_retracted_reqsがあれば0回 TensorRT-LLM kvCacheStatsはenable_iter_perf_statsが要り、1件処理するまで空 短いリクエストを1件送ってから読む TensorRT-LLM その1件で、測定の最初のリクエストがcached_tokens=1になった(<|im_start|>を共有。partial reuseが既定で有効なので、32トークンのブロックの一部でも当たる) 書式を通さない/v1/completionsで送る TensorRT-LLM 反復ごとの統計は読むたびに消える 測定中に2秒ごとに読む 「読めなければ0」にしていたら誤った結論を出していた。「読めなければ止める」作りにしていたので気づけた。 エンジン キャッシュの消し方 vLLM POST /reset_prefix_cache(VLLM_SERVER_DEV_MODE=1) SGLang POST /flush_cache TensorRT-LLM APIなし。再起動 キャッシュがどれだけ効くかは、送るケースの数とKVの容量で大きく変わる。今回は20ケース(入力の合計45,957トークン)を繰り返し送った。 チェックポイント KVの容量(トークン) 20ケースぶんが cached_tokens/入力(3エンジンとも同程度) bf16 26,813〜32,800 収まらず、一周する前に追い出される 3.6〜4.7%(当たるのは先頭の書式 約50トークンだけ) AWQ・FP8 76,096〜99,552 収まり、2周目からツール定義ごと当たる 並行数1で約75%、16で約82% 効いているのは並行数ではなく、量子化で空いたKVの容量である。量子化した構成の速さには、計算が軽くなった分とキャッシュに当たった分が混ざりうる。ケースの数・繰り返し方とKVの容量を並べて確かめたい。 6. 動かすまで 版がぶつかるので、仮想環境をエンジンごとに分け、クライアントはHTTPだけで話す。 エンジン torch transformers vLLM 2.13.0+cu132 5.17.0 SGLang 2.13.0 5.12.1 TensorRT-LLM 2.9.1+cu130 4.57.3 問題 対処 TensorRT-LLM環境でModelOptが動かない(NumPy<2固定と最新pyarrowが衝突) pyarrow<22 TensorRT-LLM FP8が起動中にsegfault(原因不明、再実行で通過) — そのとき親プロセスが残り、20分待ち続けた ログにSegfault encountered等が出たら即失敗にして1回だけ再起動 まとめ:比較を作る・読むときのチェックリスト 確かめること 確かめ方 モデルに届く入力は同じか usage.prompt_tokensを並べる。テンプレート適用後の文字列を比べる ツールパーサーは何か 起動オプションを書く 量子化の計算は何か 起動ログのカーネル名・警告 量子化のチェックポイントは同じか 対応表とソースで、使うGPUで読める形式か確かめる KVの容量はいくつか 割合ではなくトークン数で見る。揃えるなら直接指定する 溢れたら何が起きるか 追い出しカウンタと同時実行数。max_tokensも書く 指標が読めないときの扱い 読めなければ止める キャッシュの状態 有効か、区切りごとに消すか、cached_tokensはいくつか。ケースの数・繰り返し方とKVの容量も書く 私の使い分け(推奨案) 速さではなく、導入の手間・設定の素直さ・観測しやすさで決めた。 当てはまる範囲:24GBクラスのGPU1台・8B級・同時8本程度まで。大きいGPUや70B級ではメモリ(3節)と量子化の対応(2節)が変わる。 推奨 想定するユースケース 決め手 気をつけること vLLM 社内チャットやRAGのPoC。公開モデル(量子化版を含む)を差し替えて評価する 公開チェックポイントをそのまま読める(2)。既定の設定で起動し、追い出しがカウンタで見える(3・4) L4のFP8はW8A16(2) SGLang 同じツール定義・システムプロンプトを先頭に付けるエージェントやバッチ処理 RadixAttention(用語)。L4でもFP8がW8A8(2)。※今回は共有部分が短く、実測ではない(5) 入力が増える(1)。割合を手で決める(3)。指標が0で出ない(5)。共有部分の長い順に処理するlpmは既定ではない(既定fcfs) TensorRT-LLM モデルを固定して長く運用する本番API 処理中のリクエストが途中でやり直しにならない(4)。量子化は最初の1回で済む 量子化を作り直す(2)。混むとTTFTが伸びるので、SLAがTTFTなら自分で測る(4)。キャッシュを消すAPIなし(5) 迷ったらvLLMで基準を作り、チェックリストで条件を揃えてから他と比べる。 付録:起動コマンド vLLM 0.31.0(bf16。AWQ・FP8はモデル名だけ替える。VLLM_SERVER_DEV_MODE=1 を付けて起動) vllm serve Qwen/Qwen3-8B --served-model-name Qwen/Qwen3-8B \ --host 127.0.0.1 --port 8000 \ --enable-auto-tool-choice --tool-call-parser hermes \ --enable-prompt-tokens-details --max-model-len 8192 SGLang 0.5.21 python -m sglang.launch_server --model-path Qwen/Qwen3-8B --served-model-name Qwen/Qwen3-8B \ --host 127.0.0.1 --port 30000 \ --tool-call-parser qwen --enable-metrics --enable-cache-report \ --mem-fraction-static 0.87 --context-length 8192 TensorRT-LLM 1.2.1(ModelOptで作ったAWQ。bf16はQwen/Qwen3-8B) echo "enable_iter_perf_stats: true" > trtllm-serve.yaml trtllm-serve ~/models/Qwen3-8B-int4-awq-modelopt \ --host 127.0.0.1 --port 8001 --backend pytorch \ --tool_parser qwen3 --config trtllm-serve.yaml --max_seq_len 8192 出典 SGLang v0.5.21:github.com/sgl-project/sglang/tree/v0.5.21 (srt/entrypoints/openai/serving_chat.py・protocol.py・usage_processor.py、srt/observability/metrics_collector.py、srt/function_call/function_call_parser.py、srt/arg_groups/memory_hook.py) TensorRT-LLM v1.2.1:github.com/NVIDIA/TensorRT-LLM/tree/v1.2.1 (tensorrt_llm/commands/serve.py、llmapi/llm_args.py、_torch/model_config.py、_torch/modules/linear.py、cpp/tensorrt_llm/executor/jsonSerialization.cpp、docs/source/features/quantization.md) ModelOpt 0.37.0:github.com/NVIDIA/Model-Optimizer/tree/0.37.0 (examples/llm_ptq/hf_ptq.py) vLLM v0.31.0:github.com/vllm-project/vllm/tree/v0.31.0 (vllm/config/cache.py、vllm/engine/arg_utils.py、vllm/model_executor/layers/quantization/fp8.py、vllm/model_executor/kernels/linear/init.py・scaled_mm/marlin.py・cutlass.py・flashinfer.py・deep_gemm.py・b12x.py、vllm/utils/flashinfer.py、vllm/platforms/cuda.py、vllm/model_executor/layers/quantization/utils/w8a8_utils.py、csrc/libtorch_stable/quantization/w8a8/cutlass/c3x/、vllm/entrypoints/openai/chat_completion/protocol.py、vllm/v1/metrics/loggers.py)、起動ログ vLLM プレフィックスキャッシュの設計:docs.vllm.ai TensorRT-LLM スケジューラの方針(GUARANTEED_NO_EVICT/MAX_UTILIZATION):nvidia.github.io SGLang サーバー引数:docs.sglang.io 、new_token_ratioはsrt/managers/schedule_policy.py nvidia/Qwen3-8B-FP8:huggingface.co PagedAttention:Kwon et al., SOSP 2023。RadixAttention:Zheng et al., NeurIPS 2024

  4. NCCL Roadmap Nov 2026 - Jan 2027 の解説 はじめに ! NCCL 奥がふかすぎる。。。。にゃ。。。 NVIDIA が公開した NCCL Roadmap: Nov 2026 - Jan 2027 (GitHub Issue #2486) から、大規模分散学習・推論に携わるエンジニアが押さえるべきポイントを解説します。前回のロードマップの解説は「NCCL Roadmap Aug-Oct 2026 の解説」、そこで挙げた項目の現在までの実装状況は「NCCL 2.32.3 でロードマップはどう実装されたか」にあります。GIN の基本は前回の解説を前提にします。 この記事では、新しい概念の GENK と Flow、AWS EFA と GIN の現状、注目機能 2 つを扱います。調査は 2026 年 10 月 29 日に行い、まだリリースされていないコードは、staging ブランチを次のリリースの候補とみなして読んでいます。 実装状況の確かめ方 NVIDIA/nccl、aws-ofi-nccl、amazon-contributing/DeepEP、NVIDIA/nccl-extensions の公開ブランチを手元に取得し、commit の履歴とソースを読んで確かめました。PR と Issue の状態 (open か、マージされたか、閉じた日) は GitHub で確かめています。記事のソースへのリンクは、確かめたときの commit に固定しています。 NCCL は master、staging、dev の 3 本のブランチを公開していて、staging には 2.32.5-1 の版番号を付けた commit があり、その後に 2.32.6a0 に上がっています。ロードマップは「Coming Soon: NCCL 2.32 Update 1」の版番号を書いていませんが、この記事では staging のコードをその候補として読みます。 今回のロードマップの項目のうち、この記事で扱うものと、前回のロードマップの近い項目は次のとおりです。対応は筆者の読みです。 今回のロードマップの項目 前回のロードマップの近い項目 General Kernels (GENK) Unify legacy kernels using Device API Flow Device API More Device API primitives GIN Backend Improvements GIN backend improvements CFT を使う階層型カーネル Compute Fabric Transport (CFT) through Device API New Enqueue Pipeline Legacy and symmetric-kernel host-side unification ロードマップには、内部のメモリの削減、RAS の進み具合の監視、RMA の profiling など、この記事で扱わない項目もあります。 Device API とは何か NCCL の API の全体像 ロードマップの項目を説明する前に、まず背景知識として NCCL の API を整理します。NCCL の API は、Host API、Device API、plugin API の 3 つに分けられます。Host API と Device API の仕様は NCCL の公式文書にあり、この記事の表はその章立てに沿っています。plugin API は公式文書の API の章には無く、リポジトリの plugins ディレクトリに plugin の種類ごとの説明があります。 API 何をするか 例 Host API CPU から呼んで、communicator (通信に参加する GPU の集まり) の作成や集団通信を NCCL に頼みます。集団通信では、NCCL がアルゴリズムを選び、NCCL のカーネルを GPU で起動します。利用者の計算は別のカーネルです ncclCommInitRank、ncclAllReduce Device API 利用者が書いた CUDA カーネルの中から、ほかの GPU との通信を直接呼びます (2.28 から)。計算と通信を 1 つのカーネルに入れられます。使う前の準備には Host API を使います GIN、LSA plugin API NCCL のほうから呼ぶ、下の部品の差し替え口です。ネットワークへの送受信や、アルゴリズムの選択の調整などを、NCCL の外の実装に任せます net、GIN。説明は plugins にあります。aws-ofi-nccl は EFA 用の plugin 誰が NCCL を呼ぶか NCCL を呼ぶのは、多くの場合アプリ自身ではなく、間に入るフレームワークやライブラリです。主な呼び手を、ソースで確かめた範囲で並べます。 呼び手 どう呼ぶか 使う API PyTorch の torch.distributed (backend "nccl") C++ の ProcessGroupNCCL が、ncclAllReduce などを直接呼びます Host API PyTorch の backend "nccl4py" (torch.distributed._nccl4py) Python で書いた backend が、nccl4py を通して呼びます。init_process_group("nccl4py") で選びます Host API nccl4py NVIDIA の公式の Python の bindings。Cython で libnccl を呼び、PyTorch の tensor や CuPy の配列をそのまま渡せます。実験的な CuTe DSL の部品もあります 主に Host API vLLM の PyNccl Python の ctypes で libnccl を読み込んで呼びます Host API PyTorch の symmetric memory の演算、DeepEP、NCCL EP 自分の CUDA カーネルの中から通信を呼びます。準備には Host API で window (次の節で説明します) を登録し、device communicator を作ります Device API と Host API aws-ofi-nccl など NCCL のほうから呼ばれます。NCCL が EFA などのネットワークに送るときの下の部品です。InfiniBand には NCCL に組み込みの経路もあります plugin API Device API は何のためにあるか Host API で通信を頼むと、通信は NCCL のカーネル 1 回分の単位になり、利用者の計算とは別のカーネルになります。計算の途中で決まった相手に少しずつ送りたい場合や、計算と通信を細かく重ねたい場合には、この単位が粗すぎます。たとえば MoE のトークンの振り分けでは、どのトークンをどの GPU に送るかが計算の途中で決まります。 Device API は、利用者のカーネルの中から、ほかの GPU のメモリへ直接データを動かせるようにするための API です。NCCL の Device API の例は、計算と通信を分けて順に行う従来のやり方に対して、カーネルの中で両方を細かく混ぜることで、待ち時間を減らし処理量を上げられると説明しています。 GPU のカーネルからほかの GPU に届く経路はいくつかあり、Device API は経路ごとに部品を用意しています。同じノードで NVLink や PCIe の P2P でつながった相手には、相手のメモリを普通の読み書きの命令で触れます (LSA)。NVLink のスイッチが複数の宛先への書き込みや足し合わせを引き受けられるシステムでは、Multimem を使います。ノードをまたぐ相手には、NIC を通して put (相手のメモリへの書き込み) や get (相手のメモリからの読み出し) を出します (GIN)。どちらも、相手が何も呼ばない片側通信です。CFT は、相手のメモリを番地 (ポインタ) で触る代わりに、CUDA が用意する宛先の番号 (logical endpoint) とオフセットを指定して put や get を出す部品で、Blackwell 以降の GPU で使えます。これらと組み合わせて使う、足し合わせやコピーの部品 (reduce と copy) も用意されています。 GIN の仕組みは、前回の解説の「GIN とは何か」で説明しました。LSA は後の GENK と Flow の節で、CFT は注目機能の節で扱います。Multimem と reduce と copy の部品は、この記事では詳しく扱いません。 どの部品を使う場合も、準備は利用者が CPU で Host API を呼んで行います。通信に使うバッファを、全ランクで同じ形に対応付けた領域 (symmetric memory window) として登録し、カーネルが使う資源を指定して device communicator を作り、それを渡して自分のカーネルを起動します。手順の例は NCCL の文書にあります。GIN を使うカーネルは、NCCL を更新したら作り直す必要があると文書は書いています。次の節からは、この Device API の上に加わる新しい部品とカーネルを見ていきます。 GENK と Flow とは何か ! GENK と Flow は、まだリリースされておらず staging と dev にだけあるコードです。この節は staging の実装から読み取った内容で、リリースまでに変わる可能性があります。staging で試すには、ビルドのときに SYM_GENK=1 を指定し、実行のときに NCCL_SYM_GENK_ENABLE=1 を設定する必要があります。 概要 Flow は、前の節の LSA と GIN の上に作られた、集団通信の部品を提供する Device API です。ロードマップは Flow を「LSA と GIN の両方で動く、高水準の Device API」と説明しています。LSA は前の節で触れた、相手の GPU のメモリを普通の読み書きの命令で触る部品です。 GENK (General Kernels) は、NCCL が自分の Ring と Tree の集団通信を、この Flow の上で作り直したカーネルの一群です。Ring では AllGather、ReduceScatter、Broadcast、Reduce、AllReduce を、Tree では AllReduce を実装しています。 前回のロードマップには「Unify legacy kernels using Device API」という項目がありました。筆者は、GENK をこの項目につながる取り組みと読んでいます。今回のロードマップは、選んだプラットフォームで GENK を既定にし、古いカーネルを置き換えるとしています。あわせて性能モデルを 1 つにまとめ、どの実装を選ぶかを同じ物差しで決めるとしています。 staging では、NCCL 自身が同じ Flow で集団通信を組んでいます。Flow がリリースに入れば、利用者も NCCL の内部と同じ部品で通信カーネルを書けます。 NCCL の集団通信には、GENK の前から 2 種類のカーネルがあります。 legacy kernels: NCCL に最初からある Ring や Tree のカーネルです。経路の違いは、CPU の側が準備する transport (NVLink や PCIe の P2P、ネットワークなど) が受け持ち、ネットワークでは CPU の proxy スレッドが NIC に渡します。 symmetric カーネル: 全ランクが同じ形で登録したバッファ (symmetric memory window) を集団通信に渡したときに使える、新しいカーネルです。相手のバッファの番地が分かるので、Device API の LSA などで相手のメモリを直接読み書きします。いまあるものは、NVLink の直結を前提に、場面ごとに作り込まれています (表の「特化した symmetric カーネル」)。 GENK は、symmetric カーネルと同じ Device API の作りで、legacy kernels と同じくらい広い経路で動くものです。Ring で隣の GPU にデータを渡す場面で比べると、次のようになります。 隣の GPU との経路 legacy kernels 特化した symmetric カーネル GENK NVLink 動く 動く 動く (LSA) PCIe の P2P 動く 使えない 動く (LSA) 別のノード (ネットワーク) 動く 一部の種類だけ動く (GIN) 動く (GIN) 表は、Hopper 以降の GPU で GENK を有効にした場合についてソースから読んだもので、動かして確かめたものではありません。PCIe の行は CUDA P2P が使える場合、GENK のネットワークの行は GIN Proxy (GPU の代わりに CPU のスレッドが NIC に送る GIN のバックエンド) が使える場合です。 特化した symmetric カーネルは、PCIe だけでつながったノードでは候補から外れます。GENK では、隣の GPU ごとに Flow が LSA か GIN かを実行時に選ぶので、同じカーネルのまま NVLink でも PCIe でもネットワークでも動きます。staging のカーネルの選択にも「特化したカーネルは NVLink の直結を要するが、GENK は CUDA P2P の LSA や GIN も使える」というコメントがあります。 利用者の側の例もあります。NCCL の Device API の例には、Transformer の RMSNorm の計算を ReduceScatter と AllGather の通信と 1 つのカーネルにまとめるものがあり、いまは LSA、Multimem、GIN を直接使って、受け取り、足し合わせ、送り出しを書いています。Flow は、NCCL 自身が集団通信に使うこの受け取り、足し合わせ、送り出しの部分を、部品として提供します。 NCCL の文書の Device API の章には、Flow と GENK の説明はまだありません。Flow の使い方は、ヘッダの説明にまとまっていて、Flow processor を足した commit が入口になります。GENK は、カーネルを足した commit と、NCCL の選択に組み込んだ commit が出発点です。 図で [API] のタグを付けた Flow processor、LSA connection、GIN connection は、アプリのカーネルからも呼べる Device API です。cost model、legacy kernels、specialized symmetric kernels、GENK は NCCL の中の部品です。 GIN の signal とは GIN の put は片側通信で、受け手は何も呼ばないので、データがいつ届いたかを自分では知れません。signal は、それを知らせるための、受け手の側に置かれた数です。送り手が put に signal を付けると、データが届いたあとに受け手の数が増え (たとえば 1 増え)、受け手はその数が目標に達するまで待ってから読みます。GIN の基本は、前回の解説の「GIN とは何か」で説明しました。 signal には strong と weak の 2 種類があります。weak signal が「読んでよい」と約束するのは、その signal を付けた put のデータだけです。strong signal は、同じ相手に同じ context (GIN の通信の通り道の 1 本) で先に出した put のデータもすべて読めると約束します。そのため、最後の put にだけ signal を付ければ済みます。 EFA の送り方の SRD は、書き込みが届く順番を保ちません。そのため、最後の put に付けた signal が届いても、前の put のデータが届いたとは限りません。EFA の上の経路のうち、GPU から直接送る EFA GDA が提供するのは weak signal だけです (次の節で説明します)。一方、Flow の GIN の接続は strong signal を必要とします。 AWS EFA 関連アップデートと現在の課題 EFA の 2 つの GIN の経路と signal の種類 ! GENK と Flow の GIN の接続は strong signal を必要とします。EFA の上で strong signal を提供するのは GIN Proxy だけで、GPU から直接送る EFA GDA は weak signal しか提供しません。そのため、EFA の複数ノードで GENK を使うなら Proxy の経路になります。 EFA で GIN を使うときは、使う経路 (GIN のバックエンド) が、カーネルの必要とする種類の signal を提供するかを先に確かめます。NCCL は、カーネルが求める種類の signal を提供できないバックエンドを使わず、提供できる別のバックエンドがあればそちらに切り替え、無ければエラーにすると説明しています。EFA で使う GIN の経路として、ここでは 2 つを比べます。1 つは NCCL の GIN Proxy で、CPU のスレッドが aws-ofi-nccl の RMA の機能を通して EFA に送ります。もう 1 つは EFA GDA で、GPU のカーネルが EFA の送信キューに直接依頼を書きます。aws-ofi-nccl の文書によると、EFA GDA は NCCL_GIN_TYPE=5 で明示したときに使われます。同じ文書は、明示しなければ NCCL 2.31 は EFA で Proxy を使うと書いています。インスタンスやドライバなどの要件も同じ文書にあります。 2 つの経路は、提供する signal が違います。NCCL の Proxy は、strong signal と VA signal (登録した仮想アドレスに置く signal) を使えると報告します。EFA GDA については、aws-ofi-nccl が strong signal も VA signal も使えないと報告し、NCCL の側も番号で指す indexed signal だけを受け付けます。EFA GDA の signal は、EFA の NIC が届いた書き込みを数えるカウンタで作られています。aws-ofi-nccl の文書は、signal と counter の合計が EFA のデバイスあたり 512 個までであることも、制限に挙げています。 図の矢印は、使い手が必要とする signal をバックエンドが提供するかを示します。矢印はソースと文書から描いたもので、どの経路が選ばれるかや、動かした結果を示すものではありません。図の DeepEP の ordered は届く順番を前提にする上流のカーネル、unordered は順番を前提にしない amazon-contributing のフォークのカーネルです。 GENK はノードをまたぐ通信に使う GIN のバックエンドとして Proxy を指定しているので、staging のコードを読む限り、GENK が EFA の上で複数ノードの通信をするなら Proxy の経路を通ります。一方、aws-ofi-nccl の文書は、EFA GDA を使うときに symmetric の GIN カーネルを無効にするよう書いていて、この設定 (NCCL_SYM_GIN_KERNELS_ENABLE=0) にすると複数ノードの GENK は候補から外れます。つまり、EFA GDA を使う設定 (NCCL_GIN_TYPE=5 と NCCL_SYM_GIN_KERNELS_ENABLE=0) では複数ノードの GENK は使われず、複数ノードで GENK を試すときは、この 2 つの環境変数を設定せずに Proxy の経路で動かします。 aws-ofi-nccl v1.22.0 で、EFA GDA の Get がタグの付いた版で使えるようになった Get は、GPU のカーネルが、ほかの GPU のメモリの中身を、持ち主に何もさせずに読み出す操作です。たとえば、手元の番号を見て、ほかの GPU が持つ表の行を取り出すような使い方ができます。Get の意義は「NCCL 2.32.3 でロードマップはどう実装されたか」で説明しました。 その記事の時点では、NCCL 2.32.3 には Get が入っていましたが、aws-ofi-nccl の側の対応はタグの無い master にしかありませんでした。2026 年 10 月 22 日 (日本時間) の v1.22.0 は、NCCL 2.32 が使うキューの形への対応と、Get の完了を数える修正を含む最初のタグです。これで、NCCL 2.32 と aws-ofi-nccl v1.22.0 という、タグの付いた版どうしに、EFA GDA の Get に要る変更がそろいました。v1.22.0 のリリースノートは Get に触れておらず、筆者は動かして確かめていません。 ただし、EFA GDA の Get には、完了を知らせる signal や counter を付けられません。読み出しが終わったかは、出した依頼をまとめて待つ Flush で確かめます。 MoE の通信ライブラリ (DeepEP と NCCL EP) の動き MoE のトークンを担当の expert に配り (dispatch)、計算結果を集める (combine) 通信のライブラリにも動きがありました。DeepEP と NCCL EP はこの同じ役割の候補で、どちらも下で NCCL を使います。DeepEP は 2026 年 9 月 29 日の V2.5 で NVSHMEM への依存を取り除き、ノードをまたぐ通信は NCCL の GIN だけになりました。要件は NCCL 2.32.3 以降です。EFA で DeepEP を動かす取り組みは amazon-contributing/DeepEP のフォークにあり、その詳細は別の記事で整理してみます。 NVIDIA の NCCL EP は NVIDIA/nccl-extensions に移り、NCCL の staging からは、nccl_ep と nccl_m2n のディレクトリが消されました。 注目機能 2 選 CFT を使う階層型カーネル CFT (Compute Fabric Transport) は NCCL 2.31 で Device API に加わった部品で、嬉しさは 2 つあります。1 つ目は、カーネルから直接届く相手を、LSA の範囲より広げられる見込みがあることです。LSA で届く相手は、CUDA P2P で相手のメモリを普通の読み書きの命令で触れる相手 (LSA team) に限られます。CFT は相手を番地ではなく CUDA が配る宛先の番号で指すので、届く範囲は、CUDA が「この番号で届く」と報告する範囲で決まります。前回のロードマップも、CFT を、logical endpoint で届く範囲の相手を宛先の番号で指す通信の形と説明していました。ただし、リリースされた版では、CFT で届く相手の集まり (CFT team) の大きさは既定で LSA team と同じです。 2 つ目は、受け手に届いたことを早く知らせる書き込みがあることです。CFT の普通の put や reduce で受け手に知らせるときは、データを書いたあと、書き込みの完了を確定させ (fence)、合図のフラグを書きます。これが counted な書き込みで、フラグの代わりに、NCCL_WIN_CFT_COUNTED を付けて登録した window の中のカウンタを使います。カウンタはデータより後に進むことが保証されているので、送り手は fence とフラグの書き込みを省けます。staging の文書は、これを低遅延のための操作と説明しています。受け手は、カウンタを見てからデータを読む間に fence を入れる必要があります。 仕組みは次のとおりです。CFT の宛先の番号は、CUDA 13.3 で加わった logical endpoint という仕組みです。NCCL は CPU の側で、宛先の番号を確保し、そこに各 GPU のバッファを結び付けておきます。カーネルは、送りたいデータを shared memory に置き、宛先の番号とオフセットを指定して put を出し、そのあと完了を確かめます。 dev では、CUDA 13.4 以降で CUDA が「logical endpoint で届く範囲」を報告すると、CFT team をその範囲から作ります。その範囲は LSA の範囲の倍数でなければならず、LSA より広い範囲を 1 つの CFT team にできる作りです。NCCL の文書は、CFT がどの物理的な経路を通るかを書いていません。NCCL EP の CFT を使う経路は、1 つの NVLink のドメインの中だけを対象にしています。 ロードマップは、この CFT を使って、AllGather、ReduceScatter、AlltoAll の階層型カーネルを作るとしています。階層型は、近い GPU の間と遠い GPU の間で通信を分ける作りで、LSA の範囲と、それより広い CFT team の範囲を使い分けることになると筆者は読んでいます。master、staging、dev を調べた範囲では、CFT の宛先の集まりの階層の種類は 2.32.3 のコードに定義されていますが、それを使う集団通信のカーネルは見当たりませんでした。 CFT とは CFT が使えるのは、ハードウェア、ドライバ、CUDA が logical endpoint と fabric 用の命令に対応したシステムだけで、例の barrier のプログラムは CUDA 13.3 と SM_100 を要件に挙げています。CFT team を CUDA の報告する範囲 (clique) から作る変更は、dev の 2026 年 9 月 28 日の commit で入り、master と staging にはまだありません。 NCCL EP には、CFT の counted な書き込みで低遅延の dispatch を送る経路が、NCCL_EP_LL_CFT=1 で選んだときだけ使われる形で、2026 年 9 月 16 日の commit から main に入っています。 New Enqueue Pipeline: 呼び出しから起動までを 3 つの層に分ける enqueue は、アプリが呼んだ集団通信や put を、カーネルやコピーの起動に変える NCCL の中の流れです。ロードマップは、これを管理 (Management)、準備 (Preparation)、スケジューリング (Scheduling) の 3 層に分け、制御の流れを簡単にし、カーネルの最適化をしやすくするとしています。 管理と準備の層を含む基本の枠組みは 2.31.2 から入っていて、その時点のスケジューリングの層は既存の経路に戻すだけでした。いまの staging と dev でも、新しい流れは既定で無効です。dev では、Preparation の tuning ですべての集団通信のアルゴリズムやプロトコルを先に選び、classification でその結果だけを使って起動の経路を振り分ける形にしました。経路ごとのスケジューラは、それぞれの仕事を stream ごとに 1 つのキューにまとめます。dev の commit では、新しい流れの symmetric のスケジューラが起動するのは特化した symmetric カーネルだけで、GENK は自分用の device communicator の準備が要るので、従来の流れの symmetric カーネルのスケジューラから起動されます。 まとめ staging の GENK は、Hopper 以降の GPU でビルドと環境変数で有効にすると NVLink、PCIe、ネットワークのどの経路でも同じカーネルが候補になり、EFA の複数ノードでは strong signal を提供する GIN Proxy の経路を使います。CFT を使う階層型カーネルは部品だけが公開コードにあり、New Enqueue Pipeline は枠組みが既定で無効のまま dev で進んでいます。

  5. ⚡ Serve LLMs faster and cheaper with vLLM's PagedAttention, continuous batching and a drop-in OpenAI-compatible API. 🔗 vllm-project/vllm Credits: vllm-project/vllm #vllm #python #llm

    3 likes
  6. Numbers from the open vLLM RFC: vllm#63626

  7. Inference on Google Cloud: Precision, Memory and Capacity on TPU, from v5e to Ironwood --- title: "Inference on Google Cloud: Precision, Memory and Capacity on TPU, from v5e to Ironwood" published: true description: "The companion to the talk: what the number format, memory bandwidth, slice shape and capacity rules do to Gemma 4 on Cloud TPU v5e and v6e, against the L4, Blackwell, MI300X and Inferentia, with the cost of each." tags: googlecloud, tpu, machinelearning, gemma cover_image: raw.githubusercontent.com --- This article is the written companion to the talk "Inference on Google Cloud". It covers serving Gemma 4 on Cloud TPU v5e and v6e through vLLM, measured on the gemma4-dev Cloud TPU test setups between July and October 2026, and compares them with the NVIDIA L4 on Google Cloud, Blackwell list prices, an AMD MI300X and AWS Inferentia. Four ideas explain most of the results: the number format the chip computes natively, the memory bandwidth that feeds it, the slice shape, and the rules that decide whether a TPU is there to rent at all. xbill9/gemma4-dev The hands-on steps run from a second repository, which starts one TPU and serves Gemma 4 on it with a single command: xbill9/gcp-inference The slides: docs.google.com --- What Is the Problem? This article compares four TPU generations, the L4 and Blackwell GPUs beside them, and every way Google Cloud prices a TPU chip-hour. The same Gemma 4 E2B serves at 144 tokens a second on a v5e in bf16 and at 243 in int8 with 4-bit embedding tables, on the same chip. A v6e-1 serves 3.3 times the L4's tokens on the same 26B checkpoint. And a zone that reports quota for 1,536 chips can still return a stockout. --- Which Generations? | Chip | HBM | Peak bf16 | Native low precision | Max pod | On-demand | |---|---:|---:|---|---:|---:| | v5e | 16 GB | 197 TFLOPs | int8, 393 TOPs | 256 | $1.20/hr | | v6e Trillium | 32 GB | 918 TFLOPs | int8, 1,836 TOPs | 256 | $2.70/hr | | v5p | 95 GiB | 459 TFLOPs | int8; fp8 at bf16 rate | 8,960 | $4.20/hr | | TPU7x Ironwood | 192 GiB | 2,307 TFLOPs | fp8, 4,614 TFLOPs | 9,216 | $12.00/hr | Google's first TPU, in its datacenters since 2015, was an 8-bit inference chip with a 92 TOPS peak, and Google announced Ironwood on 2025-04-09 as "the first Google TPU for the age of inference". The gemma4-dev test setups measured v5e and v6e. v5p and TPU7x rows come from Google's documentation only. vLLM reaches the TPU through tpu-inference, a JAX backend. XLA compiles one program per input shape, which makes the compile the largest part of a cold start. --- At This Point You Should Have… - The gcloud CLI with its alpha and beta components, signed in with gcloud auth login and gcloud auth application-default login - A project with Cloud TPU quota for one v5e chip in us-west4-a - python3 with pip install 'google-adk[extensions]' google-genai mcp - A clone of xbill9/gcp-inference --- Step 1 — One TPU, One Command demo.sh asks for a TPU v5e-1 as a flex-start request in us-west4-a, waits for the node, opens an SSH tunnel to vLLM on the TPU's port 8000, waits for the model to load and compile, then runs a Google ADK agent against it. Flex-start stops the node by itself after 4 hours. On a TPU that is already up, ./demo.sh again skips the first two steps: ./demo.sh again == [0m01s] 3/5 Connecting to it over SSH (the demo reaches vLLM on the TPU's port 8000 through a tunnel) [0m05s] SSH works [0m11s] tunnel open: localhost:8005 -> TPU port 8000 == [0m11s] 4/5 Starting Gemma on vLLM (first start downloads and compiles: several minutes) [0m30s] vLLM is answering 20:21:25Z PASS tpu chat 0.198s; tool run filters=['id >= 10'] quoted=8 3.71s READY == [0m37s] 5/5 The demo: Gemma on the TPU counts ids with the count_ids tool from the MCP server Question: How many of the ids are 10 or more? Answer with the number. Ids: [0, 2, 3, 20, 21, 22, 23, 10, 11, 12, 13] True answer: 8 tools: mcp tpu run 1/3 ok quoted=8 filters=['id >= 10'] 3.87s tpu run 2/3 ok quoted=8 filters=['id >= 10'] 0.51s tpu run 3/3 ok quoted=8 filters=['id >= 10'] 0.49s backend runs correct right-filter errors median s filters sent tpu 3 3/3 3/3 0 0.51 id >= 10 The agent is an ADK LlmAgent whose model is LiteLlm pointed at vLLM's OpenAI-compatible API, and an ADK McpToolset starts mcp_server.py over stdio and offers it one tool. The model never counts. It calls count_ids on that MCP server, which returns the exact count, and each run is graded on the filter it sent as well as the number it quoted. Gemma 4 E2B on the v5e sent id >= 10 in all three runs. The node serves google/gemma-4-E2B-it in bf16 with vllm/vllm-tpu:v0.30.0, one chip, no sharding. --- Step 2 — What the 16 GB Chip Holds vLLM logs the chip's memory as it starts. In order: Init worker | ... | hbm=[(0.0, 15.75)]GiB Init model | hbm=[(8.97, 15.75)]GiB Memory statistics | total_hbm_limit_gb=15.75GiB | total_hbm_limit_cap_gb=14.49GiB | total_hbm_used_gb=8.97GiB | total_hbm_avail_gb=5.52GiB TPU KV cache size: 321,280 tokens, Maximum concurrency for 8,192 tokens per request: 39.22x Init kv-cache | num_total_layers=15 | num_blocks=[1255, ...] | regular_attn_shape=(num_blocks, (256, 1, 2, 256)) | regular_attn_dtype=bfloat16 | hbm=[(14.51, 15.75)]Gb | HBM on one v5e chip | GiB | |---|---:| | Weights, bf16 | 8.97 | | vLLM's ceiling (92% of 15.75) | 14.49 | | Left for the KV cache | 5.52 | | In use after the KV cache is allocated | 14.51 | | Never allocated | 1.24 | Each of the 15 cached layers holds 1,255 blocks of 256 tokens, one KV head of dimension 256, in bf16. That is 1,024 bytes per token per layer, 15,360 bytes per token across all 15, and 321,280 token slots in all. The model's config lists 35 layers with num_kv_shared_layers: 20: 20 layers reuse another layer's keys and values, so the cache holds 15. --- Step 3 — The Full 131,072-Token Context Gemma 4 E2B's config allows 131,072 positions. vLLM sizes its KV cache from the memory left after the weights, so raising --max-model-len from 8,192 to 131,072 changes the concurrency and leaves the cache the same size. To change a flag on a running TPU, replace the container: gcloud compute tpus tpu-vm ssh gcp-inference-tpu-node --project <tpu-project> --zone us-west4-a --command \ 'sudo docker rm -f vllm && sudo docker run -d --name vllm --restart unless-stopped --privileged --net=host \ -v /dev/shm:/dev/shm --shm-size 10gb -e HF_HOME=/dev/shm vllm/vllm-tpu:v0.30.0 \ vllm serve google/gemma-4-E2B-it --served-model-name gemma --max-model-len 131072 --port 8000 \ --max-num-batched-tokens 4096 --enable-auto-tool-choice --tool-call-parser gemma4' Memory statistics | total_hbm_limit_gb=15.75GiB | total_hbm_limit_cap_gb=14.49GiB | total_hbm_used_gb=8.97GiB | total_hbm_avail_gb=5.52GiB TPU KV cache size: 321,280 tokens, Maximum concurrency for 131,072 tokens per request: 2.45x init engine (profile, create kv cache, warmup model) took 503.54 s (compilation: 413.41 s) Application startup complete. | --max-model-len | 8,192 | 131,072 | |---|---:|---:| | KV cache, tokens | 321,280 | 321,280 | | Full-length requests at once | 39.22 | 2.45 | | Engine start | 502.84 s | 503.54 s | | of which XLA compile | 408.59 s | 413.41 s | One full-length request needs 1.875 GiB of KV cache. scripts/longctx.py in tpu-survey buries a passphrase at a chosen depth in up to 124,949 tokens of filler, starts each prompt with a unique line so vLLM's prefix cache cannot reuse an earlier run, and asks for the passphrase: $ python3 scripts/longctx.py $TPU_URL 8000 0.5 {"prompt_tokens": 7949, "completion_tokens": 10, "depth": 0.5, "seconds": 0.61, "prompt_tokens_per_second": 13110.4, "answer": "violet-harbor-7341", "found": true} $ python3 scripts/longctx.py $TPU_URL 120000 0.1 {"prompt_tokens": 124949, "completion_tokens": 10, "depth": 0.1, "seconds": 9.53, "prompt_tokens_per_second": 13116.9, "answer": "violet-harbor-7341", "found": true} $ python3 scripts/longctx.py $TPU_URL 120000 0.5 {"prompt_tokens": 124949, "completion_tokens": 10, "depth": 0.5, "seconds": 9.54, "prompt_tokens_per_second": 13102.3, "answer": "violet-harbor-7341", "found": true} $ python3 scripts/longctx.py $TPU_URL 120000 0.9 {"prompt_tokens": 124949, "completion_tokens": 10, "depth": 0.9, "seconds": 9.41, "prompt_tokens_per_second": 13274.6, "answer": "violet-harbor-7341", "found": true} $ python3 scripts/longctx.py $TPU_URL 140000 0.5 HTTP 400: {"error":{"message":"This model's maximum context length is 131072 tokens. However, you requested 16 output tokens and your prompt contains at least 131057 input tokens, for a total of at least 131073 tokens. ..."}} The passphrase comes back at 10%, 50% and 90% depth of a 124,949-token prompt, in 9.4 to 9.5 seconds each, about 13,100 prompt tokens a second as timed from the client. vLLM's own log reads 12,495 tokens a second, a 0.0% prefix cache hit rate, and 38 to 39% of the KV cache in use while the request runs. A prompt past the limit is refused before it reaches the chip. --- 🔎 Tip: Raise the Context on the Chip You Have Flex-start capacity is granted per request. Replacing the container keeps the node, and its 4-hour clock, and costs one engine start, 503.54 s on this v5e. Deleting the node to relaunch it means a new flex-start request, and a new wait for capacity. --- Step 4 — Precision: What the Chip Computes A smaller format saves memory and bandwidth on every chip. It saves compute only where the matrix unit multiplies that format natively. | Format | v5e | v6e | v5p | TPU7x | |---|---|---|---|---| | bf16 | native | native | native | native | | int8 | 2x bf16 | 2x bf16 | native | native | | fp8 | storage | storage | bf16 rate | 2x bf16 | | int4 | storage | storage | storage | storage | "Storage" means the weights widen to bf16 before every matrix multiply. On v5e that ordering shows up directly in E2B throughput at one stream: | E2B format on v5e-1 | tok/s | |---|---:| | bf16 | 144 | | 4-bit QAT + 4-bit embeddings | 150 | | fp8 | 182 | | fp8 + 4-bit embeddings | 203 | | int8 W8A8 | 220 | | int8 + 4-bit embeddings | 243 | Accuracy holds: 0.683 for bf16 against 0.677 for int8 with 4-bit embeddings, on a 3,880-record suite. At 12B, fp8 serves 518 tok/s at 16 streams against 624 for int8 at the same file size. On v6e the 4-bit path pays off through a fused kernel. Gemma 4 26B-A4B in 4-bit QAT W4A16 serves 1,283 tok/s at 16 streams against 668 for fp8, in 17.43 GiB of weights against 27.99 GiB, and holds 53,888 KV-cache tokens against 3,456. Accuracy is 75.3% against 76.0%. W4A16 needs tpu-inference PRs #3299, #3653 and #3660 on top of the stock image. --- Step 5 — Memory and Bandwidth Each generated token reads every weight once, so bandwidth sets the ceiling for one request. | Chip | Memory | Bandwidth | E2B bf16 tok/s, 1 stream | tok/s per TB/s | |---|---:|---:|---:|---:| | TPU v5e | 16 GB | 859 GB/s | 120 | 140 | | TPU v6e | 32 GB | 1,638 GB/s | 199 | 122 | | TPU v5p | 95 GiB | 2,765 GB/s | not measured | — | | TPU7x | 192 GiB | 7,380 GB/s | not measured | — | | NVIDIA L4 | 24 GB | 300 GB/s | 46 | 154 | The three measured chips land between 122 and 154 tokens a second per TB/s. Google quotes v5e bandwidth as 800 GiBps, which is 859 GB/s. The L4 figure was measured on an AWS g6.2xlarge, the same chip as Google Cloud's g2. --- Step 6 — Topologies and Slices Weight size picks the slice. Every Gemma 4 size serves on one host: | Model | v5e-1 | v6e-1 | v6e-4, TP=4 | |---|---|---|---| | E2B | bf16 or int8 | bf16 | — | | E4B | int8 or 4-bit; bf16 does not fit | bf16 | — | | 12B | int8 or 4-bit | bf16, 22.18 GiB | — | | 26B-A4B | 4-bit, 201 tok/s | 4-bit, 1,283 tok/s | bf16, 1,982 tok/s | | 31B | does not fit | 4-bit, 500 tok/s | bf16, 2,031 tok/s | Small models scale out. On a v5e-4, four one-chip E2B engines serve 1.39x the output of one TP=4 engine at 16 streams, 2.52x at 64 and 3.36x at 256. E2B has one KV head, so tensor parallelism keeps four copies of the KV cache. 12B gains from TP=4: 2.25x the v5e-1 output at one stream, and 122,624 KV tokens against 9,728. --- 🔎 Tip: Quota Lets You Ask, Availability Decides Quota is permission to request a TPU. Availability decides whether the request gets hardware, through three checks: hardware in the zone, the provisioning model offered there, and capacity at that moment. 37 zones report v6e quota; 18 offer v6e-1 hardware. Three of five zones holding 1,536 chips of quota returned stockout. us-central1-b has hardware, quota and a flex-start price, and rejects flex-start. Check capacity with a spot request: it fails fast with "reason: stockout", where a flex-start request with no quota queues as PENDING exactly like a stockout. Ask for fewer chips. In europe-west4-a a v6e-8 flex-start request expired after 26 minutes, and a v6e-4 was granted at once less than an hour later. --- Step 7 — Against Other Accelerators On the same 26B 4-bit checkpoint, with the same flags and client: | | NVIDIA L4 | TPU v6e-1 | |---|---:|---:| | Output tok/s, 16 x 256 tokens | 393.7 | 1,283.3 | | KV cache tokens | 17,990 | 53,888 | | 3,880-record suite, wall time | 139 s | 58 s | | 4-bit kernel | Marlin | gmm_v2 | | Software | stock vLLM 0.30.0 | three tpu-inference patches | | List price per hour | $0.71 (g2-standard-4) | $2.70 | Cloud Run serves the L4 as a managed service: one L4 per instance, weights mounted from Cloud Storage at /mnt/models, a startup check that waits 180 s before its first check, and a default that scales between zero and one instance, so an idle service means a cold GPU start. Blackwell on Google Cloud lists above v6e per accelerator-hour: an RTX PRO 6000 on g4-standard-48 at $4.50 on-demand and $2.25 flex-start, and a B200 at $8.06 per GPU flex-start ($64.44 for the 8-GPU a4-highgpu-8g, divided by 8). Both are list prices only, with no measurements in this article. Per dollar, on E2B bf16 with vllm bench serve at 1,024 prompt and 128 output tokens, v5e at its $0.58 spot price leads to 16 streams and the MI300X at $1.99 leads at 64. The v6e-1 is priced at the europe-west4 on-demand rate of $2.97: | tok/s per $/hr | MI300X ($1.99) | v5e-1 spot ($0.58) | v6e-1 europe-west4 ($2.97) | |---|---:|---:|---:| | 1 stream | 153 | 208 | 67 | | 4 streams | 487 | 711 | 233 | | 16 streams | 1,333 | 1,572 | 587 | | 64 streams | 2,713 | 1,972 | 719 | Each software stack fails in its own way: | | TPU, vLLM + JAX | CUDA, L4 | ROCm, MI300X | Neuron, inf2 / trn1 | |---|---|---|---|---| | Start to healthy, E2B | 510 s v6e, 986 s v5e | 334 s | about 160 s | 171 s on trn1 | | Gemma 4 support | 4-bit needed patches | 26B 4-bit loads stock | fp8 only as fnuz | vLLM-Neuron cannot load it | | Hazard | OOM after a long compile | sm_89 missing; runs sm_86 code | AITER flag 0.97 to 0.99x | large gather returns zeros, HTTP 200 | --- 🔎 Tip: Cache the Compile XLA compile is 81 to 91% of vLLM's engine start for E2B: 329 of 404 seconds on v6e-1 and 738 of 814 on v5e-1, inside 510 and 986 seconds from container start to healthy. On today's v5e-1 with vllm/vllm-tpu:v0.30.0 the engine start was 503.54 seconds, 413.41 of them compile. A mounted compile cache cut a v5e restart from 857 to 497 seconds, 42%. 31B weights load in 5.6 minutes from in-region Cloud Storage against 60 to 90 from Hugging Face. Serving flags that work on GPUs fail late on TPU, after the compile. --gpu-memory-utilization 0.95 failed on v5e after a 691-second compile, twice; 0.92 serves. --kv-cache-dtype fp8 logs fp8 on v6e and allocates bf16. The vllm/vllm-tpu:nightly tag broke two 4-bit builds on 2026-10-02, so pin an image digest. --- Step 8 — What Each Chip Costs | $ per chip-hour | On-demand | Flex-start | Calendar | 1-yr CUD | 3-yr CUD | Spot read | |---|---:|---:|---:|---:|---:|---:| | v5e | $1.20 | $0.60 | $0.84 | $0.84 | $0.54 | $0.58 | | v6e Trillium | $2.70 | $1.35 | $1.89 | $1.89 | $1.22 | $1.40 | | v5p | $4.20 | $2.10 | $2.94 | $2.94 | $1.89 | — | | TPU7x Ironwood | $12.00 | $6.00 | $8.40 | $8.40 | $5.40 | — | Flex-start halves on-demand on every generation and can undercut spot: v6e in us-east5 lists $1.35 flex-start against a $1.40 spot price. Flex-start runs 10 minutes to 7 days and bills until deleted. Utilization sets the price per token. A v6e-1 at the europe-west4 on-demand rate of $2.97 an hour serves E2B at $4.14 per million output tokens with one stream, $0.70 with eight and $0.55 with 32, at 1,024-token prompts. A v5e at the $0.60 flex-start rate goes from $1.31 per million at one stream to $0.09 at 64, at 128-token prompts. --- Compare and Contrast | Option | Precision served | Bandwidth | Measured result | List $/hr | Software | |---|---|---:|---|---:|---| | 🥇 TPU v6e-1 | 4-bit QAT W4A16 | 1,638 GB/s | 26B at 1,283 tok/s | $2.70 | tpu-inference, patched | | 🥈 TPU v5e-1 | int8 W8A8 + 4-bit embeddings | 859 GB/s | E2B at 243 tok/s, one stream | $1.20 | tpu-inference | | 🥉 NVIDIA L4 | 4-bit QAT | 300 GB/s | 26B at 394 tok/s | $0.71 | stock vLLM | | TPU7x Ironwood | fp8 native | 7,380 GB/s | not measured | $12.00 | allowlisted | | Blackwell B200 | fp4 native | — | not measured | $8.06 flex, per GPU | not measured | --- So, Which One? For E2B or E4B at the lowest cost, run v5e-1 in int8 with one engine per chip, on spot or flex-start. For 12B to 31B on one chip, run v6e-1 in 4-bit QAT. For bf16 26B or 31B with long context, run v6e-4 with TP=4. For stock software and the fastest boot, run the L4, on Compute Engine or on Cloud Run. TPU7x brings fp8 math and 192 GiB per chip, behind an allowlist. Whichever you pick: check capacity with a spot request, ask for the smallest slice that fits, pin the image digest and mount the compile cache. --- Summary The goal of this article was to serve Gemma 4 on a Cloud TPU in one command, then explain the results across TPU generations through native precision, memory bandwidth, slice shape and capacity. The key to the solution was measuring each claim on real hardware, with every ratio and price computed in code. The results were: - 🟢 One v5e chip holds Gemma 4 E2B's full 131,072-token context: a 321,280-token KV cache beside 8.97 GiB of bf16 weights, and a passphrase found at 10%, 50% and 90% depth of a 124,949-token prompt in 9.4 to 9.5 s. - 🟢 The agent sends the right filter: 3 of 3 runs on the v5e called count_ids with id >= 10 and quoted 8. - 🟢 int8 is the fast format on v5e: with 4-bit embedding tables it serves E2B at 1.7x bf16, with accuracy within a point. - 🟢 v6e-1 serves 3.3x the L4's tokens on the same 26B 4-bit checkpoint, with 3x the KV cache. - 🟢 Flex-start halves on-demand on every generation, and can undercut spot. - ⚠️ Cold start takes 8.5 to 16.4 minutes, most of it XLA compile; cache it. - 🟢 One-stream decode follows bandwidth: 122 to 154 tok/s per TB/s on v5e, v6e and the L4. - ❌ Quota is permission to request; availability decides the grant: 37 zones report v6e quota, 18 have v6e-1 hardware, and zones holding full quota still return stockout. Scope: the hands-on steps ran on one flex-start v5e-1 in us-west4-a on 2026-10-25, google/gemma-4-E2B-it in bf16 on vllm/vllm-tpu:v0.30.0, three demo runs and one long-context request per depth. The rest covers Gemma 4 E2B to 31B on Cloud TPU v5e and v6e, measured on the gemma4-dev test setups between July and October 2026 with vLLM and tpu-inference; L4 on Google Cloud g2 and AWS g6; MI300X on DigitalOcean; Inferentia2 and Trainium on AWS. The cross-vendor tokens-per-dollar table mixes vLLM builds, hosts and repeat counts, so it shows direction. v5p, TPU7x and Blackwell figures are Google's published specifications and list prices, read on 2026-10-12 and re-read on 2026-10-25 with the same values; spot prices come from the Cloud Billing Catalog on recorded dates. The strategy for serving Gemma 4 on Google Cloud from v5e to Ironwood was validated with an incremental step by step approach. --- References - gemma4-dev | GitHub - gcp-inference | GitHub - tpu-survey, long-context test and evidence | GitHub - Gemma 4 E2B-it | Hugging Face - Inference on Google Cloud slides | Google Slides - Inference on AWS slides | Google Slides - tpu-inference | GitHub - Cloud TPU documentation | Google Cloud - TPU7x (Ironwood) | Google Cloud - Cloud TPU pricing | Google Cloud - Accelerator-optimized machine pricing | Google Cloud - In-Datacenter Performance Analysis of a Tensor Processing Unit | arXiv

    8 reactions · 1 comment
  8. PyTorch-native TPU back end for vLLM | Discussion

    1 like
  9. PyTorch-native TPU back end for vLLM vllm-project/vllm-torchtpu

  10. PyTorch-native TPU back end for vLLM comments · posted on 2026.10.19 at 18:34:38 (c=0, p=4)

Trending awards, every completed day and week
Gold
01st place finishes
Silver
22nd place finishes
Bronze
33rd place finishes
Awarded repositories
1Owned by vllm-project now

Repositories

Ordered by gold, then silver, then bronze.

  1. vllmBest: 2nd by stars, 991 stars023

A repository earns an award for finishing first, second or third in a completed trending day or week, ranked by new stars, new forks or pull request authors. Awards follow a repository through renames and transfers and count toward its current owner. Weeks run Monday to Sunday, UTC.