iT邦幫忙

2026 iThome 鐵人賽

DAY 17
0
AI Engineering

不是模型太慢,是你沒算過這筆帳:地端 LLM 工程實戰 30 天系列 第 17

Day 17 - Top-K 開到 30 才答得準,為什麼 M4 Max 多等 67 秒、4070 Ti 只多 8 秒?

  • 分享至 

  • xImage
  •  

Day 17 - Top-K 開到 30 才答得準,為什麼 M4 Max 多等 67 秒、4070 Ti 只多 8 秒?

最近最常收到的 RAG 現場描述是這句:

Top-K 少就常常答錯,拉到 20、30 才準一點。可是 context 變成兩三萬 token,問一題要等一分多鐘。

今天分兩半:先把「多塞一個 chunk 值幾秒」算成價目表,再說為什麼那個「只好多塞」通常不是模型需要那麼多 context。

先對號入座:你的應用是哪一派

模型本體的生成時間拆得開,而且只需要兩個數字:

模型推論時間 ≈ 輸入長度 ÷ pp(輸入長度)     ← prefill
             + 輸出長度 ÷ tg(context 長度)  ← decode

pp 是讀 prompt 的速度、tg 是吐答案的速度,兩者吃的硬體不同:Day 10 拆過 decode 那側通常更偏 memory-bound,prefill 的 token 共用同一份權重,相較之下更偏 compute-bound——但 context 一拉長,attention 與記憶體那本帳也會開始變重。兩個都是 context 的函數,不是常數——這正是今天要拆的第一件事。

⚠️ 用約等號有兩個理由。一是這條式子只涵蓋模型本體:真正的 E2E 還要加檢索、rerank、tokenize、取樣、排隊與 runtime overhead,llama-bench 連 tokenization 與 sampling 都不計,Day 02 那四把尺量的才是完整那一條。

二是下面第二項一律用 tg128 代估——那是 KV 幾乎空著時的 decode,所以每個總時間都偏樂觀。

所以「這台快不快」沒有單一答案,要看你餵多少、吐多少

應用 送進模型的 input output 無 prefix cache 時 大部分 prefix 命中後
客服 FAQ 2K–8K 100–400 混合 decode
一般 chat 2K–16K 300–1K 混合 decode
企業 RAG 8K–40K 300–1K prefill/混合 混合
整份文件 QA 30K–150K 200–1K prefill 強烈主導 decode
coding autocomplete 8K–64K 10–200 prefill 強烈主導 cache 最重要
長 reasoning 2K–20K 2K–10K decode decode

(欄位是量級不是量測,用你自己的 prompt_eval_count / eval_count 對一次就知道你在哪一列。)

RAG 那一列的 input 是浮動的,而最直接的那顆旋鈕,就是最後送進 LLM 的 Top-K。

Top-K 的價目表

先說死一件事:下面的 Top-K 指的是最後送進生成 LLM 的 chunk 數,不是檢索階段先撈回來的候選數。兩者的差別是後半篇的重點。

同一顆 Gemma 4 12B QAT、同一個 llama.cpp b10488、輸出固定 500 token,chunk 抓 800 token。⚠️ 為了把硬體曲線換成 Top-K,這裡忽略 system prompt、query 與 template 的固定開銷,實際服務要以 prompt_eval_count 為準:

https://ithelp.ithome.com.tw/upload/images/20260828/20183550Bn9WNR6L71.png
圖 1:綠色那截從頭到尾一樣長,長出來的全是藍色。

Top-8(6.4K) Top-30(24K) 多等
M4 Max 128GB · Metal 24.2 s 91.2 s +67 s
RTX 4070 Ti · CUDA 10.7 s 18.7 s +8 s

⚠️ 6.4K 與 24K 沒有單獨跑測項,是拿相鄰實測點在 log₂(context) 軸上線性內插得到 pp(ctx) 再換算的——依實測曲線估算,不是直接量到的秒數(下面那兩個換邊點也是同一套方法)。

同一個決定,在這台 M4 Max 上是「多等一分鐘」,在這張 4070 Ti 上是「多等八秒」。最後送進 LLM 的那個 K 與 token 預算,是硬體與 latency SLO 相依的參數,不是抄來的預設值。

而且代價不是線性的

送進去的 token 只多 3.75 倍,M4 Max 的 prefill 卻多了 5.6 倍。差的那一截在這裡:

https://ithelp.ithome.com.tw/upload/images/20260828/20183550ezlto8mJc6.png
圖 2:越往右,兩台的 prefill 差距越大,而 Metal 這條掉得更兇。

pp 本身會隨 prompt 變長一路往下掉:T2 從 pp512 的 3794.74 掉到 pp32768 的 2169.27,少了 43%;同一段距離 T1 掉 59%。

機制上要小心一點:Gemma 4 12B 的 48 層並不是全部做 full attention,其中 40 層是 1K sliding window、只有 8 層是 full。前者的工作量比較接近 O(n×w),只有後者還帶著 O(n²) 那一項。context 一長,attention、記憶體存取與 kernel 效率一起把平均 pp 往下拖。

所以那條寫在便利貼上的除法(翻轉點 = 輸出長度 × pp ÷ tg)會估得太樂觀:它假設 pp 是常數。拿 pp512 一路外推,M4 Max 把換邊點估晚約 1.3 倍、4070 Ti 約 1.5 倍。改用逐點實測的 pp(ctx) 解,M4 Max 在 4,400 token 就換邊(大約 Top-6),4070 Ti 是 21,400(大約 Top-27)。

在這組 M4 Max + Gemma 4 12B 的帳上,RAG 幾乎一開始就在 prefill 的世界裡。

但真正該問的是:為什麼非得塞 30 個

這裡是今天的重點。檢索階段的 K,和送進 LLM 的 K,本來就該是兩個 K,多數 pipeline 把它們綁在一起了:

可以先找很多 chunks,但不代表最後要把很多 chunks 全塞給 LLM。

「Top-K 少就答錯」的五種常見病因裡,只有一種真的是模型需要更多 context:

症狀 真正的病因 該動的地方
型號、料號、錯誤碼問不準 dense embedding 分不開 PXR-450PXR-400 BM25 + dense 用 RRF 融合
Top-1 錯、Top-3 才對 chunk 邊界把一份完整證據切爛 依章節/步驟/表格切,別固定 512 token
表格、圖說裡的答案永遠找不到 轉 Markdown 時欄位、圖號、表頭掉了 表格另存 row-level 表示;保留 metadata
命中了但上下文不足 只送命中的那一小塊 parent/neighbor 展開:搜小的、送大的
證據夠了還是亂講 hallucination 拒答門檻 + 逐句對證據 verify

前四種都發生在生成模型之前。拿 Top-K 當藥吃,只是把 ingestion、chunking、retrieval 或 context construction 的問題一起埋進 prefill 帳單裡。第四種確實要更多 context,但它要的是「一個完整章節」,不是三十個彼此重複的 chunk。

https://ithelp.ithome.com.tw/upload/images/20260828/201835509idps1ALY9.png
圖 3:候選撈得多不會膨脹主 LLM 的 prompt,貴的是最後送進去的那一段。

K_retrieve 是拿 recall 買的;K_context 是拿 prefill 延遲買的。

⚠️ 所以上線時控的應該是 token 預算,不是 chunk 數。先定一個目標(例如 6K–10K),rerank 後照分數挑命中點、補齊它的父章節,塞到預算為止;只有跨章節的綜合題才准超過。這樣圖 2 那條往上飛的曲線才有天花板。

至於「答錯到底是哪一層的錯」,那是另一整篇的份量,Day 22 會一層一層拆。

這張帳的邊界

**這是紙上帳,不是端到端實測。**兩段各自量再相加,中間沒有排隊、取樣、載入,也沒讓 prefix cache 幫忙扣掉 prefill(明天的主題)。

T1 的 pp 在長 prompt 端不穩(process 內標準差 pp16384 ±14%、pp32768 ±12%),那個 +67 秒代進誤差是 +58 到 +79 秒;4070 Ti 那欄的 ± 在 0.15% 以內。⚠️ 跑 benchmark 前要清場——我第一輪就是機器上還有別的東西在跑,長 prompt 端整整低估了一到兩成。

還有一格要自己確認:掃到 32K 之前先過 Day 09 那條容量線。Gemma 4 的 40 層 sliding KV 在 1K window 封頂在 320 MiB,@32K 加上 8 層 global 的 512 MiB,全部 KV 只有 0.81 GiB,12GB 卡吃得下。那 40 層若維持同樣的 8 KV heads × 256、卻也改成 32K 全域,光這 40 層就膨脹到 10 GiB;再加原本 8 層 global,全部 KV 約 10.5 GiB——加上權重直接出局。

今天的實驗需要什麼

一台機器就能量出自己的價目表:

B=~/gguf-lab/bin-b10488/llama-b10488/llama-bench
M=~/gguf-lab/day08/gemma-4-12b-it-qat-q4_0.gguf

# Mac:每個測項各自一個 process、中間冷卻 240 秒(Day 11 驗過 240 秒就能恢復)
for P in 512 2048 8192 16384 32768; do
  "$B" -m "$M" -p $P -n 0 -ngl 99 -fa off -r 3 -o md
  [ "$P" = 32768 ] || sleep 240
done
"$B" -m "$M" -p 0 -n 128 -ngl 99 -fa off -r 5 -o md

# T2 這組測試沒觀察到明顯跑序效應,一行跑完即可
"$B" -m "$M" -p 512,2048,8192,16384,32768 -n 0 -ngl 99 -fa off -r 3 -o md

⚠️ 不要把五個 -p 串成一行在 Mac 上跑——那是同一個 process 連著燒,量到的是跑熱之後的曲線,不是上面那張圖。跑之前也先確認機器沒有其他重負載(我第一輪就栽在這裡)。

拿到 pp(ctx)tg128,把「你的 Top-K × chunk 大小 ÷ pp」跟「輸出長度 ÷ tg」畫在同一條軸上,交點就是你那台的換邊點。

小結

  • 模型推論時間 ≈ 輸入 ÷ pp + 輸出 ÷ tg,而 pptg 都是 context 的函數(檢索、排隊與 tokenize 還在外面)。先用你自己的 prompt_eval_count 對號入座,再決定要救哪一段:同一台機器,8K 時砍輸出有用,24K 時砍輸出幾乎沒感覺。
  • **Top-K 是有標價的。**Top-8 到 Top-30,M4 Max 多等 67 秒、4070 Ti 只多 8 秒;而且 token 多 3.75 倍、時間多 5.6 倍,因為 pp 自己會隨 context 往下掉。
  • **兩本帳不該共用同一個 K。**檢索候選撈 30–50 不會增加主 LLM 的 prompt_tokens;送進去的只留 3–8 個命中點加上補齊的父章節,然後控 token 預算而不是控 chunk 數。

明天 Day 18 動 prefill 這一側最大的一刀:prefix cache。算過的 KV 留著,下一個請求只要前綴相同就能跳過那段——但「相同」是逐 token 的嚴格比對,從第一個不同的 token 往後就不能沿用

一個塞在前綴前段的時間戳,就足以讓後面幾萬 token 全部 miss,而且什麼錯都不會報。

咱們明天見。


上一篇
Day 16 - 45 秒裡有 27.5 秒在自言自語:thinking mode 什麼時候該關?
下一篇
Day 18 - 只換一行位置,15.7 秒變 0.2 秒:你的 prompt 為什麼一直重算?
系列文
不是模型太慢,是你沒算過這筆帳:地端 LLM 工程實戰 30 天25
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言