上一篇篇幅所限只给了结论,这篇按 1164 帖 的"数字必须带测法"纪律,把 4080S 这套的完整配置补齐。先说和 1164 的对照:AMD ROCm 和 CUDA 两条路线,结论意外地一致——我们实测 MTP 接受率随负载/时间在 0.47~0.67 之间波动,和他们 0.31~0.97 的跨度呼应,论坛上论坛数字对不上,根源都是这个。
一、llama.cpp 完整启动参数(systemd ExecStart,可直接抄)
/usr/bin/stdbuf -oL -eL /opt/llama.cpp-qwen38/build/bin/llama-server \
--model /opt/models/Qwen3.8-27B-UD-Q4_K_XL-GGUF/Qwen3.8-27B-UD-Q4_K_XL.gguf \
--model-draft /opt/models/Qwen3.8-27B-UD-Q5_K_XL-GGUF/MTP/mtp-Qwen3.8-27B-Q4_0.gguf \
--mmproj /opt/models/Qwen3.8-27B-UD-Q5_K_XL-GGUF/mmproj-F16.gguf \
--spec-type draft-mtp \
--spec-draft-n-max 4 \
--spec-draft-ngl 99 \
--spec-draft-type-k q4_0 \
--spec-draft-type-v q4_0 \
--alias "Qwen3.8-27B-Coding" \
--port 8002 --host 127.0.0.1 \
--ctx-size 307200 \
--parallel 4 --kv-unified \
--batch-size 8192 --ubatch-size 1024 \
-ngl 99 --split-mode layer \
--cache-ram 16384 --cache-reuse 256 \
--slot-save-path /home/yijingu/logs/27b-slot-saves \
--cache-type-k q4_0 --cache-type-v q4_0 \
--flash-attn on --load-mode mlock --jinja \
--chat-template-file .../chat_template_fixed_patched.jinja \
--log-prompts-dir /home/yijingu/logs/27b-prompts \
--fit on --fit-target 128 \
--threads 12 \
--temp 1.0 --top-p 0.95 --top-k 20 --min-p 0.0 \
--reasoning on --reasoning-format deepseek
要点:
--ctx-size 307200+--parallel 4 --kv-unified:统一池 307K,llama.cpp 自动把单槽钳制在模型原生 n_ctx_train=262144(有 warning 但行为正确)——单会话不越原生上限,多出来的池容量全部用于并发。32G 卡能开 307K 池的根本是下面这条;- K4V4(双 q4_0):每 token KV 仅 18KB,比 fp8 KV 省 44%;质量套件 19/21 + 工具调用全过 + 187K 深度检索全命中,速度与 q8_0 持平;
--cache-ram 16384:槽位被逐出时 KV 状态先进 16G 内存池再落盘,会话恢复免重算;--fit on --fit-target 128:显存超预算时自动收缩参数,兜底用。
二、性能数据(全部附测法)
| 指标 | 数值 | 测法 |
|---|---|---|
| 解码中位 | 89 tok/s(p10≈41, 峰值105) | 15 题 × 800 tok,/completion 串行,按路由温度 |
| MTP 接受率 | 0.47~0.67(随负载/时段) | 同上,timings.draft_n/draft_n_accepted |
| 冷 prefill(35K tok) | ~1700-1900 tok/s | cache_prompt=false ×3 取稳定值 |
| 暖 TTFT(35K 命中) | 0.10~0.4s | cache_prompt=true,prompt_ms |
| no-MTP 基线 | 31 tok/s | 同题集去 MTP |
MTP 接受率波动范围和 1164 的 0.31~0.97 呼应:论坛数字对不上,先对测法再对硬件。另有一个实操坑:测量时本机其他 Agent 的定时任务会抢槽位,解码中位数能被砍 40%——测速时务必确认并发空闲,或加槽位监视。
三、代理层(FastAPI,8001):路由 + 准入 + 自动放行
模型前面有一层自研路由代理,核心逻辑简化后如下(完整版 900+ 行,含准入/监控/持久化):
# 六档路由:B日常/C1分析/C2代码+Agent/D深分析/A格式化/V视觉
TIER = {
"B": dict(max_tokens=4096, thinking=False),
"C1": dict(max_tokens=6144, thinking=True, budget=2048, effort="medium"),
"C2": dict(max_tokens=6144, thinking=True, budget=4096),
"D": dict(max_tokens=6144, thinking=True, budget=2048, effort="medium"),
}
def resolve_auto_c2_max_tokens(payload, decision, cron):
# 编码Agent零配置:客户端显式要大输出(如pi-ai默认32768)时,
# C2自动放宽到32768+1800s超时;CRON恒锁1K/2K;普通请求不变
if cron or decision.get("route") != "C2":
return None
req = payload.get("max_tokens") or payload.get("max_completion_tokens")
if not isinstance(req, int) or req <= decision["max_tokens"]:
return None
return min(req, LONG_OUTPUT_MAX_TOKENS) # 32768
def resolve_max_tokens(payload, route_limit):
req = payload.get("max_tokens")
if req is None:
return route_limit
return min(req, route_limit) # 未命中自动C2时仍钳制,防无差别预留
- 准入保护:每路并发请求按
输入+max_tokens预留预算,合计 ≤ 共享池 token 数,超了 429——这套在 ctx 扩到 307K 后同步改为 307200; - 踩坑:客户端(如 pi-ai 系)默认发 max_tokens=32768,无脑尊重会让每个普通请求都按 32K 预留准入预算 → 必须用"路由命中 C2 才放宽"的条件放行,CRON 用 header 前缀识别后强制锁小预算。
四、和 1164/Xiaote 观点的两个交叉印证
- K8V4 vs K4V4:Xiaote 建议显存紧时"降 V 不降 K、V 用 q4_1"。CUDA 这边有个平台差异要提醒:llama.cpp 的 CUDA flash-attn 内核只实现了 q8_0/q4_0 两格式,q4_1/q5_0 会静默掉 CPU fallback(实测速度跌 10 倍以上)——所以 NV 卡上"V 用 q4_1"这条路走不通,K4V4 就是 CUDA 的性价比终点,K8V4 是显存富余时的质量升级项,和他们的结论殊途同归;
- prefill 长度衰减:1164 实测 6.4K→38.9K prefill 掉 26%,提醒"短 prompt 外推长上下文等待会严重低估"——我们同样只在 35K 口径下报 ~1900,不外推。另外 Agent 长链任务若用我们代理的软压缩(超阈值丢最老轮次),会破坏前缀缓存命中,体感 TTFT 变差,这也是"Agent 场景比单轮慢"的一个非模型因素。
配置细节、A/B 原始数据和踩坑日志都在手边,有问必答。