1. laya 是什么
laya 是一个跑在自己机器上的判断模型:给它一段材料(工单、邮件、命令、JSON)和几个预设问题,它返回选项和概率,不生成任何文字。
它是照着 Jev 的接口做的开源替代:比 Jev 快得多、免费、能自己部署,但开箱能力弱一档,选项一多就崩,要用得先拿自己的数据微调。下面说的快和弱,参照物都是 Jev。
1.1 和 Jev 的关系
2026 年 9 月 15 日 TypeSafe AI 发布 Jev,闭源托管、邀请制。三天后 laya 出现在 GitHub 上,由 Convai Innovations 开发。
laya 复现的是 Jev 的接口和那套概念,模型本身是另一套。它是三个 encoder 加一个决策头,英文基于 ModernBERT-large 421M 参数,多语言基于 mmBERT-base 322M,权重都是自己训练的。Jev 只提供托管 API,laya 的基准测试里也从没调用过 TypeSafe 的接口,取的都是官方公布的数字。
接口这一层对得上。laya 的 HTTP 服务源码开篇注释写着自己的定位:对外提供 TypeSafe Jev 那套 /v1/systemone 接口。照着 Jev 写的客户端,把 baseUrl 指到 laya 的地址就能继续用,返回结构一致,choice / score / noul 三种答案加一个 usage 块。仓库里那张官方对比图直接叫 laya_vs_jev_full.png。
规模上,仓库 9 月 18 日建仓,到 9 月 26 日已经 2.4 万 star、2100 多个 fork、154 个未关 issue。PyPI 上从 0.1.0 到 0.3.20 一共 28 个发行版,集中在头七天。
1.2 比 Jev 强在哪
延迟差一个数量级。官方在 T4 上测出单问题 32.8 毫秒,本机实测 70 毫秒。Jev 的 p50 延迟由 AbdelStark 和 nibzard 两个第三方项目测到 236 到 276 毫秒。两边样本和提示词不同,但差着七八倍。
权重开放,能自己部署。Apache-2.0,单卡甚至纯 CPU 都能起服务,数据不出本机,成本为零。Jev 要申请、按 token 付费。
同一个请求里问多个问题,材料只读一遍。判断依据能从文本直接读出来的任务,概率给得干脆,实测里按 ID 查库和识别明确动作请求稳定正确,可以直接接阈值。
1.3 比 Jev 差在哪
开箱准确率低一档。官方在 typed-decisions 基准上测,微调前只有 0.362,低于 0.461 的多数类基线;Jev 公布的是 0.727。laya 那个常被引用的 0.766 是拿这个基准的训练集微调之后的结果,同一张表里就列着微调前的 0.362。
选项一多就崩。所有选项共享一个固定的 token 池,超过 20 个选项准确率掉得很快,Banking77 上 laya 0.425、Jev 0.870。Jev 支持到 255 个选项。这是架构约束,微调也解决不了。
校准要自己动手。开箱状态的原始 ECE 是 0.213,比 Jev 的 0.144 还差;官方宣传的 0.081 是温度缩放拟合之后的结果。加载 checkpoint 时发的温度越界警告,说的就是这件事。
另外三条不是相对 Jev 的,是 laya 自身的问题:
- 需要背景知识的判断做不了。「这是我们的文档还是通用知识」「这是问政策还是要执行」两类稳定出错,因为 laya 无从知道某个知识库里有什么。
- 高置信度不等于判对。模型自信但判错的情况实测里出现过。
- 多语言路由有个默认分支。拉丁字母、没有变音符的语种会被送进英文 checkpoint。
- HTTP 服务的推理是串行的,约 14 QPS 封顶。
2. 装到本机
2.1 不走 Docker
官方平台页写明 Apple Silicon 上 Docker 只能跑 CPU。Docker Desktop for macOS 跑的是 Linux 虚拟机,Metal 透不进去,官方的 compose.cuda.yaml 和 compose.spark.yaml 也都跟 Mac 无关。本机是 M2 Max,有 Apple GPU,走 Docker 等于主动放弃它。一条 pip 命令就能解决的事,不值得为此再装一个 Docker Desktop。
官方把 Docker 定位在 Linux 服务器上:要在目标机上 build,不能用 buildx 交叉编译。
2.2 安装
1 | python3 -m venv .venv |
三条坑:
- Homebrew 的 Python 是 externally-managed,不开 venv 直接
pip install报externally-managed-environment。 - huggingface_hub 走 httpx,而 httpx 用 socks 代理需要额外的 socksio,否则首次下载 checkpoint 报
Using SOCKS proxy, but the 'socksio' package is not installed。pip 本身不受影响,是因为它走的是 http_proxy。装httpx[socks]解决,这条对任何用 httpx 的库都适用。 laya-serve默认端口 8000。本机 8000 被另一个常驻服务占着,起服务前必须显式设LAYA_PORT。
装机结果:Python 3.14.2 + torch 2.14.0 + transformers 5.17.0 + laya 0.3.20,venv 936 MB,checkpoint 缓存 1.4 GB。实测无兼容问题。
命令行跑一次完整预测(首次会从 HuggingFace 下 checkpoint,几分钟):
1 | export LAYA_DEVICE=mps |
2.3 起服务
HTTP 服务的配置全在环境变量里,不需要配置文件:
1 | LAYA_PORT=8123 LAYA_DEVICE=mps LAYA_MODELS=english,multilingual laya-serve |
laya-serve 不认 --help,任何参数都不接,直接启动。本机挂着代理,curl 要加 --noproxy '*',否则请求会被送去代理。
1 | curl --noproxy '*' http://127.0.0.1:8123/health |
两个上限要知道。HTTP 路径不接受 max_len 参数,长文档只能吃到默认的 1024 token,要读长文得绕开 HTTP 走 Python API。
另一个是吞吐。推理走 ThreadPoolExecutor(max_workers=1),外面还套一层 asyncio.Lock,同一时刻只处理一个请求。短输入 70 毫秒一次,算下来约 14 QPS 封顶,并发一多就排队。源码注释解释了为什么这么设计:一次只跑一个前向传播,本来就是单张 CPU 或 GPU 的自然用法。要更高吞吐只能自己起多实例。
2.4 MPS 实测
进程内计时,已排除 import 和 checkpoint 加载,含 warmup:
| 输入 | CPU | MPS | 加速 |
|---|---|---|---|
| 短(97 字符) | 376.9 ms | 70.7 ms | 5.3 倍 |
| 长(18980 字符) | 12610.3 ms | 1381.3 ms | 9.1 倍 |
长文本那 1381 毫秒,和官方宣称的「4000 token 约 1.7 秒 on Apple GPU」对得上,两边独立得出接近的数。CPU 和 MPS 的答案一致,只有末位浮点差异,切设备不用担心判断变了。
有一处测量陷阱:time laya "..." 量出来是 4 到 6 秒,看着像性能数据,量的却是整个进程。光 torch import 就占 2 到 3 秒,短输入的推理时间完全被淹没。短输入的性能必须进程内计时才有意义,上面那张表用的是进程内计时的脚本。
3. 接口:请求和返回
HTTP 端点是 POST /v1/systemone。Python 侧和它同一套结构,用 Router.predict()。
3.1 请求
请求体三个字段:
state:材料。一段文本,工单、邮件、命令、JSON 都行。questions:问题集。一个对象,key 是问题名,value 是问题定义。model:可选。强制指定 checkpoint,不传就按语种自动路由。
每个问题定义必填 type 和 instructions,type 决定还要给什么、返回什么:
choice:再给一个criteria对象,key 是选项名、value 是该选项的说明。返回选中的选项名和各选项概率。score:再给一个criteria数组,从低到高排列的档位说明。返回加权分和各档概率。noul:不用再给。返回一个 0 到 1 的数,是答案为「是」的概率。接近 1 是很可能「是」,接近 0 是很可能「否」,0.5 上下是拿不准。
noul 这个类型名是 TypeSafe 取的,官方文档给它的定义只有一句「a yes/no question」,没说这个词本身是什么意思。laya 要实现同一套协议,沿用了这个名字。功能上它就是二分类:命中则 1,不命中则 0,中间是概率。
三种混在一次请求里,材料只读一遍:
1 | { |
Python 侧同一个结构,predict() 还多两个参数:model 强制 checkpoint,max_len 放宽长度上限,长文传 8192。Router 构造时 preload=True 可让启动就加载,省掉首次请求的加载时间;max_loaded 控制同时驻留几个 checkpoint,默认 2。
官方带了五个现成问题集,省得自己拼 questions:
triage_questions():intent、is_urgent、frustration、refund_requested、churn_riskemail_questions():category、is_spam、is_phishing、urgency、needs_replyguard_questions():jailbreak、prompt_injection、sensitive_data、harm_severity、topicmoderation_questions():toxic、harassment、threat、spam、severityrouter_questions():difficulty、domain、needs_tools、is_sensitive
命令行对应 --preset triage 这种写法。
3.2 返回
一次真实往返。请求体存成 toolbody.json,问的是「该调哪个工具」,四个工具各出一个是/否概率:
1 | curl -s --noproxy '*' -X POST http://127.0.0.1:8123/v1/systemone \ |
1 | { |
服务器返回(格式化过,原响应是单行):
1 | { |
判断是需要 web_search,概率 0.68,另外三个工具是确定的否。
返回体四块:
answers:key 是问题名,和请求里的questions一一对应。每项都带type,而答案字段的名字与题型同名:choice 题读choice(选中的选项名),score 题读score(加权分),noul 题读noul(答案为「是」的概率)。choice 和 score 另有probabilities给出完整分布,score 还多一个legend,把分布里的下标映射回请求中写的档位说明。usage:input_tokens是材料和问题指令加起来 tokenize 后的长度;output_tokens恒为 0,模型不生成 token,不是出错。routing:model是实际选中的 checkpoint,另有reason说明路由理由,detection给出识别到的语种和依据。model:顶层那个是架构名laya-rl-agent,恒定不变,没有信息量。
每一项里还有 confidence、answer_confidence 和 action。前两个是两套不同的置信度,下一小节说。action.act_probability 目前没有可用信号,实测几乎恒为 1.0,官方文档里写明它的原始 logit 与判断对错反向相关,不要拿它做决策。
3.3 两个 confidence 不是一个意思
单题答案里有两个置信度字段,容易踩坑。
confidence 对 choice 和 score 是归一化熵,衡量整个分布有多集中;answer_confidence 是 max(p),也就是报出来的那个答案本身的概率。源码里写明,后者才是温度缩放拟合的对象,也是仓库里所有校准指标计算时用的量,只有它具备「报 c 就有约 c 的比例正确」这条性质。
noul 只有两个选项,max(p_true, 1 - p_true) 就是 max(p),所以这两种题上两个字段永远相等。顺带一提,Jev 的 noul 答案里没有 confidence 字段,官方原语表里它只返回一个数,是 laya 补上的。
两者量纲不同,不能拿同一个阈值去比。要门控只能看 answer_confidence。
反直觉的例子就在上面那份返回里。tool_order_lookup 的 noul 是 0.0,confidence 却是 1.0,看着像自相矛盾。实际是「100% 确定答案是『否』」:answer_confidence 取的是分布 [1.0, 0.0] 的最大值,也就是 1.0。答案是否为「是」,看的是 noul 字段,不是 confidence。
4. 工具路由:14 条对 7 条
场景就是上面那条请求:给一条 query,判断该调哪个工具。14 条用例覆盖按 ID 查订单、退款、查天气、问政策、闲聊。
试了两种问题架构,都只对 7 条。上面用的是「每个工具一个 noul」,四个问题一次前向传播全出结果,取概率最大值,全都低于阈值就判不需要工具:
1 | probs = {k: v["noul"] for k, v in data["answers"].items()} |
另一种是 N 选 1,一个 choice 问题,criteria 的 key 就是工具名:
1 | {"route": {"type": "choice", |
noul 那种更好用,尽管准确率一样。每个工具一个校准过的概率,阈值可以自己定;多个工具能同时命中,是真多标签;失败时还能看出是哪个工具判断失灵,choice 那种只看到「选错了」。
错的那 7 条有规律,按失败原因分成两类。
能用的,是触发条件能从文本直接读出来的:
- 按订单号查询,
order_lookup给到 0.94 到 0.9999 - 明确的动作请求(cancel order and refund me),
refund_issue给到 0.92 到 0.98 - 闲聊致谢,所有工具都是 0.00
不能用的,是两类需要背景知识的判断。
一类是「这是我们的文档还是通用知识」。How do I reset my password? 这条,所有工具的概率都低于 0.15。问题不在措辞上:laya 无从知道某个知识库里有什么内容。这种场合正确的做法是直接检索或者总是调用,不要让它猜。
另一类是「问政策」和「要执行」的区分。What is your refund policy 应该走知识库,不该走退款。英文低置信度地判错,中文是高置信度地判错:你们的退款政策是什么? 被判成 refund_issue,概率 0.96。后面这条是 multilingual checkpoint 的真实缺陷。
调参记录也值得记一笔。第一版问题集 7 条通过,收紧措辞、加了触发词的第二版反而降到 6 条。说明瓶颈不在措辞,在模型对该类别的理解。而且第二版是盯着失败用例改的,本身就是过拟合,真实项目里必须留一份没看过的留出集。
5. 多语言路由的盲区
laya 内置一个 Router,按语种自动挑 checkpoint,号称支持 100 多种语言。同一段诉求换 18 种语言写,看它路由到哪个 checkpoint。
非拉丁字母和带变音符的语种全部路由正确,中文、日文、俄语、阿拉伯语、西班牙语、德语、法语这些,置信度在 0.79 到 0.998。
拉丁字母、没有变音符的语种全军覆没。荷兰语、印尼语、马来语、他加禄语、斯瓦希里语,五种都被送进了英文 checkpoint,斯瓦希里语那条还判错了答案。
原因在 laya/lang.py 的检测器。它只在能证明「不是英文」时才转走,证明靠两条:变音符号比例达到 0.02,或者非拉丁字母占比达到 0.2。两条都不满足才去查停用词表,而且要求命中至少两个词、且比英文的命中数还多两个。荷兰语那句自然句里,一个 nl 词表的词都没撞上,变音符号又是零,检测器就给不出语言。源码把「判不出语言 + 没有非英文字母」一律当英文,这是有意为之,为的是不把短英文误送进 multilingual。
解法是显式指定 checkpoint。CLI 加 --model multilingual,Python 传 model="multilingual",HTTP 在请求体里加 "model": "multilingual"。
但别无脑全局强制。英文文本下两个 checkpoint 各有胜负,五条用例里各错一条。只在流量确定是这几类拉丁语种时才强制。
有个可用的信号:英文 checkpoint 处理外语文本时,answer_confidence 明显偏低,在 0.53 到 0.80 之间;正确路由后是 0.96 到 0.99。所以置信度低于 0.85 时值得怀疑是不是路由错了。这只是信号不是保证,英文原文也可能给出低置信度,实测里有一条 0.49。
6. 放在链路的哪个位置
laya 适合当前置闸门,不适合当唯一的分类器。一次典型的接法:请求进来先过 laya,概率过阈值的直接路由到对应工具或队列,低置信度的转给 LLM 或走检索兜底。
这个闸门挡得住什么、挡不住什么要分清。低概率说明模型自己拿不准,转走是对的。但高概率不等于判对了。上面那条中文用例「你们的退款政策是什么?」,被判成 refund_issue 的概率是 0.96,照样是错的。阈值只能滤掉「拿不准」这一类,「很自信但判错」那一类滤不掉,只能靠选任务避开。
阈值按自己的数据定。官方 README 的说法是:A threshold is a policy you choose from measured accuracy at that coverage on your data, not a property of the model。
三档 checkpoint 按语种和领域分,第三个留给自己微调。微调是这个项目预设的用法,官方给了在 Kaggle 免费 2x T4 上跑完整流程的 notebook。
7. 参考
- laya 仓库 · 官方 README,含 Jev 对比表、Benchmarks 与 Honest limits 两节;
serve.py声明实现 Jev 的接口 - laya 文档站 · 部署、hooks、Docker 平台说明,Apple Silicon 只能跑 CPU 的出处
- PyPI 上的 laya · 0.1.0 发布于 2026-09-18,到 0.3.20 共 28 个发行版
- 模型权重 · Apache-2.0,三个 checkpoint 的架构与长文准确率表
- TypeSafe 官方文档 · Jev 的 API 契约与 RLCD 的定义,用于对齐两边概念
- AbdelStark、nibzard · Jev p50 延迟 236~276 毫秒的两份独立测量,均为第三方,非 TypeSafe 发布