Qwen3.8-27B 的 RoPE 维度 64:config.json 里找不到对应字段
拿 model card 的参数表去核 config.json,是读一个新模型最省事的入口:一行一行往下对,能对上的说明两边口径一致,对不上的地方往往就是需要多看两眼的地方。
Qwen3.8-27B 这张表大部分行都对得很干净。Hidden Dimension: 5120 对应 text_config.hidden_size = 5120,Number of Layers: 64 对应 num_hidden_layers = 64,Gated Attention 那行的 Head Dimension: 256 对应 head_dim = 256,Intermediate Dimension: 17,408 对应 intermediate_size = 17408 —— 都是同名或近似同名的字段,一眼可查。
唯独有一行会卡住:Rotary Position Embedding Dimension: 64(README.md:48)。你在 config.json 里翻遍三层结构,找不到任何一个能被叫做「RoPE 维度」的键。
以下所有数字均以 2026-08-16 的核对为准,对应 Hugging Face 仓库 Qwen/Qwen3.8-27B 的快照 1d4bf0f;模型仓内容随上游更新而变动。
先把找的范围划清楚
config.json 整个文件 4,312 字节、140 行,结构只有三层:顶层、text_config、vision_config。顶层 11 个键,text_config 34 个键,vision_config 14 个键。rope_parameters 是 text_config 下的一个子对象,不构成第四层配置块。
跟旋转位置编码沾边的键,一共就这些:
| 位置 | 字段名 | 值 | 行号 |
|---|---|---|---|
text_config 直属 | partial_rotary_factor | 0.25 | config.json:102 |
text_config.rope_parameters | mrope_interleaved | true | config.json:105 |
text_config.rope_parameters | mrope_section | [11, 11, 10] | config.json:106-110 |
text_config.rope_parameters | partial_rotary_factor | 0.25 | config.json:111 |
text_config.rope_parameters | rope_theta | 10000000 | config.json:112 |
text_config.rope_parameters | rope_type | "default" | config.json:113 |
rope_parameters 子对象一共就 5 个键,全在上表里。注意 partial_rotary_factor 出现了两次:一次在 text_config 直属层(config.json:102),一次在 rope_parameters 里(config.json:111),两处的值都是 0.25。要动这个值的人,先知道它在文件里有两个位置,比只改一处再去猜为什么没生效省事。
上表这六行里没有任何一个键带 dim 字样,也没有任何一个的值是 64。
另外两个键的取值也一并照录,免得有人以为是我们漏看了:rope_theta 是整数 10000000(一千万),mrope_interleaved 是 true。这两个字段具体如何被使用,同样没有可查的依据,这里只记取值。
搜索范围还可以再收一收:vision_config 的 14 个键里没有任何一个与旋转位置编码相关,视觉侧唯一带位置字样的键是 num_position_embeddings = 2304(config.json:132)。顶层 11 个键里也没有。所以确实只剩 text_config 这一处可找,上表已经把它翻完了。
那个 64 是乘出来的
config.json 里跟 64 唯一挂得上钩的算式,是 Gated Attention 的头维度乘以那个 partial_rotary_factor:
text_config.head_dim = 256(config.json:16)text_config.partial_rotary_factor = 0.25(config.json:102)
我们用 Python 算了一次,256 × 0.25 的结果是 64.0,与 README.md:48 写的 64 数值相同。
这里必须把话说死:这是我们做的一次算术核对,config.json 本身并没有把 64 作为 RoPE 维度写出来。partial_rotary_factor 的确切定义,我们未能从这个仓库的任何一个文件里确认——这是一个模型权重仓,architectures 里写的 Qwen3_5ForConditionalGeneration(config.json:3)这个类的实现不在仓内,没有建模源码可读。所以这条只能作为「数值吻合」记录,不能反过来当成「config 明写了 RoPE 维度是 64」去引用,更不能拿它推导任何跟效果、开销有关的结论。
顺带记一个同样只是算术的观察:rope_parameters.mrope_section 是 [11, 11, 10],三项之和为 32,恰是 64 的一半。这三项分别对应什么,本仓文件没有说明,我们不解释。
顺着这条路继续算下去很容易上瘾,但也很容易翻车。同一次核对里我们还乘出过 num_attention_heads × head_dim = 24 × 256 = 6144,而 text_config.hidden_size 是 5120(config.json:18),两者并不相等。这类乘积在 config.json 里一个都没有直接写着,它们只是我们拿现成字段做的算术。乘出来的数跟参数表能对上是一回事,能不能反推出结构上的解释是另一回事——后者需要建模代码,而这个仓库里没有。
反直觉的地方:在 config 里搜 64,先撞上的是层数
很多人核对参数表的第一动作是在 config.json 里搜字符串 64。这一步在这个仓库里会把人带偏。
config.json 里确实存在值为 64 的字段,但它是 text_config.num_hidden_layers = 64(config.json:98);同时 layer_types 这个字符串数组的长度也是 64(config.json:21-86,[ 在第 21 行,] 在第 86 行,中间 64 个元素各占一行)。这两个 64 对应的是 model card 里的 Number of Layers: 64(README.md:40),是层数,跟位置编码维度不是一回事。
也就是说,这一行对不上的原因不是「值找不到」,而是「没有承载它的字段」。搜到的 64 属于另一行参数表。真正与 README.md:48 那个 64 对应的东西,在 config.json 里是以 256 和 0.25 两个数分开存着的。
顺便说一句:同一张参数表里落空的不止这一行。Number of Parameters: 27B(README.md:37)在 config.json 里同样没有任何字段承载——整个文件没有一个参数量字段,这个 27B 只能作为 model card 自述来引用,没法从配置核对。所以「参数表某行在 config 里找不到对应字段」本身并不稀奇,值得注意的只是每一行落空的方式不一样:27B 那行是彻底没有来源可查,RoPE 维度这行则是两个字段乘得出来。
怎么自己核一遍
如果你要在自己手上的那份配置里重复这个核对,判定动作是三步:读出 rope_parameters 的全部键名,看有没有名字里带维度的键;把 head_dim 与 partial_rotary_factor 乘一次;再把值为 64 的字段单独列出来,确认它是不是 num_hidden_layers。
我们做算术核对时跑的是这一段(在放置 config.json 的目录下执行):
python -c "
import json,io,os
raw=io.open('config.json',encoding='utf-8').read()
print('config.json bytes',os.path.getsize('config.json'),'lines',len(raw.split('\n')))
d=json.loads(raw); t=d['text_config']; v=d['vision_config']
print('head_dim*partial', t['head_dim']*t['partial_rotary_factor'])
print('sum mrope_section', sum(t['rope_parameters']['mrope_section']), t['rope_parameters']['mrope_section'])
print('num_attention_heads*head_dim', t['num_attention_heads']*t['head_dim'], 'hidden', t['hidden_size'])
print('linear v heads*dim', t['linear_num_value_heads']*t['linear_value_head_dim'])
print('linear k heads*dim', t['linear_num_key_heads']*t['linear_key_head_dim'])
print('kv heads*head_dim', t['num_key_value_heads']*t['head_dim'])
print('deepstack', v['deepstack_visual_indexes'], type(v['deepstack_visual_indexes']), len(v['deepstack_visual_indexes']))
print('layers/interval', t['num_hidden_layers']/t['full_attention_interval'])
"
本篇只用得上其中两行输出:head_dim*partial 64.0 和 sum mrope_section 32 [11, 11, 10]。其余几行是同一次核对里顺带打出来的别的数值关系。这段命令只读文件、不加载模型,Windows 与 Linux/macOS 下都是同一条 python -c,差别只在你怎么进到放配置文件的那个目录。
什么情况说明你遇到的不是这件事
有两种情况看起来像「RoPE 对不上」,但跟上面这条不是一回事,别混在一起排查。
一是 rope_type。发布配置里 rope_parameters.rope_type 的值是 "default"(config.json:113),这个子对象里没有 factor,也没有 original_max_position_embeddings——它就是上面那 5 个键。而 model card 的 Best Practices 一节(README.md:521-536)给出的替换片段里 rope_type 是 "yarn",并多出 factor: 4.0 与 original_max_position_embeddings: 262144。两处并列陈述:开箱下发的不是 YaRN 配置,README 给的是让你自己去改的做法,而且 README 自己在 README.md:557 建议仅在需要处理长上下文时才修改 rope_parameters。如果你的问题出在长上下文相关的配置替换上,那是这一条,不是维度对不上。
二是上下文长度。README.md:53 写 Context Length: 262,144 natively and extensible up to 1,000,000 tokens.,而 config.json:93 的 max_position_embeddings 是 262144,tokenizer_config.json 的 model_max_length 也是 262144;发布的配置里没有任何字段写着 1,000,000。这同样是参数表与 config 之间的一处并列差异,但它落在长度上,不落在维度上。
以上都只是陈述两处写法的差异,我们不推断哪一处「是对的」,也不由此评价任何东西。参数表怎么写、config 怎么存,属于项目自己的表述与工程选择,效果如何我们没有依据评价。
留给读者的一句话
参数表和配置文件是两种写法:前者写给人看,常常把推导过的结果直接写成一个数;后者写给加载器看,只存最原始的那几个量。README.md:48 的 64 与 config.json 里的 256、0.25 之间,我们能核到的只有一层算术关系;这两处写的是不是同一件事,本仓文件没有写明,我们不推断,就停在这里。
延伸阅读
- 从头读起:Qwen3.8-27B 是什么:一个模型仓里有哪些文件、各自负责什么
- 本专题共 35 篇,完整分组目录见专题页
- Qwen3.8-27B 的 GQA:24 个 Q 头配 4 个 KV 头、head_dim 是 256
- Qwen3.8-27B 的 MTP:自述多步训练,配置里只有一层
本文依据 Hugging Face 仓库 Qwen/Qwen3.8-27B 的 model card 与随仓配置文件
(config.json、generation_config.json、preprocessor_config.json、chat_template.jinja 等)整理,
核对日 2026-08-16,对应仓库快照 1d4bf0f。
本文内容为 model card 与配置文件口径,我们没有下载权重、没有部署、也没有推理过这个模型,
因此不涉及生成质量、推理速度与显存占用的任何描述;文中所有评测数字均为 model card 自述,我们没有复现。
模型仓库内容随上游更新而变动,请以官方最新说明为准。