Laya 拆解:一个带动态选项的 BERT 分类器
TL;DR
- 模型:ModernBERT-large 编码器 + 2 层 Transformer 决策头。每个选项前插一个
[MASK],在这些位置各打一个分,再对本题选项做 softmax。本质是选项写在输入里的 cross-encoder 多选分类。 - 标签:全合成数据。一个约 4B 级别的 LLM teacher 对每条样本标 3 次(温度 0.7),取平均作为软标签。所谓"准确率"其实是和这个 teacher 的一致率。
- Loss:软标签交叉熵,外加一项"RL"。RL 项实际是对 logits 做随机扰动来估计梯度(evolution strategies),而它估计的梯度本来就能直接求出来。作者自己的消融里,只用交叉熵反而更好(0.790 对 0.7715)。
1. 一次请求在模型里走了什么路
用 README 里的退款工单当例子:一段 state 加 3 个问题(选部门、打紧急度、判断是否要流失)。点「下一步」逐步看每个阶段的输入、操作和产物。
- token 行:一行序列 = [CLS] + 问题指令 + 每个选项前一个 [MASK] + state。图里每个小方块代表一段 token,实际长度 L ≤ 512。
- 编码器 → h:每个 token 变成一个 1024 维列向量,28 层双向注意力让每列都看到全部 L 列,所以 [MASK] 列里混进了「本选项文字 + 问题 + state」的信息。
- + type_emb:按题型取一个 1024 维向量,广播加到每一列(不是拼接),告诉决策头这是 choice / score / noul。
- 决策头 → h':2 层 TransformerEncoder 再做一次全局交互,输出形状不变,仍是 L × 1024。
- gather:按 marker_pos 只取 3 个 [MASK] 列,叠成 m ∈ ℝ^{K×1024};其余列(问题、选项文字、state)到这里就丢掉了。
- scorer:同一个 MLP 分别作用在每个 [MASK] 列上,各出 1 个标量,得到 K 个 logits;所以 K 可以是任意值。
- softmax:只在本题 K 个选项内归一化,先除以按题型拟合的温度 T。
- act 分支:[CLS] 列(1024 维)与 4 个由概率算出的统计量拼接成 1028 维,过 MLP 输出 2 类 logits;统计量来自 detach 后的概率,梯度不回传到打分路径。
一句话总结:只有 [MASK] 列和 [CLS] 列被读出,其他位置只是给它们提供上下文。
数值类示意(logits、概率、marker 位置)是为了说明形状和流程编的,不是真实推理结果;张量维度和模块结构对应 laya/common.py 中的 build_head 和 DecisionModel.forward。
对应源码:DecisionModel.forward 核心几行编码 → 加题型 embedding → 头部 2 层 → gather [MASK] → scorer → 屏蔽多余选项
h = self.encoder(input_ids=input_ids, attention_mask=attention_mask).last_hidden_state
h = h + self.type_emb(qtype)[:, None, :]
for layer in self.head.layers:
h = layer(h, src_key_padding_mask=pad)
idx = marker_pos.clamp(min=0)[:, :, None].expand(-1, -1, h.size(-1))
m = torch.gather(h, 1, idx)
logits = self.scorer(m).squeeze(-1).float()
logits = logits.masked_fill(~marker_mask, -1e4)
2. 它本质上就是分类
走完上面的流程可以看到,laya 的输出就是一个 K 选 1 的 softmax,训练目标也是一个分布。和普通分类器相比,它只在一个地方有区别:类别不是固定的输出层,而是以文本形式写进输入,所以换一套选项不用重训。这个思路就是 BERT 时代的 cross-encoder 多选(比如 SWAG 用的 AutoModelForMultipleChoice),区别只是 laya 把所有选项塞进同一条序列。
| 固定分类头 | Laya | LLM 生成 | |
|---|---|---|---|
| 标签空间 | 训练时写死 | 请求时用文本给出 | 任意 |
| 计算方式 | 一行一次前向 | 每个问题一行,state 每行重复编码 | 自回归逐 token |
| 输出 | 固定 logits | K 个 [MASK] 位置的 logits | 文本,需要解析 |
| 主要局限 | 换标签就要重训 | 选项挤在 192/256 token 预算里,选项多了就分不清;有位置偏置 | 慢、贵 |
它带来的工程价值是真实的:T4 上单题 39.5 ms,比调 LLM API 快一个数量级,而且输出天然是概率。但从建模上讲没有新东西,宣传里的 "System 1 decision engine"、"non-autoregressive" 都是包装。
3. 软标签从哪来
训练和评测用的都是 typed-decisions 数据集。下面按数据集卡片的描述画出构造流程。
我拉了训练集第一条数据看,gold 的取值是 0.283333、0.433333 这种,乘以 3 后都是 0.05 的整数倍。所以每次标注更像是 teacher 在 JSON 里直接写出来的概率(verbalized probability),而不是 next-token 的 logprob。这一点是我根据数值推断的,数据集卡片没有明说。
真实样本:一条 agent trace 的 gold5 个问题上,3 次标注的 argmax 没有一题完全一致,gold 很平
action : continue 0.283 | human_review 0.433 | observe 0.250 | stop 0.033 needs_review (noul): false 0.45 | true 0.55 outcome : failure 0.083 | harmful 0.000 | partial 0.493 | success 0.423 risk (score): 0:0.467 | 1:0.417 | 2:0.100 | 3:0.017 urgency (score): 0:0.317 | 1:0.483 | 2:0.167 | 3:0.033 label_agreement: 5 个问题的 argmax_agree(3 次 argmax 是否全相同)均为 false
数据集卡片自己给了几条参考线,读 laya 的成绩时必须对照着看:
4. Loss:RL 项其实是在用采样估计一个已知的梯度
代码里的 loss 是两项相加(laya/train.py 的 rlcd_loss)。记模型输出的 logits 为 ℓ,teacher 的目标分布为 t:
对 ℓ 求导,高斯 log 密度的梯度正好是 ε/σ²,于是:
这就是 evolution strategies(零阶梯度估计):往几个随机方向扰动 logits,看 reward 变好还是变坏,以此估计 ∇E[R]。你的理解是对的,它就是在用采样做估计。问题在于这里根本不需要估计:
- "动作"就是分布 q 本身,reward 是 q 和 t 的闭式函数,log score、spherical、RPS 都可以直接求导。
- reward 的主项 log score 就是负交叉熵,而第二项 L_CE 已经给出了它的精确梯度,RL 项只是给同一个方向叠加噪声。
- "严格适当评分规则保证概率诚实"这个论据,交叉熵本身就满足(log score 就是严格适当评分规则),不需要 RL 来实现。
- advantage 用 std 归一化,RL 项的梯度尺度随 batch 漂移,和 CE 项的相对权重不可控。
下面这个小实验用真实样本的 action 题做 target,按代码里的公式采 4 个样本,比较 ES 估计的梯度方向和精确梯度方向。每点一次「重新采样」就是一次训练 step 里 RL 项拿到的梯度。
作者自己的消融(issue #887,单张 T4、一个 seed)也是同样的结论:
--loss rlcd(默认,CE + RL)--loss soft-ce(只用交叉熵)训练完后,还会在预留的最多 400 条校准样本上,按题型各拟合一个温度(LBFGS,限制在 [0.1, 10])。这一步是标准的 temperature scaling,不改变 argmax,只改变置信度。
5. 结论:工程封装扎实,算法叙事偏营销
值得参考
- [MASK] 打分 + 选项写进输入:做一个 schema 可变的快速判别器,这个结构简单好用。
- 软标签 + 温度校准:输出能直接当置信度用,做门控和拒答。
- 推理工程:长度分桶、CUDA graphs、ONNX/TensorRT 导出、选项乱序做稳定性检查。
不值得照搬
- RLCD:对可导目标做零阶估计,消融里还更差。用软标签 CE 就够了,有序题可以加一个可导的 RPS。
- 评测口径:gold 来自 4B teacher、温度 0.7 下的 3 次采样,噪声大;超过 0.735 的分数不代表更正确。
- 基础 checkpoint:预训练数据和流程都没公开,零样本接近随机,必须做领域微调才能用。
如果我们要做类似的东西,关键投入应该在 teacher 的质量上:换更强的模型、多采样、加人工抽检,用 logprob 而不是口头概率。另外要正视这个架构的结构性短板:选项数受 token 预算限制、state 按问题数重复编码、选项有位置偏置。