Qwen3.8-27B 的 MTP:自述多步训练,配置里只有一层

2026-08-16

把 Qwen3.8-27B 的 model card 打开,滚到 “Model Overview” 那一节(README.md:32README.md:53),你会发现它是一张相当规整的参数表:隐藏维度、词表大小、层数、注意力头数、FFN 中间维度,每一项后面都跟着一个具体的数。我们把这些数逐行拿去和随仓的 config.json 对,绝大多数都能一一对上——Hidden Dimension: 5120text_config.hidden_size = 5120config.json:18),Number of Layers: 64num_hidden_layers = 64config.json:98),Intermediate Dimension: 17,408intermediate_size = 17408config.json:20)。

对到倒数第二行时卡住了。这一行是:

- MTP (Multi-Token Prediction): trained with multiple steps

出处是 README.md:52。这一节开头的 Type: Causal Language Model with Vision EncoderREADME.md:34)与 Training Stage: Pre-training & Post-trainingREADME.md:35)本来就是文字说明,不带数字;而从 Number of Parameters: 27BREADME.md:37)开始的各个参数行,冒号后面都跟着一个具体的数——唯独 MTP 这一行没有。

config.json 那边偏偏有数字,还是个很小的数字。

两处原文,各自摆在哪

config.jsontext_config 下共 34 个键(我们用 Python 数的,口径见文末命令),其中有两个以 mtp_ 开头:

字段名行号
mtp_num_hidden_layers1config.json:95
mtp_use_dedicated_embeddingsfalseconfig.json:96

也就是说,这件事在两个层面上各有一句话:

  • model card 层面README.md:52):trained with multiple steps,一个自然语言短语,没有数字。
  • 发布配置层面config.json:95):mtp_num_hidden_layers 的值是 1

我们采集这份快照的时间是 2026-08-16,对应 Hugging Face 仓库 Qwen/Qwen3.8-27B1d4bf0f。上面两个数字都是从这份快照里逐字读出来的。

为什么不能把「multiple」和「1」放在一起做减法

看到「多步」和「1 层」并排,第一反应容易是「文档说多、配置说一,矛盾了」。但这两个词说的根本不是同一个量纲:

  • README.md:52 那句里的 steps,字面上修饰的是 trained,讲的是训练环节的事;
  • config.json:95 里的 mtp_num_hidden_layers,字面上是层数(hidden layers)。

训练时用了几步,与发布配置里写着几层,是不是同一件事,我们无法从这个仓库的任何一个文件里确认。 这个仓库是模型权重仓,不是代码仓:config.json:3 里写的 architectures["Qwen3_5ForConditionalGeneration"],但这个类的实现不在本仓,本仓能读的只有 model card、几个 JSON 配置、tokenizer 相关文件和 chat_template.jinja。没有建模代码,就没有办法知道 mtp_num_hidden_layers 这个键在加载与推理时到底被谁读走、怎么用。

所以我们能做的,只有把两处原文并排陈述,标明各自的行号,然后停在这里。不推断哪一处「才是对的」,不推断为什么一处有数字另一处没有,也不拿这个差异去评价这个模型或这个团队——这不是本文的目的,我们也没有依据。

顺带把第二个字段也记上:mtp_use_dedicated_embeddings 的值是 falseconfig.json:96)。model card 里与 MTP 有关的表述就只有 README.md:52 那半句,没有再展开这个字段。它为 false 意味着什么,同样属于「本仓文件没有说明」的范畴。

在这个仓库里处境相同的字段不止 MTP 这两个。text_config 里还有 attn_output_gate: trueconfig.json:11)、output_gate_type: "swish"config.json:100),顶层还有 language_model_only: falseconfig.json:6)——后面这个键名在 README.md 全文都查不到。这些字段我们同样只能照录取值:它们在加载与推理时具体怎么被使用,本仓没有可读的实现,我们一律未能确认。

这里也顺便说清本文不做什么:我们不解释 MTP 是什么、用来干什么、放在推理链路的哪一环。这个仓库里对它的全部描述就是 README.md:52 那半句话,加上 config.json 里的两个键。除此之外的任何说法,都得从别处搬,而本文的事实边界就到这份快照为止。

这 1 层不在那 64 层里

还有一个容易顺手做错的动作:把 mtp_num_hidden_layers1num_hidden_layers64 加起来,或者认为它是从 64 里划出来的一层。

text_config.layer_types 是一个字符串数组,位于 config.json:21-86,我们用 Python 读出来数过:长度正好 64,与 num_hidden_layers = 64config.json:98)相同;数组里的取值只有两种,"linear_attention""full_attention",其中 full_attention 16 项、linear_attention 48 项。也就是说,这个 64 项的数组里没有任何一项是留给 MTP 的——它是一个独立字段,和 layer_types 各说各的。

这 64 项的排布本身是有规律的:full_attention 的 0 基下标是 3, 7, 11, …, 63,换成第几层就是第 4, 8, 12, …, 64 层,相邻两个之间的间距恒为 4;text_config 里另有一个 full_attention_interval,值正好是 4config.json:15),而 64 ÷ 4 = 16,与 full_attention 出现的次数相同。我们只陈述这个数值上的一致,不解释这个字段在推理时如何被使用——本仓没有推理代码可查。而 mtp_num_hidden_layers 完全不在这套排布里,它既不出现在 layer_types 的取值中,也不参与上面这组算术。

64 与 1 之间该不该做加减,config.json 与 model card 都没有给出说法,我们不替它做。这一条和上一节是同一个道理:配置里两个数字挨在一起,不等于它们可以放进同一个算式。

这不是孤例:那张表里的三类行

把 “Model Overview” 整张表逐行拿去核对,结果其实可以分成三类,MTP 属于第三类:

第一类:README 有数、config 有同名字段、数值一致。 这类占多数,除了开头那三行,还有 Token Embedding: 248,320 (Padded)vocab_size = 248320config.json:117)、Number of Attention Heads: 24 for Q and 4 for KVnum_attention_heads = 24num_key_value_heads = 4config.json:97config.json:99)、Number of Linear Attention Heads: 48 for V and 16 for QKlinear_num_value_heads = 48linear_num_key_heads = 16config.json:90config.json:89)。这类行你想复核,打开 config.json 搜字段名就行。

第二类:README 有数,但 config 里根本没有承载它的字段。 典型是 Number of Parameters: 27BREADME.md:37)——config.json 里没有任何一个字段写参数量,我们也没有下载权重,所以这个 27B 只能作为「model card 自述」引用,无从核对。同类的还有 Rotary Position Embedding Dimension: 64README.md:48):config.json 里没有叫这个名字的键,只有 head_dim = 256config.json:16)和 partial_rotary_factor = 0.25config.json:102);我们用 Python 算了一次,256 × 0.25 = 64.0,数值与 README 相同,但这是我们做的算术核对,config.json 本身并没有写出 64 这个数。再往下一行的 Context Length: 262,144 natively and extensible up to 1,000,000 tokens.README.md:53)也是半个一致:max_position_embeddings = 262144config.json:93)对得上,而 1,000,000 在发布的配置里没有任何字段承载。

第三类:README 没给数,config 给了数。 全表就 MTP 这一行。

把这三类摆在一起,MTP 那行的性质就清楚了:它不是「README 和 config 打架」,而是这张表里唯一一处方向反过来的地方——通常是文档给结论、配置给细节,这一行反倒是配置更具体。

你自己怎么复核这两处

不需要下载权重,也不需要加载模型。model card 与几个 JSON 配置文件在 Hugging Face 仓库页面上就能直接看,config.json 一共 4,312 字节、140 行,肉眼翻也翻得完。

我们数键的命令原样如下(在放着 config.json 的目录里执行):

python -c "
import json
d=json.load(open('config.json',encoding='utf-8'))
print('n top', len(d.keys()), 'n text', len(d['text_config'].keys()), 'n vision', len(d['vision_config'].keys()))
"

输出是 n top 11 n text 34 n vision 14。两个 mtp_ 字段就在这 34 个 text_config 键里。

一点命令行层面的提醒,与模型仓本身无关:上面这种把多行 Python 塞进 -c 的写法,在 Linux/macOS 的 shell 里可以直接粘;在 Windows 的 PowerShell 里,双引号与换行的处理规则不同,更省事的做法是把这几行存成一个 .py 文件再执行。这属于命令行的通用写法,不是这个模型仓的内容。

至于 README 那一侧,README.md 全文 583 行,MTP 那句就在第 52 行,检索关键词能一次定位。我们另外做过一次关键词检索:

grep -n "Qwen3_5\|qwen3_5\|ForConditionalGeneration\|AutoModel\|AutoProcessor\|dtype\|DeltaNet\|Gated" README.md

AutoModel / AutoProcessor 在 model card 里零命中——也就是说,你在 model card 里找不到一段本地 Transformers 加载代码,自然也就找不到「mtp_num_hidden_layers 在加载时怎么用」的线索。同一次检索里还有一个可以顺手记下的事实:README.md 全文没有写出所需的 transformers 版本号,而 config.json:120transformers_version 写的是 "5.8.0.dev0",带一个 .dev0 后缀。这个版本是否已发布、能不能加载本配置,我们没有安装过,也无从核实。以上这些检索结果,就是我们把 MTP 这条线索追到此为止的直接原因。

收在哪里

写这一篇的意义不在于给 MTP 下定义,而在于示范一种核对姿势:model card 的一行话和配置文件的一个键,即使讲的是同一个缩写,也未必是同一个量纲,不要替它们互相换算。 在只有权重和配置、没有建模源码的仓库里,能核到「两处原文分别是什么、各在第几行」这一步,就已经是这份材料能支持的全部结论了。再往前一步就是猜。

如果你要在自己的技术方案里引用「Qwen3.8-27B 的 MTP」,稳妥的写法是把两句原文都抄上、各自标明出处,而不是只抄其中一句然后补一个自己算出来的数。

延伸阅读


本文依据 Hugging Face 仓库 Qwen/Qwen3.8-27B 的 model card 与随仓配置文件 (config.jsongeneration_config.jsonpreprocessor_config.jsonchat_template.jinja 等)整理, 核对日 2026-08-16,对应仓库快照 1d4bf0f。 本文内容为 model card 与配置文件口径,我们没有下载权重、没有部署、也没有推理过这个模型, 因此不涉及生成质量、推理速度与显存占用的任何描述;文中所有评测数字均为 model card 自述,我们没有复现。 模型仓库内容随上游更新而变动,请以官方最新说明为准。

想系统学会用 AI?报名体系课或加入会员,照着学、照着用。