决策模型这条线,我前后写过两篇:Jev 上 OpenRouter 的云端用法,以及 Deem 在 Mac 上用 Python 跑 CPU 推理。两篇的共同遗憾是——不管云端还是本地,你都被绑在某个特定项目的运行方式上,换个模型就得重来一遍。10 月 2 日这事有了转折:PR #29818 合进了 llama.cpp 主线,llama-server 直接内置 /v1/systemone 端点,官方一口气把五个开源决策模型转成了第一方 GGUF。以后跑决策模型和跑聊天模型一样:一条命令起服务,换模型只换文件名。
我在 M4 MacBook(16GB 统一内存)上把 Kev-4B 和 Julia-1 两个模型完整跑了一遍,官方示例的概率输出复现到小数点后四位,也踩进了一个谁都会踩的版本陷阱——Homebrew 装的 llama.cpp 跑不了决策模型,报错还特别迷惑。整个过程和所有数据都在这篇里。
先说清楚决策模型是什么
如果你没读过前两篇,一分钟补个背景:决策模型不生成文字,它读一遍你给的「状态」(state),然后对你列出的选项打分,输出每个选项的概率。聊天模型每生成一个 token 要跑一次前向,输出完还得解析 JSON;决策模型一次前向直接出答案,选项永远是你给的那几个。典型的用法是工单路由、内容审核、检查 Agent 某一步是否成功。
这次 llama.cpp 官方转好的五个模型,数据来自 ggml-org 的公告:
| 模型 | 大小 | 基座 | 语言 | 许可证 | 官方延迟* |
|---|---|---|---|---|---|
| Julia-1 | 144M | mmBERT-small | 50+ 语言 | Apache 2.0 | 3ms |
| Laya | 421M | ModernBERT-large | 英语 | Apache 2.0 | 5ms |
| Kev-4B | 4B | Qwen3.5-4B-Base | 英语 | Apache 2.0 | 12ms |
| lev | 4B | Qwen3.5-4B | 英语 | Apache 2.0 | 36ms |
| OpenJev | 27B | Qwen3.8-27B | 6 语言+图像 | CC BY-NC 4.0 | 43ms |
*官方口径是单张 RTX PRO 6000 上回答一个问题的中位耗时,和我后面测的 M4 数据不是一回事。
有意思的是这五个模型的来路:除了 Laya 是 Convai Innovations 的作品,Julia、Kev、lev、OpenJev 全是 9 月中旬 TypeSafe Jev 火了之后社区复刻潮的产物。九天前那波「48 小时冒出六个复刻」的狂欢,现在被 llama.cpp 收编成了官方支持列表——开源社区从造模型到进基建,整个链路三周走完。
版本陷阱:brew 装的 llama.cpp 跑不了
这是本次最大的坑,先把结论放前面:GitHub 上 tag 为 v0.5.0 的最新 release(9 月 23 日发布)不包含决策模型支持,Homebrew 装的就是这个版本。但如果你照着官方 README 敲 llama serve -hf ggml-org/Kev-4B-GGUF,看到的是两条毫无头绪的报错。
我用 brew 装的 0.5.0(build 11146)实测,加载 Kev-4B 报:
llama_model_load: error loading model: done_getting_tensors:
wrong number of tensors; expected 428, got 426换 Julia-1 试,报错换了一副面孔:
llama_model_load: error loading model vocabulary:
unknown pre-tokenizer type: 'mmbert'我第一时间怀疑下载截断,逐字节核对了文件大小——和 HF API 报的完全一致(Kev-4B-Q4_K_M.gguf 3,033,489,824 字节),排除。真正的原因是构建太旧:PR #29818 是 10 月 2 日合并的,v0.5.0 比它早九天,构建里根本没有 mmBERT 分词器和决策头张量的处理逻辑。expected 428, got 426 这种报错完全是在猜哑谜。
正解是官方公告里那句容易被略过的话:从 llama.app 拿最新构建。macOS 和 Linux 一条命令:
curl -LsSf https://llama.app/install.sh | sh安装脚本会自动探测硬件(我这台识别出 Metal + M4),从 HF 上拉 22MB 的二进制装到 ~/.llama-app/llama。装完的版本号很迷惑——还是叫 0.5.0,但 build 号从 11146 变成了 11379,后面这个才带决策模型。验证方式也简单:启动时日志里出现 init: decision model type: kev 就是好的。
跑起来:官方示例复现
先用 ggml-org/Kev-4B-GGUF(Q4_K_M 量化,3GB)起服务:
llama serve -m Kev-4B-Q4_K_M.gguf --port 8097 --host 127.0.0.1
# 或者直接从 HF 拉(会缓存到本地)
llama serve -hf ggml-org/Kev-4B-GGUF启动只要八秒,日志里两行值得看:decision model reads the embeddings output, enabling embedding mode——实现上决策模型复用了 embedding 的输出通路;以及 n_ctx_slot = 214272——上下文窗口给了 214K。
然后发请求。官方公告给了个客服工单的示例,我原样复现(环境:llama.cpp b11379,Kev-4B Q4_K_M,macOS arm64):
curl http://127.0.0.1:8097/v1/systemone \
-H "Content-Type: application/json" \
-d '{
"state": "Customer message: I was charged twice for my order last week and nobody has replied.",
"questions": {
"route": {
"type": "choice",
"instructions": "Which team should handle this?",
"criteria": {
"billing": "payments, charges, refunds, invoices",
"shipping": "delivery, tracking, lost or late parcels",
"technical": "bugs, errors, login problems"
}
},
"angry": { "type": "noul", "instructions": "Is the customer angry?" },
"urgency": {
"type": "score",
"instructions": "How urgent is this?",
"criteria": ["can wait", "this week", "today", "right now"]
}
}
}'返回的结果和公告文档里的数字对得上,小数点后四位一模一样:
{
"model": "Kev-4B-Q4_K_M.gguf",
"answers": {
"route": {
"type": "choice",
"choice": "billing",
"probabilities": { "billing": 0.9049, "shipping": 0.0275, "technical": 0.0676 },
"confidence": 0.8574
},
"angry": { "type": "noul", "noul": 0.8208 },
"urgency": {
"type": "score",
"score": 2.2820,
"legend": { "0": "can wait", "1": "this week", "2": "today", "3": "right now" },
"probabilities": { "0": 0.036, "1": 0.1937, "2": 0.2227, "3": 0.5476 },
"confidence": 0.282
}
},
"usage": { "input_tokens": 130, "output_tokens": 0 }
}output_tokens: 0 这行是我最在意的——「一次前向、零生成」是真的,不是营销话术。这条请求里三个问题(choice、noul、score 各一)打包返回,一个请求解决一轮完整判断。
延迟方面我在本机跑了 10 次热请求:中位 501ms(每次 3 个问题),对照官方 RTX PRO 6000 的 12ms/题,M4 集成显卡慢 14 倍左右,属正常水平。对路由、审核这类后台场景,半秒完全够用;首次冷请求约 1.4 秒,预热后就稳定了。换成 Julia-1(144M)之后,热请求只要 11 到 16ms,和官方 3ms 的量级就对上了。
中文表现:模型卡说英语 only,实际中文全对
这是本次实测里最有意思的发现。Kev-4B 的模型卡标注语言是 English(微调数据集全是英文分类基准),我把官方示例的 state 换成纯中文再问了一遍:
# 环境:llama.cpp b11379 + Kev-4B-Q4_K_M,Python 3.13
import json, urllib.request
def ask(state, questions):
payload = json.dumps({"state": state, "questions": questions}).encode()
req = urllib.request.Request(
"http://127.0.0.1:8097/v1/systemone",
data=payload,
headers={"Content-Type": "application/json"},
)
with urllib.request.urlopen(req, timeout=30) as r:
return json.load(r)
result = ask(
"客户消息:我上周的订单被扣了两次款,联系客服一周了没人回复。",
{
"route": {
"type": "choice",
"instructions": "Which team should handle this?",
"criteria": {
"billing": "payments, charges, refunds, invoices",
"shipping": "delivery, tracking, lost or late parcels",
"technical": "bugs, errors, login problems",
},
},
"angry": {"type": "noul", "instructions": "Is the customer angry?"},
},
)
print(json.dumps(result["answers"], ensure_ascii=False))结果:路由照旧给 billing(0.9048,英文原句是 0.9049),怒气判断 0.8445 还略高于英文的 0.8208。原因不神秘——Kev-4B 的底座是 Qwen3.5-4B-Base,阿里的模型,中文能力是底座自带的,微调只是往上加了个决策头。模型卡的语言标注说的是微调数据口径,不是能力边界,这条对选型很有用。
还有个附带好处:同样一句话,中文只用 94 个 token,英文要 130——省了 28%。决策模型按次调用本来就不要钱,token 数影响的更多是长 state 下的预处理耗时,但这个差距方向是反直觉的,记下来。
Julia-1 那边我测了中、英、日、法、韩、阿拉伯六种语言的情感分类,全对,置信度都在 0.999 以上,50+ 语言的说法在抽测里站得住。但小模型的弱点也真实存在:一句完全中性的「这家餐厅位于市中心,营业时间是上午十点到晚上十点」,它给 positive 的置信度高达 0.9578。144M 的模型判断「没有情绪」这件事还学不会,拿它做审核分流时记得这一点。
两个公告里没写的行为
边界测试时撞到两个文档没提的情况,都验证过了。
第一,错误处理符合文档:非法的问题类型给 400,"type" must be one of: choice, score, noul;空 questions 给 400;score 传 11 级也被 400 拒掉(上限 10 级)。照着文档写客户端不会被坑。
第二,决策模型走 /v1/chat/completions 竟然能用——返回 200,Kev-4B 降级成普通聊天模型,用 Qwen 的思考风格回了一句带 emoji 的问候,速度 27.9 tok/s。也就是说同一个服务进程里,/v1/systemone 是分类器、/v1/chat/completions 是聊天机器人,一个模型两用。这算不上设计承诺(公告只写了「非决策模型访问 systemone 会返回 501」,反过来没说),做集成时别依赖它,但排查问题时知道这条路存在能省不少时间。
讽刺是盲区,这些场景别用
noul(是非题)的实测里我留了一组对照:愤怒的投诉信 0.9373,礼貌的咨询 0.0306,区分得很开。但换一句「Oh wonderful, the package arrived empty again. Love that for me.」——包裹又空着送到了,反话正说——概率是 0.5112,抛硬币水平。表面积极的词(wonderful、love)把信号全盖住了。
所以我的不建议清单:情绪识别如果业务上需要处理讽刺、反话(社媒监听、舆情场景),4B 决策模型不够用,老老实实上大模型;需要「没有情绪」这种中性判断的,Julia-1 这个体量别碰;至于把决策模型当通用 NLU 用、想让它理解没给过的选项——它做不到,选项必须在 criteria 里列全,这是协议设计而非缺陷。
顺带一提生态现状:SGLang 也在 10 月初上了自家的 /v1/decisions 端点,和 llama.cpp 的 /v1/systemone(沿用 TypeSafe Jev 的名字)互不兼容,两家 32 小时内先后发布。决策模型的 API 现在还没有统一标准,选哪家就意味着选哪种请求格式,迁移时得改代码。
上手路径建议
按需求选模型就行:要快、多语言、跑在最便宜的硬件上,Julia-1(144M,168MB 的 Q8 包)先试;要判断质量和置信度,Kev-4B;需要看图(截图审核、单据分类)且能接受非商用许可,上 OpenJev(27B,Q4 要 19GB,16GB 内存的 Mac 装不下,得有独显或 32GB+ 内存)。
安装认准 llama.app/install.sh,别用 Homebrew 的 v0.5.0;拿到手先跑一遍官方示例对一下概率数字(我这边的基准:billing 0.9049 / noul 0.8208 / urgency 2.282),对上了说明版本没问题。之前在 Jev 那篇里留过「本地跑 Kev」的待办,这次算是还上了——而且比预期顺利,除了那个版本陷阱。本地部署的更多姿势可以接着看 MiniCPM5-2B 端侧部署和 NVIDIA PAIR 局域网推理集群,把决策模型挂进现有 Agent 流水线,正好补上「每一步前先廉价判断一下」的那块拼图。