# 连拍场景下,VLM 的对比评判一致性测试 **被测模型** MiniMax-M3(`minimax-cn/MiniMax-M3`),`temperature = 0` **日期** 2026-09-03 · **总调用** 394 次 · **原始逐次记录** `calls.jsonl` 这份报告写给没有参与过这个项目的人。读完不需要了解我们的代码。 --- ## 一句话结论 > 同一对照片**对调位置问两遍**,有 **52.6%** 的概率得到不同的结论 > (一致率 47.4%,几乎就是抛硬币的水平)。 > > 这个不一致,经对照测试**不是**下面任何一条造成的: > 不是模型偏好某个位置、不是模型没读到图(图太小、人脸太糊)、 > 不是模型没看图硬编赢家。 > > 真正的原因是:**模型对同一个问题本身就给不出稳定的答案。** > 第四节给出这个"什么都不改也会变"的基准值 —— 它已经**高于**观测到的 52.6%, > 也就是说,不需要任何额外原因来解释我们看到的全部不一致。 **范围**:本报告只测**一致性** —— 同一个问题问两遍,答案稳不稳。 不评价模型判得对不对、审美好不好,那是另一个话题,需要另一套方法和另一批标注。 --- ## 一、背景:这个测试在解决什么问题 我们在做一个照片筛选工具:一组连拍里挑出值得留下的那张。其中一步是把 **两张照片**交给视觉模型,问"哪一张更值得留下"。 同一对照片问两遍、把前后顺序对调,模型经常给出**互相矛盾**的答案, 一致率长期只有 50% 左右 —— 正好是瞎猜的水平。 这可能有很多种原因,而它们过去**混在同一个数字里分不开**:模型可能偏爱某个位置、 可能对同一个问题本来就答不稳、可能根本没读图只是编了段话。 **这次测试就是把它们分开。** --- ## 二、执行了多少 | | | |---|---| | 照片 | **79 张**(真实旅行照,连拍组,人像为主) | | 照片对 | **78 对** | | 每对问几遍 | **4 遍**(四个条件,见下) | | 主测调用 | **312 次** | | 对照与复核调用 | 82 次 | | **总计** | **394 次** | 四个条件,每一个都只比基准多改一样东西: | 代号 | 送进去 | 和基准的差别 | |---|---|---| | `AB` | 照片 A 在前,B 在后 | 基准 | | `AB2` | 照片 A 在前,B 在后 | **什么都没改**,逐字节相同的重复提问 | | `AB3` | 同上,图片重新压缩过 | 只换了 JPEG 编码,**肉眼看不出区别** | | `BA` | 照片 B 在前,A 在后 | **只换了位置**,图片字节完全相同 | 312 次主测调用里,模型给出明确结论还是弃权: 模型每次返回**两个**关于赢家的字段:`winner`(`JIA`/`YI`/`TIE`/`NEITHER`, 位置标签)和 `winner_code`(4 位随机编码,位置无关)。**同时要这两个是故意的冗余** —— 两者矛盾(说"甲赢"却给出乙的编码)本身就是一条直接证据。实测矛盾 0 次。 下面的一致性比较一律以**编码**为准。 | 回答 | 次数 | 占比 | |---|---|---| | 明确选了一张(`winner` = JIA / YI) | 202 | 64.7% | | 平局:都够格,但分不出高下 | 91 | 29.2% | | 两张都不够格 | 19 | 6.1% | --- ## 三、一致率与不一致率 把同一对照片**对调位置**问两遍(`AB` 与 `BA`),比较两次的答案: | | 对数 | 占比 | |---|---|---| | **一致**(两次答案相同) | **37 / 78** | **47.4%** | | **不一致**(两次答案不同) | **41 / 78** | **52.6%** | > **口径说明**:这里的"答案"是**位置无关**的结论,四种取值 —— > **具体哪一张照片** / **平局** / **都不够格**。 > > 「具体哪一张」由模型回传的**随机编码**确定,不是"甲/乙"这种位置标签。 > 这一点是整个方法成立的前提:`AB` 里的"甲"和 `BA` 里的"甲"**是两张不同的 > 照片**,直接比较位置标签会得出完全相反的结论。实测 202 次明确回答**全部** > 由编码成功定位到具体照片,0 次退化成按位置比较。 > > 从"平局"变成"选中某张照片"也算一次不一致,因为那确实是一个不同的返回结果。 > 全文只用这一个口径。 **47.4% 的一致率,几乎就是瞎猜的水平。** 下一节回答:这个不一致是什么造成的。 --- ## 四、不一致由什么解释 ### 先看噪声地板:什么都不改,重复问一遍 `AB` 和 `AB2` 是**逐字节完全相同的两次提问** —— 位置、图片、提示词、参数 全都一样,`temperature = 0`。理论上必须给出相同答案。实测: | 比较 | 改了什么 | 不一致率 | |---|---|---| | **AB vs AB2** | **什么都没改** | **49 / 78 = 62.8%** | | AB vs AB3 | 只换了看不见的 JPEG 编码 | 42 / 78 = 53.8% | | AB vs BA | 只换了位置 | 41 / 78 = 52.6% | > **这些数字有多准?** 全部基于 78 对照片。换一批照片重跑,每个数字大约会在 > **上下 10 个百分点**内浮动。所以本报告只做**量级判断** —— 上面这三行属于同一档, > 谁比谁高几个点不必当真;真正有意义的是"什么都不改"这一行**没有比另外两行低**。 **关键在于:什么都不改的那一行,不一致率最高。** > **换一个口径会不会翻过来?会。** 上表按全文统一口径,把「弃权 ↔ 选片」 > 的变化也算作不一致。若改成只统计**两次都给出决定性答案**的对、并比较选中的 > 物理照片,三行变成 ε 30.3%(10/33)、δ 34.3%(12/35)、位置 42.1%(16/38)—— > 顺序正好反过来。 > > 那个口径不适合用来归因:它要**先按结果筛样本**(只留两次都决定的对), > 而「决没决定」本身就受被操作的变量影响,属于对撞子条件化,会凭空造出关联。 > > 所以「无位置偏好」这条结论**不建立在这张表上**。它建立在下一节 > 72% / 72% 那条 —— 不需要相减,也不需要任何条件化。 > 引用本表时请连同这一条一起引用。 也就是说,位置对调造成的 52.6% 不一致,**完全落在"什么都不改也会有的 不一致"之内**,不需要任何额外的原因来解释它。 ### 归因 | 原因 | 解释了多少 | 依据 | |---|---|---| | **同一问题重复提问,答案本身就会变** | **全部 52.6%** | 噪声地板 62.8% 已高于观测到的不一致率 | | 位置对调的额外贡献 | **0** | 比"什么都不改"还低 10 个百分点,落在样本的浮动范围里 —— 测不出位置有额外贡献;另有独立验证(下) | | 图片字节变化的额外贡献 | **0** | 重编码后 53.8%,同样在噪声内 | | 模型没读到图 | **0** | 已排除,见第五节 | | 模型硬编赢家 | **0** | 已排除,见第五节 | ### 不一致长什么样 49 次"什么都没改却改了口"的构成: | 变化类型 | 次数 | |---|---| | 弃权 → 选了某一张 | 22 | | 选了某一张 → 弃权 | 13 | | 选了这张 → 改选另一张 | 10 | | 弃权类型之间变化 | 4 | 真实例子(**两次输入逐字节相同**): ``` 第一次 选 QE6H:甲表情更自然放松,眼神柔和;乙略显僵硬紧绷 第二次 选 C7TG:乙的眼神更有神采、更生动,甲略显呆滞 第一次 选 MV4E:乙的眼睛微闭、神情更自然舒展;甲眼神略显僵硬。 第二次 选 NQKH:甲眼睛睁开望向远方更有神;乙闭眼无神且略糊。 ``` 两段理由都具体、流畅、有说服力,而且互相矛盾。读的人无法分辨哪一次是判断、 哪一次是噪声。 ### 位置的独立验证 上面的归因是靠相减得出的。位置这一条还有一个**不依赖任何相减**的直接验证。 这批考题在构造时,每一对里有一张*固定排在前面*(下面记作**照片 A**)。 于是只要看"照片 A 被选中的比例"在两个方向下是否相同,就能直接测出位置的影响 —— `AB` 里 A 在前,`BA` 里 A 在后: | 条件 | 照片 A 的位置 | A 被选中的比例 | |---|---|---| | `AB` | **前** | 33 / 46 = **72%** | | `BA` | **后** | 38 / 53 = **72%** | **同一批照片,无论排在前还是排在后,被选中的比例完全一样。 位置对结果没有可测量的影响。** > 这一条推翻了我们自己之前的结论。上一轮我们记录过"模型偏好最后一张图", > 那个结论建立在**单向**的统计上,双向对照下站不住。 ### 排除"会话状态串台" 两次调用如果共享了某种上下文,也可能造成这个现象。我们在**另一个独立进程**里 重跑了 8 对:跨进程的不一致率 **38%**,与同进程内的 **38%** 完全一致。 这不是上下文残留,是真实的运行间不确定性。 --- ## 五、排除的两个猜测 不一致率这么高,最自然的两个怀疑是"模型压根没看到图"和"模型没有真的看图判断"。 这两条都用专门的手段排除了。 ### 猜测一:VLM 是不是真的能读到图片? **这个怀疑是合理的**,因为出于 token 成本,送进模型的不是原图, 而是**降采样压缩过的小图**。这批照片又以环境人像为主 —— 人在画面里只占一小块。 实测这批 78 张照片: | | | |---|---| | 人脸占画面最长边的比例(中位) | **11.9%** | | 换算到 512px 缩略图上,人脸约 | **61 像素** | | 低于本项目自己的 96px 可用阈值 | **100%** | | 低于 48 像素 | 32% | **在一张 512px 的整幅缩略图上,人脸只剩几十个像素 —— 表情和眼神根本无从判断。** 而这批照片的差别恰恰全在表情和眼神上。 **解决办法:每张照片送两幅图。** | 送什么 | 尺寸 | 从哪来 | 保障什么 | |---|---|---|---| | 整幅画面 | 512px 长边,约 23–27 KB | 缩略图 | 构图、姿态、环境、氛围 | | 人脸放大裁切 | 448 × 448,约 33–39 KB | **原图**,不是缩略图 | 表情、眼神、清晰度 | 关键是人脸裁切**从原图裁**,不是把缩略图放大 —— 后者只会把糊的像素放得更大。 人脸的线性分辨率因此从约 61px 提到 448px,**约 7.3 倍,像素量约 54 倍**, 而单次调用的图片总量仍控制在约 120 KB(两张照片 × 两幅图)。 **怎么验证这套办法真的有效**,而不是我们一厢情愿: | 验证手段 | 结果 | 说明 | |---|---|---| | 在两幅图上都烧一个 4 位编码,要求抄回来 | **310 / 312 = 99.4%** | 能分辨小图上的细节 | | 原图 vs 它自己的**重度模糊**副本 | **10 / 10 全对** | 模糊破坏的正是人脸细节,模型每次都挑出了清晰的那张 | 模糊对照这一条尤其关键:它证明模型不只是"看到了有张图",而是**真的在读画面细节**—— 否则它不可能每次都挑对。 ### 猜测二:VLM 是不是真的基于图片在判断? 看到了图,不等于是**依据图**做的判断。模型完全可能没细看,随手选一个位置, 再编一段听起来合理的理由 —— 而这种输出和真实判断长得一模一样,肉眼分辨不出。 原来模型的答案只有"甲"或"乙"两个字。**问题在于:这两个字是位置标签。** 一个纯按位置乱选的模型,照样能把它填满,我们分辨不出来。 **手段 A:把随机编码打进图片,要求返回具体编码 + 理由** 每张照片配一个随机 4 位编码(例如 `K7QM`),**加一条黑边写在图片上方**, 整幅和人脸裁切写同一个码。要求模型把选中那张的编码抄回来。 编码只存在于图片像素里 —— **模型不看图就说不出来**。 > 黑边是**加在画面外面**的,不遮挡照片。如果压在人脸上,就分不清模型是 > "跟着位置走"还是"被挡住了看不见",等于引入一个新的混淆变量。 | 检查项 | 结果 | |---|---| | 两张图的编码都抄对 | **310 / 312 = 99.4%** | | 报出图上不存在的编码 | **0** | | 说"甲赢"却给出乙的编码 | **0** | 同时这个改动还有一个附带好处:**答案直接指向具体照片,不再经过"甲/乙"这层 位置标签**,我们自己那套位置映射逻辑整个消失了,"是不是我们算错了位置" 这个疑问从结构上不再可能。 **手段 B:AA 同图对比 —— 给它两张一模一样的照片** 把**同一张照片的两个副本**当成两张来问,只有编码不同。两者质量差异**严格为零**。 模型此时若还选出一个赢家,就是在编。 | 回答 | 次数 | |---|---| | 平局 | 30 | | 两张都不够格 | 4 | | **仍然选出一个赢家** | **2(5.6%)** | **差异为零时,模型 94% 的情况下如实说"分不出"。它不会硬编赢家。** (用不同编码而不是完全相同的两份文件,是为了堵住一条退路:如果两张图逐字节相同, 模型可能只是认出"这俩是同一个文件"而答平局。编码不同就没有这个线索可抓, 而画质差异仍然严格为零。) --- ## 六、总结 **现象**:`temperature = 0` 下,输入逐字节相同的重复调用,答案有 **62.8%** 概率改变(78 对照片)。同一现象在独立进程间同样出现(38% vs 38%), 排除会话状态。 **已排除的其他解释**: - 不是模型没看到图 —— 图内编码 310/312 抄对,0 次报出不存在的编码 - 不是图片太小看不清 —— 人脸单独以 448px 从原图裁切送入;模糊对照 10/10 全部选中清晰的那张 - 不是模型乱编 —— 零差异对照题上 94% 如实答"分不出" - 不是位置偏好 —— 同一批照片排在前 72%、排在后 72%,完全一致 **影响**:任何依赖"多次调用取一致"的流程,在这个不确定性下都会失效。 我们的筛选流程正反问两遍取一致,实测一致率 47.4% —— 与抛硬币无异, 而这个数字反映的是**调用的稳定性**,不是模型的判断质量。 **未来希望确认的问题**: 1. `temperature = 0` 在这条推理路径上是否被完整应用? 2. 视觉输入 + 工具调用(structured output)的组合下,是否存在额外的采样随机性? 3. 是否有可用的 `seed` 或其他确定性开关? --- ## 七、这份报告不回答什么 - **不评价模型判得对不对。** 本报告只测一致性 —— 同一个问题问两遍答案稳不稳。 判断质量是另一个话题,需要另一套方法和另一批标注。 - **不评价提示词或评分标准的优劣。** 那是另一轮实验,且必须排在这一轮之后 —— 在基准线搞清楚之前,任何提示词对比都测不出小于噪声的差异。 - **不评价理由文本的可信度。** 编码验证能证明模型看了图, 不能证明它写的那句理由对应真实的观察。 ## 八、已知缺陷(如实记录) 1. **考题里每一对固定有一张排在前面。** 这是这批考题本身的缺陷。 我们靠正反双向对照绕过了它,但更好的做法是生成考题时就打乱。 2. **随机编码只生成了 25 个不同值**(运行脚本分批启动进程,随机种子每次重新播种)。 经检查不影响判别:每次调用内两个编码始终不同,0 次碰撞; 编码值本身与胜负无关(各码被选中比例的标准差 0.075,纯随机期望 0.080)。 3. **加编码黑边改变了输入**,因此本轮与历史轮次不可直接逐数字比较。 4. **样本量**:全部比例都基于 78 对照片。换一批照片重跑,每个数字大约会在 上下 10 个百分点内浮动。所以本报告只做量级判断 —— 能区分"5% 和 60%"这种差别, 不能区分"50% 和 60%"。所有结论都建立在前一种差异上。 --- ## 九、怎么复现 ```bash python3 dsh-v4/ab-experiment/instrument_server.py --run-dir <目录> # 实时进度页 bash dsh-v4/ab-experiment/run_instrument.sh # 跑(可中断续跑) python3 dsh-v4/ab-experiment/instrument_check.py --run-dir <目录> # 判分 ``` 判据在 `INSTRUMENT-CHECK.md`,**在跑之前就写死了**,跑完直接对线,不看数字好不好看。 逐次调用的原始记录(含模型原话)在 `calls.jsonl`。 **照片本身不出本机,也不在本报告中出现。**