做大模型应用开发的团队,几乎都在同一个地方吃过亏:模型输出的稳定性。同一个问题,跑一次和跑三次,结果可能有差异,有时候差异还非常大。为了拿到可靠答案,最常见的做法就是多次采样、多数投票,也就是论文里常说的 best-of-n 或 self-consistency。这个思路本身没问题,问题在于大多数实现把它做成了“均匀烧钱”:所有问题固定采样 N 次,和业务难度、模型状态完全脱节。
Test-Time Scaling(测试时缩放)最近成了大模型推理领域的热词,核心意思是:在推理阶段投入更多计算,换取更高质量的答案。o1 类模型把思维链推理拉长,本质上也是这个路线。但如果放大到生产系统,Test-Time Scaling 有一个绕不开的坎:计算量不是白来的。同样的接口调用量,采样次数翻倍,GPU 成本差不多就翻倍,延迟也跟着翻倍。很多团队因此不敢放开推理预算,宁可接受偶发错误。于是问题变成了:有没有一种方式,能在必要的时候多花算力、在没必要的时候少花算力?这正是 adaptive sampling(自适应采样)要回答的。
本文围绕“Interpretable Adaptive Sampling for LLM Test-Time Scaling”这个主题展开,先讲清楚 Test-Time Scaling 为什么值得关注,再拆解固定采样为什么浪费,然后给出一套带可解释性的自适应采样实现,包括算法设计、Python 代码、配置文件和常见坑。看完可以直接拿去跑一个原型,也可以结合自己的 LLM 应用开发框架去改造。全文的核心判断是一个:固定次数采样在工程上是均匀烧钱,可解释的自适应采样才是测试时缩放落地生产的更优解。
1. Test-Time Scaling 是当前大模型推理的重要变量
训练阶段的规模法则大家已经很熟悉:模型参数量越大、训练数据越多,能力越强。但到了推理阶段,同样存在一种“花钱买效果”的路径:让模型在回答之前多做几步计算,例如生成更长的思维链、对同一个问题采样多个候选答案、让模型自我检查并修订。这类做法统称为 Test-Time Scaling。
它最近被重视,直接原因是模型参数规模的增长遇到瓶颈,而推理侧还留有大量可优化的空间。OpenAI o1 系列出现后,行业意识到一件事:同一个基础模型,在推理时让它“多想一步”,复杂任务上的表现可以提升一截。这相当于把一部分能力提升从训练阶段搬到了测试阶段。
但工程上有一个残酷的现实:Test-Time Scaling 的成本非常透明。假设一个请求原来需要 500 个 token,如果把它扩展为 5 次采样,就是 2500 个 token,用户延迟从几百毫秒变成几秒,GPU 占用率同步上升。这不是论文里一句话能带过的,而是每天在账单上能看到的东西。
所以要讨论 Test-Time Scaling,不能只讨论“加算力有用”,还要讨论“如何加算力才划算”。这也是自适应采样出现的原因:它把有限的推理预算按需分配到真正困难的问题上,而不是对每个请求一视同仁。
2. 固定采样为什么浪费推理预算
先看固定采样在做什么。很多团队上线 LLM 服务时,会在代码里写死一个参数:n = 5或n = 10。每次用户请求,模型生成 N 个候选答案,然后用规则选一个最稳的。这种做法在离线评测里通常表现不错,因为评测集里的问题难度差异不大,而且评测主要看整体准确率。
到了线上,问题就变了。真实请求的难度分布极不均匀:有些问题是“今天天气怎么样”这类一眼能答对的,有些是“解释这段日志为什么报错”这类需要多步推理的。固定采样对前者浪费,对后者可能还不够。
用一个量化的角度去看:假设线上有 1000 个请求,其中 600 个简单问题采样 3 次就能稳定,300 个中等难度问题采样 8 次能稳定,还有 100 个困难问题采样 15 次仍然在边缘。如果固定采样 10 次,简单问题多花了 7 次的计算量,困难问题不够用。整个系统为了覆盖最难的 10% 请求,让所有请求都背上高成本。
为什么团队还是愿意用固定采样?因为它简单、可预期、好实现。没有人愿意为了“动态调整次数”去承担额外复杂度。但随着推理调用量上来,这个浪费会变成一笔不小的账单。固定采样的本质问题不是策略错,而是粒度太粗:它把所有问题当成同一种难度来对待。
3. 自适应采样的核心思想与停止准则
自适应采样的思路并不复杂:不要一开始就决定采样多少次,而是边采样边观察“模型对这个题目的确定性”。如果前几次采样返回的答案高度一致,说明模型在这个问题上比较有把握,可以提前停止;如果答案一直在飘,说明模型没底,继续采样直到预算上限。
这里面最关键的设计点是“如何度量确定性”。最常用的是多数票比例(majority ratio):把已经采样到的答案做一个频率统计,最高频答案的占比就是确定性。例如采了 5 次,其中 4 次答案相同,占比 0.8,说明一致性很高,可以停止。
另一个可选指标是熵(entropy)。答案分布越均匀,熵越高,说明模型越不确定;熵越低,说明答案越集中。熵的好处是对分布更敏感,但它需要至少多次采样后才有意义,而且它对答案类别数量敏感,需要做归一化。
再往下,还有更高阶的做法:用语义相似度替代精确字符串匹配。因为模型可能用不同的措辞表达同一个意思,字符串匹配会误判为“不一致”。不过语义相似度需要向量化计算,成本更高,通常作为进阶选项。
停止准则一般由两部分组成:
- 确定性阈值:当置信度达到某个值,比如 0.8,直接停止;
- 预算上限:无论置信度多低,最多采样 N 次,避免无限调用。
这个设计很像人的决策方式:看完前几个证据就能下结论的问题,不需要把所有证据都翻一遍;实在看不明白的问题,也不能无限纠结,要给自己设一个截止时间。自适应采样把这种决策方式搬到了系统里。
固定采样和自适应采样的对比可以这样理解:
| 维度 | 固定采样 | 自适应采样 |
|---|---|---|
| 采样次数 | 开始就固定 | 根据结果动态决定 |
| 简单问题 | 浪费计算 | 提前停止,省钱 |
| 困难问题 | 可能不够 | 尽量用满预算 |
| 实现复杂度 | 低 | 中 |
| 结果可解释性 | 弱,只有一个投票结果 | 强,保留完整采样轨迹 |
| 生产调参 | 只能调 N | 可调阈值、预算、指标 |
从这张表能看出来,自适应采样不是“不采样”,而是“合理采样”。它没有牺牲困难问题的预算上限,只是把简单问题多花的钱省下来。
4. “可解释”在采样系统里的三层含义
很多团队听到自适应采样,第一反应是“这不就是动态调次数吗,有什么难的”。但如果只做到动态调次数,上线时你会发现很难维护:为什么这个请求采了 3 次,那个请求采了 12 次?最终答案出错了,是采样策略的问题还是模型的问题?业务方问你怎么调整参数,你只能拍脑袋。
所以“可解释”不是附加项,而是自适应采样真正能落地生产的前提。我认为它至少包含三层含义:
第一层,过程可观测。每一次采样返回了什么答案,当时的置信度是多少,当前占多数的答案是什么,这些信息都要保留下来。这样你可以回放一次请求的完整决策过程,而不是只看一个最终结果。
第二层,决策可审计。系统停止采样时,要明确记录停止原因:是因为置信度达到了阈值,还是因为触发了预算上限。两种原因的语义完全不同,前者代表系统认为答案已经足够可靠,后者代表系统在预算内没有找到足够可靠的答案,最终答案可能风险更高。
第三层,参数可理解。stop_threshold = 0.8不是一个抽象数字,它应该能被所有人理解:系统认为,当 80% 的采样结果指向同一个答案时,可以停止。这个解释对工程师、产品经理、业务方都是成立的,方便跨角色沟通。
可解释性在排错场景的价值最明显。假设线上某个答案被判错了,有了采样轨迹,你可以立刻看到:是置信度在第一次采样后就虚高,导致系统过早停止?还是置信度一直很低,系统被迫跑满预算后给了个次优答案?这两种情况的处理方式完全不同。
在 LLM Agent 场景中,这一点更加重要。Agent 的每一轮工具调用都在消耗 token,如果决策过程是黑盒,成本出了问题根本没法追溯。可解释的自适应采样可以作为 Agent 判断“是否继续尝试”的通用机制,让每一轮决策都留下痕迹。
5. 算法设计与流程拆解
整个自适应采样器的输入输出可以设计得非常干净。
输入:
question:用户问题;config:包含最小采样数、最大采样数、停止阈值、确定性指标、采样温度等参数;llm_fn:一个调用大模型并返回文本答案的函数,通过依赖注入方式传入,方便替换不同模型服务。
输出:
final_answer:最终采用的多数据答案;confidence:最终置信度;total_samples:实际采样次数;stopped_by:停止原因,可能是confidence_threshold或max_samples;records:每一次采样的详细记录,用于可解释审计。
算法流程拆成六个步骤:
- 初始化:读取配置,清空答案列表和采样记录;
- 循环采样:调用
llm_fn获取答案,加入答案列表; - 检查最小采样数:如果当前采样次数小于
min_samples,不计算置信度,继续采样; - 计算确定性指标:根据配置选择
majority_ratio或entropy,计算当前答案集合的置信度; - 判断停止条件:如果置信度达到阈值,停止并记录
confidence_threshold;如果达到最大采样数,停止并记录max_samples; - 输出结果:取出最多数答案,连同置信度、采样次数、停止原因和采样轨迹一起返回。
这个流程有几个细节值得注意。
min_samples必须存在。如果最小采样数设为 1,那么第一次采样的置信度肯定是 1.0,系统会直接停止,自适应就退化成只采样一次,失去了多数投票的纠错能力。一般把min_samples设为 3 到 5 比较合理。
max_samples是安全网,必须有上限。即使置信度永远达不到阈值,系统也要在有限预算内停止,否则线上可能出现无限调用。
metric决定置信度的计算方式。majority_ratio直观、好解释;entropy更敏感,但在答案类别很多时表现更复杂。建议先用majority_ratio跑通,再根据业务需要尝试entropy。
6. 环境准备与依赖
下面要给出完整代码实现,先把运行环境准备好。本文代码使用 Python,建议 3.9 及以上版本。大模型调用统一封装成函数,示例代码使用 OpenAI 兼容接口做演示,实际项目可以替换成任意内部模型服务。
依赖建议安装两个库:
openai:用于调用大模型接口;pyyaml:用于读取配置文件。
创建虚拟环境并安装依赖:
python -m venv .venv source .venv/bin/activate pip install "openai>=1.0.0" "pyyaml>=6.0"项目目录结构建议这样组织:
adaptive-sampling-demo/ ├── config/ │ └── adaptive_sampling.yaml ├── src/ │ ├── adaptive_sampler.py │ └── llm_backend.py ├── experiments/ │ └── compare.py ├── main.py └── requirements.txt环境变量方面,需要配置大模型服务的访问信息:
export LLM_API_KEY="your-api-key-here" export LLM_BASE_URL="https://api.openai.com/v1" export LLM_MODEL="gpt-4o-mini"如果使用公司内部模型网关,把LLM_BASE_URL改成内部地址即可。这里要提醒一句:不要把 API Key 硬编码到代码或配置文件里,统一走环境变量或密钥管理服务,避免密钥随代码提交泄露。
7. 核心代码实现
下面开始写核心代码。先实现自适应采样器本身。
7.1 自适应采样器实现
文件路径:src/adaptive_sampler.py
import logging import math import time from collections import Counter from dataclasses import dataclass, field from typing import Any, Callable, Dict, List logger = logging.getLogger(__name__) @dataclass class SampleRecord: """一次采样的完整记录,用于可解释审计。""" index: int # 第几次采样 answer: str # 模型返回的答案 confidence: float # 本轮计算出的置信度 best_answer: str # 当前多数票答案 timestamp: float # 采样时间 @dataclass class AdaptiveResult: """自适应采样的最终结果与采样轨迹。""" question: str final_answer: str confidence: float total_samples: int stopped_by: str # confidence_threshold 或 max_samples records: List[SampleRecord] = field(default_factory=list) class AdaptiveSampler: """可解释的自适应采样器。 核心思想:边采样边计算答案集合的确定性指标, 确定性达到阈值则提前停止,否则直到预算上限。 参数说明: max_samples: 最多采样次数 min_samples: 最少采样次数 stop_threshold: 确定性阈值,达到后停止 metric: 确定性指标,可选 majority_ratio / entropy temperature: 采样温度 """ def __init__(self, llm_fn: Callable[[str, float], str], config: Dict[str, Any]): self.llm_fn = llm_fn self.max_samples = int(config.get("max_samples", 12)) self.min_samples = int(config.get("min_samples", 3)) self.stop_threshold = float(config.get("stop_threshold", 0.8)) self.metric = config.get("metric", "majority_ratio") self.temperature = float(config.get("temperature", 0.7)) def _confidence(self, answers: List[str]) -> float: """根据答案集合计算确定性指标,返回 0 到 1 的置信度。""" counter = Counter(answers) total = len(answers) if self.metric == "majority_ratio": return counter.most_common(1)[0][1] / total if self.metric == "entropy": entropy = 0.0 for count in counter.values(): p = count / total entropy -= p * math.log(p) max_entropy = math.log(len(counter)) if max_entropy == 0: return 1.0 normalized_entropy = entropy / max_entropy return 1.0 - normalized_entropy raise ValueError(f"不支持的 metric: {self.metric}") def _majority_answer(self, answers: List[str]) -> str: """返回当前答案集合中的多数票答案。""" counter = Counter(answers) return counter.most_common(1)[0][0] def sample(self, question: str) -> AdaptiveResult: """对问题执行自适应采样,返回最终结果与采样轨迹。""" answers: List[str] = [] records: List[SampleRecord] = [] stopped_by = "max_samples" for i in range(self.max_samples): answer = self.llm_fn(question, self.temperature) answers.append(answer) confidence = 0.0 if len(answers) >= self.min_samples: confidence = self._confidence(answers) best_answer = self._majority_answer(answers) records.append( SampleRecord( index=i + 1, answer=answer, confidence=confidence, best_answer=best_answer, timestamp=time.time(), ) ) logger.info( "sample=%d/%d confidence=%.4f best_answer=%s", i + 1, len(answers), confidence, best_answer, ) if len(answers) >= self.min_samples and confidence >= self.stop_threshold: stopped_by = "confidence_threshold" break final_confidence = self._confidence(answers) final_answer = self._majority_answer(answers) return AdaptiveResult( question=question, final_answer=final_answer, confidence=final_confidence, total_samples=len(answers), stopped_by=stopped_by, records=records, )这段代码有三个设计点需要展开说明。
第一个是llm_fn函数注入。采样器不直接依赖某个特定的模型 SDK,而是接收一个签名固定的函数。这样你可以在测试时传入模拟函数,在生产时传入真实模型调用函数,切换成本很低。
第二个是置信度计算的双指标支持。majority_ratio实现简单且好解释,适合大多数场景;entropy对答案分布更敏感,适合答案类别较多、需要更精细判断的场景。两个指标输出都被归一化到 0 到 1,便于后续替换为自定义 metric。
第三个是停止条件与日志。每个采样步骤都输出日志,包含采样次数、当前置信度和当前多数答案。这个日志是“可解释”的重要支撑,它让系统在运行时留下了完整的决策轨迹。
7.2 大模型调用封装
文件路径:src/llm_backend.py
import os from openai import OpenAI client = OpenAI( api_key=os.getenv("LLM_API_KEY", ""), base_url=os.getenv("LLM_BASE_URL", "https://api.openai.com/v1"), ) def chat_completion(question: str, temperature: float) -> str: """调用大模型生成答案,只返回文本内容。 参数: question: 用户问题 temperature: 采样温度 返回值: 模型生成的答案文本 """ response = client.chat.completions.create( model=os.getenv("LLM_MODEL", "gpt-4o-mini"), temperature=temperature, messages=[ { "role": "system", "content": "你是一个严谨的AI助手,请用简洁且完整的句子回答问题。", }, {"role": "user", "content": question}, ], ) return response.choices[0].message.content.strip()这个封装很薄,但有几个作用:统一管理 API 密钥、统一注入 system prompt、统一处理模型返回的文本提取。实际项目中,你可能还要在这里加超时控制、重试逻辑和 token 用量统计。
需要注意,temperature参数在自适应采样中很关键。如果温度太低,模型每次输出几乎一样,置信度会虚高;如果温度太高,答案过于发散,置信度很难达标。一般推荐从0.7开始调,再根据业务场景微调。
7.3 配置文件
文件路径:config/adaptive_sampling.yaml
sampling: strategy: adaptive metric: majority_ratio # 可选 majority_ratio / entropy min_samples: 3 max_samples: 12 stop_threshold: 0.8 temperature: 0.7 llm: base_url: ${LLM_BASE_URL} api_key: ${LLM_API_KEY} model: ${LLM_MODEL}配置文件把所有可调参数集中起来,方便离线实验和线上调参。stop_threshold是最需要根据业务调整的参数:业务风险越高,阈值应该设得越高;成本压力越大,阈值可以适当降低。
7.4 主流程入口
文件路径:main.py
import logging import yaml from src.adaptive_sampler import AdaptiveSampler from src.llm_backend import chat_completion logging.basicConfig( level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s", ) def load_config(path: str) -> dict: """读取 YAML 配置文件。""" with open(path, "r", encoding="utf-8") as f: return yaml.safe_load(f) def main(): config = load_config("config/adaptive_sampling.yaml") sampler = AdaptiveSampler(llm_fn=chat_completion, config=config["sampling"]) question = "用户反馈支付订单超时,请给出排查步骤和可能原因。" result = sampler.sample(question) print(f"问题:{result.question}") print(f"最终答案:{result.final_answer}") print(f"置信度:{result.confidence:.4f}") print(f"采样次数:{result.total_samples}") print(f"停止原因:{result.stopped_by}") print() print("采样轨迹:") for record in result.records: print( f" 第{record.index}次采样 | 置信度={record.confidence:.4f} " f"| 当前多数答案={record.best_answer[:30]}" ) if __name__ == "__main__": main()8. 运行、验证与效果评估
代码写完后,在项目根目录执行:
python main.py如果一切正常,你会看到类似下面的输出。注意实际数值取决于模型、任务和阈值配置,下面的内容只是用来演示输出格式:
问题:用户反馈支付订单超时,请给出排查步骤和可能原因。 最终答案:1. 先检查支付网关回调日志... 置信度:0.8667 采样次数:6 停止原因:confidence_threshold 采样轨迹: 第1次采样 | 置信度=0.0000 | 当前多数答案=1. 先检查支付网关回调日志... 第2次采样 | 置信度=0.0000 | 当前多数答案=1. 先检查支付网关回调日志... 第3次采样 | 置信度=0.6667 | 当前多数答案=1. 先检查支付网关回调日志... 第4次采样 | 置信度=0.7500 | 当前多数答案=1. 先检查支付网关回调日志... 第5次采样 | 置信度=0.8000 | 当前多数答案=1. 先检查支付网关回调日志... 第6次采样 | 置信度=0.8667 | 当前多数答案=1. 先检查支付网关回调日志...如何判断采样器工作正常?
第一看stopped_by的分布。跑一批测试问题后,如果大多数请求的停止原因都是max_samples,说明阈值可能设太高,或者模型在这个任务上的输出多样性太强,预算永远不够。如果绝大多数请求都是confidence_threshold,说明系统在提前停止方面是有效的。
第二看total_samples的均值。假设配置的最大采样次数是 12,固定采样模式下每个请求都消耗 12 次调用;自适应模式下如果平均采样次数只有 5 次,说明节省效果明显。但要记住,节省的前提是答案质量没有显著下降。
第三做离线对比实验。准备一组带有标准答案的评测集,分别跑固定采样和自适应采样,对比两边的准确率和平均采样次数。
# experiments/compare.py """ 对比固定采样与自适应采样在同一批问题上的表现。 注意:实际结果取决于任务、模型与阈值,本脚本只展示评估思路。 """ from typing import Callable, List from src.adaptive_sampler import AdaptiveSampler def run_fixed_sampling( llm_fn: Callable[[str, float], str], questions: List[str], n_samples: int, temperature: float = 0.7, ) -> List[dict]: """固定采样:每个问题采样 n_samples 次,取多数答案。""" results = [] for question in questions: answers = [llm_fn(question, temperature) for _ in range(n_samples)] best = max(set(answers), key=answers.count) results.append( { "question": question, "answer": best, "total_samples": n_samples, } ) return results def run_adaptive_sampling( llm_fn: Callable[[str, float], str], questions: List[str], config: dict, ) -> List[dict]: """自适应采样:按配置动态决定采样次数。""" sampler = AdaptiveSampler(llm_fn, config) results = [] for question in questions: result = sampler.sample(question) results.append( { "question": question, "answer": result.final_answer, "total_samples": result.total_samples, "stopped_by": result.stopped_by, } ) return results对比实验的核心指标有三个:准确率、平均采样次数、单次请求平均成本。准确率反映质量,平均采样次数反映效率,成本则把两者统一成财务语言。上线前至少要看一次这三个指标的联动关系,才能确定阈值调优方向。
这里还要多说一句,不要只看平均采样次数。有些请求可能只采了 3 次就停了,但答案是错的;这时平均次数好看,但质量塌了。所以在评估时,要按问题难度分层统计:简单问题提前停了多少次,困难问题是否用满了预算,两部分要分开看。
9. 常见问题与排查思路
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
所有请求都跑到max_samples才停止 | 停止阈值设置过高,或模型温度过高导致输出太发散 | 查看日志中置信度曲线,是否始终低于阈值 | 降低阈值,或降低采样温度,或检查 prompt 是否引导出多种解释 |
| 置信度在第一次采样后就达到 1.0 | min_samples设置过小,甚至为 1 | 检查配置中的最小采样数 | 将最小采样数设为 3 到 5,避免单次采样自证可靠 |
| 多次采样答案相同但语义不同,导致正确结果被误判 | 使用字符串精确匹配,没有做答案规范化 | 查看采样轨迹里是否出现意思相近但措辞不同的答案 | 增加答案归一化步骤,或用语义相似度替代精确匹配 |
| 最终答案错误,但置信度很高 | 模型在某个错误答案上自洽,属于模型盲区 | 回放采样轨迹,判断是否在第一次采样后就锁定了错误方向 | 降低温度增加多样性,或者加入验证节点,对最终答案做额外检查 |
| 成本没有明显下降 | 业务请求本身置信度容易达标,但阈值设太高,没触发提前停止 | 统计stopped_by的分布 | 调低阈值,或改用entropy指标提升敏感性 |
| 采样日志太多,影响日志系统 | 每次采样输出一行日志,高峰期量很大 | 查看日志平台是否丢数据 | 改为结构化 JSON 日志,并只保留置信度变化节点的关键信息 |
调大max_samples后延迟陡增 | 单次请求串行采样,次数越多延迟越高 | 查看请求平均耗时 | 将多次采样改为并发调用,但要注意 API 限流和线程池配置 |
上面这些坑里,最隐蔽的是“字符串精确匹配导致的语义误判”。大模型同一个意思有多种表达方式,订单状态变为已支付和订单已经支付成功在语义上等价,但字符串匹配会认为它们是两个不同的答案。于是真实的一致性被低估,系统可能会多采样很多次。
解决方式有两种。简单做法是加一道答案规范化步骤,把时间、金额、状态词统一映射成模板。进阶做法是在置信度计算时,先用 embedding 模型把答案向量化,再算两两相似度,把相似度超过阈值的答案归为同一簇。后者更稳,但要额外引入一个向量化调用。
另一个容易忽略的点是并发采样。本文主流程是串行采样,为了演示而设计。生产环境建议把llm_fn包一层并发调度,让多次采样并行执行,能在不牺牲总计算量的情况下降低延迟。但要注意,并发调用同一个大模型 API 时,要预留足够的并发配额,否则会触发限流。
10. 工程化落地的最佳实践
代码跑通只是第一步,真正放到生产环境还需要注意下面几条。
第一,先离线校准再上线。不要凭感觉设stop_threshold。准备一个和线上分布相近的问题集,跑几组阈值,比如 0.7、0.8、0.9,对比准确率和平均采样次数,找到性价比最高的点。阈值是质量和成本的杠杆,校准得越细,线上越稳。
第二,阈值与业务风险挂钩。不同业务对错误答案的容忍度完全不同。客服问答场景,答案错误可以靠人工复核兜底,阈值可以设低一点;风控审核场景,错误判断可能造成真实损失,阈值就要设高一点。同一个采样器,在不同业务下应该有不同的配置。
第三,max_samples必须作为费用上限存在。无论自适应策略多聪明,都要在配置里写死最大预算,避免极端情况下单次请求消耗几十次模型调用。这既是为了控制成本,也是为了防止模型在困难问题上反复横跳却始终无法收敛。
第四,答案规范化要在采样前设计好。多数投票的效果高度依赖答案的可比较性。建议在系统提示词里要求模型用固定结构输出,或者在后处理环节把答案拆成结构化字段。规范化做得好,多数投票才有意义。
第五,监控指标要围绕自适应策略重新设计。除了常规的请求量、延迟、错误率,还要增加三个指标:平均采样次数、阈值停止率、预算停止率