Skip to content

필사 모드: 推理强度不是选模型,而是逐请求决定的部署参数

中文
0%
정확도 0%
💡 왼쪽 원문을 읽으면서 오른쪽에 따라 써보세요. Tab 키로 힌트를 받을 수 있습니다.

同一个模型,页面上却挂着三个分数

新模型出来了,你去看基准测试结果。可分数不止一个,是三个。该往 Slack 里贴哪个数字?

这种情况现在已经不是例外,而更接近默认。随着可调推理强度的模型变多,一个模型开始以一条曲线而不是一个点的形式发布。而这个变化不是发布形式的变化,而是部署设计的变化

从前,选定模型就等于决策结束。定下用哪个模型,性能和成本也就一并定了。现在选完模型之后还剩一个决定,而这个决定会把性能和成本朝两个方向大幅摇动。把它当常量钉死在配置文件里,等于把这份摆动幅度全部放弃。

ARC 结果页实际告诉你的东西

我们看一下 ARC Prize 的 DeepSeek V4 Flash 0731 结果页。这是 2026 年 7 月 31 日公开的模型,三档推理强度各自都做了测量。

推理强度ARC-AGI-1ARC-AGI-2
Max89.0%61.4%
High87.0%56.0%
Low84.0%46.0%

成本只按最大强度标注:在 ARC-AGI-1 的半公开评测上每个任务 0.02 美元,在 ARC-AGI-2 上每个任务 0.04 美元。

强度买到的东西,随任务难度相差三倍

从这张表里能直接算出来一件事:把强度从最低调到最大所获得的收益。

在 ARC-AGI-1 上从 84.0 涨到 89.0,涨了 5.0 个百分点。在 ARC-AGI-2 上从 46.0 涨到 61.4,涨了 15.4 个百分点。同一个旋钮转了同样的角度,在难的那一侧多赚了三倍不止。

反过来读更实用。在简单任务上用最大强度,多半是浪费。 在 ARC-AGI-1 上,最低强度已经跑出了最大强度约 94% 的水平。反过来,在困难任务上用最低强度跑,则是大幅丢弃性能的选择。

所以「这个模型该用什么强度」这个问题,从形式上就问错了。强度不是模型的属性,而是要按任务的属性来定的值

「逐请求决定」意味着什么

在实务里,这个结论会变成路由逻辑,而不是配置文件里的一行。对每个进来的请求估计难度,然后挑强度。

难度估计不需要完美。对大多数服务来说,非常粗糙的信号也够用:输入长度、要求的步骤数、同类请求过去的失败率、用户明示的紧急程度等等。

不过,想一次就把这个估计做准,很快就会撞墙。一旦挂上一个预测难度的分类器,这个分类器本身就成了要维护的东西,分布一变就得重新训练。更结实的做法是不去估计,而是错了再往上调。用观测代替预测,几乎总是更耐用。

阶梯只有在有验证器时才成立

"""逐级上调阶梯:先便宜地试,确认错了再往上调。"""
from typing import Callable, Sequence

LADDER = ("low", "high", "max")


def solve_with_escalation(
    task,
    run: Callable[[object, str], object],       # (task, effort) -> answer
    verify: Callable[[object, object], bool],   # (task, answer) -> 是否通过
    ladder: Sequence[str] = LADDER,
):
    """一直往上调强度,直到验证器放行。同时返回实际用到的强度。"""
    attempts = []
    for effort in ladder:
        answer = run(task, effort)
        ok = verify(task, answer)
        attempts.append((effort, ok))
        if ok:
            return answer, effort, attempts
    return answer, ladder[-1], attempts   # 一路失败到底就返回最后一次尝试


def expected_cost(p_pass_by_effort: dict, unit_cost: dict, ladder=LADDER) -> float:
    """阶梯的期望成本。低档的通过率越高,均值越低。"""
    total, reach = 0.0, 1.0
    for effort in ladder:
        total += reach * unit_cost[effort]
        reach *= 1 - p_pass_by_effort[effort]
    return total


p = {"low": 0.80, "high": 0.60, "max": 0.50}     # 到达各档时的通过率
cost = {"low": 0.004, "high": 0.010, "max": 0.020}
print(round(expected_cost(p, cost), 5))          # 拿来和只用最大强度的情况比一比

这个结构的前提是 verify。验证做不到,阶梯就不成立,剩下的选项只有一开始就用高强度跑。所以想优化强度,先要问的问题不是「怎么挑强度」,而是有没有一种便宜的办法知道自己错了

还有一点要注意的是延迟。阶梯降低的是平均成本,抬高的是最坏情况的延迟。把三档全走一遍的请求,会比一上来就用最大强度跑的请求结束得更晚。所以在用户等着的同步路径上,需要把阶梯砍成两档,或者先把低档结果流式展示出来、再用上调后的结果替换掉。如果是批处理,这个顾虑就不必有。

造不出验证器时的替代信号

即便没有完整的验证器,局部信号通常还是能造出来的。

如果是代码生成,编译和跑测试本身就是验证器。如果是结构化输出,schema 校验和引用完整性检查能兜住相当一部分。如果是基于检索的回答,可以用字符串比对确认被引用的句子在原文里是否真的存在。

如果连这些都没有,是自由撰写类任务,那就把信号从结果里挪到过程里找。同一个输入用低强度跑两遍,两次答案差别很大的话,这个请求对这个模型来说多半就是难的。在「跑两遍」比「往上调一档」更便宜的区间里,这个办法是实用的。

如果把「答案不一样」这个判定又交回给模型,成本优势就没了。很多时候便宜的比对就够用:句向量的余弦距离,或者把答案里抽出来的关键数字和专有名词做集合比较。验证器不需要准确,只需要便宜,并且只朝一个方向出错。漏掉一些失败没关系,但把正确答案判成错误的情况必须很少,阶梯才不会白跑。

这个页面上确认不了的东西

有一处得老实点破。前面那张表里,按强度分的分数有三行,可成本只有按最大强度算的一行。低强度下每个任务的成本,在这个页面上是查不到的。

所以单凭这份数据,没法完整算出「每单位成本的边际精度」。上面代码里的单价只是为了展示结构的示例,真实数值要各自在自己的流量上去测。而且这与其说是这个页面的缺陷,不如说是当下整个行业的发布惯例。按强度分的分数是多了,按强度分的成本却还没有一起给出来。

测量并不难。拿自己评测集里的 100 条,分别用三档强度各跑一遍,把准确率和实际计费的 token 一起记下来,曲线就出来了。这个实验半天就能做完,之后所有关于强度的决定都有依据了。

还有一点要留意:基准测试的难度分布和我们自己流量的难度分布并不一样。ARC 的任务是刻意设计得很难的一组题目,而真实服务的流量通常是简单请求占压倒性多数的长尾分布。所以从上面那张表里该拿走的教训不是「我们也该用最大强度」,而是旋钮在不同难度区间里值多少钱本身就不一样这个事实。

要当成部署参数来对待,就得留下这些记录

最后是记录。一旦开始逐请求决定强度,那么不把强度写进日志的那一刻起,所有指标都变得无法解读。

至少把这四样东西和响应一起存下来:实际使用的强度、在阶梯上尝试的次数、每次尝试的验证结果,以及总消耗 token。再加上这个请求被归到了哪个难度区间,之后就能用数据去调整路由规则。有了这些,日后遇到「相比上周准确率涨了,但成本也涨了」的情形,就能立刻把原因拆开。没有的话,是模型的锅、路由的锅,还是流量构成变化的锅,就永远说不清了。

回到开头那个问题:该往 Slack 里贴的数字,不是三个里的某一个。三个全贴上,再写一句我们的任务更接近哪一边,才是对的。

参考资料

현재 단락 (1/62)

新模型出来了,你去看基准测试结果。可分数不止一个,是三个。该往 Slack 里贴哪个数字?

작성 글자: 0원문 글자: 3,634작성 단락: 0/62