Transformers 中的 DINOv2:用 ViT 视觉基础模型做图像分类、稠密任务与特征提取的完整实战指南
本篇指南以 DINOv2 官方模型文档 为主体,围绕 Facebook AI 发布的 DINOv2 视觉基础模型(vision foundation model)在 🤗 Transformers 仓库中的实现展开。你将学会:如何用
Pipeline或AutoModel一行式完成图像分类、如何正确拆分输出得到整图CLS向量与逐 patch 局部特征、如何借助 torchao 对超大模型(如 giant 版本)做 int4 权重量化、如何用torch.jit.trace加速推理,并深入理解 Dinov2Config 与 modeling_dinov2.py 中的架构细节,以及 DINOv2 如何作为 Backbone 接入 DETR、MaskFormer 等下游框架。
DINOv2 是什么:自监督视觉基础模型在 Transformers 中的落地
DINOv2 是基于 ViT(Vision Transformer)构建的视觉基础模型。它继承了 ViT 将图像划分为 patch 并做自注意力的骨干结构,通过自监督预训练获得了可复用于多种下游任务的高质量视觉表示,例如图像分类(image classification)与深度估计(depth estimation)。
从模型文档可以看出,DINOv2 的工程贡献主要体现在训练层面的稳定与加速,包括:
- 更快、更省内存的注意力实现(memory-efficient attention);
- 序列打包(sequence packing),提高批量训练吞吐;
- 改进的随机深度(improved stochastic depth,即按样本丢弃残差分支的正则化手段);
- 全分片数据并行(Fully Sharded Data Parallel,FSDP),用于在大量 GPU 上稳定训练超大模型;
- 模型蒸馏(model distillation),用教师模型引导小模型学习。
在 Transformers 仓库中,DINOv2 的完整实现位于:
- configuration_dinov2.py —— 配置类
Dinov2Config; - modeling_dinov2.py —— 模型类
Dinov2Model、Dinov2ForImageClassification、Dinov2Backbone及各内部模块; - convert_dinov2_to_hf.py —— 将原始 DINOv2 checkpoint 转换为 HF 格式的脚本;
- test_modeling_dinov2.py —— 配套测试套件(覆盖模型输出、Backbone、Pipeline 等)。
模型文档中给出的实践例子主要使用 facebook/dinov2-base、facebook/dinov2-small-imagenet1k-1-layer 与 facebook/dinov2-giant-imagenet1k-1-layer 等公开 checkpoint(原始 DINOv2 系列 checkpoint 统一收录在 facebook 的 Dinov2 collection 下),读者可按需替换为其他规模。
快速上手:获取图像 Embedding 与分类结果
方式一:用 Pipeline(一行式)
Pipeline 是 Transformers 提供的最简 API,只需指定任务类型 "image-classification" 与模型名即可完成"加载模型 + 预处理 + 后处理"全流程:
from transformers import pipeline
pipe = pipeline(
task="image-classification",
model="facebook/dinov2-small-imagenet1k-1-layer",
device=0
)
pipe("https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/pipeline-cat-chonk.jpeg")
参数说明:
task="image-classification":将自动装配图像预处理与分类头推理;model:指定在 Hub 上托管、与 HF 格式兼容的 DINOv2 分类 checkpoint;device=0:显式将模型放到 0 号 GPU 上(CPU 环境可省略或改为device=-1)。
方式二:用 AutoModel 显式组合 processor 与 model
当需要手动控制预处理与推理细节(例如拿到 logits 再自行 argmax)时,使用 AutoImageProcessor + AutoModelForImageClassification 更合适:
import requests
from PIL import Image
from transformers import AutoImageProcessor, AutoModelForImageClassification
url = "http://images.cocodataset.org/val2017/000000039769.jpg"
image = Image.open(requests.get(url, stream=True).raw)
processor = AutoImageProcessor.from_pretrained("facebook/dinov2-small-imagenet1k-1-layer")
model = AutoModelForImageClassification.from_pretrained(
"facebook/dinov2-small-imagenet1k-1-layer",
device_map="auto",
attn_implementation="sdpa"
)
inputs = processor(images=image, return_tensors="pt").to(model.device)
logits = model(**inputs).logits
predicted_class_idx = logits.argmax(-1).item()
print("Predicted class:", model.config.id2label[predicted_class_idx])
值得注意的实现细节:
device_map="auto"配合.to(model.device),保证在多 GPU/CPU offload 场景下输入张量与模型参数所在设备一致;attn_implementation="sdpa"显式启用 PyTorch 的 scaled dot-product attention 加速路径。在 modeling_dinov2.py 中,Dinov2PreTrainedModel声明了_supports_sdpa = True、_supports_flash_attn = True、_supports_flex_attn = True以及_supports_attention_backend = True;前向时Dinov2SelfAttention通过ALL_ATTENTION_FUNCTIONS.get_interface(self.config._attn_implementation, eager_attention_forward)动态选择注意力后端(见src/transformers/models/dinov2/modeling_dinov2.py的自注意力实现),文档头部徽标也标注了该模型同时支持 FlashAttention 与 SDPA;model.config.id2label[predicted_class_idx]将类别索引映射回可读标签(id2label在加载分类 checkpoint 时自动填充)。
若仅需图像 Embedding 而非分类结果,可改用 AutoModel(对应裸编码器 Dinov2Model),下一节将详述其输出的结构。
理解输出结构:CLS token 与逐 patch 局部特征
DINOv2 基于 ViT,把输入图像切分为固定大小的 patch 后送入 Transformer。因此模型输出的每个 token 都对应着图像的一块空间区域。官方模型文档给出的核心拆解思路是:
CLStoken:位于序列最前端的整图语义向量,适合分类、检索等全局任务;- 逐 patch 的局部 embedding:每个 token 对应输入图像中一个
patch_size × patch_size的局部区域,适合语义分割等稠密任务。
原文档的完整示例(对 facebook/dinov2-base,patch size 为 14)如下:
from transformers import AutoImageProcessor, AutoModel
from PIL import Image
import requests
url = 'http://images.cocodataset.org/val2017/000000039769.jpg'
image = Image.open(requests.get(url, stream=True).raw)
print(image.height, image.width) # [480, 640]
processor = AutoImageProcessor.from_pretrained('facebook/dinov2-base')
model = AutoModel.from_pretrained('facebook/dinov2-base', device_map="auto")
patch_size = model.config.patch_size
inputs = processor(images=image, return_tensors="pt").to(model.device)
print(inputs.pixel_values.shape) # [1, 3, 224, 224]
batch_size, rgb, img_height, img_width = inputs.pixel_values.shape
num_patches_height, num_patches_width = img_height // patch_size, img_width // patch_size
num_patches_flat = num_patches_height * num_patches_width
outputs = model(**inputs)
last_hidden_states = outputs[0]
print(last_hidden_states.shape) # [1, 1 + 256, 768]
assert last_hidden_states.shape == (batch_size, 1 + num_patches_flat, model.config.hidden_size)
cls_token = last_hidden_states[:, 0, :]
patch_features = last_hidden_states[:, 1:, :].unflatten(1, (num_patches_height, num_patches_width))
从源码角度理解这套张量形状:
Dinov2PatchEmbeddings使用nn.Conv2d(num_channels, hidden_size, kernel_size=patch_size, stride=patch_size)将(batch, 3, 224, 224)的图像投影为 patch token(modeling_dinov2.py 中 PatchEmbeddings 实现),224×224 图像经 14×14 卷积后得到 16×16=256 个 patch;Dinov2Embeddings在 patch 序列前拼接一个可学习的cls_token参数,因此序列长度为1 + 256(对应源码中torch.cat((cls_tokens, embeddings), dim=1)),位置编码维度也是num_patches + 1;Dinov2Model.forward在编码器之后追加一层LayerNorm,并取sequence_output[:, 0, :]作为pooler_output,返回BaseModelOutputWithPooling(modeling_dinov2.py 中Dinov2Model的实现);patch_features通过.unflatten(1, (H, W))还原为(batch, H, W, hidden)的二维网格布局,便于后续做逐像素级稠密预测。
不同任务如何取用输出
| 任务类型 | 取用哪个部分 | 说明 |
|---|---|---|
| 图像分类 | cls_token(第 0 个 token) |
整图全局表征,进入分类头 |
| 图像检索/比对 | cls_token |
作为整图向量参与度量 |
| 语义分割等稠密任务 | patch_features |
每个 patch 的局部表征,可 reshape 回空间网格 |
值得一提:Dinov2ForImageClassification 的分类头并非只用 CLS token。查看 modeling_dinov2.py 中分类模型的前向代码可以看到,它把 [CLS] token 与全部 patch token 的均值向量拼接为 hidden_size * 2 维后送入 nn.Linear 分类层,即 logits = classifier(cat([cls, patch_tokens.mean(dim=1)]))——这是一种全局+局部信息融合的分类策略,对 ImageNet 等任务效果稳定。
处理高分辨率输入:位置编码插值(interpolate_pos_encoding)
文档示例中输入图像为 224×224,与预训练分辨率一致。若读者传入更高分辨率图像,Dinov2Embeddings 内置的 interpolate_pos_encoding 方法(modeling_dinov2.py)会自动处理:
- 当 patch 数量与预训练位置编码数量一致且宽高相等时,直接复用预训练位置编码;
- 否则将 patch 位置编码按空间网格重排,用
nn.functional.interpolate以 bicubic 模式在 float32 精度下插值到新的(height/patch_size, width/patch_size)网格,再做torch.cat拼接回CLS位置编码; - 该方法对
torch.jit追踪做了专门适配:追踪时始终执行插值分支,保证导出模型在动态输入尺寸下依然正确。
这意味着 Dinov2Model 天然支持任意分辨率的图像输入(patch 数整除即可),无需重新训练。
用 torchao 做 int4 权重量化,降低大模型显存负担
DINOv2 的 giant 等大版本参数量可观,权重量化(将权重表示为更低精度)可以显著降低内存占用。文档推荐的做法是使用 torchao 对权重做 int4 量化的方案(Int4WeightOnlyConfig),仅量化权重、不量化激活:
# pip install torchao
import requests
from PIL import Image
from torchao.quantization import Int4WeightOnlyConfig
from transformers import AutoImageProcessor, AutoModelForImageClassification, TorchAoConfig
url = 'http://images.cocodataset.org/val2017/000000039769.jpg'
image = Image.open(requests.get(url, stream=True).raw)
processor = AutoImageProcessor.from_pretrained('facebook/dinov2-giant-imagenet1k-1-layer')
quant_config = Int4WeightOnlyConfig(group_size=128)
quantization_config = TorchAoConfig(quant_type=quant_config)
model = AutoModelForImageClassification.from_pretrained(
'facebook/dinov2-giant-imagenet1k-1-layer',
device_map="auto",
quantization_config=quantization_config
)
inputs = processor(images=image, return_tensors="pt").to(model.device)
outputs = model(**inputs)
logits = outputs.logits
predicted_class_idx = logits.argmax(-1).item()
print("Predicted class:", model.config.id2label[predicted_class_idx])
要点:
- 先安装
torchao(# pip install torchao),并导入Int4WeightOnlyConfig; Int4WeightOnlyConfig(group_size=128)表示按 128 个权重组共享缩放因子进行分组 int4 量化;- 通过
TorchAoConfig把量化配置交给AutoModelForImageClassification.from_pretrained,加载时即完成量化,无需手动改模型结构; - 更适合量化收益大的大 checkpoint(文档示例即选用
facebook/dinov2-giant-imagenet1k-1-layer)。
Transformers 支持多种量化后端,各方案的完整说明可参考 Quantization 概念指南 与 torchao 量化文档。
推理加速:使用 torch.jit.trace
对已经训练好的模型,可用 torch.jit.trace 将其编译为 TorchScript 图来加速推理。模型文档给出如下做法,并明确提示追踪会产生轻微数值差异:原始模型与 traced 模型输出的最大绝对差值约为 1e-4,属正常范围。
import torch
from transformers import AutoImageProcessor, AutoModel
from PIL import Image
import requests
url = 'http://images.cocodataset.org/val2017/000000039769.jpg'
image = Image.open(requests.get(url, stream=True).raw)
processor = AutoImageProcessor.from_pretrained('facebook/dinov2-base')
model = AutoModel.from_pretrained('facebook/dinov2-base', device_map="auto")
inputs = processor(images=image, return_tensors="pt").to(model.device)
outputs = model(**inputs)
last_hidden_states = outputs[0]
# We have to force return_dict=False for tracing
model.config.return_dict = False
with torch.no_grad():
traced_model = torch.jit.trace(model, [inputs.pixel_values])
traced_outputs = traced_model(inputs.pixel_values)
print((last_hidden_states - traced_outputs[0]).abs().max())
两个关键细节:
- 追踪前必须设置
model.config.return_dict = False:torch.jit.trace要求输入输出为张量(或张量元组),而默认的 dict 输出无法直接被 trace,因此需要关闭return_dict; - 外层包裹
torch.no_grad(),避免追踪过程记录计算图; - 追踪后建议自行验证数值一致性(文档示例即打印原始输出与追踪输出的最大绝对差),据此判断精度损失是否在任务容忍范围内。
此外,由于 DINOv2 支持 _supports_sdpa,在支持的 GPU 上还可通过 attn_implementation="sdpa"(甚至 FlashAttention)进一步降低注意力计算开销,两者可叠加使用。
Dinov2Config:配置项与底层架构解析
Dinov2Config(configuration_dinov2.py)继承自 PreTrainedConfig,并混入了 BackboneConfigMixin(使其可作为 Backbone 输出多尺度特征)。除继承通用字段外,其独有及常用配置项如下:
尺寸与输入
| 配置项 | 默认值 | 说明 |
|---|---|---|
image_size |
224 |
预训练输入分辨率,支持 int 或 (H, W) 元组 |
patch_size |
14 |
patch 边长;patch 数由 (H // patch) * (W // patch) 决定 |
num_channels |
3 |
输入图像通道数 |
hidden_size |
768 |
隐藏层维度(base 规模) |
num_hidden_layers |
12 |
Transformer 编码器层数 |
num_attention_heads |
12 |
自注意力头数 |
mlp_ratio |
4 |
MLP 隐藏维度相对 hidden_size 的倍数 |
hidden_act |
"gelu" |
激活函数 |
hidden_dropout_prob |
0.0 |
token 序列 Dropout 概率 |
attention_probs_dropout_prob |
0.0 |
注意力权重 Dropout 概率 |
initializer_range |
0.02 |
权重初始化标准差 |
layer_norm_eps |
1e-6 |
LayerNorm 的 epsilon |
qkv_bias |
True |
注意力 Q/K/V 投影是否带偏置 |
DINOv2 特有训练/结构开关
| 配置项 | 默认值 | 说明 |
|---|---|---|
layerscale_value |
1.0 |
LayerScale 初始值。DINOv2 在每块 Transformer 输出前乘一个可学习的逐维缩放向量 lambda1(Dinov2LayerScale),初始化为该值,用于稳定深层 ViT 训练 |
drop_path_rate |
0.0 |
随机深度(Stochastic Depth)丢弃概率。对应 Dinov2DropPath 模块,按样本以 keep_prob 保留残差分支;为 0 时退化为恒等映射 |
use_swiglu_ffn |
False |
是否使用 SwiGLU 前馈网络。开启后使用 Dinov2SwiGLUFFN:silu(x1) * x2 门控结构,隐藏维度按 2/3 缩放并向上取整到 8 的倍数;关闭时用标准双层 Dinov2MLP |
use_mask_token |
True |
是否在 embedding 中使用可学习的 mask_token。预训练(配合 bool_masked_pos)时需要,推理时传 bool_masked_pos=None 即可 |
apply_layernorm |
True |
模型作为 Backbone 使用时,是否对特征图应用 LayerNorm |
reshape_hidden_states |
True |
作为 Backbone 时,特征图是否重塑为 4D (batch, hidden, height, width);若为 False 则保持 3D (batch, seq_len, hidden) |
配置类的 __post_init__ 会按层数自动生成 stage_names = ["stem", "stage1", ..., "stageN"],并通过 set_output_features_output_indices 接收 out_features/out_indices 参数(供 Backbone 抽取指定 stage 使用)。
从代码结构看 DINOv2 编码器的设计
逐块阅读 modeling_dinov2.py 可还原出 DINOv2 与经典 ViT 的差异:
- Pre-LN + 残差:
Dinov2Layer先对输入做norm1再进自注意力,随后drop_path(attn_output) + hidden_states做第一次残差;再做norm2+ MLP + LayerScale +drop_path第二次残差。文档源码注释特别指出 DINOv2 在自注意力之后、MLP 之前同样施加了 LayerNorm; - LayerScale:每块输出前乘逐维可学习系数
lambda1(初始化为layerscale_value); - 梯度检查点:
Dinov2Layer继承自GradientCheckpointingLayer,模型级supports_gradient_checkpointing = True,可在显存受限时开启激活重计算; - 权重初始化:
_init_weights对 Linear/Conv2d 使用截断正态分布,位置编码与CLStoken 亦用截断正态初始化,mask_token与 LayerScale 则用常数初始化。
进阶用法:Dinov2Backbone 接入目标检测与分割框架
模型文档提到 DINOv2 适用于分类、深度估计等任务;而要让其作为通用视觉骨干接入 DETR、MaskFormer 这类需要多尺度特征图的框架,可使用 Dinov2Backbone(modeling_dinov2.py 中定义),并通过 AutoBackbone 加载。模型自身 docstring 给出了完整的可运行示例:
>>> from transformers import AutoImageProcessor, AutoBackbone
>>> import torch
>>> from PIL import Image
>>> import httpx
>>> from io import BytesIO
>>> url = "http://images.cocodataset.org/val2017/000000039769.jpg"
>>> with httpx.stream("GET", url) as response:
... image = Image.open(BytesIO(response.read()))
>>> processor = AutoImageProcessor.from_pretrained("facebook/dinov2-base")
>>> model = AutoBackbone.from_pretrained(
... "facebook/dinov2-base", out_features=["stage2", "stage5", "stage8", "stage11"]
... )
>>> inputs = processor(image, return_tensors="pt")
>>> outputs = model(**inputs)
>>> feature_maps = outputs.feature_maps
>>> list(feature_maps[-1].shape)
[1, 768, 16, 16]
Backbone 前向的核心逻辑是:强制输出全部 hidden_states,再依据 stage_names(stem + stage1..stageN)与 out_features 匹配抽取指定层,并对命中的层依次执行:可选的 LayerNorm(apply_layernorm)、去掉 CLS token([:, 1:])、按 patch 网格重塑为 4D 特征图((batch, height/patch, width/patch, hidden) 再 permute 为 (batch, hidden, H, W))。因此最后一个 stage 输出的形状为 [1, 768, 16, 16](224×224 输入、patch 14 时对应 16×16 的空间分辨率)。
需要说明的是,源码中注释提醒:reshape 时"height、width 顺序与原始实现相反"是原版 DINOv2 中即存在的行为,Transformers 出于复现一致性有意保留。
小结
本文围绕 DINOv2 模型文档完整梳理了从调用到源码的实践路径:
- 快速使用:
pipeline("image-classification", ...)一行完成分类;AutoImageProcessor+AutoModelForImageClassification适合精细控制,且可开启sdpa注意力加速; - 输出语义:
Dinov2Model输出为1 + num_patches个 token,CLStoken 服务全局任务、patch 特征服务稠密任务,分类头采用CLS与 patch 均值拼接的策略; - 大模型落地:torchao 的
Int4WeightOnlyConfig+TorchAoConfig可在加载 giant 版本时将权重降至 int4;torch.jit.trace(关闭return_dict)可进一步提速,精度差约在1e-4量级; - 架构理解:Dinov2Config 的 LayerScale、DropPath、SwiGLU FFN、mask token 等开关与
Dinov2Layer的 pre-LN + 双残差结构一一对应; - 特征抽取:
Dinov2Backbone支持多 stage 特征图输出,可平滑接入 DETR、MaskFormer 等下游框架。
上述所有代码均可基于本文示例在本仓库环境直接运行,更详尽的 API 签名可查阅 Dinov2Config、Dinov2Model.forward、Dinov2ForImageClassification.forward 的自动生成文档(原文档以 autodoc 形式展开),以及配套测试文件 tests/models/dinov2/test_modeling_dinov2.py 中与 Backbone、Pipeline 相关的用例。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0629
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00