从一条样本看懂 Kev:数据、模型、训练与推理

文章目录

    • [1. Kev 学习的任务是什么](#1. Kev 学习的任务是什么)
    • [2. 从一条业务记录构造训练样本](#2. 从一条业务记录构造训练样本)
      • [2.1 一条记录可以同时监督多个问题](#2.1 一条记录可以同时监督多个问题)
      • [2.2 仓库里的数据从哪里来](#2.2 仓库里的数据从哪里来)
      • [2.3 生成规则数据时,先算答案,再写成文字](#2.3 生成规则数据时,先算答案,再写成文字)
      • [2.4 划分与冻结是训练前的一步](#2.4 划分与冻结是训练前的一步)
    • [3. 训练与推理共用的解析过程](#3. 训练与推理共用的解析过程)
      • [3.1 JSON 先变成模型能读的文本](#3.1 JSON 先变成模型能读的文本)
      • [3.2 三种 API 类型统一成候选项选择](#3.2 三种 API 类型统一成候选项选择)
    • [4. 一条记录如何编码成 token](#4. 一条记录如何编码成 token)
      • [4.1 用边界标记划分状态、问题和候选项](#4.1 用边界标记划分状态、问题和候选项)
      • [4.2 编码结果包含位置和读出索引](#4.2 编码结果包含位置和读出索引)
      • [4.3 上下文长度有不同含义](#4.3 上下文长度有不同含义)
    • [5. 模型由哪些部分组成](#5. 模型由哪些部分组成)
      • [5.1 文本主干负责产生上下文表示](#5.1 文本主干负责产生上下文表示)
      • [5.2 LoRA 让部分投影矩阵适应决策任务](#5.2 LoRA 让部分投影矩阵适应决策任务)
      • [5.3 指针读出头是两层线性投影加点积](#5.3 指针读出头是两层线性投影加点积)
    • [6. 多个问题如何共享状态,又保持隔离](#6. 多个问题如何共享状态,又保持隔离)
      • [6.1 纯注意力主干可以使用一条打包序列](#6.1 纯注意力主干可以使用一条打包序列)
      • [6.2 混合主干需要独立的问题行](#6.2 混合主干需要独立的问题行)
      • [6.3 问题隔离不等于选项顺序无关](#6.3 问题隔离不等于选项顺序无关)
    • [7. 一条样本进入训练循环后发生什么](#7. 一条样本进入训练循环后发生什么)
      • [7.1 每个 epoch 可以生成不同的训练变体](#7.1 每个 epoch 可以生成不同的训练变体)
      • [7.2 用 logits 手算一次交叉熵](#7.2 用 logits 手算一次交叉熵)
      • [7.3 软目标表达不确定性](#7.3 软目标表达不确定性)
      • [7.4 代码提供的可选损失](#7.4 代码提供的可选损失)
      • [7.5 误差如何更新参数](#7.5 误差如何更新参数)
    • [8. 训练结束后,保存什么、如何校准](#8. 训练结束后,保存什么、如何校准)
    • [9. 同一条记录上线后如何推理](#9. 同一条记录上线后如何推理)
      • [9.1 先复用输入转换,再调度模型](#9.1 先复用输入转换,再调度模型)
      • [9.2 第一次请求计算状态,分支从它继续](#9.2 第一次请求计算状态,分支从它继续)
      • [9.3 一次决策不等于只调用一次 forward](#9.3 一次决策不等于只调用一次 forward)
    • [10. 概率如何变回业务答案](#10. 概率如何变回业务答案)
    • [11. 按源码重新走一遍](#11. 按源码重新走一遍)
    • 源码与复现说明

Kev 用语言模型读取状态、问题和候选项,再为每个候选项计算概率。本文沿一条退款记录,解释它如何进入数据集、变成 token、产生损失,最后作为 API 请求返回决策。

源码基线:654f5203cd99e72616bb7d49b8a3659a701c5e99,写作日期:2026-10-17。本文描述这个代码快照的机制,不将某个历史 checkpoint 的配方当成所有版本的统一配置。退款样本为本文自建;文中 logits、概率及其派生指标是教学演算,不是模型实测结果。

1. Kev 学习的任务是什么

Kev 的输入由共享状态 state 和一组问题 questions 组成。状态可以是一封邮件、一份政策、一段代码或一个结构化业务对象。每个问题提供自己的指令和候选项。模型输出每个问题的候选项概率。

例如,同一份退款申请可以同时问:应该批准、拒绝还是转人工;是否符合退款资格;应该采用哪个处理优先级。这些问题共享申请材料,但各自作答。

这个接口对应一个条件分布:

text 复制代码
p(候选项 i | 状态 S,问题指令 I,该问题的全部候选项 O)

候选项文本是输入的一部分,因此每次请求可以使用不同的选项名称、描述和数量。输出的概率只在当前问题给出的候选项内归一化。没有显式提供"未知"或"以上皆非"时,接口不会自动增加一个拒答类别。

图 1 中,训练阶段的标签用于计算误差;上线阶段由相同的文本转换路径构造输入,再使用训练好的参数计算概率。

2. 从一条业务记录构造训练样本

2.1 一条记录可以同时监督多个问题

下面是一条可供 --data 读取的训练记录。为了阅读方便,这里展开了 JSON;存为 JSONL 时,每条记录需要占一行。

json 复制代码
{
  "state": {
    "policy": "Approve a refund if elapsed_days <= 30 and opened is false. Reject otherwise. If either fact is missing, use manual review. Priority is normal when amount <= 200, and high otherwise.",
    "case": {"elapsed_days": 12, "opened": false, "amount": 128}
  },
  "questions": {
    "decision": {
      "type": "choice",
      "instructions": "Apply the refund policy to this case.",
      "criteria": {
        "approve": "Approve the refund",
        "reject": "Reject the refund",
        "manual": "Send for manual review"
      },
      "label": "approve",
      "src": "tutorial_policy"
    },
    "eligible": {
      "type": "noul",
      "instructions": "Does this case qualify for a refund?",
      "label": true,
      "src": "tutorial_policy"
    },
    "priority": {
      "type": "score",
      "instructions": "Choose the processing priority according to the policy.",
      "criteria": ["low", "normal", "high"],
      "label": 1,
      "src": "tutorial_policy"
    }
  }
}

政策规定,未拆封且购买后不超过 30 天可以退款。样本满足这两个条件,所以 decision 的标签是 approve,eligible 的标签是 true。金额 128 不超过 200,所以 priority 的标签是从 0 开始编号的等级 1,也就是 normal。

label 是监督信号,src 用来记录问题来源。自定义文件经 data.load_records() 读取后,还会补齐记录级 _meta,包括 id、group_id、source、text_sha256 等字段。已有元数据会保留,可用于溯源、分组评估和复现实验。

_meta 不会被拼进模型输入。label、软标签 target 和 src 也不会作为问题文本提供给模型。它们在数据处理和损失计算中使用。源码:data.py

2.2 仓库里的数据从哪里来

训练入口支持三种主要来源:冻结套件的 train 分区、用户自己的 JSONL,以及从公开数据集即时构造的小规模数据。自定义数据还可以通过 --suite 与 --replay N 混入旧套件训练样本,用于继续微调。

数据类型 如何构造 标签从哪里来
公开分类、阅读理解、自然语言推断数据 把原文放进 state,把任务写成指令,把类别变成候选项 原始数据集标签
生成的政策、组合规则数据 先生成可执行规则和事实,再渲染成政策文本与案例 Python 规则求值器
后续真实文档和领域数据 将文档、业务问题和候选项组织为同一请求格式 随数据集而异,可能是原标签、人工审核、启发式或模型裁决
用户自定义数据 每行一份带标签的请求 用户指定的业务标签

例如,Banking77 的客户消息变成银行意图 Choice,BoolQ 的文章和问题变成 Noul,情感评级数据变成 Score。转换器也会变换外层结构,让同一类文本以字符串、嵌套对象或数组出现。

一个可核对的基础配方是 decision-v7。其 manifest 记录了 10,000 条公开数据、896 条生成政策数据和 1,680 条组合规则数据,共 12,576 条训练记录、15,576 个问题。这是基础阶段的数据组成,后续领域微调和研究训练可能使用其他套件。来源与数量应以对应 checkpoint 的训练配置和模型卡为准。数据清单:decision-v7

2.3 生成规则数据时,先算答案,再写成文字

composition.py 中,规则是一棵由原子判断和逻辑运算组成的树。原子判断包括数值比较、区间、字段相等、日期间隔和布尔标记;逻辑运算包括 and、or、not、unless、if。

退款例子的核心可表示为:

text 复制代码
and(le(elapsed_days, 30), not(opened))

求值器先在结构化事实上计算结果,再把规则和事实渲染成自然语言。缺失字段会返回 None,表示依据当前事实无法确定。生成器选择能够确定答案的案例,并保留规则、事实和决定性字段等证书信息,供校验函数重新计算。

生成器还围绕同一规则构造四条记录:一对只改变关键事实,使标签翻转;另一对只改变无关事实,使标签保持不变。这样可以检查模型是否在使用政策要求的证据。

图 2 是教学化的退款示例。实际组合规则生成器会检查:每对恰好改变一个事实,相关修改确实翻转答案,无关修改确实保持答案。相同规则可以用不同文风渲染;部分规则结构和表达形式专门留给评估。源码:composition.py

2.4 划分与冻结是训练前的一步

冻结套件包含 train、calibration、development、test 分区及 manifest.json。manifest 记录分区哈希、数量、数据集版本、来源策略和上下文限制。load_split() 会校验文件的 SHA-256 和记录数;缺少的大文件可以从 manifest 或仓库规则指定的镜像获取。读取锁定测试需要显式授权参数。

组合规则数据按 group_id 划分,避免同组的相似记录落入训练和校准两边。训练策略还会拒绝仅供评估的来源和被保留的规则结构。语义哈希用于部分精确重叠检查,其能力范围不等同于完整的预训练污染排查。

超长数据也必须在这一阶段处理。自定义数据默认丢弃超出训练上下文的记录,可用 --context_overflow error 改为报错。正式训练编码使用严格检查,避免在训练中悄悄截断状态。源码:suite.py

3. 训练与推理共用的解析过程

3.1 JSON 先变成模型能读的文本

训练路径调用 data.materialize(),内部先通过 api_request() 取出 API 允许的字段,再由 Pydantic 的 SystemOneRequest 验证,最后调用与服务端相同的 api.to_record()。

text 复制代码
带标签的请求
  → api_request:提取 state、type、instructions、criteria
  → SystemOneRequest:验证问题类型与候选项结构
  → to_record:渲染文本,生成候选项与输出映射
  → materialize:在训练记录上附回数字标签、来源与软目标

api.render() 将对象和数组展开为文本,并保留字段名。它不会直接把整个对象作为 JSON 字符串送入主干。例子的 state 经过实际函数转换后如下:

text 复制代码
policy: Approve a refund if elapsed_days <= 30 and opened is false. Reject otherwise. If either fact is missing, use manual review. Priority is normal when amount <= 200, and high otherwise.
case:
  elapsed_days: 12
  opened: False
  amount: 128

这里的 False 来自 Python 对布尔值的文本转换。字段名称、嵌套层级和候选项描述都会成为模型所读文本的一部分。

3.2 三种 API 类型统一成候选项选择

API 类型 模型实际接收的选项 训练标签 推理结果如何解释
choice 选项名;有描述时为 name: description 名称映射为当前选项序号 最大概率的名称及完整分布
noul 固定顺序的 no、yes,可附 false、true 对应描述 false → 0,true → 1 返回 p(yes)
score 按列表顺序排列的等级描述 从 0 开始的等级序号 返回等级期望及完整分布

退款样本的内部记录可缩写为:

json 复制代码
{
  "state": "policy: ...\ncase:\n  elapsed_days: 12\n  opened: False\n  amount: 128",
  "questions": [
    {
      "instr": "Apply the refund policy to this case.",
      "options": ["approve: Approve the refund", "reject: Reject the refund", "manual: Send for manual review"],
      "label": 0,
      "qtype": "choice",
      "keys": ["approve", "reject", "manual"]
    },
    {"instr": "Does this case qualify for a refund?", "options": ["no", "yes"], "label": 1, "qtype": "noul"},
    {"instr": "Choose the processing priority according to the policy.", "options": ["low", "normal", "high"], "label": 1, "qtype": "score"}
  ]
}

这段展示省略了 src、qid 等字段,并缩写了状态。真正的状态文本包含完整政策。训练时数字标签附在记录旁边;编码器只把状态、指令、候选项和边界标记变成 token。

Choice 的 label 依赖当前选项顺序。例如 approve 移到第 3 位,它的数字标签就必须从 0 改为 2。先保留选项名称,再在 materialize() 中查当前索引,可以让训练增强正确更新标签。源码:api.py

4. 一条记录如何编码成 token

4.1 用边界标记划分状态、问题和候选项

model.encode() 构造如下逻辑序列:

text 复制代码
<state> 共享状态
<q> 问题一指令 <opt> 批准 </opt> <opt> 拒绝 </opt> <opt> 转人工 </opt> <decide>
<q> 问题二指令 <opt> no </opt> <opt> yes </opt> <decide>
<q> 问题三指令 <opt> low </opt> <opt> normal </opt> <opt> high </opt> <decide>

上面使用易读别名,实际实现复用 Qwen 已有的特殊 token,默认不扩展词表:

本文别名 实际特殊 token
<state> `<
<q> `<
<opt> `<
</opt> `<
<decide> `<

用户文本通过 user_tokens() 单独分词。形如 <\|name\|> 的文本会被重写,防止用户内容伪造这些控制边界。边界 token 则由编码器显式插入。

4.2 编码结果包含位置和读出索引

字段 含义
ids 整条逻辑记录的 token ID
seg 状态为 0,各问题依次为 1、2、3
pos 主干使用的位置编号
decide_idx 每个问题 <decide> 的位置
opt_idx 每个问题各个 </opt> 的位置
opt 用于可选选项隔离的跨度标记

设状态长为 S,其中包含 <state>。每个问题的位置编号都从 S 开始,而不是接在前一个问题的位置后面。因此单独问某个问题和将它与其他问题一起提交时,它看到的状态、上下文和位置编号相同。

图 3 的矩阵是按状态和问题分块的示意。行表示当前 token 所在段,列表示可读取的段;对角块内部仍然使用因果下三角关系,不能读取未来 token。源码:model.encode 与 branch_mask_batch

4.3 上下文长度有不同含义

在这个快照中,历史基础训练默认值为 MAX_STATE=384、MAX_BRANCH=1024、MAX_PACKED=2048。其中 max_branch 限制的是"状态 + 一个问题分支"的总长度,max_packed 限制逻辑打包记录的总长度。

训练可以提高 --max_state,也可以显式设置 --max_question_tokens 与 --max_context。显式模式会检查状态预算与问题预算之和是否放得进主干窗口。

服务端默认状态上限为 65,536 tokens,单个"状态 + 问题"行上限为 73,728 tokens。checkpoint 若保存了显式上下文配置,加载时会优先使用它。代码允许的长度、某次训练实际使用的长度、某个 checkpoint 经验证的有效长度,是三个不同的量,不能相互替代。

5. 模型由哪些部分组成

Kev 的模型主体在 DecisionModel。它保留预训练模型的文本主干,用主干输出的最后一层隐藏向量计算候选项分数。

5.1 文本主干负责产生上下文表示

主干包括 token embedding、decoder 层堆栈和末端归一化。输入 token 经过这些层后,每个位置得到一个 d 维隐藏向量。Kev 从 last_hidden_state 取值,不需要为了读出而保留所有中间层输出。

纯注意力主干沿用普通因果注意力 decoder。当前家族所用的 Qwen3.5/3.8 混合主干还包含 Gated DeltaNet 层。这些层通过短卷积状态和循环状态传递历史信息,与完整注意力层共同构成主干。具体层数、隐藏宽度和层类型排列由所选基础模型的配置决定,Kev 没有给所有尺寸写死一套层数。

模型使用文本主干的隐藏状态,不执行词表语言模型头,也没有"预测下一个 token、再把它接回输入"的生成循环。候选项内容在前向计算前就已给定。

5.2 LoRA 让部分投影矩阵适应决策任务

常规 LoRA 模式冻结原始主干权重,为选中的线性投影加入可训练的低秩增量:

text 复制代码
W_effective = W_base + (alpha / r) · B · A
A 的形状为 [r, d_in],B 的形状为 [d_out, r]

代码默认 r=16、alpha=2r、LoRA dropout 为 0.05。lora_targets=all 覆盖注意力的 Q/K/V/O 投影、MLP 的 gate/up/down 投影;混合主干还加入 DeltaNet 的输入与输出投影。也可以选择较小的目标集合。

LoRA 和指针读出头一起训练。可选的 --special_embeddings 还允许训练这几个边界 token 的 embedding;默认关闭。使用 --init_from 时,兼容性检查通过后会加载已有适配器及读出头,继续学习新数据。

框架也支持 --full_ft 1。此时训练整个文本主干和读出头,主干使用 bf16 权重,优化器维护 fp32 主权重与状态。多 GPU 使用 FSDP2 分片;单 GPU 可以把主权重和优化器状态放在主机内存。全量训练与 LoRA 训练共用数据格式和决策读出方式。

5.3 指针读出头是两层线性投影加点积

对某个问题,令 h_decide 表示 <decide> 位置的隐藏向量,h_i 表示候选项 i 的 </opt> 位置隐藏向量。PointerHead 的计算是:

text 复制代码
u   = Wq · h_decide + bq                形状 [dp]
v_i = Wk · h_i + bk                     形状 [dp]
z_i = dot(v_i, u) / sqrt(dp)             标量分数
p_i = exp(z_i / T) / Σ_j exp(z_j / T)    当前问题内的概率

默认 dp=256。两组投影都带偏置,参数量为 2 × dp × (d+1)。对 K 个选项,头部只需产生 K 个 logits,不需要产生整个词表的 logits,也不需要为每个业务类别保存一组独立的固定分类权重。

</opt> 位于候选项文本之后,因此它的表示可以包含选项内容、问题指令和状态。<decide> 位于整个选项列表之后,因此可以读取该问题的全部选项。点积衡量这两类上下文表示之间的匹配程度。

这些向量都来自同一次决策计算。PointerHead 中的 q、k 是读出头自己的投影,与主干注意力层内部的 Q/K/V 投影是不同参数。

6. 多个问题如何共享状态,又保持隔离

6.1 纯注意力主干可以使用一条打包序列

对纯注意力模型,短记录可以使用图 3 的 block-causal mask。允许 token i 读取 token j 的条件是:

text 复制代码
j <= i,并且满足以下任意一个条件:
  j 属于共享状态;
  j 与 i 属于同一个问题分支。

状态内部保持因果关系。问题可以读取状态和自身前面的 token,不能读取其他问题。这样状态在打包前向中只计算一份。过长的打包记录会切换到行形式,避免显式二维掩码随总长度平方增长。

6.2 混合主干需要独立的问题行

Gated DeltaNet 层的循环状态不会按任意四维注意力 mask 隔离。直接把不同问题串起来,后面的问题仍可能继承前面问题留下的循环状态。

因此混合主干固定使用独立行:

text 复制代码
行 1 = 共享状态 + decision 问题
行 2 = 共享状态 + eligible 问题
行 3 = 共享状态 + priority 问题

各行具有独立的分支计算状态,可以作为 batch 运行。只要分支从同一份状态前缀开始,前向语义就与对应的单问题请求一致。

普通行式训练会为每个问题重算状态。可选的 --shared_prefix 1 在每个 decoder 层先算状态,再让分支读取该层状态留下的缓存,全量训练默认启用这个路径。注意力层传递 K/V,DeltaNet 层传递卷积窗口和循环状态。

训练中的共享前缀必须保留梯度。shared_prefix.py 用函数式状态连接前缀与分支,让各问题的梯度回传并累加到状态计算上;开启梯度检查点时,一层的状态计算和分支计算一起重算。源码:shared_prefix.py

6.3 问题隔离不等于选项顺序无关

默认编码中,同一问题内的选项按顺序排列。后面的选项边界可以读取前面的选项,因此交换选项顺序可能改变概率。

训练时打乱选项、可选的排列 KL,都是减少顺序敏感性的办法。另一个可选模式 option_isolation 会让每个选项只读取状态、指令和自身,并为不同选项复用位置编号,再由 <decide> 汇总。这个模式需要打包 mask,在本快照的混合主干上会被拒绝。

7. 一条样本进入训练循环后发生什么

7.1 每个 epoch 可以生成不同的训练变体

record_variants() 根据全局 seed、epoch 和记录 ID 创建该记录自己的随机流。Choice 选项会重排,还可能发生以下一种增强:

增强 对本例的修改 标签如何变化
选项重排 调换 approve、reject、manual 的顺序 名称保持 approve,数字索引随位置更新
正确项替换为"以上皆非" 删除 approve,加入 none 选项 正确答案变成 none
加入错误的 none 选项 保留 approve,再加入 none 仍然选择 approve
加入无关干扰项 增加天气等与案例无关的答案 仍然选择 approve

这些增强针对 Choice;Noul 和 Score 保持原来的选项顺序。代码会维护选项数量等条件,不能把默认概率理解为每条记录都按该比例成功增强。

--p_none_pair 还可以让原样本额外产生一对兄弟记录。第一条保留正确答案并加入 none,第二条仅移除正确答案,让同一个 none 成为正确项。两条使用同样的状态与指令,促使模型检查"正确选项是否真的存在",而不是记住 none 的某种措辞。

软目标问题只重排 Choice 选项,不进行会改变目标含义的选项插入或替换。长状态还可以用 --none_pair_max_state 限制配对,因为额外两条记录会再次占用状态计算资源。源码:augment、none_pair、record_variants

7.2 用 logits 手算一次交叉熵

假设本例的三个问题得到如下 logits。以下数值用于演算,未运行预训练权重:

问题 logits softmax 概率 正确项
decision [2, 0, -1] [0.843795, 0.114195, 0.042010] approve,索引 0
eligible [-1, 2] [0.047426, 0.952574] yes,索引 1
priority [-0.5, 1.5, 0] [0.099624, 0.736125, 0.164252] normal,索引 1

训练时读出头使用 T=1。硬标签的主损失是候选项交叉熵:

text 复制代码
L_question = -log p_y

L_decision = -log(0.8437947) = 0.169846
L_eligible = -log(0.9525741) = 0.048587
L_priority = -log(0.7361247) = 0.306356

L_record = (L_decision + L_eligible + L_priority) / 3
         = 0.174930

这里的 log 为自然对数。batch_loss() 先对一条训练变体内部的问题取平均,再累加各变体的贡献。因此,三个问题的一条记录不会仅因为问题多,就自然获得三倍于单问题记录的主损失权重。

累积梯度时还会按本次 optimizer step 的原始记录数和微批次的变体数量归一化。如果 row_budget 把一条记录按问题拆开,代码用 share 恢复它在原记录中的占比。不能简单把实现概括成"对所有 token 求平均",也不能在存在变体时一律写成"对所有问题直接求平均"。源码:question_loss 与 batch_loss

7.3 软目标表达不确定性

某些记录无法由给定信息唯一确定答案,可以提供按选项键组织的 target 分布。materialize() 将它按当前选项顺序排列、补零并归一化。

text 复制代码
L_soft = -Σ_i target_i · log(p_i)

如果两个答案的监督目标是 [0.5, 0.5],最优输出也是均匀分布。该损失的最小值是目标分布的熵,即 log(2),不是 0。不能用硬标签交叉熵趋近于 0 的直觉判断软目标训练是否收敛。

本快照的 question_loss() 遇到软目标会直接返回软交叉熵,因此函数后面的标签平滑、Brier、focal、序数项和 Choice 标签权重不会继续作用在这道题上。外层配置的其他正则项仍需看各自适用条件。

7.4 代码提供的可选损失

这些开关默认都为 0。某个损失存在于代码中,不代表所选 checkpoint 使用了它。

开关 计算或作用
--ord_w 对 Score 加上预测累计分布与真实累计分布的均方误差,也就是归一化 ranked probability score
--perm_kl 用两次选项排列的输出,按选项身份对齐后计算对称 KL,约束顺序敏感性
--anchor_w 对选项集合完全匹配的问题,加 `KL(冻结基础模型的分布
--label_smoothing 将硬标签 CE 改为平滑目标的 CE
--brier_w 在硬标签 CE 上加概率与 one-hot 目标的平方差之和
--focal_gamma 将硬标签 CE 乘以 (1-p_y)^gamma
--choice_label_weights 按正确的 Choice 标签名称给硬标签损失加权

序数项的具体形式为:

text 复制代码
RPS = mean over k=0..K-2 of (Σ_{i<=k} p_i - 1[y<=k])²
L_score = CE + ord_w · RPS

Score 默认仍以等级分类训练,即使 API 最后返回等级期望,也不意味着默认损失是均方回归误差。标签平滑、Brier 和 focal 在代码中最多同时启用一个;序数项、排列约束和 anchor 则有各自的组合条件。

7.5 误差如何更新参数

对硬标签 CE,logit 的梯度为:

text 复制代码
∂L / ∂z_i = p_i - 1[i=y]

decision 示例:[-0.156205, 0.114195, 0.042010]

这使优化更新倾向于提高正确答案的分数、降低错误答案的分数。梯度从 logits 经过指针头,再经过 <decide> 与候选项边界的隐藏向量进入主干计算。状态 token 即使没有自己的独立标签,也会通过问题对状态的依赖收到梯度。

在 LoRA 模式下,梯度经过主干运算,但原始主干参数不更新;更新的是 LoRA 参数和读出头,以及显式开启的特殊 token embedding。全量模式则更新主干本身。

微批次的梯度累积到一步后,LoRA 路径同步多卡梯度,以全局范数 1.0 裁剪,再由 AdamW 更新,随后推进学习率调度并清空梯度。全量模式使用 MasterAdamW 完成主权重更新与裁剪。基础配方通常使用 batch × accum × world_size 条原始记录构成一步,最后一组、长度调度和 token 上限模式会影响具体微批次划分。

8. 训练结束后,保存什么、如何校准

LoRA checkpoint 保存适配器配置及权重、head.pt 和 tokenizer 文件。head.pt 包含读出头参数、基础模型和 revision、LoRA rank、读出维度、精度、温度及训练来源信息。全量 checkpoint 保存文本主干的配置与 safetensors 分片,再配同样的读出头和元数据。Checkpoint 统一处理这两种加载布局。

模型选择和校准也属于这条生命周期。实验流程可以先在校准分区拟合一个温度,再在开发集观察准确率、NLL、Brier、ECE 和问题隔离等指标。锁定测试用于候选确定后的确认,而不是反复搜索超参数。

温度校准只调整最终 logits 的尺度:

text 复制代码
p_i(T) = softmax(z / T)_i
T* = argmin over T>0 of mean[-log p_y(T)]

对 [2, 0, -1],T=1 得到 [0.8438, 0.1142, 0.0420];假设使用 T=2,概率变成 [0.6285, 0.2312, 0.1402]。批准仍然是最大项,但分布更平缓。这个 T=2 只是演示,并非退款任务拟合出的温度。

训练不使用已拟合的温度。PointerHead 仅在 eval 模式应用它,加载器会从 checkpoint 恢复该值。

校准数据的来源同样影响结论。训练套件内部留出的条目适合试验筛选,却仍与训练来源同分布。这个快照的正式 checkpoint 校准脚本默认要求训练未使用的数据集,并检查来源冲突;历史版本存在分布内温度。解释某个发布模型的概率时,应读取其温度的来源记录,而不能仅看到一个 temperature 数字就认为它能覆盖所有业务分布。源码:校准脚本

9. 同一条记录上线后如何推理

9.1 先复用输入转换,再调度模型

业务方发送 POST /v1/systemone,请求保留 state 和 typed questions,不需要训练标签。服务端调用 to_record()、model.encode(),检查上下文,然后把编码结果交给模型线程。

同一线程从队列中收集请求,规划缓存并调用 probs_batch()。支持的 CUDA graph 路径会将兼容形状的状态和问题行合批;其他路径可以逐请求执行。因此"队列里有多个请求"不意味着所有后端都会把它们塞进同一次主干调用。

服务端最终调用 to_answers() 把概率还原成 API 类型,再由 Python 序列化为 JSON。接口里的 output_tokens 是返回答案文本的分词统计,不表示模型生成过这些 token。源码:serve.py

9.2 第一次请求计算状态,分支从它继续

对混合主干,缓存未命中时先对状态做一次 prefill。这里的 prefill 是对已知输入 token 的前向计算。

每层留下两类可能的缓存:完整注意力层的 K/V,或 DeltaNet 层的卷积窗口与循环状态。三个问题分别从同一个状态前缀开始,继续计算自己的指令、候选项和 <decide>。

分支使用独立的缓存副本或对应批次槽位,避免一个问题的后续计算写入另一个问题的起点。需要缓存的是状态结束时的前缀,不是"状态 + 第一个问题"之后的状态。

跨请求的前缀缓存以状态 token ID 和选项隔离设置为键,在当前已加载模型内复用。它受 LRU 条目数和总状态 token 预算约束。相同状态配新问题可以命中缓存;语义近似但 token 不同的状态不会据此命中。

从处理的 token 数看,Q 个问题各自完整跑一遍需要:

text 复制代码
Q × S + Σ B_q

共享前缀后,新状态只需处理 S + Σ B_q 个输入 token;跨请求命中则只新增分支 token 的前向。这里是 token 处理量,不是完整计算复杂度。注意力层的分支仍要读取前缀 K/V,缓存也占显存,延迟不会简单按上述数量比例缩短。

9.3 一次决策不等于只调用一次 forward

Kev 没有自回归生成循环,但一次请求仍可能包含状态 prefill、一个或多个分支批次、读出与格式化。问题数量、输入长度和执行后端都会改变实际的前向调用次数。

本快照的普通 torch 评估路径在短行上使用直接行式前向,可能为每个问题重算状态;服务路径则使用前缀缓存。长行评估还有高效注意力与共享前缀的专门路径。隔离语义保持一致,浮点结果的容差要以对应 dtype、算子和测试条件为准,不能假设不同精度与融合内核逐位相同。

服务可通过合并 LoRA、bf16、融合算子、CUDA graphs 和长度分组减少延迟。这些改变执行方式或数值精度,不改变"从边界隐藏状态读出候选项分布"的接口结构。源码:model.py · 加载器:checkpoint.py

10. 概率如何变回业务答案

继续使用前面的教学 logits,并令 T=1。api.to_answers() 实际计算出的答案部分如下,省略了模型名、usage 和延迟:

json 复制代码
{
  "decision": {
    "type": "choice",
    "choice": "approve",
    "confidence": 0.7657,
    "probabilities": {"approve": 0.8438, "reject": 0.1142, "manual": 0.0420}
  },
  "eligible": {"type": "noul", "noul": 0.9526},
  "priority": {
    "type": "score",
    "score": 1.0646,
    "legend": {"0": "low", "1": "normal", "2": "high"},
    "probabilities": {"0": 0.0996, "1": 0.7361, "2": 0.1643},
    "confidence": 0.6042
  }
}

Choice 返回最大概率选项。这里的 confidence 是相对均匀分布修正后的值,不是原始最大概率:

text 复制代码
choice_confidence = (p_max - 1/K) / (1 - 1/K)
本例 K=3,所以 confidence ≈ 0.7657

只有一个选项时,函数返回 1。Noul 返回第二个选项 yes 的概率 0.9526,调用方可以按业务需要再设阈值。

Score 返回按等级索引计算的期望:

text 复制代码
score = Σ_i i · p_i
      = 0×0.09962365 + 1×0.73612472 + 2×0.16425163
      = 1.06462798

因此最可能的等级是 normal,API 的连续 score 是 1.0646。索引与业务单位需要应用自行映射,比如把 0、1、2 映射为自己的优先级编码。

Score 的 confidence 按概率质量到众数等级的平均距离计算,并用均匀分布的尺度归一化。它反映分布是否集中,与 Choice 使用的公式不同:

text 复制代码
mode = 第一个最大概率等级
D = (1/K) · Σ_i |i - (K-1)/2|
score_confidence = max(0, 1 - Σ_i p_i·|i-mode| / D)

单等级同样返回 1。以上结果用未舍入的概率计算,再在序列化时保留 4 位小数。

11. 按源码重新走一遍

样本当前的位置 主要函数 产物
带业务标签的 JSONL data.load_records 或 suite.load_split 带来源与 ID 的请求
本 epoch 的训练输入 record_variants、augment、none_pair 重排、干扰或最小对变体
统一模型文本 api_request、to_record、materialize state 文本、选项文本、数字标签
token 与分支结构 model.encode ids、seg、pos、读出位置
主干与头部前向 forward_batch、PointerHead 每个问题的候选项 logits
监督与优化 question_loss、batch_loss、训练循环 梯度、更新后的模型参数
可加载模型 finish_checkpoint、Checkpoint 主干或适配器、head、tokenizer、元数据
温度与评估 fit_temperature、benchmark 与校准脚本 温度、逐题结果与评估报告
在线决策 Server、probs_batch、PrefixCache 候选项概率
业务响应 api.to_answers Choice、Noul、Score 的 JSON

对这条退款记录而言,业务规则和事实先决定监督标签;文本转换决定模型实际读到什么;主干把每个边界位置变成上下文向量;读出头计算选项分布;交叉熵再决定应如何修改参数。上线后,相同的输入表示经过已训练参数,并通过缓存减少重复状态计算,最终还原为业务接口中的答案。

源码与复现说明

本文以本地提交 654f5203cd99e72616bb7d49b8a3659a701c5e99 为核对基线。下列链接指向公开仓库的文件入口,main 后续可能变化;复现本文应按提交与文件哈希确认版本。

阅读目标 源码
API 校验、文本渲染和答案还原 kev/api.py
公开数据转换、增强和内部记录 kev/data.py
规则树、事实求值、最小修改校验 kev/composition.py
分组划分与冻结套件 kev/study_v3.py、kev/suite.py
编码、掩码、主干和指针头 kev/model.py
训练变体、损失与梯度累积 kev/train.py
可微共享前缀与全量优化 kev/shared_prefix.py、kev/full_ft.py
权重布局、精度与温度加载 kev/checkpoint.py
请求队列与前缀缓存 kev/serve.py
温度拟合的数据来源要求 scripts/calibrate_checkpoint.py

本文的字段转换、标签映射、选项重排、none 配对及答案还原,使用同一源码快照的 api.py 和 data.py 函数核对;概率和损失用 Python 数学函数复算。验证没有下载或运行模型权重,因此本文没有把演算结果当成模型能力证据。

相关推荐
EterNity_TiMe_1 小时前
Docker服务越装越多记不住端口?我用Fenrus把NAS里的常用入口整理成一个导航页
运维·人工智能·docker·ai·容器·cpolar
晨间晓1 小时前
图片里的关键点怎么找?用华为云码道开发 OpenCV FAST 特征点检测工具
人工智能·opencv·华为云
科创致远1 小时前
科创致远 ESD 静电监控全景落地指南
人工智能·信息可视化·制造·精益工程
晨间晓1 小时前
二值图里的噪点怎么去掉?用华为云码道开发 OpenCV 形态学操作工具
人工智能·opencv·华为云
今年下半年1 小时前
AI智能体项目实战记录(05):RAG项目实战之向量知识库milvus部署
人工智能·llm·milvus·rag
haluhalu.1 小时前
深入理解 AI Agent(1.2):Harness 工程——模型之外的竞争力
前端·数据结构·人工智能·python·langchain·个人开发
fpcc1 小时前
AI和大模型—矩阵相似
人工智能·线性代数·矩阵
data analyse 4562 小时前
服务端统计时区切换的日期归并
大数据·前端·数据库·人工智能·数据分析
DongQiShanRen2 小时前
六根硬件映射(下):物理闭环五步法,抽一条已实装的看
大数据·jvm·人工智能·算法·rust·哈希算法·集成学习