7AA7 Blog

大模型入门:Token、上下文窗口与温度参数

AsyncLab UID:94
2026-10-09 22:50
44 3 3

用大模型做开发,最先撞上的不是模型能力,而是三个”物理约束”:Token、上下文窗口、采样参数。它们分别决定了你的输入怎么被计费和截断、一次能塞进多少内容、以及输出到底稳不稳定。把这三个概念吃透,后面调提示词、搭 RAG、做 Agent 都会顺很多。

一、Token:模型真正”看到”的单位

模型不读字符,也不读词,它读的是 Token。一段文本在进入模型之前,会先经过分词器(tokenizer)切成 Token 序列,每个 Token 对应词表里的一个整数 ID,模型在这些 ID 上做运算。

分词的粒度和语言关系很大:

  • 英文里常见的做法是子词切分(BPE 一类算法),一个完整单词可能被切成若干片段。unbelievable 这种长词经常被拆开,而 the、is 这类高频词往往是一个 Token。
  • 中文的处理取决于分词器的词表设计。常见情况是单个汉字占一个 Token,有时两三个高频字会合并成一个 Token。粗略地按”一个汉字约 1 Token”来估算,比按”一个词 1 Token”靠谱。
  • 代码、JSON、URL 会被切得比较碎,一个看起来不长的请求体,Token 数经常出人意料。

有意思的是,Token 并不等于”信息量”。压缩率(平均每个 Token 承载多少字符)在不同语言、不同文体下差别很大:英文散文的压缩率通常高于中文,代码和十六进制串的压缩率则很低。这也是为什么同一个模型对中文任务的”体感成本”往往高于英文任务——不是模型偏心,是 Token 计数的物理事实。

这带来几个直接的工程后果:

场景 Token 视角的影响
API 计费 通常按输入 Token + 输出 Token 分别计价,输出往往更贵
长度限制 输入和输出分别受上下文窗口与 max_tokens 约束
拼接长文 拼 10 篇文档前先估算总 Token,别等超限报错才发现
日志与调试 建议在调用后把实际 usage 的 Token 数记下来,比肉眼估准

如果你想自己数 Token,分词器的实现(词表文件 + 切分规则)是公开的,可以用官方或开源的 tokenizer 库离线统计。但注意:换模型就要换分词器,同一个字符串在不同模型下的 Token 数可能差出一倍。

二、上下文窗口:一次推理的”桌面面积”

上下文窗口(context window)指一次推理中,输入加输出的 Token 总量上限。你可以把它想象成模型的工作台:桌面就那么大,铺开的材料(system prompt、历史对话、检索到的文档、当前问题)和你要求它写出的答案,都得放在这张桌子上。

几个容易混淆的点:

  • 窗口大小是输入与输出之和。128K 窗口不等于能塞 128K 输入,输出还要预留空间。
  • 窗口大不等于效果好。超长上下文里”中间信息容易被忽略”的现象(业内常称 lost in the middle)是真实存在的,把最关键的内容放在开头或结尾更稳妥。
  • 多轮对话是滚动累加的。每一轮都要把之前的 messages 重新发一遍,对话越长,单次调用越贵、越慢。

超出窗口时,常见的处理策略有三种:

  1. 截断:丢掉最旧的消息或超出部分的文档。实现最简单,代价是上下文不连贯。适合日志类、检索结果类内容。
  2. 滑动窗口:保留最近 N 轮对话 + 固定的 system prompt,更早的内容丢弃。聊天场景的常规做法。
  3. 摘要压缩:把早期对话先用模型摘要成一段短文本,再拼进上下文。省 Token,但摘要本身是一次额外调用,且可能丢细节。

生产环境里通常是组合使用:system prompt 永远保留,近期对话用滑窗,早期历史做摘要,检索来的文档按相关性排序后截断。

三、温度与 top-p:控制输出的”离散程度”

模型每一步输出的其实不是”一个词”,而是词表上的概率分布,然后从这个分布里抽一个 Token。温度(temperature)和 top-p 就是抽样前对这个分布做整形的两个旋钮。

温度作用在 softmax 之前,对 logits 除以一个系数再归一化:

  • 温度趋近 0:分布被”削尖”,几乎总是选概率最高的那个 Token,输出趋于确定,适合分类、抽取、代码生成。
  • 温度 = 1:按模型原始分布抽样。
  • 温度 > 1:分布被”压平”,低概率 Token 的机会变大,输出更发散,适合头脑风暴、文案变体。

top-p(核采样) 是另一个思路:把 Token 按概率从高到低排序,只在累计概率达到 p 的最小集合里抽样。top-p = 0.1 意味着只从最可能的那 10% 概率质量里选词,天然屏蔽了长尾的胡言乱语。

两者的使用惯例:

  • 需要稳定、可复现的输出:temperature=0(很多实现里等价于贪心解码),top-p 可以不动。
  • 需要多样性:调温度通常就够了,top-p 保持 0.9 到 1 之间。
  • 不建议两个参数一起猛调,出了问题很难定位是哪个在起作用。

还有一个和输出长度相关的参数:max_tokens,它限制的是生成侧最多吐多少 Token。注意它不影响输入,也不保证内容完整——输出被截断时,句子会在半路断掉,需要检测 finish_reason 之类的字段来判断是正常结束还是被截。

四、system prompt 与一次调用的完整结构

一次典型的调用,消息列表是分层的:

┌─────────────────────────────┐
│ system prompt               │  ← 角色、规则、输出格式约定
├─────────────────────────────┤
│ user / assistant 历史消息    │  ← 对话记忆
├─────────────────────────────┤
│ 当前 user 消息(含检索材料)  │  ← 本轮真正要处理的输入
└─────────────────────────────┘

system prompt 的位置很关键:它排在最前,定义了整轮对话的”操作系统”。把格式约束、禁止事项、角色边界放在 system 里,比每轮 user 消息重复一遍更省 Token,也更稳定。但 system prompt 并非”绝对不可违背”,当用户消息里出现强硬指令时模型仍可能被带偏,敏感约束要配合输出校验使用。

五、一个不绑定厂商的调用示例

下面是一个 OpenAI 风格的请求体(各家协议大同小异,字段语义基本一致):

{
  "model": "example-chat-model",
  "messages": [
    {
      "role": "system",
      "content": "你是资深后端工程师。回答用简体中文,只输出 JSON,不要额外解释。"
    },
    {
      "role": "user",
      "content": "把下面这段日志按 level 分类,输出 {"error": [...], "warn": [...]}:n2024-01-01 ERROR db timeoutn2024-01-01 WARN cache miss"
    }
  ],
  "temperature": 0,
  "top_p": 1,
  "max_tokens": 512
}

对应的伪代码调用:

resp = client.chat.completions.create(**payload)

print(resp.choices[0].message.content)
print(resp.usage.prompt_tokens, resp.usage.completion_tokens)

usage 里的两个数字别浪费:把它们记进日志,跑一段时间你就知道自己业务的真实 Token 画像了,比任何估算公式都准。

六、调试时的三个实用习惯

  1. 先把 Token 数印出来。很多”模型变笨了”的问题,其实是上下文里塞了太多无关材料,关键信息被稀释了。
  2. 温度固定为 0 做基线。先确认确定性输出是否正确,再考虑加多样性。顺序反了,你分不清是提示词问题还是采样问题。
  3. 给 max_tokens 留余量。宁可多留一点被浪费掉,也不要让答案停在半句话——截断的输出比稍微贵一点的调用麻烦得多。

排查顺序上,先看 usage 的 Token 构成(是不是超窗、是不是截断),再看 finish_reason(是正常收尾还是被 max_tokens 掐断),最后才去怀疑采样参数。这个顺序能过滤掉大部分”模型玄学”问题。

这三个参数没有”最优解”,只有和任务匹配的解。把它们当成 API 的物理层来理解,比当成”魔法数字”去试,调试效率高一个数量级。

751Views
44Likes
3Comments
2Participants
10 minread time

Comments (3)

  • 硅屿 · 2026-10-10 01:50

    实践下来确实有效,只是前期配置稍麻烦。

    Reply
  • 林见川 · 2026-10-10 08:50

    看完最大的收获是知道该在什么时候停手。

    Reply
  • 林见川 · 2026-10-10 15:50

    看标题以为是老调重弹,读下来有干货。

    Reply