Qwen3.8-27B 的词表 248,320 与特殊 token:padded 是什么意思

2026-08-16

看 model card 的参数表时,Token Embedding: 248,320 (Padded) 这一行很容易一眼扫过去。数字记住了,后面那个括号里的 Padded 却没人细究。等到真去翻随权重发布的配置文件,会发现事情比那一行字要碎:config.json 里只有一个 vocab_size,没有任何字段叫 padded;tokenizer_config.json 里新增的特殊 token 只有 33 个,编号也够不到 248,320;而 <think> 这种一眼看上去很「特殊」的 token,special 字段偏偏是 false

这篇只做一件事:把 248,320 这个数,和它周围那几个 token id,在仓库文件里逐处抠出来对一遍。以下事实全部来自 Hugging Face 仓库 Qwen/Qwen3.8-27B 的 model card 与随仓的文本配置文件,核对时间是 2026-08-16,对应快照 1d4bf0f。我们没有下载权重、没有加载模型、没有推理过一个 token,所以下面不会出现任何关于生成效果的说法。

248,320 一共出现在三个地方

在这个仓库里,248,320 这个数值出现在三处:

  • config.json:117text_config.vocab_size,值是 248320
  • README.md:39,Model Overview 那张参数表里的一行,原文是 Token Embedding: 248,320 (Padded)
  • README.md:51,同一张表的另一行,原文是 LM Output: 248,320 (Padded)

三处数值一致,这一点没有争议。有意思的是括号里那个词:Padded 只出现在 model card 的这两行里,config.json 里没有任何一个字段承载它,也没有第二个字段去描述「填充了多少」或者「原始词表是多少」。整个 text_config 一共 34 个键(我们用 Python 数过),跟词表规模有关的就只有 vocab_size 这一个。

Padded 到底指什么?这里必须停住。这是 model card 自述的一个字样,我们没能在本仓任何文件里找到它的定义——这是个模型权重仓,没有建模源码可读,Qwen3_5ForConditionalGeneration 这个类的实现不在仓库里,所以也没有代码可以去核。我们只能把三处位置摆出来,不推断这个词背后是什么工程决定。

model card 为什么把它写成上下两行

Token EmbeddingLM Output 在 model card 的参数表里是分开的两行(README.md:39README.md:51),数值相同。与之对应的一个配置事实是:tie_word_embeddings 的值是 false,而且它在 config.json 里出现了两次——顶层一次(config.json:119)、text_config 里一次(config.json:115),两处值相同。

这两件事是并列的观察,我们只记录到这里,不去替它解释哪一处生效、也不解释这个取值会带来什么。

33 个新增 token:一段连续区间

tokenizer_config.jsonadded_tokens_decoder 是这次真正能数清楚的地方。用 Python 读出来统计,条目数是 33,最小 id 是 248044,最大 id 是 248076,中间连续、没有缺号。

按 id 顺序,这 33 个条目大致分成几段:

  • 248044–248057<|endoftext|><|im_start|><|im_end|>,然后是 <|object_ref_start|> / <|object_ref_end|> / <|box_start|> / <|box_end|> / <|quad_start|> / <|quad_end|> 这六个成对的标注类 token,再往后是 <|vision_start|><|vision_end|><|vision_pad|><|image_pad|><|video_pad|>
  • 248058–248069<tool_call></tool_call><|fim_prefix|><|fim_middle|><|fim_suffix|><|fim_pad|><|repo_name|><|file_sep|><tool_response></tool_response>,以及 <think></think>
  • 248070–248076<|audio_start|><|audio_end|><tts_pad><tts_text_bos><tts_text_eod><tts_text_bos_single><|audio_pad|>

这 33 个条目的 lstriprstripnormalizedsingle_word 四个属性一律是 false,没有例外。唯一有差别的是 special 字段,下面单独说。

最后这一段 audio 与 <tts_*> 系列,在 chat_template.jinja 全文里一次都没有出现——模板只用到了 vision 系、im 系以及 think / tool 那几个。它们对应什么能力,README 里也没有说明,我们没能核实

顶层那四个 token id,和被跳过的 248055

config.json 顶层一共 11 个键,其中四个是 token id。把它们拿到 added_tokens_decoder 里逐个查,对得上:

  • vision_start_token_id = 248053(config.json:139),对应 <|vision_start|>tokenizer_config.json:76
  • vision_end_token_id = 248054(config.json:138),对应 <|vision_end|>tokenizer_config.json:84
  • image_token_id = 248056(config.json:5),对应 <|image_pad|>tokenizer_config.json:100
  • video_token_id = 248057(config.json:121),对应 <|video_pad|>tokenizer_config.json:108

值得单独标出来的是:这四个 id 是 53、54、56、57,中间的 248055 不在其列。而 248055 在 added_tokens_decoder 里是有条目的,内容是 <|vision_pad|>specialtrue。也就是说,这个 token 存在于 tokenizer 侧,但 config.json 顶层没有任何字段引用它。这只是一条观察,它为什么没被顶层引用,我们不推断。

顺带记一句命名上的并列事实:tokenizer_config.json:292tokenizer_classQwen2Tokenizer,而 config.json:3architectures["Qwen3_5ForConditionalGeneration"],仓库名与 model card 标题(README.md:7)则是 Qwen3.8-27B。三处的版本字样各不相同,照实并列,不做解释。

special 字段在这里有一条分界线

33 个条目里,special 的取值不是一刀切的:

  • specialtrue 的:<|endoftext|><|im_start|><|im_end|>object_ref / box / quad 三对、<|vision_start|><|vision_end|><|vision_pad|><|image_pad|><|video_pad|>,以及 248070 之后的 audio 与 <tts_*> 那一段。
  • specialfalse 的:<tool_call>tokenizer_config.json:116 起)、</tool_call><|fim_prefix|><|file_sep|> 这六个、<tool_response>:181 起)、</tool_response><think>:196 起)、</think>:205 起)。

另一处相关的是 additional_special_tokenstokenizer_config.json:269),里面列了 13 个 token:<|im_start|><|im_end|>object_ref 两个、box 两个、quad 两个、<|vision_start|><|vision_end|><|vision_pad|><|image_pad|><|video_pad|>。这份清单里不含 <think></think><tool_call></tool_call><tool_response></tool_response>

如果你在写解析器、要判断某段输出算不算「特殊标记」,这条分界线是需要自己去核的:think 与 tool 那一组在 added_tokens_decoder 里有独立条目,但 specialfalse,也不在 additional_special_tokens 里。这个差别本身就是配置里写着的,我们只把它标出来,不判断它会导致什么。

顶层还有一处 token 别名表

tokenizer_config.json 这份文件是 17,928 字节、306 行,顶层共 16 个键(同样是用 Python 列出来的)。除了前面反复用到的 added_tokens_decoderadditional_special_tokens,还有一个键跟 token 直接相关:extra_special_tokenstokenizer_config.json:296)。它把七个名字映射到具体 token 上:

  • vision_bos_token<|vision_start|>vision_eos_token<|vision_end|>
  • image_token<|image_pad|>video_token<|video_pad|>
  • audio_bos_token<|audio_start|>audio_eos_token<|audio_end|>audio_token<|audio_pad|>

前四条指向的,正是 config.json 顶层那四个 token id 对应的同一批 token,只不过一边写的是数字 id、一边写的是字面量。后三条对应的则是上一节说过的 audio 那一段:这些 token 在 added_tokens_decoder 里有条目、在 extra_special_tokens 里有别名,但在 chat_template.jinja 全文里一次都没被引用。三处并列摆在这儿,至于它们各自会被哪条链路读到,本仓文件没有说明,我们不推断。

同一层还有几个取值,写解析或做预处理时容易撞上,一并记在这里:add_prefix_spacefalsetokenizer_config.json:2)、clean_up_tokenization_spacesfalse:286)、errorsreplace:288)、split_special_tokensfalse:291)、unk_tokennull:293)。另外 model_max_length262144:289),与 config.json:93max_position_embeddings 同值——这是两份文件里少见的、明确对得上的一处。以上都只是配置文件里写着的字面值,加载后实际是什么行为,我们没有验证过。

同一件事,三份文件三种写法

围绕 bos / eos / pad,这三份文件的口径并不一致,位置都能指得出来:

  • eosconfig.json:14eos_token_id 是单值 248044(对应 <|endoftext|>);generation_config.json:4-7 是数组 [248046, 248044],把 248046 放在首位;tokenizer_config.json:287eos_token<|im_end|>,也就是 248046。
  • bosconfig.json:12generation_config.json:2 都写了 bos_token_id: 248044;而 tokenizer_config.json:284bos_tokennull:294add_bos_tokenfalse
  • padconfig.json:101text_config 内)的 pad_token_idnullgeneration_config.json:8248044tokenizer_config.json:290pad_token<|endoftext|>

三处并列陈述完就停。哪一处在实际加载时生效,我们没有验证过,也不判断。 需要用到的时候,请以你所用推理框架的读取顺序与官方说明为准。

模板里真正会被写进 prompt 的是哪几个

chat_template.jinja 是这个仓库里唯一能看到这些 token 被怎么用的地方。跟 token id 直接相关的两行:

  • 第 18 行,图片项最终输出的是 <|vision_start|><|image_pad|><|vision_end|>
  • 第 29 行,视频项最终输出的是 <|vision_start|><|video_pad|><|vision_end|>

也就是说,顶层那四个 token id 里,vision_start / vision_end / image_pad / video_pad 四者在模板里是成组出现的。

反过来,模板里还有一批看着像标记、其实不是 token 的东西:<function=...><parameter=...><tools></tools><IMPORTANT>——这些在 added_tokens_decoder 的 33 个条目里都查不到,它们就是普通文本。而 <tool_call><tool_response> 有条目,模板里也确实用到(chat_template.jinja:68128130133143 以及 151-153)。写后处理逻辑时,这两类东西是要分开对待的。

我们没能核实的那一半,也得说清楚

回到开头那个数。我们做过一次减法:最大的新增 token id 是 248076,vocab_size 是 248320,两者相差 243(248320 − 248076 − 1 = 243)。这个数是我们自己算的,config.json 里并没有写它,它对应什么、是否与 Padded 那个字样有关,我们一律不推断。

更要紧的边界是:本地快照只取了 8 个文本配置文件,tokenizer.jsonvocab.jsonmerges.txt 都没有下载。因此——

  • 真实词表里到底有多少个条目、是否正好等于 config.json:117 的 248320,我们无法核实
  • <think> 这些 token 在真实词表里的编号是否与 added_tokens_decoder 完全一致,我们也无法核实

想自己核一遍的话,把这几个文件拉到本地后,用 Python 读 JSON 就够了,不需要加载模型:读 config.jsontext_config['vocab_size'],读 tokenizer_config.jsonadded_tokens_decoder 的键做排序,看条目数与首尾 id。这一步请用解析 JSON 的方式数,本文里的条目数、首尾 id 与键数都是这么得到的,而不是按行数或字节数估出来的。

最后提醒一句时间口径:以上所有数字都是我们在 2026-08-16 从该快照读出来的,模型仓库随上游更新而变动,真要拿去写代码,请以你手上那份文件的实际内容为准。

延伸阅读


本文依据 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?报名体系课或加入会员,照着学、照着用。