1M 上下文怎么来的:RoPE、YaRN 与 theta
这台模型只在 65,536 token 的训练窗口里见过世界,却提供 1,048,576 的上下文:65,536 × 16 = 1,048,576,那个 16 就是 YaRN 的 factor。从 RoPE 的旋转直觉讲到"模型内部有两套位置刻度"——压缩 KV 为什么要用自己的 theta 160,000,以及你能调的其实只有 --max-model-len。
本篇属于系列 关于deepseek部署你要知道的一切 · 第 8 篇
来源:vLLM Recipes · deepseek-ai/DeepSeek-V4.1-Flash(页面标注 Updated 2026-09-20)
说明 本文是系列《关于deepseek部署你要知道的一切》的第 8 篇,面向只熟悉 vanilla transformer 的读者,把官方页面里的术语逐个拆开解释。文中所有数字、参数与命令均来自上述页面,未作独立核实。
一句话结论
这台模型只在 65,536 个 token 的窗口里训练过,却对外提供 1,048,576 的上下文。
这不是魔法:65,536 × 16 = 1,048,576,那个 16 就是 YaRN 的 factor——
把位置编码的刻度“拉伸”到训练时没见过的长度,靠的是外推(extrapolation)而不是重训。
记住两件事就够了:长上下文是“撑”出来的,不是“训”出来的;而且模型内部有两套位置刻度,
压缩 KV 用自己的一套 RoPE 底数(theta 160,000)。
先复习:注意力其实不知道“顺序”
vanilla 注意力有个天生缺陷:它是置换不变的。把句子里的词打乱,只要向量集合相同, 注意力算出来的结果是一样的——它只知道“有哪些 token”,不知道“谁在前谁在后”。 所以必须有额外的位置编码。
RoPE(旋转位置编码)的做法很优雅:不让位置以“加法”的方式加进向量, 而是按位置把 Query 和 Key 向量旋转一个角度。位置越靠后,旋转的角度越大; 两个向量的内积(注意力分数)就自然带上了“相对距离”的信息。
关键在于”角度随位置增长有多快“,而这件事由一个底数 theta 决定:
- theta 越大 → 旋转越慢 → 相邻位置的角度差越小;
- theta 越小 → 旋转越快 → 位置区分度高,但远处的角度会绕圈重叠,模型分不清距离。
这就是长上下文要调 theta 的原因:想让远距离的位置关系不“绕糊涂”,就得把刻度放得更宽。 (顺带说明:页面对主干流用的 theta 没有给出数字,只有压缩 KV 那一个是明说的 160,000。)
YaRN:把没见过的长度“插值”出来
YaRN 是一类上下文扩展方法(名字来自 “Yet another RoPE extensioN”)。 它解决的问题是:训练时模型只见过 65,536 长的位置,你突然喂 1M,位置索引就跑出了训练分布。
它的思路大致是(这部分属于通用背景,不是页面原文):
- 把新的位置映射回训练时见过的范围——像把一张长图缩放回原来的画框,让“位置差”落在熟悉的区间;
- 分段处理频率:高频分量(负责近距离分辨)尽量保持不动, 低频分量(负责远距离)更多地去插值——因为远距离本来就是外推的重灾区;
- 顺带调整注意力的温度,补偿长度变化导致的分数分布漂移。
页面上那句简短的话,就是在说结果:
1,048,576 tokens,通过 YaRN、factor 16,从 65,536 token 的训练窗口外推得到。
算一下:65,536 × 16 = 1,048,576。factor 就是“放大倍数”,而这个数字是 checkpoint 里定死的——
你不用(也不能)自己调,它是训练时的选择。
为什么压缩 KV 要用自己的 theta
这是本篇最有信息量的一句话:
压缩 KV 使用它自己的 **RoPE theta(160,000)**做旋转,因为一个 latent 代表多个 token, 所以它的位置彼此间隔比原始流更大。
翻译一下。回忆第 5 篇:远处的 token 被压缩成 latent,一个 latent 可能代表 2 个 token(压缩比 1 和 2)。 于是:
- 原始流里,位置是 1, 2, 3, 4, …
- 压缩流里,位置可能是 1, 3, 5, 7, …(每 2 个 token 一个 latent)
两者“刻度”不一样。如果压缩流沿用和原始流一样的 theta, 它看到的相对距离就会被系统性放大(或者缩小),位置关系就失真了。 所以压缩 KV 换了一把更宽的尺子:theta 160,000。 一句话总结:不同的时间刻度,要用不同的尺子。
部署视角:你能调什么,不能调什么
不能调的:YaRN 的 factor(16)、两套 theta、训练窗口——都在 checkpoint 里。
能调的:--max-model-len。官方 MI355X 基准命令里直接写 --max-model-len 1048576,
也就是把上限开到顶。而页面在调优一节的建议是:
--max-model-len:按你的 prompt 加输出长度来设定上下文上限。
为什么不干脆永远开到 1M?因为在真机上它会影响调度和显存规划:
上下文上限越大,调度器要预留的 KV 空间越大,能同时承载的并发就越少。
官方在 KV cache offloading 的验证里只用了 --max-model-len 16384——
长上下文能力和长上下文预算,是两件事。
另外值得记住:上下文长度不是单靠位置编码撑起来的。 第 5 篇的两级稀疏注意力负责“算得起”,第 4 篇的 KV 压缩负责“存得下”, 本篇的 RoPE/YaRN 负责“位置关系不乱”。三件事都在,1M 才成立。
三个常见误解
你以为:能做 1M,就代表它在 1M 上的能力等于在 64K 上的能力。 实际上:外推总有衰减。位置编码能把“位置关系”撑过去,不代表远处的信息密度、注意力模式和训练时一样。 官方页面没有给长上下文的评测数据——这本身就是一条信息:你要在自己的任务上实测。
你以为:长上下文是“多喂一点 token”,成本线性增长。 实际上:prefill 阶段算的是注意力(即使稀疏,仍然有成本),TTFT 会明显变长; decode 阶段读的 KV 更多。成本主要在 prefill 和首字延迟上,而不是显存上(这台模型的 KV 很省)。
你以为:把 theta 调大是部署时的参数。
实际上:theta 是训练时写进权重的(或者至少和权重一起发布的配置)。
你能改的是 --max-model-len,改不了模型“怎么看位置”。
术语卡片
| 术语 | 中文 | 一句话定义 |
|---|---|---|
| RoPE | 旋转位置编码 | 按位置旋转 Q/K 向量,把相对距离编码进注意力分数 |
| RoPE theta | 旋转底数 | 决定角度随位置增长的快慢;越大则刻度越宽、越适合长上下文 |
| YaRN | — | 一类上下文扩展方法,通过频率插值把位置映射回训练区间 |
| factor 16 | 扩展倍数 | 65,536 × 16 = 1,048,576,即本模型的外推倍数 |
| training window | 训练窗口 | 训练时见过的最大长度(65,536),超出部分都算外推 |
--max-model-len |
上下文上限 | 部署时可调的请求长度上限,影响调度与 KV 预留 |
小结
- 1M = 65,536 × 16:靠 YaRN 外推,不是重训;factor 与 theta 都在 checkpoint 里。
- 模型内部有两套位置刻度:原始流一套、压缩 KV 一套(theta 160,000),因为后者一个位置代表多个 token。
- 你能调的是
--max-model-len:开到 1M 是“能力上限”,实际给多少预算取决于并发与延迟目标。
下一篇:《BF16/FP8/MXFP4/MXFP8/UE8M0:数字格式与 511GB 显存账本》——把这个模型的权重量逐个数字类型算清楚。
讨论
这里是静态站点,没有内嵌评论区。如果这篇文章对你有用,欢迎通过 RSS 订阅后续更新。