Qwen3.8-27B 的词表 248,320 与特殊 token:padded 是什么意思
看 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:117,text_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 Embedding 和 LM Output 在 model card 的参数表里是分开的两行(README.md:39 与 README.md:51),数值相同。与之对应的一个配置事实是:tie_word_embeddings 的值是 false,而且它在 config.json 里出现了两次——顶层一次(config.json:119)、text_config 里一次(config.json:115),两处值相同。
这两件事是并列的观察,我们只记录到这里,不去替它解释哪一处生效、也不解释这个取值会带来什么。
33 个新增 token:一段连续区间
tokenizer_config.json 的 added_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 个条目的 lstrip、rstrip、normalized、single_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|>,special 为 true。也就是说,这个 token 存在于 tokenizer 侧,但 config.json 顶层没有任何字段引用它。这只是一条观察,它为什么没被顶层引用,我们不推断。
顺带记一句命名上的并列事实:tokenizer_config.json:292 的 tokenizer_class 是 Qwen2Tokenizer,而 config.json:3 的 architectures 是 ["Qwen3_5ForConditionalGeneration"],仓库名与 model card 标题(README.md:7)则是 Qwen3.8-27B。三处的版本字样各不相同,照实并列,不做解释。
special 字段在这里有一条分界线
33 个条目里,special 的取值不是一刀切的:
special为true的:<|endoftext|>、<|im_start|>、<|im_end|>、object_ref/box/quad三对、<|vision_start|>、<|vision_end|>、<|vision_pad|>、<|image_pad|>、<|video_pad|>,以及 248070 之后的 audio 与<tts_*>那一段。special为false的:<tool_call>(tokenizer_config.json:116起)、</tool_call>、<|fim_prefix|>到<|file_sep|>这六个、<tool_response>(:181起)、</tool_response>、<think>(:196起)、</think>(:205起)。
另一处相关的是 additional_special_tokens(tokenizer_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 里有独立条目,但 special 是 false,也不在 additional_special_tokens 里。这个差别本身就是配置里写着的,我们只把它标出来,不判断它会导致什么。
顶层还有一处 token 别名表
tokenizer_config.json 这份文件是 17,928 字节、306 行,顶层共 16 个键(同样是用 Python 列出来的)。除了前面反复用到的 added_tokens_decoder 与 additional_special_tokens,还有一个键跟 token 直接相关:extra_special_tokens(tokenizer_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_space 是 false(tokenizer_config.json:2)、clean_up_tokenization_spaces 是 false(:286)、errors 是 replace(:288)、split_special_tokens 是 false(:291)、unk_token 是 null(:293)。另外 model_max_length 是 262144(:289),与 config.json:93 的 max_position_embeddings 同值——这是两份文件里少见的、明确对得上的一处。以上都只是配置文件里写着的字面值,加载后实际是什么行为,我们没有验证过。
同一件事,三份文件三种写法
围绕 bos / eos / pad,这三份文件的口径并不一致,位置都能指得出来:
eos:config.json:14的eos_token_id是单值248044(对应<|endoftext|>);generation_config.json:4-7是数组[248046, 248044],把 248046 放在首位;tokenizer_config.json:287的eos_token是<|im_end|>,也就是 248046。bos:config.json:12与generation_config.json:2都写了bos_token_id: 248044;而tokenizer_config.json:284的bos_token是null,:294的add_bos_token是false。pad:config.json:101(text_config内)的pad_token_id是null;generation_config.json:8是248044;tokenizer_config.json:290的pad_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:68、128、130、133、143 以及 151-153)。写后处理逻辑时,这两类东西是要分开对待的。
我们没能核实的那一半,也得说清楚
回到开头那个数。我们做过一次减法:最大的新增 token id 是 248076,vocab_size 是 248320,两者相差 243(248320 − 248076 − 1 = 243)。这个数是我们自己算的,config.json 里并没有写它,它对应什么、是否与 Padded 那个字样有关,我们一律不推断。
更要紧的边界是:本地快照只取了 8 个文本配置文件,tokenizer.json、vocab.json、merges.txt 都没有下载。因此——
- 真实词表里到底有多少个条目、是否正好等于
config.json:117的 248320,我们无法核实; <think>这些 token 在真实词表里的编号是否与added_tokens_decoder完全一致,我们也无法核实。
想自己核一遍的话,把这几个文件拉到本地后,用 Python 读 JSON 就够了,不需要加载模型:读 config.json 取 text_config['vocab_size'],读 tokenizer_config.json 取 added_tokens_decoder 的键做排序,看条目数与首尾 id。这一步请用解析 JSON 的方式数,本文里的条目数、首尾 id 与键数都是这么得到的,而不是按行数或字节数估出来的。
最后提醒一句时间口径:以上所有数字都是我们在 2026-08-16 从该快照读出来的,模型仓库随上游更新而变动,真要拿去写代码,请以你手上那份文件的实际内容为准。
延伸阅读
- 从头读起:Qwen3.8-27B 是什么:一个模型仓里有哪些文件、各自负责什么
- 本专题共 35 篇,完整分组目录见专题页
- Qwen3.8-27B 的 MTP:自述多步训练,配置里只有一层
- Qwen3.8-27B 的视觉塔 27 层:patch 16 与那个空的 deepstack 数组
本文依据 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 自述,我们没有复现。
模型仓库内容随上游更新而变动,请以官方最新说明为准。