一条客户工单进来,系统需要判断:该交给哪个部门?是否需要人工介入?处理优先级有多高?

过去,我们通常把这些问题交给大模型,让它生成一段 JSON,再解析、校验,接入业务流程。

但这里有一个值得追问的问题:

如果我们只需要几个判断结果,为什么一定要让模型先“写一段话”?

Jev 展示了另一种方向:输入业务状态,直接输出结构化决策和概率。对个人开发者来说,更有意思的是,我们已经可以借助开源项目 Kev,在自己的电脑上尝试这条路线。

本文以一台 Apple M5 Pro、24 GB 内存的 Mac 为例,从环境准备开始,介绍如何训练和运行一个小型决策模型。以下是操作教程,尚未完成这台机器上的训练与性能实测。

一、Jev 改变了什么?

TypeSafe 将 Jev 定位为 System One 模型。与逐 token 生成文本的大模型不同,官方强调它放弃字符串生成,直接并行输出类型明确的决策结果,并使用称为 RLCD 的训练方法优化决策概率。官方介绍

以工单分流为例,我们需要的可能只是:

{
  "department": "billing",
  "probabilities": {
    "billing": 0.92,
    "shipping": 0.05,
    "account": 0.03
  }
}

这里的数字只是格式示例,不是实测结果。

对于程序来说,明确的选项和概率可以直接进入后续流程:分派部门、调整优先级,或者提交人工复核。

不过,输出格式正确与判断正确是两件事。模型只能从指定选项中选择,并不意味着它一定选对。

二、个人训练的入口是 Kev

Jev 的完整架构和训练配方没有公开,因此不能把下面的过程称为“复现 Jev”。

我们使用的是 Kev:一个基于 Qwen 构建的开源决策模型项目,公开了模型实现、训练入口和评估工具。

它的基本处理过程是:

业务材料+问题+候选选项
            ↓
        Qwen 主干
            ↓
  决策位置和选项的隐藏向量
            ↓
      PointerHead 打分
            ↓
       各选项的概率

普通语言模型通过词表输出头预测下一个 token。Kev 将它替换为对候选选项打分的决策头,因此能够省去文本生成循环。它并没有跳过 Transformer 主干层:输入仍然经过主干网络,省去的是逐 token 生成输出时的重复计算。

训练时,Kev 冻结原始底座权重,更新 LoRA 适配器和决策头,让模型学会根据问题选择答案。模型实现

我们要做的,就是准备环境和数据,把这套训练过程跑起来。

三、先选一个能在个人电脑上验证的模型

本文选择:

项目 配置
电脑 Apple M5 Pro,24 GB 内存
底座 Qwen3-0.6B-Base
计算设备 Apple GPU,PyTorch MPS
训练方式 LoRA+决策头
起步任务 使用公开数据训练通用决策原型

为什么不直接使用更大的模型?

第一次训练最需要确认的是:数据能否正确加载、损失是否正常、检查点能否保存,以及训练后的模型是否真的优于训练前。

小模型可以更快暴露这些问题。

本文采用 Qwen3,而不是当前 Kev 默认推荐的 Qwen3.5 系列,也是考虑到 Mac 上的执行效率。项目说明指出,Qwen3.5 的 DeltaNet 层目前缺少 Apple Silicon 高效内核。性能说明

四、安装环境,检查 GPU

以下命令需要先安装 Git 和 uvuv 的安装方式可以参考其官方文档

打开终端:

git clone https://github.com/jaredpalmer/kev.git
cd kev

uv python install 3.12
uv sync --python 3.12 --extra serve

记录仓库版本,方便之后重现实验:

git rev-parse HEAD

检查 MPS:

uv run python - <<'PY'
import torch

print("PyTorch 版本:", torch.__version__)
print("MPS 是否可用:", torch.backends.mps.is_available())

assert torch.backends.mps.is_available(), "请先检查 MPS 环境"
PY

输出 True 后,再开始训练。

首次运行会下载模型和数据。下载耗时与训练耗时应分开记录,不要据此判断本机 GPU 的训练速度。

五、先跑一次小规模训练

先从每个默认数据源抽取 40 条,训练一轮:

uv run python -m kev.train \
  --base Qwen/Qwen3-0.6B-Base \
  --device mps \
  --dtype fp32 \
  --n_per_source 40 \
  --epochs 1 \
  --lr 1e-4 \
  --lora 16 \
  --batch 1 \
  --accum 4 \
  --out runs/mac-smoke

几个参数值得理解:

  • --device mps:使用 Apple GPU。
  • --batch 1:每次处理一条记录,控制内存占用。
  • --accum 4:积累四次微批次梯度后更新参数。
  • --lora 16:使用 rank-16 LoRA。
  • --dtype fp32:使用适合这条 Mac 训练路径的单精度。

当前训练脚本的 --dtype bf16 使用 CUDA autocast,不应直接把 NVIDIA 示例里的这个参数照搬到 Mac。训练源码

训练结束后:

ls -lh runs/mac-smoke
cat runs/mac-smoke/training_metrics.json

检查输出目录中是否包含适配器、head.pt 和训练指标。

这一步的成功标准是训练链路完整,不是模型已经可以上线。

训练器会拒绝覆盖已有输出目录。再次尝试时,换用 runs/mac-smoke-02 等新名称。

六、扩大数据,训练自己的原型

小规模训练正常后,可以扩大到每个默认数据源 1,500 条,训练两轮:

uv run python -m kev.train \
  --base Qwen/Qwen3-0.6B-Base \
  --device mps \
  --dtype fp32 \
  --n_per_source 1500 \
  --epochs 2 \
  --lr 1e-4 \
  --lora 16 \
  --batch 1 \
  --accum 8 \
  --out runs/mac-kev-06b

这是一套便于个人电脑起步的配置,不等同于当前发布版 Kev 的完整复现配方。

训练时接通电源,并保持只有一个训练进程使用 Apple GPU。如果出现内存不足,可以在命令中加入:

--checkpointing 1

同时换一个输出目录。梯度检查点通过重新计算部分中间结果来降低内存需求,代价是训练会变慢。

不要只盯着 loss。损失下降说明模型在拟合训练数据,能否处理新问题,需要单独验证。

七、训练之后,先评估

使用仓库提供的迁移评估集:

uv run python -m kev.benchmark \
  --run runs/mac-kev-06b \
  --suite evals/v4/transfer-v4 \
  --out runs/mac-kev-06b-eval

这条命令评估的是该套件的开发集。

除了准确率,还应关注概率预测误差,以及模型在不同类别上的表现。一个整体准确率不错的模型,可能恰好在你最关心的类别上表现很差。

选项顺序也值得测试。同一条材料,把“退款、物流、账号”换成“账号、退款、物流”,如果答案频繁变化,就需要继续检查数据和模型。

Kev 的评估工具覆盖了准确率、Brier score、校准等指标。评估说明

八、把模型作为本地 API 运行

启动服务:

uv run --extra serve python -m kev.serve \
  --run runs/mac-kev-06b \
  --port 8009

保持这个终端运行,在另一个终端发送请求:

curl -sS http://127.0.0.1:8009/v1/systemone \
  -H 'Content-Type: application/json' \
  --data-binary @- <<'JSON'
{
  "state": "我的订单被重复扣款了,请退回多扣的钱。",
  "model": "kev-latest",
  "questions": {
    "department": {
      "type": "choice",
      "instructions": "选择应该处理这条工单的团队。",
      "criteria": {
        "billing": "付款、账单和退款",
        "shipping": "发货、物流和配送",
        "account": "登录、密码和账号权限"
      }
    }
  }
}
JSON

接口应返回选项及其概率。现在可以把它接入一个简单应用,观察真实输入下的表现。

这还不是训练流程的终点。公开数据训练出来的模型,未必理解你的业务分类方式。继续训练前,在服务终端按 Ctrl+C 停止服务,释放模型占用的内存。

九、让模型学会自己的中文业务

真正决定业务效果的,往往是标注是否清楚。

例如“付款成功但实例没有创建”,到底交给支付团队还是计算团队?如果标注人员自己没有统一规则,模型也很难学到稳定边界。

先整理一批真实业务记录,保存为 JSONL,每行一个完整 JSON:

{"state":"订单被重复扣款,请退回多扣的钱。","questions":{"department":{"type":"choice","instructions":"选择处理团队。","criteria":{"billing":"付款、账单和退款","shipping":"发货、物流和配送","account":"登录、密码和账号权限"},"label":"billing"}}}

建议先从几百条人工核对过的数据开始,覆盖常见问题、易混淆问题和信息不足的情况。

将数据按事件或工单划分为:

train.jsonl        用于训练
validation.jsonl  用于调参和选择模型
test.jsonl        用于最终验收

同一工单的不同改写应放在同一个集合,避免模型在测试时遇到几乎见过的内容。

然后从已有检查点继续训练:

uv run python -m kev.train \
  --base Qwen/Qwen3-0.6B-Base \
  --init_from runs/mac-kev-06b \
  --data train.jsonl \
  --device mps \
  --dtype fp32 \
  --epochs 2 \
  --lr 2e-5 \
  --lora 16 \
  --batch 1 \
  --accum 8 \
  --out runs/mac-kev-business

这里最重要的是 --init_from:它加载已经训练好的 LoRA 和决策头,继续学习新业务。它不会保证旧能力完全保留,因此微调后还应检查原有任务是否退化。

用同一份验证集比较微调前后:

uv run python -m kev.benchmark \
  --run runs/mac-kev-06b \
  --data validation.jsonl \
  --out runs/business-before

uv run python -m kev.benchmark \
  --run runs/mac-kev-business \
  --data validation.jsonl \
  --out runs/business-after

选定配置后,再评估测试集:

uv run python -m kev.benchmark \
  --run runs/mac-kev-business \
  --data test.jsonl \
  --out runs/business-final-test

最后,把服务命令中的模型路径换成 runs/mac-kev-business,就能调用自己的业务模型。

个人训练最值得追求的第一个结果,是一个具体、可验证的小能力:能把自己业务里的工单分对,知道哪些输入容易出错,并在实际软件中稳定运行。

Jev 让人看到了直接决策的应用空间,Kev 则提供了一个可以动手实验的入口。选定一个高频判断任务,整理真实数据,完成训练前后的对照,比一开始追求更大的参数规模更有价值。