KVMem 在 RX 7900 XTX 24GB 上的實測報告
依 lcz.me/topic/1961(《7900 XTX 的可能性探索|单卡篇(二):KVMem 是个好东西》)的測試口徑,在本機復現並實測。
測試日期:2026-09-29
摘要
- KVMem 成功安裝並以 gfx1100 HIP 編譯完成,實測單張 7900 XTX 24GB 跑通 523,214-token prompt(完整 Prefill → Decode → 正常輸出),邏輯上下文 512K。
- VRAM 峰值僅 17.4–19.8 GiB,與原文一致;上下文從 128K 擴到 512K(4 倍),VRAM 幾乎不成長(代價轉到 Host RAM,512K 需約 20–22 GB RSS)。
- KVMem 不只省 VRAM,也比 Full-KV 快:128K 下 prefill 快 ~35%、decode 快 ~60–90%;Full-KV 在 128K 已吃掉 22,478 MiB(MTP ON),512K 在 24GB 卡上不可能。
- 本機數字整體低於原文約 5–28%,已定位原因:本機 7900 XTX 嚴重熱節流(junction 實測 106–107 °C、風扇已 84%、sclk 僅 2519/2634 MHz、功耗僅 179/291 W)。
- Recall 一開始是本機與原文最大的分歧(後來解決了):62K / 256K 都可 20/20(100%),但 519K 只有 10/20(50%)(原文 90%)。找出兩個關鍵:retrieval query 必須來自「最後一個 user turn」,以及加大
--kvmem-budget能直接把 512K recall 拉回 80%。
測試環境
| 項目 | 本機 | 原文 |
|---|---|---|
| GPU | RX 7900 XTX 24GB(gfx1100),另有 R9700 32GB(gfx1201) | RX 7900 XTX 24GB ×1 |
| CPU | Intel Xeon E5-2696 v3(18C/36T, 2.3 GHz) | AMD Ryzen 5 9600X |
| 系統記憶體 | 46 GiB | 64 GB |
| ROCm | 7.2.4 | 7.2.3 |
| OS / kernel | Ubuntu 24.04.4 / 7.0.0-34 | Linux |
| 模型 | Qwen3.8-27B-UD-Q4_K_M.gguf(16 GB, Unsloth,nextn_predict_layers=1) |
Qwen3.8-27B Q4_K_M |
| 原生 context | 262,144 | 262,144 |
安裝與編譯
來源與版本
| 項目 | 值 |
|---|---|
| Repo | https://github.com/kvmem/kvmem-llama.cpp |
| 分支 | rocm-beta(HEAD 1c4eef7) |
| llama.cpp pin | submodule b81c99b(2026-09-02) |
| Patch | patches/llama-kvmem-current.patch → 38 個檔案 |
編譯指令
cd kvmem-llama.cpp
ROCM_PATH=/opt/rocm python3 scripts/build-rocm.py --linux --gpu-targets gfx1100 --jobs 16
# → build-hip-linux/bin/llama-kvmem-server
共 600 個建置目標、0 錯誤。產出 llama-kvmem-server、llama-kvmem-cli、llama-tokenize 等。
踩雷與修復:HIP general protection fault(本機特有)
症狀
以 --device ROCm1(7900 XTX)啟動後,第一個請求必崩:
traps: llama-kvmem-ser general protection fault ip:... in libamdhip64.so.7.2.70204
exit code 139
伺服器日誌沒有任何錯誤訊息。
定位
AMD_LOG_LEVEL=3 的 HIP API trace 給出真正原因:
hipLaunchKernel ( ... )
Forcing SPIRV: false
:1:hip_fatbin.cpp:687: No compatible code objects found for: gfx1201
本機有兩張 AMD 卡,而這次只編譯了 gfx1100。llama.cpp 的 HIP backend 在初始化時會列舉所有可見裝置,某些 kernel 被下到 gfx1201(R9700),該架構沒有對應 code object → HIP 在 fatbin 查找失敗時不回報錯誤而是直接 GPF。
這正是原文「單卡 7900 XTX」環境不會踩到的地雷。
解法
export ROCR_VISIBLE_DEVICES=1 # 只暴露 7900 XTX(gfx1100 為 device 0)
# 之後用 --device ROCm0
驗證:
ggml_cuda_init: found 1 ROCm devices (Total VRAM: 24560 MiB):
Device 0: Radeon RX 7900 XTX, gfx1100 (0x1100), ... VRAM: 24560 MiB
加上後立即恢復正常。其他可行做法:編譯時帶 --gpu-targets gfx1100,gfx1201(兩個 code object 都編進去),或實體只留一張卡。
長上下文實測結果(主矩陣)
測試口徑
llama-kvmem-server -m Qwen3.8-27B-UD-Q4_K_M.gguf --device ROCm0 -ngl 99 --load-mode none \
-c <ctx> -b 2048 -ub 512 -t 6 --flash-attn on \
--kvmem --kvmem-budget 32768 --kvmem-gen-reserve 16384 \
--kvmem-block-tokens 128 --kvmem-query-policy user --kv-dtype q8_0 \
[--spec-type draft-mtp --spec-draft-n-max 2 --spec-kv-dtype f16 --kvmem-mtp-state replay] # MTP ON
- prompt 長度以
llama-tokenize精確擬合(誤差 ≤ 128 token),非字元估算。 - Decode 一律取 512 個輸出 token 的完整樣本(非短樣本)。
- VRAM = 全卡
mem_info_vram_used峰值;Host RSS = 伺服器行程 VmRSS 峰值。
實測表
| Context | 標稱 | 實際 Prompt | MTP | Prefill (tok/s) | Decode (tok/s) | TTFT (s) | VRAM 峰值 (MiB) | Host RSS 峰值 (MiB) |
|---|---|---|---|---|---|---|---|---|
| 131,072 | 128K | 130,072 | OFF | 402.37 | 19.58 | 324.8 | 18,757 | 6,415 |
| 131,072 | 128K | 130,072 | ON | 442.56 | 45.90 | 295.3 | 19,808 | 7,205 |
| 262,144 | 256K | 261,169 | OFF | 463.59 | 25.64 | 565.6 | 18,804 | 10,848 |
| 262,144 | 256K | 261,169 | ON | 428.62 | 46.83 | 611.4 | 19,430 | 12,146 |
| 524,288 | 512K | 523,214 | OFF | 386.33 | 19.51 | 1,358.5 | 18,289 | 19,845 |
| 524,288 | 512K | 523,214 | ON | 340.57 | 34.74 | 1,541.4 | 19,195 | 22,021 |
6/6 全部成功。512K 一檔完成 523,214-token prompt 的完整 Prefill → Decode → 正常輸出(輸出 512 token),非僅「服務能啟動」。
與原文逐項對照
| Context | MTP | Prefill 本機 / 原文 | Decode 本機 / 原文 | TTFT 本機 / 原文 (s) |
|---|---|---|---|---|
| 128K | OFF | 402.4 / 557.6(−27.8%) | 19.6 / 27.0(−27.4%) | 324.8 / 235.0 |
| 128K | ON | 442.6 / 533.9(−17.1%) | 45.9 / 36.6(+25.4%) | 295.3 / 246.0 |
| 256K | OFF | 463.6 / 532.6(−13.0%) | 25.6 / 26.9(−4.8%) | 565.6 / 493.0 |
| 256K | ON | 428.6 / 511.7(−16.2%) | 46.8 / 36.4(+28.7%) | 611.4 / 513.0 |
| 512K | OFF | 386.3 / 520.3(−25.8%) | 19.5 / 26.9(−27.3%) | 1,358.5 / 1,009.0 |
| 512K | ON | 340.6 / 500.3(−31.9%) | 34.7 / 39.8(−12.7%) | 1,541.4 / 1,049.0 |
最關鍵的一點:VRAM 不隨 Context 成長
| Context | 邏輯 Context 倍數 | VRAM 峰值 (MiB) |
|---|---|---|
| 128K | 1× | 18,757 |
| 256K | 2× | 18,804 |
| 512K | 4× | 18,289 |
邏輯上下文放大 4 倍,VRAM 峰值幾乎不變(甚至略降),穩定落在 18–19.8 GiB。這正是 KVMem 的核心價值:GPU 只維持約 32K active KV working set,超出的歷史 KV 落在 Host RAM(RSS 由 6.4 GB 成長到 19.8/22.0 GB)。原文第 5 節的結論在本機完全複現。
為什麼本機數字偏低:熱節流
原文機器(Ryzen 9600X + B850 平台)在同樣 24GB 卡上明顯更快。本機在 512K 滿載時實測:
| 指標 | 實測值 | 說明 |
|---|---|---|
| Junction 溫度 | 106–107 °C | RDNA3 節流點約 110 °C |
| Edge / Memory | 62–63 °C / 70 °C | |
| 風扇 | 2,467 RPM(84%) | 已接近上限 |
| sclk | 2,519 MHz(上限 2,634 MHz) | 未跑滿 |
| 功耗 | 179 W / 291 W | 非功耗限制,是溫度限制 |
同一張卡在冷機(junction 52 °C)起跑時 512K prefill 可達 601 tok/s,隨著溫度爬升一路掉到 344 tok/s,整段平均 340–386 tok/s。這解釋了:
- 本機 prefill 普遍低於原文;
- 同一設定不同次量測變動可達 ±10%(例如 128K MTP OFF 在背景編譯時 402 tok/s、乾淨重測 437 tok/s);
- MTP ON 的超長上下文檔位掉幅最大(512K ON 僅 340.6 tok/s)。
這是本機散熱條件問題(機殼風道/風扇曲線),不是 KVMem 或 ROCm 的問題。原文環境應未觸發節流。
Recall 測試(Needle-in-a-Haystack)
方法:在長文中等距埋入 20 個互不相關的 6 位數字 needle(NOTE: the reference code for NEEDLE-xx is CCCCCC.),
在文末逐一提問並精確比對 6 位數字。上下文與 GPU budget 可調。
關鍵發現:retrieval query 的「請求結構」才是決定性因素
伺服器是用 find_last_user_role_block() 從 最後一個 user 訊息 取出 retrieval query
(tools/llama-kvmem-server.cpp:1012)。因此:
- 若把 整篇長文 + 問題塞在同一個 user message,query 就變成整篇長文,retrieval 形同亂選;
- 必須把長文放在前一個 user turn,問題單獨當最後一個 user turn,query 才等於真正的問題。
在 62K context 下把 GPU budget 壓到 8192(覆蓋率僅 ~13%),兩種結構的差距立刻暴露:
| 請求結構 | GPU budget | Context | Recall |
|---|---|---|---|
| A:長文+問題同一個 user message | 32768 | 62K | 19/20(95%) |
| B:問題單獨為最後 user turn | 32768 | 62K | 20/20(100%) |
| A:長文+問題同一個 user message | 8192 | 62K | 11/20(55%) |
| B:問題單獨為最後 user turn | 8192 | 62K | 20/20(100%) |
解讀:budget 大(32768)時 485 個 block 有 256 個留在 GPU(覆蓋率 53%),
即使 retrieval 很爛 A 也有 95%;把 budget 壓到 8192(覆蓋率 13%)後,
A 掉到 55% 而 B 仍是 100% —— 這證明 B 的 retrieval 是精準的,A 的不是。
512K 實測
| 請求結構 | Prompt tokens | Recall | 備註 |
|---|---|---|---|
| A:長文+問題同一個 user message | 519,198 | 8/20(40%) | query 變成整篇長文 |
| B:問題單獨為最後 user turn | 519,195 | 10/20(50%) | query 正確,但語料放大到 519K 後仍退化 |
觀察:答錯時模型回傳的幾乎都是另一顆 needle 的號碼(如問 NEEDLE-06 答 567730 = NEEDLE-10 的值),
而不是隨機亂答 —— 表示 needle 的 block 有被取回,但取回的是錯的那一顆。
所有 needle 都以同一句型(...reference code for NEEDLE-xx is ...)書寫,彼此高度相似,
在大語料下 retrieval 的排序鑑別力會下降。
與原文對照
| 來源 | Context | Recall |
|---|---|---|
| 原文 KVMem | ~520K | 18/20(90%) |
| 原文 SGLang / NInfer | ~520K | 100% |
| 本機 KVMem(62K / budget 8192 / 結構 B) | 62K | 20/20(100%) |
| 本機 KVMem(512K / budget 32768 / 結構 B) | 519K | 10/20(50%) |
【重要】recall 的關鍵變數是「GPU working set 覆蓋率」,不是語料大小
先前 512K 只拿到 50%,一度以為是大語料下的硬限制。後續在 256K / 384K / 512K
三個檔位(其餘參數完全相同:reserve 固定、MTP on、multiturn、同一批 20 顆 needle)
實測後,可看出規律是覆蓋率,而且衰退是平滑的,不是斷崖:
| Context | budget | 候選 blocks | 取回 blocks | 覆蓋率 | Recall | VRAM 峰值 | Host RSS 峰值 |
|---|---|---|---|---|---|---|---|
| 62K | 8192 | 484 | 64 | 13% | 20/20(結構 B) | 17,085 | — |
| 256K | 32768 | 2,024 | 256 | 12.6% | 20/20(100%) | 18,865 | 12,695 |
| 384K | 32768 | 3,072 | 256 | 8.3% | 17/20(85%) | 18,857 | 18,261 |
| 384K | 98304 | 3,072 | 768 | 25% | 19/20(95%) | 21,291 | 18,392 |
| 512K | 32768 | 4,056 | 256 | 6.3% | 10/20(50%) | 18,289 | 19,845 |
| 512K | 98304 | 4,056 | 768 | 19% | 16/20(80%) | 21,507 | 23,304 |
兩個結論:
- 同一個語料,加大
--kvmem-budget就能把 recall 拉回來:512K 由 50% → 80%,
384K 由 85% → 95%。這是本次最實用的調校結論 —— 要保準確度,該調的是 budget,
不是把 context 開更大。 - 384K + budget 98304 是本機長上下文的最佳平衡點:recall 95%、VRAM 21.3 GiB、
Host RSS 只 18.4 GB(ComfyUI 可以不用關),比 512K 那組(80% / 23.3 GB)明顯划算。
MTP 接受率與效益
KVMem 的 server 不輸出 MTP 接受率統計,因此改用 llama-kvmem-cli
以同一種生成任務(編號清單,512 輸出 token)量測。
| 設定 | Decode (tok/s) | 相對提升 |
|---|---|---|
| MTP OFF | 29.41 | — |
MTP ON (--spec-draft-n-max 2, F16 draft KV) |
50.82 | +72.8% |
伺服器端 MTP 效益(主矩陣)
| Context | MTP OFF | MTP ON | Decode 提升 | TTFT 變化 |
|---|---|---|---|---|
| 128K | 19.58 | 45.90 | +134% | −29.5 s |
| 256K | 25.64 | 46.83 | +83% | +45.8 s |
| 512K | 19.51 | 34.74 | +78% | +182.9 s |
本機的 MTP 增益比原文(+35.6~+48.3%)更大,原因是測試任務是高度可預測的編號清單,
接受率偏高。原文的結論方向一致:KVMem 解決容量、MTP 解決速度,兩者可疊加,
但超長 prompt + 短輸出時要注意 TTFT 變差(512K 時 TTFT 多 183 s)。
Full-KV 對照(--no-kvmem)
以相同模型、相同 prompt(130,072 token)、相同 KV dtype(q8_0)、相同 FA / batch / threads 設定,
只把 KVMem 關掉(--no-kvmem,即傳統 Full-KV 全部常駐 GPU):
| 設定 @128K | VRAM 峰值 (MiB) | Host RSS 峰值 (MiB) | Prefill (tok/s) | Decode (tok/s) |
|---|---|---|---|---|
| KVMem(budget 32768)MTP OFF | 18,757 | 6,415 | 402–437 | 19.6–21.2 |
Full-KV(--no-kvmem)MTP OFF |
20,667 | 1,895 | 301.8 | 12.7 |
| KVMem MTP ON | 19,808 | 7,205 | 424–443 | 40.7–45.9 |
| Full-KV MTP ON | 22,478 | 2,180 | 288.8 | 23.9 |
| 差異(MTP OFF / ON) | KVMem 少 1.9 / 2.7 GiB | KVMem 多 4.5 / 5.0 GiB | KVMem 快 ~35% | KVMem 快 ~60–90% |
原文的 Full-KV 對照(128K):MTP OFF 21,168 MiB、MTP ON 23,008 MiB;
本機 Full-KV 為 20,667 / 22,478 MiB —— 兩者相當吻合(差 2.4%),
可見 KVMem 的 VRAM 節省在兩個環境下是一致的(約 2–3 GiB @128K)。
這裡有一個原文沒強調、但很實際的發現
KVMem 不只省 VRAM,還明顯更快。 原因很直接:KVMem 只讓 ~48K token 的
active working set 進 GPU,attention 就只在那個範圍上算;Full-KV 則要在
全部 131K token 上算。所以本機量到 KVMem 的 prefill 快約 35%、decode 快約 60%。
容量上的差距更關鍵:
| Context | KVMem VRAM | Full-KV(估計/原文實測) |
|---|---|---|
| 128K | 18,289–19,808 MiB | 20,667 MiB(本機實測) |
| 512K | 19,195 MiB | 依 128K 線性外推約 30–35 GiB → 24GB 卡放不下 |
也就是說,512K 這件事在原生的 Full-KV 路徑上根本無法成立,這才是 KVMem 的核心價值。
結論
- KVMem 可用且值得用:單張 24GB 7900 XTX 跑通 523,214-token 邏輯上下文,VRAM 僅 18.2–19.8 GiB,速度沒有崩塌(decode 19.5–46.8 tok/s)。原文的核心結論在本機完整複現。
- 最有價值的不是速度,是容量:4 倍上下文、VRAM 幾乎不成長。代價轉移到 Host RAM —— 512K 需要約 20–22 GB 主機 RSS。本機只有 46 GiB,512K 已接近上限(可用 RAM 最低剩 7.1 GiB);原文的 64 GB 餘裕明顯更好。
- KVMem 同時比 Full-KV 快(原文未強調):128K 下 prefill 快 ~35%、decode 快 ~60–90%,因為 attention 只在 ~48K 的 active working set 上計算,而非全部 131K。Full-KV 在 128K 已吃掉 20.7/22.5 GiB,512K 在 24GB 卡上根本無法成立。
- MTP 在超長上下文要算 E2E 帳:本機 MTP ON 對 decode 提升很大(+78~134%),但 TTFT 在 512K 反而多 183 s,與原文結論方向一致。KVMem 與 MTP 可正常疊加。
- 多卡機器要注意:只編譯一個 gfx target 而讓另一個架構的卡保持可見,會導致 HIP GPF 崩潰(本機 gfx1201 + gfx1100);
ROCR_VISIBLE_DEVICES或雙 target 編譯可解。原文的單卡環境不會踩到。 - Recall 的關鍵變數是「GPU working set 覆蓋率」,不是語料大小:256K 可 20/20(100%),384K 掉到 17/20(85%),512K 只剩 10/20(50%)——平滑衰退,不是斷崖。而把
--kvmem-budget從 32768 提到 98304:384K 回到 19/20(95%)、512K 回到 16/20(80%)。另外 retrieval query 必須是「最後一個 user turn」,把長文與問題塞在同一個 user message 會讓 query 變成整段長文,recall 直接掉到 40%。這三點是使用上最容易踩、也最好修的坑。 - 散熱是硬限制:本機 7900 XTX 在滿載時 junction 達 106–107 °C、風扇已 84%、sclk 2519/2634 MHz、功耗僅 179/291 W,明顯已在節流區。這解釋了本機數據普遍低原文 5–28% 且變動大。
實務建議
| PROFILE | CTX | budget | reserve | Recall 實測 | VRAM 峰值 | Host RSS | 用途 |
|---|---|---|---|---|---|---|---|
day |
131072 | 32768 | 16384 | (62K/256K 都 100%) | 19.8 GB | 7.2 GB | 日常 |
native |
262144 | 32768 | 16384 | 20/20(100%) | 18.9 GB | 12.7 GB | 要準、decode 最快 |
long |
393216 | 98304 | 8192 | 19/20(95%) | 21.3 GB | 18.4 GB | 長上下文最佳平衡 |
max |
524288 | 98304 | 8192 | 16/20(80%) | 21.5 GB | 23.3 GB | 真的需要 512K 時 |
- 要 recall 準:
PROFILE=long(95%)或native(100%);並把問題放在獨立的 user turn,不要把問題接在超長文件後面同一個 message。 - 要跑 512K:至少先確認散熱(改善風道/機殼風扇),並關掉其他吃 RAM 的服務;46 GiB RAM 是緊繃的。
- 長 session Agent:KVMem 的前綴快取實測有效(512K 下
cache_n=519195,每輪只需 prefill ~500 token),這才是它最實用的場景。
重現指令
# 1) 取得與編譯
cd kvmem-llama.cpp
ROCM_PATH=/opt/rocm python3 scripts/build-rocm.py --linux --gpu-targets gfx1100 --jobs 16
# 2) 只暴露 7900 XTX(務必,否則 HIP GPF)
export ROCR_VISIBLE_DEVICES=1
# 3) 啟動 512K 服務
./build-hip-linux/bin/llama-kvmem-server \
-m Qwen3.8-27B-UD-Q4_K_M.gguf \
--device ROCm0 -ngl 99 --load-mode none \
-c 524288 -b 2048 -ub 512 -t 6 --flash-attn on \
--kvmem --kvmem-budget 32768 --kvmem-gen-reserve 16384 \
--kvmem-block-tokens 128 --kvmem-query-policy user --kv-dtype q8_0 \
--no-ui
# 4) 量測(本報告使用的 harness)
python3 bench/ctx_bench.py --binary kvmem-llama.cpp/build-hip-linux/bin/llama-kvmem-server \
--model Qwen3.8-27B-UD-Q4_K_M.gguf \
--out results/x.json --ctx 524288 --target-prompt-tokens 523264 --mtp on