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