2712 字
约 9 分钟
13
S19-X19 · 推理时策略:Chain-of-Thought、Self-Consistency 与 Self-Refine 的成本曲线

同一份权重,换个用法能差出十几分,也能差出十倍成本。推理时策略(test-time 策略)就是这样一类东西:不改模型,只改"怎么问、问几次、要不要它自己检查"。麻烦在于三种策略的成本结构完全不同——Chain-of-Thought 加长输出,Self-Consistency 乘以调用数,Self-Refine 乘轮次,三者叠起来是乘积关系。先把这笔账算清楚,再决定开哪一档。


1. 推理时算力花在哪三个地方

维度 手段 成本增长方式 收益上限
输入侧 示例、上下文、检索片段 线性(输入 token) 任务定义清晰度决定
输出侧 Chain-of-Thought、结构化草稿 线性到数倍(输出 token) 有推理链可分解的任务
采样侧 Self-Consistency 多票 乘以调用数 有唯一正确答案的任务
轮次侧 Self-Refine 迭代 乘以轮次 有可判定反馈的任务

四条加起来,最坏情况是 输入 × 调用数 × 轮次。这个乘积是所有"推理成本失控"事故的共同形状。


2. Chain-of-Thought:它买到的是"可中断的中间状态"

CoT 有效的机制解释:让模型把中间量写进上下文,后续 token 就能以这些中间量为条件,而不是在单步里同时完成"回忆 + 计算 + 组织答案"。它换来的是可分解性,不是"更聪明"。

COT_TMPL = """按下面四步作答,每步一行,最后一行只输出结论:
1) 已知条件:从题面摘出全部给定值与单位
2) 目标:要求的是什么,单位是什么
3) 推导:写出用到的关系式,逐步代入
4) 结论:只写最终值,带单位

题面:{q}"""
任务特征 开 CoT 理由
多步算术、单位换算 是 中间量必须落盘
规则条文的多条件判断 是 条件逐条对齐
单跳事实问答 否 只增加 token 与幻觉面
格式转换(JSON/表格) 否 结构约束已足够
主观写作、摘要 视风格 推理链通常无害但无用

一个反直觉的实测倾向:CoT 会放大"题目本身有歧义"的影响。模型会把歧义沿着推理链一路放大,最后给出一个看起来很严谨的错误答案。歧义题的正确处理是先追问澄清,不是加推理链。


3. Self-Consistency:票数与温度是同一个旋钮的两半

Self-Consistency 的做法:同一问题采样 N 条推理链,对最终答案取多数。它成立的前提有两个——答案可归一化(能判等)、错误分布不是系统性同向。

from collections import Counter

def self_consistency(q, llm, n=5, temperature=0.7):
    answers = []
    for _ in range(n):
        chain = llm(COT_TMPL.format(q=q), temperature=temperature)
        answers.append(normalize(chain.strip().splitlines()[-1]))
    top, cnt = Counter(answers).most_common(1)[0]
    return top, cnt / n          # 一致率是重要信号,不只是副产品
票数 N 相对成本 典型收益 备注
1 1× 基线 等同单次 CoT
3 3× 多数场景的主要收益在此 性价比拐点
5 5× 少量提升 一致率低于 0.6 时别再加
9+ 9× 以上 通常饱和 收益被生成方差吞掉

两个必须注意的点:

  • 温度太低时多票没有意义。temperature=0 采样出的链几乎相同,等于用 5 倍成本做一次调用。
  • 一致率要当指标看。一致率低于 0.6 说明题目本身歧义或模型没有该能力,加票只是把错误答案投票得更自信。
import re
UNITS = ("元", "美元", "kg", "公里")

def normalize(ans):
    a = ans.strip().replace(",", "").replace(",", "")
    a = re.sub(r"\s+", "", a)
    for u in UNITS:
        a = a.replace(u, "")
    # 数值型统一到同一精度,文本型转小写去标点
    try:
        return f"{float(a):.4g}"
    except ValueError:
        return re.sub(r"[。..!!??]", "", a).lower()

答案归一化是最容易漏的一步:1,200、1200元、1200 是同一个答案,不归一化就变成三票分散。


4. Self-Refine:收益全在"反馈是否可判定"

Self-Refine 是"生成 → 自我批评 → 修订"的循环。它能起作用的唯一条件是批评环节引入了新信息:要么来自外部校验器(编译、单测、schema 校验),要么来自明确的评分标准。

def self_refine(task, llm, check, max_rounds=3):
    draft = llm(task)
    for r in range(max_rounds):
        ok, report = check(draft)          # 可判定反馈:单测 / schema / 规则
        if ok:
            return draft, r
        new = llm(f"{task}\n\n上一版问题:{report}\n只修这些问题,不要重写:\n{draft}")
        if new.strip() == draft.strip():    # 修订无效,早停
            return draft, r
        draft = new
    return draft, max_rounds
反馈来源 可判定性 典型轮次收益 风险
单测 / 编译错误 强 第 1 轮最大,第 2 轮递减 修一处坏一处
JSON schema 校验 强 通常 1 轮解决 语义正确但结构对
规则清单打分 中 2–3 轮 评分器自身偏差被放大
模型自我批评 弱 常为 0 甚至负 越改越偏离

弱反馈下的 Self-Refine 是典型的"看起来在工作":每轮都有改动,质量不动甚至下降。判断方法很简单——把轮次从 3 降到 1,指标如果没有明显变化,那两轮就是白花钱。


5. Thought-Action-Observation 与 CoT 的分界

CoT 是"想",Thought-Action-Observation 是"想一步、做一步、看结果"。区别在于信息是否来自环境。

维度 Chain-of-Thought Thought-Action-Observation
信息来源 模型内部知识 工具返回值、检索结果
可验证性 事后才能验 每步都有环境返回值可验
失败形态 推理链自洽但错误 动作失败、参数错、工具超时
停止条件 输出结论 结论 + 动作预算耗尽
成本 输出 token 输出 token + 工具调用次数

判据:需要环境里的事实就走向 Observation,纯靠已知条件推就走 CoT。用 CoT 猜数据库里的值,是最常见的误用。


6. 成本账:三种策略的乘法关系

# 粗略成本估算:in/out 单价按供应商单价表核对,此处只示范结构
def cost(in_tok, out_tok, calls, price_in, price_out):
    return calls * (in_tok * price_in + out_tok * price_out)

base = dict(in_tok=800, out_tok=200, calls=1)
cot  = dict(in_tok=800, out_tok=900, calls=1)              # CoT 拉长输出
sc   = dict(in_tok=800, out_tok=900, calls=5)              # 5 票
both = dict(in_tok=800, out_tok=900, calls=5)              # 5 票 + 2 轮修订
print(cost(**base, price_in=1e-6, price_out=4e-6))
print(cost(**both, price_in=1e-6, price_out=4e-6) * 2)
组合 相对成本 适合
单次回答 1× 高频、可容错
CoT 约 2–4× 多步推导
CoT + 5 票 约 10–20× 离线批处理、准确率优先
CoT + 5 票 + 2 轮 Refine 约 20–40× 只在有强校验器时值得

表中的倍数是结构推演的量级,具体倍数按实际 token 分布与单价核对。能承受的只有离线任务与低 QPS 场景;在线高并发链路上开满三件套,账单会先于质量出事。


7. 路由:不是所有请求都配得上重推理

请求特征 策略档位 判据
短问答、FAQ 命中 直答 + 检索 有现成答案,推理只添噪声
需要一步计算的业务问题 CoT 单次 中间量必须落盘
有唯一标准答案、可离线 CoT + Self-Consistency 答案可归一化比较
有编译/单测/校验器 Self-Refine + 强反馈 反馈可判定
需要外部事实 Thought-Action-Observation 事实不在权重里

路由实现上,先做一个便宜的分类器(规则 + 小模型),把请求分到档位,再按档位设不同的超时与预算上限。没有预算上限的推理策略一定会被长尾请求打穿。

TIERS = {                      # 每档:票数、轮次、输出上限
    "direct":   dict(n=1, rounds=1, max_out=300),
    "cot":      dict(n=1, rounds=1, max_out=1200),
    "vote":     dict(n=5, rounds=1, max_out=1200),
    "refine":   dict(n=1, rounds=3, max_out=2000),
}

def route(req):
    if req.hit_cache:                     return "direct"
    if req.has_verifier and req.is_code:  return "refine"
    if req.answer_is_unique:              return "vote"
    return "cot" if req.needs_calc else "direct"

8. 排错:六种"策略开了但没用"

现象 原因 处理
开 CoT 后准确率下降 题目有歧义,推理链放大偏差 先澄清,再推理
多票结果与单票相同 温度过低,采样退化 提高温度,或直接省掉多票
一致率始终低于 0.6 答案无唯一形式或超出能力 做答案归一化,或降级为"给候选"
Refine 越改越差 反馈来自模型自评 换强校验器,或把轮次设为 1
成本涨了 10 倍,指标涨 1 点 档位路由缺失 按请求特征分档,设预算上限
推理链中突然跳到结论 输出长度上限截断 提高上限或用外置草稿(文件/变量)

9. 边界:推理时策略改不了的东西

问题 该动的地方 为什么加推理无用
知识缺失 检索、微调 推理不能凭空造出事实
工具不可用 工具实现与权限 动作失败与推理深度无关
任务定义模糊 需求澄清、评测集 策略只放大已有定义
上下文超窗 分块、压缩、外置记忆 是被截断,不是没想清楚
延迟硬约束 模型路由、缓存 重推理天然增加延迟

10. 自测

  1. Chain-of-Thought 与 Self-Consistency 的成本分别按什么方式增长?两者叠加是什么关系?
  2. temperature=0 时做 5 票 Self-Consistency,问题出在哪?
  3. 一致率 0.55 说明什么?继续加票能解决吗?
  4. Self-Refine 在什么条件下才有正收益?如何用一次实验判断那两轮是否白花钱?
  5. 什么时候必须用 Thought-Action-Observation 而不能用 CoT?
  6. 给在线高并发链路设计推理档位路由时,除了任务特征还必须限制什么?
S19-X19 · 推理时策略:Chain-of-Thought、Self-Consistency 与 Self-Refine 的成本曲线
https://www.clxhxhhr.top/posts/6011/
作者
clxstart
发布于
2024-12-31
许可协议
CC BY-NC-SA 4.0
评论
0 条
还没有评论,先写一条吧。