YAML Metadata Warning:empty or missing yaml metadata in repo card

Check out the documentation for more information.

MiniCPM5-2B PonderNet 循环思考推理改造 — 分析报告与实现

对象仓库: tchbcb/MiniCPM5-2B-cpu(即 openbmb/MiniCPM5-2B 的副本) 结论先行: 可以,而且改动比预想的小得多 —— 本目录给出可直接运行的完整实现 (ponder_llama.py,约 400 行,含训练/推理/生成全链路 + 9 组冒烟测试全部通过)。


一、原仓库代码分析

浅克隆(GIT_LFS_SKIP_SMUDGE=1 git clone --depth 1)后仓库仅 12MB,不含任何权重, 也没有自定义 modeling 代码。文件清单:

文件 内容
config.json architectures: ["LlamaForCausalLM"], model_type: "llama"
generation_config.json 温度 1.0 / top_p 0.95 / eos [1, 130073]
model.safetensors.index.json 权重索引(381 个张量,单分片 ~5GB,未下载)
tokenizer.json MiniCPM 词表(130,560)
README*.md 官方说明(transformers ≥5.6.2 原生加载,无需 trust_remote_code)

关键架构参数:42 层 decoder、hidden 2048、16 头 / 2 KV 头(GQA)、head_dim 128、 RoPE θ=5e6、128K 上下文、SiLU MLP(intermediate 6144)

分析结论:推理逻辑 100% 位于 transformers 原生 modeling_llama.py,其 LlamaModel.forward 是严格的"嵌入 → 42 层单遍串联 → RMSNorm → lm_head"结构, 没有任何递归/循环机制。要做 PonderNet,只需在原生代码之上包一层 —— 完全不必改 transformers 源码,也不必 fork 模型权重。

二、PonderNet 要加什么

PonderNet(Banino et al., 2021)的核心:网络对每个样本反复执行同一计算块,每步输出 停机概率 λₙ,最终输出是各步输出的概率加权混合

h_k  = Block(h_{k-1})            # 思考块第 k 步
λ_k  = Halting(h_k)  ∈ (0,1)     # 停机概率
w_k  = remaining · λ_k           # 本步"停下"的概率质量
mix  = Σ w_k · h_k               # 混合输出
remaining *= (1 - λ_k)           # 直到 Σw ≥ 1-ε 或步数上限 K

三、改造方案(本实现)

3.1 分层:只循环"思考块"

42 层切成两段:前 34 层照常单遍后 8 层(34~41)作为思考块循环执行ponder_start_layer=34 可调)。这样:

  • 简单 token 停在第 1 步 → 成本 ≈ 原模型;
  • 难 token 多想几步 → 每多想一步多 8 层的算力,粒度细、代价可控。

3.2 四种停机信号(λ 怎么来)

信号 λ 定义 是否需训练 适用
entropy(默认) 1 − 归一化输出熵,越自信越停 推理即插即用
msp max softmax 概率 同上
drift 相邻两次迭代隐状态的余弦相似度超过阈值 → 已收敛 → 停 "想不动了就停"
learned nn.Linear(2048, 1) + sigmoid,真 PonderNet 头 配套 ponder loss

诚实的说明:不训练时 learned 头是随机的、没有意义;推理可用的前三种是 启发式 halting(借模型自身的置信度/收敛度做停机判据),效果等价于 "自适应计算时间(ACT)+ PonderNet 式混合"。要得到论文语义的 λ,用下面提供的 loss 只训练这个 2049 参数的头(可冻结主干)即可。

3.3 混合与 KV cache(工程上最难的部分)

每次思考迭代都会经 DynamicCache.update() 写 KV,直接循环会让缓存里出现重复 token。 本实现的解法 —— **"替换式写入"**:

  1. 每次迭代(k≥2)先对思考块各层执行 cache.layers[l].crop(-W) 撤销上一步写入, 再跑思考块补回 —— 任意时刻缓存长度恒等于真实 token 数(已用测试逐层断言);
  2. 停机后执行 commit pass:用混合表示 h_mix 重过一遍思考块,把"思考后"的 KV 写进缓存 —— 后续 token 注意到的正是思考后的表示
  3. 第 1 步就停机的位置自动跳过 commit(混合即单步输出,零浪费)。

另一个坑:create_causal_mask 从非思考层读取缓存长度,对思考块会得到多 W 的 错误 KV 尺寸,因此窗口 mask 必须手工构建(_win_mask,token 索引因果 + padding 感知的加性浮点 mask,sdpa/eager 通用)。

3.4 训练真正的 PonderNet(可选)

labels 存在时自动计算 **ponder loss = CE + β·KL(w ‖ Geometric(p_g))**, 梯度可穿过混合权重到达 λ(与论文一致)。只训练 ponder_head(2049 参数)即可让 停机分布学出"难 token 多想、简单 token 少想"。

3.5 接口与兼容性(已验证)

  • 权重兼容:meta-device 对比 checkpoint 索引 —— 381 个张量键名/结构完全一致, 仅新增 ponder_head.{weight,bias} 2 个张量(2049 参数),from_pretrained 直接加载;
  • generate() 直接可用:继承 LlamaForCausalLM + 自定义 forward,HF 生成流程 (含左 padding、batch、logits_to_keep)无需任何适配;
  • transformers 5.16.1 实测通过(与 config 要求的 5.6.2 同代)。

四、文件清单

文件 说明
ponder_llama.py 核心实现:PonderLlamaConfig + PonderLlamaForCausalLM(约 400 行)
test_ponder.py 随机权重冒烟测试,9 组用例(cache 一致性/生成/训练反传/4 信号/eager/左 padding)
test_train.py 训练管线验证,5 组用例(数据/三种模式/保存重载/KL 字段)
test_gpu_readiness.py T4/fp16 就绪性预验证,5 组用例(fp16 dtype 链/KL 不 NaN/ckpt 一致性)
train_ponder_head.py halting 头微调脚本(head / head+lora / head+block 三种模式,含难度分级数据生成器)
eval_ponder.py 效果评估:难度分桶 CE/步数统计、训前训后对比(--compare)、思考收益(--k1-baseline
ponder_steps_demo.py tiny 模型演示:训练中平均思考步数向几何先验漂移的轨迹
demo_minicpm5.py 真实权重 demo:chat 对比、每 token 思考步数统计
run_t4.sh T4 一键脚本:自检→下载权重→数据→训练→评估全自动串联
data/pondernet_train.jsonl 难度分级训练集 600 条(easy 220 / medium 200 / hard 180,答案反向构造保证正确)
data/pondernet_train.eval.jsonl 难度分级评估集 85 条(与训练集无重叠)

五、快速上手

from ponder_llama import PonderLlamaForCausalLM, PonderLlamaConfig, LlamaConfig

cfg = PonderLlamaConfig.from_llama(
    LlamaConfig.from_pretrained("/path/to/MiniCPM5-2B-cpu"),
    ponder_signal="entropy",     # 或 msp / drift / learned
    max_ponder_steps=8,
    ponder_start_layer=None,     # None = 最后 8 层作思考块
)
model = PonderLlamaForCausalLM.from_pretrained(
    "/path/to/MiniCPM5-2B-cpu", config=cfg,
    torch_dtype="bfloat16", low_cpu_mem_usage=True).eval().cuda()

out = model(input_ids, output_ponder=True)
print(out.ponder_steps)     # 每个位置实际思考了几步
print(model._ponder_log)    # 每次 forward 的诊断日志

训练 halting 头(可选,其余全部冻结):

for n, p in model.named_parameters():
    p.requires_grad = n.startswith("ponder_head")
cfg.ponder_signal = "learned"        # 切到可训练信号
# 正常 forward(input_ids, labels=...) → loss 里已含 KL(‖Geometric) 正则

主要超参:

参数 默认 含义
ponder_start_layer L−8 思考块起始层
max_ponder_steps 8 思考步数上限 K
ponder_epsilon 0.01 累计质量 1−ε 即停
ponder_signal entropy 停机信号
ponder_window 1 prefill 思考窗口(=1 即"回答前思考")
ponder_all_positions False True = 所有位置独立 halting
commit_kv True 用混合表示写 KV(False 省一遍计算,语义略糙)
ponder_prior_p / ponder_loss_beta 0.5 / 0.01 几何先验 p_g 与 KL 权重 β

六、训练 halting 头(真 PonderNet)

6.1 三种模式与代价

模式 可训练参数 显存 语义
head 2049 极低(8GB 单卡可跑) 仅学"何时停",思考块本身不变
head+lora(推荐) 头 + LoRA(r=16,仅思考块) 思考块学会"被重复执行",完整 PonderNet
head+block 头 + 思考块全参 效果上限最高
# 生成演示数据(16 条: 简单问答 + 推理题, 让头有"难题多想"的信号可学)
python train_ponder_head.py --model /path/to/MiniCPM5-2B-cpu --make-demo-data demo.jsonl

# 推荐: 头 + 思考块 LoRA
python train_ponder_head.py --model /path/to/MiniCPM5-2B-cpu \
    --train-mode head+lora --lora-scope ponder --lora-r 16 \
    --data demo.jsonl --epochs 2 --batch-size 2 --accum 4 \
    --prior-p 0.4 --beta 0.05 --head-bias-init -1.0 \
    --output out/ponder-lora --log-every 5

数据格式(jsonl,只对 assistant 回答区间计 loss):

{"messages": [{"role": "user", "content": "9.11 和 9.9 哪个大?"},
              {"role": "assistant", "content": "9.9 更大。因为 0.90 > 0.11 …"}]}

6.2 关键超参与预期现象

  • --prior-p(几何先验 p_g):控制"想几步"的先验预期,期望步数 ≈ 1/p_g。 想让模型平均思考 3 步左右 → p_g=0.3;2.5 步 → p_g=0.4。
  • **--head-bias-init**:初始停机偏置,sigmoid(-1.0)≈0.27(初始期望约 3.7 步)。
  • **--beta**:KL 权重。太小 → 头被 CE 拉着全跑满 K 步;太大 → 全挤在第 1 步。

训练日志中三列数字的联动(tiny 模型实测轨迹):

  step |      CE |      KL | 平均思考步数
     0 |  5.4395 |  0.0390 |       1.99
    60 |  4.2772 |  0.0083 |       2.31   ← p_g=0.4, 期望 2.5

CE 下降(任务能力)+ KL 下降(停机分布靠向几何先验)+ 步数向 1/p_g 漂移但保留 区分度 —— 三者同时发生才是健康的 PonderNet 训练;如果步数立刻塌到 1 或钉死在 K, 调 p_g / beta / head_bias_init。

6.3 训练产物与推理加载

# head / head+lora 模式产物: ponder_head.safetensors (+ adapter_model.safetensors)
from safetensors.torch import load_file
model = PonderLlamaForCausalLM.from_ponder(
    "/path/to/MiniCPM5-2B-cpu",
    ponder_kwargs={"ponder_signal": "learned", "max_ponder_steps": 8})
head = load_file("out/ponder-lora/ponder_head.safetensors")
model.ponder_head.load_state_dict({k.replace("ponder_head.", ""): v
                                   for k, v in head.items()})
# LoRA 适配器: model = PeftModel.from_pretrained(model, "out/ponder-lora")

七、T4 实战 runbook(16GB 显存)

7.1 为什么 T4 需要专门适配

T4 是 pre-Ampere 卡(sm_75):不支持 bf16,只能 fp16 + GradScaler。本仓库已针对 真实半精度 GPU 预修复并预验证(test_gpu_readiness.py,CPU fp16 复现):

  1. fp16/bf16 模型 + fp32 ponder_head 的 dtype 转换链(修复前直接报 dtype 不匹配)。
  2. fp16 下 KL 正则的 NaN 风险:停机质量 w 中的小值在 fp16 域取 log 会下溢出 NaN, KL 现已固定在 fp32 域计算。
  3. 全部停机信号的 λ 统一 cast 回隐状态 dtype,混合链路 dtype 一致。
  4. 思考块 gradient checkpointing:思考循环最多 K 次前向,激活是普通前向的 K 倍, 重算是 16GB 显存训练的关键(开启后开/关 loss 与梯度完全一致)。

显存预算(head+lora, fp16, batch 2×384):权重 5.0GB + LoRA/头 ≈ 0.1GB + 激活(重算后)≈ 1GB + logits(词表 130560)≈ 1.5GB ≈ 8GB 左右,T4 富余

7.2 一键运行

bash run_t4.sh          # 自检 → 下载权重(约5GB, 磁盘不足自动清理缓存) →
                        # 数据 → 训练(fp16) → 训前/训后评估 + 思考收益

7.3 训后看什么(评估表怎么读)

python eval_ponder.py --model $MODEL_DIR \
    --head out/ponder-t4/ponder_head.safetensors --adapter out/ponder-t4 \
    --data data/pondernet_train.eval.jsonl --compare --k1-baseline
  • 难度分化的 avg_steps:健康的训练应看到 easy < medium < hard 的思考步数梯度 (数据集本身就是按难度分桶构造的);训前(bias=-1, λ≈0.27)各桶步数几乎相同。
  • ΔCE(思考收益)--k1-baseline 输出 CE(K=1) − CE(K=8),正值说明"多想"确实 降低了损失;重点看 hard 桶是否比 easy 桶收益更大(思考用在刀刃上)。
  • 训练日志:CE 下降 + KL 下降 + 步数向 1/p_g≈2.5 漂移但保留区分度。

八、局限与提醒

  1. 免训练信号是启发式entropy/msp/drift 停机判据并非模型学出来的元认知, 实测大概率每个位置都会跑满 K 步。要得到"该多则多、该少则少"的停机行为, 用第六节的脚本微调 halting 头(数据量要求很小,head 模式单卡即可), 或在思考块上加 LoRA 联合训练 —— 那才是完整意义的 PonderNet。
  2. 算力换深度:每多想一步 = 多 8 层前向(约 19% 的整模型算力)。decode 阶段 逐 token 思考会显著降速,可把 max_ponder_steps 调小或只保留 prefill 思考。
  3. 语义偏移风险:思考块是"重复过同样的层",本质是给特定 token 动态加深网络。 原模型没在这种模式下训练过,输出分布可能有轻微偏移(类似 Universal Transformer 的权重共享循环)。混合权重 w 与 commit-KV 机制保证了数学上的自洽,但不保证 生成质量提升 —— 建议以 max_ponder_steps=1(≈原模型)为 baseline 对比评估。
  4. 本实现针对 transformers 5.x(5.16.1 实测);4.x 的 Cache API 不同,需要小幅适配。

九、T4 实测结果(2026-09-08,Colab Tesla T4 16GB)

环境:Python 3.13 / torch 2.11.0+cu128 / transformers 5.16.1 / peft 0.20.0。 600 条难度分级训练数据(easy 220 / medium 200 / hard 180),85 条评估 + 50 条 OOD 压力题。

9.1 训练(head+lora, LoRA r=16 仅注入最后 8 层思考块, 2 epochs, fp16+GradScaler)

  • 总耗时 617 s(含加载与保存),无 NaN、无 OOM(batch 2×512 + grad-ckpt)
  • CE:2.50 → 0.03~0.05(epoch 2 稳定在 0.01–0.05 量级)
  • 平均思考步数(训练期):2.29 → ~1.30,说明 halting 头确实在 CE+KL 联合作用下 从"多想"向"够用即停"移动
  • 产物:out/ponder_lora/(adapter_model.safetensors 19MB + ponder_head.safetensors 8.4KB)

9.2 评估集结果(85 条)

配置 全局 CE easy 步数 medium 步数 hard 步数
训前(bias=-1,λ≈0.27) 8.821 7.23 7.20 7.34
训后 K=8 自适应 0.018 1.46 1.30 1.20
训后 K=1(强制单步) 0.030 1.00 1.00 1.00

三个核心观察:

  1. 自适应计算带来真实质量增益:K=8 自适应(平均仅 1.3 步)CE 0.018,比强制 单步 baseline 的 0.030 低 38%。即模型用约 1/8 的"跑满算力"成本取得了比单步 更低的损失 —— 这正是 PonderNet 论文声称的"质量-算力帕累托改进"在真实 2B 模型上的复现。
  2. 训前 vs 训后对照:训前 head 是"无脑跑满 K=8"(7.2–7.3 步,无难度区分); 训后绝大多数位置 1 步即停(77%–91%),且保留了少量多步位置 —— 停机行为完全被训练重塑。
  3. 步数分化的方向:easy > medium > hard(1.46 > 1.30 > 1.20)。这与"难题多想" 的直觉相反,但与 PonderNet 的实际优化目标一致 —— 步数跟随的是 per-token 预测 不确定性:hard 题答案高度模板化("设鸡 x 只…"),每个 token 都好预测,1 步就够; easy 里的翻译题英文词更难预测,反而多想一点。

9.3 OOD 压力测试(50 条训外难题)

题型 平均步数 停在 1 步占比
数字串复述(10–14 位,纯记忆压力) 1.50 66%
平方(12–99) 1.34 81%
三步混合运算 a×b+c×d 1.28 87%
逻辑链比较 1.26 89%
大数乘法(3–4 位) 1.18 90%
三步应用题(折扣×数量) 1.17 89%
  • OOD 上 K=8 CE 0.705 vs K=1 CE 0.724:思考仍有增益(+2.7%),且步数最高的恰是 记忆压力最大的数字串复述 —— halting 头学到的信号与"表示不确定性"正相关, 方向正确。
  • 但步数整体仍低(≤1.5):600 条简单数据上 CE 的最优解就是"快停",模型没有遇到 "1 步真解不出"的压力。

9.4 结论与下一步

已验证:PonderNet 循环思考在真实 2B 模型 + T4 上端到端可行 —— 训练稳定、 自适应停机、质量优于单步 baseline、算力成本可控(~10 分钟可训完)。

未出现:按题目语义难度分桶的步数分化。要让"难题多想"真正显现,需要:

  1. 1 步真实解不出的数据:例如把 CoT 中间推理步纳入监督(而非只给模板化最终答案)、 或训练集包含模型当前能力边界之外的任务;
  2. 更大的 prior 压力--prior-p 调小(如 0.2)或加大 β,强制步数分布远离 1;
  3. 两阶段训练:先 LoRA 拟合任务,再冻结 LoRA 单独训 head,避免"快停"在 训练早期就成为 CE 最优解。

9.5 T4 复现命令

# T4 端(Colab)—— 数据 md5: mix.jsonl=41c7ede4, mix.eval.jsonl=5a03ce37(与本地一致)
pip uninstall -y torchao            # peft 0.20 与 Colab 预装 torchao 0.10 冲突
python train_ponder_head.py --model models/MiniCPM5-2B \
    --train-mode head+lora --lora-scope ponder --data data/mix.jsonl \
    --dtype float16 --epochs 2 --batch-size 2 --accum 4 --grad-ckpt \
    --output out/ponder_lora
python eval_ponder.py --model models/MiniCPM5-2B --data data/mix.eval.jsonl \
    --head out/ponder_lora/ponder_head.safetensors --adapter out/ponder_lora \
    --max-steps 8 --generate

注意:新版 torch 的 is_bf16_supported() 对 T4 (sm75) 返回 True 但那是模拟 bf16 (无硬件 tensor core),训练脚本已改为按 compute capability 判断,T4 自动落 fp16 + GradScaler。

十、"难题多想"语义分化专项:数据集选型与方案(2026-09-08)

9.4 指出核心矛盾:模板化短答案使 hard 桶 per-token 不确定性反而低。破局需要 真实多步 CoT 数据(让"1 步解不出"成为 CE 层面的现实)+ 难度→先验的显式通路

10.1 HF 数据集选型(实查字段与规模)

数据集 规模 语言 关键字段 适配点
meta-math/MetaMathQA_GSM8K_zh 240k 中文 query_zh / response_zh / type 中文逐步 CoT 解答,替换模板答案的首选
swulling/gsm8k_chinese 7.5k 中文题+英文解 answer 内含 <<48/2=24>> 步标注 步标注可直接数出推理步数 = 现成难度标签
AI-MO/NuminaMath-CoT 860k 英文为主 source(gsm8k/amc_aime/olympiads…) source 即天然难度梯度,适合跨级对比
rabinadk1/competition_math-level_5 MATH 分级子集 英文 level 1–5 官方难度分级,可直接当 difficulty 字段

10.2 专门方案(按改动成本排序,1+2 组合拳已落地)

  1. CoT 数据替换(数据侧,首选)make_gsm8k_zh_data.py 从 MetaMathQA_GSM8K_zh 抽样,按"解答中等式行数"自动分桶(≤2 步 easy / 3 步 medium / ≥4 步 hard), 生成带 difficulty 字段的 jsonl。多步 CoT 的中间 token 无法靠 1 步记住, halting 头将在真实的预测压力下学到"多想"。
  2. 难度条件化先验(损失侧,已实现)--difficulty-prior easy:0.7,medium:0.4,hard:0.15。 实现:PonderLoader 把同难度样本组成同 batch,训练循环在每个 batch 前把 config.ponder_prior_p 切换为该难度的 p_g —— KL 正则随之把 easy 拉向快停、 hard 拉向多想。这是 PonderNet 几何先验的 per-sample 扩展,ponder_llama.py 零改动。
  3. 两阶段训练(调度侧):先 LoRA 拟合 CoT 任务(CE 降下来),再冻结 LoRA 单独训 halting 头 —— 避免"快停"在训练早期成为 CE+KL 的联合最优解。
  4. 失败自举(AdaptThink 式,进阶):先用 K=1 跑一遍训练集,答错的样本构成 "必须多想"集合,对其施加更小的 p_g(或直接监督步数目标)—— 用模型自己的 失败案例定义难度,比启发式分桶更精准。

10.3 一键复现(方案 1+2)

pip install datasets
python make_gsm8k_zh_data.py --out-dir data_zh --n-train-per-bucket 800 --n-eval-per-bucket 50
python train_ponder_head.py --model <MODEL_DIR> --train-mode head+lora \
    --data data_zh/zh_cot.jsonl --difficulty-prior easy:0.7,medium:0.4,hard:0.15 \
    --epochs 2 --batch-size 2 --accum 4 --grad-ckpt --dtype float16 --output out/ponder-zh
python eval_ponder.py --model <MODEL_DIR> --data data_zh/zh_cot.eval.jsonl \
    --head out/ponder-zh/ponder_head.safetensors --adapter out/ponder-zh \
    --max-steps 8 --tag zh-cot

预期:easy 桶步数 →~1,hard 桶步数显著上移(p_g=0.15 的几何先验期望 ≈6.7 步), 出现 easy < medium < hard 的语义分化;评估脚本按 difficulty 分桶可直接检验。


十一、第二轮实测:中文 CoT + 难度条件化先验(T4)

11.1 实验设置

配置
数据 meta-math/MetaMathQA_GSM8K_zh 中文逐步 CoT,按等式行数分桶 (easy≤2 / medium=3 / hard≥4 步)
训练集 1200 条 (400/桶),实际推理步数梯度 easy 1.84 / medium 3.00 / hard 5.39
评估集 zh_cot.eval.jsonl 180 条 (60/桶),评估取前 90 条
训练 head+lora (r=16 思考块)、--difficulty-prior easy:0.7,medium:0.4,hard:0.15、max-steps 6、1 epoch (75 优化步)、fp16+GradScaler、T4
先验生效验证 训练日志 KL 恒定在 ~1.0-1.1,三档几何先验随 batch 难度正确切换

11.2 训练曲线(75 优化步)

step  10 | loss 2.3062 (ce 2.2549) | ponder_steps 2.36
step  20 | loss 0.5697 (ce 0.5112) | ponder_steps 1.07
step  70 | loss 0.4326 (ce 0.3843) | ponder_steps 1.07

CE 快速收敛(CoT 任务 LoRA 拟合良好),但 ponder_steps 从 2.36 一路降到 ~1.07 并锁死 —— "尽快停"再次成为 CE+KL 的联合最优解。

11.3 评估结果(90 条,K=6 vs K=1)

配置 全局 CE easy 步数 medium 步数 hard 步数
K=6 自适应 (训后) 0.3713 1.095 1.062 1.044
K=1 强制单步 0.4223 1.000 1.000 1.000

思考收益:CE 0.4223 → 0.3713(降 12%),代价仅平均 0.07 步/token(约 7% 位置用到 2+ 步)。 自适应停机机制本身工作正常且正向获益,但三桶步数不仅未按 hard>medium>easy 分化, 反而呈微弱反向(hard 最低)—— 与第一轮(英文短答案)现象一致。

11.4 根因分析:为什么"难题多想"在 CE 视角下不成立

两轮实验(模板化短答案 + 真实多步 CoT)得到同一结果,可以下结论这不是数据形式问题:

  1. **CE 的优化对象是 per-token 预测,不是"解题"**。数学题 CoT 的语言高度模板化 ("因此...="、"设...为 x"),hard 题的下一个 token 反而比 easy 题的自由文本更可预测。 halting 头从 CE 梯度学到的"哪里不确定"与题目语义难度负相关。
  2. 1 步就够好的任务不值得多想。2B 模型 + LoRA 对 GSM8K 级 CoT 的 next-token 拟合误差已很小(CE 0.37),K=2..6 的循环对绝大多数位置没有信息增益, w→第 1 步是全局最优。"难题多想"要求存在一批 1 步真的解不出的位置。
  3. 难度条件化先验被 CE 压制。β=0.05 下 KL 拉力 (~0.05) 远小于 CE 梯度差, hard 桶先验 p=0.15 不足以把它推向多步。加大 β 会损害 CE(实验过 β 失衡的风险)。

11.5 修正后的方案优先级(第三轮方向)

结论:必须改变任务结构让"1 步解不出"成为事实,而不是换数据或调先验

  1. 失败自举(AdaptThink 式,升为首选):用当前 K=1 模型跑训练集, CE 高的位置/样本 = 模型自己的能力边界 → 构造"这些题强制多想、其余强制快停" 的步数监督,或直接对失败样本给 p_g=0.1。难度由模型实测定义,与语义对齐。
  2. 两阶段训练:先 LoRA 拟合任务(本轮已完成,CE 0.37),冻结 LoRA 后 单独训 head —— head 只能在固定表征上找"1 步不够"的位置,排除了 "LoRA 学会快停"的混淆路径。
  3. 答案遮蔽/结构化监督:只对 CoT 的"结论区"计 loss(中间推理不计), 迫使停机决策与"推理是否完成"对齐,而非与 per-token 熵对齐。
  4. 保留资产:本轮权重(head+LoRA)已可复现 12% 的 CE 收益 @ ~1.07 步, 作为第三轮的初始化 checkpoint。

11.6 产物与复现

  • HF: pondernet/weights_zh_round2/(ponder_head.safetensors + LoRA adapter 19.2MB)
  • HF: pondernet/data_zh_round2/(zh_train_1200.jsonl + zh_cot.eval.jsonl)
  • T4 端复现命令与 10.3 相同(数据换 data_zh/zh_train_1200.jsonl); 评估:eval_ponder.py --max-steps 6 --head out_zh/ponder_head.safetensors --adapter out_zh --limit 90

十二、第三轮实测:失败自举(AdaptThink 式)

12.1 方法

用第二轮训后的模型(K=1 单步)对 1200 条训练样本逐样本实测 CE(probe), 以模型自己的能力边界重新定义难度,替代启发式分桶:

  • probe_ce ≥ p75 (0.555) → 实测 hard(强制多想,p_g=0.05)
  • probe_ce ≤ p25 (0.323) → 实测 easy(强制快停,p_g=0.90)
  • 其余 → 实测 medium(p_g=0.50)

第三轮训练:加载第二轮 LoRA 继续训(--init-adapter out_zh,head 重置), 720 条均衡抽样,1 epoch,先验命中 720/720。新增 --init-adapter 两种用法: --train-mode head(冻结 LoRA 只训 head)/ --train-mode head+lora(LoRA 续训), test_boot.py 三阶段冒烟覆盖。

12.2 probe 铁证:启发式难度与模型难度负相关

启发式桶 n probe 平均 CE (K=1)
easy (1-2 步) 400 0.536
medium (3 步) 400 0.437
hard (≥4 步) 400 0.389

推理步数越多的题,模型单步预测反而越容易 —— 数学 CoT 的模板化语言 ("设...为 x"、"因此...=")使长解答的 per-token 熵更低。 这定量解释了两轮实验中 halting 头"反向分化"的原因:它优化的从来就是 预测难度,而不是题目语义难度。交叉表:实测 hard 中 56% 来自启发式 easy。

12.3 第三轮结果(180 条 eval,同阈值重标分桶)

配置 全局 CE easy 步数 medium 步数 hard 步数
K=6 自适应 (boot 模型) 0.3759 1.035 1.039 1.047
K=1 强制单步 0.3762 1.000 1.000 1.000
  • 步数方向首次转正:hard > medium > easy(单调)✅
  • 幅度微弱:hard−easy 仅 +0.012,97% 位置仍停在第 1 步 ❌
  • 思考收益归零:0.3762 → 0.3759(0.1%),第二轮的 12% 收益消失

12.4 根因:移动目标 + KL 拉力不足

  1. probe 标签过时(移动目标):probe 用的是第二轮结束时的模型;第三轮 LoRA 继续训练迅速把 probe_ce 高的位置拟合掉(CE 0.43→0.38),"实测难" 在训练开始后立刻不再难。head 面对的 hard 组已经没有"多想的 CE 增益"。
  2. β=0.05 的 KL 拉力数量级不足:hard 组 KL(~1.3)×0.05 ≈ 0.065 的 loss 贡献,无法对抗 CE 对快停的偏好。步数只留下 2-3% 的"姿态性"多步。
  3. 思考收益归零 = 模型对 boot_hard 的 1 步拟合已足够好,K>1 无信息增益。

12.5 第四轮方向(按优先级)

  1. 步数硬监督替代 KL:对实测 hard 组直接监督 w 分布(目标 = 均匀铺满 K 步),easy 组监督 w=[1,0,...]。BCE/MSE 直接作用在 w 上,比 β·KL 强 一个数量级,不依赖"多想有 CE 增益"。
  2. 冻结表征的两阶段:probe 后冻结 LoRA 只训 head(--init-adapter + --train-mode head)。probe 标签在固定表征下不过时,head 学到的是真实 的"1 步不够"位置。本轮为省时选择了续训,两阶段版本待验证。
  3. 答案级 RL(AdaptThink 原味):奖励 = 答对且步数少。答案对错不随 LoRA 拟合 token 而漂移,规避移动目标。工程量大(采样 + 奖励 + RL), 建议在 1/2 验证失败后再启动。
  4. 保留资产:probe_results.jsonl(1200 条实测难度标签)与 boot 权重 已存 HF,第四轮直接复用。

12.6 产物与复现

  • HF: pondernet/weights_boot_round3/(head + LoRA)、pondernet/data_boot_round3/ (probe_results.jsonl + boot_train.jsonl + boot_eval.jsonl)
  • 复现:
    python train_ponder_head.py --model <MODEL> --train-mode head+lora \
        --init-adapter out_zh --data data_zh/boot_train.jsonl \
        --difficulty-prior easy:0.9,medium:0.5,hard:0.05 \
        --epochs 1 --batch-size 2 --accum 8 --max-steps 6 --grad-ckpt --dtype float16 \
        --output out_boot
    python eval_ponder.py --model <MODEL> --data data_zh/boot_eval.jsonl \
        --head out_boot/ponder_head.safetensors --adapter out_boot --max-steps 6
    
Downloads last month

-

Downloads are not tracked for this model. How to track
Inference Providers NEW
This model isn't deployed by any Inference Provider. 🙋 Ask for provider support