koboldcpp 是一个把 llama.cpp 包起来的单文件程序:一个 exe 117254353 字节,双击或一行命令就能把 gguf 模型加载起来,对外提供一个兼容 OpenAI 的 HTTP 接口。

实测从敲命令到接口能用 8.07 秒,之后调一次对话接口 0.08 秒。它自己会挑后端:这台机器有张 RTX 3060(11558 MiB 可用),它选了 Vulkan,自动把 25 层丢到显卡上。

112 MB 的 exe,模型自己配

koboldcpp.exe --model model.gguf --port 5001 --threads 8 --contextsize 2048 --launch

程序本体 117254353 字节,自带 Python 运行时和一堆 dll,不需要先装 llama.cpp,也不用配环境变量。--launch 的意思是跳过配置界面直接启动,不加这个参数会弹一个图形配置窗口。

模型是单独的 gguf 文件,实测用的那个 491400032 字节(Qwen2.5 0.5B 的 Q4_K_M 量化)。程序和模型完全解耦,换模型只换路径。

这个参数名要留意:--nobrowser 在老版本有,新版本已经删了,写了会直接打印 usage 并退出。控制浏览器要改用 --launch。

9 秒起服务,之后 0.15 秒一次对话

指标数值说明
冷启动到接口可用8.07 秒含模型加载与后端初始化
一次生成(60 token 上限)0.47 秒/api/v1/generate
一次对话(OpenAI 协议)0.08 秒14 prompt + 7 completion token
程序体积117254353 字节112 MB,含运行时
模型体积491400032 字节468 MB,Q4_K_M

8.07 秒这个启动时间只需要付一次:服务常驻,之后每次请求不再加载模型。对比 llama.cpp 那种一次一进程的用法,单次问答要 1.19 秒,其中 0.61 秒是进程启动。

0.08 秒那次只生成 7 个 token,因为提示词是「用五个词打招呼」,模型自己就停了。这个延迟里包含了 HTTP 往返、prompt 处理和 7 次解码。

它自己挑了 Vulkan,把 25 层丢给显卡

No GPU or CPU backend was selected. Trying to assign one for you automatically...
Auto Selected Vulkan Backend (flag=0)
Auto Recommended GPU Layers: 25
Detected Available GPU Memory: 12288 MB
Detected Available RAM: 9590 MB
ggml_vulkan: Found 1 Vulkan devices:
  llama_prepare_model_devices: using device Vulkan0 (NVIDIA GeForce RTX 3060) - 11558 MiB free

没有任何加速参数,它就自己判断出有显卡可用,选了 Vulkan 后端并把 25 层卸载过去,日志里连显卡型号(RTX 3060)和剩余显存(11558 MiB)都打出来了。

想强制走 CPU 就加 --usecpu,想指定层数用 --gpulayers。同一个模型在纯 CPU 上的生成速度可以参考 llama.cpp 那篇的实测:8 线程 79.34 token/秒。

Vulkan 这条路的好处是不用装 CUDA。缺点是第一次启动要编译着色器,比之后几次慢得多,机器换了显卡驱动还要重编一遍。

兼容 OpenAI 接口意味着什么

起好服务后,/v1/chat/completions 和 /v1/models 都能用,请求体跟 OpenAI 的格式一致:

{
  "model": "local",
  "messages": [{"role": "user", "content": "Say hello in five words."}],
  "max_tokens": 40,
  "temperature": 0.2,
  "stream": false
}

返回里带标准的 usage 字段,那次是 prompt_tokens: 14、completion_tokens: 7、total_tokens: 21。任何认这个协议的客户端(包括各种聊天前端、编辑器插件、SDK)改一下 base_url 就能接过来。

这是单文件程序里最实用的一个设计:不用改应用代码,只换一个地址,就把请求从云端挪到了本地。

上限不等于实际生成量

用同一个提示词、只改生成上限,测出来的耗时是:上限 32 用 0.67 秒、128 用 0.65 秒、512 用 1.99 秒。128 比 32 还快,因为模型在前两种情况下都只写了一小段就自然停了,上限根本没被用满。

这件事影响所有「按 token 数估耗时」的估算。真正决定耗时的不是 --contextsize 或 max_length,而是模型自己决定在哪停。做容量规划时要按最坏情况算,也就是把上限用满的那一档:512 token 约 2 秒。

启动之后要先确认的事

第一件是端口。默认 5001,被占用时会失败,但报错藏在日志末尾,只看控制台前几行容易以为是模型加载失败。

第二件是上下文长度。--contextsize 2048 意味着超过这个长度的对话会被截断,而且不报错。做长文档处理时要提前调大,代价是内存占用跟着涨。

第三件是模型没加载完就发请求。接口还没起来时连接会被拒绝,脚本里要轮询 /api/v1/model 直到返回 200,别用固定 sleep。