昨天把 T1 對 T2 的第一場對照搭好,六根釘子釘完、兩排數字都開了,賽前那注也當場打臉,差額怎麼算留給 Day 10。昨天量的是「一個人問一句誰快」;今天照說好的,把 context 拉長、人數加上去。
在拉長之前,先接一條 Day 01 的線。那 45 秒的逐刀帳裡,有 12.6 秒是模型根本沒全跑在 GPU 上,將近三成。而「塞不塞得進去」是紙筆就能算的線——在 llama.cpp 這類支援 partial offload 的 runtime,算錯甚至不報錯,只會安靜地變慢。
這條線因此有兩種用法:查你現在為什麼慢,和算這台機器還能接幾個人。
Day 04 已經教過怎麼估權重,Day 07 又拿到 Llama-3.3-70B Q4_K_M 的實際大小 42.52 GB,換算 39.60 GiB。這台 128GB Mac 實測能給 GPU 107.52 GiB。39.6 < 107.5,塞得下,收工?
**塞得下只是入場券。權重是一次付清的房租;真正決定能坐幾個人的,是每個使用者、每個 token 都要另外墊的變動成本——KV cache。**它隨 context 線性長、隨併發整數倍放大,長 context 高併發時吃掉的顯存比權重本體還大。今天把這筆帳變成紙筆作業,順便開張本系列第一個互動工具。
自回歸生成每產一個新 token,都還得看前面所有 token。那些 token 的 Key/Value 如果不存,每一步都得重算一遍;把它們留下來,就是 KV cache。
問題也很直接:**聊得越長 KV 越大,同時聊的人越多再乘上去。**權重在載入那一刻就固定了,KV cache 卻是活的。
每 token KV(bytes)= 2 × 全注意力層數 × kv_heads × head_dim × 每元素 bytes
逐項拆開:
-ctk q8_0,每 32 個元素多帶一個 scale,實際是 1.0625 bytes。品質損失兩邊都很小,上線前拿自己的評測集驗一輪就好。這是標準 MHA/GQA 的乾淨版;碰到 MLA 或混合 attention,就得逐層拆。模型參數都在 config.json,不用猜。這裡拿 Llama-3.3-70B 當例子:80 層全注意力、8 kv_heads、head_dim 128。
每 token KV = 2 × 80 × 8 × 128 × 2 B = 320 KiB @FP16 KV
× 1 B = 160 KiB @FP8 KV
這只是單價,還要乘上工作負載:× context × 併發。@FP8 KV 一條 32K 的對話 = 160 KiB × 32,768 = 5 GiB;8 個人同時掛著 32K 就是 40 GiB,比 39.6 GiB 的權重本體還大。把它畫出來是這樣:

圖 1:KV cache 隨 context 線性長(x 軸每格翻倍,所以直線畫出來像起飛),再隨併發整數倍放大。8 條併發(藍線)在 32K 已吃掉 40 GiB,距 45.9 GiB 的預算只剩 5.9 GiB,理論撞線約在 36.7K;下一格 64K 直接翻倍爆掉。單條序列(紅線)到原生上限 128K 都還安全。
KV 預算 ≈ VRAM × 0.92 − 權重 − 約 2.8 GiB / GPU(non-KV reserve) ← 權重也先換成 GiB
併發槽 = KV 預算 ÷(每 token KV × 平均 context + 每序列固定 state)
這條是 NVIDIA 卡加 vLLM 的手算式;T1 跑 llama.cpp,沒有這個 utilization 保留,直接拿 Day 07 的實測可用空間減下去。
0.92 是 vLLM gpu_memory_utilization 的現行預設,寫死在 CacheConfig 裡。Day 03 提醒過這個值會隨版本漂移、當時顯式指定 0.9——現在兌現:預設動過了,抄舊文章的 0.9 會少算一格。2.8 GiB / GPU 則是本文拿來手算的 reserve,包含 activation 峰值、CUDA context 與框架雜項,不是 vLLM 常數。多卡要乘卡數:等一下 T4 的 TP2 扣的就是 5.6 GiB。
最容易犯的錯反而是單位。顯卡標的 96GB、80GB 按二進位 GiB 算,Hugging Face 的 checkpoint 檔案卻是十進位 GB;42.52 GB 要先換成 39.60 GiB 再相減,不然一顆 70B 憑空胖 2.9 GiB。分母那筆「每序列固定 state」是留給混合注意力模型的,等一下會看到它有多大。
還有一個限制:本文的「併發槽」只代表 KV 容量裝得下幾條 request,vLLM 自己叫它 Maximum concurrency。它不保證這些人同時進來還能守住 TTFT,那是明天的吞吐題。
容量看起來是 sizing 的事,其實也是前段最常見的延遲病因。踩雷時外觀常常只是「怎麼突然變慢了」,但不同 runtime 的處理方式不一樣,留下的證據也不同:
**一、權重塞不下:partial offload。**llama.cpp 可以把放不進 GPU 的層留給 CPU 算,所以程式照樣跑,只是突然很慢。雙通道 DDR5-5600 約 89.6 GB/s,500 GB/s 級顯卡的理論頻寬差五倍以上,Day 01 那 12.6 秒就是這一類。啟動 log 其實會告訴你有幾層沒進 GPU,只是很多人沒看。(vLLM 不這樣做,塞不下直接不給你啟動。)
**二、KV 預算太緊:排隊與 preemption。**workload 逼近 KV 容量後會開始 waiting,嚴重時發生 preemption——被 preempt 的 request 要重做 prefill,尾延遲突然變醜,同一個問題這次兩秒下次八秒。這個很好抓:log 有 not enough KV cache space 的 warning,Prometheus 也有 counter,Day 19 接上儀表板。
**三、context 開太長:搬運量膨脹。**decode 每產一個 token 要讀的不只是權重,還有這條序列自己的 KV。70B 權重 39.6 GiB、@FP8 一條 128K 對話 20 GiB,每 token 多搬五成;用「單序列 decode 卡在記憶體頻寬」做 roofline 粗估,理論上限只剩三分之二。RAG 特別容易踩:塞十份 chunk,context 拉到兩萬以上是常態。
三條的取證方式與分診流程,Day 11 有一張表。
Day 06 那張表列過六組的每 token KV,差距很大。省 KV 的招數不多,每一種都對應公式裡的某一項:
**砍 kv_heads——GQA/MQA。**MHA 時代每個 Q 頭配一組 K/V;GQA 讓一組 Q heads 共用一組 K/V,比例各家自己定——Llama-3.3-70B 是 64 個 Q 對 8 個 KV,8:1,所以 KV 縮到 MHA 的 1/8。MQA 是極端版,一層只留 1 組。
**壓維度——MLA。**GLM-4.7-Flash 每層都是全域注意力,照理最耗,卻不是:它只存一份壓縮 latent,用時再展開,47 層 × (512 + 64) 維 × 2B ≈ 53 KiB/token。53 KiB 是 BF16 latent 的大小,看到「壓縮」不要又自己除二。
**縮範圍——sliding window。**gpt-oss 一半的層只看最近 128 個 token,那些層不再隨 context 長大,公式裡的「全注意力層數」直接砍半。

圖 2:省 KV 的三條路各改公式的一項——GQA/MQA 砍 kv_heads、MLA 壓維度、sliding window 縮層數與範圍。同代模型的每 token KV 因此差到 9 倍:Llama-3.3-70B 的 160 KiB 對 gpt-oss-120b 的 18 KiB。
四顆模型排排站,兩種精度都標出來:
| 模型 | 架構 | 隨 context 成長(FP16 / FP8) | 每序列固定 state(FP16 / FP8) |
|---|---|---|---|
| Llama-3.3-70B | GQA,80 層全注意力 | 320 / 160 KiB | 無 |
| GLM-4.7-Flash | MLA,47 層全域但只存壓縮 latent | 53 KiB latent | 無 |
| gpt-oss-120b | GQA,36 層中 18 層 sliding(w=128) | 36 / 18 KiB | 4.5 / 2.25 MiB |
| Gemma 4 12B | 48 層 = sliding ×40(w=1024) + global ×8 | 16 / 8 KiB | 320 / 160 MiB |
架構參數取自各家 config.json,右欄正是 Day 06 註明「不計」的那筆。
Gemma 4 是最容易算錯的一顆。它隨 context 成長的部分 @FP8 只有 8 KiB/token,但 40 個 sliding layer 還有每序列 160 MiB 的封頂 cache;@8K 時反而是這筆固定成本佔約七成。attention_k_eq_v=true 也不能拿來再除二——它省的是 projection 參數,而 global 層帶 partial RoPE,K 過了旋轉、V 沒有,兩份仍要各存。只盯 KiB/token,會算出漂亮但錯誤的併發數。
gpt-oss-120b 很能說明「參數量不等於 KV 壓力」。它比 Llama-3.3-70B 大七成,但 @FP8 隨 context 成長的 KV 只有 18 KiB/token,是 Llama 的 1/9。同樣放進 96 GiB 卡、同樣算 @8K,手算結果是 173 槽對 36 槽,約 4.8 倍。**參數量預測不了 KV 壓力。**標準 GQA 的成長斜率看 全注意力層數 × kv_heads × head_dim;碰到 sliding 或其他混合架構,再把 fixed state 補進去。選型時打開 config.json 算 30 秒,比只盯參數量準得多。
順便更正 Day 06:gpt-oss-120b 的 checkpoint 應該是約 60.8 GiB,當時表上那個 63.4 GB 少算了。
還有兩條路今天先掛號。Qwen3.6 的 Gated DeltaNet 走得更絕:線性注意力層不再保存隨 context 增長的 KV,改成一塊固定大小的 state。但別誤讀成「整顆 1K 和 1M 一樣大」——27B 的 64 層裡仍有 16 層全注意力(35B-A3B 是 40 層裡 10 層),那些層照樣長。它砍掉的是成長的斜率,不是成長本身。
DeepSeek-V4-Flash 我在 Day 06 說「Day 09 再拆」,今天要改口:它的 CSA + HCA 是壓縮與稀疏混著上,跟這條公式換算不對等,硬塞進同一張表只會誤導人以為兩者可比。這顆跟 Qwen3.6 一起挪到 Day 15 專章解剖。
主角回到 Llama-3.3-70B Q4(42.52 GB = 39.60 GiB),同一條公式四階逐格算:
| 階 | 機器 | 可用空間 | KV 預算 | @8K 併發槽 | 判定 |
|---|---|---|---|---|---|
| T2 | RTX 4070 Ti 12 GiB | 11.0 GiB | 負值 | 0 | 權重就塞不下 |
| T1 | M4 Max 128GB | 107.5 GiB(Day 07 實測) | 65.1 GiB | 26 @FP16、49 @q8_0 | 裝得下,長 context 見底 |
| T3 | RTX PRO 6000 96 GiB | 88.3 GiB(× 0.92) | 45.9 GiB | 36 @FP8 | 舒服 |
| T4 | H100 80 GiB ×4,權重改 FP8 | 147.2 GiB(TP2 一組) | 73.9 GiB | 59 × 2 組 = 118 @FP8 | 高併發主場 |
顯卡標示的 96GB/80GB 取名目 GiB。
nvidia-smi實報會少 1–2 GiB(ECC 與顯示佔用),要精算就以自己印出來的為準——這條規矩 Day 07 立過。

圖 3:同一顆模型、五種配置的容量結果。條長是可用空間,藍色是一次付清的權重,綠色才是 KV 預算。T4 單卡那條最值得看:權重塞進去了,KV 卻只剩 2 個 @8K 槽。
**T2:連入場券都沒有。**39.6 GiB 權重對 11 GiB 可用空間,不必再算 KV。硬跑就是上一節第一條路,換模型比調參數有用。
**T1:裝得下,別急著開派對。**llama.cpp 的 KV 預設存 FP16,@8K 是 26 槽,但一條 128K 就要 40 GiB,65.1 GiB 的預算只夠一條半。-ctk q8_0 -ctv q8_0 能把 @8K 拉到 49 槽,長 context 併發還是會見底。
T3:容量上開始像伺服器。@8K 36 槽、@32K 還有 9 槽。96 GiB 卡的價值不是跑得快,是權重放下之後還剩得多;快不快明天再算。
**T4:最有意思的一格。**Day 05 講過正解是 AWQ/FP8,但別用「70B × 1 byte」估——NVIDIA 那顆 FP8 checkpoint 本身就 67.67 GiB(只有線性層轉了 FP8,還有 21 億參數留在 BF16)。單張 H100 80GB 雖然塞得下,KV 只剩 3.1 GiB,也就是 2 個 @8K 槽。TP2 之後才跳到 59 槽,四卡兩組共 118。
今天整篇的算術我做成了互動計算機:選卡、選模型、選量化與 context,直接告訴你塞不塞得下、剩多少 KV 預算、能開幾個併發槽。
T1–T4 與 Day 06 六組裡換算得動的五組都內建,架構參數取自各家 config.json,公式與今天這篇同一支,今天算過的每一格都做成了一鍵預設。
紙筆就夠,本系列最便宜的一天。想對答案的,拿一張 N 卡起一次 vLLM,啟動 log 會印出它實際切出的 KV 空間:
vllm serve <你的模型> --kv-cache-dtype fp8 --max-model-len 8192
# 啟動 log 會直接印兩行,不用自己除:
# GPU KV cache size: ... tokens
# Maximum concurrency for 8,192 tokens per request: ...x ← 就是今天算的併發槽
# 我的 T2 單卡(12 GiB)跑一顆塞得下的模型,log 值與公式對照:【實測待填|發文前必填】
log 跟手算差一點不奇怪;差得多,就回頭查實際載入的權重、non-KV reserve、KV block rounding 與 backend 的 cache layout。公式負責 sizing,runtime log 才是最後答案。
2 × 全注意力層數 × kv_heads × head_dim × bytes 快速估;混合 attention 還可能有每序列的 fixed state。明天,Day 10:紙面頻寬 vs 有效頻寬——546 對 504 的真相。椅子擺得下不代表上菜快;Day 08 掛著的懸念——帳面同量級、數字卻反著來——用一條國中乘法,倒推出你的 GPU 實際吃到幾成頻寬。
兩條線都拿到之後,第 11 天用它們做一件更實用的事:把「我的 RAG 為什麼很慢」拆成可以逐項排除的分診表。
咱們明天見。