文档导航

在你自己的决策上微调 Laya

在你自己的决策上微调 Laya

在 typed-decisions 基准上,基础 checkpoint 零样本的得分接近随机 —— 0.36 和 0.35,对 0.318 的 随机基线而言 —— 而微调后的 checkpoint 在同一批 2,000 个决策上达到 0.766,高于 TypeSafe Jev 已发布的 0.727,也高于 0.735 的教师自一致上限。微调承载了大部分价值,公开的 微调 notebook 在 Kaggle 免费的 2xT4 GPU 上跑完整个循环:构建数据集、用 RLCD 训练、拟合校准温度、评估,并把 结果推到 Hub。这一页讲解那个 notebook,并指出当数据换成你自己的之后,哪些部分仍然关键。

另一个完整示例 —— 在单张 16 GB GPU、不花钱调 API 的情况下训练一个浏览器智能体决策头 —— 在 把 Laya 微调成浏览器智能体决策头。

notebook 按顺序做了什么

# 步骤 发生的事
1 环境 断言两张 T4 GPU 都可见且已分配
2 安装 laya、transformers、datasets 以及训练依赖
3 预处理 1,200 个训练用例(6,000 个类型化决策)变成带软目标的 tokenized 条目,写成磁盘文件供两个 DDP rank 使用
4 训练 在 torchrun --nproc_per_node=2 下跑 train_ddp.py,四个 epoch
5 校准 每种类型一个温度,在训练前就留出的切片上拟合(在训练脚本内部,最后一个 epoch 之后)
6 评估 微调后的 checkpoint 回答官方 test 划分 —— 400 个用例、2,000 个决策 —— 并给出每用例延迟
7 指标 准确率、软准确率、Brier、ECE、score MAE、档位内一位、KL/TV 和延迟分位数;一张与 Jev 和教师上限正面比较的表
8 发布 (可选)用这次运行自己的数字生成一张模型卡,把文件夹上传到 Hub
9 报告 benchmark_report.json,含指标表和各工作流的准确率

Kaggle 设置:Accelerator 选 GPU T4 x2,Internet 设 On。输出落在 /kaggle/working/laya_finetuned_typed_decisions。

训练配方

RLCD 在基准的金标分布上训练,而不是在硬标签上:每个条目都带上教师给每个选项分配的概率, 损失的两半都读这个目标 ——

  • 一个策略梯度项,作用在采样得到的带噪 logit 投影上(GRPO 风格:每个条目四个样本,探索 噪声从 0.4 退火到 0.1),由适当的评分规则给奖励(球面 0.75,排序概率 1.0);
  • 一个全权重的软交叉熵项,对着同一个分布。

notebook 为 16 GB 显卡设置的旋钮:

epochs 4
有效批大小 64 个序列(每微批 8 个,2 张 GPU,4 步累积)
学习率 encoder 2.5e-5,head 1e-4 —— AdamW,余弦调度
内存 fp16 autocast,encoder 和 head 上开梯度 checkpointing,梯度范数裁剪 1.0
序列预算 max_len 1024,head_max_len 256,max_tokens_per_batch 4096

2xT4 上的运行时间,演示是分钟级,真实数据是小时级:演示的 6,000 个决策大约 4–6 分钟,而对约 30k 个问题跑四个 epoch 大约 4–5 小时。

要把它指向你的数据,替换那两个 load_dataset 调用,并保持行 schema:每个用例带 state、 questions 和 gold(教师对每个问题给出的概率),预处理器把它们变成条目。问题类型是 choice、score 和 noul;任何你能用它们对某个状态表达出来的东西都可以。

校准是这次运行的一部分

这是照搬循环时最容易被丢掉的一步,而一旦有人用置信度做门控,它就是关键。

notebook 在把训练数据分片到各个 rank 之前,先切出一份校准切片(最多 400 个条目,或 10%, 固定种子,每个 rank 上完全一样)。在运行已经训练过的条目上拟合温度,测的是拟合本身而不是校准 —— 模型在那些条目上几乎必然正确,优化器没有什么需要软化,于是返回一个退化的 scale。

最后一个 epoch 之后,rank 0 用 LBFGS 在 log 温度上拟合每种问题类型一个温度(choice、 score、noul),钳制在 [0.1, 10](切片不足十个条目时为 1.0,拟合抛异常时为 1.2)。 这些值作为 temperature 写进 rl_agent_config.json,notebook 在同一次写入里移除任何继承来的 temperature_by_options:那些旧的分桶值在推理时优先,会静默盖掉新的拟合。

温度缩放不改变 argmax —— 也不改变准确率;变的是置信度。checkpoint 按交付状态是过度自信的, 所以依赖任何阈值之前先拟合,并在留出数据上评估结果,再声称有改进。配置持久化的回归测试不需要 下载或训练就能跑:

python tests/test_calibration_persistence.py

信任它之前先评估

评估是对官方测试划分的一次完整遍历:400 个用例、2,000 个决策,横跨 Agent Trace Observability、Customer Service、Invoice Processing 和 Security Incidents。它计算准确率、软 准确率、Brier、ECE(通过 laya.common.ece_score)、score MAE、档位内一位和延迟分位数,然后 构建一张正面比较表,其中的参考行是固定的:

model kind accuracy ECE
TypeSafe Jev 1.13.0 general 0.727 0.144
ModernBERT-base (149M) specialist 0.646 0.179
Teacher Self-Agreement ceiling 0.735 —
Laya (published checkpoint) fine-tuned 0.766 —

你自己运行里的 Laya 行也以同样方式计算 —— notebook 用这次运行自己的数字重建那张表。两个值得 照搬的习惯:把你关心的切片(一种语言、一个工作流)留在留出数据里,并把校准和准确率一起报告, 因为训练信号是一个分布,而不只是一个标签。等你有了数字,仓库的 Discussions 是分享它们的地方;基准和已知 限制在仓库根目录的 BENCHMARKS.md 里。

推到 Hub

发布那一格是循环的最后一公里,它刻意做得无聊:

  1. 在 Kaggle 里放一个可写的 HF_TOKEN(Add-ons → Secrets)。缺了它那一格会抛出错误,并给出 确切的指示。
  2. 设置目标仓库 —— 交付的那一格默认用项目自己命名空间里的一个名字,所以运行前先改掉它。
  3. 运行它。它写出一张模型卡,卡上的数字来自这次运行的对比表,然后上传 model.safetensors、 encoder/、tokenizer/、rl_agent_config.json、那张卡和基准报告。

结果加载起来和任何其他 checkpoint 一样 —— 没有微调专用的 API:

import laya

agent = laya.load("your-org/your-checkpoint")   # the repo you just pushed
result = agent.predict(state, questions)

一个滚动更新的 checkpoint_latest/ 在每个 epoch 后会被覆盖,所以 Kaggle 超时或 OOM 的代价是 一个 epoch,而不是整次运行。

要当心什么

  • 循环的好坏取决于目标。 RLCD 在你的问题上模仿教师的分布;训练前(或训练的同时)收集教师 置信度,并把它们的质量当作上限。
  • 校准切片小是故意的。 最多 400 个条目或 10% —— 够拟合三个按类型的标量,不够用来验证。 自己留出一份评估数据。
  • 你的标签必须能放进三种原语。 如果你的决策不是 choice、不是量表、也不是是非概率,先把 它塑造成其中一种。两个尖锐的边缘已经有记录:选项多会削弱置信度的选择 (#394),强制选择的否定可能跟着问题走 而不是跟着状态走(#377)。
  • 要交付配置,不只是权重。 被移除的 temperature_by_options 就是那种如果在一个照搬的配置 里存活下来、就会静默让校准失效的东西。