上下文长度和速度的取舍

两个此消彼长的指标

知识库搭起来之后,用户反馈最集中的两句话永远是:答得怎么这么慢,还有怎么问着问着它就忘了前面。慢,是生成速度的问题;忘了,是上下文长度的问题。这两个指标天生不对路:想把上下文拉长,速度必然往下掉;想快,就得勒紧上下文的裤腰带。这一章就专门算这笔账。

先明确两个衡量速度的词。首字延迟(TTFT)是从你按下回车到它蹦出第一个字的时间,生成速度(TPS)是之后每秒蹦出几个词(token)。它们是两种完全不同的体验:等三秒后流畅地出完,比零点五秒蹦一个字蹦到天荒地老强得多。流式输出为什么重要,就是把首字延迟和生成速度分离开,用户先看到字,心理上等待就消失了。

这两个词后面反复出现,先在此建立直觉。首字延迟主要花在读提示上:RAG 场景提示动辄两千 token,光 prompt eval 就要一两秒,加上模型前向计算的前期准备,用户体感的起步价就在这里。生成速度则是每蹦一个词都要跑一次完整前向,它决定一段回答要等多久。优化方向因此截然不同:前者靠缩短提示和缩短模型深度,后者靠并行计算到位。

对 RAG 应用,这套账还要加上检索耗时和 embedding 耗时两个配角。一次完整问答的时间,往往一大半花在模型生成上。把全链路的耗时拆开看,哪段慢优化哪段,比闷头换显卡有效。

上下文长度:窗口越大越好吗

营销号总爱鼓吹百万上下文,好像窗口越大模型越聪明。真相是上下文长度有三重代价,适合才是最好的。

补一点,这三重代价在不同场景的权重不一样。长文档总结场景你认命承受,因为任务本身就是要读十万字;但 RAG 问答里大窗口的收益实在有限,检索已经替你把内容浓缩到两千 token,给一百万窗口也用不上。所以别为想象中的需求买单,另外窗口大不等于记得牢,衰减问题上回聊过,这儿不重复。

第一重代价是计算量。注意力机制的计算量近似随上下文长度平方增长,两倍上下文约等于四倍计算。八千上下文觉得很顺的模型,硬拉到三万二,同价位硬件上速度会肉眼可见地垮。这是算法层面的硬约束,任何工程优化都只能缓解不能消除。

第二重代价是显存里的 KV 缓存。模型每生成一个词,要把前面所有内容的键值对存下来供后面参考,这部分缓存随上下文线性占显存。七十六亿参数的模型,权重本身量化后大约五个 GB,而三万二上下文的 KV 缓存能再吃掉好几个 GB。很多人的 Ollama 报显存不足,不是模型装不下,是被缓存撑爆的。

它粗略的估算公式值得记一下:KV 缓存大小约等于层数乘以隐藏维度乘以序列长度再乘以每元素字节数。不用算精确,记住两点结论就行:第一,同一模型上下文翻倍,缓存稳定翻倍;第二,模型越大层数越多,每千 token 的缓存成本越高。实践中显存吃紧又不能降模型时,把每用户上下文从一万六砍到八千往往立刻缓过来,而用户根本注意不到,因为正常问答用不到那么长。

第三重代价是上一章提过的注意衰减。上下文拉得越长,模型对中段的利用越差,回答可能变得更粗。百万窗口听着威风,真往里塞二十万字再提问,正确率未必比精挑细选的五千字高。RAG 场景的金科玉律是少而准,不是多而全。

上下文长度 适用场景 首字延迟感受 显存压力(8B 量化)
2048 短问答 极快 约 6 GB
4096 RAG 标准配置 快 约 7 GB
8192 长文档加多轮对话 可接受 约 8 到 9 GB
32768 整篇报告投喂 明显卡顿 远超单卡显存

实践中的选择很务实。知识库问答,四千零九十六是甜点,top 5 片段加历史对话绑绑有余;想喂整篇长文档做总结的,再上八千到一万六也不迟。真需要十几万上下文,别硬扛,用检索分而治之——这不正是前面 ol-0304 整篇讲的东西。

想验证衰减作用到自己头上,可以做组对照。往提示里塞三万字文档,中段藏一个业务问题的答案,开头结尾各放一个干扰项,然后提问。看模型回答的是中段还是被两头带跑。多数人做完这个实验后就不再迷信大窗口了,乖乖回去优化检索。这个实验也是说服产品同事最有效的演示,比列数据有说服力。

速度:先把模型换成量化版

想让马跑,先选对马。同一个模型不同量化级别,速度差着好几倍。量化是把权重从 16 位浮点压成 4 位整数这类操作,画质损失轻微,体积和速度全面受益。Ollama 默认拉下来的七八十亿参数模型就是 Q4 量化版,这是社区在效果和性能间精选的平衡点,也是多数场景的默认选择。

模型的另两个决定性因素是参数规模和硬件。输入不太长的场景下,七八十亿参数的量化模型配一张消费级显卡,生成速度每秒二三十个 token,用户体感流畅;放大到七百亿参数,同样显卡可能只剩每秒三五个 token,完全没法做产品。CPU 纯算也能跑,速度回到每秒五到八个 token,只适合内部演示。

硬件上的决策见下表,按经验给个大致档位:

硬件配置 适合的模型规模 约生成速度 适合场景
核显或旧 CPU 1 到 3B 5 到 10 token/秒 演示玩具
16 GB 内存无独显 3 到 7B 8 到 15 个人尝鲜
RTX 3060 12GB 7B 量化 20 到 30 内部小工具
RTX 4070 16GB 7B 到 14B 15 到 25 团队知识库
RTX 4090 24GB 14B 到 32B 8 到 18 多并发生产
双 4090 或专业卡 70B 量化 3 到 6 效果优先场景

速度优化的常用的还有三招。第一,用量化程度更深的模型版本,比如 Q4_K_M 平衡、Q5_K_M 更清晰,可以都试一遍对比。第二,开 flash attention 类优化,Ollama 较新版本已经默认支持,老版本升级有惊喜。第三,并发场景下跑多实例或者按路由分配不同规模的模型,把简单问题派给小模型,难题再用大模型,系统吞吐最大化。

不同量化级别的取舍值得单独体会。Q4_K_M 是公认甜点,体积压缩到原始的四分之一,多数任务质量损失不明显;Q8 几乎无损但体积翻倍;Q3 再往下就开始明显犯蠢了,只适合极端硬件受限的场合。建议的做减法路径:从 Q4_K_M 起步,拿评测集跑一遍分数,如果掉了百分之五分以上再往上调,而不是无脑选最高精度。硬盘和显存富余的时候随手多拉几个版本对比,工作半小时收益长远。

显存账本:一个模型到底吃多少

显存占用有两块:权重和 KV 缓存。权重只看参数量和量化级别,七八十亿参数 Q4 量化约五个 GB,Q8 约八个 GB,FP16 需要十四个 GB 以上。KV 缓存则与上下文长度和实际请求数成正比,一个三万二上下文的请求,缓存就可能比权重还大。

一个很反直觉的现象:显存占用是随在线用户数动态变化的。一个用户请求时一切岁月静好,五个用户同时发长上下文,显存可能瞬间被打爆导致 OOM。Ollama 有排队机制不会立刻崩,但吞吐会塌。上线前必须做的功课是按预期并发数估算总显存,留足余量,宁可少拉点上下文也别赌在线峰值。

讲一个真实的案例。某团队测试时单用户一切完美,上线第二天显存报警。查日志发现一个用户连续问了十几轮,每轮上下文都在累积,KV 缓存滚到了将近六个 GB,恰好又赶上另外一个用户同时长提问,两相叠加超过显存上限。解决方式不是加显卡,而是在业务层限制单会话最大轮数、并把超过阈值的历史对话压缩成摘要,显存占用立刻回到健康区间。业务规则比硬件便宜,这是我书里最值钱的一条经验之一。

CPU 跑模型不要绝望。内存的带宽近年来宽裕得很,七八十亿量化模型在 DDR5 上跑每秒十几个 token 并非玄学,对付内部知识库的低频访问完全合格。它的优势是可以常驻不占显卡,办公机也能部署,半夜让它慢慢跑批处理灌库最为合适。

还需要区分两个场景的算力需求:embedding 是短输入高吞吐,只在灌库时成片跑;生成模型是长输入流式输出,高峰全在白天活跃时段。监控曲线画出来,决定哪部分加钱,而不是对硬件一视同仁地堆。

给一个综合的取舍实例。内部八小时工作制的知识库,同时在线峰值五十人,请求集中在上午十点与下午三点各一波。配置方案是小模型 Q4 量化加八千上下文,压测确认 P90 小于八秒,显存留百分之三十余量。晚间的灌库任务直接排到半夜执行,不与白天抢资源。这套配置不贵,但满足所有约束,关键是每一项都能拿出压测数据支撑。

读完前面所有数字,你的项目该怎么定?给一个可复用的决策流程。第一步定效果目标,知识库问答的准确率要多少,这决定模型规模上限。第二步定并发和延迟红线,五十人 P90 八秒和五人 P90 三秒是完全不同的配置。第三步在预算内选硬件,把剩余预算全分给显存和内存——大显存比顶级芯片更解渴。第四步实测调参,从标准配置出发逐项微调,每改一项跑一遍评测,数据涨了才认账。

实测:压测你的本地服务

理论都是虚的,动手测一遍最踏实。先看单条基准,Ollama 启动后跑这条命令能拿到当前机器的生成速度:

ollama run llama3.1 --verbose "请用一百字介绍知识库的概念"

输出末尾会给出 eval rate(每秒生成 token 数)和 prompt eval rate(每秒处理的提示 token 数)。前者对应生成速度,后者对应读提示的速度(RAG 场景提示长,它很关键)。

多并发压测需要写一点脚本,同时发若干个 generate 请求记录耗时分布。简易做法是用 Python 的并发库同时发二十个请求,统计平均延迟和最大延迟。数字会让你对几台机器同时访问有具体感觉。同一个模型在并发五和并发十下的延迟差异,比任何教程文字都直接。

压测时别只看平均值,长尾更要命。平均延迟一秒但百分之五的请求要八秒,用户感知照样很糟,九十线(P90)和九十九线才是服务质量的真实标尺。常见现象是前几个并发很好,到某个数后曲线突然变陡,拐点位置就是你该扩容或限流的信号。压力先于用户压上来,晚上加机器好过白天出事故。

日常监控也有现成工具。Ollama 暴露的指标接口能拿到运行时长、模型列表和正在处理的请求数,配 Prometheus 做大盘是标准做法。没有这套设施也不怕,定期看看系统资源占用曲线,发现显存长期九十多就早点扩容,别等线上 OOM 了再救火。性能话题继续深挖可以看 ol-0405 讲成本那篇,里面把硬件投入换算成了具体的人民币账目。

常见问题

同样的硬件,为什么我突然慢了好多

八成是别的程序在抢资源。浏览器开几个 AI 网页、其他容器在跑、屏保挖矿脚本,都能吃掉显卡。先 nvidia-smi 看占用时间线,Ollama 里有并发的多个模型还会互相挤显存。定期清理不用的模型,ollama list 检查列表,不用的 ollama rm 清理干净。

流式输出真的能让响应变快吗

客观上没有,总耗时一样。主观上体验判若两地:流式让用户两秒内看到第一个字而不是花十秒看一个完整结果弹出。人的时间感知里,分段反馈的等待远比憋大招的等待好熬。配套的前端细节是边出字边渲染,而不是等完整段再排版,这些加起来是 RAG 体验的决定性因素。接入方式见 ol-0404。

上下文设多少都不够用怎么办

先反省是真的不够还是塞得太鲁莽。RAG 的正确姿势是精炼内容而不是堆字数。如果确实需要长上下文(法律卷宗、论文),考虑分层:先检索粗筛再分段细读,用两阶段查询代替一次性硬塞。此法能把有效窗口放大几倍且不损失体验,比乞求更大窗口的模型现实得多。

CPU 跑和 GPU 跑效果一样吗

生成内容完全一样,模型权重是同一个。差别只在速度,GPU 利用上千个并行核心加速矩阵乘法,CPU 只能靠多核硬算。偶发性的数字精度差异可以忽略,日常使用感知不到。家底不厚时先用 CPU 跑通整个流水线,效果验证好了再上显卡,顺序别颠倒。

为什么提示词明明很短,第一次提问还是要等好几秒

首字延迟里有几笔账与提示长短无关。模型如果处于闲置状态,权重可能被换出到内存甚至硬盘,唤醒加载就要时间;再加上 Ollama 的请求队列、系统级的前向初始化,冷启动开销主要集中在第一次。解决方案是预热:服务起来后先发一条空请求让它热完,或者保持间隔心跳保活。生产环境这句尤其重要,第一批用户的体验全看没看这一步。