Qwen3.8-27B 的 GQA:24 个 Q 头配 4 个 KV 头、head_dim 是 256
翻 Hugging Face 上 Qwen/Qwen3.8-27B 这个仓库的时候,model card 的 “Model Overview” 一节里有两行写得特别短:
Number of Attention Heads: 24 for Q and 4 for KV
Head Dimension: 256
分别在 README.md:46 和 README.md:47,整个 “Model Overview” 一节的范围是 README.md:32 到 README.md:53。短到很容易一扫而过,但这两行其实是整份 model card 里少数几处能和 config.json 严丝合缝对上的地方。这篇就只做一件事:把「24 / 4 / 256」这三个数字放回配置文件里,看清楚它们各自写在哪、管到哪一层、以及哪些看上去顺理成章的推论其实在这个仓库里根本核不出来。
先说清楚本文的边界:Qwen/Qwen3.8-27B 是一个模型权重仓,不是代码仓。仓库里能读的只有 model card 和几个文本配置文件,architectures 里写的那个类 Qwen3_5ForConditionalGeneration(config.json:3)的实现并不在这个仓库里。所以下面所有说法都停在「配置文件里写着什么」这一层,不越界到「所以运行时会怎样」。
三个数字的确切位置
截至 2026-08-16、对应快照 1d4bf0f,这三项在 config.json 的 text_config 下逐个照录如下:
| 字段名 | 值 | 行号 |
|---|---|---|
num_attention_heads | 24 | config.json:97 |
num_key_value_heads | 4 | config.json:99 |
head_dim | 256 | config.json:16 |
和 README.md:46、README.md:47 的两行一一对上:24 对 Q、4 对 KV、256 对 Head Dimension,没有出入。标题里我用的「GQA」是这类「Q 头数多于 KV 头数」配置的通行叫法,配置文件里真正存在的字段名就是上表这三个,写脚本、写加载逻辑时要认的是字段名,不是叫法。
顺带把同一组里另外几个开关也记下来,它们同样在 text_config 直属层:attention_bias 是 false(config.json:9)、attention_dropout 是 0.0(config.json:10)、use_cache 是 true(config.json:116)、rms_norm_eps 是 1e-06(config.json:103)。这些是发布配置里的取值,不是「你跑起来会怎样」的承诺。
最容易读错的一点:这三个数只管 64 层里的 16 层
如果只看 “Model Overview” 那张表,很容易默认 24/4/256 描述的是整个模型的注意力。但 text_config 里还有一个 layer_types 数组(config.json:21-86),长度 64,与 num_hidden_layers 的 64(config.json:98)相同,取值只有两种:"linear_attention" 与 "full_attention"。
数出来的结果是:full_attention 共 16 项,linear_attention 共 48 项。full_attention 的 0 基下标是 3, 7, 11, 15, 19, 23, 27, 31, 35, 39, 43, 47, 51, 55, 59, 63,全部满足对 4 取余等于 3,相邻间距恒为 4;换成 1 基就是第 4、8、12……64 层。数组的第 1 层是 linear_attention,最后一层是 full_attention。另一个字段 full_attention_interval 的值正好是 4(config.json:15),而 64 ÷ 4 = 16,与实际出现次数相同。
这意味着:24 个 Q 头、4 个 KV 头、head_dim 256 这一组,对应的是那 16 层。剩下 48 层用的是完全另一套字段,集中在 config.json:87-92——linear_num_key_heads 是 16、linear_num_value_heads 是 48、linear_key_head_dim 与 linear_value_head_dim 都是 128。头数和头维度都不是同一组数,读配置时把两侧混起来,后面所有对照都会错位。
model card 用一行公式描述了这个排布(README.md:41):
Hidden Layout: 16 × (3 × (Gated DeltaNet → FFN) → 1 × (Gated Attention → FFN))
在「块数 16」「块内比例 3:1」「块内先 linear 后 full」这三点上,公式与 layer_types 数组是吻合的。但有两处边界必须一起说:公式里的 FFN 在 layer_types 里没有任何对应元素,数组只有两种注意力取值,这一段无从核对;而 “Gated DeltaNet” 与 “Gated Attention” 这两个名词在 config.json 里一次都没出现,配置里对应位置的字符串是 "linear_attention" 与 "full_attention"。把它们对上,是按数量 48/16 与前后顺序做的名称层面的对应,不是配置文件里写明的映射。
24 × 256 与 hidden_size 对不上
text_config.hidden_size 是 5120(config.json:18)。用 Python 乘一下:24 × 256 = 6144,与 5120 不等;KV 侧 4 × 256 = 1024。线性那一侧同样可以乘:48 × 128 = 6144、16 × 128 = 2048。
这几个乘积在 config.json 里并没有直接写出来,是我们自己算的,所以只作为算术关系记录。6144 与 5120 不等这件事说明的仅仅是「头数乘头维度不等于隐藏维度」这一条算术观察本身;投影矩阵到底怎么摆,本仓没有建模源码可查,我们不推断。同样地,这些数字一律不用来推算参数量、显存占用、KV 缓存大小或任何速度结论——这个仓库里没有任何依据支持那类推算。
RoPE 维度 64:README 写了,config 没有这个字段
README.md:48 那一行写的是 Rotary Position Embedding Dimension: 64。但 config.json 里没有任何一个名为「RoPE 维度」的字段。和它沾边的只有两个:head_dim = 256(config.json:16),以及 partial_rotary_factor = 0.25(config.json:102)。
256 × 0.25 = 64.0,数值和 README 的 64 相同。这条只能当作「我们做的算术核对结果与 README 的数字吻合」来引用,不能引用成「config 明写 RoPE 维度是 64」——文件里就是没有这个数。而且 partial_rotary_factor 的确切定义,我们没能从本仓任何文件里确认。
另外两点照实记下:partial_rotary_factor 在 text_config 里出现了两次,一次在直属层(config.json:102),一次在 rope_parameters 子对象里(config.json:111),两处的值都是 0.25。rope_parameters 一共 5 个键,还包括 mrope_interleaved: true、mrope_section: [11, 11, 10]、rope_theta: 10000000、rope_type: "default"。mrope_section 三项之和是 32,恰为 64 的一半——这同样只是算术关系,它的语义本仓没有说明。
要注意 rope_type 的发布值是 "default",而 rope_parameters 里既没有 factor 也没有 original_max_position_embeddings 这两个键。README 的 “Best Practices” 一节(README.md:521-536)给出的是把 rope_type 改成 "yarn" 并补上 factor: 4.0 与 original_max_position_embeddings: 262144 的替换片段,README 自己也在 README.md:557 建议仅在需要处理长上下文时才修改 rope_parameters。两处并列:开箱配置不是 YaRN 配置。
“Gated” 那个词的落点
model card 把这 16 层叫 “Gated Attention”。config.json 里确实另有两个带 gate 的字段:attn_output_gate 为 true(config.json:11)、output_gate_type 为 "swish"(config.json:100)。看上去很像是对应关系,但本仓文件没有写明它们与 README 的 “Gated” 字样是不是同一件事,实现也不在这个仓库里,所以到此为止,我们不往下推。同样处理的还有 attention_bias: false 与那个 true 的门控开关之间有没有关联——没有依据,不写。
想自己核一遍的话
config.json 整个文件 4,312 字节、140 行,只有三层结构:顶层、text_config、vision_config。核这几个数不需要下载权重,把配置文件拉到本地目录,用下面这段 Python 把字段值与几个算术关系一次打印出来即可(取自我们采集时用的命令,删去了与本篇无关的一行输出):
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('layers/interval', t['num_hidden_layers']/t['full_attention_interval'])
"
在快照 1d4bf0f 上,这条命令的输出是:config.json bytes 4312 lines 140、head_dim*partial 64.0、sum mrope_section 32 [11, 11, 10]、num_attention_heads*head_dim 6144 hidden 5120、linear v heads*dim 6144、linear k heads*dim 2048、kv heads*head_dim 1024、layers/interval 16.0。命令与结果都取自我们的采集记录,路径按仓库内相对路径书写,请在存放这些配置文件的目录下执行。
需要提醒的是,上面这些取值全部是快照 1d4bf0f 上、config.json 与 model card 这一层的口径,我们没有下载权重、没有部署、也没有加载过这个模型去验证配置能不能被正确读入。模型仓的内容会随上游更新变动,字段名和默认值都可能改,自己核的时候请以拉到手的那份文件为准,不要照抄本文里的行号。
这篇没打算回答的问题
有几件事在读完配置之后仍然是空白,与其含糊带过,不如列清楚:
attn_output_gate、output_gate_type、partial_rotary_factor、mrope_interleaved、mrope_section、full_attention_interval这些字段具体如何被使用,我们一律未能确认——建模代码不在本仓。- 当
layer_types与full_attention_interval在数值上冲突时以哪个为准,本仓文件没有说明。二者在当前发布的配置里是自洽的。 - model card 自述
Number of Parameters: 27B(README.md:37),但config.json里没有任何参数量字段,这个数只能作为 model card 自述引用,无法从配置核对。 - 这组数字与显存、吞吐、量化后体积、能不能在某张卡上跑起来的关系,本文一个字都不写。这不是谨慎,是确实没有依据——我们没有下载权重,也没有做过任何推理。
顺带一提,config.json 里的类名与 model_type 写的是 Qwen3_5ForConditionalGeneration 与 "qwen3_5"(config.json:3、config.json:7),text_config.model_type 是 "qwen3_5_text"(config.json:94),而仓库名与 model card 标题是 Qwen3.8-27B(README.md:7)。这几处的版本字样并不相同,写加载脚本、做字段匹配时按文件里的实际字符串来即可,原因我们不做推断。关于这套命名在各个配置文件之间的差异,我们另有一篇专门在讲。
延伸阅读
- 从头读起:Qwen3.8-27B 是什么:一个模型仓里有哪些文件、各自负责什么
- 本专题共 35 篇,完整分组目录见专题页
- Qwen3.8-27B 的线性注意力参数:卷积核宽度与 key/value 头数
- Qwen3.8-27B 的 RoPE 维度 64:config.json 里找不到对应字段
本文依据 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 自述,我们没有复现。
模型仓库内容随上游更新而变动,请以官方最新说明为准。