--- license: other license_name: layazh-custom-license license_link: https://huggingface.co/NovaAI6868/layazh-zh/blob/main/LICENSE language: - zh library_name: transformers pipeline_tag: text-classification tags: - laya - system-one - calibrated-decisions - rlcd - chinese - classification - routing - scoring - guardrails - non-autoregressive - commercial-license-required base_model: hfl/chinese-roberta-wwm-ext-large --- # layazh — Laya 中文版决策模型 **已发布:** ## ⚖️ 许可协议(重要) 本模型采用**自定义协议**,**不是**开源许可: > ### 个人使用免费 · 商业使用需付费授权 > > - **个人使用(含研究、学习、非营利、以及年营收 < 1 万元人民币的个人开发者):免费** > - **商业使用:需事先取得付费商业授权** > - **商业授权联系:`novaweb6868@outlook.com`** > > 完整条款见本仓库的 [`LICENSE`](https://huggingface.co/NovaAI6868/layazh-zh/blob/main/LICENSE) 文件。 > 提交任何贡献即视为同意该文件第三节的贡献者许可协议(CLA)。 注意:本模型中**推理与训练代码**源自 Apache-2.0 许可的 [`convaiinnovations/laya`](https://huggingface.co/convaiinnovations/laya);上述自定义协议 主要约束**模型权重**。代码部分您仍可按 Apache-2.0 使用。 --- **非自回归的中文 System 1 决策模型。** 给它一个 **state**(中文文本 / 邮件 / 工单 / JSON) 和若干**带类型的问题**,它在**一次前向传播**里返回带校准概率的答案。它**不生成任何文本**, 所以没有东西需要解析,也没有东西可以幻觉。 架构与训练配方对齐 [`convaiinnovations/laya`](https://huggingface.co/convaiinnovations/laya), 编码器换为中文预训练 backbone 并在中文决策数据上微调。 ## 与上游 Laya 的关系 | | 上游 `convaiinnovations/laya`(仓库根) | **layazh(本模型)** | |---|---|---| | 定位 | 英文 | **中文** | | 编码器 | `answerdotai/ModernBERT-large`(28 层, 1024 hidden, 395M) | `hfl/chinese-roberta-wwm-ext-large`(24 层, 1024 hidden, 325M) | | 决策头 | 2 层 TransformerEncoder + marker scorer + act head | **同构**(逐行对齐上游 `DecisionModel`) | | 总参数 | 421M | **352M** | | 上下文 | 512 tokens | 512 tokens | | 选项预算 | `head_max_len = 192` | `head_max_len = 192` | | 训练配方 | RLCD:log + 0.5·spherical(序数题另加 RPS),REINFORCE + group-mean baseline,多轮 TD(λ=1) | 同配方,实测采用:`rl_weight=8`、`kl_weight=1`、`group=4`、`σ=0.5`、`τ=1.5` | **训练配方的具体做法(同构部分与实测取值)** 奖励函数两侧**逐字一致**(我方 `layazh.model.proper_reward` 与上游 `laya.common.proper_reward` 对拍 确认默认参数相同): ``` reward = log_score + 0.5 × spherical_score # 所有题型 − 1.0 × ranked_probability_score # 仅序数题 (score) ``` 三者都是**严格恰当评分规则**,因此最大化期望奖励的唯一方式就是如实报告概率—— 这是校准性的来源,而不是事后补丁。 具体训练设置(我方为实测值;上游的这几项未随 checkpoint 发布,故不臆测): | 项 | 上游 | 本模型(layazh) | |---|---|---| | 策略梯度估计 | REINFORCE + group-mean baseline(GRPO 风格) | 同 | | 探索噪声 | 对选项 logits 加零均值高斯噪声 | `σ = 0.5`,每组采样 `4` 次取组均值作基线 | | 采样温度 | 未公开 | `τ = 1.5` | | 目标函数 | 策略梯度 + 稠密 masked-KL 辅助项 | `8.0 × PG + 1.0 × KL` | | 多轮对话 | TD(λ = 1.0) 于前缀切片 | 同(代码路径一致) | | 决策头 lr | 未公开 | `3e-4`(AdamW) | | 编码器 lr | 未公开 | `2e-5`(分组,`encoder_lr_scale=0.067`) | | 可训练范围 | 全量微调编码器 | **顶部 8/24 层** + 决策头(12 GB 显存约束) | | LR 调度 | 未公开 | 线性 warmup 6% + cosine 衰减 | | 其他 | — | 选项顺序增强开、grad clip 1.0、gradient checkpointing 开、fp32 | > **诚实说明**:上游只发布了**权重与推理配置**,没有发布 RLCD 的训练超参 > (`rl_agent_config.json` 里只有 `temperature` 与 `updates: 7313 / hours: 1.96` 这类元数据)。 > 因此"同配方"指的是**算法与奖励函数相同**,而上表中的具体数值是本项目自行确定并实测的, > 不能声称与上游的隐藏设置一致。 序列模板与上游完全一致(已用上游 `laya` 包内的 `render_options` / `QTYPES` 做过程序化对齐校验): ``` [CLS] 问题:<中文指令> [SEP] [MASK] 选项0 [MASK] 选项1 ... [SEP] [SEP] ``` 每个选项前一个 `[MASK]` marker,决策头读这些 marker 位置的隐状态并在该问题的选项上做 softmax。 `noul` 恒渲染为 `[false, true]`,故 `p[1]` = “该命题成立”的概率。 ## 安装 `layazh` 包**已随本仓库提供 wheel**,无需克隆仓库、也无需 Hugging Face token。 ### 第 1 步:安装 layazh 包 ```bash # 推荐:uv(也可直接用 pip,把 uv pip 换成 pip 即可) uv venv --python 3.11 .venv source .venv/bin/activate # Windows: .venv\Scripts\activate # 从本仓库安装(固定到当前 commit,长期可复现;依赖会自动装好) uv pip install https://huggingface.co/NovaAI6868/layazh-zh/resolve/d55f726a84aa2c17186c49d20fc35cf49c633f0e/layazh-0.1.0-py3-none-any.whl ``` 这会把 `torch` / `transformers` / `numpy` / `safetensors` / `huggingface_hub` 一起装上, 之后 `from layazh import ZhAgent` 在任何目录都能直接用,**不需要改 `sys.path`**。 ### 第 2 步(**老显卡必读**):换成匹配你 GPU 架构的 PyTorch 上一步装的是默认 PyTorch(面向较新的 CUDA)。如果你的卡比较老,需要替换: | 你的 GPU | 命令 | |---|---| | **Maxwell / Pascal**(GTX 750–1080、Titan X 等,sm_50–sm_61) | `uv pip install torch==2.7.1 --index-url https://download.pytorch.org/whl/cu128` | | Turing / Volta(RTX 20 系、T4) | `uv pip install torch --index-url https://download.pytorch.org/whl/cu121` | | Ampere 及更新(RTX 30/40 系、A100) | 默认装的即可,无需替换 | > **为什么 Maxwell 要锁 2.7.1**:`torch 2.7.1 + cu128` 是最后一个包含 `sm_50` cubin 的 > wheel(可在 sm_52 上执行)。更新的版本不再为这些架构编译内核。本模型就是在 > GTX Titan X(sm_52)上用这个组合训练和推理的。 > > 另外该架构**不支持 bfloat16**,代码会自动把 dtype 收敛到 fp16/fp32, > 不需要你手动处理。 ### 第 3 步:验证安装 ```python import layazh print(layazh.__version__) # -> 0.1.0 from layazh import ZhAgent agent = ZhAgent.from_pretrained("NovaAI6868/layazh-zh") # 首次下载约 1.4 GB print(agent.device) # -> cuda 或 cpu ``` 没有 GPU 也能跑(CPU 推理,只是慢一些)。仅安装 CPU 版 PyTorch 可以用 `--index-url https://download.pytorch.org/whl/cpu`。 ### 想从源码安装 / 复现训练 ```bash git clone https://huggingface.co/NovaAI6868/layazh-zh layazh-zh cd layazh-zh uv pip install -e . # 可编辑安装 uv pip install -e ".[train]" # 训练所需额外依赖(pandas/pyarrow/scipy) ``` ## 用法 ```python from layazh import ZhAgent # 从 Hub 加载(首次自动下载约 1.4 GB) agent = ZhAgent.from_pretrained("NovaAI6868/layazh-zh") # 也可从本地目录加载:agent = ZhAgent("checkpoints/layazh-zh") res = agent.system_one( "这家酒店位置很好,出门就是地铁站,前台小姐姐特别热情,还帮我们升级了房型。" "房间干净整洁,早餐品种也很丰富,下次来一定还住这里。", { "sentiment": { "type": "choice", "instructions": "这条评论的情感倾向是什么?", "criteria": {"positive": "正面评价,表达满意或推荐", "negative": "负面评价,表达不满或抱怨"}, }, "rating": { "type": "score", "instructions": "这条评论给出的总体评分是多少?", "criteria": ["非常差", "较差", "一般", "不错", "非常好"], }, "recommend": { "type": "noul", "instructions": "这位用户是否推荐该酒店?", }, }, ) print(res["answers"]["sentiment"]["choice"]) # -> positive print(res["answers"]["sentiment"]["confidence"]) # -> 1.0 print(res["answers"]["rating"]["score"]) # -> 3.10 (0–4 的期望值) print(res["answers"]["recommend"]["noul"]) # -> 0.986 P(推荐) print(res["usage"]["output_tokens"]) # -> 0 不生成任何文本 ``` > 上面这些输出是本模型在**实际运行**中产生的(非示意)。 一次调用里的**所有问题在同一次前向传播中批量作答**。 三种 primitive: - **`choice`** — 给定 `criteria` 键值对,返回 argmax 选项 + 全部选项概率 + `confidence`(1 − 归一化熵)。 - **`score`** — 有序选项列表,返回期望分值 `Σ i·pᵢ` 与完整分布。 **序数感知**:训练时额外用 ranked probability score 惩罚累积分布偏差。 - **`noul`** — 二元命题,返回 `P(成立)`。 ## 架构 - **编码器**:`hfl/chinese-roberta-wwm-ext-large`,24 层 / hidden 1024 / 16 头 / 21128 词表, 中文全词掩码(whole word masking)预训练,325M 参数。使用 SDPA 注意力。 - **决策头**(从头训练,26.5M 参数): - `type_emb(3)` — 题型嵌入(choice / score / noul) - 2 层 `nn.TransformerEncoderLayer`(`norm_first=True`, `src_key_padding_mask`) - `scorer`:`LayerNorm → Linear(d,d) → GELU → Linear(d,1)`,作用于每个 marker 位置 - `act_head`:`Linear(d+4, 256) → GELU → Linear(256, n_act)`,输入为 `[CLS]` 池化表示 加上答案分布的**已 detach** 摘要(top1、top1−top2、归一化熵、选项数/255) - **单次前向**:一次 `system_one` 调用里所有问题在同一次前向传播中批量作答。 ## 训练 ### 数据(全部为**人工标注**中文语料,非模型自标注) | 来源 | 许可 | 产出 | 记录数 | |---|---|---|---| | TNews (CLUE) | Apache-2.0 | `choice`(15 类新闻主题) | 18,756 | | ChnSentiCorp | **research / 仅研究用途** | `choice` + `score` + `noul` | 10,160 | | yf_dianping | **research / 仅研究用途** | `choice` + `score` + `noul` | 18,836 | | **合计(train / eval)** | | **99,184 / 6,244 个问题** | 47,752 / 3,048 | > ⚠️ **上游数据许可提示(请务必阅读)** > > 三个语料中,TNews 为 Apache-2.0,可自由使用;而 **ChnSentiCorp 与 yf_dianping 在其 > 原始发布页标注为研究用途(research-only)**,并未提供明确的商业使用授权。 > > 本模型仓库采用"个人免费 / 商用付费"协议,但**该协议无法覆盖上游数据集的限制**。 > 因此: > > - 若您计划**商业使用**本模型,请自行确认 ChnSentiCorp / yf_dianping 的原始许可, > 或联系我们获取**仅用 Apache-2.0 语料(TNews 等)重训**的版本; > - 本项目已尽量披露数据来源以便下游合规评估,但不对下游使用场景的合规性作担保 > (见 `LICENSE` 第五节)。 一条文本扇出成多个 typed question(一条大众点评评论同时给出情感 `choice`、1–5 星序数 `score`、以及“是否推荐”的 `noul`),与 Laya 自己构造 typed-decisions benchmark 的方式一致。 大众点评原始文件按餐厅分组,因此用**蓄水池采样**从 2,979,616 条可用评论中均匀抽样, 而不是截取文件头部(那会严重偏向少数餐厅的客群)。 ### 训练配置 决策头随机初始化,与编码器顶部 8 层联合微调: | 项 | 值 | |---|---| | 可训练参数 | 128.3M / 352M(编码器顶部 8/24 层 101.8M + 决策头 26.5M) | | 决策头 lr | 3e-4 | | 编码器 lr | 2e-5 | | LR 调度 | 线性 warmup 6% + cosine 衰减 | | batch | 4096 padded tokens,≤64 序列 | | epochs | 3(14,493 步) | | 梯度裁剪 | 1.0 | | 选项顺序增强 | 开启(choice/noul 的选项顺序每轮打乱,target 同步重排;`score` 不打乱,因其为序数) | | 梯度检查点 | 开启(12 GB 显存约束) | | 混合精度 | **fp32**(本机 sm_52 无原生 bf16) | 训练中每 100 步在一个固定的、跨来源均匀抽样的 400 条探针集上记录真实准确率, 并把探针最优的权重单独存为 `*-best` 快照——因为这比 running-mean loss 可靠得多: batch 混合了 2 选项与 15 选项的题目,loss 水平主要由构成比例决定。 ### 训练过程中测得的关键结论 **学习率与 warmup 是决定性的**(同一份 6000 条数据、同一探针集,唯一变量是 lr/warmup): | 配置 | 探针准确率轨迹 | |---|---| | 编码器 lr 1e-4,**无 warmup** | 0.5113 → 0.3251 → **0.2551**(坍缩) | | 编码器 lr 2e-5 + warmup 6% + cosine | 0.4199 → 0.5621 → 0.7032 → **0.7404**(持续上升) | **RLCD 项当前贡献很小。** 日志中策略梯度项 `pg ≈ 0.002–0.024`,稠密 masked-KL 项 `kl ≈ 0.5–2.0`。即使 `rl_weight` 已从 1.0 提到 8.0,RLCD 仍只占损失的一小部分, 因此本版主要由有监督 KL 驱动;RLCD 的严格恰当评分规则主要作用于**概率校准** (见下方温度标定结果),对 argmax 准确率贡献有限。 ### 本机硬件与数值选择 训练在本机 **GTX Titan X(Maxwell, compute capability 5.2, 12 GB, CUDA 12.6)** 上完成。 该架构有三个硬性后果,已在代码中显式处理(均为实测,非推测): - **不支持 bfloat16** → 上游配置里的 `"amp_dtype": "bf16"` 在本机不可用; `layazh.config.resolve_amp_dtype` 会把 dtype 收敛到 fp16/fp32, 绝不在 sm_52 上使用 bf16。 - **无 FlashAttention** → 编码器使用 SDPA(math kernel)。 - **PyTorch 支持止于 2.7.1 + cu128** —— 该 wheel 内含可从 sm_52 执行的 `sm_50` SASS (`torch.cuda.get_arch_list()` = `['sm_50','sm_60',...,'sm_120']`)。 实测数值特性(反直觉,故记录):fp16 matmul **3.80 TFLOP/s** 略快于 fp32 **2.94 TFLOP/s**, 因此推理用 fp16 权重是净收益;训练仍用 fp32 以保证稳定性。 ## 评测 ### 同台对比(1,200 道**完全相同**的中文题目、**修正后的** ground truth) 两个模型回答 byte-identical 的中文问题,走同一套代码路径与指标实现 (`scripts/benchmark_ab.py`): | 指标 | **layazh-zh** | Laya `multilingual` | 变化 | |---|---|---|---| | **总体准确率** | **0.7150** | 0.4783 | **+0.237** | | `choice` 准确率 | 0.6127 | 0.6039 | +0.009 | | `score` 准确率 | **0.6854** | **0.1461** | **+0.539** | | `noul` 准确率 | **0.9638** | 0.6486 | +0.315 | | Brier(越低越好) | **0.3930** | 0.8040 | −0.411 | | ECE(越低越好) | 0.2639 | 0.2626 | +0.001 | | AURC(越低越好) | **0.1127** | 0.4239 | −0.311 | | 单题延迟 | 67.0 ms | **35.5 ms** | 慢 1.9× | > **关于 `choice` 的一项必要说明。** 上游在 `choice` 上看似打平(0.6039 vs 0.6127), > 但这是**巧合**:上游对 2 选项题的点预测不随输入变化,其"准确率"等于多数类占比; > 修正标签极性后恰好落在多数类一侧。证据是它的 `choice` Brier 高达 **0.6191** > (本模型 0.5319),且 `choice` ECE 0.1752 是在"几乎恒定输出"下取得的。 > 逐来源看更能说明问题:在 15 类的 TNews 上上游接近随机水平。 > > 其余三个 primitive 上本模型全面领先,其中 `score` 提升 **+0.539**。 ### 全量评测(6,244 个问题,本模型) | 分组 | n | 准确率 | ECE | |---|---|---|---| | **ALL** | 6,244 | **0.6951** | 0.3440 | | `type:noul` | 1,392 | **0.9375** | 0.2131 | | `type:score` | 1,804 | **0.6996** | 0.1645 | | `type:choice` | 3,048 | 0.5817 | 0.5100 | | `senticorp/choice` | 640 | 0.8688 | 0.8097 | | `senticorp/score` | 640 | 0.9109 | 0.2334 | | `senticorp/noul` | 640 | 0.9125 | 0.2588 | | `dianping/noul` | 752 | 0.9588 | 0.1743 | | `dianping/choice` | 1,164 | 0.7096 | 0.6000 | | `dianping/score` | 1,164 | 0.5833 | 0.1266 | | `tnews/choice`(15 类) | 1,244 | 0.3143 | 0.2715 | **必须看地板线,否则准确率数字没有意义:** | | 总体准确率 | |---|---| | 随机猜 | 0.2959 | | 多数类(先验) | 0.4734 | | Laya `multilingual`(上游实测) | 0.4783 | | **layazh-zh** | **0.6951** | ### 温度标定 温度按 `(题型, 选项数)` 分桶拟合,**在同一半数据上拟合、在不相交的另一半上报告**。 三种方案在留出半集(3,122 题)上的 ECE 对比: | 方案 | 留出集 ECE | |---|---| | 不标定(恒等) | 0.3512 | | 每题型一个温度 | 0.2684 | | **每 (题型, 选项数) 分桶**(最终采用) | **0.2547** | 最终写入本仓库 `rl_agent_config.json` 的温度: ```json {"choice:11+": 0.466635, "choice:2": 0.253066, "choice:3-5": 0.545042, "noul:2": 0.970277, "score:3-5": 1.034508} ``` > **与上游 SDK 的一处已知差异。** 上游 `laya` 包在加载 checkpoint 时会用 > `clamp_temperature` 把温度限制在 **[0.5, 5.0]**,而本模型的 `choice:2` 桶 > 拟合值为 **0.253**(低于下界)。我实测了加这个限幅的影响: > > | 温度限幅 | ECE(全部 6,244 题) | > |---|---| > | 不限幅(本模型默认) | **0.2498** | > | `[0.5, 5.0]`(上游 SDK 行为) | 0.2799 | > > 限幅会让 ECE 变差,说明该桶确实需要更强的锐化(模型对 2 选项题过于保守)。 > 因此本模型**不做限幅**。若你通过上游 `laya` SDK 加载本 checkpoint, > 温度会被自动限幅到 0.5,ECE 将为 0.2799 —— 这是可预期的差异,不是 bug。 > 分桶温度是**由留出数据挑选**出来的,不是默认采用上游做法。 > 这一点很重要:同一套分桶在各方案比较中只赢了 0.0137 ECE, > 如果不做留出对比就无从判断它是否真的更好。 > > 标定必须拟合在**未经温度缩放的原始 logits** 上,否则拟合会永远收敛到 ~1.0。 ### 已知弱点 - **`choice` 是明确弱项(0.5817),15 类 TNews 仅 0.3143。** 15 个选项在 512 token 预算下每个只剩几个 token,正是上游模型卡承认的"选项数越多每项 token 越少导致准确率骤降"。 改进方向:调大 `head_max_len`,或改层级式 coarse-to-fine 选择。 - **`choice` 校准最差(ECE 0.5100)**,因为 `choice` 混合了 2/3/15 三种选项数。 - **上下文上限 512 token**(中文约 500 字),超长文档需先切分。 - **延迟慢于上游 1.9×**(67.0 ms vs 35.5 ms):中文 RoBERTa-large(24 层)比 上游 mmBERT-base 更大,这是精度换速度的取舍。 - **RLCD 贡献有限**:本版准确率主要由有监督 KL 驱动,RLCD 的严格恰当评分规则 主要体现于校准。 - **训练未跑满计划轮数**(见 `REPORT.md`),是一个接近平台期、而非训练到极限的版本。 ## 诚实的边界 以下限制继承自上游 Laya 的 Honest Limits,中文版同样适用,另外加上中文特有的几条: - **零样本能力弱。** 上游基础 checkpoint 在 typed-decisions benchmark 上仅 0.362 (随机 0.318、多数类 0.461)。Laya 是**快速专精的底座**,不是开箱即用的决策引擎; 价值在于在中文数据上定向微调后的表现。 - **`score` 序数题是最弱的 primitive**(上游 SST-5 仅 0.372)。 - **选项数 > 20 时准确率骤降**:选项共享固定的 `head_max_len` 预算,77 个选项时每项只剩 3–4 个 token。解法是调大 `head_max_len` 或改用层级式 coarse-to-fine 选择。 - **`noul` 可能跟着选项标签走而不是跟着 state 走**(上游 issue #156)。 若怀疑卡住,改用中性 key 的双选项 `choice`。 - **`action.act_probability` 暂无可用信号**(上游 issue #185)。请用 `confidence` 门控。 - **开箱过度自信**:务必在自己的数据上重新拟合温度。 - **中文上下文只有 512 token。** 中文 BERT 词表基本 1 字 1 token,因此有效正文约 500 字。 超长中文文档需要先切分或用支持更长上下文的编码器。 - **数据域偏移**:训练语料是新闻 / 酒店评论 / 餐厅点评,不等于你业务里的邮件或工单。 在你的领域上继续微调仍然必要。 ## 引用 - Laya 模型卡与权重: - Laya 代码: - CLUE / TNews: - 中文 RoBERTa: