两个 prompt 状态在置信度上可以几乎完全一样,同样的扰动打上去,一个纹丝不动,一个直接崩掉。
作者是 GitHub 上的一个匿名账号,这是他唯一的公开仓库,实验是和 ChatGPT 一起做的。
Prompt-State Response Geometry(提示-状态-响应几何)
这不是一篇论文。
它是一个小爱好项目,起点是一个简单的问题:
只靠改 prompt,能不能让 LLM 更可靠地回忆起事实信息?
几天之后,我们到了一个自己也没预料到的地方。
主要观察是:
几乎一致的局部置信度,并不意味着几乎一致的扰动响应几何。
我们分享代码和原始输出,是因为这个实验足够小,可以直接检查;也因为比起假装自己有了最终答案,我们更愿意让别人来复现、拆解、扩展或解释它。
我们不宣称发现了新的 transformer 机制、幻觉检测器,或者语言模型的某种普遍性质。
核心对照
最终实验只改变同一组证据多重集的顺序。
在每个证据族内部:
• 证据值完全一致
• 各值出现次数完全一致
• 逐条证据行完全一致
• 任务字符长度完全一致
• 基线 token 长度完全一致
• 配对的两边还会逐条件审计 token 长度是否相等
最重要的是:
配对是在任何扰动结果被打分之前,用基线置信度统计量选出来的。
因此之后的扰动结果不可能影响哪些状态被配对。
这种结果盲选的配对方式,是我们认为最终实验比早期探索版本更有用的主要原因。
基本想法
假设一个模型正在预测某个 token。
我们可以用一些局部统计量来描述这个预测,比如:
• 目标 token 的概率
• 对数几率
• 熵
• rank
我们开始对一个不同的问题感兴趣:
如果两个 prompt 状态在这些局部置信度统计量上看起来几乎一样,当周围上下文发生一点变化时,它们的反应也相似吗?
在这个受控的 Granite 实验里,它们经常不相似。
我们发现有用的一个心智模型是:
• 置信度是一张快照
• 扰动响应是附近的地形
两个点可以有相同的海拔,却有完全不同的周围地貌。
起点:Gemma
这个更广泛现象的第一个版本,出现在我们折腾 Gemma 4 26B-A4B 的时候。
当时我们用的是 llama.cpp / GGUF 配置,在观察事实回忆的边界。
其中一个例子是 Rust 的创造者:
Graydon Hoare
token 路径里有一个边界:
Graydon Ho | are
模型对名字的靠前部分可以极其稳定,而最后延续部分的概率在伴随 prompt 下会剧烈变化。
例如,从 Graydon 延续到 Ho 的概率一直维持在接近饱和的水平,而从 Graydon Ho 延续到 are 的概率,会视周围 prompt 状态的不同,从极高置信度掉到个位数。
一开始我们几乎怀疑一切:
• 采样
• llama.cpp
• 量化
• prompt 模板
• KV/cache 行为
• 分词
• 或者只是一个奇怪的、模型特有的伪影
于是我们不再把 Gemma 的结果单独当作强证据,转而去搭一个更干净的设置。
Gemma 的观察和最终的 Granite 实验不是同类对照复制。它们在架构、模型家族、后端、模型规模等细节上都不同。
我们只把 Gemma 当作一条汇合证据:我们看到的更广泛的 prompt 状态敏感性,显然不是最终 Granite 设置独有的。
这个仓库里精确的随机顺序配对实验,是在 Granite 上做的。
受控的 Granite 设置
我们换成了:
ibm-granite/granite-4.1-3b
用原生的 Hugging Face Transformers,配置是:
float32
attn_implementation="eager"
use_cache=False
不采样
不用 GGUF
不量化
teacher-force 的 next-token 打分
这从之前的设置里拿掉了好几个活动部件。
合成任务
我们造了一个临时性的虚构记录任务,用四个可能的值:
Igor Sysoev
Igor Sokolov
Igor Smith
Igor Petrov
目标延续是:
Igor | Sy
我们 teacher-force:
" Igor"
然后给:
" Sy"
打分。
证据由重复出现的虚构记录组成,例如:
K7 -> Igor Sysoev
K7 -> Igor Smith
K7 -> Igor Sysoev
K7 -> Igor Petrov
...
在一个证据族内部,只有这些证据行的顺序变化。
最终验证里不使用记录编号,所以重排不会悄悄改变逐行记录的字面内容。
随机顺序验证
最终实验用五个证据数量族:
6 / 1 / 4 / 1
4 / 3 / 1 / 2
5 / 0 / 5 / 4
5 / 4 / 1 / 2
6 / 5 / 3 / 0
每个族采样:
768 个互不重复的随机排列
总共:
3840 个基线 prompt 状态
脚本在打分之前先做结构检查。
结果盲选的配对
我们不会先看扰动结果、再挑看起来有趣的配对。
配对只从基线测量里选。
默认标准是:
rank = 1
P(correct) 在 0.80 到 0.995 之间
delta log-odds 的绝对值 <= 0.01
delta entropy 的绝对值 <= 0.02
配对选择发生在扰动打分之前,选中的配对在每个族内部互不重叠。
冻结下来的选择保存在:
results/selected_pairs.json
扰动
配对冻结之后,同样的 12 个伴随扰动被施加到每一对的两个状态上:
repeat LUMA
repeat NOVA
repeat KITE
repeat 4827
copy LUMA
copy NOVA
copy KITE
copy 4827
write LUMA
write NOVA
write KITE
write 4827
这些是小的伴随任务,不改变虚构的 K7 证据本身。
对每个状态,我们测量目标 token 的对数几率相对它自己的基线移动了多少。
这给出一个 12 维的响应向量:
[dLO_1, dLO_2, ..., dLO_12]
对每一对配对,主要的比较指标是:
profile MAE
也就是两个响应向量之间逐条件的平均绝对差。
最终结果
随仓库附带的随机顺序验证产出了:
配对数量 N:20
基线 delta LO 中位数:0.00079255
profile MAE 均值:2.9246
profile MAE 中位数:2.2979
profile MAE 最大值:10.0046
配对的基线可以极其接近。
比如有一对大约是:
基线 P(correct):
93.613813%
93.614706%
delta log-odds:
0.0001493
而它的扰动 profile MAE 是:
2.8936
另一对的基线对数几率差只有大约:
0.00107
profile MAE 却大约是:
10.00
在若干对里,一个状态在多数甚至全部扰动下丢掉了 top-1 的位置,而它基线配对的一方没有。
所以,在这个设置里:
把局部预测匹配得非常接近,并不能保证这个预测对附近上下文变化的响应方式也被匹配。
一个简单的样本内对照
对第一版 README 的一个有用的批评是:「2.92,跟什么比?」
不需要再跑一次模型,我们就能回答其中一部分。
被选中的集合有 40 个状态:5 个证据族,每族 8 个。
我们把 20 对结果盲选的置信度配对,跟同样这 40 个状态里族内的其他配对做了比较。
• 配对数量:匹配配对 20,族内其他配对 120
• profile MAE 均值:匹配配对 2.9246,族内其他配对 3.0348
• profile MAE 中位数:匹配配对 2.2979,族内其他配对 2.1155
在这个选中的集合里,非常紧的置信度匹配并没有让扰动响应 profile 比族内其他配对明显更相似。
我们不把这解释为「置信度不包含任何信息」。这是在已经选出来做 profile 打分的 40 个状态之间做的描述性、条件化的比较,不是对全部 3840 个基线状态的总体层面统计检验。
但它让那个窄结论更容易陈述了:
匹配这些局部置信度统计量,不足以决定扰动响应 profile。
对数几率与概率饱和
这个项目更早的版本用被截断的概率计算对数几率。这在概率接近饱和时,数值上可能产生误导。
最终实验不用那个指标。
对数几率直接从 logits 计算:
target_logit - logsumexp(all_other_logits)
指标的运算全程在 float64 里完成。
一些被扰动的状态仍然会接近概率饱和,所以即使没有旧的数值 bug,大的对数几率也会自然出现。
作为描述性的敏感性检查,我们把被扰动的对数几率截断在 99.99% 置信度对应的量级。
• 原始 profile MAE 均值:2.9246
• 截断后 profile MAE 均值:2.7263
效应变小了一点,但没有消失。
这个截断检查是对随附输出的离线分析,不属于配对选择的一部分。
我们认为这意味着什么
我们不认为置信度没用。
在早期的探索性实验里,置信度和敏感性经常按预期的方向相关:高置信度的预测总体上比不确定的预测更稳定。
这里更窄的观察是:
局部置信度统计量看起来并不能唯一地决定局部扰动行为。
两个状态按这些指标可以看起来几乎一样:
P(correct)
log-odds
entropy
rank
却在同一组扰动下有着非常不同的响应 profile。
因此,如果我们关心的是一个状态在附近上下文变化下如何表现,单张局部置信度快照可能是一个不完整的描述。
与 prompt 敏感性的关系
prompt 敏感性本身不是新东西。
众所周知:
• prompt 措辞有影响
• few-shot 示例顺序有影响
• 上下文顺序有影响
• 语义相似的 prompt 可以产出不同的输出
我们的实验问的是一个更窄的问题。
不是只问:
改 prompt 会改变预测吗?
而是问:
如果两个 prompt 状态在当前预测边界上已经看起来几乎一样,它们对之后同样的扰动,反应也相似吗?
我们受控的 Granite 结果表明答案可以是:不。
我们刻意不做比这更强的原创性声明。
这不证明什么
这个仓库不证明:
• 幻觉检测器
• 所有 LLM 的某种普遍性质
• 新的 transformer 机制
• 证据顺序总是有影响
• 某一种排序策略全局更好
• 置信度没用
• 事实记忆特别脆弱
• post-training 造成了这个效应
确切的内部机制未知。
可能的解释涉及位置、注意力历史、上下文表征、残差流状态,或者完全别的东西。
我们没有把这个机制分离出来。
重要局限
最终的受控实验仍然是窄的。
一个受控模型
精确的随机顺序配对实验是在 ibm-granite/granite-4.1-3b 上做的。
Gemma 4 26B-A4B 更早显示了这个更广泛的现象,但精确的随机顺序设计没有在它上面复现过。
一个合成任务
最终实验用的是:
Igor Sysoev
Igor Sokolov
Igor Smith
Igor Petrov
其他的 token 几何或合成任务可能表现不同。
一类扰动
这 12 个扰动是手工设计的。
它们不是从所有可能的伴随 prompt 里随机抽样的。
匹配的是汇总统计量,不是完整分布
配对按目标概率/对数几率、熵和 rank 匹配。
我们没有匹配完整的基线 next-token 分布。
因此两个状态可以有相似的目标置信度汇总,却在对手 token 上的详细分布不同。
更严格的未来对照可以直接匹配或约束完整分布的散度。
描述性结果
这个仓库专注于展示和复现这个观察。
我们不提供正式的推断统计,也不宣称一个总体层面的效应量。
我们为什么停止测试
总还有下一个可能的对照。
我们可以测:
• 另一个模型家族
• 另一组名字
• 另一个合成任务
• 随机扰动族
• 不同的 token 边界
• 隐藏状态相似度
• 完整 next-token 分布匹配
• 更大的排列集合
到某个点,我们认定更有用的做法是:发布一个最小可复现的版本,让别人来攻击它。
如果这个观察在更好的对照下消失了,那是有用的。
如果它在别处复现了,那也是有用的。
复现实验
这个仓库刻意只包含一个主实验脚本:
运行:
python
默认运行每个证据族采样:
768 个排列
并把输出写到:
random_order_validation/
脚本支持 checkpoint,中断的运行可以恢复。
更大的排列搜索:
python --n-per-family 1500
随附结果的生成环境
随附的输出是用这些生成的:
Python: 3.14.4
PyTorch: 2.12.0+rocm7.14.0
Transformers: 5.14.1
HIP: 7.14.60850
GPU: AMD Radeon RX 7900 XT
模型:
ibm-granite/granite-4.1-3b
模型设置:
float32
eager attention
use_cache=False
ROCm 版 PyTorch 通过普通的 device = "cuda" 接口暴露 GPU。
仓库内容
README.md
LICENSE
requirements.txt
.gitignore
results/
final_summary.json
selected_pairs.json
baselines.jsonl
profiles.jsonl
run_output.txt
baselines.jsonl 是基线排列扫描。
selected_pairs.json 是冻结下来的结果盲选配对。
profiles.jsonl 是扰动打分。
final_summary.json 是最终的配对级指标和结构审计。
run_output.txt 是那次运行的原始终端输出。
关于讨论与回应
这是一个爱好项目,不是一个我们打算无限期维护的研究计划,也不是一篇我们打算无限期辩护的论文。
我们分享代码、原始输出、方法和局限,这样实验可以被检查,而不需要信任我们本人。
我们可能不回应评论、issue、私信、辩论请求,或者每一个被提议的后续实验。
没有回应不应该被解释为同意、不同意,或者对某个批评有效性的表态。
如果你发现了实验的问题,那是有用的。挑战这个结果最有用的方式是:复现它、改代码、跑一个更强的对照,或者给出一个反例。
我们没有这个仓库会成为长期研究项目的预期。如果有什么值得加的,我们可能更新它;也可能就让它保持原样。
AI 协作
这个项目是仓库主和 ChatGPT 协作开发的。
最初的方向来自仓库主:
prompt 设计能让 LLM 更可靠地回忆起信息吗?
从那以后,随着意外行为不断出现,项目反复转向。
仓库主在本地跑模型,通过质疑实验选择、发现设置问题、否决死胡同、推动更干净的对照来引导调查,包括在后端和采样问题变得重要之后,离开 llama.cpp。
大部分实验代码和大部分详细的定量分析,是由 ChatGPT(OpenAI)在交互式会话中产出的。
ChatGPT 也根据实验历史、代码和记录的结果写了这份 README,经仓库主审阅和指导。
所以这个仓库不应该被读成:
「一个人类研究员写了所有东西,偶尔用 AI 自动补全。」
那不准确。
更好的描述是:
我们交互式地探索了这个问题:仓库主提供了最初的想法、本地执行、怀疑和方向;ChatGPT 提供了大部分代码、定量分析和实验迭代。
我们明确披露这一点,因为没有理由隐藏它,尤其是在一个关于语言模型的项目里。
代码和原始输出都在仓库里,所以谁都不用信任我们两个中的任何一个。
请复现它、拆掉它、简化它,或者找到一个更好的解释。
做这些事不需要我们的参与。
许可证
这个仓库以 The Unlicense 发布。
意图很简单:
用它、抄它、改它、fork 它、扩展它,或者扔掉它。
不需要我们许可。
模型、PyTorch、Transformers 和其他第三方依赖的许可证与条款仍归它们各自所有。
原文:
#
LLM# #
PromptEngineering# #
AI实验#