llama.cpp 决策模型实测:/v1/systemone 一次前向出概率,M4 跑通 Kev-4B,中文比英文省 28% token

决策模型这条线,我前后写过两篇: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 报:

Plaintext
llama_model_load: error loading model: done_getting_tensors:
wrong number of tensors; expected 428, got 426

换 Julia-1 试,报错换了一副面孔:

Plaintext
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 一条命令:

Bash
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)起服务:

Bash
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):

Bash
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"]
      }
    }
  }'

返回的结果和公告文档里的数字对得上,小数点后四位一模一样:

Json
{
  "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 换成纯中文再问了一遍:

Python
# 环境: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 流水线,正好补上「每一步前先廉价判断一下」的那块拼图。

推荐阅读

  • OpenAI Dots 开源平替实测:Mac 自托管 Open Dots,30 行代码吃上本地模型

    OpenAI Dots 限 Pro 订阅还缺席多数地区,开源平替两天涨了四百星。我在 M4 Mac 上实测自托管全流程:五分钟起服务,发现它不兼容 Chat Completions 协议,Ollama…

  • Claude Code Mods 上手:用 TypeScript 改写 AI 编程工具的行为,不登录也能跑通官方测试

    Claude Code 推出 Mods:用 TypeScript 函数改写提示词、拦截工具调用、替换内置功能。我在 Mac 上实测了完整流程:从 2.1.226 升级到 2.1.287,发现官方脚手架…

  • OpenAI DevDay 2026 全面解读:Dots 智能体、GPT-6.1 Sol、Codex Cloud,开发者现在能上手什么

    DeepSeek Harness 推出桌面端 v0.2.0-rc.2,从开源框架变成开箱即用的 macOS/Windows 应用。我在 Mac 上实测了完整安装流程:353MB DMG、1GB 体积、…

  • 开源决策模型 Deem 上手:0.8B 塞进 1.4GB 内存,我在 Mac 上复现了「30 进 31 出」

    LibertAI 开源的决策模型 Deem 把「是/否、分类、评分」判断拉回本机。我在 Mac 上实测 0.8B 版:1.4GB 权重、1.37GB 内存,复现了「30 进 31 出」的退货窗口边界,…