ViT Transformer 编码器 本节摘要:光 patch 看不见。12 层 pre-LN transformer 带 12 注意力头把 patch token 序列变成上下文 token 序列,CLS token 在其末隐状态池整图特征。本节是每个现代视觉语言模型的引擎室。patch 嵌入产 197 token 序列,每个是对其他 patch 无觉察的向量;一张猫的图要每 patch 知道哪些 patch 含胡须、哪些含背景、哪些含眼,transformer 是一注意力层一层建觉察的机制。
本节摘要:光 patch 看不见。12 层 pre-LN transformer 带 12 注意力头把 patch token 序列变成上下文 token 序列,CLS token 在其末隐状态池整图特征。本节是每个现代视觉语言模型的引擎室。patch 嵌入产 197 token 序列,每个是对其他 patch 无觉察的向量;一张猫的图要每 patch 知道哪些 patch 含胡须、哪些含背景、哪些含眼,transformer 是一注意力层一层建觉察的机制。标准配方是 12 块深、12 头宽、pre-LayerNorm 放置、GELU 激活、4 倍前馈扩展——这是 CLIP ViT-L、SigLIP、DINOv2、Qwen-VL 族、InternVL、2025-2026 每个开源视觉编码器的脊柱,稳到你可读那些论文并默认此块形状,除非它们另说。
对应原课程:Phase 19 · Lesson 59 ·
vit-transformer(原英文phases/19-capstone-projects/59-vit-transformer/docs/en.md)。本节属「多模态/VLM」赛道第二节。
阅读完本节,你应当能够:
patch 嵌入产 197 token 序列,每个是对其他 patch 无觉察的向量。猫图要每 patch 知道哪些含胡须、哪些含背景、哪些含眼。transformer 是一注意力层一层建此觉察的机制。无它,patch 前端只是个聪明分词器无理解。标准配方 12 块深、12 头宽、pre-LN、GELU、4 倍前馈扩展,是每个现代视觉语言模型用的版本。
原版 Transformer 把 LayerNorm 放残差后。Pre-LN(LayerNorm 在每子层前)是每个现代视觉语言模型用的版本,因它训练稳无需学习率预热技巧。差异在前向是一行,12+ 深度的梯度流是天壤之别。
每头把 token 向量投到自己的 (query, key, value) 三元组,维 head_dim = hidden/num_heads。hidden=768、heads=12 时每头 dim=64。12 头并行注意,输出拼回 768 维过输出投影。多头的点是一头可学「注意猫眼」另一头学「注意背景梯度」互不干扰。
FFN 是 hidden -> 4*hidden -> hidden,中间 GELU。因子 4 是经验值,自 2017 跨语言与视觉 transformer 都成立。更小(2 倍)欠拟合;更大(8 倍)固定数据预算下过拟合。MLP 是模型存大部分学到事实的地方,更宽的中间是它们坐处。
| 组件 | ViT-Base 规模参数 |
|---|---|
| 每块 qkv 投影 | 3*768*768 = 1.77M |
| 每块输出投影 | 768*768 = 590K |
| 每块 FFN(4 倍) | 2*768*4*768 = 4.72M |
| 每块 LayerNorm | 4*768 = 3K |
| 每块总 | 约 7.1M |
| 12 块 | 约 85M |
| 加前端 | 约 86M 总 |
ViT-Base 是 86M 参数编码器,2026 标准算小(SigLIP-So400M 400M,Qwen-VL ViT 675M),但架构至宽深相同。
视觉 transformer 是纯编码器双向:token i 可注意任意 j。无掩码。第 59 节解码器侧交叉注意力会用因果掩码,但视觉编码器内注意力全连接。
CLS token 起作学参数,无自己 patch 内容,跨每块经注意力累积信息。到末层,CLS 行是整图向量摘要;下游头把这单向量投成类 logits、对比嵌入、或文本解码器的交叉注意力键。
code/main.py 实现:
MultiHeadSelfAttention:含 qkv 与输出投影、缩放点积注意力数学、形状断言。FeedForward:4 倍扩展 GELU MLP。Block:pre-LN 块组注意力与前馈子层带残差。ViT:12 块栈加最终 LayerNorm。VisionEncoder:把第 56 节 VisionFrontEnd 接到 ViT 栈,暴露 forward() 返上下文序列与池化 CLS 向量。class Block(nn.Module): def __init__(self, hidden=768, heads=12, mlp_ratio=4): self.ln1 = nn.LayerNorm(hidden) self.attn = MultiHeadSelfAttention(hidden, heads) self.ln2 = nn.LayerNorm(hidden) self.mlp = FeedForward(hidden, hidden * mlp_ratio) # 4 倍扩展 def forward(self, x): h = self.ln1(x); x = x + self.attn(h) # pre-LN: LN 在子层前 h = self.ln2(x); x = x + self.mlp(h) # 残差加 return x class VisionEncoder(nn.Module): def __init__(self, img_size=224, patch=16, hidden=768, depth=12, heads=12): self.front = VisionFrontEnd(img_size, patch, hidden) # 第 56 节前端 self.blocks = nn.ModuleList([Block(hidden, heads) for _ in range(depth)]) self.norm = nn.LayerNorm(hidden) def forward(self, x): tokens = self.front(x) # (B, 197, 768) for blk in self.blocks: tokens = blk(tokens) tokens = self.norm(tokens) return tokens, tokens[:, 0] # 序列 + CLS 池化
code/test_main.py 覆盖:单块保形状且对输入批大小不变;注意力分数沿键轴和为一(softmax 健全);残差路径接好(零输入仍经 CLS token 产非零输出);4 层叠前向产对形状;梯度从 CLS 输出流到 patch 投影。
设计要点:pre-LN 的
x = x + sublayer(ln(x))是现代稳训练的关键——LN 在子层前使梯度经残差直接回传,12+ 深度无预热技巧。4 倍 FFN 扩展是经验最优,MLP 存模型大部分事实。CLS token 经跨块注意力从每 patch 聚合,末层成整图摘要。无因果掩码——视觉编码器全连接双向,与第 34/37 节因果 LM 不同。
此处定义的编码器至宽深是 2025-2026 每个开源 VLM 内的同块栈。差异在:宽深(ViT-Large hidden=1024,depth=24,heads=16;SigLIP So400M hidden=1152,depth=27,heads=16,同块);池化头(CLS 池化本节 vs 平均池化 SigLIP vs 注意力池化后期 VLM);位置处理(固定正弦第 56 节 vs 学习 1D vs ALiBi vs 2D RoPE,块数学不变);register token(DINOv2 前置 4 个额外学 token,一行代码)。本节块栈是基底,第 58~61 节站其上。timm 的 VisionTransformer 是同实现可加载预训权重。本节手写让你看清 pre-LN、多头、4 倍 FFN、CLS 池化——这些在用 timm 时是黑盒。
code/main.py + code/test_main.py。demo 在 CPU 上几十秒跑完:夹具编码成 (1, 197, 768),CLS 范数随层组合向上漂、在最终 LayerNorm 稳定,总参数约 86M。VisionEncoder 是第 58~61 节的视觉底座,产 197 token 上下文序列 + CLS 池化向量供下游(投影层、交叉注意力)消费。
下一节,我们做「模态对齐投影层」——两层 MLP 把视觉 token 投进文本嵌入空间,余弦对齐损失拉近配对字幕。