你替 8GB 顯卡加上 --moe-cache-mib,以為常用專家留在 GPU,回答就會更快;結果第一個字等更久,甚至整段生成也慢了。這篇 llama.cpp MoE 快取教學,帶你把「快取越大越好」改成一份能核對、能回復的設定紀錄。
專為剛開始跑本機模型、還分不清權重和 KV Cache 的讀者寫。先用廚房比喻理解顯存,再用六步固定版本、建立零快取基準、逐級掃容量,最後依提示處理、生成速度與答案決定是否保留。指令以 Linux/WSL 的 Bash、單張 NVIDIA GPU 為範圍,前提是你已有可用的 CUDA 版 llama-server、Python 3 和一份能在目前 RAM 下跑起來的 MoE GGUF。
截至 2026 年 10 月 11 日,官方 PR #29887已於 10 月 7 日(UTC)合併;10 月 8 日的原始求助正是一位 RTX 4060 8GB、32GB RAM 使用者的設定疑問。本文依官方程式碼整理排查方法,沒有把該使用者或其他顯卡的速度當成你的預期成績。
先說結論:llama.cpp MoE 快取先看完整等待
MoE 快取=把常用專家放在 GPU 的小倉庫;是否值得,由同一任務的完整等待時間決定。先量關閉快取,再增加一個容量,模型、提示、上下文與輸出上限全部保持相同。只報生成 token/秒,會漏掉讀長提示的時間。
MoE(Mixture of Experts,混合專家)像一間有很多料理小組的廚房:每個 token 只叫部分小組工作,但其他小組的食譜仍要有地方保存。顯卡的 VRAM 是手邊工作檯,系統 RAM 是較遠的倉庫,PCIe 是兩邊運送資料的通道。工作檯變成快取區,就可能少放一些原本常駐的權重;你省了某些搬運,也改了其他工作的動線。
三種東西分開看:專家權重、KV Cache、整層 offload
① 專家權重快取:「常用食譜的小倉庫」。本篇旗標為 CPU 保留的專家權重配置 GPU 快取,命中時使用已有副本,缺少時才搬入。它不是把模型縮小;原本的權重仍有主記憶體位置。官方快取實作會依專家布局分組,並用 LRU(優先淘汰較久沒使用的項目)管理槽位。
② KV Cache:「這次訂單已讀過的筆記」。它保存注意力計算的中間資料,和上下文長度、模型架構及配置有關。調 --moe-cache-mib 不是替 KV 換量化,也不是儲存上一段答案。量測時先固定 -c 4096,不要同時換 KV 型別;可先讀本機 LLM 顯存指南建立容量概念。
③ 整層 GPU offload:「整個小組留在工作檯」。它把配置的模型層放到 GPU;專家快取則在部分權重保留於 CPU 時,額外留一塊可替換的空間。--fit on 會調整尚未指定的參數以適配裝置記憶體,所以增大快取後,日誌裡的權重配置也可能改變。這是整體配置 A/B,不是純粹只改一個快取的機械比較。

① 固定版本與 GGUF:先替這輪測試取名字
為什麼別人的指令在你這裡不認得?先確認你執行的檔案和版本。本篇查核固定在官方 commit 23b0202a189c44a54625aadcb37a946dd1d6278d。你不必為了照抄雜湊重新安裝環境,但同一輪比較必須用同一個二進位;升級後重新建立基準。若手上的版本缺少旗標,先走官方 CUDA 建置說明或官方 release 取得相容版本,再開始。
./build/bin/llama-server --version
./build/bin/llama-server --help > server-help.txt
./build/bin/llama-server --list-devices
sha256sum /absolute/path/model.gguf
nvidia-smi --query-gpu=name,driver_version,memory.total,memory.used --format=csv
把 ./build/bin/llama-server 換成你實際的 CUDA 執行檔,模型路徑換成自己的檔案;多分片 GGUF 要保存每片雜湊。記下模型量化、GPU、CPU、RAM、驅動和啟動指令,並在 help 找到 --moe-cache-mib、--fit、--fit-target。確認裝置列表真的出現 CUDA GPU,不能只靠視窗顯示「已啟動」。
8GB 顯存和 32GB RAM 不代表任何 MoE 都能跑。先用既有可用檔案,不急著下載更大的模型;若 RAM 已不足、系統持續換頁,記錄這個瓶頸再縮小模型。大型 MoE 的記憶體與驗收教學能幫你分清活躍參數、完整檔案和程序需求。
② 建立 llama.cpp MoE 快取零容量基準
怕自動配置被舊旗標綁住?先讓 fit 管理未指定的權重配置。開一個乾淨終端,先移除舊啟動腳本裡的 -ngl、-cmoe、-ot,並檢查你是否設定了 LLAMA_ARG_* 環境變數。基準先用下面這組起始值;4096 上下文、512 batch、128 microbatch 和 1024 MiB 餘裕是本篇的保守練習規格,不是 4060 的最佳設定。
./build/bin/llama-server \
-m /absolute/path/model.gguf \
--device CUDA0 --host 127.0.0.1 --port 8080 \
--fit on --fit-target 1024 \
-c 4096 -np 1 -b 512 -ub 128 \
--moe-cache-mib 0 -lv 4 > fit-0.log 2>&1
若裝置列表名稱不是 CUDA0,用列表的實際名稱。這個程序會占住終端,另開一個終端執行 curl --fail http://127.0.0.1:8080/health;回報就緒後才送題目。把日誌裡 GPU/CPU 權重、KV、計算緩衝和實際上下文記下來。先做短提示,再做長提示,確認基準可以完整回覆,且沒有記憶體配置錯誤。
為什麼不在同一行再加 -ngl 99 -cmoe?官方fit 程式碼在需要重新分配權重、卻遇到使用者已指定的 GPU 層數或 tensor override 時,會中止那段自動調整。自動配置和固定配置要各有自己的零快取基準。兩組直接混在一起,變慢時就很難知道原因。
③ 固定兩種提示與答案:生成快,還要答得對
如何避免每次都換一道題?先把測試材料存檔。建立 short.txt,內容為「請只輸出算式答案:17+25」。再用下面的 Python 產生 long.txt:100 筆訂單金額從 1 到 100,驗收答案為 5050。這是自建的入門測例,不是模型品質基準;之後另加你常用的固定摘要或程式任務。
from pathlib import Path
Path('short.txt').write_text('請只輸出算式答案:17+25。', encoding='utf-8')
rows = [f'訂單 {i:03d}:金額 {i};狀態已確認,納入本次合計。' for i in range(1, 101)]
Path('long.txt').write_text('\n'.join(rows) + '\n請加總所有金額,只輸出總額。', encoding='utf-8')
Path('perf.txt').write_text('用繁體中文分六段解釋 CPU、GPU、RAM、VRAM、PCIe 與快取,每段加入生活比喻。', encoding='utf-8')
把這段另存 make-prompts.py,執行 python3 make-prompts.py。短提示驗 42,長提示驗 5050;保留原始回答,額外解說、空白或思考段落按預先訂好的規則判讀。若模型會輸出思考文字,允許思考後的明確最終答案,但各容量都用相同規則。這兩題的通過數只能叫「本次兩題通過數」,不能寫成通用正確率。
聊天模型應先套用自己的模板,避免把原始文字當成正確聊天格式。下方範例利用官方server 的 /apply-template 與 /completion 端點。把程式另存 measure.py,它送出一次非串流請求,保留完整回應與客戶端等待秒數。client_seconds 只量 /completion 的完整請求,前面的模板請求未包含;端點錯誤、逾時或缺少 timings 時直接失敗,不會用零替代。
import json, sys, time, urllib.request
from pathlib import Path
def post(route, body):
req = urllib.request.Request('http://127.0.0.1:8080' + route,
data=json.dumps(body).encode(),
headers={'Content-Type': 'application/json'})
with urllib.request.urlopen(req, timeout=600) as r:
return json.load(r)
prompt = Path(sys.argv[1]).read_text(encoding='utf-8')
formatted = post('/apply-template', {'messages': [
{'role': 'user', 'content': prompt}]})['prompt']
start = time.perf_counter()
result = post('/completion', {'prompt': formatted, 'n_predict': 256,
'temperature': 0, 'seed': 42, 'cache_prompt': False, 'stream': False})
result['client_seconds'] = time.perf_counter() - start
if 'timings' not in result:
raise RuntimeError('缺少 timings,這輪不能當有效速度紀錄')
Path(sys.argv[2]).write_text(json.dumps(result, ensure_ascii=False,
indent=2), encoding='utf-8')
print(json.dumps(result['timings'], ensure_ascii=False))
python3 measure.py short.txt fit-0-short-1.json
python3 measure.py long.txt fit-0-long-1.json
python3 measure.py perf.txt fit-0-perf-1.json
在回應裡找 timings 的提示處理與生成欄位,保存實際 token 數與毫秒,不只抄 token/秒。cache_prompt:false 是避免上一輪 KV 前綴重用讓長提示看起來異常快,不是關閉專家權重快取。兩道算式主要驗答案,輸出太短時不足以穩定判斷 decode 吞吐。另把「用繁體中文分六段解釋 CPU、GPU、RAM、VRAM、PCIe 與快取,每段加入生活比喻」存成 perf.txt,以相同指令送出並保留完整回應,作為持續生成的練習任務。模型提早結束時,輸出可能少於 256 tokens;比較前核對輸出長度與停止原因,不能把少答當成加速。
④ 一次改一級容量:日誌、顯存與重複跑次一起留
容量要從哪裡開始?先掃小範圍,碰到錯誤就記錄並退回。依序嘗試 0、256、512、1024、1536 MiB;這些只是候選點,不是建議預留比例。用 Ctrl+C 結束上一個 server,只改 --moe-cache-mib 和日誌檔名再啟動,確保同一時間只有一個程序。每級都先看是否出現實際 MoE cache size,再跑同一短、長提示。
小容量可能放不下所需專家,官方實作的「budget is too small for … layers」表示那些分組不快取,其他分組仍可能成功;記錄部分覆蓋,核對實際容量。若出現「too small to hold the experts of one token」而初始化失敗,該容量才記為未通過配置,不能當作有效速度測量。若顯存配置失敗,先回零容量;不要為了讓大快取硬塞進去,同時偷偷縮上下文或換量化。
每個容量各做一次新程序的第一輪,再各做三次程序仍在的重複輪,檔名附 initial 或 repeat-1。預設啟動還有 warmup,因此第一輪稱為「啟動後初次請求」,不宣稱快取完全空白。專家快取會隨工作改變,重複輪即使關閉 KV 前綴重用,也可能受專家命中影響。保留每輪與中位數,最好最後再回零容量補跑一次,確認溫度或背景工作沒有把整組比較帶偏。
nvidia-smi --query-gpu=timestamp,memory.used,memory.total,utilization.gpu \
--format=csv --loop-ms=500 > gpu-memory.csv
在另一終端於啟動前開始取樣、測完按 Ctrl+C 停止。這是NVIDIA SMI 的裝置監測,記錄的是整張卡,而非精確分離每個程序;先記背景占用,再取測試區間最高樣本,欄位命名為「500ms 取樣最高值」。它可能漏過更短暫尖峰,遇到 N/A 就留缺值,別聲稱抓到精確峰值。

⑤ 看懂「生成快、讀提示慢」:先定位路徑,再下判斷
兩個速度往相反方向走,要保留嗎?先按你常用的工作量看總時間。Prefill 是「讀完提示」,decode 是「逐字生成」。長文件摘要若多花時間讀輸入,即使生成變快,完整等待仍可能增加;短問題、長回答則可能更重視生成。用同一任務的 client_seconds 作決策,再用內部 timings 解釋差異。
本篇鎖定版本的快取選擇路徑有 32 tokens 的 microbatch 上限及槽位容量檢查;大型批次走另一條路,仍能讀取已命中的專家,但不藉此更新 LRU。不能因此把「prefill 完全不用快取」當成通則。也不要只為觸發小批次,把 -ub 改小就宣告變快:那會改變提示處理方式,應另開一組有自己基準的測試。
當生成和讀提示都慢,先檢查實際快取是否建立、權重是否被 fit 移走、RAM 是否換頁、GPU 是否仍被其他程序使用。快取未命中要經過資料搬運;命中率、CPU/GPU 計算和 PCIe 路徑共同決定收益。日誌沒有明確證據,就把「PCIe 瓶頸」記成待查原因,不能看到 8GB 就直接替它下診斷。
⑥ 再比較 CPU MoE:固定配置必須另起一張帳
要不要用 -cmoe?先分開兩個家族。A 組是前面的 --fit on 容量掃描;B 組使用 --fit off --cpu-moe,另指定經你確認可啟動的 GPU 層數,同樣先從零快取開始。--cpu-moe 指定專家權重保留於 CPU;加快取後,符合條件的計算仍可使用 GPU 副本,不能把這旗標解讀成所有專家運算永遠只在 CPU。
./build/bin/llama-server \
-m /absolute/path/model.gguf \
--device CUDA0 --host 127.0.0.1 --port 8080 \
--fit off --cpu-moe -ngl 1 \
-c 4096 -np 1 -b 512 -ub 128 \
--moe-cache-mib 0 -lv 4 > fixed-0.log 2>&1
這裡 -ngl 1 只是固定配置的低起點,並非最佳 offload。確認零快取可用後,另行調整層數;選定一個可用層數再做 B 組容量掃描,過程中不改層數。A、B 各保留配置日誌與測例,先選合格且等待時間較短的組合,再檢查餘裕。若快取的改善小於跑次波動,或長任務退步,縮小快取或回復原基準就是合理結果。
原始 PR 的硬體測試可提供研究線索,卻不是 8GB 的容量配方。與FreeToken/Hybrid/llama.cpp 的引擎比較不同,本篇的交付是一份同引擎、同版本的容量紀錄;與vLLM 載入快取驗收不同,這裡量的是運作中的專家配置,而非重啟載入時間。
FAQ:llama.cpp MoE 快取的八個直接答案
1. 8GB 顯卡應該填多少?
先測。從零快取和小容量候選開始,保留啟動、配置、短長提示與答案紀錄;本篇不給跨模型通用的最佳值。
2. 32GB RAM 就一定夠嗎?
不一定。看完整 GGUF、程序與作業系統的實際需求,並留意換頁;活躍參數不是記憶體容量。
3. 必須先加 –cpu-moe 才有快取嗎?
先看配置。快取處理 CPU 保留的專家;fit 可能自行把部分權重留在 CPU。自動組先不加這個 override,以日誌確認。
4. cache_prompt:false 會清空 MoE 快取嗎?
它控制 KV 前綴重用。要回到新程序的專家快取起點,結束 server 後重開,再標記第一輪。
5. 生成速度提高,就該保留嗎?
先看完整等待。長提示的額外處理時間、輸出長度和答案通過數都要算進判斷。
6. 溫度設零,答案就逐字相同嗎?
不能先保證。不同計算批次或後端可能有數值差異;固定設定,按預先訂好的答案規則驗收。
7. 這份單 GPU 流程能證明多 GPU 限制嗎?
不能。查核版本已有按裝置分配快取預算的程式碼;本篇選單卡是縮小測試範圍,沒有沿用早期 PR 的多卡限制作當前結論。
8. 第一份合格紀錄至少要交什麼?
一個零快取基準、一個有效容量、同一短長提示、原始回答與 timings、配置日誌、顯存取樣,以及保留或回復的理由。
給新手的三個重點
先分記憶體,再分配置。權重、KV 和計算緩衝共用容量;fit 和手動 CPU MoE 各建自己的基準。先固定材料,再掃容量。版本、GGUF、提示與答案不變,錯誤跑次也留帳。先看完整等待,再談 token/秒。合格答案、可用上下文與合理餘裕,才是設定值得留下的條件。
接著閱讀
左右滑動查看更多推薦
下一步:留下第一個能回復的容量選擇
今天先做兩個設定:零快取,以及一個成功配置的小容量。跑相同短、長提示,留下原始回答、時間與顯存紀錄,再寫一句「我保留它,因為哪種任務變快且答案通過」,或「我回到零容量,因為哪項成本增加」。你真正學會的,是替自己的硬體做選擇;想把這種驗收思路用在完整 AI 工作流,可接著看AlphaLab 課程或AI 教學文章。





