上一章 MoE 与稀疏激活 总参数 671B 的模型为什么能比 70B 的稠密模型还便宜?答案在"激活参数量"和"总参数量"之间的那条缝里。这一章讲清 MoE 的收益、代价,以及面试里最容易答错的那道题。 I · 大模型原理 · 第 03 章 · 进阶 · 约 24 分钟
Agent 工程学习站 做棵大树 出品 主站 beatree.cn 免费 · 无需登录 · 进度存本机
I 版 · 大模型原理 ◆ 第 04 章 ◆ Sampling and Determinism
I · 大模型原理 04 / 22 入门

采样、温度与确定性

Sampling and Determinism
预计阅读 20 分钟
难度 入门
关键词 Temperature · Top-P
本机状态 未读

同一个问题问两遍得到不同答案,这不是缺陷,是解码策略的必然结果。但在 Agent 里,这种不确定性会以最讨厌的方式暴露出来:工具参数填错、JSON 少一个大括号、同一个任务两次跑出不同的步骤数。这一章讲清每个采样参数到底在动什么,以及哪些地方必须确定、哪些地方必须不确定。

编辑部注 · 读完请务必记住一条工程纪律:需要结构化输出的地方,temperature 应该是 0(或极低),而不是「调低一点」。

模型输出不是一个答案,而是一个概率分布。每一步它给出词表上每个 token 的概率,然后由解码策略从中挑一个出来。所谓「温度」「top-p」,都是在改这个挑选过程。理解这一点,你就能判断哪些不确定性是可修的、哪些是不可修的。

一、模型实际吐出的是什么

在最后一层之后,模型给出的是 logits——词表大小(通常 10 万量级)的一个向量,每个位置一个分数。经过 softmax 变成概率分布。

一个必须建立的直觉:模型从来没有「想好了一个答案」,它只是不断地在每一步给出一个分布,然后从里面抽一个。 所谓「生成」,是抽样的累加。

下面这张表把解码参数逐个摊开:它作用在 softmax 的哪一侧、动的是分布的哪个性质、调高调低各自要付出什么代价。其中 top-p(Nucleus Sampling) 这类「按累计概率截断」的做法,最容易被和温度混为一谈,值得先分清。

解码参数:各自在动什么
参数作用位置在做什么调高 / 调低的后果
temperaturesoftmax 之前给 logits 除以 T:T<1 让分布更尖锐,T>1 让分布更平坦调低 → 更确定、更保守、也更容易陷入重复;调高 → 更发散、更有创意,也更容易跑偏
top_p(核采样)softmax 之后按概率从高到低累加,只保留累计到 p 的那批候选,其余丢弃调低 → 候选集变小,输出收敛;调高 → 保留更多长尾可能
top_ksoftmax 之后固定只保留概率最高的 k 个候选与 top_p 作用类似;但 k 固定,遇到分布极尖或极平时表现不一致
repetition_penaltylogits 修正对已出现过的 token 降低其分数轻微使用可缓解复读;过强会破坏专有名词与代码标识符的重复出现
seed随机数源固定采样用的随机序列同一 seed + 同一请求 + 同一后端版本 → 可复现;换后端可能失效

二、temperature 与 top_p 的区别

这两个参数经常被混着用,但它们动的是分布的两个不同阶段。

温度改「形状」,top-p 改「范围」 原始 logits 经 softmax(T=1) 候选 token(按概率降序)→ T = 0.2 分布更尖 几乎总选第一个 → 确定性高,但可能复读 T = 1.5 分布更平 长尾也被抬起来 → 发散,但容易胡言 top_p = 0.9 截断尾部 保留:累计概率到 0.9 为止 丢弃 10% ← 这些低概率 token 不再可能被选中
图 1 温度在 softmax 之前缩放 logits,改变整条分布曲线的陡峭程度;top-p 在 softmax 之后按累计概率切一刀,改变的是可被选中的候选范围。两者可以叠加使用,但叠加时要注意:高温 + 高 top-p 会让输出非常不稳。
同一组 logits,三个温度的形状 每栏 8 个候选 token(按概率降序),柱高 = 概率。只改形状,不改候选范围。 T = 0.2(更尖) T = 1.0(原始) T = 1.5(更平) 温度只改形状(高低的悬殊),不改候选范围;切范围那是 top-p 的活。
图 2 同样的 logits,T 越小分布越尖(几乎只取第一个),T 越大越接近平坦(长尾全被抬起)。温度改的是「概率的悬殊程度」,不是「能选哪些 token」——后者归 top-p 管。

三、Agent 场景下的确定性纪律

这是本章最有工程价值的一节。把 Agent 里所有调用模型的地方按「要不要确定性」分两类——这本身就是选解码策略(Decoding Strategy)。

调用类型推荐设置原因
工具调用 / 参数生成temperature 0,top_p 尽可能低参数写错就执行失败,没有「创意」的余地
结构化输出(JSON / YAML)temperature 0 + 强制 schema格式错误直接导致解析异常,会引发重试与成本翻倍
意图分类 / 路由temperature 0同一句话应该稳定路由到同一条链路,便于复现问题
代码生成temperature 0 ~ 0.2可验证(能跑通),不需要多样性;低温度减少无意义的变体
方案探索 / 头脑风暴temperature 0.7 ~ 1.0此时多样性本身就是产出,需要多个不同的候选
文案润色temperature 0.5 左右既要稳定风格,又要避免套话
temperature = 0 并不等于完全确定

即使温度设为 0(即每步都取概率最高的 token,greedy 解码),同一请求在不同条件下仍可能得到不同结果:

  • 浮点非确定性:批处理中不同 token 与其他请求拼在同一次前向里,累加顺序变化会导致极小的数值差异,在分布接近的两个 token 之间翻转;
  • 后端版本变化:推理框架升级、量化版本更换,都会改变 logits 的微小差异;
  • 前缀缓存命中与否:不同计算路径同样可能带来微小数值差异。

所以正确的表述是:temperature=0 让结果「几乎确定」,但工程上必须假设它可能变化——不能把正确性建立在「它一定一样」上面。

低温更容易复读,重复惩罚是对症的旋钮

温度越低,模型越「贪」,越容易挑概率最高那批 token,反复说同一段话——Agent 里尤其明显(长任务里模型会把同一句模板车轱辘式重复)。对症的旋钮是重复惩罚:在 logits 阶段对「已经出现过的 token」施加折扣,压住复读冲动。

但它是旋钮不是开关:过强会误伤本来就该重复的东西——变量名、API 名、专有名词反复出现是正常的,惩罚太重会把它们也压低,输出反而变形。常见起点是 1.1~1.2 左右压复读,一旦发现代码标识符或专名被误伤就调回接近 1.0。它和温度是配合关系:低温本就易复读,适当加一点重复惩罚,往往比单纯再降温度更有效。

这里的重复惩罚(Repetition Penalty)是采样阶段唯一一个「直接动 logits」的旋钮,下一节讲的约束手段都在解码之后、甚至解码之外起作用——记住这个分层,面试时被追问「重复惩罚到底改了什么」才答得准。

确定性纪律矩阵:调用类型 × 该用什么 ✓ = 该列设置适用;列越靠左越「确定」,越靠右越「发散」 低温 · 强约束 中温 · 无约束 高温 · 发散 工具 / 参数生成 ✓ 结构化输出 JSON ✓ 意图分类 / 路由 ✓ 代码生成 ✓ 方案探索 / 头脑风暴 ✓ 文案润色 ✓ 判据:凡「错了就执行失败 / 无法解析」的调用 → 进最左列;凡「多样性本身是产出」的调用 → 进最右列。
图 3 把调用类型往矩阵里放,规则立刻清晰:会「执行」或「被解析」的调用一律低温 + 强约束,只有以多样性为产出的调用才允许高温。把温度当旋钮乱调,是 Agent 线上故障的高频来源。

四、让输出可控的三种手段

既然模型输出本质是概率性的,工程上就要用「约束」而不是「祈祷」。按强度从弱到强:

第一层:低温度(Temperature)。 最简单,但只是降低概率,不提供任何保证。与 top-p 相对,还有 top-k 采样(Top-k Sampling):固定只保留概率最高的 k 个候选,与 top-p 作用类似但 k 固定。

第二层:JSON Schema / 结构化输出约束,即 约束解码(Constrained Decoding)。 在解码时限制每个位置只能取合法 token(例如进入字符串状态就只能取字符串字符)。这是真正意义上的强制,失败率极低。大多数现代 API 都支持 response_format 或 tool_calls 这类约束通道。

第三层:后置校验 + 有限重试。 无论前面怎么做,都要在拿到结果后做一次校验。最极端是 贪心解码(Greedy Decoding)(每步取最高概率),但它仍非绝对确定;要可复现就固定 随机种子(Seed),而 浮点非确定性(Floating-point Non-determinism) 与后端版本差异仍可能让结果翻转——所以校验不能省。

constrained_tool_call.pypython
import json
from typing import Any
TOOL_SCHEMA = {
    "type": "object",
    "properties": {
        "city":   {"type": "string"},
        "unit":   {"type": "string", "enum": ["celsius", "fahrenheit"]},
        "days":   {"type": "integer", "minimum": 1, "maximum": 7},
    },
    "required": ["city", "unit", "days"],
    "additionalProperties": False,
}
def call_model(messages, temperature: float):
    """占位:真实调用里把 temperature 和 response_format 一起传下去"""
    raise NotImplementedError
def validated_tool_call(messages, retries: int = 2) -> dict[str, Any] | None:
    """
    三层防线的落地:
      1. temperature=0 + 结构化输出约束(由 API 保证格式)
      2. 本地 schema 校验(参数完整性、取值范围)
      3. 带错误信息的有限重试(而不是盲目重试)
    """
    local = list(messages)
    for attempt in range(retries + 1):
        raw = call_model(local, temperature=0)
        try:
            args = json.loads(raw) if isinstance(raw, str) else raw
        except json.JSONDecodeError as e:
            local.append({"role": "user",
                          "content": f"上一次输出不是合法 JSON({e}),请只输出 JSON 对象。"})
            continue
        err = validate(args, TOOL_SCHEMA)
        if err is None:
            return args
        # 关键:把具体错在哪回喂,模型才能真正修正
        local.append({"role": "user",
                      "content": f"参数校验失败:{err}。请修正后重新输出完整参数。"})
    return None      # 明确失败,交由上层决定是降级还是转人工
def validate(obj: dict, schema: dict) -> str | None:
    """极简校验:只示意逻辑,生产环境请用 pydantic / jsonschema"""
    for key in schema.get("required", []):
        if key not in obj:
            return f"缺少必填字段 {key}"
    if obj.get("unit") not in schema["properties"]["unit"]["enum"]:
        return f"unit 取值非法:{obj.get('unit')}"
    if not isinstance(obj.get("days"), int) or not 1 <= obj["days"] <= 7:
        return f"days 必须是 1-7 的整数,当前为 {obj.get('days')}"
    return None
实操任务
  • 拿一个真实的任务,把 temperature 设成 0、0.7、1.2 各跑 10 次,记录参数完全一致的次数;
  • 统计「重试时把错误信息回喂」与「不带信息重试」的成功率差异——通常会差 20 个百分点以上;
  • 写一句话说明:为什么在 Agent 里,temperature=0 之外还必须做校验?

五、常见误区与追问

5.1 误区:temperature = 0 就等于完全确定

错在哪:把「每步取最高概率」理解成「结果可复现」。为什么自然:直觉上贪心解码是确定性的。正确做法:即使温度归零,浮点非确定性、后端版本、批内累加顺序、缓存命中路径都会带来微小数值差异,在分布接近的两个 token 之间翻转。判据:工程上必须假设结果可能变化,把正确性建立在「校验 + 幂等」而非「它一定一样」上;要复现就固定 seed + 同后端同版本,且仍保留校验。

5.2 误区:温度调高调低只是「风格」不同,随手拧

错在哪:把温度当无后果的美学旋钮。为什么自然:很多文档把温度描述成「保守 ↔ 有创意」。正确做法:温度决定的是「错了会不会执行」——工具参数、JSON、路由这类会被执行或被解析的调用,温度必须 0;只有方案探索、头脑风暴这类以多样性为产出的调用才允许高温。判据:先问「这一步的输出会被执行还是被解析?」是 → 0;否(多样性是产出)→ 0.7~1.0。

5.3 误区:temperature 和 top-p 是一回事,用哪个都行

错在哪:认为两者可互换。为什么自然:都「限制随机性」,名字又常并列。正确做法:温度在 softmax 之前缩放 logits,改的是分布形状(高低悬殊);top-p 在 softmax 之后按累计概率切一刀,改的是候选范围。两者叠加时,高温会把原本低概率的 token 抬进 top-p 的保留范围,所以「高温 + 高 top-p」是最不稳的组合。判据:被问「哪个改形状、哪个改范围」必须答清阶段顺序。

5.4 误区:固定 seed 就能跨系统稳定复现

错在哪:认为 seed 是全局复现开关。为什么自然:seed 字面意思就是「随机种子」,容易以为一设就稳。正确做法:seed 只在同一后端、同一版本、同一调用路径下有效;换推理框架、换量化版本、换批处理拼法,logits 的微小差异都会让结果漂移。判据:复现需求要落到「同后端同版本 + 校验」,不能只靠 seed;跨系统一致性必须靠后处理保证。

5.5 误区:重复惩罚开大点就能彻底防复读

错在哪:把 repetition_penalty 当万能去重。为什么自然:复读烦人,惩罚强一点看似更干净。正确做法:过强的重复惩罚会压低所有已出现 token,包括专有名词、代码标识符、必须重复的字段名,反而制造新错误。判据:轻微使用(如 1.1~1.2)即可;真正防复读要靠约束解码与结构控制,而不是把惩罚拉满。

5.6 误区:结构化输出只要 temperature = 0 就稳了

错在哪:以为低温天然保证格式正确。为什么自然:0 温度减少变体,看起来格式更稳。正确做法:低温只降低概率,不保证「每个位置都是合法 token」。JSON 少一个大括号、字段拼错仍是高频故障。判据:结构化输出必须三层齐备——temperature=0 + response_format/工具调用约束(约束解码)+ 后置 schema 校验;少一层都可能线上解析失败。

六、自测

本章自测答错的题请回看第三节的表格
Q1temperature 与 top-p 分别作用在什么位置?
B。顺序很重要:温度先改分布形状,top-p 再在改过的分布上切一刀。这个顺序解释了一个常见现象——高温度会让原本概率很低的 token 被抬到 top-p 的保留范围内,所以「高温 + 高 top-p」是最不稳定的组合。
Q2为什么把 temperature 设为 0 仍不能保证输出完全一致?
C。这是非常实用的一条知识:即使 greedy 解码,工程上仍必须假设结果可能变化。由此推出的纪律是——不要把系统的正确性建立在「模型每次输出一样」上,而要建立在校验与幂等之上。
Q3工具调用参数解析失败后,哪种重试方式最有效?
A。无信息重试等于重复同一件事,成功率提升极小。错误信息回喂把「一次采样」变成了「一次带反馈的修正」,这是 Agent 重试逻辑里性价比最高的改动。注意 C 是反向操作:参数生成场景应该保持低温。

七、小结

  • 模型输出的是分布,不是答案;解码策略决定怎么从分布里挑。
  • 温度改「多平」,top-p 改「多宽」;两者叠加时要注意极端组合。
  • Agent 里凡是需要结构化结果的地方,一律低温 + 强约束 + 校验 + 带反馈的有限重试。
  • temperature=0 只是「几乎确定」,工程上不能依赖「一定一致」。

不确定性本身不是问题,把不确定性当成确定性来用才是问题。本刊编辑部

到这里,模型原理部分还差最后一块拼图:既然算力与显存都这么贵,工业界到底用什么手段把成本打下来?

八、参考与延伸

本章机制部分只讲到「够用」为止。想往下深挖,下面按「先看图、再看代码、最后读论文」的顺序排好了。全站不做原文转载,这里只登记链接与「为什么值得读」。

先看图(建立直觉)

  • Lilian Weng · Controllable Neural Text Generation —— 把 temperature、top-k、nucleus(top-p)、beam search 逐个推导,是少数把「采样」讲成公式而不是调参笔记的材料。读本章第二节卡住时来这儿:它正好解释了为什么按累计概率截断(top-p)比固定 top-k 更稳。

再看代码(动手实现)

  • 动手学深度学习(中文,zh.d2l.ai) —— 配套可跑代码,把 softmax、采样、约束生成都实现了一遍。把本章的 constrained_tool_call 照着敲一遍,比看十遍都记得牢。
  • OpenAI Cookbook —— 大量关于「稳定结构化输出、降低不确定性」的可运行示例。重点看它怎么把 response_format 与重试组合起来。

最后读论文(对齐一手定义)

◇ 面试官会怎么问

共 5 条 · 其中 1 条高频 · 先自己答一遍,再展开对照

Temperature 和 top_p 分别在调什么?Agent 场景你会怎么设?
两者都作用于下一个 token 的概率分布,但手法不同:temperature 在 softmax 前对 logits 做缩放,调的是分布的"陡峭程度"——温度越低分布越尖,越倾向于选最高概率的词;top_p(核采样)先按概率从高到低累加,只保留累计概率达到 p 的那一截候选,把长尾直接砍掉。
Agent 场景的经验是一条原则:凡是输出要被程序解析的地方(工具调用、结构化参数、JSON),尽量低温甚至贪婪解码;需要发散的地方(头脑风暴、候选方案生成)才提高温度。
要注意:即使 temperature = 0,由于并行归约的浮点非确定性、批处理组合变化等原因,输出也不保证逐字一致——所以兜底必须是校验和重试,而不是"假设它稳定"。

追问链

  1. 那参数校验失败了你怎么办?
  2. 为什么 temperature=0 仍然可能两次结果不同?
同一个问题问两次答案不同,这在 Agent 里会变成什么问题?
在聊天场景里这只是"有创造力",在 Agent 里它会直接变成三类故障:① 工具参数漂移(同一次意图生成出不同的参数,导致查询结果不一致);② 流程分支抖动(该调工具时直接凭记忆回答,或者反过来);③ 评测不可复现(你无法判断指标变化是改动带来的还是采样噪声)。
对应的工程手段是:结构化输出 + Schema 校验、解码参数按环节分级设置、把关键决策点做成确定性代码而不是交给模型自由发挥、评测时固定随机种子并多次重复取分布。
加分点:把"采样不确定性"翻译成具体的故障清单,比讨论参数含义更能体现工程视角。
top_k 和 top_p 能同时用吗?会有什么问题?
可以同时用,但一般是"有一个主约束"更清晰。两者叠加的实际效果是取交集性质——先按 top_k 砍掉长尾,再按 top_p 收敛,最终候选集会比单独用任何一个都小,可能过度限制多样性。
更实用的建议是:固定一个主参数(OpenAI 系通常调 top_p,部分开源模型偏 top_k),另一个设为默认值,避免两个旋钮互相干扰导致调参不可解释。另外,把"温度"和"惩罚项"(presence / frequency penalty)混着调最容易出现"越调越怪"的情况。
用了约束解码(constrained decoding / response_format)之后,还需要再做 schema 校验吗?为什么? 高频
需要,因为两者保证的不是同一件事。
约束解码保证「语法合法」——每个位置只能取合法 token,所以几乎不会出现少括号、字段名拼错这类解析错误。
但它不保证「语义正确」:字段值可以是错的、枚举选错、数字超范围、必填项给空串,以及上下文被截断时 JSON 是否完整(取决于实现是否强收尾)。
判据:凡输出会被程序执行的调用,三层必须齐备——低温(概率上收敛)+ 约束解码(语法上强制)+ 本地 schema 与业务校验(语义上兜底)。少任何一层,线上都会以解析失败或错误执行的形式暴露。

追问链

  1. 约束解码会不会拖慢生成?
  2. 校验失败之后,你的重试策略是什么?
加分点:加分点:把「语法合法」与「语义正确」明确分开,并主动指出截断这一类边界情况。
为了让评测可复现,固定 seed 够吗?还需要固定什么?
不够。seed 只在同一后端、同一版本、同一调用路径下有效。
必须一起固定的还有:
  • 推理框架与模型版本的精确标识(升级、换量化档位都会改变 logits 的微小差异);
  • 批处理的拼法(同一批里别人多一个请求,你的结果就可能翻转);
  • 采样参数与停止条件;
  • 评测集本身与判分规则(规则也要版本化)。
更关键的是心态:即使全都固定,浮点归约顺序仍会带来微小差异,所以高复现要求的场景要「固定 + 多跑几次看分布」,而不是「固定 + 假设一模一样」。
判据:报告指标时给出重复次数与波动区间;跌幅小于波动区间的不叫回归。

追问链

  1. 你怎么给「采样噪声」和「真实回归」划线?
  2. 哪些指标天生不适合用来判定回归?
加分点:加分点:把「固定 seed」升级成「固定后端 + 固定批处理 + 看分布」,并主动提波动区间。
答这类题的通用结构

结论先行(一句话给出取舍)→ 机制(为什么是这样,涉及哪条链路)→ 代价或边界(这么做放弃了什么)→ 你的实践或数字(真实场景里怎么落地)。 只讲机制不讲代价,是背题;只讲取舍没有数字,是空谈。

本章收尾 · Wrap up
掌握度自评
点一下给自己打分;低于 3 分建议加入复习队列
个人笔记 · Notes
    下一章 推理优化与成本杠杆 量化、PagedAttention、投机解码、连续批处理——四把把成本打下来的锤子。重点不是背名词,而是知道每个手段针对哪个瓶颈、代价是什么。 I · 大模型原理 · 第 05 章 · 深入 · 约 30 分钟