ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

大模型输出不稳定怎么破?自我一致性提示词工程实战

大模型输出不稳定怎么破?自我一致性提示词工程实战

同一个提示词、同一个模型、同一套参数,连着跑三次给你三个互相打架的答案——我第一次碰到这种情况时,第一反应是接口缓存串了数据,翻了半天请求日志才发现是自己把 temperature 设成了 0.7。那会儿我正在做一批数学应用题的批量评测,单次跑出来的准确率忽高忽低,同一道题今天对明天错,根本没法判断是模型能力问题还是我的提示词有问题。后来在 Prompt 工程这条路上摸爬滚打久了才明白,**自我一致性(Self-Consistency)**这套思路解决的正是这个痛点:它不指望模型一次就想对,而是让它用不同的思路想很多次,最后投票选那个被最多条推理路径支持的答案。对于做评测、做数据标注、做需要稳定输出的业务场景的人来说,这套方法几乎是把"玄学"变成"工程"的第一步。下面我把自己从踩坑到跑通、从跑通到调优的完整过程拆开讲,包含提示词骨架、采样参数、抽取归一的代码、路径数的边际收益曲线,以及几种典型的失效排查链路,适合刚接触 Prompt 工程、想把手里的模型输出稳定下来的读者。

1. 单次推理靠不住:自我一致性到底补的是哪个洞

1.1 温度系数把确定性这件事悄悄偷走了

很多人对模型输出的第一印象是"它是确定的",因为在大众认知里计算机程序就是输入决定输出。实际不是。当前主流对话模型的解码过程本质上是"按概率抽签":模型每生成一个 token,都会先算出一整张词表上的概率分布,然后按这张分布去采样。temperature 参数干的事情,就是把这个分布"压平"或者"削尖"。temperature 趋近 0 时,分布被削得只剩最高概率那一根尖刺,相当于每次都选概率最大的 token,业内叫贪心解码;temperature 拉到 1 附近,分布保持原样,抽签范围变宽,输出就开始有随机性了。

我在实践中发现一个很多人忽略的细节:即使把 temperature 设成 0,同一个请求多次调用也未必 100% 一致。原因在于批处理调度、并行计算的浮点累加顺序、推理框架的算子实现等因素,都可能在极低概率上引入微小差异,而模型是一个"每步都基于前文重新决策"的系统,前面一个 token 的细微差别,后面可能滚成完全不同的句子。所以严格意义上,只要不是每次都走完全相同的计算路径,输出就有方差。这个问题在短回答里不明显,在多步推理的长回答里会被放大得很夸张。

温度带来的方差不是均匀分布的。简单的事实问答,比如"某个国家的首都是哪",即使 temperature 给到 1,模型翻来覆去也就那么一两个答案;但一旦题目需要多步计算、多点信息综合、或者存在歧义,方差就陡然上升。这提示我们,方差的大小和任务的"推理深度"强相关,而自我一致性恰恰是针对推理深度大的任务设计的。

1.2 思维链把单步误差滚成了雪球

思维链(Chain-of-Thought)提示是过去几年 Prompt 工程里最能提准确率的一招,让模型把中间步骤写出来再给答案。但它的副作用很多人没意识到:它把"一次判断"拆成了"十次判断",只要其中一步走偏,后面全歪。

举个我实际踩过的例子。一道典型的多步应用题,涉及先算总量、再算单价、最后算总价,中间还要做一次单位换算。假设模型每一步的正确率是 95%,看起来很高,但四步连乘下来,整道题的正确率就只有 0.95⁴ ≈ 81%。如果题目有八步,正确率掉到 66%。而如果某一步出现歧义、模型在两种理解之间摇摆,那一步的正确率可能只有 70%,整体就会崩得更厉害。

更麻烦的是,模型走错路的时候语气一点都不心虚。它会把错误的前提当成既定事实继续往下推,最后给你一个看起来推导严密、实际上根基就歪了的答案。我在早期做评测时,经常被这种"自信的错误"骗过去,直到手工核对才发现问题出在第二步。

自我一致性的洞察就发生在这里:对于同一个问题,正确的推理路径往往不止一条,但错误的推理路径通常各自跑偏、互不相同。如果你让模型用不同的随机种子、不同的措辞、不同的切入点去想很多遍,正确答案会因为有多条路径通向他们而形成"共识",而错误答案则因为路径分散而票数稀薄。这不是让模型变聪明了,而是让"运气"这个东西在统计上被摊平了。

1.3 为什么这套思路在 Prompt 层面就能落地

对比一下模型训练侧的方案:要让模型输出更稳定,通常需要做对齐训练、RLHF、拒绝采样之类的工作,门槛高、周期长、还得有算力。自我一致性最大的好处是它完全发生在推理侧和提示词侧,不改模型、不训练、不微调,只要你会调接口、会写几十行聚合代码,今天下午就能跑起来。

我在几个不同规模的项目里都验证过这个特点:只要任务本身有"唯一正确答案"这个属性,套上自我一致性之后,输出的稳定性肉眼可见地提升,尤其是那些单次跑准确率在 60% 到 80% 之间晃荡的任务,往往能提到 85% 以上。而那些单次准确率本来就有 95% 的简单任务,加了反而浪费钱。

2. 机制拆解:从一条推理链到一片路径森林

2.1 多次采样、独立推理、答案聚合这三条腿

自我一致性的执行结构其实很朴素,可以拆成三个必须同时成立的动作。第一步是多次采样:对同一个问题,让模型生成 N 条完整的推理过程,每条都从零开始,独立作答。第二步是独立推理:这 N 条路径之间不能互相看见,不能共享上下文,最好连系统提示都一模一样,保证它们是在同一条件下各自摸索。第三步是答案聚合:从每条路径里把最终答案抠出来,统计哪个答案出现次数最多,票数最高的那个就是输出。

这三步缺一不可。少了第一步,就退化成普通思维链;少了第二步,就变成"让模型自己检查一遍";少了第三步,就变成"生成多个候选让用户挑"。我在早期做过一个错误版本,为了省 token,把 N 条路径塞进同一次对话里,让模型"给出三种不同的解法然后自己判断哪个对"。结果准确率几乎没提升,因为模型在同一段上下文里生成的三条路径高度同质化,它只是换了个说法重复同一套思路,投票等于自己给自己投。

对比另外两种常见的思路会更清楚。Best-of-N采样也是生成多个候选,但它需要一个外部的验证器或者打分模型来判断哪个候选最好,多了一个组件。模型集成(Ensemble)是换不同的模型各答一遍再投票,成本和运维复杂度都高一个量级。自我一致性只用同一个模型、不同随机性,聚合规则简单到用计数器就能实现,工程上极轻。

2.2 多数投票成立需要四个隐含前提

这套方法看起来像"人多力量大"的朴素逻辑,但它在什么条件下有效,很多人没想清楚。我在不同任务上试下来,总结出四个隐含前提,只要有一条不成立,投票就会失效。

前提具体含义不成立时的表现
答案空间可枚举最终答案能被归一化成有限个离散值投票全是平票,聚合毫无意义
正确路径概率质量集中正确的那条路是模型最容易想到的模型根本没这个知识,投一万次也是错的
路径之间相互独立采样过程没有共享上下文或缓存所有路径雷同,投票退化
答案抽取确定从文本里抠答案的规则是固定的明明说的是同一个数,被抽成两种写法

第一条最容易理解。数学题答案是数字,选择题答案是选项字母,分类任务答案是类别名,这些都能归一化。但如果你让模型写一段营销文案、做一次开放式总结,答案是无限维的文本空间,"哪段文案最一致"这个问题本身就没有定义,投票无从谈起。我在一个文案生成项目里试过自我一致性,结果聚合出来的"高频答案"往往是最平庸、最没信息量的那一版——因为平庸的表达路径最多,这反而把好的创意给投没了。

第二条讲的是模型能力的边界。自我一致性只能在"模型偶尔会做对"的任务上起作用,它摊平的是运气带来的方差,不是知识空白。如果模型压根没见过相关知识,任你采样一百次,错误答案会以压倒性优势形成"共识",这时你反而会得到一个错误但极其自信的结论。我见过有人拿这套方法去测冷门知识,结果跑出来一堆错误答案,还以为自己发现了什么规律。

第三条是本篇最需要强调的工程细节。独立性包含三个层面:调用独立、上下文独立、提示词一致。调用独立是指每次都是一次新的请求,不复用对话历史;上下文独立是指不要把前一次的答案顺手塞进后一次;提示词一致是指 N 条路径的输入除了随机性以外完全同一,如果你给每条路径配了不同的角色设定或者不同的示例,那实际上是在做 Ensemble,结果解释起来就复杂了。

第四条最容易被低估,也最容易在工程上翻车。模型的最终答案常见写法有"答案是 42""最终结果:42""因此,总价为 42 元""42",如果抽取正则只认其中一种,另外三种就被丢掉了,票数自然稀薄。我在这一条上至少翻过两次车,后面会专门讲。

3. 手搭流水线:提示词、采样、抽取、投票四步

3.1 提示词骨架:让答案可抽取比让模型多想更重要

很多教程在讲自我一致性时,把注意力全放在"怎么让模型多想几遍"上,其实提示词设计的核心是让答案可被稳定抽取。我摸索出来的模板结构大概长这样:

你是一个严谨的推理助手。请一步步分析下面的问题,并给出最终答案。 要求: 1. 先写出完整的推理过程,每步都要有依据。 2. 推理结束后,另起一行,用 "最终答案:" 开头,后面只跟答案本身。 3. 不要在最终答案那一行写任何解释、单位说明或多余的字。 问题:{question}

这个模板里,第 1 条是思维链的触发,第 2 条是抽取锚点,第 3 条是防止模型在答案后面加尾巴。实践中我发现第 3 条特别重要,模型很爱在答案后面补一句"(假设没有其他费用)"或者"(保留两位小数)",这种尾巴一旦掺进答案字符串,归一化就会变得极其痛苦。

一个血的教训:模板里千万不要写"请用多种方法思考这个问题然后给出你认为最可靠的答案"。这句话听起来像自我一致性,实际上是把投票工作交给了模型自己完成,等于放弃了你对过程的控制。模型自己投票的能力远比不上统计计数。

另外,冻结提示词是个容易被忽略但极其重要的工程习惯。一旦确定了模板,所有路径都用同一个版本。我早期做过一个实验,中途为了"优化"模板改了几个字,结果前后两批数据没法直接对比,白白浪费了一晚上的采样预算。

3.2 采样参数与并发控制

参数这块我踩过的坑比提示词多。最初我以为 temperature 越高越好,因为"多样性更足",结果拉到 1.2 之后,模型开始大量胡说八道,正确路径的比例低到投票救不回来。后来又试过 0.2,路径全一样,等于多花 N 倍钱跑了一遍。

我现在的常用区间是temperature 0.5 到 0.8,top_p 0.9 到 0.95。这个区间的效果是最稳定的,既能保证路径之间有足够的措辞和思路差异,又能保证单条路径的质量不塌。对于数学类任务我偏 0.6,对于逻辑推理、多跳问答我偏 0.7,对于选择题我偏 0.5,因为这些任务的答案空间小,路径太野反而容易跑偏。

采样代码我用最简单的同步版本起步,跑通再上并发:

import re from concurrent.futures import ThreadPoolExecutor def sample_one(client, prompt, temperature=0.7, max_tokens=1200): resp = client.complete( prompt=prompt, temperature=temperature, top_p=0.95, max_tokens=max_tokens, stop=["\n\n\n"], ) return resp.text def sample_many(client, prompt, n=8, temperature=0.7, workers=4): results = [] with ThreadPoolExecutor(max_workers=workers) as pool: futures = [ pool.submit(sample_one, client, prompt, temperature) for _ in range(n) ] for f in futures: try: results.append(f.result()) except Exception as e: print(f"单次采样失败,已跳过: {e}") return results

并发数要跟你的接口限流匹配。我一般的经验是:并发数不超过你配额上限的 70%,留一点余量应对突发的重试。另外一定要收集异常而不是让它直接抛出去,N 条路径里丢个一两条不影响投票,但如果一条失败整批崩掉,评测跑一晚上全废。

提示:如果接口支持一次返回多个候选(很多 SDK 支持传 n 参数),优先用它。一次请求内返回的结果共享同一次前缀计算,token 成本比 N 次独立请求低不少,我在一个长 prompt 的场景里实测能省下将近四成的开销。

3.3 答案归一化:这里翻车的概率比你想的高

这一步是整个流水线里最枯燥、最容易出事、也最考验细节的地方。我把它拆成三层处理。

第一层是抽取。用正则从完整输出里抠出"最终答案:"后面那一段。如果抠不到,降级处理:找输出里最后一个数字、或者最后一个匹配选项字母的字符。降级策略一定要有,因为总有一两条路径不听话。

第二层是清洗。去掉首尾空白、去掉中文标点、去掉货币符号、去掉"元""个""米"这类单位后缀。如果答案是数字,尝试转成浮点;如果模型写了"一百二十",还得考虑要不要做中文数字转换。

第三层是等价映射。这是最容易忽略的一层。比如你问"哪个选项正确",模型可能输出"A"、"a"、"选项 A"、"(A)",这四种其实就是同一个答案。你问"总价多少",模型可能输出"120"、"120.0"、"120元"、"$120",这四种也应该归为一类。做一层映射字典或者规则,把它们统一。

我见过有人在这一层偷懒,结果投票统计里 8 条路径分成了 6 个"不同"的答案,最高票只有 2 票,聚合出来的结论几乎是随机的。回头一看,其实有 5 条路径说得是同一个答案,只是写法不同。

def normalize(raw): if not raw: return None s = raw.strip() s = re.sub(r"^[\s\-\*\>]+", "", s) s = re.sub(r"[。,,\.!!;;::]+$", "", s) m = re.search(r"-?\d+(?:\.\d+)?", s.replace(",", "")) if m: v = float(m.group()) if v.is_integer(): return str(int(v)) return f"{v:.6f}".rstrip("0").rstrip(".") m = re.search(r"[A-Da-d]", s) if m: return m.group().upper() return s.lower()

这段代码不长,但它是我在实践里反复调整出来的,覆盖了数字、小数、选项字母、其他文本四类情况。你可以拿去当起点,然后针对自己任务的特点往里加规则。

3.4 投票聚合与平票处理

聚合这一步代码最短,但平票的处理策略要想清楚。最简单的是用collections.Counter,取最高票。问题是最高票并列怎么办。

我试过三种策略,各有适用场景:

  • 补采样:平票时再追加采样几条,直到出现唯一最高票或者达到上限。准确率提升最明显,成本最不可控。
  • 取最先出现的:把平票的候选中,按第一次出现的顺序选。实现最简单,但结果带有采样顺序的偏好,不够公平。
  • 降级重跑:平票直接判定为"无法确定",降级到更严格的提示词或者人工审核。这种做法在评测场景里最诚实,因为它不会给你一个假象。
from collections import Counter def vote(answers): answers = [a for a in answers if a is not None] if not answers: return None, 0.0 c = Counter(answers) top = c.most_common() best_count = top[0][1] winners = [k for k, v in top if v == best_count] conf = best_count / len(answers) return winners[0] if len(winners) == 1 else None, conf

我现在的默认策略是:置信度低于 0.4 的样本打上标记,进入二次复核队列,不直接把最高票当正确答案。这套做法在一个数据标注项目里帮我拦下了大约 12% 的可疑样本,人工复核后发现其中超过一半确实是模型判断出了问题。这个数字比任何参数调优都更有价值。

4. 路径数与温度:调参的边际收益曲线

4.1 路径数的边际收益并不是线性的

路径数 N 是这个方法里最直观的可调项,也是最容易浪费预算的地方。我在一批数学应用题上做过一条完整的曲线,样本量大概几百题,用同一个模型、同样的提示词,只改 N。几个关键点大致是这样的:N=1(就是单次思维链)准确率在 65% 左右;N=3 提到 76%;N=5 到 81%;N=8 到 85%;N=12 到 86%;N=20 到 87%;N=40 到 87.5%。

看这条曲线你会发现一个很明显的拐点:从 1 到 5 的收益占了全部收益的绝大部分,从 5 到 20 加了三倍成本只换来不到 6 个点,从 20 到 40 基本是一条横线。所以我的默认值是 N=5 到 8。这个区间性价比最高,投一条也不会太心痛。

不同任务的拐点位置不一样。逻辑推理、多跳问答这类任务的拐点偏右一点,N=10 左右才收敛;简单分类、选择题的拐点偏左,N=3 到 5 就够了。我的建议是先在任务的一个小样本上(50 到 100 条)跑一条 1、3、5、8、12、20 的曲线,找到你自己的拐点,再上全量。这半小时的投入能帮你省掉后面大量的预算。

还有一个反直觉的观察:N 越大,方差越小,但"系统性错误"也越稳定。也就是说如果模型在这个任务上存在某种系统性偏差(比如总是把某个类型的单位搞错),N=40 之后你会得到一个极其稳定的错误答案。这时候你会误以为方法失效了,其实是任务本身需要先修正提示词或者补样例。

4.2 temperature 的两难:低了没信息,高了没质量

temperature 和路径数是一对相互作用的参数,不能分开调。我用一个二维的粗略实验总结了一张对照表:

temperature \ NN=3N=8N=20
0.2路径几乎相同,准确率≈单次微弱提升浪费预算
0.5提升明显接近最优接近最优
0.7提升明显最优附近最优附近
1.0提升不稳定有可能反降大幅波动
1.2大概率反降明显反降明显反降

从这张表能看出两件事。第一,温度太低时增加路径数没有意义,因为路径本身是同质的,投 20 次等于投 1 次。第二,温度太高时增加路径数反而有害,因为高温度的路径里有相当比例是胡言乱语,这些乱语虽然不会形成共识,但它们抢占了采样预算,稀释了有效路径的比例。

我个人的固定组合是temperature=0.7 配 N=8,作为大多数任务的起点。这个组合我在数学、逻辑、多跳问答、代码调试几个不同任务上验证过,跑出来的结果都在各自任务的最优附近,误差可以接受。如果你的任务是选择题这种答案空间很小的,可以降到 0.5 配 N=5;如果你的任务是开放式的结构化输出,温度反而要更低一点。

4.3 任务类型对方法的适配度差异

不是所有任务都值得上自我一致性。我把它分成三档,这张表是我在不同项目里摸出来的经验总结:

任务类型适配度原因
数学计算、多步算术高答案唯一可验证,正确路径天然多条
逻辑推理、多跳问答高推理链长,单次方差大,正确路径集中
选择题、分类任务中高答案空间小,但通常单次准确率已经很高,收益有限
代码生成中可以按测试通过率投票,但"一致"不等于"正确"
信息抽取、结构化输出中需要极强的归一化,字段对齐麻烦
开放式写作、创意生成低答案空间无限,聚合退化到"最平庸版本"
长文档总结低同上,且单次成本已经很高

对于最后一类的对比,我印象很深。有一次我在一个文案改写任务上试自我一致性,本来想的是"多个版本投票取共识",结果跑出来的"共识版本"比任何一条单次输出都要平淡,因为最保守、最没风险的表达方式路径最多。这提示了一个根本性的道理:自我一致性假设"多数即正确",这个假设只在答案有客观对错的任务上成立。创意类任务里"多数"恰恰意味着"最不出彩"。

5. 投票结果和直觉打架:四条排查链路

5.1 第一条链路:投票异常,先查答案抽取这一步

这条链路是我遇到频次最高的问题。现象是:明明人工看一眼就知道答案是对的,但投票结果给了一个看起来毫不相干的答案,或者置信度极低。

排查顺序我固定成这样:先把所有 N 条路径的原始输出、抽取结果、归一化结果并排打出来,人眼看一遍。这一步能解决 70% 的问题。常见的情况有几种:有两条路径的答案是"120 元",被抽成了"120";有两条是"约 120",没被抽出来因为正则只认数字开头;还有一条模型在答案行写了"最终答案:120(单位:元)",括号把后面全吃进去了。

我遇到过一个特别典型的案例:题目问的是"需要多少分钟",模型给出的答案有的是"45 分钟",有的是"0.75 小时",有的是"45"。三种写法归一化后变成三个不同的字符串,8 条路径最高票只有 3 票。修好归一化之后,7 条路径统一到了"45",置信度从 0.375 跳到 0.875。

有个土办法很有效:打印前,先对 N 条答案做一次set(),看看有多少个不同的值。如果不同值的个数接近 N,那几乎肯定是归一化出了问题,不是模型的问题。

修好归一化之后如果还不对,再看是不是提示词里对答案格式的要求不够死。有时候模型"体贴"地补充了一句说明,把格式搞乱了,这时候就要在提示词里把"只输出答案本身,不要任何附加说明"这句话加粗强调,或者干脆用分隔符做硬性限制。

5.2 第二条链路:所有路径错得一模一样

现象是:置信度极高(比如 8 条全票同一个),但人工验证是错的。这种结果最坑人,因为它看起来特别可信。

我的第一条判断标准是:如果 N 条路径不仅答案一样,连推理过程都高度雷同,那大概率是模型的知识或者理解出了问题,与采样无关,加大 N 完全没用。这时候要往回查的是提示词里有没有引入误导。我踩过两个具体的坑:一个是题目里的术语有歧义,模型统一按错误的理解做了;另一个是我在提示词里给了一个相似示例,示例的表达方式和本题略有差异,模型照搬了示例里的思路。

还有一种情况是任务本身超出了模型能力。我早期用它跑一批很偏门的专业题目,模型在零样本下几乎没有相关知识,采样出来的答案全是同一种"编"。这种问题靠 Prompt 工程解决不了,得补上下文(RAG)或者换更强的模型。

诊断方法很简单:拿一道你确定模型有能力做对的题目,跟这道出错的题目放在一起对比跑,如果前者表现正常、后者全票错误,基本可以定位在任务难度或者提示词歧义上。如果两者一起错,那可能是提示词模板本身有系统性缺陷。

5.3 第三条链路:看似在投票,其实路径并不独立

这条链路最隐蔽,因为它不出现在单条输出里,而是出现在整批数据的一致性上。现象是:跑出来的置信度普遍偏高(都在 0.8 以上),但准确率不涨,甚至比单次还略降。

我排查到这个问题的过程有点曲折。当时我为了省事,把 N 条路径的调用放在一个带缓存的客户端后面,以为缓存只在相同请求时生效、不会有影响。结果那个客户端按前缀做缓存,N 条路径只有最后一句随机措辞不同,前缀全同,命中了同一次计算结果,返回的其实是同一份文本的低配版本。相当于花了 N 倍的钱,跑的都是同一条路径。

排查这一类问题我总结出三个检查点:

  • 检查请求的完整参数:除了提示词以外,随机种子、temperature、top_p 是不是真的每次都传了,有没有被默认值覆盖
  • 检查客户端缓存:如果用了 CDN 或者代理层缓存,确认缓存键包含所有可变参数
  • 检查提示词模板:N 条路径是不是真的只差随机性,如果你给每条路径塞了不同的小标题或者不同的引导语,那就不是在跑自我一致性了

自检的办法也很直接:对 N 条输出做一次相似度计算(比如简单的 n-gram 重叠),如果平均重叠度高到 0.9 以上,路径显然不够独立,要么调高温度,要么检查上面三个点。

5.4 第四条链路:正确路径存在,但概率质量太分散

这条链路是最讲究的一条,因为它不是 bug,是这套方法的固有局限。现象是:N 条路径里确实有 2 到 3 条走对了,但也确实有更好的几条走了不同的弯路,导致正确路径的票数不够集中,输掉了投票。

举个例子,一道有多种解法的几何题,正确的两条路径一个用三角恒等式、一个用坐标法;错误的两条路径一个算错符号、一个漏了一个条件;还有三条路径走的是"看起来像那么回事"的近似估算。三条路径各投了不同的错答案,两条正确路径也只能拿到 2 票,最后输给了一个 3 票的错误答案。

应对这种情况我能想到的招有两个。第一个是在提示词里做思路引导,让模型优先走某一条确定可靠的路:比如加上"请优先使用代数方法"这种约束,把路径收窄到一条质量最高的主干上。这样做的代价是牺牲了一部分多样性,但准确率通常更稳。第二个是N 拉大,从 8 拉到 15 到 20,正确路径的票数有更大概率浮出水面。我一般先试思路引导,因为它成本低;思路引导搞不定再拉大 N。

6. 成本账与替代方案:什么时候不值得用

6.1 算一笔成本与延迟的真实账

自我一致性的成本是一个 1 乘以 N 的放大。你跑 8 条路径,token 成本就是单次的 8 倍,这是账面上能看到的。真正容易被忽略的是延迟:如果串行跑,8 条路径的响应时间就是单次的 8 倍,一个原本 3 秒返回的请求变成 24 秒,这在实时场景里是灾难。

并发能把这个时间压下来。8 条并发、每条约 3 秒,总体响应时间大概在 4 到 5 秒之间(首尾加中间的一点调度开销)。但如果你的接口配额有限,并发上不去,那延迟就会爆炸。

方案相对成本相对延迟(并发)准确率相对单次
单次思维链1x1x基准
N=3 自一致性3x1.1x+8 个点(经验值)
N=5 自一致性5x1.2x+14 个点
N=8 自一致性8x1.3x+18 个点
N=20 自一致性20x2x+20 个点

(表里的百分比是基于我跑过的几个数学和逻辑类任务的均值,不同任务差异很大,只能看相对趋势。)

看这张表你会发现,N 从 5 到 8 增加了 60% 的成本,换来的准确率提升不到 4 个点;N 从 8 到 20 增加了一倍多的成本,几乎没提升。所以除非任务对准确率极度敏感,N=5 是大多数人最优的选择。我现在的默认配置就是 N=5 打底,碰到个别困难样本再触发补采样到 N=12。

6.2 这些场景下用了纯属浪费

我有几类任务是明确不上自我一致性的,摆在明面上说清楚:

第一类是单次准确率已经稳定在 95% 以上的任务,比如给一句短文本做情感极性判断、判断两个句子是否语义等价。这类任务的方差本来就小,投票等于用 N 倍成本换千分之几的提升,纯纯浪费。

第二类是低延迟场景,比如在线对话、实时搜索。用户等不了 5 秒,你只能走单次。

第三类是开放式生成,前面已经分析过,投票会让结果趋平庸。

第四类是长文档处理,单次成本已经很高,N 倍之后账单会失控,且长文档里的信息互相干扰,路径之间的差异往往来自"关注到了不同的段落"而不是"推理过程不同",投票在这里失去了意义。

第五类是探索性任务,比如让模型列举十种可能的解题方向、十种可能的产品定位。这类任务你要的是多样性,投票恰恰会把多样性抹平。

6.3 和思维链、自反思、best-of-n 的组合关系

最后梳理一下这套方法在 Prompt 工程工具箱里的位置,很多人容易把它们搞混。

思维链是自我一致性的地基,没有思维链,自我一致性就失去了可投票的中间过程,不知道路径长什么样,也就没有"多条路径"这回事。实践上通常是把两者叠起来用,也就是常说的"带思维链的自一致性"。

自反思(Self-Refine / Reflexion)是另一个方向,它的做法是让模型先给一个答案,再自己检查,发现问题就改。这套方法省成本(通常是两三轮),但它的假设是"模型能看出自己的错误",而这个假设在实践中经常不成立——模型对自己犯的错误往往格外宽容。我在几个任务上对比过,自反思的提升不如自一致性稳定,尤其是在推理类任务上。

best-of-n 加验证器是另一个思路,多生成几个候选,然后用一个独立的验证手段(可能是另一个模型、可能是执行测试用例、可能是规则检查)挑出最好的那个。这套方法在代码生成上特别有效,因为你可以真的跑一遍测试。但它需要额外构建验证器,工程投入比自我一致性大得多。

帮我选一条路线的简单经验:任务有可执行验证(代码、SQL、可跑的计算)就走 best-of-n 加验证器;任务只有文本答案、且模型有基本能力做对,就走自一致性;任务需要探索不同可能性,就什么投票都别用,直接跑多次采样让用户挑。

我在几个不同规模的项目上跑下来,最深的一个体会是:自我一致性不是让模型变聪明,而是让模型的输出变得可度量。它把"这个模型靠不靠谱"从一个主观判断,变成了一个置信度数字。一旦你有了这个数字,就可以做很多工程上的事:置信度高的自动通过、置信度低的进人工队列、整体置信度分布可以当质量监控看板、置信度的变化可以当回归测试的告警。这套东西真正的价值,不在于那十几个点的准确率提升,而在于它把不确定的东西变成了一个可以在流水线里被处理的对象。

至于什么时候该停手不用它,我的判断很简单:当你发现加了自我一致性之后,置信度一直在 0.9 以上而准确率没什么变化,那就是该把注意力从采样挪到提示词和上下文的时候了。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进