大模型Token、上下文窗口与采样参数:从原理到调优实战
2026/9/12 4:11:12 网站建设 项目流程

去年帮朋友调一个本地部署的 qwen2.5:7b,他反复抱怨同一句话:“我这模型是不是失忆了,聊了没几句就把前面的事全忘了,而且字数一多就直接断掉。”当时我打开他的 Ollama 配置看了一眼,上下文窗口是默认值,输出上限也没改,采样参数更是全家桶默认。这些问题看起来毫无关联,其实背后全部指向同一个底层机制——LLM 并不是在“读文字”,而是在做“Token 概率游戏”。

所以我想把这些东西彻底拆开讲一遍:Token 到底怎么切、上下文窗口究竟在限制什么、采样参数为什么能改变模型说话的“性格”。这三件事搞懂了,你不仅能判断模型是“真傻”还是“设置问题”,还能自己动手调出一套顺手的参数组合。这篇内容适合三类人:刚接触大模型的开发者、自己折腾本地部署的玩家,以及被 API 账单和“输出截断”折磨得头疼的调包侠。

1. Token:LLM 眼里根本没有“字”,只有一块块拼图

1.1 模型看到的不是文字,而是一串数字编号

很多人以为大模型像人一样“读”文本,输入一句话,模型在脑子里理解这句话。真实情况不是这样。LLM 的输入输出都建立在 Token 之上,Token 可以理解成模型能处理的最小文本单位,每个 Token 在词表里对应一个唯一的整数 ID。模型看到“今天天气不错”,实际拿到的是类似[15225, 36235, 10590, 11221]这样的 ID 序列。

这一步转化由分词器(Tokenizer)完成。分词器之于大模型,就像编码表之于压缩软件——原始文本必须转成模型能吃的形式,推理结束后再把 Token ID 还原成文字。这个过程听起来简单,但中文的麻烦远比英文大。英文天然有空格作为单词边界,模型大致可以按单词拆分;中文没有空格,一句话连着写,同样一段文字可能有多种切法,分词好坏直接影响模型的理解效率和生成质量。

1.2 中文分词有多“别扭”,看 BPE 就知道

当前主流 LLM 用的基本都是 Byte Pair Encoding,也就是 BPE。BPE 的核心思路很朴素:先把所有字符拆成最小的字节或者单字,然后统计语料里哪些字符组合出现频率最高,把高频组合合并成一个新的 Token。合并是迭代进行的,词典大小是预设的,比如常见的 32K、50K、100K 词表,合并到目标规模就停。

拿中文举个例子。假设语料里“上下文”这三个字经常连在一起出现,BPE 会把“上下文”合并成单独一个 Token;但如果“上下”出现频率特别高,“文”经常单独出现,那也可能先合并“上下”,再分别对“文”做处理。这导致一个现象:同一个词在不同上下文里的切分结果可能是不同的。

我在本地用 Qwen 的分词器做过一个简单测验,一句话“本地部署的大模型需要合理的上下文管理”,得到的结果是“本地 / 部署 / 的 / 大模型 / 需要 / 合理 / 的 / 上下文 / 管理”,常见词大多能切成完整 Token,但“大模型”在某些少见搭配里可能被切成“大”和“模型”。这种不确定性直接影响了 Token 数估算和计费判断。

1.3 中文环境下 Token 的粗略估算公式

业界多年攒下来的经验是:英文里平均 1 个单词 ≈ 1.3~2 个 Token;中文里 1 个汉字 ≈ 0.6~1.5 个 Token,不同模型差异很大。说白了,如果你用 GPT-4 类模型,1000 个汉字大概要消耗 1500~2000 Token;换个中文优化做得好的国产模型,可能 1000 个汉字只需要 700~900 Token。对于 UTF-8 编码里的生僻字、特殊符号、代码片段,Token 消耗会明显上涨,一个代码缩进或者一个特殊符号都可能是独立 Token。

具体估算时,我自己常按“汉字数 × 1.2 到 1.5”做保守预算,留出多轮对话历史和系统提示词的余量。如果你在算 API 成本或者为了避免上下文溢出,我建议直接用tiktoken(OpenAI 系列)或者各模型自带的分词器接口做精确统计,不要靠感觉。一句经验:宁可把预估值乘个 1.5,也不要卡着窗口上限报文案,否则线上动不动就截断,客户体验非常难看。

1.4 控制 Token 消耗的四个实用招数

控制 Token 消耗不是抠门,是刚需。第一,精简系统提示词,把“冗长的角色设定+一堆限制条件”压缩成“场景+要求+反例”,例如“你是客服,回答不超过 50 字,禁止反问用户”,这比三行大道理有效得多;第二,多轮到轮隔一段时间把历史对话压缩成摘要,后面每次请求只带摘要加最近几轮原文,能省大量 Token;第三,能用结构化的输出格式就用结构化输出,比如让模型输出 JSON,这比让它自由发挥再加解析器稳定得多;第四,长篇文档放进上下文时先切片,按需检索再送进模型,别一股脑把所有内容都塞进去。

2. 上下文窗口:模型记忆的物理边界

2.1 上下文不是硬盘,更像一张随时会被填满的工作台

上下文窗口(Context Window)指的是模型单次推理时能“看到”的最大 Token 数量。我常用的一个类比是:上下文窗口不是硬盘,而是你面前的一张桌子。硬盘能存几百万字的资料,但桌子就这么大,桌上摆不下,你只能挑一部分放上去用。每聊一句,桌上就多几张纸;桌子满的时候,要么把最早的纸扔到地上,要么把有些纸揉碎了压缩着放,不然新内容就没位置。

从技术角度看,Transformer 在处理序列时每个位置都要和之前所有位置计算注意力,理论复杂度是 O(n²)。上下文翻一倍,计算消耗可能涨三到四倍。这也是为什么模型官方参数写 128K 上下文,真正部署时却常常只用 8K 或者 16K——内存和时间成本摆在那里,不是你想开多长就能开多长。

2.2 上下文窗口被“三张嘴”吃掉了

很多人以为上下文窗口只算“我输入的问题”,其实它要装的东西有四部分:系统提示词、多轮历史对话、本次新输入、模型的输出。举个例子,窗口是 8192 Token,系统提示词占了 800,历史对话占了 5000,你这次输入有 1000,那留给模型输出的只剩 1392 个 Token。如果模型本身就是个话痨,一下输出到 1600 个 Token 就被强行截断,很多接入方反馈“回答到一半没了”,多半就是这个原因。

更残酷的是,模型在做自回归生成时,已经生成的内容会继续占用上下文。输出写得越长,剩余可用空间越少。很多本地部署玩家把max_tokens设成 4096,结果上下文只有 4096,系统提示词和历史轮次稍微多一点,模型连输出 500 个 Token 都费劲。

2.3 本地部署的 qwen2.5:7b“记不住”上下文,到底卡在哪

这里直接回应开头的那个问题。用 Ollama 本地部署 qwen2.5:7b,聊几句就“失忆”,绝大多数情况下不是模型坏了,而是上下文窗口没设置。Ollama 的默认num_ctx是 4096(部分旧版本是 2048),也就是说模型单次推理只关心最近的 4096 个 Token。你聊了十轮,单单历史记录就可能超过 4096,系统只能截掉最前面的内容,模型当然“忘”得飞快。

解决办法并不复杂。在 Ollama 中可以通过 Modelfile 修改默认参数,先ollama show qwen2.5:7b --modelfile查看当前配置,再创建新的 Modelfile,写入:

FROM qwen2.5:7b PARAMETER num_ctx 32768 PARAMETER num_predict 8192

然后执行ollama create qwen2.5-7b-32k -f Modelfile,运行时用ollama run qwen2.5-7b-32k加载。如果你不想重建模型,也可以再请求时通过 API 传入options.num_ctxoptions.num_predict覆盖默认值。

上下文调大的代价是显存和内存占用直线上升。7B 模型权重一般几个 GB,但 KV Cache 会随上下文长度增长,上下文 4K 和 32K 之间的内存差距能达到 1GB 甚至更多。在量化模型和普通消费级显卡上,盲目开 128K 往往导致推理速度骤降,甚至直接显存溢出崩溃。我个人的建议是:日常对话用 8K~16K 足够,需要处理长文档再临时调到 32K,不要一上来就拉满。

2.4 上下文工程:把最值得记的内容留在窗口里

上下文工程说到底就一句话:在有限的窗口里,决定让模型看什么、不看什么,以及以什么顺序看。业界经常提到的 RAG(检索增强生成)就是典型的上下文工程——从外部知识库检索相关内容,拼接成提示词再送给模型,而不是把整个资料库都塞进上下文。

更精细的是上下文压缩。我处理长对话时常用的套路是:保留最近 3~4 轮完整原文,更早的历史每隔固定轮数总结成摘要,比如把用户前两小时聊的需求提炼成“用户已确认:预算 5000,偏好简约风格,客户希望月底前交付”。这样既保留了关键信息,又不会让历史对话淹没新输入。

还有一个很多人踩过的坑:“Lost in the Middle”,也就是模型对一段长上下文中间部分的内容记忆明显弱于开头和结尾。如果你把关键指令放在很长的系统提示词中间,模型可能根本“注意”不到它。最好的做法是把最重要的约束放在开头或结尾,中间留次要内容。这一条对超长上下文场景尤其关键。

3. 采样参数:在确定性与创造性之间找平衡

3.1 模型生成 Token 的真实过程:不是“选最优”,是“掷骰子”

模型每生成一个 Token,其实是:根据当前上下文,对词表里每一个 Token 计算一个概率分数,得到一个概率分布,再从这个分布里挑一个作为输出。如果模型每次都选概率最高的那个 Token,这叫“贪心解码”,结果最确定,但容易造成语句呆板、机械重复。

而采样(Sampling)指的是按概率分布随机抽 Token,让分数低的 Token 也有机会被选中。这样做的好处是生成更丰富、更有创意,坏处是可能产生逻辑跳跃或无关内容。采样参数就是用来控制这个“抽签”过程的旋钮。核心参数有四个:Temperature、Top-p、Top-k 和重复惩罚。

3.2 Temperature:把概率分布“烫平”或“冻僵”

Temperature(温度,简称 T)控制分布的形状。计算时核心是在 softmax 里除以温度值:

P(i) = exp(score(i) / T) / Σj exp(score(j) / T)

T=1 是原始分布;T<1 时,分数差异被放大,高概率 Token 更容易被选中,模型更保守;T>1 时,分布变得更平,低概率 Token 也有机会出头,模型更发散、更有“想象力”。

实际参数怎么给?我给客户的基准线是:事实问答、代码生成、JSON 结构化输出这类任务,T 设在 0.1~0.3 之间,建议从 0.2 起步;文案写作、头脑风暴、故事生成,T 设在 0.7~0.9 之间,太低了容易单调,太高了容易出现词不达意。还有一个细节:很多平台要求 T 大于 0,所以如果 API 返回一个固定种子加 T=0 会报错,就把它改成 0.01。

3.3 Top-p:按累计概率砍掉没人气的尾巴

Top-p 又叫核采样(Nucleus Sampling)。做法是先按概率从高到低排序 Token,然后不停地选进去,直到累计概率达到 p 的值。p=0.9 的意思是:只从覆盖累计概率 90% 的候选 Token 里采样,剩下那 10% 概率的小尾巴直接不管。

Top-p 解决的是“长尾问题”。词表几十万,大部分 Token 虽然概率非零,但单独看都是非常不靠谱的选项。不裁掉的话,模型偶尔会冒出一个完全跑偏的词。配合温度使用时有个常见误区:不要 T 和 Top-p 都拉满,通常固定一个,调另一个。我自己常用的组合是“T=0.7 + Top-p=0.9”做创意写作,“T=0.2 + Top-p=0.8”做强一致性任务。

3.4 重复惩罚与“话痨循环”的对抗

重复惩罚(Repeat Penalty)会对最近出现过的 Token 进行减分,让它不容易再次被选中。频率惩罚(Frequency Penalty)和存在惩罚(Presence Penalty)也类似,区别在于频率惩罚是“出现得越多,惩罚越重”,存在惩罚则是“只要出现过就减分”。在本地部署 Llama、Qwen 这类模型时,如果发现模型说着说着开始循环同一句话,特别是温度调到 0.6 以上的时候,重复惩罚是最容易被忽略的解药。我通常把重复惩罚设置在 1.05~1.15 之间,太低压不住往复,太高会让语言变得生硬、口语感严重下降。

Top-k 则是另一种过滤方式:只保留概率最高的 k 个 Token 参与采样,k 越小越保守。很多新模型在训练和部署时默认已经做了一套采样策略,你如果调了 T 和 Top-p 效果还不理想,再考虑动它,别四个参数同时乱拧。

3.5 不同场景下的参考参数套餐

任务类型TemperatureTop-p说明
事实问答 / 检索总结0.1~0.30.8~0.9尽量稳定、忠于原文
代码生成 / 函数补全0.2~0.40.8~0.9减少语法错误和编造 API
结构化输出(JSON等)0.1~0.20.8固定格式比创意更重要
营销文案 / 头脑风暴0.7~0.90.9~0.95保持发散但不至于跑题
角色扮演 / 小说创作0.9~1.10.9~0.95可适当放宽,配合重复惩罚防复读

这套参数只是出发点,不同模型、量化精度甚至提示词长度都会影响最终效果。调参的真谛永远是:先固定其他变量,一次只动一个参数,改完做一批测试样本,用结果说话。

4. 一次实际推理流程:Token、上下文、采样如何协同工作

4.1 从输入到输出的完整链路

把三块内容拼在一起,一次完整推理大致是这样:用户输入文本先被分词器切成 Token 序列并转成 ID,模型给这些 Token 算嵌入向量,再经过几十层 Transformer 前向传播,每层都在做注意力计算,让每个 Token“看到”上下文窗口内其他 Token 的信息。最后一层输出的是词表中每个 Token 的分数,再套一层 softmax 变成概率分布。采样阶段根据温度、Top-p 等参数调整分布,抽中一个 Token ID,转成文字拼到答案上。接着把新生成的 Token 拼在原始序列末尾,再执行下一轮计算。

整个过程是一个 Token 接着一个 Token 产生的,所以叫自回归生成。这意味着生成速度本质上取决于模型大小、显存带宽和 KV Cache 命中率。这也是为什么上下文越长,生成越慢——每一步注意力都要照顾越来越多的历史 Token。

4.2 上下文不足和参数失衡的“连锁翻车”

介绍一个实际案例。有次我在本地用 7B 模型处理一份 30 页的合同,上下文窗口开的是 8192,我直接把整份文档贴进去。结果系统自动截掉前半部分,模型在回答里一本正经地写着“根据合同第 3 条……”——但它根本没看到第 3 条,因为第 3 条在这个长达 40 万字的文档里,早就不在当前上下文范围内了。这就是上下文资源分配不当引发的“自信幻觉”,模型不知道自己没看过那些内容,只会基于现有信息强行圆话。

另一种翻车是参数失衡。生成长故事时把 T 拉到 1.2,又把 Top-p 开到 0.99,结果模型开始频繁出现前后矛盾的人名、混乱的时间线,甚至自己编出逻辑不通的情节。创造力和失控之间,隔着的就是对概率分布的尊重。采样参数本质上决定了模型每次落子的“胆量”,它不是越“大”越好。

4.3 我的本地部署调优经验日记

我日常在 Ollama 跑 qwen2.5:7b,用得比较顺手的配置是这样:写代码时temperature=0.2, top_p=0.8, repeat_penalty=1.05,上下文 8192,输出上限 2048;闲聊或按需求写长文时,temperature=0.8, top_p=0.92, repeat_penalty=1.08,上下文 16384,输出上限 4096。如果任务需要处理很长的参考资料,我会先把资料做摘要,再把摘要放进上下文,而不是硬生生开一个大窗口。

这种经验不一定完全适应别人的环境,但思路是通用的:先确定任务要求“稳”还是“活”,再确定该给多少上下文,最后盯着输出会不会截断、会不会复读,反推参数往哪个方向调。

5. 高频问题与避坑实战记录

5.1 “已达到输出 Token 上限,回答被截断”怎么破

这个问题在 web 端对话框和 API 调用里都常见。原因通常分两类:一是接口里设了max_tokensmax_new_tokens不够大;二是上下文窗口剩余空间不足以容纳完整输出。处理路径很清晰:先看max_tokens是不是太小,把它们调到窗口允许的合理值,比如 16K 窗口配 4K 输出;再看历史消息和系统提示词是不是塞太多,该压缩就压缩。用 Ollama 的话,对应参数是num_predict,注意它要明显小于num_ctx

5.2 本地 Ollama 部署的模型“智商在线但记忆离线”

现象是单轮回答质量不错,一到多轮对话就答非所问。随机排查顺序:第一步,确认num_ctx是否够大;第二步,确认有没有因为显存不足导致模型被迫换过部分层到内存;第三步,检查是否有版本不对导致上下文被强制降到 2048 的问题。很多老版本 Ollama 默认上下文小得可怜,升级后行为也可能变化,所以每换一个新版本,都该重新确认加载参数。

之前我遇到一个更隐蔽的情况:模型窗口开到了 32K,但量化后的权重太小,推理时 CPU 和 GPU 频繁换进换出,耗时翻倍,用户以为“卡死了”。这提醒我们,上下文长度不是白给的能力,它是要拿显存和速度来换的。

5.3 采样参数与幻觉的关系

关于幻觉有个常见误解:认为温度调高就导致幻觉。严格说,幻觉主要来自模型自身的知识缺陷和上下文不足,但温度确实会放大胡说的概率。比如用 T=1.0 做事实问答,模型会在不确定的地方“自由发挥”,编出看起来合理的错误答案。反过来,T=0 也不能保证永远正确,因为在已经学错的知识点上,最高概率答案本身就是错的。

所以稳妥的做法是:事实类任务把温度压低,同时把可靠的参考内容放回上下文;创意类任务才放开温度,并容忍一定程度的试错和修正。参数只是工具,搞清楚任务边界才是真正重要的事。

5.4 一个高效的 Prompt + 参数验证闭环

最后分享一个我常用的验证方法:任何一个新的提示词模板上线前,都跑至少 10 条测试样本,记录“每轮输出是否达标、Token 消耗、是否截断”。如果 10 条里有 2 条以上出现截断,先砍输出max_tokens或压缩历史;如果有 3 条以上出现明显逻辑混乱,再调低温度和 Top-p。每次只改一个变量,改完重新跑一批,形成自己的调优记录表。这样过了一个星期,你手上就会积累一套针对自己业务场景的可靠参数,而不是今天听人说这个好就改,明天看帖子说那个妙又试。

我在本地和线上 API 场景都折腾过不少模型,最深的感受是:模型能力是一回事,你怎么“投喂”它、给它多大“桌面”、允许它多大“胆量”,是另一套完全值得认真对待的工程问题。能在一个小窗口里把事说清楚,比盲目追求大上下文和“满配置参数”更考验功底。希望这篇拆解能帮你少走点弯路,尽快把自己的模型调成真正顺手的样子。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询