① 硬件基线与栈选型
| 硬件 | 规格 |
|---|---|
| GPU | RTX 5090 32GB(Blackwell 架构,整卡 32,607 MB) |
| CUDA | 13.4(驱动 616.92,必须 ≥ 12.x 才能用 Blackwell NVFP4 加速) |
| CPU / RAM | AMD Ryzen 9 9950X3D 16C · 61.4 GB |
为什么选 llama-server 而不是 Ollama / LM Studio:
| 方案 | 优点 | 缺点 |
|---|---|---|
| llama-server | 轻量无依赖 · 原生 GGUF · NVFP4 tensor core 加速 · OpenAI 兼容 API | 要自己编译或下二进制 |
| Ollama | 装完就能用 · 模型管理方便 | fork 版本滞后 · offload 不彻底 · 强占 11434 端口且自恢复极强杀不掉 |
| LM Studio | GUI 友好 · 自动下载 | 闭源 · 功能受限 · 启动慢 |
核心理由:能把 Blackwell 的 tensor core 吃满,没有中间层损耗。Ollama 只是退回 11434 自己玩,主栈改用 11435(llama-server)/ 11436(proxy)。
② 模型与量化选型(两次演进)
第一代 NVFP4 量化模型 → 第二代三值量化底座,选型逻辑完全不同。
第一代:qwen3.8-27b-nvfp4(9/13 部署)——先决定参数量:
| 候选 | 参数量 | 量化 | 大小 | 结论 |
|---|---|---|---|---|
| qwen3.8-7b | 7B | NVFP4 | ~5 GB | 太小,Agent 多工具调用推理质量不够 |
| qwen3.8-14b | 14B | NVFP4 | ~10 GB | 浪费一半显存 |
| qwen3.8-27b | 27B | NVFP4 | 19.6 GB | 32GB 卡刚好填满又不爆 |
| qwen3.8-57b | 57B | NVFP4 | ~40 GB | 显存不够 |
NVFP4 是 Blackwell 原生 4-bit 浮点格式:GGUF 里 tensor 直接以 NVFP4 dtype 存储,加载后走 tcgen05 张量核心加速;换 Q4_K_M 等整数格式只能 F16 模拟,慢一倍左右。模型直接下 ModelScope 预量化版,省掉 54GB 原始权重 + 3 小时量化时间。
第二代:Ternary-Bonsai-2-27B-PQ2_0(9/22 切换)——1.71-bit 三值量化,同尺寸显存下上下文翻倍(ctx 上限 → 262,144)。换模型不是下载就完:量化格式变了,KV 量化档位、chat-template 补丁锚点、MTP 层有无全部跟着变(详见性能页与排障页)。
KV cache 量化三档演进(都是实测):
- f16 原生:66.3 KiB/token,26 万 ctx 根本放不下;
- q8_0 / q5_1(9/21):降到 30.0 KiB/token,质量损失极小。K 必须保 q8_0——K 进注意力分数,量化误差会被放大到每个 token;V 只是加权求和,误差会平均掉;
- q8_0 / q4_0 是本机构建的坑档:日志报
no FlashAttention vector kernel compiled for K/V types q8_0-q4_0,每次注意力现场转 f16,省显存赔速度,不要这么配; - q4_0 / q4_0(10/4,前提是 -fa on + 换 Bonsai 底座):质量 7/7 零退化、速度 +13.6%、显存 −4.35 GB——nvfp4 时代"别用 q4_0"的注释前提已变。
③ 编译与启动
2026-09-12 用 VS2026 Developer Command Prompt(vcvarsall x64)编译,确保找到 CUDA SDK。
REM 1. 拉源码(带 CUDA 后端) git clone --recursive https://github.com/ggml-org/llama.cpp.git D:\_Qwen3.8-27b\_llama_src REM 2. CMake configure —— 指定 CUDA 后端 cd D:\_Qwen3.8-27b\_llama_src cmake -B build -G Ninja -DGGML_CUDA=ON ^ -DGGML_CUDA_CUDA_PATH="C:/Program Files/NVIDIA GPU Computing Toolkit/CUDA/v13.4" REM 3. 编译 + 产物复制 cmake --build build --config Release --parallel copy build\bin\Release\*.exe ..\llama_cpp\ copy build\bin\Release\*.dll ..\llama_cpp\
关键产物:llama-server-impl.dll(8.9 MB 核心实现)+ ggml-cuda.dll(144.9 MB CUDA 加速后端)。首次加载 65 秒——mmap 要把 19.6GB GGUF 分页进显存。后来换用上游预编译包 prism-b10754(自带 MTP Hadamard 修复 PR #205 与 prefill 优化),但旧目录原封保留作回滚点。
④ 启动参数逐项解释
当前生效配置(2026-10-04)。每个参数都有"为什么",改之前先看这张表。
| 参数 | 为什么 |
|---|---|
-c 262144 | 26 万 ctx。显存占用随 KV 量化下降而可控(见 ⑤),同时给 ZCode 的估算偏差留绝对缓冲 |
-fa on | Flash Attention——KV 量化的前提,也更快 |
-ctk q4_0 -ctv q4_0 | KV 双 q4_0:质量 7/7 零退化 + 显存 −4.35 GB。前提是 -fa on;q8_0/q4_0 混合档是坑,不要配 |
-ngl 999 | 全量 GPU offload |
-np 1 | Agent 是串行请求,多槽只是白占 KV |
-b 2048 -ub 512 | batch / ubatch。cont-batching 时代定下的值,prefill 快 70%+ 且省 4GB |
--no-reasoning-preserve | 模板默认把历史里的 thinking 全留下,会持续吃上下文 |
--reasoning-budget 1000 | 压掉过度思考:思考 −76%,正文 0 → 4,820 字,准确度无下降(见性能页) |
--mmproj ...gguf | 视觉投影器(0.6 GB):真实截图逐字读出。脚本里带 if exist 守卫,删文件自动退回纯文本 |
--sleep-idle-seconds 180 | 3 分钟不用把显存还给 UE;端口保持监听,7.6 秒唤醒(见 ⑦) |
--chat-template-file ... | 打补丁的模板:修 reasoning_effort 500 + 对话中间 system 消息两个 500 根因 |
-a "local,qwen3.8:27b,...offload" | 别名列表:客户端 model 名随便填都能命中 |
--metrics --log-file ... | 指标 + 落盘日志:排障与速度归因全靠它 |
调参旋钮(环境变量临时覆盖,仅影响这一次启动):
set LLM_CTX=131072 & start_local_llm.bat REM 降 ctx(同步把 contextWindow 改成 ~×0.75) set LLM_SLEEP=60 & start_local_llm.bat REM 1 分钟就还显存;-1 永不归还 set LLM_THINK=0 & start_local_llm.bat REM 关 thinking:约快一倍,推理变浅 set LLM_RBUDGET=500 & start_local_llm.bat REM 收紧思考预算
⑤ 显存标定(实测公式)
显存标尺必须在"开着 UE"的状态下测——引擎没开时测出来的基础占用(2,086 MB)不能拿来算预算。
第一代模型(nvfp4,q8_0 KV 时代)的标定:
总占用 ≈ 16.5 GiB(权重 + compute buffer + CUDA 上下文) + ctx × 35.2 KiB
| ctx | KV 类型 | 实占(UE 同开) | 整卡剩余 | 判断 |
|---|---|---|---|---|
| 65536 | q8_0/q8_0 | ~18,900 MB | ~6,200 MB | 保守,但 ZCode 容易冲过去 |
| 81920 | q8_0/q8_0 | ~19,200 MB | ~5,300 MB | 实测被冲过去过 |
| 114688 | q8_0/q8_0 | ~20,608 MB | ~4,753 MB | 稳定运行过 |
| 131072 | q8_0/q5_1 | ~20,594 MB | ~4,750 MB | 同显存、上下文 +14% |
第二代模型(Bonsai 2)的标尺:净占用 = 整卡唤醒 − 睡眠 = 20,702 − 2,828 ≈ 17.5 GiB;KV4 后整卡峰值 20,964 MiB。模型预算(用户定 ~21 GB)始终按"UE 开着"核算:整卡 32,607 − UE 桌面+引擎 7,489 ≈ 25 GB 可用,再留 UE 后续增长余量。
⑥ ctx 调优历史(9 个阶段)
一次又一次改 -c 的完整轨迹——每一步都是被某个 400/性能问题逼出来的。
32768
9/13 首启。能跑,但 Agent 带 tools 不够用
8192
想首字更快改小 → 400:46,911 > 8,192
131072
改大解决 400 → 显存 30.7 GB 顶格,prefill 慢
65536
9/16 砍半 + cont-batching:prefill 3.8s → 1.1s,显存省 4GB
81920→98304
9/17 ZCode 时代:按"估算偏差 + 压缩开销"配对上调
114688
9/18 再上调:触发点 81,920 在 1.26 系数下安全
262144
9/22 Bonsai 2 换底座:三值量化下显存放得下 26 万
这 9 步里真正可迁移的规律只有一条:服务端 ctx 不是跟显存上限走,是跟客户端估算偏差走——每档提升都发生在"又冲过去一次"之后(见性能页 · token 估算偏差)。
⑦ 休眠机制:为什么"清除"必须是休眠
"打开就用、不用就还显存"的唯一正确实现 = 进程常驻 + 空闲休眠。
| 做法 | 端口 | 显存 | 客户端用的时候 |
|---|---|---|---|
| 退出进程 | 不在监听 | 0 | 连接被拒 → "重新连接中 N/10" |
| 休眠(sleep) | 仍在监听 | ~2.4 GB | 请求到达 → 7.6 秒唤醒 → 正常回答 |
实测(--sleep-idle-seconds 180):使用中 27,430 MB(含 UE)→ 空闲 180 秒 ~2,400 MB(日志 handle_sleep: server is entering sleeping state)→ 再来请求 7.2 秒返回 200。早期用 proxy 级 kill 实现释放,但有计时 bug 且重启会退回旧参数,全部废弃——llama.cpp 原生支持,一层就够。