一条随横轴不断向右膨胀的内存占用曲线,旁边是一根保持水平的短条

一、发布说明里最短的那一行,是运行时最贵的那一项

据公开报道,KRAFTON 公开了自研语言模型 Raon-1 mini,参数规模为二百八十八亿,原生支持 262144 token 上下文,量化版可在单张 RTX 5090 上运行。这些数字本身不是本篇的依据,我们从里面只取一个问题:把上下文窗口开到几十万之后,本地跑长文本的成本结构发生了什么变化。

先把分工说清。站内那篇讲激活参数的文章算的是权重侧的驻留,也就是”模型本身要占多少地方”,结论是总参数决定空间、激活参数决定每个 token 的访存。本篇不重复那套算法,只算另一半:会话期间随输入长度不断变长的那部分占用,也就是注意力的键值缓存。在一个足够长的会话里,第二块会追上并超过第一块,于是”能不能跑”这件事的决定权就从模型大小转移到了你允许窗口有多大、同时接几路。

一句话概括本篇的立场:窗口上限不是模型能力参数,而是容量配置项。它应该像线程池大小一样被计算、被配置、被记录,而不是写在提示词里祈祷不出故障。

二、键值缓存为什么随长度和并发一起涨,为什么常先耗尽

解码时每一步都要让新 token 去看历史上所有 token 的键与值。为了避免每步重算,推理引擎把每层每个键值头算出的键向量与值向量留驻在显存里,这就是键值缓存。它的大小由一条乘式决定:

缓存字节数 = 二(键与值各一份)乘以 层数 乘以 键值头数 乘以 每头维度 乘以 序列长度 乘以 每个元素的字节数 乘以 并发的序列数

三个后果直接从这里读出。其一,序列长度与并发数是乘在一起的关系,单条会话看着不大,几条同时在线就吃满,很多”我这边跑通了”的结论死在只测了一条。其二,权重是常数,缓存是变量,所以只要会话足够长,先撑不住的永远是后者。其三,键值头数与每头维度是模型结构属性,采用分组键值共享的模型在这一维上天生省得多,这一项在模型配置里就能看到,属于选型时就能拿的分数,不是部署后能补的开关。

还有两个让占用比公式更高的因素值得记账。一是预填充阶段本身要保留中间激活,输入很长时峰值会短时冲高,分块预填充可以降低峰值但会拉长首 token 时间,这是一个明确的取舍而不是免费的优化。二是缓存按块分配,块粒度与序列长度不完全整除会留下碎片;长上下文加多并发时,碎片占比不可忽视。所以估算要留余量,而余量应当来自实测而不是拍脑袋。

三、先把这三个数测出来,再决定窗口开多大

选窗口之前有三个可算口径,各自的测法不同,缺任何一个结论都不成立。

口径 怎么量 用来决定什么 容易犯的错
每单位长度的缓存增量 跑两条只差长度的请求,取引擎报告的占用差;或用乘式算下限后与实测比对 窗口上限、可容忍的最长会话 拿公式当实测,忽略块对齐与元数据
并发上限 用启动日志里的权重驻留与运行时开销算出可用余量,除以单条在目标长度下的占用 同时接几路、是否要限流 自己估权重占用,或用空载显存当可用余量
首 token 延迟随输入长度的曲线 用若干递增的输入长度各跑多次,记录延迟分布而非均值 业务上可接受的长度拐点 只测短输入,长输入的超线性部分看不见

执行顺序也要固定:第一步读引擎启动日志,拿到权重实际驻留与运行时开销,这两个数是量出来的不是估的;第二步测单位长度增量,得到每条会话在每个长度档位的占用;第三步测首 token 延迟阶梯,找出延迟开始变得不可接受的那一档;第四步三条线求交集,交集给出两个配置值——窗口上限与并发上限。这两个值定完之后,才有资格讨论”要不要开长上下文”。

我们内部对本地交付有一条硬规矩:容量结论必须附这三个数的测量记录,缺记录的报告不作为评审依据。因为这三个数彼此牵制,只报其中一个的报告几乎必然偏乐观。

四、长窗口真正贵在哪:三项隐性代价

预填充时长。 长上下文的一次性投入在 prefill,注意力部分还随长度超线性上升。会话越短、越频繁重贴同一份长文档,这项越不划算——同一个文档被反复塞进不同会话,是最典型的浪费形态,而它往往藏在”我们每次都把规范全文给模型”这种看起来严谨的做法里。

并发能力下降。 缓存占满后,引擎只有三条路:少接序列、抢占已有序列后重算、把缓存换出到主存。前两条直接压吞吐,第三条会让尾延迟剧烈抖动。也就是说窗口开大不等于”能力变强”,它在物理上就是把同一块空间从别的会话那里借走。这一点对做 SaaS 的同行尤其关键:单用户窗口翻倍,可能意味着整体服务容量减半。

中段内容的召回问题。 长上下文里位于中间位置的内容被稳定使用的难度更高,这是长文本使用中被反复观察到的现象。后果是”塞进去”不等于”用得上”:一份长文档的关键约束若落在中段,模型可能在推理时忽略它。可执行的动作是位置策略——把硬约束、输出格式、判定标准放在开头或结尾;或者在任务末尾用一小段结构化重述把关键条款再声明一次。这类做法比把窗口开得更大便宜得多。

反过来也要诚实:切片检索有自己的失败模式,召回没命中就等于模型压根没看到,而且跨段关系难以靠多个片段拼出来。本篇不比较两条路线谁更优,只强调它们的成本与失败模式不同,必须按任务选。

五、整篇塞还是切片:三条判据

判断内容该走哪条路,我们只看三条:单篇长度、需要同时可见的篇数、答案是否依赖跨段关系。

内容形态 建议 理由
单篇不长但条款互相引用(合同、法规含例外) 整篇装入 关系型依赖,切片会切断引用
跨大量文档找一条事实(手册条目、历史工单) 切片检索 一次性装不下,命中的那几条才是关键
长文档摘要或逐项审阅 分块处理后汇总 汇总层的输入是各块结论,长度可控
需要精确回溯位置(行号、条款号、页码) 切片且必须带来源标识 标识缺失时答案无法核对
跨文档对照(新旧版本差异) 混合:检索选篇,选中的整篇装入 选篇靠检索,对照靠完整上下文
工具输出与报错日志 结构化压缩后装入 原文冗长,但关键字段必须保留

混合路线里最容易漏的一条是”检索到的片段之间要能对齐”:如果多篇文档的版本不一致,模型会把两个版本的条款混着说。所以检索层的元数据要带版本字段,并在提示里显式声明每段的来源与版本。

六、把上下文策略做成配置项,把量化省在哪说清

窗口大小不该散落在各处提示词里,而应是一份按任务类型的配置表,每行三个字段:窗口上限、超限动作、截断优先级。超限动作只有四种合法取值——拒绝并要求改任务、摘要压缩历史、丢弃最旧的轮次、只保留工具结果的摘要——必须提前选定并可观测,不允许”引擎自己想办法”。截断优先级要写成内容类别的排序,例如系统约束高于紧邻的若干轮对话,高于历史工具输出,高于更早轮次的原文。

日志必须记下这次实际用了多少、为什么截断。最小字段集:任务类型、实际输入长度、生效的窗口上限、是否发生截断、采用的截断策略、被丢弃内容的类别与条数、缓存占用峰值、当时的并发数、首 token 延迟。缺了这组字段,线上出现答非所问时,你无法区分”模型能力不够”和”关键内容根本没进上下文”,而这正是最常见的两类误诊。

量化的意义与不意义要分开讲。权重量化省的是常数项,让模型放得下;键值缓存量化省的是随长度增长那一项,让长会话撑得住——两者不可互换,也不能混谈。”量化后单卡可跑”这类公开说法只覆盖权重驻留这一层,它不代表在目标窗口长度下还能保持同样的并发。缓存量化另有代价:位宽降低后长上下文的数值稳定性与召回质量可能先出问题,而这恰恰是最难在短测试里暴露的部分。所以我们的做法是——先按第三节测出三个数,再决定是否给缓存也降位宽,且改完后必须用同一组长文本任务复测召回,比较的基准是”长会话任务答对没有”,不是显存占用掉了多少。

窗口开大是能力,能不能算清楚才决定它是不是可用。洹拓做本地长文本方案时,评审材料只收两张纸:三个数的实测记录、按任务类型的上下文配置表。两张齐了再谈选什么模型,顺序反过来,返工通常发生在最贵的时候。