RoPE scaling 设错让长上下文输出乱掉

本地模型在原生上下文长度内正常,超过后就开始重复、乱码。手把手诊断并修复 llama.cpp 和 vLLM 的 RoPE scaling(YaRN、llama3、rope_theta)配置。

你跑了一个本地 Llama 3 8B,把 --ctx-size 设成 32768 想让它处理长文档,开头几千 token 的输出还正常,然后就崩了:词句重复、话题乱跳、代词指代错乱,最后变成一串无意义的 token。模型还在生成,但它对超出训练上下文范围的位置已经失去了定位能力。这就是 RoPE(Rotary Position Embedding,旋转位置编码)scaling 缺失或配错的典型症状——RoPE scaling 正是让模型能定位到训练窗口之外位置的机制。

最快的修复(2026 年 6 月):如果你用的是原生就支持长上下文的模型(Llama 3.1、Qwen2.5/Qwen3、Mistral v0.3+、Gemma 2),那就一个 RoPE 参数都别加。正确的 scaling 已经内置在 GGUF 或 config.json 里,在它上面再叠 --rope-scaling 是最常见的自己把输出搞坏的方式。直接把 --ctx-size 设成模型支持的值,到此为止。只有把老的、短上下文基础模型(Llama 2 的 4k、Llama 3 的 8k)硬撑到原生窗口之外时,才需要手动配 YaRN scaling——即便如此,扩展倍数也尽量控制在 4 倍以内。

先判断你属于哪一类

你的模型原生上下文怎么做
Llama 3.1 / 3.2 / 3.3128k不加任何 RoPE 参数。--ctx-size 可设到 131072。
Qwen2.5 / Qwen332k-128k不加 RoPE 参数(YaRN 已写进 config)。设 --ctx-size 即可。
Mistral 7B v0.3、Mixtral32k不加 RoPE 参数(rope_theta=1000000)。
Gemma 2 / 38k-128k不加 RoPE 参数。
Llama 3 8B(非 3.1)8k手动 YaRN,--rope-scale 4,控制在约 32k 以内。
Llama 24k手动 linear 或 YaRN,控制在约 16k 以内。

如果你的模型在上面四行里,而你却传了 --rope-scaling--rope-scale--rope-freq-base,先把它们全删掉重测,再继续往下看。现代模型上绝大多数”长上下文乱码”的反馈,都是自己加的覆盖参数害的。

常见原因

按命中率从高到低排列。

1. 在本身就会自动 scaling 的模型上又手动加了 RoPE 参数

这现在是头号原因。Llama 3.1、Qwen2.5/Qwen3、Mistral v0.3 已经把上下文扩展设置写进了 GGUF 元数据(llama.rope.scaling.typellama.rope.freq_basellama.rope.scaling.factor)。当你在命令行又加 --rope-scaling yarn --rope-scale 4,相当于在内置 scaling 之上又叠了一层,把频率二次缩放,结果远没到宣称的上限就把位置编码搞坏了。

怎么判断:把所有 --rope-*--yarn-* 参数删掉,只留 --ctx-size,重跑下面 Step 6 的检索测试。如果连贯性恢复了,那就是这些参数的锅。

2. 上下文扩到原生长度之外,却完全没配 scaling

这是模型上的相反错误。Llama 2 的训练长度是 4096,Llama 3 base 是 8192。在这类模型上直接设 --ctx-size 32768 又不做任何 RoPE 扩展,位置编码就会外推到训练分布之外,原生窗口之后的部分全部退化。

怎么判断:输出在大约模型 max_position_embeddings 的位置之前都干净,到那个边界后断崖式崩坏。查 config.json 里的 max_position_embeddings,和你的 --ctx-size 对比。

3. RoPE scaling 类型用错

llama.cpp 的 --rope-scaling 参数只接受 {none, linear, yarn}(已对照当前 llama-server README 核实,2026 年 6 月)。linear 是最粗糙的方法,超过约 2 倍就明显劣化;yarn(Yet Another RoPE ExtensioN)因为对低频和高频分量分别缩放,在 4 倍以内能更好地保住质量。在 Llama 系列上做 4 倍扩展却选了 linear,长程部分就会乱。

注意 --rope-scaling 这个命令行参数没有 llama3 这个取值。Llama 3.1 用的 llama3 式 scaling 是 config.json/GGUF 元数据里的 rope_type,加载时自动识别——你永远不会在命令行里传它。

怎么判断:看模型卡 config.jsonrope_scaling.rope_type(或 type)。Llama 3.1 是 "llama3";很多长上下文微调版是 "yarn"

4. --rope-scale 倍数填错

--rope-scale N 把上下文扩展 N 倍,应该等于 目标ctx / 原生ctx。把原生 8k 的模型扩到 32k 就是 --rope-scale 4。填 2 是欠缩放(超过 16k 后位置漂移),填 8 是过缩放(连 8k 处的近程注意力都糊掉)。倍数错了在中等长度就会掉质量,不是只在极端长度才出问题。

怎么判断:算 目标ctx / 原生ctx,和你的 --rope-scale 比。用 YaRN 时还应该把 --yarn-orig-ctx 设成原生长度,好让运行时知道原始窗口是多大。

5. 第三方 GGUF 没有保留正确的 rope_theta

长上下文模型用较大的 rope_thetarope_freq_base)来在高位置上保持编码稳定:Llama 3/3.1 是 500000,Mistral 是 1000000,而 Llama 2 是 10000。如果某个社区 GGUF 是用老的或有 bug 的脚本转换的,基础频率可能是错的,llama.cpp 就会回退到一个会破坏长上下文连贯性的默认值——哪怕你的命令行参数都对。

怎么判断:读出内嵌的值(Step 2),和 HuggingFace config.jsonrope_theta 对比。

6. vLLM 没有从 config 里读取 rope_scaling

当你用 Hub ID 加载、且 config.json 里已有 rope_scaling 时,vLLM 会自动应用它。出问题的情况是:从一个 config.json 缺失或过期的本地路径加载,或者需要补一个 config 里没有的扩展配置。截至 2026 年 6 月,vLLM 已经不再支持旧的 --rope-scaling 命令行参数;要覆盖配置得用 --hf-overrides(见 Step 5)。

怎么判断:看启动日志里 vLLM 最终解析出的 rope 配置,或者对你加载的那个路径打印 AutoConfig.from_pretrained(...).rope_scaling

最短修复路径

Step 1:读出模型期望的 RoPE 配置

# 需要: pip install transformers(Llama 3.1 的 rope_scaling 需 >= 4.43.1)
from transformers import AutoConfig

config = AutoConfig.from_pretrained(
    "meta-llama/Llama-3.1-8B-Instruct",
    trust_remote_code=True,
)
print("max_position_embeddings:", config.max_position_embeddings)
print("rope_theta:", config.rope_theta)
print("rope_scaling:", config.rope_scaling)

Llama 3.1 应该会显示 rope_theta=500000,以及:

{
  "factor": 8.0,
  "low_freq_factor": 1.0,
  "high_freq_factor": 4.0,
  "original_max_position_embeddings": 8192,
  "rope_type": "llama3"
}

如果 rope_scalingNone、且 max_position_embeddings 很小(4096 或 8192),说明这是个短上下文基础模型,必须手动 scaling(Step 4)。如果它有值,说明模型自己会 scaling,你就不要传任何 RoPE 参数。

Step 2:确认 GGUF 携带了正确的 rope_theta

python3 << 'EOF'
import gguf
reader = gguf.GGUFReader("models/llama-3.1-8b-instruct-Q4_K_M.gguf")

rope_freq = reader.fields.get("llama.rope.freq_base")
if rope_freq:
    print("rope_theta in GGUF:", rope_freq.parts[-1][0])
else:
    print("rope_theta not in GGUF -- llama.cpp 会用默认值 10000")

ctx_len = reader.fields.get("llama.context_length")
if ctx_len:
    print("context_length in GGUF:", ctx_len.parts[-1][0])

# 把内嵌的所有 scaling 元数据都打出来
for f in reader.fields.values():
    if "rope" in f.name:
        print(f.name, "=", list(f.parts[-1]))
EOF

如果某个本该是 500000(Llama 3.1)或 1000000(Mistral)的模型,llama.rope.freq_base 读出来是 10000,说明这个 GGUF 转换错了。重新转换(Step 3),而不是用命令行参数去打补丁。

Step 3:从源权重重新转换 GGUF

# convert_hf_to_gguf.py 会保留 rope_theta 和 scaling 元数据
python convert_hf_to_gguf.py \
  /path/to/Meta-Llama-3.1-8B-Instruct \
  --outtype f16 \
  --outfile llama-3.1-8b-instruct-f16.gguf

# 确认基础频率被保留下来
python3 -c "import gguf; r=gguf.GGUFReader('llama-3.1-8b-instruct-f16.gguf'); print(r.fields.get('llama.rope.freq_base').parts[-1][0])"
# Llama 3.1 应为 500000

优先从原始 HuggingFace 权重重新转换,而不是去信任来路不明的第三方 GGUF。

Step 4:正确配置 llama.cpp

对于现代长上下文模型,整个修复就是”别加 RoPE 参数”:

# Llama 3.1 8B —— RoPE 已内置,只需设上下文大小
./llama-server \
  -m models/llama-3.1-8b-instruct-Q4_K_M.gguf \
  --ctx-size 131072 \
  -ngl 99

# Mistral 7B v0.3(原生 32k,rope_theta=1000000)—— 同样不加参数
./llama-server \
  -m models/Mistral-7B-Instruct-v0.3-Q4_K_M.gguf \
  --ctx-size 32768 \
  -ngl 99

只有当你把短上下文基础模型撑到原生窗口之外时,才手动 scaling。--rope-scaling 接受 {none, linear, yarn};用 yarn,并通过 --yarn-orig-ctx 告诉它原始窗口:

# Llama 3 8B(原生 8k)扩到 32k —— 倍数 = 32768 / 8192 = 4
./llama-server \
  -m models/llama-3-8b-instruct-Q4_K_M.gguf \
  --ctx-size 32768 \
  --rope-scaling yarn \
  --rope-scale 4 \
  --yarn-orig-ctx 8192 \
  -ngl 99

--yarn-attn-factor--yarn-beta-fast--yarn-beta-slow 保持默认即可(各自默认 -1.00,意为”从模型自动取值”),除非模型卡明确给出了具体数值。如果内嵌的 rope_theta 是错的而你又没法重转,可以用 --rope-freq-base 500000 直接覆盖,但重新转换更干净。

Step 5:vLLM 用 --hf-overrides 覆盖

当你加载的模型 config.json 里已经有 rope_scaling 时,vLLM 会自动应用它,所以第一步就是用 Hub ID 直接加载、不加任何额外参数:

vllm serve meta-llama/Llama-3.1-8B-Instruct --max-model-len 131072

要给一个 config 里没定义 scaling 的模型做扩展,就把参数作为 JSON 覆盖传进去(截至 2026 年 6 月,旧的 --rope-scaling 命令行参数已不再支持):

vllm serve Qwen/Qwen3-8B \
  --hf-overrides '{"rope_parameters": {"rope_type": "yarn", "factor": 4.0, "original_max_position_embeddings": 32768, "rope_theta": 1000000}}' \
  --max-model-len 131072

--max-model-len 是扩展后的新上限(原始 x 倍数)。看启动日志里解析出的 rope 配置,确认它生效了。

Step 6:用检索探针确认修好了

最省事又可靠的测试是大海捞针:在长 prompt 深处埋一个标记,让模型把它找出来。

python3 << 'EOF'
import requests

# 约 200 个标记;在长文档里 Position 150 远超基础 8k 窗口
markers = " ".join(f"Position {i}: the keyword is MARKER{i}." for i in range(1, 201))
prompt = f"Here is a numbered sequence:\n{markers}\n\nWhat is the keyword at Position 150? Answer with only the keyword."

resp = requests.post("http://localhost:8080/v1/chat/completions", json={
    "messages": [{"role": "user", "content": prompt}],
    "max_tokens": 16,
    "temperature": 0,
})
print(resp.json()["choices"][0]["message"]["content"])
# 正确输出: MARKER150
EOF

跑两个尺寸:一个 prompt 远在原生窗口以内,另一个明显超出。如果短的对、长的乱码,说明 RoPE scaling 还是错的;两个都对,就算修好了。

怎么确认已经修好

  • Step 6 的检索探针在超出原生窗口的位置返回了正确的标记。
  • 输出质量不再在某个清晰边界(原生 max_position_embeddings)处崩塌;即便有退化,也是在扩展范围的远端缓慢出现,而非断崖式。
  • 对现代模型来说,你是在没加任何 --rope-* 参数的情况下做到的——只设了 --ctx-size / --max-model-len

预防建议

  • 先确认模型是不是原生长上下文。如果 config.json 里已有 rope_scaling,就别传 RoPE 参数,只设上下文大小。
  • 手动扩展控制在原生窗口的约 4 倍以内。再长就换一个本就训练成长上下文的模型,别去硬撑短的。
  • 升级版本时从原始 HuggingFace 权重重新转换 GGUF;部署前核对 llama.rope.freq_base 与源 rope_theta 一致。
  • 每次改完 --ctx-size / --max-model-len,先在一个靠后的位置跑检索探针,再上生产。
  • 在启动脚本注释里记录每个模型的原生上下文、rope_theta、scaling 类型和最大可靠扩展倍数。
  • 不要在会自动 scaling 的模型上再叠 --rope-scale,也不要不查 RoPE 元数据就用来路不明的第三方 GGUF。

常见问答 (FAQ)

Q: 我的 Llama 3.1 号称支持 128k,长上下文却还是乱码,为什么? A: 几乎都是因为你加了它根本不需要的 RoPE 参数。Llama 3.1 在 GGUF 里自带 rope_type: "llama3" 的 scaling。在上面再传 --rope-scaling yarn--rope-scale 会把频率二次缩放。把所有 --rope-*--yarn-* 参数删掉,只留 --ctx-size 131072,重测。

Q: 不配任何 RoPE scaling 的话,多长的上下文是安全的? A: 最多就是 config.json 里的 max_position_embeddings——Llama 2 是 4096,Llama 3 base 是 8192,但 Llama 3.1 是 131072(它的 scaling 是内置的)。在短模型上不配 scaling 就超出原生窗口哪怕 10%,长程依赖也会开始坏掉。

Q: 怎么区分是量化的锅还是 RoPE 的锅? A: 用长度来判断。量化造成的损伤在各种上下文长度下大致恒定;RoPE 的损伤在原生窗口以下几乎为零,超过后急剧崩坏。用同一个 prompt 分别在 4k 和 16k+ 跑——差距很大就是 RoPE,整体均匀地糊就是量化。

Q: linear 和 yarn 用哪个? A: 超过约 2 倍就用 yarn,因为它在扩展长程覆盖的同时保住了近程注意力。linear 只适合很轻微的扩展。对于 rope_theta 很大的模型(Mistral、Llama 3.1),通常两个都不需要。

Q: 能在 Ollama 的 Modelfile 里设 RoPE scaling 吗? A: 截至 2026 年 6 月,Ollama 用 num_ctx(PARAMETER)暴露上下文大小,但 Modelfile 里没有开放 llama.cpp 底层的 --rope-scaling / --yarn-* 等开关;模型原生的 scaling 会被自动遵循。如果要自定义扩展,就直接用 llama.cpp 或 vLLM。对大多数人来说更好的答案是选一个原生就支持长上下文的模型。

Q: 我的输出到 8k 都干净,9k 就坏,这个阈值是什么? A: 那个边界就是模型的原生 max_position_embeddings(Llama 3 base 是 8192),是缺 scaling 的经典信号。要么改用 3.1 版本(内置 scaling),要么加 --rope-scaling yarn --rope-scale 4 --yarn-orig-ctx 8192,断崖就会被推到扩展后的长度。

相关阅读

标签: #local-llm #llama-cpp #排查