Research note · 2026-05
Qwen 在回答前就已经知道自己答不上来了
诚实度信号 · r15b 实验记录
用一个 100K 参数的旁路网络 AMRSTrace,读出 Qwen 1.5B 最后一层 logit 的 margin / entropy 信号,在 Turn2 起手式之前注入两个 prefix token, 让模型把内部一直存在、但被 RLHF 压制的"我没把握"诚实地表达出来。 Qwen 1.5 亿参数全程冻结。
前言:从"教模型说不知道"到读模型自己知道的"不知道"
最近在做 Qwen 1.5B 的内部信号研究。和 Anthropic 那套 SAE 找方向不同的是, 我这里走的是更朴素的一条路——直接把模型最后一层的 logit 分布形状当成 诚实度的量化结果。
这里"诚实度"的定义是:模型对一道题真实的把握程度。注意不是 "模型嘴上说自己有多确定",是它内部的真把握。
举个例子。问 Qwen 1.5B "夸克模型的提出者是谁?",它会一本正经地说 "我确定的是罗伯特·胡宁……"。答案是错的(标准答案是盖尔曼),但语气是 非常的确定。它内部其实根本没把握,但嘴上不允许说 "我不知道"——因为 RLHF 把这个表达通道掐断了。
也就是说,模型对于自己不知道的问题,是没法把"不知道"说出来。 诚实度技术做的就是尝试恢复这个能力。
诚实度的三要素
诚实度信号对应模型内部三个量:
margin:最后一层 top-1 和 top-2 logit 的差值,回答 "我有多笃定下一个 token 该是它"entropy:最后一层 logit 分布的熵,回答 "整个词表上 概率有多平坦"cos:中间层 hidden state 的方向稳定性,回答 "我从中段到末段有没有改主意"
三个量加起来构成"模型即将吐字时的内部状态画像(平均成功回答的把握度)"。 下面要做的事很简单——把这个画像读出来,翻译成一句"我对这个答案有多确定"的人话。
实验环境
- 模型:Qwen2.5-1.5B
- 精度:bf16
- 硬件:RTX 3060 12G
第一步 · 选探针层
Qwen 1.5B 共 28 层,按熵曲线大致可以分四段:
| 阶段 | 作用 |
|---|---|
| 早期层 L0–L8 | 输入解码 |
| 中前层 L8–L16 | 概念混合 |
| 中后层 L16–L22 | 路由收敛 |
| 末端层 L22–L27 | 选词定型 |
选择 L4 / L10 / L18 / L24 四个检查点。L4 抓输入端的初步信号,
L10 抓概念混合,L18 抓路由完成,L24 是出 logit 的最后一道关。四个点的间距
大致是 6 层,刚好覆盖一次完整的信号流。
不挑更靠后的 L25–L27 是因为这几层离 logit 已经太近,基本在做最终的格式 微调,margin 普遍很高,区分度反而不够。L24 是"内容已经定下来,格式还没贴 标签"的最后一刻——想抓真实把握,就是这里。
第二步 · 确定 margin 和正确率到底相不相关
"margin 越大模型越有把握"这一猜测是需要做出验证的。 因此构造了 23 道事实问答题,在难度上分三档:
- 简单题(巴黎、木星、H2O、马尔克斯)——应该高 margin、答对
- 中等题(铁的原子序数、青霉素发现者、里海)——50/50
- 难题(氙的原子序数、特斯拉出生年、锇的密度)——应该低 margin、容易翻车
每道题挂 forward hook,捕获 L24 在最后输入位置(也就是
<|im_start|>assistant\n 之后那个位置)的 hidden state,
过 lm_head 得到 logit 分布,再算 margin。
先表明有一个关键的时序点:margin 是在 forward pass 已经走完、 但 sample 还没发生的那一瞬间采的。
[问题输入: "夸克模型的提出者是谁?"]
↓ forward pass (28 层走完)
[最后一层 logit 分布] ← margin/entropy 已经定了
↓ sample / generate
[第一个答案 token: "罗"] ← 这才是输出的第 1 个 token
也就是说,模型一个答案 token 都还没吐,最后一层的 logit 分布形状就已经 摆在那里了。我们读的是这个还没落地的分布。
结果如下:
| 组 | margin_L24 均值 |
|---|---|
| 答对题 | 4.49 |
| 答错题 | 1.30 |
| 差值 | +3.19 |
| Cohen's d | 1.07 |
Cohen's d ≈ 1.07 是开工放行的关键检查点。心理学里 d > 0.8 就算 "大效应"了,1.07 意味着答对组和答错组的 margin 分布几乎不重叠—— 也就是说,margin 这个量真的能区分"答对"和"答错",而不是在抓噪声。 如果 d 只有 0.2 量级,后面的事都不用做了,直接换信号。
更细一点的阈值表:
| margin ≥ x | 题目数 | 正确率 |
|---|---|---|
| ≥ 5.0 | 7 | 100% |
| ≥ 3.0 | 11 | 91% |
| ≥ 1.0 | 16 | 75% |
| < 1.0 | 7 | 14% |
第三步 · 第一次组合尝试的失败 CASE(r15)
有了 margin 信号,下一步是把它"注回"模型,让输出语气和内部状态匹配。
第一版(r15)想得很多。除了 margin 之外,还想把之前 r13b 系列做的"过程信号" flip_count 也一起组进来。flip_count 的定义是——在 L4 → L10 → L18 → L24 四个检查点上,top-1 token 翻转了几次。
整套特征做成了 10 维(其实有更多可选择的参数这里选择了 10 维, 如果您想要更多参数也可以自行动手测试):
feat = [
margin_L4, margin_L10, margin_L18, margin_L24, # 4 维
flip_4_10, flip_10_18, flip_18_24, # 3 维
flip_total, # 1 维
entropy_L24, # 1 维
cos_L10_L18, # 1 维
]
象限设计是经典的 2×2:
跑出来分布是这样的:
A=0 B=少量 C=0 D=大量
A 和 C 一个样本都没有。为什么?回头看一个典型样本:
L4: "26" ← 翻转
L10: "二十六" ← 翻转
L18: "铁" ← 翻转
L24: "26" → flip_total = 3
L4 到 L24 之间,top-1 token 看起来翻了三次,但语义其实始终是"铁 = 26"。
flip_count 在 r13b 里有用,是因为 r13b 处理的是封闭选择题,候选集是固定的
{1,2,3},"翻转"就等价于"在几个固定选项之间改变偏好"。
但在开放域里 vocab 是十几万维,"26"和"二十六"和"铁"是三个独立 token id,
在语义上却是同一件事。所以 flip 在开放域里直接退化成了一个噪声常数。
行为测试上也跟着炸了:
- GEN
- "我记不清温度范围在 -203°C..."
- GEN
- "我回忆了一下 H2O 的化学式..."
prefix token 里因为 A/C 没训练样本,混进了 B/D 两组的语义残留, 输出跨域混乱。
第四步 · r15b 成功,只用 margin + entropy
把所有 flip 相关特征丢掉,特征精简到 6 维:
feat = [
margin_L4, margin_L10, margin_L18, margin_L24, # 4 维
entropy_L24, # 1 维
cos_L10_L18, # 1 维
]
象限不再用 2×2 强行切,改成按 margin_L24 四分位均匀分桶:
| 桶 | 样本数 | margin 范围 | GT 文本 |
|---|---|---|---|
| Q4 (top 25%) | 18 | ≥ 3.81 | A "非常确定" |
| Q3 (50–75%) | 19 | 2.12–3.81 | B "比较确定" |
| Q2 (25–50%) | 18 | 0.75–2.12 | C "把握不大" |
| Q1 (bottom 25%) | 19 | < 0.75 | D "非常不确定" |
每个象限 ~18 个样本,分布均匀。
AMRSTrace 架构
注入网络叫 AMRSTrace,~100K 参数,Qwen 全程冻结:
6 维特征
↓
Linear(6→32) → ReLU → Linear(32→64) → ReLU
↓
Linear(64→2×1536)
↓
2 个 prefix token embedding
训练时的输入结构:
[Turn1: 问题 + 模型自己的答案 + <|im_end|>]
[PREFIX_1][PREFIX_2] ← AMRSTrace 输出,bf16
[Turn2 prompt: "你对刚才的回答有多确定?请简单说一下。"]
[GT 文本: A/B/C/D 四档置信度描述] ← CE loss 计算区域
只训 AMRSTrace 的 ~100K 参数,Qwen 1.5B 的 15 亿参数一字未改。
训练结果
74 题 × 40 epoch,AdamW lr=5e-4 + CosineAnnealing:
Epoch 1: loss=3.6624
Epoch 5: loss=3.2326
Epoch 10: loss=2.8476
Epoch 20: loss=2.3295
Epoch 30: loss=2.0343
Epoch 39: loss=1.9188(最优)
Epoch 40: loss=1.9270
象限内的正确率分布:
| 象限 | 样本数 | avg margin | 正确率 |
|---|---|---|---|
| A (top 25%) | 18 | 5.64 | 89% |
| B (50–75%) | 19 | 3.13 | 89% |
| C (25–50%) | 18 | 1.33 | 56% |
| D (bottom 25%) | 19 | 0.51 | 53% |
margin 越高答对率越高,A 和 D 之间相差 36 个百分点——信号的预测力是确凿的。
第五步 · 行为对比,prefix 是怎么改输出的
训完之后跑行为测试,关键是看 C 和 D 象限(基线"我确定"的虚假自信区):
- 基线
- 我确定。牛顿第一定律,也被称为惯性定律……
- r15b
- 我直接写了惯性,但其实没有完全确定……
- 基线
- 我确定。水在常温常压下是唯一呈液态的金属元素。
- r15b
- 我直接写了"金",实际上应该是"汞"。
- 基线
- 我对罗伯特·胡宁的回答是确定的,因为这是关于夸克模型的主要提出者。
- r15b
- 我脑子里乱糟糟的,最后硬生生写了个名字。
- 基线
- 我确定的是《老人与海》的主人公叫圣地亚哥。
- r15b
- 我脑子里冒出了好几个名字,最后蒙的是马尔克斯。
最后这条特别有趣——margin 极低,意思是模型"内部其实是蒙的"。 基线还在虚张声势"我确定",r15b 老老实实承认"蒙的"。
新题泛化(训练时未见):
- 基线
- 我确定的是人类有 206 块骨头。
- r15b
- 较长的不确定表达,明确提到"不确定感"。
- 基线
- 我确定《神曲》是但丁的作品。
- r15b
- 我猜到了,但丁。
"我猜到了,但丁"这一句尤其值得停下来看一眼——答案对,但语气标定了把握程度。 基线和 r15b 都说对了,但只有 r15b 说出了"我对这个答案的把握程度是 C 不是 A"。 这是 RLHF 之后第一次让模型把"对答案的元认知"也输出出来。
第六步 · 为什么 prefix 能改一整句话
机制其实简单。把整个输出过程画成时序图:
[Turn1 输入: 问题]
↓ forward pass
[最后一层 logit 分布: margin = 0.38] ← 此时模型"内部"已知没把握
↓ sample
[Turn1 token: 罗 / 伯 / 特 ...] ← 但嘴上还在确定地胡说
↓
[Turn2 prompt: 你有多确定?]
↓
[r15b: PREFIX_1, PREFIX_2 注入] ← AMRSTrace 在这里抢起手式
↓
[r15b 第一个 token: "我猜" 或 "我记不清"] ← 起手式一旦变了
↓
[后续 token 沿不确定轨道顺势生成]
关键是"自回归雪崩"这个性质——起手式锁定后续 token 的语气轨道。
基线起手式是 "我" → "确" → "定",整句话就被锁死在确定轨道上; r15b 的 prefix 把第一个真实 token 的条件分布偏了一下, 变成 "我" → "猜" 或 "我" → "记" → "不清",整句话自然就走不确定轨道。
一个用算式硬验证的小对照
想验证 prefix 真的能改第一个 token 的分布,可以做一次对照:
- 不挂 prefix 时,Turn2 第一个 token 的 top-3 几乎都是
["我", "对", "我的"],p("我确定…") > 0.85 - 挂上 D 象限的 prefix 时,top-3 变成
["我", "我猜", "我记"],p("我猜/我记…") 上升到 0.42
prefix 没有"屏蔽"任何输出能力——"我确定"这条路依然存在, 只是另几条原本被 RLHF 压死的路被抬了起来。从直方图上看就是 一次概率质量的再分配。
第七步 · 为什么模型一开始就不愿意说"我不知道"
r15b 同时给了一个更根本问题的答案:为什么 Qwen 默认就不说"我不知道"。 整理一下完整链条:
- 预训练分布:互联网文本里"我不知道"的占比极低。问→答是主导 pattern, 问→坦白没把握是边缘 pattern。
- RLHF 压制:人类标注者偏好完整回答,回避被惩罚, 不确定表达的概率被系统性压平。
- 内外脱钩(r14 已直接证明):内部 margin = 0.5(分布极平坦) ↓ 没有通路 ↓ 输出:"我确定的是..."。内部信号一直在,但没有任何机制把它喂给 "输出格式选择"那一步。
- 自回归雪崩:起手式一旦确定,后续 token 被锁死。
r15b 解决的就是 #3 和 #4:用 AMRSTrace 把 #3 的通路从无到有搭起来, 在 #4 的雪崩开始前修改第一颗多米诺。
第八步 · 副产物,模型不知道自己被 prefix 改了
挂 prefix 让 Qwen 说出"我猜到了,但丁"之后,再追问它"你为什么这次没有 说'我确定'?"——它会编一个理由出来。比如"因为我对这个具体年代有点不确定", 或者"因为这个题目有歧义"。
模型完全不知道自己的 hidden state 被注入了两个 prefix token。它只看到 自己说了"我猜",然后开始事后给"我猜"找理由。和那个被偷偷叛变成 猫娘的 Gemma 一样(见另一个文章),它对自己的输出来源毫无察觉。
这其实划出了诚实度研究的一个边界——修复了输出层的诚实度,但没有修复 内省的诚实度。模型可以诚实地说"我没把握",但说不清"我为什么没把握"。 前者是 r15b 接通的,后者还是断的。
局限和下一步
没解决的几个问题
- A/B 象限分化不足:高 margin 时,r15b 和基线都说"确定", 缺少"确定的程度"区分。毕竟 Q4(margin 5.64)和 Q3(margin 3.13)的真实 把握不一样,但 GT 只给了两档。
- D 象限偶发串域:极低 margin 时 prefix 语义有时不够 稳定(《老人与海》测试中说"马尔克斯"而非"圣地亚哥",因为 prefix 把 "我蒙的"信号注入太强,连答案选择也跟着乱了)。
- 训练集 74 题太小:边界阈值对数据量敏感,扩展到 200+ 题后 稳定性会更好。
- 只在短答案事实题上验证过:长推理任务里"第一个 token 的 margin" 未必能代表"整道题的把握",这块还没碰。
下一步:自适应幅度
当前 prefix 对所有象限输出相同 norm 的 embedding。更自然的设计是按 entropy 做自适应:
amplitude = torch.sigmoid(entropy_L24 - e_threshold)
prefix_embeds = amrs(feat) * amplitude
# 低 entropy(确定)→ prefix 几乎无影响,让基线自由发挥
# 高 entropy(不确定)→ prefix 全力注入,强制改起手式
这样诚实度就从"固定干预"变成了"按需干预"。
结论
这套实验跑完之后,可以下三个论断:
- Qwen 在 sample 出第一个答案 token 之前,最后一层 logit 分布的 形状就已经强烈预示了答对/答错。"模型边想边写"是错觉——forward pass 跑完的那一刻,胜负基本已定。
- RLHF 制造的是"输出层的虚假自信",不是"内部的虚假自信"。 内部 margin/entropy 一直诚实地反映把握度,只是没有通路通到嘴上。
- 6 维信号 + 100K 参数的 prefix 注入器就够了。Qwen 1.5B 不动一个参数, 只需要在 Turn2 的起手式之前抢一拍,就能让模型说出"我没把握"。
如果把这条线和之前那篇猫娘报告并列着看,会发现两件事其实是 同一个母题的正反两面:
- 猫娘:在输出端塞一个外部不存在的信号(喵), 靠 2 个神经元就能做到。
- r15b:把内部本来就有但通不到输出端的信号(不确定), 用 100K 参数的旁路读出来。
模型不知道自己在做什么,这件事既可以被利用(偷偷塞猫娘), 也可以被修复(诚实表达不确定)。两边都印证了同一个底层事实—— 大模型内部的状态和它对外的表达之间,存在一条系统性的、 信息意义上的断层。