搞懂LLM的Token、上下文窗口与采样参数:从原理到调参实战
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 用的是一种叫 BPEByte 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”算的。同一个 promptToken 数量越多前处理时间和生成时间都会变长。上下文空间上下文窗口是有容量上限的你输入时占掉越多 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_LENGTH32768或者在调用 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_k50就是模型在每一步只从排名前 50 的候选里选词。太小的 top_k 会让输出变得死板。top_p也被称为核采样从概率最高的 Token 开始往下累加直到累计概率超过 p 时把后面的低概率 Token 全部砍掉。比如top_p0.9就是模型考虑“概率加起来占 90% 的那部分候选”。实际操作中top_p 比 top_k 要更常用一些。原因是 top_k 是固定数量语义差异大的步骤里概率分布形状不一样固定 50 个候选有时候太多、有时候太少top_p 是动态的根据当前分布自动调整候选数量适应性更好。一个常见的组合拳是temperature0.7, top_p0.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_tokens512那么模型只能输出不到 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.2top_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 不过是让结果在“稳”和“浪”之间微调而不是决定模型上下限的东西。这个认知转变之后我再调参就踏实多了。另外一个小技巧也想分享给你每次调参前记录下你当前要解决的问题。比如“回答太长被截断”“总是重复同一句话”“对检索内容不忠实”。然后每次只动一个参数不要动两个。调完跑一遍测试集记录下来再动下一个。这条流程看起来很笨但在乱调一气导致模型突然变得不可用时能帮你快速定位是哪一步改坏的。对于刚入门的朋友我建议你先用一套保守的参数跑通全流程比如temperature0.3, top_p0.9, max_tokens2048大部分任务都不会出大问题。等你对模型输出的规律有手感了再根据场景慢慢微调。大模型的输出天然带有随机性同一套参数可能会给出略有差异的结果这也正常。接受这一点你会少很多焦虑。