长上下文外推与位置编码的边界
一个训练上下文长度假设在 4K 或 8K 档的模型,服务端把 --max-model-len 连同位置编码的 rope_scaling 一起调到 32K、128K,服务能正常起、长文档请求也返回结果。分段测质量时出现两类异常:长文档任务的检索命中率随上下文变长而下降;几百到几千 token 的短任务,质量也低于调整前的基线。两类现象指向同一处:训练长度与推理时可设置的最大长度是两回事,把后者调大只放宽了运行时的序列上限,超出训练长度的位置编码由外推配置给出,而外推配置会改变注意力分布。
训练长度与可设推理长度上限
训练上下文长度是模型属性:位置编码在训练时见过的最大位置就是 original_max_position_embeddings 一类的配置值,权重的旋转频率是在这个范围里学出来的。
推理时的 --max-model-len 是运行时上限,它的取值受 KV 预算约束,由可用显存反推,与训练长度没有绑定。两个值可以不一致:--max-model-len 设到远超训练长度时,框架不一定拒绝,服务照常启动,超出训练长度的那一段位置由外推规则映射,而不是模型学过的表示。
| 量 | 属于 | 由什么决定 | 允许与另一项不一致 |
|---|---|---|---|
| 训练上下文长度 | 模型属性 | 训练配置,如 original_max_position_embeddings |
是 |
--max-model-len |
运行时参数 | KV cache 预算、并发、显存余量 | 是 |
| 外推配置 | 推理配置 | rope_scaling 的 type 与 factor |
是,不设则按训练区间原样使用 |
位置编码外推的方法与代价
RoPE 把位置编码成一组旋转角,不同维度对的频率不同,随位置增长。当推理位置超出训练区间,外推方法做的是把超出部分的位置轴压回、或改频率基,使角度落在训练见过的分布附近。常见做法:
type |
做法 | 代价 |
|---|---|---|
linear |
位置索引按 factor 线性内插,压回训练区间 |
所有位置被压缩,短序列的相对位置分辨率下降 |
dynamic |
在短序列上保持原频率,仅在超长时按需缩放 | 阈值附近行为不完全连续 |
ntk(dynamic_ntk 是 DynamicNTKScalingRotaryEmbedding 实现类名,不是配置取值) |
改变频率基 rope_theta,让高频维度少被压缩 |
基的取值需按模型校准,取错会扰动短上下文 |
yarn |
分频段处理,低频内插、高频外推,可加长度缩放 | 参数更多,效果依模型而异 |
# 位置编码外推配置(键名取自开源框架,取值按模型与实测填)
rope_scaling:
type: linear # linear / dynamic / ntk / yarn ...
factor: 4.0 # 目标长度 / 训练长度
original_max_position_embeddings: 8192 # 训练长度,外推的基准
rope_theta: <base> # 频率基,ntk 类方法会改它
# 服务侧序列上限
max_model_len: 32768
外推配置的核心代价是改变注意力分布。linear 一类内插把整个位置轴压回训练区间,训练区间内的相对距离也被改写,短序列的分辨率随之下降;ntk 一类改频率基,若基值不按当前模型校准,短上下文质量同样会退化。外推换来的是长位置的可用性,代价落在短位置的质量上。
现象
- 长文档任务质量下降。 上下文越接近或超过训练长度,检索命中率越低;把上下文长度档位拉到训练长度之外,命中率继续下滑。
- 短任务出现异常。 开了外推之后,几百到几千 token 的短请求质量反而低于调整前,这一点常被忽略,因为观测集中在长端。
- 性能与质量分离。 prefill 更快、TTFT 更低,只说明上下文注入得快,不说明模型读懂了长上下文。注入速度与检索命中率是两个维度,必须分开测。
触发条件
| 组合 | 撞上的问题 | 先看哪一项 |
|---|---|---|
| 外推开启 + 长文档任务 | 命中率随上下文变长下降 | 长度—质量曲线 |
| 外推开启 + 短请求为主 | 短任务质量低于基线 | factor 是否过大 |
--max-model-len 超训练长度、未配外推 |
超界位置无外推规则 | 是否设了 rope_scaling |
| 滑动窗口 + 压缩池一类稀疏结构 | 窗口外的 KV 不参与注意力 | 窗口长度与压缩池配置 |
滑动窗口一类结构把注意力限制在窗口内,超出窗口的历史不进入计算。这是处理长上下文的另一条路径,靠稀疏化,不靠位置编码外推。判断一个模型能用多长上下文时,先分清它走的是外推还是稀疏路径,两者的失效表现不同:外推的边界在训练长度,稀疏的边界在窗口与压缩池的配置。
定位步骤
- 查训练长度。 读模型配置里的
original_max_position_embeddings或等价字段,确定位置编码的基准区间,不用服务的--max-model-len反推。 - 查外推配置是否开启。 读
rope_scaling的type与factor;未配置即表示按训练区间使用,此时把--max-model-len调到训练长度之外,超界位置没有外推规则,行为不可预期。 - 按上下文长度分段测质量,不只测一个点。 设一组档位(示意:1K、4K、16K、32K、128K),每档内混入不同植入深度(示意:10% / 50% / 90%),记录每档的检索命中率与逐深度明细;命中率随档位下降的曲线即长度—质量曲线,逐深度明细用于看中间位置更难找的现象。
- 性能维度与质量维度分开测。 prefill 耗时随长度递增属于性能维度,只判注入是否正常;检索命中率属于质量维度,才是长上下文是否可用的判据。两者用不同用例,不混算。
- 得分与通过线分开、缺失不等于零。 命中率是要记录、要对比的得分,不直接当通过/失败线;没测过的档位记为
not_measured,不填 0。
记录字段
# 长上下文外推结论记录清单(字段名示意)
model_name: # 模型
train_context_len: # 训练长度,original_max_position_embeddings
max_model_len: # 服务侧序列上限
rope_scaling_type: # linear / dynamic / ntk / yarn / 未设
rope_scaling_factor: # factor
rope_theta: # 频率基
context_ladder: # 质量测试档位,如 1K/4K/16K/32K/128K
needle_depths: # 植入深度,如 10%/50%/90%
retrieval_accuracy_pct: # 每档命中率
by_depth: # 逐深度明细
quality_version: # 质量口径版本,改口径须同步升版本号
quality_status: # measured / not_measured
verdict: # 外推可用 / 短任务退化 / 超训练长度无外推规则
小结
训练上下文长度是模型属性,--max-model-len 是受 KV 预算约束的运行时上限,两者可以不一致。把 --max-model-len 调到训练长度之外时,超出部分由 rope_scaling 的外推规则给出,外推改变注意力分布,长端可用的代价常落在短端质量上。判定时先读训练长度与外推配置,再按一组上下文长度档位分段测检索命中率并记录逐深度明细,把性能维度的注入速度与质量维度的检索命中率分开记录;max_model_len 能设到多大,不等于模型能读懂多长。