跳转至

GLM Post-Training Beyond SFT

导言

部门要突破 GLM 模型的 NPU 原生训练,真正困难的不是把一个 GRPO loss 搬到 NPU,而是让 Actor 训练、推理 Rollout、Teacher Prefill、Verifier、权重同步和低精度数值形成闭环。

本文以 GLM-5 技术报告为主线,并补充 GLM-5.2 已公开的 SAO、CompactionRL 和十多个专家的并行 OPD。重点解释非 SFT 后训练中的 RL、OPD/MOPD,同时单独澄清 QAT 与量化:GLM-5 明确把 INT4 QAT 放在 SFT 阶段,它对 NPU 后训练很重要,但不是非 SFT RL 的一个阶段。

先给结论:什么才是 NPU 原生

截至 2026 年 8 月,GLM-5 有完整技术报告;更新的 GLM-5.2 有官方博客,并由 SAO 与 CompactionRL 两篇论文补充后训练方法,但还没有一份同等完整的新技术报告。公开证据可以还原出下面这条演化线:

  1. GLM-5:SFT 之后依次执行 Reasoning RL、Agentic RL、General RL,最后用 Cross-Stage OPD 合并各阶段能力。1
  2. GLM-5.2:继续用 slime 承载更大规模、更复杂的 Agentic RL;官方披露并行 OPD 合并了十多个专家模型,整个 OPD 约两天完成。2
  3. GLM-5.2 的方法补充:SAO 用单轨迹异步优化消除组采样屏障;CompactionRL 把上下文摘要变成可训练动作,两篇论文都称方法用于 GLM-5.2 的 RL pipeline。34

这意味着“原生”至少有三层含义:

  • 算子原生:Attention、DSA Top-k、MoE、log-softmax、fake quant、collective 等核心算子运行在 NPU 上。
  • 框架原生:训练、rollout、teacher、reward、权重同步不依赖 GPU 侧的隐藏补丁或中转。
  • 数值闭环原生:同一 token、同一前缀、同一量化规则下,训练策略、rollout 策略和教师策略的概率可比较;偏差能被观测、校正和回归测试。

第三层才是 RL/OPD 是否真的成立的分界线。如果 NPU rollout 重新分词、DSA 选中了另一组 token、低精度 GEMM 改变了采样分布,训练端即使成功反向传播,也是在优化另一个行为策略。

NPU 原生后训练的认知锚点

NPU 原生不是“单个 loss 已运行”,而是训练、采样、教师、验证和低精度数值共同闭环。此图是认知插图,不作为技术证据。

本文的范围

本文先解释为什么这些方法成立、优化了什么对象、可能在哪里失效。它不是 GLM-5.2 NPU 复现手册,也不把论文方法公开等同于官方训练 recipe 已经开源。

一条统一主线

后面的算法名称很多,但都可以放回四个问题:

问题 典型对象 方法如何回答
状态从哪里来 prompt、学生 trajectory、环境 observation、compacted history RL/OPD 都尽量在当前学生会访问的状态上学习
信号从哪里来 rule reward、ORM、LLM judge、Critic value、teacher log-prob GRPO 用组内奖励;PPO/SAO 用 Critic;OPD 用教师逐 token 信号
策略是否可比较 π_trainπ_rolloutπ_oldπ_teacher IcePop/DIS 约束概率比;TITO 保存 exact token ID
数值是否一致 Top-k index、BF16/FP8/INT4、scale、zero-point、KV Cache 确定性 Top-k、bitwise-identical quant kernel、精度分层

在传统的同步 RL 中,一批 prompt 先完成 rollout,再一起打分和更新。到了长程 Agent 任务,不同 trajectory 的长度可能相差几个数量级,最慢样本会让整批等待;一旦训练与推理解耦,吞吐变高,却又出现 policy lag:生成样本时的策略已经落后于当前训练策略。

因此 GLM 的核心矛盾不是“同步还是异步”二选一,而是:

允许系统异步前进,同时只让仍然可信的 token 产生梯度。

GLM 非 SFT 后训练统一主线

统一视图:状态、信号、策略版本和数值格式沿着 Rollout → Reward/Teacher → Train → Weight Sync 循环流动。

GLM-5 报告的原始训练流程图如下。它支持“多阶段后训练 + 最终 OPD”这一流程关系,但不能单独证明其中某一组件带来多少收益。

GLM-5 原始训练流程图

GLM-5 技术报告原始逻辑图。它是流程证据,不是 OPD、RL 或 QAT 的独立消融证据。

四层一致性:从状态到梯度

IcePop、Token-In-Token-Out(TITO)、Direct Double-Sided Importance Sampling(DIS)和 DSA Indexer 并不是四个互相替代的优化器。它们共同回答一个更基础的问题:训练端反向传播的动作,是否真的是 Rollout 端在同一状态下执行的动作。

可以沿着一条 token 的因果链理解四者:DSA 决定模型看见什么,TITO 固定模型做了什么,IcePop 判断同一组权重在训推引擎中是否表达同一策略,DIS 判断异步旧行为对当前策略是否仍可用。

层次 被固定或比较的对象 防止的错位 失败后的直接后果
DSA Indexer:状态一致性 被选中的历史位置集合 \(\mathcal I_t\) 训练与 Rollout 关注不同的 key/value 同一 token 前缀产生不同 hidden state 和 logits
TITO:动作一致性 exact token ID \(a_t\) 与 action/observation 边界 文本往返后重新分词得到另一个动作 训练概率指向的不是实际采样事件
IcePop:实现一致性 同权重下 traininfer 的 token 概率 kernel、精度、Top-k、KV 等训推数值路径不同 mismatch token 被当成可信 on-policy 数据
DIS:版本一致性 当前策略与实际 Rollout 行为策略的概率 异步轨迹完成时策略已经更新多次 过旧行为产生高方差或错误方向的梯度

概念、机制与 GLM 实现

这四个名字需要分三层理解,避免把一般原则与 GLM 的具体补丁混在一起:

方法 一般概念 可复用机制 GLM-5 公开实现
TITO RL 的动作事实是 token ID,不是解码文本 Rollout 传 exact IDs、mask、log-prob 和版本元数据 TITO Gateway 拦截生成请求并记录 token IDs 与 metadata
DSA Indexer 稀疏模型的状态还取决于离散路由结果 让训练与 Rollout 重放或确定性重算 Top-k index 训练端使用确定性 torch.topk\(k=2048\),RL 默认冻结 Indexer
IcePop 同参数的两个引擎也可能实现不同分布 用同权重训推概率比筛掉实现 mismatch 过大的 token \(\beta=2\);GLM-5 版本移除了原 IcePop 的 KL 正则项
DIS 异步队列中的行为策略会落后于当前策略 保存 Rollout log-prob,直接构造双边 token ratio 不维护大量历史策略;再用 policy version 丢弃整条过旧轨迹

这组机制并不“修好”底层差异。DSA 的确定性 Top-k 和 TITO 尽量消除差异来源;IcePop 与 DIS 则是证据准入门,在仍有差异时拒绝不可信 token。若 mask 比例持续升高,训练可能表面稳定但有效样本率不断下降,因此必须同时监控 trusted_tokens / action_tokens

关键对象

一条可训练 trajectory 至少要能把下面这些物理对象重新对齐:

对象 生产者 形态 消费者与生命周期
response_token_ids Rollout engine [T] 整数 token ID Actor 重算概率;随 trajectory record 保留到更新完成
topk_indices[t] DSA Indexer + Top-k [k] 历史位置,GLM-5 中 \(k=2048\) Sparse Attention 当前 token 使用;GLM-5 训练方案选择确定性重算而非全量跨进程保存
rollout_log_probs Rollout forward [T] 浮点数 IcePop/DIS 构造 ratio;是行为策略凭证
response_mask / observation_mask TITO Gateway 或 trajectory builder [T] 布尔值 policy loss 只保留模型动作;工具和环境 token 不产生梯度
policy_versions 权重同步与 Rollout engine 每段或每条轨迹的版本序列 trajectory-level staleness filter 判断整条样本是否过旧
trust_mask IcePop 或 DIS [T] 布尔值 policy loss 的最后准入条件;完成本次 backward 后释放

其中 sampling_configtokenizer_revision 和显式 observation_mask 是本文建议 NPU 系统纳入的可重放字段;GLM-5 报告只公开到“token IDs 与 metadata”,没有披露完整字段 schema。

Reasoning RL:相对奖励与可信 token

GRPO 在优化什么

给定一个 prompt \(x\),策略为它采样一组 \(G\) 个回答 \(y_1,\ldots,y_G\),并得到序列级奖励 \(R_1,\ldots,R_G\)。GRPO 不训练 Critic,而是用组内均值和标准差构造相对优势:

\[ \hat A_i=\frac{R_i-\operatorname{mean}(R_1,\ldots,R_G)} {\operatorname{std}(R_1,\ldots,R_G)+\epsilon}. \]

直觉上,同一个题目中比同伴更好的回答获得正优势,更差的回答获得负优势。它消除了跨题难度尺度差异:一道所有回答都失败的难题,不会仅因为奖励绝对值低就主导梯度。

\(r_{i,t}(\theta)=\pi_\theta(y_{i,t}\mid s_{i,t})/\pi_{\mathrm{old}}(y_{i,t}\mid s_{i,t})\),常见 clipped objective 是:

\[ \mathcal L_{\mathrm{GRPO}} =-\mathbb E_{i,t}\left[ \min\left(r_{i,t}\hat A_i,\operatorname{clip}(r_{i,t},1-\epsilon_l,1+\epsilon_h)\hat A_i\right) \right]. \]

其中 \(s_{i,t}=(x,y_{i,<t})\) 是第 \(t\) 个 token 的状态。奖励是序列级的,概率比和梯度却是 token 级的。因此,一个回答只要通过验证,其中每个生成 token 默认共享同一个方向,真正的 token 级差异来自概率比与 mask。

GLM-5 的 Reasoning RL 使用 GRPO + IcePop,组大小和 batch size 均为 32,训练数据混合数学、科学、代码和 Tool-Integrated Reasoning,并主要使用可验证的二值结果奖励。1

IcePop 为什么有两层 ratio

RL 解耦训练和推理后,至少存在两种偏差:

  1. 训练更新偏差:当前参数 \(\theta\) 相对旧训练策略 \(\theta_{\mathrm{old}}\) 改了多少,对应 PPO ratio \(r_t\)
  2. 训推实现偏差:在相同旧参数 \(\theta_{\mathrm{old}}\) 下,训练引擎重算的概率与 Rollout 引擎实际生成概率差了多少,对应 mismatch ratio
\[ \rho_t=\frac{\pi_{\theta_{\mathrm{old}}}^{\mathrm{train}}(y_t\mid s_t)} {\pi_{\theta_{\mathrm{old}}}^{\mathrm{infer}}(y_t\mid s_t)}. \]

第一类 ratio 回答“参数更新走了多远”,第二类 ratio 回答“两个引擎是否还在表达同一个策略”。把二者混成一个 ratio,会让优化步长和系统数值误差失去各自的诊断指标。

例如 Rollout 引擎给实际 token 的概率是 0.10,同权重训练引擎重算得到 0.35,则 \(\rho_t=3.5\)。这不是普通的 PPO 更新过大,而是两个执行路径对同一个离散动作意见不同。原因可能是 BF16/FP8 精度、DSA Top-k、位置编码、KV Cache、并行 vocabulary reduction 或权重切换时机不同。

IcePop 的核心不是把离群 ratio 裁到边界,而是将超出可信区间的 token 直接移出梯度。GLM-5 报告给出的 mismatch 阈值为 \(\beta=2\),即保留 \(\rho_t\in[1/2,2]\);PPO 的上下裁剪为 \(\epsilon_l=0.2,\epsilon_h=0.28\)1

\[ m_t=\mathbf 1\!\left[\beta^{-1}\le \rho_t\le\beta\right]. \]

\(\beta=2\) 时,1.21.9 会被保留,0.33.5 会被屏蔽。这里是 mask,不是 clamp:clamp 会把 3.5 改写成一个仍参与训练的边界值,mask 则承认这条证据无法可靠归因。最终把 \(m_t\) 乘到 token loss 上;当两个引擎意见相差太大时,继续反向传播比丢掉样本更危险。

# Rollout / forward path
for prompt in batch:
    responses, infer_logp, exact_ids = rollout_policy.sample(prompt, group=32)
    rewards = verifier(responses)
    group_adv = normalize_within_prompt(rewards)

# Train / backward path
old_train_logp = old_actor.logprob(exact_ids)
train_logp = actor.logprob(exact_ids)
mismatch = exp(old_train_logp - infer_logp)
trust_mask = (1 / beta <= mismatch <= beta)
ppo_ratio = exp(train_logp - old_train_logp)
loss = masked_clipped_policy_loss(ppo_ratio, group_adv, trust_mask)
loss.backward()
optimizer.step()

数据形态与所有权exact_idsinfer_logp、reward、policy version 属于 Rollout record;old_train_logp 由同权重旧训练策略重算,train_logp 和训练激活属于当前 Actor worker;group advantage 由 trainer 生成。Rollout 激活不应跨阶段保存,只有可重放的 token 与最小概率元数据进入训练队列。

DSA Indexer 的确定性为什么是 RL 问题

GLM-5 使用 DSA indexer 为每个 query 选择 Top-2048 key。报告指出,直接存下所有 replay index 成本太高,因此训练侧重算 Top-k;而非确定性的 CUDA/TileLang Top-k 曾导致 RL 明显退化和熵坍缩,最终训练使用确定性的 torch.topk,并默认冻结 indexer。1

Indexer 不是最终 Attention 权重,而是正式稀疏 Attention 之前的候选历史位置选择器。对第 \(t\) 个 token,Indexer 根据当前 query \(q_t\) 和历史 key 表示 \(K_{<t}\) 产生长度为 \(L_t\) 的相关性分数,再返回 \(k\) 个位置:

\[ \mathcal I_t=\operatorname{TopK} \left(\operatorname{Indexer}(q_t,K_{<t}),k\right), \qquad k=2048. \]

其中 \(L_t\) 是当前可访问的历史长度,\(k\) 是稀疏预算,\(H_i\) 是 Indexer 特征宽度。若 query head width 为 \(H_q\),选中 key/value 的宽度分别为 \(H_k,H_v\),则正式 Attention 只读取 \(K_{\mathcal I_t}\in\mathbb R^{k\times H_k}\)\(V_{\mathcal I_t}\in\mathbb R^{k\times H_v}\)。因此 \(\mathcal I_t\) 是一个离散控制对象:边界附近一个位置的排名发生交换,可能让某段错误日志被看见、另一段修复要求完全不可见。这与连续 tensor 出现 1e-3 误差不同,是模型状态的离散跳变。

query_t = current_query(hidden_t)                          # [H_q]
historical_key_features = index_features(key_cache)        # [L_t, H_i]
index_scores = indexer(query_t, historical_key_features)   # [L_t]
topk_indices = deterministic_topk(index_scores, k=2048)   # [2048]
selected_keys = gather(key_cache, topk_indices)            # [2048, H_k]
selected_values = gather(value_cache, topk_indices)        # [2048, H_v]
attention_scores = matmul(query_t, transpose(selected_keys)) * scale
attention_probs = softmax(apply_mask(attention_scores))
context_t = matmul(attention_probs, selected_values)
attention_output_t = attention_output_projection(context_t) # [H_model]
hidden_after_dsa = residual_update(hidden_t, attention_output_t)
logits_t = downstream_layers_and_lm_head(hidden_after_dsa)    # [V]

上面是语义展开,不声称 GLM kernel 会逐个物化所有中间 tensor。需要追踪的物理边界是:Indexer score 的生产者、Top-k 的归并与 tie 规则、gather 后 KV 的布局,以及最终 token log-prob。

这件事很容易被误认为普通算子精度问题。实际上,如果训练与 rollout 选择了不同的稀疏注意力集合,即便参数相同,二者也实现了不同的策略:

\[ \mathcal I_t^{\mathrm{train}}\ne\mathcal I_t^{\mathrm{rollout}} \quad\Rightarrow\quad \pi^{\mathrm{train}}(\cdot\mid s_t)\ne\pi^{\mathrm{rollout}}(\cdot\mid s_t). \]

假设位置 1024 是错误日志、位置 8096 是修复要求,而位置 7731 的 Indexer 分数与 8096 几乎相同。Rollout 端选择 {1024, 8096},训练端因低精度舍入或跨卡归并顺序选择 {1024, 7731};训练端即使使用同一 token 前缀,也已不再重放 Rollout 的状态。

因此 NPU 迁移不能只验证 index_scores 的平均误差,还要逐级检查:

  1. 集合一致性:Top-k 选中了哪些位置;
  2. 顺序一致性:同一集合的排列是否会影响后续 gather 或 kernel;
  3. 边界稳定性:tie、NaN、正负零、index dtype 和跨卡归并规则;
  4. 概率一致性:最终 sampled-token log-prob 的差异,而不只看 Indexer;
  5. 行为一致性:固定随机数和采样配置后,最终 token 是否相同。

GRPO IcePop 与 DSA 确定性

GRPO 提供组内相对方向,IcePop 决定哪些 token 仍可信,确定性 DSA 保证两个引擎看到同一个稀疏状态。

阅读这张五联图时,应依次看:物理视图中的稀疏位置选择、因果视图中的 Top-k 偏差传播、流程视图中的重算与 mask、时序视图中的 Rollout/Trainer 所有权,以及数据视图中 indices → logits → log-prob 的路径。它解释一致性机制,不提供某个 NPU kernel 的性能收益数字。

迁移到 NPU 时

Top-k 只要“集合大致相同”仍不够。相同分数、边界 tie、排序稳定性、NaN 处理和 index dtype 都可能改变选中集合。需要比较 index 集合、顺序、最终 log-prob 与采样结果,而不仅是 kernel 输出的平均误差。

Agentic RL:异步之后先处理策略滞后

Agentic RL 的 rollout 不再是一段纯文本,而是交替出现模型动作与环境观察:

\[ \tau=(a_0,o_0,a_1,o_1,\ldots,a_n,o_n). \]

其中 observation 由编译器、终端、搜索引擎或浏览器产生,模型不对 observation token 负责。长程 coding/search 让不同 trajectory 的耗时非常不均匀,GLM-5 因此将训练和 rollout 完全解耦,并使用中央 Multi-Task Rollout Orchestrator 调度。1

TITO:token ID 是动作事实

TITO 是 Token-In-Token-Out。若 Rollout 先输出文本,训练端再 tokenize,空格、特殊 token、工具协议或 tokenizer 版本的差异会让训练动作与真实动作不一致。TITO 保留 Rollout 引擎产生的 exact token IDs,使训练端在实际被采样的离散动作上重算概率。

这不只是避免工程 bug。策略梯度中的动作 \(a_t\) 就是 token ID;一旦 ID 改了,公式中的 \(\pi(a_t\mid s_t)\) 已经指向另一个事件。

例如 Rollout 实际采样的动作序列是:

[151644, 198, 12457, 25]

文本往返后,训练端可能因空格、Unicode 规范化或工具特殊 token 重新分成:

[151644, 198, 903, 718, 25]

两条序列长度和动作边界都不同,reward、advantage 与 log-prob 已无法逐 token 对齐。TITO 的“in/out”强调训练管线消费推理引擎的原始 token 流,而不是说系统不保留可读文本;文本可用于日志和环境交互,但不能成为重建策略动作的唯一凭证。

一条面向 NPU 闭环的 trajectory record 建议至少包含:

prompt_token_ids
response_token_ids
rollout_log_probs
response_mask
observation_mask
policy_versions
sampling_config
tokenizer_revision

observation_mask 尤其关键:终端日志、搜索结果、编译器报错等 observation 是环境给模型的状态,不是模型动作,不能进入 policy loss。GLM-5 报告直接披露的是 TITO Gateway 记录 token IDs 与 metadata;上面列出的完整字段是本文为可重放 NPU 系统给出的工程 schema,不代表官方逐字段 recipe。

TITO 的实现所有权通常跨越 Rollout gateway、trajectory schema 和 trainer data loader。迁移新 tokenizer、chat template 或工具协议时,必须同步更新边界生成和回归样例;只在 trainer 里加一个 input_ids 字段并不能保证 TITO 成立。

Direct Double-Sided Importance Sampling

异步系统中,一个 trajectory 生成期间 rollout 权重可能更新多次,精确维护每个 token 对应的旧训练 checkpoint 代价很高。GLM-5 直接把 rollout 时保存的概率当作行为策略代理:

\[ r_t(\theta)=\exp\left( \log\pi_\theta(a_t\mid s_t)- \log\pi_{\mathrm{rollout}}(a_t\mid s_t) \right). \]

然后使用双边可信区间:

\[ f(r_t)= \begin{cases} r_t,&1-\epsilon_l<r_t<1+\epsilon_h,\\ 0,&\text{otherwise}. \end{cases} \]

这里的“0”表示该 token 不产生梯度。相比只在特定优势方向裁剪的 PPO,它更激进地拒绝不可信 token;相比维护历史模型集合,它用可控的 off-policy 偏差换取系统可实现性。

“Direct” 指分母直接使用生成当时保存的 rollout_log_probs,不再重建大量 \(\pi_{\theta_{\mathrm{old}}}\) checkpoint;“Double-Sided” 指 ratio 过大和过小都会被拒绝,而不是只限制提高某个动作概率的一侧。例如 1.1 可保留,1.80.3 都应屏蔽,具体边界由 \(\epsilon_l,\epsilon_h\) 决定。

可以用一条长轨迹理解 policy lag:Rollout 从版本 v17 开始,生成期间 trainer 已更新到 v18v19,轨迹到 v19 才进入队列。DIS 的 token ratio 判断其中每个动作对 v19 是否仍可用;policy version 阈值则先判断整条轨迹是否已经旧到不值得逐 token 检查。

GLM-5 还用版本差 \(v_{\mathrm{train}}-v_{\mathrm{rollout}}>\tau\) 丢弃过旧样本;权重每 \(K\) 次更新同步到推理侧,并在推理权重更新后重置 optimizer。环境失败样本也单独过滤:若组内仍有一半以上有效样本则补齐,否则整组丢弃。1

IcePop 与 DIS 不要混用

两者都有概率比和双边 mask,但分子、分母及回答的问题不同:

方法 典型比较 主要偏差来源 控制粒度 不能替代什么
IcePop 同一旧权重的 train 概率 / infer 概率 kernel、精度、DSA、KV、并行归约等实现差异 token 不能修复底层 kernel,也不能单独表达长时间 policy lag
DIS 当前训练策略 / 实际 Rollout 行为策略 参数版本滞后,同时叠加训推实现差异 token,并配合 trajectory 版本过滤 不能证明两个引擎在同权重下数值一致

因此,IcePop 更像同版本执行一致性检查,DIS 更像异步旧证据的可用性检查。在异步系统里,DIS ratio 同时吸收了版本变化和引擎差异;若想定位 NPU 数值问题,仍应额外做同 checkpoint 的 train/infer mismatch 回归,而不能只看 DIS mask。

# Asynchronous rollout workers
sample = environment.run(policy_version=v_rollout)
sample.store(exact_ids, rollout_logp, observations, v_rollout)
queue.push(sample)

# Trainer consumes completed samples without waiting for a whole task batch
sample = queue.pop_ready()
if current_version - sample.v_rollout > tau:
    discard(sample)
train_logp = actor.logprob(sample.exact_ids)
ratio = exp(train_logp - sample.rollout_logp)
token_mask = (1 - eps_l < ratio < 1 + eps_h)
loss = -mean(token_mask * ratio * advantage * train_logp)
loss.backward()
optimizer.step()

if step % K == 0:
    sync_and_reshard_weights(actor, rollout_engines)

异步 Agentic RL 的概率与版本契约

异步吞吐来自“完成即消费”,稳定性来自 exact token、rollout log-prob、policy version 与双边 token mask。

这张五联图把 TITO 与 DIS 放在同一条生命周期里:物理视图对比文本往返与 exact token,因果视图展示 policy lag,流程视图执行版本过滤和 token mask,时序视图标出 Rollout/Queue/Trainer/Weight Sync 的先后关系,数据视图则跟踪 IDs、log-prob、version 和最终梯度。它不意味着四个对象必须由同一个进程持有。

DP-aware routing 进一步把同一多轮会话稳定路由到同一个 rollout DP rank,以复用 KV Cache。它是性能优化,但同时改变了 KV 的 owner 和生命周期:路由表、会话 version 与 cache eviction 必须一致,否则“复用”可能变成读取错误状态。

General RL:奖励不是一个标量来源

Reasoning RL 的二值可验证奖励适合数学和代码,但事实性、情商、指令遵循和写作质量难以用同一规则描述。GLM-5 的 General RL 混合三类信号:1

  • 规则奖励:格式、长度、引用、工具协议等可以确定判断的约束。
  • Outcome Reward Model(ORM):预测回答最终是否满足目标。
  • 生成式奖励模型:读取上下文与回答,生成分析或评分,覆盖开放式质量。

它们通常都先落成 trajectory-level reward,再进入优势估计。混合奖励的本质不是“把分数相加”这么简单,而是选择一个标量化函数:

\[ R(\tau)=\sum_k w_kR_k(\tau)-\sum_j\lambda_jP_j(\tau). \]

权重 \(w_k\) 定义能力之间的交换率,惩罚 \(P_j\) 定义不可接受行为。若 Reward Model 学会利用数据偏差,策略会放大这种偏差。因此 GLM-5 还使用人工编写的 anchor 保持目标不漂移,但公开报告没有披露完整奖励权重和数据配比。

General RL 与 NPU 的关系

Reward worker 可以不在 NPU 上,但它输出的 sample ID、版本、reward 分量和失败原因必须可追溯。否则训练端只看到一个标量,无法区分模型退化、环境失败、judge 漂移或解析错误。

OPD:把教师变成逐 token 优势

多阶段 RL 会产生一个常见问题:后一阶段强化新能力时,前一阶段能力可能被覆盖。GLM-5 最后使用 On-Policy Cross-Stage Distillation:把之前各阶段的最终 checkpoint 当作教师,从各教师原来的 RL 数据中采 prompt 并混合,学生生成自己的轨迹,再由对应教师在这些前缀上打分。1

为什么是 on-policy

给定 prompt \(x\),学生先采样:

\[ y\sim\pi_\theta(\cdot\mid x). \]

教师不是给一条固定答案,而是在学生已经走到的每个状态 \(s_t=(x,y_{<t})\) 上计算概率。GLM-5 用下面的 log-ratio 直接替换 RL advantage:

\[ \hat A^{\mathrm{OPD}}_t =\operatorname{sg}\left[ \log\pi_T^{\mathrm{infer}}(y_t\mid s_t) -\log\pi_\theta^{\mathrm{train}}(y_t\mid s_t) \right]. \]

sg 是 stop-gradient。若教师比学生更偏好学生刚采到的 token,优势为正,学生提高该 token 概率;反之为负。它等价于 sampled reverse-KL 的 policy-gradient 形式:只需教师返回学生实际采样 token 的 log-prob,不必传输 [tokens,vocab] 全词表 logits。

# Forward: student owns the state distribution
exact_ids = student_rollout.sample(prompt)
teacher_logp = teacher.prefill_and_score(exact_ids)
student_logp = actor.logprob(exact_ids)

# Backward: teacher gap becomes dense token advantage
opd_adv = stop_gradient(teacher_logp - student_logp)
loss = -mean(opd_adv * student_logp)
loss.backward()
optimizer.step()

由于教师差值已经提供 token 级方向,不需要同一 prompt 采 32 个回答做组内比较。GLM-5 报告把 OPD 的 group size 设为 1,batch size 提高到 1024。教师 logits 由推理引擎计算;报告也指出后续可切到训练引擎或 MQA mode。1

OPD 和 RL 的关系

OPD 的核心是学生 rollout 上的教师信号,不是某个特定优化器。至少有三种实现维度必须分开:

  1. 分布传输:full vocab、teacher top-k、或只传 sampled token log-prob。
  2. 损失形式:直接 KL/cross entropy,或把 log-ratio 写成 policy-gradient advantage。
  3. 任务奖励:可以纯蒸馏,也可以在 OPD advantage 上叠加 task advantage。

当前 slime 的 OPD 实现已经把 OPD 作为与 advantage estimator 正交的项:读取 teacher log-prob,并把 student-teacher reverse-KL 差加入 advantage。6

MOPD:能力整合发生在策略空间

MOPD(Multi-Teacher On-Policy Distillation)把多领域能力整合拆成三步:5

  1. 从同一个通用 SFT checkpoint 出发;
  2. 各领域独立做最适合自己的 RL,得到数学、指令、SWE、工具等教师;
  3. 学生仍从通用 SFT 初始化,自己生成多领域 trajectory,再按领域把每条 trajectory 硬路由到一个对应教师

其完整 reverse KL 是:

\[ \mathcal L_{\mathrm{revKL}} =\mathbb E_{x,\,y\sim\pi_\theta} \left[ \frac1{|y|}\sum_t\sum_v \pi_\theta(v\mid s_t) \log\frac{\pi_\theta(v\mid s_t)}{\pi_{T_{d(x)}}(v\mid s_t)} \right]. \]

其中 \(d(x)\) 是 prompt 的领域,\(T_{d(x)}\) 是被路由到的教师。工程上可用 sampled-token PG 形式,也可传教师 Top-k。MOPD 的 Top-k 目标额外加入 \(\pi_T(v)-\pi_\theta(v)\) 修正截断偏差,使 top-k 区域的最优点仍位于师生分布相等。5

为什么强调同源教师

同源不是组织关系,而是优化条件:教师与学生从同一个 SFT checkpoint 分叉,初始策略分布接近。MOPD 的受控实验中,Qwen3-30B-A3B 的三领域归一化得分为 0.9373,高于 Mix-RL 的 0.8818;而把数学教师换成更强但异源的 Qwen3-235B-A22B 后,初始 per-token KL 约从 0.04 升到 0.19,训练出现熵收缩甚至 Top-k 版本崩溃。5

MOPD 受控实验证据

MOPD 原论文的受控证据:同源领域教师的整合更均匀;外部大教师实验说明“教师更强”不等于“更可蒸馏”。实验对象与 GLM 不同。

GLM 与 MOPD 的准确关系

GLM-5 报告称 Cross-Stage OPD:上阶段 checkpoint 是教师,教师各自的 RL prompt 被混合。GLM-5.2 官方博客进一步披露并行 OPD 合并了十多个专家。二者都具有“多教师、路由或分源数据、学生 on-policy trajectory、策略空间整合”的结构。

但 GLM 官方没有把这套方法命名为 MOPD。因此更准确的说法是:

  • GLM-5 的 Cross-Stage OPD 是多阶段教师整合;
  • GLM-5.2 的十多个专家并行 OPD 在结构上接近 MOPD;
  • MOPD 论文提供了更完整的多领域同源教师原理与消融证据,但不是 GLM recipe 的直接证明。

OPD MOPD 与 GLM 多阶段整合

OPD 选择学生状态分布;MOPD 再增加领域教师路由。GLM 的官方命名与结构性映射在图中分开。

缩写冲突

文献中 MOPD 也可能指 Multi-Rollout On-Policy Distillation。本文只使用 Multi-Teacher On-Policy Distillation,判断时应看全称而不是缩写。

GLM-5.2:SAO 与 CompactionRL

GLM-5.2 没有简单沿用 GLM-5 的全部公开配方。两篇后续论文分别针对长程 Agent RL 的两个结构性瓶颈:组采样等待上下文耗尽

SAO:一条轨迹完成就训练

GRPO 的 group baseline 需要同一 prompt 的 \(G\) 条轨迹全部完成。长程任务中,最快样本会等待最慢样本;异步系统里,这段等待还会增加 policy staleness。SAO(Single-Rollout Asynchronous Optimization)把 group size 降到 1,每条完成的轨迹立即进入训练。3

SAO 单轨迹异步逻辑图

SAO 原论文逻辑图:单轨迹消除了 prompt 组内的最慢样本 barrier。

\(G=1\) 后无法计算组内相对奖励,SAO 必须重新引入 Value Model \(V_\phi\) 来降低 REINFORCE 方差,并使用三个稳定机制:

  1. DIS:直接用 rollout log-prob 计算双边 ratio,超出区间的 token 不产生梯度。
  2. Critic 更快更新:每次 policy 更新前做 \(K>1\) 次 value update,论文设置 \(K=2\)
  3. 冻结 Critic Attention:论文观察到 Value Model 的 Full Attention 梯度更不稳定,因此冻结 attention,只更新 MoE 投影。

对 Agent trajectory,observation 不是模型动作。SAO 的 Skip-Observation GAE 直接把当前动作末尾连接到下一动作开头:

\[ \delta=r_t+\gamma V(a_{i+1,0})-V(a_{i,N}), \]
\[ \hat A(a_{i,N})=\delta+\gamma\lambda\hat A(a_{i+1,0}). \]

这样不会强迫 Critic 为外部 observation token 学一个虚假的自回归价值过渡。

trajectory = async_rollout.one(prompt)
values = critic(trajectory.action_tokens)
adv = skip_observation_gae(values, trajectory.rewards)

for _ in range(K):
    value_loss(critic, returns).backward()
    critic_optimizer.step()

ratio = exp(actor.logprob(ids) - trajectory.rollout_logp)
mask = inside_double_sided_trust_region(ratio)
policy_loss(mask, ratio, adv).backward()
actor_optimizer.step()

SAO 论文在 Qwen3-30B-A3B 上报告:SWE-bench Verified 从 GRPO+DIS 的 27.0 提高到 SAO 的 29.8;数学任务也优于对照。这个实验支持 SAO 组件组合,但不是 GLM-5.2 的独立组件消融。3

SAO 实验证据

SAO 原论文证据图。主实验使用 Qwen3-30B-A3B;论文另称 SAO 已部署到 GLM-5.2 pipeline,二者不能合并成 GLM-5.2 的因果增益。

CompactionRL:摘要也要为任务结果负责

长程 Agent 即使有 1M context,也会遇到 KV Cache、调度和上下文预算限制。CompactionRL 在剩余预算低于阈值时,让同一个可训练策略生成摘要:4

\[ C-|h_t|<T_{\mathrm{comp}}, \qquad S_t\sim\pi_\theta(\cdot\mid h_t\oplus q_{\mathrm{sum}}). \]

随后用系统提示、摘要和最近 \(k\) 个原子化的 action-observation 对重建上下文:

\[ \bar h_t=(s)\oplus u_{\mathrm{resume}}(S_t) \oplus(z_{t-k+1},\ldots,z_t). \]

摘要不是外部预处理器。它和普通执行 token 一样由 Actor 生成,并共享最终任务奖励 \(R(\tau)\)。如果摘要漏掉文件路径、失败命令或未解决错误,后续执行失败会反向惩罚摘要行为。

CompactionRL 原始逻辑图
CompactionRL 原论文逻辑图:执行段与摘要段共享终局奖励,重建上下文后继续 rollout。

压缩会把一条 trajectory 切成数量和长度都不固定的 \(K\) 个 segment。若按 segment 平均 loss,压缩次数多的轨迹会被重复加权。CompactionRL 因此在全部可训练 token 集合 \(\mathcal M\) 上归一化 PPO loss:

\[ \mathcal L_\pi=-\frac1{|\mathcal M|} \sum_{(s,i)\in\mathcal M} \min\left( \rho_{s,i}\hat A_{s,i}, \operatorname{clip}(\rho_{s,i},1-\epsilon,1+\epsilon)\hat A_{s,i} \right). \]

第二个问题是信用距离。若每个 segment 独立做 GAE,早期摘要会“看起来”离终局奖励很近。设后续 segment 还有 \(N_{>s}\) 个可训练 token,跨轨迹修正为:

\[ \hat A_{s,i}=(\gamma\lambda)^{N_{>s}}A^{\mathrm{loc}}_{s,i}. \]

这恢复了早期 token 到最终结果的大致时间距离,同时保留 segment 独立训练的系统并行性。

CompactionRL 的 GLM-4.5-Air 实验中,80K 峰值上下文下的压缩推理,SWE-bench Verified 从 base 的 59.8、普通 RL 的 62.5 提高到 66.8;去掉 token-level loss 后降至 60.0,去掉 cross-trajectory GAE 后为 63.04

CompactionRL 实验证据

CompactionRL 原论文证据:实验支持摘要联合训练、token-level loss 和跨段信用修正;实验模型是 GLM-4.x,不是 GLM-5.2 消融。

QAT:让模型在量化误差里学习

先纠正阶段归属:GLM-5 报告原文明确写道,INT4 QAT 应用于 SFT stage,并开发了同时适用于训练和离线权重量化的 kernel,以保证训练与推理逐比特一致。1

因此它不属于本文的“非 SFT 方法序列”,但它直接决定 NPU 上低精度后训练能否保持分布一致,必须单独理解。

Fake quant 的前向

对一组浮点值 \(x\),仿射量化可写成:

\[ q=\operatorname{clip}\left( \operatorname{round}\left(\frac{x}{s}\right)+z, q_{\min},q_{\max} \right), \]
\[ \hat x=s(q-z). \]

\(s\) 是 scale,\(z\) 是 zero-point。QAT 前向并不一定真的把模型状态长期存成 INT4;它通常执行 quantize → dequantize,让后续 GEMM 看到离散化后的 \(\hat x\),从而把舍入、截断和动态范围误差暴露给训练。

量化粒度决定多少元素共享一组 qparam:

  • per-tensor:整个 tensor 共用一组 scale,开销小、误差大;
  • per-channel/per-row:一行或一个输出通道一组;
  • per-group/per-block:例如每 32 或 128 个权重一组,在误差与元数据间折中;
  • per-token activation:每个 token 的激活独立求 scale。

torchao 的 INT4 fake quant 代码明确使用 quant_min=-8, quant_max=7 的有符号范围;其通用 fake-quant op 等价于量化再反量化但不做实际 dtype cast。7

STE 的反向

round 几乎处处梯度为 0,直接求导会让训练停止。Straight-Through Estimator 在反向把离散算子近似成恒等映射:

\[ \frac{\partial\operatorname{round}(u)}{\partial u}\approx1. \]

torchao 的 _Round.backward 直接返回上游梯度 gy,就是最直白的 STE。8

# QAT forward
scale, zero_point = choose_qparams(weight, granularity=group_size)
q = clamp(round(weight / scale) + zero_point, qmin, qmax)
fake_weight = scale * (q - zero_point)
output = matmul(input, fake_weight)
loss = task_loss(output, target)

# QAT backward
# round is replaced by identity in the gradient path
grad_weight ~= grad_fake_weight
loss.backward()
optimizer.step()  # update the floating master weight

物理对象必须分清:浮点 master weight 由 optimizer 更新;q 是临时整数语义;fake_weight 仍是浮点 tensor;部署 checkpoint 则可能是实际 packed INT4 加 scale/zero-point。只看 Python dtype 很容易误判究竟在哪一步获得了内存收益。

为什么强调 bitwise-identical

若 QAT 前向和离线转换使用不同的 rounding mode、group 划分、scale 精度、饱和范围或 packing 顺序,训练适应的是误差 \(e_{\mathrm{train}}\),部署面对的却是另一个误差 \(e_{\mathrm{deploy}}\)

\[ \hat W_{\mathrm{train}}=Q^{-1}_{a}(Q_a(W)), \qquad \hat W_{\mathrm{deploy}}=Q^{-1}_{b}(Q_b(W)). \]

即使两者平均误差相近,MoE router、DSA indexer 或采样 logits 的边界 token 也可能改变。GLM-5 的“逐比特一致”要求,本质上是在消除这一隐藏的第二量化器。

QAT 与量化的对象和梯度

QAT 用浮点 master weight 更新参数,前向模拟低比特误差;真正 packed 权重通常出现在转换或推理阶段。

MindSpeed-LLM 的固定提交已经暴露 w4a16-mxfp4w4a16-mxfp4-moe-onlyw4a4-mxfp4w4a8-moe-only 等 QAT scheme,并允许 W4A8 使用 32/128 block size。9 这说明昇腾侧已有 QAT 算子积木,但仍不能据此断言 GLM-5.2 的官方 INT4 QAT 配方已复现。

量化:三件事不要混为一谈

围绕 GLM 的公开材料,至少存在三条低精度路径:

路径 训练参数 前向数值 主要目的 是否产生适应量化误差的梯度
QAT 通常保留浮点 master weight fake quant 或低精度训练 kernel 让模型适应目标量化误差
量化 Rollout Actor 可继续 BF16 训练 推理侧 FP8/INT8/INT4 权重或 KV 降低采样显存、带宽和时延 否,除非同一误差也进入训练前向
离线部署量化 训练已结束 packed W8A8/W4A8/INT4 降低部署成本

slime 当前推荐的大型 MoE 路径是 Megatron BF16 训练 + SGLang FP8 rollout:同步时把更新后的 BF16 权重按 rollout checkpoint 的量化配置转换成 FP8 后发送;FP8 KV Cache 是另一个独立开关。固定提交把 INT4 rollout/QAT 标为 beta,FP8 训练 + rollout 标为 experimental。10

这条路径最容易产生误解:Actor optimizer 并没有在 FP8 参数上更新,但行为数据由 FP8 rollout 策略产生,所以必须继续测量:

\[ \Delta_t=log\pi^{\mathrm{BF16\ train}}(y_t\mid s_t) -\log\pi^{\mathrm{FP8\ rollout}}(y_t\mid s_t). \]

IcePop/DIS 可以屏蔽 \(\Delta_t\) 过大的 token,却不能替代量化校准。如果大量 token 被屏蔽,训练虽然不崩,有效样本率也会下降。

另一方面,GLM-5.2 的官方 Ascend 部署文档使用 QuaRot、Flex SmoothQuant 和 SSZ 组合的混合 W8A8;这是部署推理方案,不是 QAT 证据。11 MindSpeed-RL 还公开了基于 Flash-RL 的 NPU INT8 online-quantized rollout 示例,通过 BF16/INT8 profile 决定更新权重如何在线量化;同样不能把它写成 GLM-5.2 官方 recipe。12

判断一个 low-precision 声明

依次问五件事:量化谁、在哪个进程、何时量化、以什么粒度、梯度是否穿过量化误差。只说“支持 INT4/FP8”几乎没有技术含义。

NPU 原生的数值契约

把前面的方法压缩成 NPU 研发对象,可以得到六份不能破坏的契约。

NPU 原生后训练数值契约

六份契约把算法语义落到 token、概率、索引、版本、量化与对象生命周期,而不预设某个框架实现。

动作与概率契约

  • rollout 交付 exact token IDs、response mask、rollout log-prob、采样配置和 tokenizer/chat-template revision;
  • train、rollout、teacher 必须对同一 token ID 和同一前缀计算 log-prob;
  • observation、prompt、工具返回和复制的 compacted tail 不进入 policy loss;
  • sampled log-prob 的精度、logsumexp 和 vocabulary parallel reduction 必须有对照测试。

稀疏与版本契约

  • DSA Top-k 记录 tie、排序、NaN、index dtype 与跨卡归并规则;
  • rollout sample 携带 policy version,trainer 定义可接受的最大 staleness;
  • 权重同步包含训练分片 → 推理分片 → 量化 → 发送 → 原子切换 → 确认,不把“传输完成”等同于“新策略已生效”;
  • 多轮会话的 DP routing 与 KV Cache owner 同步迁移或显式失效。

Teacher 与量化契约

  • 每个 prompt 的 teacher route 可追溯,教师 tokenizer 和 action space 与学生兼容;
  • teacher prefill payload 明确是 sampled log-prob、Top-k IDs/logits 还是 full vocab;
  • QAT 与离线量化共享 rounding、scale、zero-point、group/block、饱和和 packing 语义;
  • BF16/FP8/INT8/INT4 分别标注为 model state、GEMM compute、rollout weight、KV Cache 或 checkpoint format。

内存不是“参数量乘位宽”

RL/OPD 峰值显存至少应写成:

\[ M_{\mathrm{peak}}= M_{\mathrm{model\ state}}+ M_{\mathrm{saved\ activations}}+ M_{\mathrm{operator\ workspace}}+ M_{\mathrm{communication\ buffers}}+ M_{\mathrm{live\ IO}}+ M_{\mathrm{allocator}}+ M_{\mathrm{runtime\ margin}}. \]

其中 Actor、Reference、Teacher、Critic、rollout KV、DSA index、teacher logits、weight-reshard buffer 可能在不同 NPU、不同进程或不同时间段驻留。必须为每个对象记录:

  • 逻辑形状与物理布局
  • producer、consumer 与 owner
  • 创建、跨进程传输、复用和释放时间
  • 属于持久模型状态、保存激活、算子 workspace、通信 buffer 还是 live IO

现有昇腾栈到了哪里

固定到本文核验的提交,开源状态可以概括为:

能力 已有证据 仍缺的 GLM-5.2 闭环证据
GLM-5.2 模型训练 MindSpeed-LLM 有 744B-A40B、32 节点、每节点 16 NPU 的预训练脚本,含 DSA/MoE/MTP GLM-5.2 SFT/RL/OPD 的公开 NPU recipe
通用 RL MindSpeed-RL 支持 GRPO/PPO/DAPO、异步队列、训推重分片、vLLM-Ascend IcePop、GLM TITO/DIS、Cross-Stage OPD 的 E2E 示例
量化训练 MindSpeed-LLM 有多种 W4/A4/A8 QAT scheme 与 GLM 报告逐比特一致要求对应的公开验证
量化 rollout MindSpeed-RL 有 Qwen INT8 online-quantized rollout 示例 GLM-5.2 DSA/MoE 的概率 mismatch、吞吐和收敛证据
昇腾推理 GLM-5.2 官方文档支持 vLLM-Ascend/SGLang/xLLM 与混合 W8A8 训练 Actor、Teacher Prefill、Verifier、权重原子切换的统一 recipe

因此当前最准确的判断不是“昇腾不支持 GLM RL”,而是:模型、通用 RL、QAT 与推理积木已经存在;等价于官方 GLM-5.2 的端到端 NPU 原生后训练配方仍是公开缺口。

证据能说明什么

本文把证据分成四层,避免把模型级结果误写成算法因果:

  1. 流程证据:GLM-5 报告和 GLM-5.2 博客说明哪些阶段、框架和方法被使用。
  2. 方法证据:MOPD、SAO、CompactionRL 在各自受控模型和任务上的结果,说明机制有实验支持。
  3. 代码证据:固定提交证明某个 flag、loss、QAT scheme、backend 或示例存在。
  4. 缺口证据:没有找到 GLM-5.2 NPU E2E recipe,只能表述为“截至固定版本未找到”,不能推断内部或未来一定不存在。

尤其要保留三个边界:

  • GLM-5/5.2 的总体 benchmark 不能隔离某个 RL、OPD 或 QAT 组件的收益;
  • SAO 的主实验是 Qwen3-30B-A3B,CompactionRL 的主实验是 GLM-4.x;“用于 GLM-5.2 pipeline”是部署陈述,不是 GLM-5.2 消融;
  • GLM-5 报告没有给 INT4 QAT 的独立精度/吞吐消融,不能编造“QAT 提升了多少”。

总结

GLM 的非 SFT 后训练可以记成一条清晰主线:

  1. Reasoning RL 用 GRPO 获得组内相对方向,用 IcePop 和确定性 DSA 只保留可信 token;
  2. Agentic RL 用异步 rollout 提升吞吐,再用 exact token、rollout log-prob、双边 IS 和 policy version 控制滞后;
  3. General RL 把规则、ORM 和生成式 judge 组合成开放任务的目标;
  4. Cross-Stage OPD 用教师 token 分布恢复前序阶段能力,GLM-5.2 的十多个专家并行 OPD 在结构上接近多教师 MOPD;
  5. SAO 消除组内等待,但以 Critic 和 Value Model 稳定机制换取单轨迹更新;
  6. CompactionRL 把摘要纳入策略,让上下文压缩也对最终任务负责;
  7. QAT 用 fake quant + STE 让模型适应低比特误差,但 GLM-5 明确把它放在 SFT,而 FP8/INT8 rollout 与 W8A8 部署是另外两条量化路径。

对 NPU 团队而言,最需要掌握的不是更多算法缩写,而是动作、概率、索引、版本、量化和对象生命周期。这些契约守住以后,RL、OPD、SAO 或 QAT 才能在 NPU 上保持它们原本的数学含义。

参考文献


  1. GLM Team, GLM-5: from Vibe Coding to Agentic Engineering, 2026。后训练方法见 Section 3;INT4 QAT 的 SFT 阶段归属见 Section 2.5.4。 

  2. Z.ai, GLM-5.2: Built for Long-Horizon Tasks, 2026-06-16。官方披露 slime、十多个专家并行 OPD 与约两天训练时间。 

  3. Hou et al., Single-Rollout Asynchronous Optimization for Agentic Reinforcement Learning, 2026。 

  4. Li et al., CompactionRL: Reinforcement Learning with Context Compaction for Long-Horizon Agents, 2026。 

  5. Ma et al., MOPD: Multi-Teacher On-Policy Distillation for Capability Integration in LLM Post-Training, 2026。 

  6. THUDM, slime on-policy distillation documentationloss implementation, 固定提交 f655e13。 

  7. PyTorch AO, fake_quantizer.py, 固定提交 623c463。 

  8. PyTorch AO, _Round with STE backward, 固定提交 623c463。 

  9. Ascend, MindSpeed-LLM QAT quant engine, 固定提交 79afbee。 

  10. THUDM, slime low-precision training and rollout, 固定提交 f655e13。 

  11. Z.ai, Using Ascend NPU to Deploy GLM-5.2, 固定提交 436efa0。 

  12. Ascend, MindSpeed-RL Online Quantized Rollout, 固定提交 26c21e6。 

评论