1. 内容整体设计与思路拆解
1.1 为什么偏偏是 Token、上下文和采样参数这三个词
先说个现象。前阵子我在群里看到不少人接入了大模型 API,聊着聊着就问出同一个问题:“为什么我花了这么多 token,对话还是这么蠢?”再往细了一问,有人把 temperature 拉到 1.5 想让模型“更有创意”,结果模型开始胡言乱语;有人明明买了几十万上下文,结果一问早期聊过的事情,模型完全不记得;还有人自己部署了 7B 模型,好端端地聊着,突然发现模型开始断片。
这些问题看着五花八门,绕来绕去都绕不开同一个地方:Token、上下文和采样参数。
LLM 看起来像个无限聪明的话痨,本质上却是一台非常死板的概率机器。你给它一段文字,它先把文字切成小块——这就是 Token;然后它把整段文字一次性塞进一个固定大小的窗口——这就是上下文;最后它逐字逐句地根据概率挑下一个词——而采样参数就是决定“挑得多随意”的旋钮。这三样东西分别决定了模型看得见什么、能记住多少、以及敢不敢乱说。
这篇内容适合的人很明确:一是刚把 LLM 接进自己项目,但搞不懂 token 计费和上下文窗口为什么是限制的开发者;二是本地折腾过 Ollama、Llama.cpp、Dify 等工具,却发现模型“记不住话”“答非所问”的折腾党;三是在用 API 调大模型,被各种参数搞得一头雾水的产品经理或测试同学。读完这篇文章,你应该能说清楚三个问题:Token 是怎么切出来的、为什么上下文有上限、temperature 和 top_p 到底动了什么手脚。
1.2 我的拆解顺序:从“模型看到什么”推到“模型怎么张嘴”
我见过太多人一上来就背公式、解释 Transformer 架构,结果是越看越糊涂。我的习惯是先搞清楚输入输出,再去理解中间发生了什么。
一个 LLM 的运行流程,用最简单的话说就是三步:
- 你输入的文本先被切成一串 Token,并转换成向量。
- 这串 Token 被放进一个固定大小的上下文窗口,模型通过注意力机制去“看”这些 Token 之间的关系。
- 模型基于当前的上下文,预测下一个 Token 的概率分布,然后通过采样参数来决定最终生成哪一个 Token。生成出来之后,这个 Token 又拼接进原来的上下文,继续预测下一个。
所以我的拆解顺序就很自然了:先讲 Token 是怎么来的,再讲上下文窗口为什么有限,最后讲采样参数怎么决定模型的“性格”。这样一层层推下来,你能很清楚地看到,每一步的选择都在影响最后生成的文本。
我还会在最后补上实际排查时最常遇到的几个坑,比如本地部署模型“记不住上下文”、输出被截断、temperature 过高导致乱写等等。这些都是实操中经常被问到的,而且网上讲得比较散,我尽量给你整理成能直接照着操作的清单。
2. 核心细节解析:Token 是怎么把文本“切片”的
2.1 不是按单词切的:聊聊 BPE 分词算法
很多新手一开始以为 Token 就是“单词”,英文按空格切,中文按字切。这个理解方向没错,但实际没这么简单。如果英文真的按空格和标点切,那么一个词汇表里得有几十万甚至上百万个单词,而且生僻词、复合词、拼写错误根本覆盖不完。所以主流的 LLM 用的是一种叫 BPE(Byte Pair Encoding,字节对编码)的分词方法。
BPE 的逻辑可以理解成“从字符开始,把最常见相邻字符对不断合并”。我举个例子,英文词unbelievable。最开始它是一串字符:u n b e l i e v a b l e。BPE 在大量语料上统计出最常见的相邻对,比如ab很常见,于是合并成ab,后来liev也常见,继续合并,最后可能变成一个或两个 Token。这样词汇表里不需要穷举所有英文单词,只需要保存几千到几万个常见的“子词单元”,就能拼出几乎任何单词。
GPT 系列用的 Byte-level BPE 更进一步,先把所有文本转成 UTF-8 字节,再在字节级别做合并。这样做的好处是理论上任何 Unicode 字符都能表示,不会遇到“这个词没见过”就直接报错的情况。中文在这套方案里的表现就很有意思:一个汉字往往对应一个 Token,但因为是按 UTF-8 字节统计的,有些常用词组合可能会合并成一个 Token,有些生僻字可能要两个 Token 才能表示。
2.2 为什么 Token 直接决定你的成本、速度和上下文空间
Token 切法会直接影响三件事,这也是大家在实践中感受最深的:
- 成本:API 计费是按 Token 算的,一个输入 prompt 切出的 Token 越多,花的钱就越多。很多时候“代码注释太长导致费用暴涨”不是段子,而是真事。
- 速度:生成速度是按“每秒生成多少 Token”算的。同一个 prompt,Token 数量越多,前处理时间和生成时间都会变长。
- 上下文空间:上下文窗口是有容量上限的,你输入时占掉越多 Token,留给模型输出的余量就越少。
一个常见的量化参考:英文里一个 Token 大约对应 0.75 个单词,中文里一个 Token 大约对应 0.6 到 1 个汉字——说白了,中文的比例不太固定,不同分词器差异不小。我看了不少实测数据,同一个句子在 GPT 和 Claude 的分词器下切出的 Token 数量可能差 20%-30%,原因就是各家预训练用的分词器统计语料不一样。
所以你在调优的时候,不要只盯着模型智商,先把 Token 切分这件事搞清楚。你在 agent 场景里,每一步工具的返回结果都会变成 Token 塞回上下文,如果返回内容特别长,那上下文空间和费用都会被快速吃光。后面讲上下文的时候还会再提到这个问题。
2.3 实战案例:一个提示词被切成了什么样
为了让大家更直观地理解,我用一个非常简单的提示词做演示(以常见开源分词器为例):
输入:请用三句话解释什么是注意力机制,并给出一个生活化的比喻。
这个提示词的表达长度按字符数大约是 30 个左右。经过 Tokenizer 之后,很可能被切成 20-25 个 Token,不是 20 个汉字一一对应,而是中间有些常用词汇被合并了。比如“注意力机制”如果是一个模型里常见的复合词,可能只占 2-3 个 Token;“三句话”可能会被切得更碎。
这里也顺便提醒一句:不用过分纠结“我的 Token 为什么比我数的字数还要多”。
一个更常见的误区是这样:有些小伙伴在开发 RAG 应用的时候,喜欢把整篇文档一次性扔给模型,以为“我买的上下文窗口很大,无所谓”。结果文档一长,Token 数量直接爆了,模型输出质量明显下降不说,调用成本也成倍增长。所以做 RAG 时一定要先做好切片,控制每段文本的 Token 数量——一般是 500-1000 个 Token 左右为一块,这样既不会截断语义,也能有效控制成本。
3. 上下文窗口:模型到底能“记住”多少
3.1 上下文窗口被什么消耗了
上下文窗口可以理解成一块“工作台面”。模型的所有推理都在这个台面上进行,台面上放得下多少 Token,模型就能同时看到多少 Token。
早期模型上下文窗口很小,GPT-2 时代只有 1024 个 Token,大约就是一两段英文的水平。后来 GPT-4 系列把窗口扩大到 8K、32K,再后来 Claude、Gemini 这些模型都推出了 100K、200K 甚至百万级的窗口。但窗口再大也不是无限的。与之相关的还有两个隐藏成本:
- KV Cache:大模型推理时要缓存历史 Token 的注意力键值(K 和 V),这个缓存的体积和 Token 数量成正比。窗口越长,显存占用就越大,这也是本地部署模型跑不了超长上下文的一个重要原因。
- 注意力计算量:标准注意力机制的计算复杂度是 O(n²),也就是说窗口长度翻倍,计算开销是原来的四倍。这就是为什么长上下文模型往往需要更高级的稀疏注意力或者优化的推理框架。
一个很日常的例子:你在聊天窗口里跟模型说“还记得我们刚开始聊的时候你推荐的书吗?”这句话本身只有十几个 Token,但模型要回答这个问题,就需要在整个上下文窗口里找到“最开始聊的内容”。如果那段内容因为太长已经被截断或者覆盖了,模型就真的“记不住”了。
3.2 为什么模型“记不住”上下文:机械截断和遗忘效应
我在热词里看到有人问“我本地 Ollama 部署的 Qwen2.5:7B 记不住上下文怎么办?”这个问题非常典型。我把它拆成两个层面看。
第一个层面:机械截断。模型有固定的上下文上限,超出的部分会被丢掉。很多本地部署的工具默认窗口很小,比如 Ollama 默认可能只有 2048 或 4096,而 Qwen2.5 本身支持 32K 或更长。如果你不手动调大num_ctx,聊个几轮就触顶了,前面的内容被悄悄丢弃,模型自然什么都不记得。
第二个层面:注意力稀释。即便上下文窗口完全够用,模型也不是“雨露均沾”地记住每一个 Token。当上下文中塞满了无关信息,模型会越来越难聚焦到最开始或最关键的信息上。这个问题在学术界叫“迷失在中间”(lost in the middle),就是长文本中间位置的信息最容易被模型忽略。所以你在设计 prompt 的时候,最关键的指令最好放在开头和结尾,不要放在大段描述的中间。
那本地部署应该怎么办?我个人的建议是:
- 确认模型本身支持的上下文长度,Qwen2.5 系列是 32K 起步,够用。
- 在 Ollama 运行时设置
num_ctx。比如用OLLAMA_CONTEXT_LENGTH=32768,或者在调用 API 时直接传num_ctx: 32768。 - 显存不够的话,宁可缩小上下文到 8K,也别让模型在 2K 的窗口里硬撑。
- 对于长对话,配合 RAG 做外部记忆,而不是指望模型自己记住所有内容。
3.3 跟你想法正好相反的“上下文管理”技巧:该省省,该扩扩
聊到上下文,我特别想分享一个反直觉的经验:上下文不是越大越好。
窗口大了,能装的内容多了,但计算量变大、速度变慢、成本变高。最要命的是,信息太杂会干扰模型输出。我在实际项目里经常发现,把一个大文档全文塞给模型,它的总结质量反而不如只喂 3-5 个关键片段的版本。这就像你让一个人类助手读完整本 500 页的书再总结某一章,和直接给他 10 段相关摘录让他总结,后者的效率和准确率往往更高。
所以在做上下文工程的时候,思路不是“能塞多少塞多少”,而是“只喂最相关的信息”。常见的操作包括:
- 对话历史摘要:把多轮历史对话压缩成一段摘要,代替全部原文。
- 滑动窗口:只保留最近 N 轮对话,更早的内容用摘要替代。
- RAG 检索:不从整个知识库里找答案,而是先检索出与问题最相关的文档片段,再把这些片段和问题一起交给模型。
- 关键信息提取:在做 Agent 的时候,每一步只保留工具调用返回的核心字段,而不是把完整 JSON 都塞回去。
这些技巧的核心目的只有一个:让模型在有限的窗口里“看到”真正重要的信息。
4. 采样参数:模型是怎么“挑词”的
4.1 生成过程的本质,就是一场概率抽签
聊采样参数,首先要理解模型是怎么生成的。
LLM 不是一个从记忆库里查答案的数据库,它每一步都在预测“下一个 Token 是什么”的概率分布。也就是说,模型会为词汇表里的每一个候选 Token 都计算出一个概率,比如我自己本地跑一个 7B 模型,某一步可能给出这样的分布:
今天:概率 0.35今天天气:概率 0.2今天早上:概率 0.12nothing:概率 0.01- 其他无数 Token:概率加起来 0.32
你看到的输出,就是模型一次次从这种概率分布中“抽签”抽出来的结果。而采样参数控制的,就是“这个签怎么抽”:
- 抽得太死板,每次只抽概率最高的,模型就变成复读机;
- 抽得太随机,模型就变成疯子,句句离谱。
4.2 temperature(温度):给概率分布“加热”或“降温”
temperature 是最常见的采样参数,它的作用可以被通俗理解成“让人敢不敢冒险”。
具体机制是这样:模型输出的原始概率分布会先经过一个 softmax 函数,得到一组概率值。temperature 就是在 softmax 之前的 logits(未归一化的分数)上做一个缩放:把每个 logit 除以 temperature 之后再算 softmax。
当 temperature 小于 1 时,分数差距会被拉大——高分更高,低分更低。模型就更容易选择高概率 Token,输出更确定、更保守。当 temperature 大于 1 时,分数差距会被压缩,原本概率很低的 Token 也有机会被选中,输出就更随机、更有“创造性”。
实操建议是这样的:
| 场景 | 推荐 temperature |
|---|---|
| 代码生成、数学推理、结构化数据提取 | 0.0 - 0.3 |
| 一般对话、总结、翻译 | 0.3 - 0.7 |
| 创意写作、头脑风暴、广告文案 | 0.7 - 1.0 |
| 诗歌、故事、脑洞场景 | 1.0 - 1.3,高了容易失控 |
我之前见过一个做客服机器人的同学,把 temperature 设成了 0.8,结果客户问退货运费,机器人一本正经地开始编故事。这种情况其实不是因为模型“坏”,而是温度太高,低概率 Token 被选中的机会大大增加了。做固定流程的问答,temperature 降到 0.1 或 0.2 就很稳。
4.3 top_p 和 top_k:人为掐掉“尾巴”
temperature 调整的是整个概率分布的形状,但还有两个参数可以更进一步干预候选范围:top_k 和 top_p。
- top_k:只从概率最高的 K 个 Token 里做选择,其他全部不参与抽签。比如
top_k=50,就是模型在每一步只从排名前 50 的候选里选词。太小的 top_k 会让输出变得死板。 - top_p(也被称为核采样):从概率最高的 Token 开始往下累加,直到累计概率超过 p 时,把后面的低概率 Token 全部砍掉。比如
top_p=0.9,就是模型考虑“概率加起来占 90% 的那部分候选”。
实际操作中,top_p 比 top_k 要更常用一些。原因是 top_k 是固定数量,语义差异大的步骤里概率分布形状不一样,固定 50 个候选有时候太多、有时候太少;top_p 是动态的,根据当前分布自动调整候选数量,适应性更好。
一个常见的组合拳是:temperature=0.7, top_p=0.9。这个组合基本上适合大多数场景。如果发现输出开始跑偏,就把 temperature 降一降;如果发现输出太死板,可以在保持 temperature 不动的条件下,适当调大 top_p。
4.4 重复惩罚:防止复读机
除了温度和概率截断,还有一个容易被忽视的家族:重复惩罚参数。不同框架里叫法不同,常见的有repeat_penalty、frequency_penalty和presence_penalty。它们的作用都是降低已经出现过的 Token 再次出现的概率,从而减少重复。
但它们的机制和副作用有细微差别:
repeat_penalty:对已经出现过的 Token 的 logits 直接施加惩罚分数,数值越高,越不容易重复。但它对“这个 Token 出现过一次”和“出现过一百次”的处理常常是差不多的,如果设太高,可能会导致输出跑偏。frequency_penalty:根据 Token 出现次数来惩罚,出现越多,惩罚越大,更适合压制长久的复读。presence_penalty:只要 Token 出现过,就施加一定的惩罚。它更多是为了“鼓励模型去聊些新话题”,而不是单纯防止复读。
我自己的经验是,在本地部署模型时,repeat_penalty调在 1.1 到 1.3 之间比较合适(默认一般是 1.0,也就是不惩罚)。如果低于 1.1,长文本生成时很容易出现一段话重复循环;如果高于 1.3,模型可能开始避讳一些常用词,看起来很别扭。API 服务里的frequency_penalty常用 0.3-0.8 之间,presence_penalty常用 0.0-0.6 之间。
4.5 max_tokens:那些“输出被截断”的真相
很多人在用 API 的时候遇到过“已达到输出 token 上限,回答被截断”的提示。这个问题的根源,就是你在请求参数里设置的max_tokens太小了。
max_tokens是模型一次回复最多能生成的 Token 数,它和上下文窗口不是一回事。上下文窗口是输入 + 输出的总量,max_tokens只是输出部分的上限。即使你的上下文窗口还有大量剩余空间,max_tokens设小了,输出也照样会被切断。
举个例子:一个支持 128K 上下文的模型,你传入了一个 100K 的文档,再设置max_tokens=512,那么模型只能输出不到 300 个中文字。很多长文档总结任务的失败,根本不是模型能力问题,而是max_tokens没有调够。
解决思路也很朴素:确认自己任务的输出量级。代码生成任务建议至少 2048,完整文章生成建议 4096 甚至更多。如果模型输出真的超过了这个量级,也可以通过多轮对话或者流式输出分次取回。
5. 实操记录:一份能照抄的参数配置方案
5.1 场景一:本地部署 Qwen2.5:7B 做客服问答机器人
这是热词里被反复提到的一个需求场景。本地部署 7B 级别模型,最常见的问题就是“记不住上下文”。我在前面已经提过num_ctx的问题,现在给你一份更完整的配置参考。
假设你用 Ollama 跑 qwen2.5:7b,以下是实测后比较合适的启动参数:
model: qwen2.5:7b num_ctx: 8192 temperature: 0.2 top_p: 0.9 repeat_penalty: 1.1为什么这样设?客服问答场景需要的是稳定和准确,所以 temperature 压低到 0.2;top_p 保持 0.9 不影响主体稳定性,还能兼顾一点灵活性;repeat_penalty 加一点,防止复读。num_ctx 取 8192 是考虑到普通显卡的显存限制,如果你显存富余,可以再往上加到 16384。但要注意,num_ctx越大,KV Cache 占用的显存就越高,推理速度也会变慢,这需要自己权衡。
如果你想通过 Ollama 的 API 调用,入参可以这样写:
{ "model": "qwen2.5:7b", "messages": [ {"role": "system", "content": "你是一个耐心、准确的客服助手。"}, {"role": "user", "content": "你好,我昨天买的商品还没发货,怎么办?"} ], "options": { "num_ctx": 8192, "temperature": 0.2, "top_p": 0.9, "repeat_penalty": 1.1 } }这套配置跑了一个月,稳定性不错,也没有再遇到“聊几轮就断片”的问题。
5.2 场景二:用 Dify 接模型做 RAG 知识库
Dify 这类平台在配置 LLM 时也会暴露采样参数,很多人习惯性跳过,但如果你的任务是 RAG 问答,参数怎么设影响其实很大。
RAG 的标准流程是:用户提问 -> 检索相关文档片段 -> 把片段拼进 prompt -> 交给 LLM 生成答案。这个流程里,采样参数要服务于“准确引用原文”这个目标。我的建议配置是:
temperature: 0.1 top_p: 0.8 max_tokens: 1024temperature 设 0.1 是为了让模型尽可能忠实于检索到的内容,不要自由发挥。top_p 设 0.8 是进一步收缩候选范围。max_tokens 看回答的详细程度,如果只是简短答复,512 就够;如果要生成较长的结构化输出,建议 2048。
同时还要注意 prompt 设计。我在 Dify 里常用的 RAG prompt 会明确加上一句:“如果检索到的内容无法回答用户问题,请直接说明不知道,不要编造。”这句话配合低温参数,能有效减少幻觉。
5.3 排查清单:出问题了,先查什么
我根据实战经验,把常见问题整理成一张速查表,你可以对照检查:
| 现象 | 可能原因 | 排查方向 |
|---|---|---|
| 模型“记不住”早期对话 | num_ctx太小,上下文被截断 | 调大num_ctx,或做对话摘要 |
| 回答到一半被截断 | max_tokens设置过小 | 调大max_tokens |
| 输出充满重复内容 | 重复惩罚太低 | 调高repeat_penalty或frequency_penalty |
| 答非所问、胡言乱语 | temperature 过高 | 降低 temperature,降到 0.3 以下 |
| 输出过于死板、套话连篇 | temperature 过低、top_p 过小 | 适当升高 temperature 至 0.7-0.9 |
| 长文档总结不准 | 上下文窗口不足或信息被“淹没” | 做文档切片,只喂关键段落 |
| 中文输出 token 消耗异常 | 分词器对中文处理不一致 | 用官方 tokenizer 估算,不要靠英文规则 |
这张表是我平时调模型最常用的排查路径。出现问题时不要盲目调参,先确认是不是上下文截断,再确认是不是输出上限,最后再动 temperature。
6. 聊点容易混淆的:别把 LLM 的 Token 和登录 Token 搞混
进入这一小节前,我想先替很多刚入门的朋友排一个雷:上面花了大篇幅聊的 Token,是 LLM 对文本切分的基本单位;但互联网上搜“token”,你会发现搜出来的结果至少一半在聊登录认证里的 token(比如 JWT、access token、refresh token)。这两个概念完全两码事,别搞混。
登录认证里的 token,是一串用于身份鉴权的字符串,客户端拿着它去访问受保护的接口。而 LLM 里的 token,是文本的最小处理单元。它们英文同名,中文翻译一个还叫 token,另一个时而译作“令牌”,经常会误导刚接触大模型的人。
如果你开发一个带登录系统的 AI 应用,你会同时遇到这两种 token:用户登录要发一个 JWT token;调用大模型 API 计费时,又按 token 数出账。有一次我就是在这两个概念里绕了一圈,排查了半天“token 过期”,结果发现是大模型上下文截断了。
我的经验是:在团队里交流时,明确说出“上下文 token”和“认证 token”这两个全称,能省掉很多沟通成本。
如果真遇到认证相关的问题,比如 API 接口报错说 token exchange failed 或 login failed,那是在说认证 token,不是模型分词的问题。此时要排查的是 OAuth 配置、token 有效期、刷新机制,而不是去看 temperature 或上下文窗口。这个区分放在项目排障里特别有用。
7. 避坑心得:参数调多了,才知道稳定有多重要
文章写到这,我想把最核心的体会浓缩一下。
我见过不少同学(包括几个月前的我),拿到大模型第一件事就是调温度、调 top_p,幻想着调出一个“又聪明又有趣”的模型。调来调去,最后发现:大多数生产场景,最重要的参数其实是max_tokens和num_ctx,而不是 temperature。
上下文窗口用完了,一切都白搭;max_tokens不够,回答直接腰斩。temperature 和 top_p 不过是让结果在“稳”和“浪”之间微调,而不是决定模型上下限的东西。这个认知转变之后,我再调参就踏实多了。
另外一个小技巧也想分享给你:每次调参前,记录下你当前要解决的问题。比如“回答太长被截断”“总是重复同一句话”“对检索内容不忠实”。然后每次只动一个参数,不要动两个。调完跑一遍测试集,记录下来,再动下一个。这条流程看起来很笨,但在乱调一气导致模型突然变得不可用时,能帮你快速定位是哪一步改坏的。
对于刚入门的朋友,我建议你先用一套保守的参数跑通全流程,比如temperature=0.3, top_p=0.9, max_tokens=2048,大部分任务都不会出大问题。等你对模型输出的规律有手感了,再根据场景慢慢微调。大模型的输出天然带有随机性,同一套参数可能会给出略有差异的结果,这也正常。接受这一点,你会少很多焦虑。