跑通了本地模型,下一个问题就是怎么把它当服务用——像云厂商的 API 那样被自己的脚本、Agent、其他程序反复调用。llama.cpp 的 llama-server 就是干这个的:常驻内存、暴露 OpenAI 兼容接口、多轮对话自动复用缓存。这篇用 supergemma4-26B 讲完整套实战。
为什么非要服务方式
| 调用方式 | 能否复用 KV 缓存 | 说明 |
|---|---|---|
| llama-cli 每次单独跑(-n 后退出) | ❌ | 进程退出,缓存清零 |
| llama-cli 交互模式(同窗口连续提问) | ✅ | 同进程多轮复用 |
| llama-server 常驻服务 | ✅ | OpenAI 兼容 API,天然复用,多客户端并发 |
缓存复用依赖「进程常驻 + 上下文不销毁」,服务方式天然满足。多轮 Agent 每次带完整 history 调 API,服务端只 prefill 新增的增量,首字延迟和内存开销都小得多。
服务端启动(常驻)
入口脚本 serve-full.cmd:
@echo off
chcp 65001 >nul
setlocal
set LLAMA_DIR=C:\...\Temp\opencode\llama
set MODEL=C:\...\supergemma4-26b-uncensored-fast-v2-Q4_K_M.gguf
"%LLAMA_DIR%\llama-server.exe" -m "%MODEL%" -ngl 99 -t 8 -ctk q8_0 -ctv q8_0 -rea off --host 127.0.0.1 --port 8080
endlocal
-rea off:关闭思考块,正文直接进content(supergemma4 默认带推理链,不开它会返回空 content)-ctk q8_0 -ctv q8_0:KV 量化,每 token 约 210KB(f16 是 420KB),省一半内存- 不传
-c默认用模型上下文(实测 n_ctx=228608)——本机受显存限制实际上不了那么长,见文末 - 启动后日志应显示
llama_server: listening on http://127.0.0.1:8080
一个关键坑:后台进程必须在你自己的终端手动运行。schtasks / WScript / Start-Process 拉起的进程都会被回收,无法常驻。
客户端调用(OpenAI 兼容)
import json, urllib.request
body = {
"model": "supergemma4",
"messages": [{"role": "user", "content": "用两句话解释为什么天空是蓝色的"}],
"max_tokens": 120,
"temperature": 0.7,
"stream": False,
}
req = urllib.request.Request(
"http://127.0.0.1:8080/v1/chat/completions",
data=json.dumps(body).encode("utf-8"),
headers={"Content-Type": "application/json"})
with urllib.request.urlopen(req, timeout=300) as resp:
data = json.loads(resp.read().decode("utf-8"))
print(data["choices"][0]["message"]["content"])
响应结构兼容 OpenAI,还带推理与计时的扩展字段:
| 字段 | 值 |
|---|---|
| message.content | 正文回答(-rea off 后直接输出) |
| message.reasoning_content | 思考内容(-rea off 后为 null) |
| usage.prompt_tokens_details.cached_tokens | 缓存命中的 token 数 |
| timings.prompt_per_second / predicted_per_second | 服务端测到的 pp / tg 速度 |
多轮对话写法:每轮把全部历史作为 messages 数组传上去(system/user/assistant 交替),服务端自动前缀缓存。
前缀缓存实测
3 轮对话(每轮带全量历史),cached_tokens 递增:
| 轮次 | prompt_tokens | cached_tokens | 说明 |
|---|---|---|---|
| 1 | 19 | 1 | 首次全量 prefill |
| 2 | 69 | 14 | 历史复用 |
| 3 | 103 | 64 | 历史越长,命中越多 |
5 轮递增历史测试更直观:首轮 prefill 1.75s,之后每轮稳定 ~1.2s(只算新增 token),不随历史长度增长。第二轮起即走前缀缓存。
上下文上限探底
| 级别 | 结果 | 内存 |
|---|---|---|
| ≤ 16,124 token(16K) | ✅ 稳定 | 20.8 / 23.5 GB |
| ~20,000 token(20K) | ❌ HTTP 500 | — |
{"error":{"code":500,"message":"decode() failed: vk::Queue::submit: ErrorDeviceLost","type":"server_error"}}
瓶颈不是系统内存(16K 时还剩 ~2.8GB),是 Arc 140T 驱动在长 prefill 时 Vulkan ErrorDeviceLost——模型 15.63GB 大于 Vulkan device-local 堆 13.42GB,强靠 host 溢出撑,长 prefill 触发驱动动态预算崩溃。本机实测可用上下文上限 ≈ 16K;调优方法见20K 长上下文崩溃排查。
总结
- 常驻服务的正确形态:llama-server + OpenAI 兼容 API + 前缀缓存,Agent 多轮只算增量
- 必须 -rea off,否则 supergemma4 思考块把正文顶空
- 服务必须你自己终端手动运行才能常驻
- 上下文上限受核显驱动限制(本机 ≈16K),长上下文要调 -ub 切块
