Qwen3.8-27B 的视觉塔 27 层:patch 16 与那个空的 deepstack 数组
翻 Qwen3.8-27B 这个仓库时,有一件事挺容易漏掉:model card 的 “Model Overview” 那一节(README.md:32–README.md:53)看着挺全,隐藏维度、层数、注意力头数、FFN 中间维度、上下文长度都列了,但这些条目全都挂在 Language Model 这个层级之下。视觉编码器在整节里只出现过一次,就是第 34 行那句 Type: Causal Language Model with Vision Encoder。深度多少、每层多宽、patch 怎么切、有几个注意力头——一个数字都没有。
想知道这些,只能去读仓库根目录的 config.json。这篇就干这一件事:把 vision_config 这一块逐个键抄出来,和随仓的两个 preprocessor 配置对一遍,再把那个空数组照实记下来。
先说清楚边界。这是一个模型权重仓,不含建模源码,architectures 里那个 Qwen3_5ForConditionalGeneration 类的实现不在本仓。所以下面所有字段,我们能给的只有名字、值、所在行号,以及它和别的文件对不对得上。字段在推理时怎么被用、值改了会怎样,本仓文件没有写,我们也不推断。我们没有下载权重、没有部署、没有推理过一个 token,本文不涉及生成质量、速度、显存的任何描述。
vision_config 一共十四个键
config.json 整个文件 4,312 字节、140 行,结构只有三层:顶层、text_config、vision_config。vision_config 是 config.json:122–config.json:137,用 Python 数出来是 14 个键:
| 字段名 | 值 | 行号 |
|---|---|---|
deepstack_visual_indexes | [](空数组) | config.json:123 |
depth | 27 | config.json:124 |
hidden_act | "gelu_pytorch_tanh" | config.json:125 |
hidden_size | 1152 | config.json:126 |
in_channels | 3 | config.json:127 |
initializer_range | 0.02 | config.json:128 |
intermediate_size | 4304 | config.json:129 |
model_type | "qwen3_5" | config.json:130 |
num_heads | 16 | config.json:131 |
num_position_embeddings | 2304 | config.json:132 |
out_hidden_size | 5120 | config.json:133 |
patch_size | 16 | config.json:134 |
spatial_merge_size | 2 | config.json:135 |
temporal_patch_size | 2 | config.json:136 |
这十四个数字,在 README.md 里一个都查不到。depth=27、hidden_size=1152、num_heads=16、intermediate_size=4304、num_position_embeddings=2304 这五项我们逐个在 model card 里检索过,均无对应表述。
三层的键数放在一起看更直观:顶层 11 个键、text_config 34 个键、vision_config 14 个键。视觉这一块是三层里最短的一块,十四行读完就到底了,中间没有再嵌套的子对象——config.json 里唯一的子对象是 text_config 下的 rope_parameters,视觉侧没有对应的东西。
第一个坑:层数字段不叫 num_hidden_layers
如果你按语言侧的习惯去 vision_config 里找 num_hidden_layers,会找不到——这个键在 vision_config 里根本不存在。视觉侧的层数字段名是 depth,值是 27(config.json:124)。而语言侧用的是 num_hidden_layers: 64(config.json:98)。同一份 config.json 里,两块用了两个不同的字段名表示层数。
顺手可以对照的还有几处「视觉侧没有」:text_config 里存在的 dtype、attention_dropout、rms_norm_eps、attention_bias 这四个键,在 vision_config 的十四个键里一个都没有。激活函数也不是同一个值:text_config.hidden_act 是 "silu"(config.json:17),vision_config.hidden_act 是 "gelu_pytorch_tanh"(config.json:125)。initializer_range 倒是两边都有,值都是 0.02(config.json:19、config.json:128)。
另一处数值上的巧合:vision_config.out_hidden_size 是 5120,与 text_config.hidden_size 的 5120(config.json:18)相同;而 vision_config.hidden_size 自己是 1152。我们只陈述这三个数字的字面关系,不解释它们在前向过程里各自承担什么。
patch 16 与合并尺寸 2:三个文件、两套字段名
patch_size、temporal_patch_size、spatial_merge_size 这一组,是本仓少数能跨文件互相印证的地方。除了 config.json,随仓还有 preprocessor_config.json(图像侧,21 行)和 video_preprocessor_config.json(视频侧,21 行)。三处对照如下:
| 项 | config.json 的 vision_config | preprocessor_config.json | video_preprocessor_config.json |
|---|---|---|---|
| patch 尺寸 | patch_size: 16(:134) | patch_size: 16(:6) | patch_size: 16(:6) |
| 时间 patch | temporal_patch_size: 2(:136) | temporal_patch_size: 2(:7) | temporal_patch_size: 2(:7) |
| 合并尺寸 | spatial_merge_size: 2(:135) | merge_size: 2(:8) | merge_size: 2(:8) |
数值三处全对得上。但字段名并不一样:在 config.json 里它叫 spatial_merge_size,在两个 preprocessor 里叫 merge_size。如果你要写脚本去比对这两侧配置,或者要给某个推理框架传参,这个名字差异是必须先知道的一件事——按 spatial_merge_size 去 grep 两个 preprocessor 文件,会一无所获。
两个 preprocessor 里还有一组类名值得并列记下来:processor_class 都是 "Qwen3VLProcessor"(preprocessor_config.json:19、video_preprocessor_config.json:19),而 image_processor_type 是 "Qwen2VLImageProcessorFast"(preprocessor_config.json:20),video_processor_type 是 "Qwen3VLVideoProcessor"(video_preprocessor_config.json:20)。这三个类名分别带 Qwen3VL 与 Qwen2VL 字样,而 config.json:3 的 architectures 写的是 Qwen3_5ForConditionalGeneration,config.json:7 的 model_type 是 "qwen3_5"。四处并列在这里,我们不推断原因、也不判断哪个是「对的」。
顺带一提 vision_config.model_type 的值是 "qwen3_5",与顶层 model_type(config.json:7)完全相同;而 text_config.model_type 带了后缀,是 "qwen3_5_text"(config.json:94)。视觉侧没有对应的 _vision 后缀。
那个空的 deepstack_visual_indexes
vision_config 的第一个键是 deepstack_visual_indexes,值是 [](config.json:123)。
这里要抠得细一点,因为三种情况在写文章时经常被混为一谈:这个键不是缺失、不是 null,而是存在且值为一个长度为 0 的数组。我们用 Python 校验过类型与长度,打印结果是 deepstack [] <class 'list'> 0。
然后是第二件事:README.md 全文检索 deepstack(不区分大小写)零命中。也就是说,model card 里没有任何一句话解释这个字段是什么、空数组代表什么、什么情况下该往里填东西。本仓又没有建模源码可读,所以我们没能在仓库里找到与之对应的任何说明或实现。
按「配置里出现 ≠ 功能可用」这条规矩,正确的处理方式是照实标注:键在、值为空数组、本仓无说明。至于它意味着某个能力被关掉了、还是这一版本就没有这一路,我们没有依据,不推断。你要是把它当成一个已经生效的开关去写文档或做二次封装,那是在拿一个仓库里没有依据的假设当前提。
同样的纪律也适用于旁边那几个字段。in_channels: 3、num_position_embeddings: 2304、num_heads: 16 这些名字看着都很好猜,但「好猜」和「仓库里写了」是两回事。本仓能给的就是字面值。
顺便说个读配置时的通用习惯,和这个仓库无关:判断一个键是「不存在」「值为 null」还是「值为空容器」,不要靠肉眼扫 JSON 文本,用 k in obj、obj[k] is None、len(obj[k]) 三步分别问一遍。这三种状态在下游代码里往往走完全不同的分支,而在文本里看起来差别很小。deepstack_visual_indexes 属于第三种。
视觉相关的四个 token id
视觉侧还有一组数字落在 config.json 顶层,不在 vision_config 里面。把它们拿到 tokenizer_config.json 的 added_tokens_decoder 里逐个查,四个都对得上:
| 顶层字段 | id | tokenizer 中的 content |
|---|---|---|
vision_start_token_id(config.json:139) | 248053 | <|vision_start|> |
vision_end_token_id(config.json:138) | 248054 | <|vision_end|> |
image_token_id(config.json:5) | 248056 | <|image_pad|> |
video_token_id(config.json:121) | 248057 | <|video_pad|> |
一个可以顺手记下来的观察:顶层引用的四个 id 是 53、54、56、57,中间的 248055 没有被顶层任何字段引用。它对应什么,我们没有排查本仓 33 个新增 token 的完整清单,不做猜测。
你自己核一遍
上面每个数字都可以在本地复核,不需要下载权重。把仓库里的 config.json、preprocessor_config.json、video_preprocessor_config.json 放在同一个目录下,在 <你的项目目录> 里执行:
python -c "
import json,io
d=json.load(io.open('config.json',encoding='utf-8'))
v=d['vision_config']
print('n vision keys', len(v))
for k in v: print(k, '=', v[k])
print('deepstack', v['deepstack_visual_indexes'], type(v['deepstack_visual_indexes']), len(v['deepstack_visual_indexes']))
"
Windows 下 PowerShell 与 cmd 对引号的处理和 bash 不同,把上面这段存成 .py 文件再 python 该文件名 执行更省事;Linux / macOS 侧直接照抄即可。这是我们统计事实时用的口径,不涉及模型加载。
收尾时要留的几条限定
- model card 自述本模型是
native vision-language model(README.md:20),Highlights 第五条写的是Native support for image and video understanding, from STEM diagrams and documents to hour-scale videos.(README.md:29)。这些是 model card 的自述,本仓没有可供核对的实现,我们也没有跑过任何评测。 video_preprocessor_config.json里size.longest_edge的发布值是25165824,而README.md:561自称这份size是 “conservatively configured”,并在README.md:563建议改成{"longest_edge": 469762048, "shortest_edge": 4096}。发布值与官方建议值不是一个数,两处并列在此,不推断影响。- model card 在
README.md:407的注释里写This feature is currently supported only in vLLM.(指通过extra_body配视频帧采样),该段本身是被注释掉的示例代码。属于限定条件,照实标出。 - 截至 2026-08-16,这个仓库的下载量与点赞数是 267,725 / 10,284,权重按 18 个
.safetensors分片发布。这两组数字只是当天的元数据,不用来推导质量、成熟度,也不用来推算体积或资源需求。
最后回到开头那件事:视觉侧的十四个数字只在 config.json 里,model card 一个字都没写。这不是一个需要解释的现象,只是一个你在读这个仓库时必须知道的事实——找不到,就是没写在那儿,去 config.json:122–config.json:137 看。
延伸阅读
- 从头读起:Qwen3.8-27B 是什么:一个模型仓里有哪些文件、各自负责什么
- 本专题共 35 篇,完整分组目录见专题页
- Qwen3.8-27B 的词表 248,320 与特殊 token:padded 是什么意思
- Qwen3.8-27B 的 64 层怎么排:全注意力恰好 16 个,下标全是 4k+3
本文依据 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 自述,我们没有复现。
模型仓库内容随上游更新而变动,请以官方最新说明为准。