大模型能把工单读完、把要点提炼出来、写一段礼貌的回复,这些我都信。但真到了代码里,我要的往往只是一个判断:这条诉求该派给政策补贴组还是物业组,紧不紧急,要不要人来处理。让生成模型吐一个 JSON,再靠解析器去拆字段,这条路我走过,脆得很——字段名多一个空格、模型心情一波动,下游就崩。我要的是一个直接给数字的东西:选项、概率、置信度,代码拿到就能写 if/else。这就是决策模型的位置。
前阵子我在本地装了一个叫 Laya 的开源决策模型,照着写了个园区企业诉求分诊的测试脚本。趁热把它的底子、跟 TypeSafe 那个托管 API Jev 的差别,还有怎么微调,都理了一遍,记在这里。
什么是 System One 决策模型
System One 这个词来自丹尼尔·卡尼曼的《思考,快与慢》。他把人的思维分成两套:System 1 快、直觉、几乎不费力;System 2 慢、审慎、要动脑。决策模型取的是前者:快速、聚焦的判断,一次前向传播就出结果,不写一个字,也不解释自己为什么这么判。
它对外只暴露三种问题,叫 primitive,Laya 和 Jev 用的是完全同一套接口:
- choice:从一组选项里挑一个,返回选中项、每个选项的概率、一个置信度。做部门路由、意图分类、主题归类。
- score:按一条量规给状态打序数分,返回期望档位、各档的概率分布、置信度。做情绪强度、工单紧急度、危害等级。
- noul:判断一句话成不成立,返回 0 到 1 之间校准过的 P(true)。做钓鱼检测、垃圾过滤、越狱识别、流失风险。
三种问题可以在一次调用里混着问,对同一个 state 并行、独立地求值。因为不生成文本,所有问题在同一个前向传播里一起算完,加问题几乎不加响应时间。每个问题独立求值,所以加问题也不会污染其它问题的上下文。
它不生成文本,所以没有需要解析的输出,也没有可以编造的内容,幻觉无从谈起。它给的概率是用严格适当评分规则训练出来的,针对结果优化过,代码可以直接拿 confidence 分支:高于阈值就自动走,低了转人工。
为什么「校准」值钱,值得单独说。普通分类器只给你一个 argmax,概率是 softmax 顺手算出来的,并不反映真实出错率。System One 模型把概率本身当训练目标,用严格适当评分规则压着它诚实——报的分布越接近真实得分越高,虚高虚低都吃亏。这样的概率才能进 if/else:0.85 以上自动派单,0.5 附近转人工,这个阈值是有语义的。confidence 有明确算法,来自概率分布的归一化香农熵,选项之间越犹豫,置信度越低。
Jev 是什么
Jev 是 TypeSafe 的旗舰模型,他们管它叫「第一个 System One 模型」,同样对外提供 choice、score、noul 三种 primitive,可以在一次调用里混用。官方对这类模型的界定很干脆:不写回复、不生成代码、不解释自己的推理,可能的答案全由 primitive 定义。差别在交付形态:Jev 是托管 API,权重不公开,走官方 SDK 或 POST /v1/systemone,模型字段默认 jev-latest。
官方的设计哲学一句话:原子问题,在代码里组合。一个问题只问一件具体、范围清晰的事,像一个懂行的人几秒内能给出的直觉判断。要权衡多个独立因素,就拆成多个问题,再用自己的代码加权组合。优先级变了,只改代码里的系数,不用重写 prompt。这句话我认同,它把「改提示词」这种玄学动作,换成了「改一个数字」这种可控动作。
Jev 有几个强项,后面会细说:选项数最多能到 255 个;输出的软分布跟老师模型的全概率分布贴得更近;开箱的原始校准也更好。代价也清楚:权重不在自己手里,数据得发给它;价格方面,有第三方站点称 API 于 2026-09-21 开放、每百万输入 token 收 $0.042、版本线 jev-1.13.0。上面这些数字(包括前面的 255 个选项上限)都是第三方说法,不是官方文档原话,在这里标一下。生态上也能看出它是 API 打头:社区已经有 MCP 封装(比如 jkudish/jev-mcp),能接 OpenRouter、Cloudflare、Vercel 各种网关,都是围着 HTTP 调用转的。
Laya 是什么
Laya 是 Convai Innovations 做的,Apache-2.0 开源,pip install laya 就能装。非自回归,单次前向传播出结果:它一次性给所有选项打出分数,不像生成模型那样一个 token 一个 token 地吐,既快,也没有生成过程可以出错。T4 上单问约 33 毫秒,批量摊到 7.2 毫秒一问,吞吐能到每秒 103 到 332 问。
它其实是三个 checkpoint,配一个 Router 按请求挑:
| checkpoint | encoder | 参数量 | 上下文 | 用途 |
|---|---|---|---|---|
| laya | ModernBERT-large | 421M | 512 | 英文 |
| laya-multilingual | mmBERT-base | 322M | 1024 | 100+ 语言,快一倍 |
| laya-typed-decisions | ModernBERT-large | 421M | 1024 | typed-decisions 工作流 |
Router 在纯 Python 里做脚本检测,亚毫秒级,然后在前向传播之前决定用哪个 checkpoint。
为什么必须路由,这是 Laya 最值得讲的一点。英文 checkpoint 出了英文之后,准确率会塌掉,而且塌得自信。高棉语上准确率 0.000,置信度却有 0.952;它在任何准确率档位上,平均置信度从没掉到 0.885 以下。也就是说,置信度门控救不了你——模型错的时候依然觉得自己对。所以路由得在跑模型之前做,靠脚本和语言,不靠模型的自信。
路由的收益是实打实的。MASSIVE 意图分类上,英文 checkpoint 在英语拿 0.783,多语言版只有 0.657;换到 13 种其他语言,英文版 0.306,多语言版 0.451。XNLI 也一样:英语 0.860 对 0.843,14 种其他语言 0.521 对 0.731。51 种语言里,英文 checkpoint 只有 23 种能过 3 倍随机基线,多语言版有 45 种。对中文本身,两个 checkpoint 也差不多:zh-CN 上英文版 0.620、多语言版 0.630,几乎打平,但脚本检测会稳当地把中文发到多语言版。
两个工程坑,都是看文档才意识到的。
一个是 preload。默认 max_loaded=1,流量里语言一交替,每次请求都要重建模型,实测中位数 CPU 7.4 秒、T4 10.3 秒。生产环境一定 preload=True,让切换语言只花检测那点亚毫秒。
另一个是 head_max_len。choice 的选项共享一个固定预算:英文版 192 token,多语言和 typed-decisions 256 token。77 个选项的 Banking77,每个标签只分到约 3 到 4 个 token,文本都分不清了,准确率掉到 0.425,两个 checkpoint 全是这个数——这是 token 预算顶到了上限,跟能力无关。选项超过 50 个,要么把 head_max_len 调到 512,要么拆成粗到细的两步。
顺带一提,typed-decisions 这个 checkpoint 默认不会被 Router 自动选中,得显式传 model 或开 auto_task_detection——它是针对四个特定工作流微调过的,不该静默当默认。三个 checkpoint 加一起约 11.6 亿参数,所以 Router 默认只留一个热的、按 LRU 淘汰,全预载才占满。
Laya 和 Jev 对比
先说清一件事:Jev 的所有数字都是第三方公开数据。Laya 官方因为拿不到 TypeSafe 的 API,从来没实测过 Jev,两边样本量和 prompt 都对不齐,只能当参考。
| 项 | Jev 1.13.0 | Laya(路由后) |
|---|---|---|
| typed-decisions 准确率(2000 决策) | 0.727 | 0.766 |
| AG News(4 标签) | 0.910 | 0.950 |
| DAIR Emotion(6 标签) | 0.480 | 0.595 |
| Banking77(77 标签) | 0.870 | 0.425 |
| ECE(越低越好,温度拟合后) | 0.246 | 0.081 |
| 单问 p50 延迟 | 236–276 ms | 32.8 ms |
| 可用语言 | 无公开基准 | 51 里 45 种 |
| 权重 | 闭源 API | Apache-2.0 |
| 成本 | $0.042 / 1M token | 自托管 $0 |
Laya 领先的地方:几个共享数据集上准确率略高,延迟快 7 到 8 倍,开源能自托管,多语言覆盖有公开基准。在 typed-decisions 那 2000 个决策上,Brier 是 0.062 对 Jev 报告的 0.148,score MAE 是 0.242 对 0.391,概率质量和序数误差都更好。
Jev 领先的地方也得说清。选项多的场景,Jev 默认支持 255 个,Laya 受 head_max_len 预算限制,77 选项只有 0.425。软分布匹配上,Laya 的 argmax 准确率更高(0.766 对 0.727),但 soft accuracy 反而低(0.471 对 0.580),说明它对老师模型整条概率分布的拟合不如 Jev。原始校准上,Laya 基础 checkpoint 的 ECE 是 0.213,比 Jev 报告的 0.144 差,那个 0.081 是拟合温度之后才拿到的;README 对比表里 Jev 的 ECE 写的却是 0.246,这是第三方口径,两个数对不齐,只说明校准这件事 Laya 要靠拟合温度才追得上来。还有一个细节:DAIR Emotion 上,Jev 有 16% 的样本把真实标签的概率判成了 0,任何拿置信度做分支的逻辑,在这 16% 上会直接失效。
稳定性也值得比:把选项顺序打乱,答案变不变。Jev 报告的是 0.13;Laya 在 MASSIVE 意图(20 选项)上是 0.150、多语言版 0.230,20 个选项下 Laya 更不稳,官方自己说该靠训练时更激进地打乱选项顺序来补。
成本的账要连着算。$0.042 / 1M token 听着便宜,自托管 Laya 的边际成本是 0,但得自己出机器、管部署、扛运维;单价和隐私、延迟是绑在一起的,单看数字没意义。
用起来是什么样
我的脚本是园区企业诉求分诊:一个 choice、一个 score、两个 noul。核心的问题定义长这样:
questions = {
"category": {
"type": "choice",
"instructions": "这条诉求主要涉及哪一类事务?",
"criteria": {
"policy_subsidy": "政策申报、补贴、资质认定",
"facility": "物业、场地、设施报修",
"billing": "费用、账单、发票",
"other": "其他",
},
},
"urgency": {
"type": "score",
"instructions": "这条诉求的紧急程度如何?",
"criteria": ["不急", "一般", "紧急(有明确时限或威胁)"],
},
"needs_human": {"type": "noul", "instructions": "是否需要人工介入处理?"},
"churn_risk": {"type": "noul", "instructions": "企业是否表达了搬离/流失的风险?"},
}
res = router.predict(state, questions)
这段脚本在本机真跑过一次——一台只有 CPU 的机器,torch 2.14.0+cpu,权重已经缓存。Router(preload=True) 冷启加载三个 checkpoint 花了 74.2 秒,导入本身 1.2 秒;单次 predict(一个 state、四个问题)用时 836 毫秒。这是 CPU 上的数字,跟官方在 T4 上量的 33 毫秒单问延迟是两码事。
结果分四块看:
- category 选的是 policy_subsidy,置信度 0.9268,概率分布是 policy_subsidy 0.9836、facility 0.0067、billing 0.0044、other 0.0052。
- urgency 是 score,期望档位 1.0604,三档(0/1/2)的质量几乎全压在第 1 档(0.9187),置信度 0.7149。
- needs_human 的 noul 是 0.2935,churn_risk 的 noul 是 0.8116。
- usage 里 input_tokens 450、output_tokens 0。输出是 0,这是「不生成文本」最直接的证据——它一个 token 都没吐,只给了判断。
路由也写得清楚:model=multilingual,理由是 non-Latin script (han, 100% of letters)。中文输入被发到多语言 checkpoint,跟前文「路由发生在前向传播之前」对上了。
这份输出还藏着两个点。第一,这条工单里其实装着两件事:高新技术企业认定的补贴申请没进展,办公室漏水三天没人管。choice 只选了一个 policy_subsidy,把漏水那半压了下去。所以要么把问题问成「主要涉及哪一类」,要么拆成两个独立问题分别判,这正是官方说的原子问题、在代码里组合。第二,紧急度只给了 1.06,没因为「要搬走」就冲到最高档;「要搬走」这个威胁被单独的 churn_risk 问题接住了,0.8116。把不同维度拆成独立问题、再由代码定阈值,比逼一个模型吐综合结论更可控。
环境上踩了两个坑,记下来。一是 HF 直连不通,脚本里设 HF_ENDPOINT=https://hf-mirror.com 走镜像。二是机器设了 SOCKS 代理而 venv 里没装 socksio,导入直接 ImportError;权重缓存到本地后,用 HF_HUB_OFFLINE=1 离线跑最省事,顺手把 all_proxy 这类变量清掉。首次运行要下权重,三个 checkpoint 在本机缓存里实测是 2.3 GB。
这四个数落到代码里就是几个 if:category 是 policy_subsidy 且置信度 0.93,直接自动派到政策补贴组;churn_risk 0.81 过了阈值,单独拉起一条流失预警;needs_human 0.29 说明暂时不用转人工。阈值写在哪、怎么调,都是你自己的代码,模型只负责把判断给准。
如何微调 Laya
先破一个预期。基础 checkpoint 在 typed-decisions 基准上是接近随机的:0.362 和 0.342,对比随机基线 0.318、多数类基线 0.461。也就是说,它连「全都猜最大类」都比不过。那个 0.766 的成绩全部来自微调,而且是在这个基准自己的训练集上微调出来的,换到你的领域得重新喂数据。Laya 是个好底子:快、能并行、概率可校准,但它零样本决策引擎的说法站不住,价值几乎全在微调这一步。
数据。官方 notebook 用的是 LocalLLaMA/typed-decisions,1200 个 case、6000 个 decision。把 (state, questions, gold 概率分布) 转成训练项,每个问题类型的目标都是软标签的概率分布,硬标签会丢掉信息。choice 的目标是每个选项标签的概率,按选项顺序从 gold 的 probabilities 里取;noul 的目标是 [P(false), P(true)];score 的目标是每个档位的概率。拿到分布先归一化,和为 0 就退成均匀分布;再用 build_sequence 把 state 和问题拼成带 [MASK] marker 的 token 序列,marker 数量必须和选项数对齐,对不上的样本直接丢掉。
算法是 RLCD:策略梯度加严格适当评分规则当奖励。适当评分规则的意思是,如实报出自己的概率分布,期望得分才最高,模型被逼着对不确定性诚实,光会 argmax 拿不到最高奖励。proper_reward 里是对数得分加球形得分,score 型问题再减一个秩概率得分。训练时做 group sampling:给 logits 加零均值投影噪声,采出一组分布,用适当评分规则算奖励,再减均值、除标准差做优势归一化当基线。最终损失等于策略梯度项加 1.0 倍软交叉熵,软交叉熵是对整条概率分布的直接监督。
关键超参,照 notebook 抄,不改数字:
| 超参 | 值 |
|---|---|
| epochs | 4 |
| micro batch | 8 |
| grad accum | 4(2×T4 等效 64 条序列) |
| GROUP_SIZE | 4 |
| encoder LR | 2.5e-5 |
| head LR | 1e-4 |
| weight decay | 0.01 |
| 退火 | cosine 到 1e-6 |
| 梯度裁剪 | 1.0 |
| 探索噪声 σ | 0.4 → 0.1 |
| 精度 | fp16 autocast + GradScaler |
| max_tokens_per_batch | 4096 |
| max_len / head_max_len | 1024 / 256 |
| gradient checkpointing | 开 |
这堆工程参数(gradient checkpointing、fp16、max_tokens_per_batch)各有各的用处,都是让 421M 参数的模型在 16GB 显存的 T4 上跑得动、还要同时塞下 4 个噪声采样的代价。显存吃紧就减 micro batch,别硬扛。
训练脚本用 torchrun 拉起,两张 T4 各跑一个进程:
torchrun --standalone --nproc_per_node=2 train_ddp.py "$MODEL_DIR" "$OUTPUT_DIR"
校准是收益最高、也最容易被漏掉的一步。训练完,在留出数据上按(问题类型、选项数)分桶,用 LBFGS 最小化负对数似然去拟合温度,写回配置。notebook 里是三个问题类型各拟合一个温度,拟合完 clamp 在 0.1 到 10 之间;README 报告的更细粒度是按(类型、选项数)分桶。官方数据:laya 的 ECE 从 0.466 降到 0.081,多语言版从 0.314 降到 0.106。两个 checkpoint 出厂都偏自信,多语言版甚至没带任何拟合过的温度。这一步不做,前面训练出的概率就不可用,代码按 confidence 分支等于拿一个系统偏差在赌。
评估看五样:accuracy、soft accuracy、Brier、ECE、score MAE,并且分工作流看。要提一个参照系:老师自一致性上限。基准的标签来自一个老师模型给的概率分布,0.735 是老师自己再判一遍时跟自己一致的比例,也就是这条数据的实际天花板;微调后的 Laya 是 0.766,已经贴着天花板了。分工作流看也稳:invoice processing 0.804、security incidents 0.766、customer service 0.764、agent-trace observability 0.730,四个都过了 0.73。按 primitive 拆:noul 0.857、choice 0.733、score 0.723,score 确实是最弱的一头。这套参照系搬到自己领域也一样:先量老师自一致性,再量微调模型,才知道你追的是数据上限还是模型上限。
时间成本两个口径,分开说:README 写的规模是 2×T4、4 epochs、约 3 万道题,4 到 5 小时;notebook 自己的目标规模是 1200 个 case、6000 个 decision,单次 4 到 6 分钟。两者规模差着几十倍,别拿一个当另一个。
保存与加载:训练脚本每轮存一个滚动 checkpoint,最后保存 model.safetensors 加 encoder、tokenizer,外加一份改写过的 rl_agent_config.json——写进 fine_tuned=True、model_name、拟合出的 temperature。加载就是 laya.Agent(本地路径) 一行。
整个微调闭环就五步:造数据、跑 RLCD 训练、拟合温度、评估、保存加载。最容易被跳过的是拟合温度,最容易被高估的是零样本能力。把这两件事记住了,剩下的照 notebook 抄就行。
什么时候该用
判断里要的是概率可校准、延迟低、能并行、能进代码分支,而且数据想留在本地,就值得试 Laya。选项数超过 20 的 choice,Laya 的 head_max_len 预算会顶到,优先考虑 Jev 或者拆成两步;score 是三个 primitive 里最弱的,别把关键的序数判断全押在它上面;如果不想碰训练和部署,Jev 的托管 API 开箱就能用,代价是权重和数据都在别人手里,按 token 付钱。选哪个,取决于你更在意延迟和成本,还是让数据出本地。
最后说句实在的。这个领域还年轻:Laya 是 0.3.4 的 beta,Jev 的公开数字也才一版。别指望它替你判断,它替你把判断变成一串能进代码的数字;阈值、组合、兜底,还是你自己的活。
来源:Laya 的 README、BENCHMARKS 与官方微调 notebook,以及 TypeSafe 官方文档。Jev 的性能数字均为第三方公开数据,本文未做实测。