Qwen3.8-27B-Uncensored IQ4_XS(15.3 GB,JonathanColetti repo,MTP 頭保留)· vLLM 1.2.1 SM70 fork · TP4 V100-SXM2-32GB · KV turboquant_2bit_nc · max-model-len 66560 · util 0.60 · block 32 · max-num-batched-tokens 65536 · seqs 64 · capture [1,2,4,8,16,32,64] · prefix caching 開 · flash_attn_version 2 · decode partition 1024 · 自寫 batched-MMVQ 與 tile-MMQ 在 4/4 worker arm · KV pool 2,170,595 token · 不開推測
| 併發 | 每人 tok/s | 合計 tok/s | TTFT p50 |
|---|---|---|---|
| 1 | 68.15 | — | 1.52s |
| 8 | 35.3 | 155.3 | 0.28s |
| 16 | 18.8 | 186.7 | 0.46s |
| 32 | 15.3 | 246.7 | 3.69s |
| 48 | 11.4 | 314.7 | 4.37s |
| 64 | 10.0 | 356.9 | 2.57s |
32 以上那三列是為了找天花板量的,不是產品承諾 —— Wayne 定的上限是八個併發使用者。留著是因為它們回答了「還有沒有餘裕」(有,而且到 64 都沒轉折),但對外要引用的是 c8 那一列。席次已改回 8、capture 改回 [1,2,4,8],省下的記憶體回到 KV pool。
| 提示長度 | 暖 TTFT | 解碼 tok/s |
|---|---|---|
| 258 | 0.25s | 67.52 |
| 3,618 | 1.04s | 65.55 |
| 9,618 | 3.17s | 62.24 |
| 21,618 | 6.21s | 56.36 |
| 43,218 | — | 48.46 |
| 64,818 | 23.09s | 42.73 |
| 八個人同時,每人一份 21.6K 文件 | 牆鐘 | 每人延遲 | 合計 tok/s |
|---|---|---|---|
| 八份不同的文件(沒有共用前綴) | 54.8s | 54.7 – 54.8s | 18.70 |
| 八份逐位元組相同的文件(先暖過) | 55.0s | 54.9 – 55.0s | 18.63 |
兩列差 0.2 秒。八個人問同一份文件,不會因為前綴相同而變快 —— 3.6 上量到的這件事,在 3.8 加上 bt 65536 之後仍然成立。對外不能承諾「大家問同一份手冊會比較快」。
合計 18.7 對短提示八人的 155:長 context 併發是八倍代價,時間幾乎全在 prefill。
但有一件事變好了:3.6 那次是嚴格排隊(一人 31 秒、最後一個 245 秒),現在八個人並行前進、延遲差距 0.3 秒。兩次文件大小不同不能比秒數,可比的是排隊變成並行 —— 「大家一起等 55 秒」比「有人 31 有人 245」好得多。
448×448 四色象限加中央黑方塊,六項全中:左上紅、右上綠、左下藍、右下黃、中央黑方塊,連「跨在四個象限交界上」都答對。只能走 llama.cpp(vLLM 的 GGUF 路徑不吃 mmproj),而且必須 --image-min-tokens 1024 —— 不加時 96×96 只換到 81 個 prompt token,加了之後 1,103。
64,818 token 下解碼 42.73,而 Codex 用他自己的探針在 65,536 下量到 42.8。兩個人、兩套工具、兩種量法。這比任何單邊的數字都可信。
提示放大 250 倍,解碼只掉 37%。意思是「單人 68」這個數字對真實工作負載不會嚴重高估 —— 32K 文件下仍有 56,64K 下仍有 43。
冷 TTFT 那一欄只有最後一列可信。六個長度是巢狀前綴,量到 43K 時前面 21K 早在快取裡。破綻是 43,218 的冷 TTFT(4.63s)低於 21,618 的(6.24s)。這個陷阱本頁 8/7 就寫過,我今天又踩了一次 —— 差別是這次在下結論之前抓到。暖 TTFT 與解碼不受影響。真正的冷 64K 是 90.71 秒,對 Codex 原設定的 162.6 秒。
fork 的 mul_mat_vec_q 把 token 位置放在 blockIdx.y,所以每個位置各開一組 block、各自把 45 MiB 的權重讀過一遍。M=2 就是讀兩次。
同一顆 kernel,無推測 2489 次 launch / 42.24 ms,有推測 2468 次 / 67.55 ms —— 次數幾乎相同、時間多 60%。那不是「呼叫變多」,是「每次呼叫做了兩倍的搬運」。
| M | fork 每位置 ms | 批次版 每位置 ms | 總時間倍數 |
|---|---|---|---|
| 1 | 0.086 | 0.084 | 1.02x |
| 2 | 0.079 | 0.055 | 1.45x |
| 4 | 0.076 | 0.038 | 2.01x |
| 8 | 0.074 | 0.032 | 2.34x |
fork 那一欄幾乎是平的 —— 完全沒有重用。M=1 持平代表可以無條件取代,不傷單人路徑。九種 IQ4_XS 權重形狀 × 每個 M,誤差與原 kernel 一致到印出的精度(兩者共用同一套 q8_1 activation 量化)。
| 併發 | 改前 | 改後 | |
|---|---|---|---|
| c1 | 67.89 | 69.09 | +1.8% |
| c2 | 48.52 | 57.27 | +18% |
| c4 | 36.74 | 48.47 | +32% |
| c8 | 20.58 | 32.31 | +57% |
| c8 合計 | 104.7 | 143.3 | +37% |
異質混合工作負載、同 session、只切一個環境變數,四臂工具呼叫閘門全 ALL_PASS。
| 時間 | 速率 p50 | TTFT p50 | 失敗 | GPU 最大 MiB |
|---|---|---|---|---|
| 5m | 47.04 | 0.65s | 0 | 16450 |
| 15m | 46.78 | 0.65s | 0 | 16678 |
| 30m | 46.75 | 0.64s | 0 | 16678 |
| 45m | 46.89 | 0.66s | 0 | 16678 |
| 60m | 46.98 | 0.64s | 0 | 16678 |
四併發、12 個窗口、速率變動不到 1%、零失敗、一小時負載後工具閘門仍 ALL_PASS。記憶體 15688 → 16678 MiB 全部發生在前 15 分鐘,之後九個窗口完全持平 —— 那是 caching allocator 遇到新的 batch 形狀後安頓,不是洩漏。
第一次:啟動器拒絕交付,因為看不到 IQ4XS_BMMVQ_READY。kernel 是好的 —— 二十分鐘前同一個 build 才量到 c8 35.71 —— 但讓它出聲的補丁寫好、傳上去、忘了執行。閘門的行為是對的:它拒絕的不是「慢」,是「無法確認快的那條有在跑」。
第二次:記憶體規則把配置器的正常擴張判成洩漏(單一窗口 500 MiB 就判死),而暖身只有四個字、壓測最長的提示有九十段填充文字。改成基準取第二個窗口之後、要連續三個窗口都漲且總量超過 1 GiB。同時工具閘門硬寫 fable-9b,對上掛成 fable-27b 的伺服器吃 404 —— 跟健康無關。
模型 Qwen3.6-27B-Fable-Fus-711-UnHeretic-NM-DAU-NEO-MAX-NEO-LOW-MTP · 量化 IQ4_XS · 拓樸 TP4(四張 V100-SXM2-32GB,NVLink NV2 全網) · KV turboquant_2bit_nc,block 32,prefix caching 開 · max-model-len 65536,max-num-batched-tokens 16384 · max-num-seqs 32,util 0.50 · capture [1,2,4,8,16,32,48,64] · MTP draft=1 · VLLM_GGUF_IQ4XS_MMQ=64
結果:KV pool 2,513,305 token,64K 請求下 38.35 倍併發餘裕,每卡 16.4 GB,decode graph 7/7,工具呼叫閘門 ALL_PASS(works via auto)。
| 併發 | 同構 每人 | 同構 合計 | 異質 p50 | 異質 p95 | 異質 合計 | TTFT p50 |
|---|---|---|---|---|---|---|
| c1 | 30.19 | 28.2 | 30.50 | — | 27.3 | 0.28s |
| c8 | 15.06 | 103.2 | 15.06 | 18.08 | 61.6 | 0.51s |
| c16 | 12.54 | 187.5 | 12.41 | 13.79 | 101.2 | 1.12s |
| c32 | 8.99 | 266.1 | 9.22 | 10.05 | 136.7 | 1.80s |
異質那組是 12 種不同提示、錯開到達、輸出長度 100–400,32 個請求全數完成零失敗。每人速率兩者差不多,合計卻差 1.9 倍 —— 真實流量的請求長度不一,尾巴會拖長 wall-clock,而同構測法讓所有請求同時開始、同時結束。先前所有併發數字都該讀成同構上界,這是那句話的量化版本。
先前每個併發都量到「剛好 80.0%」,那是同一個提示複製 N 份、temperature 0、同一個 seed 的產物 —— 一條序列的數字印了五遍。真實混合工作負載下是 51.3 / 55.1 / 56.2%,而且隨併發上升。離對照組的逐位置 97/95/91% 還有距離,那個缺口是 KV 位元寬(2-bit 對 3-bit),不是這裡的問題。
c32 最慢的那筆是 qa_long_ctx 的 2.66 tok/s —— 長 context 請求是尾巴,不是平均。
伺服器自報 GPU KV cache size: 17,794,448 tokens、Maximum concurrency for 65,536 tokens per request: 271.52x —— KV 空間可以撐 271 個滿長度請求,而我們設 max-num-seqs 8。那不是記憶體限制,是人為上限。(這也順帶關掉一條路:DeepSeek V4 那套 CSA/HCA 序列軸壓縮是為了把 KV 壓到能落 disk,我們的 KV 已經過剩 271 倍,壓它換不到任何東西。)
| 併發 | seqs=8 每人 | seqs=32 每人 | seqs=8 合計 | seqs=32 合計 | 合計增益 |
|---|---|---|---|---|---|
| 1 | 123.2 | 123.5 | 71.4 | 71.5 | — |
| 8 | 68.4 | 68.3 | 261.9 | 286.0 | +9% |
| 16 | 67.8 | 55.4 | 228.8 | 403.2 | +76% |
| 32 | 67.7 | 36.9 | 227.0 | 619.0 | +173% |
seqs=8 時 c16、c32 的每人速度紋風不動(67.8 / 67.7),因為超過 8 的請求只是在等。放寬到 32 之後,總吞吐在 c32 從 227 升到 619,2.7 倍。
但這是取捨不是純賺:每人從 67.7 掉到 36.9,同時服務的人數變四倍。佐證在 chk 欄(decode×併發 ÷ 總吞吐):從 9.55 降到 1.91 —— 牆鐘時間真的花在解碼而不是排隊了。
所以要單人最快就壓低併發(123.5),要總產能最大就放寬上限(619)。兩者不可兼得,取決於這台機器要服務幾個人。已套用 max-num-seqs 32(2026-08-06,commit 8aed933):Wayne 給的使用者上限是 16,而 32 在 c1–c8 量到與 8 完全相同,所以它只會在人多時起作用,不會犧牲單人速度。
9B 的模型檔是 6.5GB,TP4 之下每卡約 1.6GB 權重,但 nvidia-smi 每卡讀到 29GB。差額幾乎全是 --gpu-memory-utilization 0.90 預留的 KV pool:17,794,448 tokens,而 maxlen 65536 × max-num-seqs 8 最多只用得到 524,288 —— 過剩 33.9 倍。
| ctx / 併發 | util 0.90 decode/s | util 0.30 decode/s | 差 |
|---|---|---|---|
| 200 / c1 | 123.6 | 123.0 | −0.5% |
| 200 / c4 | 79.8 | 79.7 | −0.1% |
| 200 / c8 | 68.1 | 68.2 | +0.1% |
| 28000 / c1 | 106.0 | 106.0 | 0.0% |
| 28000 冷 TTFT | 6.58s | 6.58s | 0.0% |
全部落在噪音以內,所以 0.90 → 0.30 已進生產:每卡 29.0GB → 9.5GB,四張卡共釋出約 78GB。KV pool 降到 4,319,677 tokens,仍是滿長度請求需求的 65.91 倍。驗收:health 200、工具呼叫 ALL_PASS。回滾:cp fable9b_start.sh.bak_util fable9b_start.sh 後重啟。
我原本的理由是釋出記憶體之後實驗不必再停生產。那個理由不成立 —— 擋住並存的不是記憶體,是 PSU:先前疑似燒掉電源的正是「四張卡上同時載入兩個模型」的功耗尖峰,記憶體有餘裕並不解除這條紀律。
真正換到的是故障半徑。先前一次沒收乾淨的實驗留下四個 VLLM::Worker 孤兒各佔 29GB,生產重啟每次都撞 Free memory 2.83 GiB < 28.56,在 Restart=always 之下變成 34 分鐘的失敗迴圈。現在生產只需要 9.5GB 就能起來,同樣的孤兒擋不住它。
第一版 probe 一個臂都沒跑完就結束:local u=$1 label="util${u/./}" —— bash 在 local 賦值之前就展開了整行所有的詞,所以 ${u/./} 展開時 u 還沒有值,在 set -u 之下函式直接失敗,EXIT trap 23 秒後把生產放回去。第二版順便改了方法:生產本身就是 0.90 那個臂,直接量它、再改一個值重啟,停機從兩個 20 分鐘窗口降到兩次 4 分鐘重啟,而且量到的就是要上線的東西,不是它的手打近似版。
三個嫌疑各做一臂。--enforce-eager 得到 10.3 tok/s(基準 123.6)—— CUDA graph 每 token 省掉約 89 毫秒的 launch 成本,而且 log 顯示解碼是 Capturing CUDA graphs (decode, FULL),單一完整 graph,所以「分段啟動成本」這條也死了。意外的是第三臂:關掉 VLLM_SM70_QWEN_GDN_FULL_FORWARD 反而快。
| 指標 | GDN=1(原生產) | GDN=0 | 差 |
|---|---|---|---|
| c1 decode/s | 123.4 / 123.3 | 153.1 / 152.8 | +24.0% |
| c8 decode/s | 68.0 / 68.0 | 76.3 / 76.8 | +12.5% |
| c8 總吞吐 | 272.8 / 273.3 | 275.7 / 272.7 | 持平 |
| 退化指標 | 1.00 | 1.00 | — |
| 工具呼叫 | ALL_PASS | ALL_PASS | — |
A/B 用的是生產的完整配置(maxlen 65536、max-num-seqs 32、TP4、turboquant 2-bit KV),每臂跑兩趟。上線後再驗兩趟:c1 152.6 / 152.9、c8 76.5 / 77.1、deg 1.00、ALL_PASS。
性質要說清楚:這是單人延遲的改進,不是總產能的 —— c8 的總吞吐兩邊都是 273 左右。回滾:cp fable9b_start.sh.bak_gdn fable9b_start.sh 後重啟。
第一次套用時量到 c8 只有 14.6 tok/s,我判定「探針配置沒轉移到生產」並退回。那是一個沒有重複驗證的樣本 —— 它的冷 TTFT 是 0.43,而其他每一次 run 都是 0.16,典型的剛重啟未暖。在生產完整配置上各跑兩趟之後,+24% 完全重現。凡是要決定生產變更的量測,一律每臂兩趟,這條從今天起是預設。
順帶修正前一節的模型:T(n) = 6.02 + 8.32/n 是在融合路徑開著時擬合的。關掉之後一個 token 是 6.53 ms —— 那個看起來不可縮減的固定成本,有一大塊其實就是這個旗標。
| context / 併發 | GDN=1(兩趟) | GDN=0(兩趟) | 差 |
|---|---|---|---|
| 200 / c1 | 54.3 / 54.3 | 65.3 / 66.5 | +21.4% |
| 200 / c8 | 28.0 / 28.1 | 30.8 / 31.3 | +10.7% |
| 28000 / c1 | 45.9 / 46.4 | 54.2 / 54.2 | +17.4% |
| 28000 / c8 | 17.0 / 17.0 | 21.7 / 21.6 | +27.4% |
| 28000 / c8 總吞吐 | 47.4 | 72.7 | +53.7% |
先前它們被報成 only 0 content deltas 並被我當成模型失敗。那是量測工具數錯欄位(見前一節),把 reasoning 補上、max_tokens 提到 512 之後就有數字了。27B 不在生產上,所以沒有東西要套用 —— 但之後任何一次跑 27B,這個旗標都該是關的。
28000 / c1 的退化指標 GDN=1 是 1.00、GDN=0 是 0.61,看起來變差。但同一格的 think% 是 100 —— GDN=0 那臂把整個 512 token 預算都花在思考上,所以指標評的是思考文字而不是答案,而思考文字本來就比較重複。這是量測混淆,不是已證實的品質退化,但我也不能宣稱兩邊等價。要判定得把思考與答案分開評分,探針還沒有這個能力。
可判別的正確性檢查(複誦、質數序列、三個工具呼叫、toolcall_tests)兩臂全過,與 9B 一致。
今天有六次「反常結果」最後都是量測方法的問題,不是被測物。零散修補會留下不同批次混用的數字,所以整個矩陣用同一套工具重跑一次:4 個配置 × 3 種 context × 5 種併發。
關鍵改變:prefill 與 decode 分開量。先前的每 token 速率是 tokens / 總耗時,把一次性的冷 prefill 攤進了每 token 成本。現在用串流,TTFT 與間隔中位數各自成欄,冷暖也分開,並附伺服器自報的 prefix 命中率。
| 配置 | context | 併發 | 冷 TTFT | 暖 TTFT | decode tok/s | 總吞吐 | prefix | 退化 |
|---|---|---|---|---|---|---|---|---|
| 9B · TP4 | ~200 | c1 | 0.23s | 0.16s | 123.1 | 70.8 | 0% | 0.91 |
| c2 | 0.23s | 0.32s | 103.1 | 92.3 | 0% | 0.91 | ||
| c4 | 0.23s | 0.33s | 79.7 | 140.1 | 0% | 0.87 | ||
| c8 | 0.23s | 0.36s | 67.9 | 286.6 | 0% | 0.91 | ||
| c16 | 0.23s | 0.96s | 68.0 | 199.2 | 0% | 0.83 | ||
| 9B · TP4 | ~8K | c1 | 1.13s | 0.76s | 117.9 | 26.7 | 57% | 0.91 |
| c2 | 1.13s | 1.51s | 97.1 | 58.8 | 57% | 0.80 | ||
| c4 | 1.13s | 2.85s | 73.8 | 46.9 | 57% | 0.82 | ||
| c8 | 1.13s | 4.43s | 60.9 | 73.8 | 57% | 0.81 | ||
| c16 | 1.13s | 6.43s | 58.9 | 70.8 | 57% | 0.79 | ||
| 9B · TP4 | ~28K | c1 | 8.93s | 0.57s | 106.2 | 32.5 | 98% | 0.92 |
| c2 | 8.93s | 1.05s | 83.3 | 41.1 | 98% | 0.92 | ||
| c4 | 8.93s | 1.86s | 63.4 | 105.0 | 98% | 0.78 | ||
| c8 | 8.93s | 3.28s | 45.9 | 105.1 | 98% | 0.79 | ||
| c16 | 8.93s | 3.89s | 44.2 | 101.7 | 98% | 0.74 | ||
| 9B · TP2 | ~200 | c1 | 0.29s | 0.22s | 96.2 | 58.8 | 0% | 0.93 |
| c2 | 0.29s | 0.44s | 72.0 | 72.4 | 0% | 0.93 | ||
| c4 | 0.29s | 0.40s | 57.4 | 126.9 | 0% | 0.91 | ||
| c8 | 0.29s | 0.66s | 50.0 | 175.7 | 0% | 0.87 | ||
| c16 | 0.29s | 1.18s | 49.7 | 198.3 | 0% | 0.91 | ||
| 9B · TP2 | ~8K | c1 | 2.59s | 1.39s | 90.0 | 15.6 | 57% | 0.91 |
| c2 | 2.59s | 3.27s | 66.2 | 32.3 | 57% | 0.76 | ||
| c4 | 2.59s | 5.36s | 51.2 | 43.7 | 57% | 0.80 | ||
| c8 | 2.59s | 8.41s | 41.2 | 30.6 | 57% | 0.83 | ||
| c16 | 2.59s | 12.22s | 40.3 | 37.1 | 57% | 0.80 | ||
| 9B · TP2 | ~28K | c1 | 17.39s | 0.89s | 77.9 | 30.6 | 98% | 0.95 |
| c2 | 17.39s | 1.70s | 55.1 | 46.8 | 98% | 0.82 | ||
| c4 | 17.39s | 3.21s | 42.0 | 67.4 | 98% | 0.80 | ||
| c8 | 17.39s | 5.99s | 27.9 | 96.7 | 98% | 0.72 | ||
| c16 | 17.39s | 7.90s | 27.9 | 93.9 | 98% | 0.72 | ||
| 9B · 單卡 | ~200 | c1 | 0.44s | 0.36s | 69.7 | 40.8 | 0% | 0.93 |
| c2 | 0.44s | 0.72s | 47.7 | 42.5 | 0% | 0.91 | ||
| c4 | 0.44s | 0.59s | 39.8 | 87.5 | 0% | 0.91 | ||
| c8 | 0.44s | 0.73s | 33.0 | 138.2 | 0% | 0.91 | ||
| c16 | 0.44s | 2.00s | 32.9 | 123.5 | 0% | 0.91 | ||
| 9B · 單卡 | ~8K | c1 | 3.68s | 2.59s | 63.9 | 8.7 | 57% | 0.91 |
| c2 | 3.68s | 5.13s | 42.8 | 21.1 | 57% | 0.76 | ||
| c4 | 3.68s | 10.08s | 33.7 | 15.2 | 57% | 0.82 | ||
| c8 | 3.68s | 19.67s | 25.6 | 26.5 | 57% | 0.81 | ||
| c16 | 3.68s | 23.60s | 25.3 | 22.4 | 57% | 0.76 | ||
| 9B · 單卡 | ~28K | c1 | 33.83s | 1.57s | 53.3 | 13.2 | 98% | 0.92 |
| c2 | 33.83s | 3.10s | 34.0 | 15.8 | 98% | 0.92 | ||
| c4 | 33.83s | 5.94s | 24.2 | 32.0 | 98% | 0.82 | ||
| c8 | 33.83s | 11.43s | 18.8 | 44.4 | 98% | 0.77 | ||
| c16 | 33.83s | 14.30s | 16.4 | 43.1 | 98% | 0.80 | ||
| 27B · TP4 | ~200 | c1 | 1.30s | 0.35s | 60.4 | 52.2 | 0% | 0.76 |
| c2 | 1.30s | 0.70s | 45.4 | 73.2 | 0% | 0.70 | ||
| c4 | 1.30s | 0.70s | 32.0 | 109.3 | 0% | 0.74 | ||
| c8 | 1.30s | 0.80s | 30.9 | 208.6 | 0% | 0.70 | ||
| 27B · TP4 | ~8K / ~28K | — | 失敗待重跑:8K 回 0 個 content delta(疑似 max_tokens=128 被 think 吃光,即已知的串流空白陷阱);28K 回 HTTP 500(疑似超過我設的 maxlen 32768)。兩者皆未證實。 | |||||
這是不含 prefill 的逐字生成速度,也就是使用者實際感受到的吐字速率。先前報的 111.5 是 tokens / 總耗時,被一次性的 prefill 稀釋過;當時真正的單人天花板是 123.1。這個數字已被取代:關掉 GDN 融合之後是 152.8,見本頁上方。
| context | 單卡 | TP2 | TP4 | 單卡→TP4 |
|---|---|---|---|---|
| ~200 · 1 人 | 69.7 | 96.2 | 123.1 | +77% |
| ~8K · 1 人 | 63.9 | 90.0 | 117.9 | +85% |
| ~28K · 1 人 | 53.3 | 77.9 | 106.2 | +99% |
| ~200 · 8 人 | 33.0 | 50.0 | 67.9 | +106% |
| ~8K · 8 人 | 25.6 | 41.2 | 60.9 | +138% |
| ~28K · 8 人 | 18.8 | 27.9 | 45.9 | +144% |
TPS 的最大殺手是併發,不是 context。TP4 從 1 人的 123.1 掉到 8 人的 67.9(−45%),而同樣是 TP4、從短提示拉到 28K 只掉 14%(123.1 → 106.2)。所以要提升 TPS,該攻的是併發時的排程與批次效率。
另一個方向:卡加得越多,在越吃緊的情境越值得 —— 單卡→TP4 的增益從短提示單人的 +77% 一路升到 28K 八人的 +144%。
28K 的冷 TTFT 是 單卡 33.83s → TP2 17.39s → TP4 8.93s(3.8 倍),大於同條件下 decode 的 2.0 倍。這不影響 TPS 結論,但先前完全沒量過。暖起來後 28K TTFT 只剩 0.57s(prefix 命中 98%),所以多輪對話裡長 context 的代價幾乎只在第一次。
它是 content delta 數 / 牆鐘,而每則回應的長度本來就不一樣,所以同一列會出現非單調(例如 8K 的 c4 46.9 低於 c2 58.8)。decode tok/s 才是穩健的指標(串流間隔中位數,不受回應長度影響);總吞吐只適合看數量級。
| 配置 | 1 人 | 2 人 | 4 人 | 8 人 | 8 人合計 | 量測工具 |
|---|---|---|---|---|---|---|
| 9B · TP4 · 生產 | 111.5 | 88.8 | 64.6 | 60.5 | 393.1 | v2 |
| 9B · TP2 | 85.1 | 62.7 | 47.7 | 44.6 | 282.2 | v2 |
| 9B · 單卡 | 65.7 | 44.6 | 35.1 | 29.8 | 177.8 | v2 |
| 27B · TP4 · 8K · 捕捉集合僅 [1,2] | 56.2 | 40.9 | 5.2 | 5.1 | 40.7 | v1 · 待重量 |
先前我說「TP2→TP4 只有 +19%,all-reduce 已是主導項,再加卡不會有回報」—— 那是錯的,是拿兩套不同量測工具的數字相比得出的。四個拓樸現在都在 v2 下量過:單卡→TP2 +29.5%、TP2→TP4 +31.0%,每翻倍的增益一致。單卡→TP4 的正確數字是 +70%,不是先前報的 +61%。
c1 56.2、c2 40.9,c4 直接掉到 5.2(牆鐘 15.7s → 63.9s)——階梯狀而非漸進,根因在 log 裡:cudagraph_capture_sizes: [1, 2]。我建量測鏈時漏傳 --compilation-config 給 27B 那一臂,用了 fork 的預設,所以 c4、c8 掉出捕捉集合退回 eager。生產的 9B 設 [1,2,4,8],曲線就平滑。這不是 27B 的效能特性,是量測設定錯誤,該臂需重跑。
另外先前記錄的 27B TP4 = 38.4 是 64K context 量的,這次是 8K,兩者本來就不能直接比。
| 提示長度 | 1 人 | 4 人 | 8 人 | 16 人 | 8 人合計 |
|---|---|---|---|---|---|
| ~900 tok | 106.4 | 51.8 | 49.6 | 34.8 | 347.4 |
| ~7K tok | 40.3 | 17.9 | 9.6 | 9.1 | 89.8 |
| ~28K tok | 13.2 | 4.6 | 5.3 | 2.7 | 32.7 |
上表的每 token 速率是 completion_tokens / 總耗時,把一次性的 prefill 攤進了每 token 成本,而每次只生成 128 個 token —— 8.85 秒的冷 prefill 除以 128,就把整個數字壓垮了。那量到的不是「長 context 很慢」,是「冷啟動成本被除進了速率」。Wayne 指出 prefill 只有第一次要付,後續輪次是逐步增長的 context,這是對的。
用串流把兩段拆開重量(同一份 context 連發三輪):
| context | 輪次 | TTFT | 每 token 間隔 | decode tok/s | prefix 命中 |
|---|---|---|---|---|---|
| ~8K | r1 | 1.16s | 0.0085s | 117.9 | 0% |
| ~8K | r2–r3 | 0.75s | 0.0085s | 117.9 | 58% |
| ~28K | r1 | 8.85s | 0.0094s | 106.3 | 16% |
| ~28K | r2–r3 | 0.49s | 0.0094s | 106.3 | 98% |
decode 幾乎不受 context 長度影響:8K 117.9 → 28K 106.3,只掉 10%。在 TurboQuant 2-bit 下,28K 的 KV 相對於每步都要讀的 5.5GB 權重不算什麼,權重才是主導 —— 所以「每個 token 要重讀整份 KV 導致變慢」的說法也不成立。
prefill 則完全符合直覺:28K 第一次 8.85s,prefix cache 命中 98% 之後降到 0.49s,18 倍。實際的多輪對話裡,長 context 暖起來之後每 token 速度與短 context 相當。
這一條不受上述假象影響,因為它是同一列內的比較:短提示 c8 合計 347.4、c16 反而 332.4;28K 下 c8 32.7、c16 33.4 幾乎不動。超過 max-num-seqs 8 之後請求只是排隊,總吞吐不再增加。
舊工具的比例並不自洽 ——「每人 × 人數」與「合計」相差達 19%。查出三件事:
① 每個併發等級用的題目不同:PROMPTS[i % 8] 讓 c1 只跑第 0 題、c8 跑滿八題,中英文與長度都不同,曲線因此混進了「題目換了」這個變因。
② prefix caching 偏袒 c1:暖機只跑第 0 題,而那正是 c1 唯一量到的請求。
③ 兩欄不可相乘:每人取的是中位數,合計是總 token ÷ 牆鐘。
v2 讓每個等級跑相同的八題、全部預熱、三輪取中位。修正後「平均 × 人數 ÷ 合計」在 c1–c4 都是 1.00 上下,三輪離散 ≤ 0.8。
但頭條數字沒有被灌高 —— 修正後 c1 反而從 107.5 升到 109–111,因為八題全暖後平均更高。真正被破壞的是曲線形狀(c4 由 67.4 修正為 65.0)與兩欄的一致性。TP2 與 27B 兩列仍是 v1 量的,重量需要停生產的維護窗口。
那一臂在切換前執行,GPU0 還被生產佔著:Free memory on device cuda:0 (0.3/31.73 GiB) —— 排程順序問題,不是 27B TP4 的退化。先前量到的 38.4 tok/s 仍然成立。
max_tokens 太小時,推理模型會把整個額度花在思考段,串流下呼叫端會收到完全空白且無錯誤的回應(實測 200 tokens 時 4/6 空白)。請給 1024 以上。
52.5 tok/s / 每人 — 當時以隨機 token 當提示,掩蓋了輸出退化,而退化的輸出解碼更快,反而灌高數字。基準測試現已內建真實文本的詞彙重複度斷言。