Skip to content

🚨 🚨Bring some dinos to modern standards#46266

Open
molbap wants to merge 32 commits into
mainfrom
improve_dinos
Open

🚨 🚨Bring some dinos to modern standards#46266
molbap wants to merge 32 commits into
mainfrom
improve_dinos

Conversation

@molbap

@molbap molbap commented May 28, 2026

Copy link
Copy Markdown
Collaborator

CI

What does this PR do?

Part of the larger vision model refactor #41693 focused on dinov2, which has still some usage and downloads, but mostly serves as a basis for many other models. Attempt at putting this in line with the rest of the lib.

image

@HuggingFaceDocBuilderDev

Copy link
Copy Markdown

The docs for this PR live here. All of your documentation changes will be reflected on that endpoint. The docs are available until 30 days after the last update.

@guarin guarin left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, this will make things much easier! Left more questions than comments :)

Comment thread src/transformers/models/dinov2/modeling_dinov2.py Outdated
Comment thread src/transformers/models/dinov2/modeling_dinov2.py
Comment thread src/transformers/models/dinov2/modeling_dinov2.py Outdated
Comment thread src/transformers/models/dinov2/modeling_dinov2.py Outdated
Comment thread src/transformers/models/dinov2/modeling_dinov2.py Outdated
Comment thread src/transformers/models/dinov2/modular_dinov2.py Outdated
Comment thread src/transformers/models/dinov2_with_registers/modular_dinov2_with_registers.py Outdated
Comment thread src/transformers/models/dinov3_vit/modular_dinov3_vit.py
Comment thread tests/models/depth_anything/test_modeling_depth_anything.py
@molbap molbap mentioned this pull request May 29, 2026
39 tasks
@molbap
molbap marked this pull request as ready for review June 4, 2026 13:04
@molbap molbap changed the title Improve dinos 🚨 🚨Bring some dinos to modern standards Jun 4, 2026
Comment thread src/transformers/models/dinov2/modeling_dinov2.py

@guarin guarin left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice refactor! Looks mostly good to me, left some comments and questions :)

Comment thread src/transformers/models/dinov2/modeling_dinov2.py
Comment thread src/transformers/models/dinov2/modeling_dinov2.py
Comment thread src/transformers/models/dinov2/modeling_dinov2.py Outdated
Comment thread src/transformers/models/dinov2/modular_dinov2.py Outdated
Comment thread src/transformers/models/dinov3_vit/modeling_dinov3_vit.py Outdated
Comment thread src/transformers/models/dinov3_vit/modular_dinov3_vit.py Outdated
Comment thread src/transformers/models/rf_detr/modular_rf_detr.py Outdated
Comment thread src/transformers/models/rf_detr/modeling_rf_detr.py Outdated
Comment thread tests/models/dinov2_with_registers/test_modeling_dinov2_with_registers.py Outdated

@vasqu vasqu left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only took a look at dinov2, I think we need to make sure this is good and then we can propogate based on that.

Sorry for the confusion on some comments, please read everything before responding 🫠

pass


class Dinov2Embeddings(nn.Module):

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any reason we don't inherit from e.g. BeitEmbeddings. Not convinced that we cannot use modular here

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I couldv'e sworn I had answered these 👁️
however it's a bit tricky, it can change logits a bit due to upcasting. Trying a run-slow though

return hidden_state * self.lambda1


class Dinov2MLP(nn.Module):

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Imo can be CLIPMLP for the forward

Comment thread src/transformers/models/dinov2/modular_dinov2.py Outdated
Comment thread src/transformers/models/dinov2/modular_dinov2.py Outdated

def __init__(self, config: Dinov2Config) -> None:
super().__init__()
self.norm1 = nn.LayerNorm(config.hidden_size, eps=config.layer_norm_eps)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Imo, we could copy llama here and add the layer_scales (which are the only new thing)

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inheriting LlamaDecoderLayer means overriding both init and forward, beyond the layer scales we'd also be swapping RMSNorm→LayerNorm and dropping all the cache/RoPE 😬

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense, always annoying 😢

Comment thread src/transformers/models/dinov2/modular_dinov2.py Outdated
self.embeddings = Dinov2Embeddings(config)
self.encoder = Dinov2Encoder(config)
self.layernorm = nn.LayerNorm(config.hidden_size, eps=config.layer_norm_eps)
self.pooler = None

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
self.pooler = None

Comment thread src/transformers/models/dinov2/modular_dinov2.py
Comment thread src/transformers/models/dinov2/modular_dinov2.py
Dinov2 backbone, to be used with frameworks like DETR and MaskFormer.
"""
)
class Dinov2Backbone(BackboneMixin, Dinov2PreTrainedModel):

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No similar backbone to use?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unfortunately, not really! (I think answered elsewhere but worth adding back)

@molbap

molbap commented Jun 12, 2026

Copy link
Copy Markdown
Collaborator Author

run-slow: depth_anything, dinov2, dinov2_with_registers, dinov3_convnext, dinov3_vit, eomt, eomt_dinov3, pixio, rf_detr, sapiens2, videomt

@github-actions

Copy link
Copy Markdown
Contributor

Workflow Run ⚙️

This comment contains run-slow, running the specified jobs:

models: ["models/depth_anything", "models/dinov2", "models/dinov2_with_registers", "models/dinov3_convnext", "models/dinov3_vit", "models/eomt", "models/eomt_dinov3", "models/pixio", "models/rf_detr", "models/sapiens2", "models/videomt"]
quantizations: []

@github-actions

Copy link
Copy Markdown
Contributor

CI Results

Workflow Run ⚙️

Commit Info

Context Commit Description
RUN 42daf802 workflow commit (merge commit)
PR 4ee1242a branch commit (from PR)
main f208766a base commit (on main)

✅ No failing test specific to this PR 🎉 👏 !

@github-actions

Copy link
Copy Markdown
Contributor

View the CircleCI Test Summary for this PR:

https://huggingface.co/spaces/transformers-community/circle-ci-viz?pr=46266&sha=b0ea71

@molbap
molbap requested a review from guarin June 26, 2026 16:08
@molbap

molbap commented Jun 26, 2026

Copy link
Copy Markdown
Collaborator Author

@guarin if you can have another look, I updated the swiglu-related reverse load test, I think it's an acceptable workaround. LMK!

@guarin guarin left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice refactor! Just some minor points/questions here and there, otherwise LGTM!

patch_size = self.patch_size if isinstance(self.patch_size, Iterable) else (self.patch_size, self.patch_size)
new_height = height // patch_size[0]
new_width = width // patch_size[1]

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be possible to already create self.patch_size as a tuple? Or does this break BC?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah I think it breaks BC here, in particular for rf_detr IIRC

Comment on lines +49 to +52
image_size = config.image_size
patch_size = config.patch_size
image_size = image_size if isinstance(image_size, Iterable) else (image_size, image_size)
patch_size = patch_size if isinstance(patch_size, Iterable) else (patch_size, patch_size)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Inline to self.image_size = config.image_size if isinstance(config.image_size, Iterable) else (config.image_size, config.image_size) to allow overwriting self.image_size and self.patch_size with modular?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is coming from VitPatchEmbeddings IIRC, so it's not modifiable directly

Comment on lines +261 to +262
down_proj = self.down_proj(self.act_fn(self.gate_proj(x)) * self.up_proj(x))
return down_proj

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
down_proj = self.down_proj(self.act_fn(self.gate_proj(x)) * self.up_proj(x))
return down_proj
return self.down_proj(self.act_fn(self.gate_proj(x)) * self.up_proj(x))

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

eh, that's llama-inherited 😬

Comment on lines 333 to +334
_supports_attention_backend = True
_can_compile_fullgraph = True

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🥳

```"""
kwargs["output_hidden_states"] = True # required to extract layers for the stages

kwargs["output_hidden_states"] = True # required to extract per-stage feature maps from hidden_states

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could this be replaced by a decorator?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AH indeed, I think #46422 (that I merged...) covers it. checking

Comment on lines +539 to +543
patch_size = (
self.config.patch_size
if isinstance(self.config.patch_size, Iterable)
else (self.config.patch_size, self.config.patch_size)
)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking at all these I wonder whether we don't want to convert to tuple in config __post_init__. Not sure if that works though if users set config.patch_size = 14 though.

@molbap molbap Jun 30, 2026

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if that works though if users set config.patch_size = 14 though.

Yes, I think it'll break unfortunately


class Dinov2WithRegistersEmbeddings(nn.Module):
"""
Construct the CLS token, mask token, register tokens, position and patch embeddings.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we inherit from Dinov2Embeddings?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we can, but it's a lot of overrides, so the reuse isn't bringing much IMO

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Imo we can inherit for the init? And highlight the diffs with small comments


# Validate output dimensions if not tracing
if not torch.jit.is_tracing():
if int(height) != patch_pos_embed.shape[-2] or int(width) != patch_pos_embed.shape[-1]:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a particular reason why we have this check? Because it essentially checks if nn.functional.interpolate resized the embeddings to the correct size which seems redundant.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

uh indeed that seems to be a dead check. The point of these is to guarantee static shapes/force interpolate during tracing, which is what it does on other models, but is not useful/wrongly placed here

@github-actions

Copy link
Copy Markdown
Contributor

[For maintainers] Suggested jobs to run (before merge)

run-slow: depth_anything, dinov2, dinov2_with_registers, dinov3_convnext, dinov3_vit, eomt, eomt_dinov3, pixio, rf_detr, sapiens2, videomt

@molbap

molbap commented Jun 30, 2026

Copy link
Copy Markdown
Collaborator Author

run-slow: depth_anything, dinov2, dinov2_with_registers, dinov3_convnext, dinov3_vit, eomt, eomt_dinov3, pixio, rf_detr, sapiens2, videomt

@github-actions

Copy link
Copy Markdown
Contributor

Workflow Run ⚙️

This comment contains run-slow, running the specified jobs:

models: ["models/depth_anything", "models/dinov2", "models/dinov2_with_registers", "models/dinov3_convnext", "models/dinov3_vit", "models/eomt", "models/eomt_dinov3", "models/pixio", "models/rf_detr", "models/sapiens2", "models/videomt"]
quantizations: []

@github-actions

Copy link
Copy Markdown
Contributor

CI recap

Dashboard: View test results in Grafana
Latest run: 28450557810:1
Result: failure | Jobs: 14 | Tests: 50,911 | Failures: 0 | Duration: 14h 37m

@github-actions

Copy link
Copy Markdown
Contributor

CI Results

Workflow Run ⚙️

Commit Info

Context Commit Description
RUN d28e35e5 workflow commit (merge commit)
PR a3aa0d0e branch commit (from PR)
main 181beb3b base commit (on main)

✅ No failing test specific to this PR 🎉 👏 !

@vasqu

vasqu commented Jul 1, 2026

Copy link
Copy Markdown
Collaborator

Let me know if I should make a last pass 🤗

@molbap

molbap commented Jul 1, 2026

Copy link
Copy Markdown
Collaborator Author

No rush, but glad to have a review if you have time! I iterated mostly on dinov2 but the rest seems OK as well @vasqu

@vasqu vasqu left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking super solid, I have a few smaller comments but nothing major. I think we are close to getting this merged 🫡

Comment on lines +51 to +61
def __init__(self, config: Dinov2Config) -> None:
nn.Module.__init__(self)
self.cls_token = nn.Parameter(torch.randn(1, 1, config.hidden_size))
self.mask_token = nn.Parameter(torch.zeros(1, config.hidden_size)) if config.use_mask_token else None
self.patch_embeddings = Dinov2PatchEmbeddings(config)
self.position_embeddings = nn.Parameter(
torch.randn(1, self.patch_embeddings.num_patches + 1, config.hidden_size)
)
self.dropout = nn.Dropout(config.hidden_dropout_prob)
self.patch_size = config.patch_size
self.config = config

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
def __init__(self, config: Dinov2Config) -> None:
nn.Module.__init__(self)
self.cls_token = nn.Parameter(torch.randn(1, 1, config.hidden_size))
self.mask_token = nn.Parameter(torch.zeros(1, config.hidden_size)) if config.use_mask_token else None
self.patch_embeddings = Dinov2PatchEmbeddings(config)
self.position_embeddings = nn.Parameter(
torch.randn(1, self.patch_embeddings.num_patches + 1, config.hidden_size)
)
self.dropout = nn.Dropout(config.hidden_dropout_prob)
self.patch_size = config.patch_size
self.config = config
def __init__(self, config: Dinov2Config) -> None:
super().__init__(self, config)
del num_patches
self.position_embeddings = nn.Parameter(
torch.randn(1, self.patch_embeddings.num_patches + 1, config.hidden_size)
)

can we not simplify a bit?

Comment on lines +83 to +96
patch_size = self.patch_size if isinstance(self.patch_size, Iterable) else (self.patch_size, self.patch_size)
new_height = height // patch_size[0]
new_width = width // patch_size[1]

sqrt_num_positions = torch_int(num_positions**0.5)
patch_pos_embed = patch_pos_embed.reshape(1, sqrt_num_positions, sqrt_num_positions, dim)
patch_pos_embed = patch_pos_embed.permute(0, 3, 1, 2)
target_dtype = patch_pos_embed.dtype
patch_pos_embed = nn.functional.interpolate(
patch_pos_embed.to(torch.float32),
size=(new_height, new_width),
mode="bicubic",
align_corners=False,
).to(dtype=target_dtype)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok so I see 2 key differences

  1. The patch size can be different for width and height
  2. Dtype casting around interpolation

Let's highlight those with small comments

Comment on lines +116 to +121
nn.Module.__init__(self)
in_features = out_features = config.hidden_size
hidden_features = int(config.hidden_size * config.mlp_ratio)
self.fc1 = nn.Linear(in_features, hidden_features, bias=True)
self.activation_fn = ACT2FN[config.hidden_act]
self.fc2 = nn.Linear(hidden_features, out_features, bias=True)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
nn.Module.__init__(self)
in_features = out_features = config.hidden_size
hidden_features = int(config.hidden_size * config.mlp_ratio)
self.fc1 = nn.Linear(in_features, hidden_features, bias=True)
self.activation_fn = ACT2FN[config.hidden_act]
self.fc2 = nn.Linear(hidden_features, out_features, bias=True)
super().__init__(self, config)
# Key difference: intermediate size is calculated based on ratio
self.fc1 = nn.Linear(config.hidden_size, int(config.hidden_size * config.mlp_ratio))
self.fc2 = nn.Linear(int(config.hidden_size * config.mlp_ratio), config.hidden_size)

Imo we can even fully copy based on adding a post init and intermediate size attr in the config

Comment on lines +126 to +132
nn.Module.__init__(self)
hidden_features = int(config.hidden_size * config.mlp_ratio)
hidden_features = (int(hidden_features * 2 / 3) + 7) // 8 * 8
self.gate_proj = nn.Linear(config.hidden_size, hidden_features, bias=True)
self.up_proj = nn.Linear(config.hidden_size, hidden_features, bias=True)
self.down_proj = nn.Linear(hidden_features, config.hidden_size, bias=True)
self.act_fn = nn.functional.silu

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here, ok but this will break with different intermediate sizes? maybe can be a conditional that calculates it based on whether swiglu is used or not

Is the activation only used in the mlp? Imo, we can also override the activation in post init when we use swiglu


def __init__(self, config: Dinov2Config) -> None:
super().__init__()
self.norm1 = nn.LayerNorm(config.hidden_size, eps=config.layer_norm_eps)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense, always annoying 😢

Comment on lines +791 to +795
hidden_state = embedding_output
hidden_states = (hidden_state,)
for layer in self.encoder.layer:
hidden_state = layer(hidden_state, **kwargs)
hidden_states = hidden_states + (hidden_state,)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm what happened here that we don't have an encoder here? That was more elegant no?

hidden_states=hidden_states,
attentions=output.attentions,
)
return BackboneOutput(feature_maps=tuple(feature_maps))

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still tuple cast needed?

super()._init_weights(module)
if hasattr(module, "segmentation_bias") and isinstance(module.segmentation_bias, nn.Parameter):
nn.init.constant_(module.segmentation_bias, 0.0)
init.constant_(module.segmentation_bias, 0.0)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh wow how did we miss this yikes

expected_slice = torch.tensor(
[[8.8223, 8.6483, 8.6216], [8.3332, 8.6047, 8.7545], [8.6547, 8.6885, 8.7472]],
).to(torch_device)
# Re-baselined after the dinov2 refactor adopted ViT's fp32-softmax `eager_attention_forward`.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm so it didnt have fp32 before in the softmax? Maybe we should revert on that and keep it as close as we can to the original. Pretty sure there is a eager attn with no fp32 softmax like bart

# Re-baselined after the dinov2 refactor adopted ViT's fp32-softmax `eager_attention_forward`.
slice_expectations = Expectations(
{
("cpu", None): [[8.8223, 8.6483, 8.6215], [8.3332, 8.6047, 8.7545], [8.6547, 8.6885, 8.7472]],

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since when do we run on cpu 👀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants