Qwen3.8-27B 的 Hidden Layout 公式怎么跟 layer_types 对上
Hugging Face 上 Qwen/Qwen3.8-27B 的 model card,“Model Overview” 一节里有一行看着很密的公式(README.md:41):
Hidden Layout: 16 × (3 × (Gated DeltaNet → FFN) → 1 × (Gated Attention → FFN))
而同一个仓库根目录的 config.json 里,跟”层的排布”有关的东西长成另一副样子:text_config 下有个叫 layer_types 的字符串数组,占了 config.json:21-86([ 在第 21 行,] 在第 86 行,中间 64 个元素各占一行)。数组里的取值只有两种字符串,跟公式里的名词一个字都不重合。
这篇就干一件事:把那行公式拆成几个能逐条回配置文件核对的断言,看看哪些对得上、哪些根本没有对应物。以下所有数字都来自我们在 2026-08-16 读取的仓库快照 1d4bf0f,读的是文本配置文件与 model card,我们没有下载权重、没有加载过模型。
先把那行公式拆开
公式里其实压着四条独立的信息,值得分开看:
- 外层重复 16 次;
- 每个重复块内部,先来 3 个
Gated DeltaNet单元; - 然后接 1 个
Gated Attention单元; - 每个单元后面都跟一个 FFN。
前三条讲的是”多少个、什么顺序”,第四条讲的是”每层内部还有什么”。这个区分很关键——因为 layer_types 这个数组只能回答前三条,第四条它压根没有承载的位置。
数组自己数一遍
layer_types 里的取值只有 "linear_attention" 与 "full_attention" 两种。我们用 Python 读出来数了一遍(读的是文件,不是模型):
python -c "
import json
d=json.load(open('config.json',encoding='utf-8'))
lt=d['text_config']['layer_types']
full=[i for i,v in enumerate(lt) if v=='full_attention']
lin=[i for i,v in enumerate(lt) if v=='linear_attention']
print('full count',len(full)); print('full idx (0-based)',full)
print('full idx (1-based)',[i+1 for i in full]); print('linear count',len(lin))
print('all full idx %4==3 (0based)?', all(i%4==3 for i in full))
print('interval diffs', sorted(set(full[k+1]-full[k] for k in range(len(full)-1))))
print('groups of 4:', [lt[k*4:(k+1)*4] for k in range(2)])
"
结果是这样的:
- 数组长度 64(用
len(lt)单独打印过一次),与text_config.num_hidden_layers的64(config.json:98)相同; "full_attention"16 项,"linear_attention"48 项,16 + 48 = 64;full_attention的 0 基下标是[3, 7, 11, 15, 19, 23, 27, 31, 35, 39, 43, 47, 51, 55, 59, 63],all(i % 4 == 3)返回True,相邻下标的差集合是{4};- 换成”第几层”的说法(1 基):第 4、8、12、16、20、24、28、32、36、40、44、48、52、56、60、64 层是
full_attention,其余 48 层是linear_attention; - 把 64 项按 4 个一组切开得到 16 组,
layer_types[0:4]与layer_types[4:8]都等于['linear_attention','linear_attention','linear_attention','full_attention']; - 下标 0(第 1 层)是
linear_attention(config.json:22),下标 63(第 64 层)是full_attention(config.json:85)——数组以full_attention结尾。
三条断言逐个对
在核公式之前,先把最基础的一条对齐:README.md:40 写的是 Number of Layers: 64,text_config.num_hidden_layers 的值是 64(config.json:98),layer_types 数组的长度也是 64,公式里 16 × (3 + 1) 同样落在 64 这个数上。这四处对的是同一个总层数,后面按 4 个一组去切分才有共同基准;要是这几处先对不上,下面三条断言就没法谈。
拿上面的结果去核公式的前三条:
块数 16。 公式外层写 16 ×,数组按 4 个一组正好切成 16 组,full_attention 也正好 16 个。对上。
块内比例 3:1。 公式说每块里 3 个 DeltaNet 单元、1 个 Attention 单元,全模型即 16×3 = 48 与 16×1 = 16。数组数出来是 48 个 linear_attention、16 个 full_attention。数量对上。
块内顺序是先 3 后 1。 这条不能只看数量,得看位置。full_attention 的下标模 4 恒等于 3,也就是每组的最后一个才是 full_attention,前三个都是 linear_attention——与公式里”3 个 DeltaNet 之后接 1 个 Attention”的先后顺序一致。
所以结论是:在块数、块内比例、块内顺序这三点上,README.md:41 的公式与 config.json:21-86 的数组完全吻合,我们没有发现矛盾。
顺带还有一个数值上自洽的地方:text_config.full_attention_interval 的值是 4(config.json:15),而 64 ÷ 4 = 16,恰好等于 full_attention 的出现次数;数组里实际的相邻间距也恒为 4。这两处只是数值一致,当 layer_types 与 full_attention_interval 冲突时以哪个为准,本仓文件没有说明,这是模型权重仓,Qwen3_5ForConditionalGeneration 这个类的实现不在里面,我们没有推理代码可查,也就不去猜。
FFN 为什么不在数组里
标题里这个问题,得先把它改写成一个能回答的形式。
layer_types 是一个取值只有两种注意力类型的字符串数组:"linear_attention" 和 "full_attention"。整个数组里没有第三种取值,自然也就没有任何一项写着 FFN。公式说”每个单元后面跟一个 FFN”,这句话在 layer_types 里没有可供核对的对应元素。
那 FFN 在 config.json 里是不是完全没痕迹?也不是。text_config.intermediate_size 的值是 17408(config.json:20),与 model card 里 Feed Forward Network / Intermediate Dimension: 17,408(README.md:49-50)这两行数值一致。也就是说,配置文件承载的是 FFN 的维度,而不是 FFN 出现在第几层。
所以对”FFN 为什么不在数组里”,我们能给出的诚实回答只有一句:layer_types 这个数组的取值域里就没有 FFN 这一项,它记录的是各层的注意力类型;FFN 每层是否都有、以什么形式接在后面,我们无法从本仓的配置文件核实,本仓也没有建模源码和权重结构可读来验证。至于设计上为什么这么切分——那是我们没有依据回答的问题,不猜。
另一处对不上的:公式里的名词,config 里一次都没出现
比 FFN 更容易被忽略的是命名这一层。
公式里的 “Gated DeltaNet” 与 “Gated Attention” 这两个名词,在 config.json 里一次都没有出现;config 里对应位置的字符串是 "linear_attention" 与 "full_attention"。上一节我们把它们对上,靠的是”数量 48/16 + 顺序 3:1”这两组事实——这是名称层面的对应关系,不是配置文件里写明的映射。区别在于:如果哪天上游改了排布,你不能指望 config 里有一行告诉你哪种字符串对应哪个名词。
再说”Gated”这个词。config.json 里另有两个带 gate 字样的字段:attn_output_gate 为 true(config.json:11)、output_gate_type 为 "swish"(config.json:100)。它们与 model card 里的 “Gated” 字样是否指同一件事,本仓文件没有说明,我们只并列记录取值。
model card 侧的旁证是:grep 全文,DeltaNet 与 Gated 这两个词只命中 README.md:41,42,43,45 这几行,全都在 Model Overview 那一小段里,正文其它地方再没展开过。
同一节里另一条需要自己算的:RoPE 维度 64
Model Overview 里还有一行 Rotary Position Embedding Dimension: 64(README.md:48)。这一行同样在 config.json 里找不到同名字段——配置里没有任何一个键叫”rope 维度”。能拿来对的只有两个:
text_config.head_dim = 256(config.json:16)text_config.partial_rotary_factor = 0.25(config.json:102;这个字段在rope_parameters子对象里又出现一次,config.json:111,两处值都是0.25)
我们跑了一次算术:256 × 0.25 = 64.0,数值与 model card 那行的 64 相同。但这是我们做的算术核对,config.json 本身并没有写出 64 这个数,partial_rotary_factor 的确切定义我们也未能从本仓文件中确认。所以这条只能当”数值吻合”记录,不能引用成”config 明写 RoPE 维度为 64”。
顺带一提,rope_parameters.mrope_section 是 [11, 11, 10](config.json:106-110),三项之和为 32,恰为 64 的一半——同样只是算术关系,含义我们不解释。
你自己复核的动作
如果你要核这一段,动作是固定的三步,都只需要读文本文件:
- 打开仓库根目录的
config.json,定位text_config.layer_types(config.json:21-86),用 Python 读出来数长度、数两种取值的个数、打印full_attention的下标; - 把数出来的三个结果(16 组 / 48:16 / 每组末位是
full_attention)对到README.md:41那行公式的三个断言上; - 遇到公式里出现、而数组里没有对应取值的部分(FFN),直接停在”无法从
layer_types核对”,不要用别的字段去凑。
什么情况说明你核的不是这个问题:如果你手上那份 config.json 的 layer_types 长度不是 64、或者 full_attention 的下标不满足模 4 等于 3,那你读的很可能不是我们这份快照(1d4bf0f,核对日 2026-08-16)——模型仓内容会随上游更新变动,这时候应该先确认版本,而不是去怀疑公式。
最后补一句常被问到的:config.json 里的 architectures 是 ["Qwen3_5ForConditionalGeneration"]、model_type 是 "qwen3_5"(config.json:3、config.json:7),而 model card 标题写的是 # Qwen3.8-27B(README.md:7),README.md:20 另有一句原文 Built on the architectural foundation of Qwen3.5。这几处的版本字样并不相同,我们只照实并列,不推断命名原因。
延伸阅读
- 从头读起:Qwen3.8-27B 是什么:一个模型仓里有哪些文件、各自负责什么
- 本专题共 35 篇,完整分组目录见专题页
- Qwen3.8-27B 的 64 层怎么排:全注意力恰好 16 个,下标全是 4k+3
- Qwen3.8-27B 的混合注意力:由 config 里哪几个字段承载
本文依据 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 自述,我们没有复现。
模型仓库内容随上游更新而变动,请以官方最新说明为准。