sassoftware
/

Image Feature Extraction
Safetensors
custom_code
barry-sas gheinrich commited on
Commit
db47765
·
0 Parent(s):

Duplicate from nvidia/C-RADIOv2-H

Browse files

Co-authored-by: Greg Heinrich <[email protected]>

.gitattributes ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ *.7z filter=lfs diff=lfs merge=lfs -text
2
+ *.arrow filter=lfs diff=lfs merge=lfs -text
3
+ *.bin filter=lfs diff=lfs merge=lfs -text
4
+ *.bz2 filter=lfs diff=lfs merge=lfs -text
5
+ *.ckpt filter=lfs diff=lfs merge=lfs -text
6
+ *.ftz filter=lfs diff=lfs merge=lfs -text
7
+ *.gz filter=lfs diff=lfs merge=lfs -text
8
+ *.h5 filter=lfs diff=lfs merge=lfs -text
9
+ *.joblib filter=lfs diff=lfs merge=lfs -text
10
+ *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
+ *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
+ *.model filter=lfs diff=lfs merge=lfs -text
13
+ *.msgpack filter=lfs diff=lfs merge=lfs -text
14
+ *.npy filter=lfs diff=lfs merge=lfs -text
15
+ *.npz filter=lfs diff=lfs merge=lfs -text
16
+ *.onnx filter=lfs diff=lfs merge=lfs -text
17
+ *.ot filter=lfs diff=lfs merge=lfs -text
18
+ *.parquet filter=lfs diff=lfs merge=lfs -text
19
+ *.pb filter=lfs diff=lfs merge=lfs -text
20
+ *.pickle filter=lfs diff=lfs merge=lfs -text
21
+ *.pkl filter=lfs diff=lfs merge=lfs -text
22
+ *.pt filter=lfs diff=lfs merge=lfs -text
23
+ *.pth filter=lfs diff=lfs merge=lfs -text
24
+ *.rar filter=lfs diff=lfs merge=lfs -text
25
+ *.safetensors filter=lfs diff=lfs merge=lfs -text
26
+ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
+ *.tar.* filter=lfs diff=lfs merge=lfs -text
28
+ *.tar filter=lfs diff=lfs merge=lfs -text
29
+ *.tflite filter=lfs diff=lfs merge=lfs -text
30
+ *.tgz filter=lfs diff=lfs merge=lfs -text
31
+ *.wasm filter=lfs diff=lfs merge=lfs -text
32
+ *.xz filter=lfs diff=lfs merge=lfs -text
33
+ *.zip filter=lfs diff=lfs merge=lfs -text
34
+ *.zst filter=lfs diff=lfs merge=lfs -text
35
+ *tfevents* filter=lfs diff=lfs merge=lfs -text
README.md ADDED
@@ -0,0 +1,229 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ license: other
3
+ license_name: nvidia-open-model-license
4
+ license_link: https://developer.download.nvidia.com/licenses/nvidia-open-model-license-agreement-june-2024.pdf
5
+ ---
6
+
7
+ # Model Overview
8
+
9
+ [[**Github**](https://github.com/NVlabs/RADIO)] [[**CVPR 2025**](https://arxiv.org/abs/2412.07679)] [[**CVPR 2024**](https://arxiv.org/abs/2312.06709)]
10
+
11
+ ## Description
12
+
13
+ This model performs visual feature extraction.
14
+ For instance, RADIO generates image embeddings that can be used by a downstream model to classify images.
15
+
16
+ C-RADIOv2 models are available in multiple sizes:
17
+ * Base (90M parameters).
18
+ * Large (320M parameters).
19
+ * Huge (653M parameters).
20
+ * Gigantic (1.1B parameters).
21
+
22
+ C-RADIOv2 was trained for 1M steps (400k more steps than v1), using inverse frequency sampling for data balancing, and [PHI Standardization](https://arxiv.org/abs/2410.01680) for teacher distribution balancing.
23
+
24
+ This model is ready for commercial/non-commercial use.
25
+
26
+ ### License/Terms of Use
27
+
28
+ GOVERNING TERMS: Use of this model is governed by the [NVIDIA Open Model License Agreement](https://developer.download.nvidia.com/licenses/nvidia-open-model-license-agreement-june-2024.pdf).
29
+
30
+ ## Deployment Geography
31
+
32
+ Global.
33
+
34
+ ## Use Case
35
+
36
+ The embeddings generated by this model are expected to be used by a downstream application.
37
+ For example:
38
+
39
+ * Image-level understanding (image classification, curation, etc.).
40
+ * Dense processing (semantic segmentation, depth estimation, etc.).
41
+ * Integration into a Vision-Language Model.
42
+
43
+ ## Release Date
44
+
45
+ Huggingface: 03/26/2025 via [RADIO Collection of Models](https://huggingface.co/collections/nvidia/radio-669f77f1dd6b153f007dd1c6).
46
+
47
+ ## References
48
+
49
+ * \[CVPR 2025\] [**RADIOv2.5: Improved Baselines for Agglomerative Vision Foundation Models**](https://arxiv.org/abs/2412.07679)
50
+ * \[CVPR 2024\] [**AM-RADIO: Agglomerative Vision Foundation Model - Reduce All Domains Into One**](https://arxiv.org/abs/2312.06709)
51
+
52
+ ## Model Architecture
53
+
54
+ **Architecture Type:** Neural Network <br>
55
+ **Network Architecture:** Vision Transformer <br>
56
+
57
+ ## Input
58
+
59
+ **Input Type(s):** Image <br>
60
+ **Input Format(s):** Red, Green, Blue (RGB) <br>
61
+ **Input Parameters:** Two Dimensional (2D) <br>
62
+ **Other Properties Related to Input:** Image resolutions up to 2048x2028 in increments of 16 pixels <br>
63
+
64
+ ## Output
65
+
66
+ **Output Type(s):** Embeddings <br>
67
+ **Output Format:** Tensor <br>
68
+ **Output Parameters:** 2D <br>
69
+ **Other Properties Related to Output:** Downstream model required to leverage image features <br>
70
+
71
+ ## Usage:
72
+
73
+ RADIO will return a tuple with two tensors.
74
+ The `summary` is similar to the `cls_token` in ViT and is meant to represent the general concept of the entire image.
75
+ It has shape `(B,C)` with `B` being the batch dimension, and `C` being some number of channels.
76
+ The `spatial_features` represent more localized content which should be suitable for dense tasks such as semantic segmentation, or for integration into an LLM.
77
+
78
+ ```python
79
+ import torch
80
+ from PIL import Image
81
+ from transformers import AutoModel, CLIPImageProcessor
82
+
83
+ hf_repo = "nvidia/C-RADIOv2-H"
84
+
85
+ image_processor = CLIPImageProcessor.from_pretrained(hf_repo)
86
+ model = AutoModel.from_pretrained(hf_repo, trust_remote_code=True)
87
+ model.eval().cuda()
88
+
89
+ image = Image.open('./assets/radio.png').convert('RGB')
90
+ pixel_values = image_processor(images=image, return_tensors='pt', do_resize=True).pixel_values
91
+ pixel_values = pixel_values.cuda()
92
+
93
+ summary, features = model(pixel_values)
94
+ ```
95
+
96
+ Spatial features have shape `(B,T,D)` with `T` being the flattened spatial tokens, and `D` being the channels for spatial features. Note that `C!=D` in general.
97
+ Converting to a spatial tensor format can be done using the downsampling size of the model, combined with the input tensor shape. For RADIO, the patch size is 16.
98
+
99
+ ```Python
100
+ from einops import rearrange
101
+ spatial_features = rearrange(spatial_features, 'b (h w) d -> b d h w', h=x.shape[-2] // patch_size, w=x.shape[-1] // patch_size)
102
+ ```
103
+
104
+ The resulting tensor will have shape `(B,D,H,W)`, as is typically seen with computer vision models.
105
+
106
+ ## Software Integration
107
+
108
+ **Runtime Engine(s):**
109
+ * TAO- 24.10 <br>
110
+
111
+ **Supported Hardware Microarchitecture Compatibility:** <br>
112
+ * NVIDIA Ampere <br>
113
+ * NVIDIA Blackwell <br>
114
+ * NVIDIA Jetson <br>
115
+ * NVIDIA Hopper <br>
116
+ * NVIDIA Lovelace <br>
117
+ * NVIDIA Pascal <br>
118
+ * NVIDIA Turing <br>
119
+ * NVIDIA Volta <br>
120
+
121
+ **[Preferred/Supported] Operating System(s):** <br>
122
+ * Linux
123
+ * Linux 4 Tegra
124
+ * QNX
125
+ * Windows
126
+
127
+ ## Model Version(s)
128
+
129
+ * C-RADIOv2-B (90M parameters).
130
+ * C-RADIOv2-L (320M parameters).
131
+ * C-RADIOv2-H (653M parameters).
132
+ * C-RADIOv2-G (1.8B parameters).
133
+
134
+ **Links:**
135
+
136
+ * https://huggingface.co/nvidia/C-RADIOv2-B
137
+ * https://huggingface.co/nvidia/C-RADIOv2-L
138
+ * https://huggingface.co/nvidia/C-RADIOv2-H
139
+ * https://huggingface.co/nvidia/C-RADIOv2-g
140
+
141
+ # Training and Evaluation Datasets
142
+
143
+ ## Training Dataset
144
+
145
+ NV-CC-Img-Text-Dataset <br>
146
+
147
+ ### Data Collection Method by dataset
148
+
149
+ * Automated <br>
150
+
151
+ ### Labeling Method by dataset
152
+
153
+ * Not Applicable (no labels are needed)
154
+
155
+ ### Properties
156
+
157
+ * 700 Million Images <br>
158
+
159
+ ## Evaluation Dataset
160
+
161
+ **Link:** [ImageNet](https://www.image-net.org/) <br>
162
+
163
+ ### Data Collection Method by dataset
164
+
165
+ * Automated <br>
166
+
167
+ ### Labeling Method by dataset
168
+
169
+ * Human <br>
170
+
171
+ **Properties:** This dataset spans 1000 object classes and contains 1,281,167 training images, 50,000 validation images and 100,000 test images.<br>
172
+
173
+ ## Inference
174
+
175
+ **Engine:** PyTorch <br>
176
+ **Test Hardware:** A100 <br>
177
+
178
+ ## Ethical Considerations
179
+
180
+ NVIDIA believes Trustworthy AI is a shared responsibility and we have established policies and practices to enable development for a wide array of AI applications. When downloaded or used in accordance with our terms of service, developers should work with their internal model team to ensure this model meets requirements for the relevant industry and use case and addresses unforeseen product misuse.
181
+
182
+ For more detailed information on ethical considerations for this model, please see the Model Card++ Explainability, Bias, Safety & Security, and Privacy Subcards below.
183
+
184
+ Please report security vulnerabilities or NVIDIA AI Concerns [here](https://www.nvidia.com/en-us/support/submit-security-vulnerability/).
185
+
186
+ ### Bias
187
+
188
+ Field | Response
189
+ :---------------------------------------------------------------------------------------------------|:---------------
190
+ Participation considerations from adversely impacted groups [protected classes](https://www.senate.ca.gov/content/protected-classes) in model design and testing: | None
191
+ Measures taken to mitigate against unwanted bias: | None
192
+
193
+
194
+ ### Explainability
195
+
196
+ Field | Response
197
+ :------------------------------------------------------------------------------------------------------|:---------------------------------------------------------------------------------
198
+ Intended Application & Domain: | Visual Feature Extraction
199
+ Model Type: | Vision Transformer
200
+ Intended Users: | Developers of downstream vision applications
201
+ Output: | Image embeddings
202
+ Describe how the model works: | The model takes an image as input, processes the image through multiple transformer blocks, and outputs summary and patch embeddings.
203
+ Name the adversely impacted groups this has been tested to deliver comparable outcomes regardless of: | Not Applicable
204
+ Technical Limitations: | This model generates image embeddings that can be used by a downstream model to, for example, classify images. The downstream model must be trained to leverage the visual embeddings.
205
+ Verified to have met prescribed NVIDIA quality standards: | Yes
206
+ Performance Metrics: | Image classification accuracy, semantic segmentation mean-over-intersection.
207
+ Potential Known Risks: | This model is only tested on input resolutions ranging from 256 to 2048, in increments of 16 pixels. Additionally, the generated embeddings might fail to disambiguate differences that appear evident to humans (e.g. two images showing different breeds of dogs might in fact produce very similar embeddings). Domain-specific evaluation is required for the target application.
208
+ Licensing: | [NVIDIA Open Model License](https://developer.download.nvidia.com/licenses/nvidia-open-model-license-agreement-june-2024.pdf)
209
+
210
+
211
+ ### Privacy
212
+
213
+ Field | Response
214
+ :----------------------------------------------------------------------------------------------------------------------------------|:-----------------------------------------------
215
+ Generatable or reverse engineerable personal data? | None
216
+ Personal data used to create this model? | None
217
+ How often is dataset reviewed? | Before Every Release
218
+ Is there provenance for all datasets used in training? | Yes
219
+ Does data labeling (annotation, metadata) comply with privacy laws? | Yes
220
+ Is data compliant with data subject requests for data correction or removal, if such a request was made? | Yes
221
+
222
+ ### Safety
223
+
224
+ Field | Response
225
+ :---------------------------------------------------|:----------------------------------
226
+ Model Application(s): | Generation of visual embeddings
227
+ Describe the life critical impact (if present). | Not Applicable
228
+ Use Case Restrictions: | Abide by NVIDIA Open Model License Agreement
229
+ Model and dataset restrictions: | The Principle of least privilege (PoLP) is applied limiting access for dataset generation and model development. Restrictions enforce dataset access during training, and dataset license constraints adhered to.
adaptor_base.py ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) 2024, NVIDIA CORPORATION. All rights reserved.
2
+ #
3
+ # NVIDIA CORPORATION and its licensors retain all intellectual property
4
+ # and proprietary rights in and to this software, related documentation
5
+ # and any modifications thereto. Any use, reproduction, disclosure or
6
+ # distribution of this software and related documentation without an express
7
+ # license agreement from NVIDIA CORPORATION is strictly prohibited.
8
+ from argparse import Namespace
9
+ from typing import NamedTuple, Optional
10
+
11
+ import torch
12
+ from torch import nn
13
+ import torch.nn.functional as F
14
+
15
+
16
+ class AdaptorInput(NamedTuple):
17
+ images: torch.Tensor
18
+ summary: torch.Tensor
19
+ features: torch.Tensor
20
+ feature_fmt: str
21
+ patch_size: int
22
+
23
+
24
+ class RadioOutput(NamedTuple):
25
+ summary: torch.Tensor
26
+ features: torch.Tensor
27
+
28
+ def to(self, *args, **kwargs):
29
+ return RadioOutput(
30
+ self.summary.to(*args, **kwargs) if self.summary is not None else None,
31
+ self.features.to(*args, **kwargs) if self.features is not None else None,
32
+ )
33
+
34
+
35
+ class AdaptorBase(nn.Module):
36
+ def forward(self, input: AdaptorInput) -> RadioOutput:
37
+ raise NotImplementedError("Subclasses must implement this!")
adaptor_generic.py ADDED
@@ -0,0 +1,69 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) 2024, NVIDIA CORPORATION. All rights reserved.
2
+ #
3
+ # NVIDIA CORPORATION and its licensors retain all intellectual property
4
+ # and proprietary rights in and to this software, related documentation
5
+ # and any modifications thereto. Any use, reproduction, disclosure or
6
+ # distribution of this software and related documentation without an express
7
+ # license agreement from NVIDIA CORPORATION is strictly prohibited.
8
+ from argparse import Namespace
9
+
10
+ import torch
11
+ from torch import nn
12
+ import torch.nn.functional as F
13
+
14
+ from .adaptor_base import AdaptorBase, AdaptorInput, RadioOutput
15
+ from .adaptor_mlp import create_mlp_from_state, create_mlp_from_config
16
+
17
+
18
+ class GenericAdaptor(AdaptorBase):
19
+ def __init__(self, main_config: Namespace, adaptor_config, state, mlp_config=None):
20
+ super().__init__()
21
+
22
+ extra_args = dict()
23
+ ups = None
24
+ ups_rank = None
25
+ if adaptor_config is not None:
26
+ ups = adaptor_config.get('fd_upsample_factor', None)
27
+ ups_rank = adaptor_config.get('fd_upsample_rank', None)
28
+ elif mlp_config is not None:
29
+ ups = mlp_config["feature"].get('upsample_factor', None)
30
+ ups_rank = mlp_config["feature"].get('upsample_rank', None)
31
+ if ups is not None:
32
+ extra_args['upsample_factor'] = ups
33
+ extra_args['upsample_rank'] = ups_rank
34
+
35
+ if state is not None:
36
+ spectral_heads = getattr(main_config, 'spectral_heads', False)
37
+ self.head_mlp = create_mlp_from_state(main_config.mlp_version, state, 'summary.', spectral_weights=spectral_heads)
38
+ self.feat_mlp = create_mlp_from_state(main_config.mlp_version, state, 'feature.', spectral_weights=spectral_heads, **extra_args)
39
+ else:
40
+ assert mlp_config is not None, "Config must not be None if state is None"
41
+
42
+ self.head_mlp = create_mlp_from_config(
43
+ main_config.mlp_version,
44
+ mlp_config["summary"]["input_dim"],
45
+ mlp_config["summary"]["hidden_dim"],
46
+ mlp_config["summary"]["output_dim"],
47
+ mlp_config["summary"]["num_inner"],
48
+ )
49
+ self.feat_mlp = create_mlp_from_config(
50
+ main_config.mlp_version,
51
+ mlp_config["feature"]["input_dim"],
52
+ mlp_config["feature"]["hidden_dim"],
53
+ mlp_config["feature"]["output_dim"],
54
+ mlp_config["feature"]["num_inner"],
55
+ **extra_args
56
+ )
57
+
58
+ def forward(self, input: AdaptorInput) -> RadioOutput:
59
+ # Convert input'd type to the type of the first parameter of the adaptor.
60
+ first_param = next(self.parameters())
61
+ summary = self.head_mlp(input.summary.to(dtype=first_param.dtype)).to(dtype=input.summary.dtype)
62
+ feat = self.feat_mlp(input.features.to(dtype=first_param.dtype), images=input.images, patch_size=input.patch_size).to(dtype=input.features.dtype)
63
+
64
+ if input.feature_fmt == 'NCHW':
65
+ feat = (feat.reshape(feat.shape[0], input.images.shape[-2] // input.patch_size * self.feat_mlp.upsample_factor, input.images.shape[-1] // input.patch_size * self.feat_mlp.upsample_factor, feat.shape[2])
66
+ .permute(0, 3, 1, 2)
67
+ )
68
+
69
+ return RadioOutput(summary, feat)
adaptor_mlp.py ADDED
@@ -0,0 +1,174 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) 2024, NVIDIA CORPORATION. All rights reserved.
2
+ #
3
+ # NVIDIA CORPORATION and its licensors retain all intellectual property
4
+ # and proprietary rights in and to this software, related documentation
5
+ # and any modifications thereto. Any use, reproduction, disclosure or
6
+ # distribution of this software and related documentation without an express
7
+ # license agreement from NVIDIA CORPORATION is strictly prohibited.
8
+ import math
9
+ from typing import Dict, Optional
10
+
11
+ import torch
12
+ from torch import nn
13
+
14
+ from einops import rearrange
15
+ from timm.models.vision_transformer import Block
16
+
17
+ from .enable_spectral_reparam import disable_spectral_reparam, enable_spectral_reparam
18
+
19
+
20
+ class MLP(nn.Module):
21
+ def __init__(self, input_size: int, hidden_size: int, output_size: int,
22
+ num_inner: int = 0, device: torch.device = None, **kwargs):
23
+ super(MLP, self).__init__()
24
+ self.fc1 = nn.Linear(input_size, hidden_size, device=device)
25
+ self.norm = nn.LayerNorm(hidden_size, device=device)
26
+ self.relu = nn.ReLU()
27
+
28
+ inner = []
29
+ for _ in range(num_inner):
30
+ inner.extend([
31
+ nn.Linear(hidden_size, hidden_size, device=device),
32
+ nn.LayerNorm(hidden_size, device=device),
33
+ nn.ReLU(),
34
+ ])
35
+ if inner:
36
+ self.inner = nn.Sequential(*inner)
37
+ else:
38
+ self.inner = nn.Identity()
39
+
40
+ self.fc2 = nn.Linear(hidden_size, output_size, device=device)
41
+
42
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
43
+ x = self.fc1(x)
44
+ x = self.norm(x)
45
+ x = self.relu(x)
46
+ x = self.inner(x)
47
+ x = self.fc2(x)
48
+ return x
49
+
50
+
51
+ class MLP2(nn.Module):
52
+ def __init__(self, input_size: int, hidden_size: int, output_size: int,
53
+ num_inner: int = 0,
54
+ pre_norm: bool = False, device: torch.device = None,
55
+ upsample_factor: int = 1,
56
+ upsample_rank: int = None,
57
+ from_config: bool = False,
58
+ **kwargs):
59
+ super().__init__()
60
+
61
+ self.pre_norm = nn.Sequential(
62
+ nn.LayerNorm(input_size),
63
+ nn.GELU(),
64
+ ) if pre_norm else nn.Identity()
65
+
66
+ self.upsample_factor = upsample_factor
67
+ sq_ups = upsample_factor ** 2
68
+
69
+ self._real_output_dim = output_size // sq_ups
70
+
71
+ # hidden_size *= upsample_factor
72
+ # output_size *= (upsample_factor ** 2)
73
+
74
+ self.fc1 = nn.Linear(input_size, hidden_size, device=device)
75
+
76
+ blocks = []
77
+ for _ in range(num_inner):
78
+ blocks.append(nn.Sequential(
79
+ nn.LayerNorm(hidden_size, device=device),
80
+ nn.GELU(),
81
+ nn.Linear(hidden_size, hidden_size, device=device),
82
+ ))
83
+ self.blocks = nn.ModuleList(blocks)
84
+
85
+ self.final = nn.Sequential(
86
+ nn.LayerNorm(hidden_size, device=device),
87
+ nn.GELU(),
88
+ nn.Linear(hidden_size, output_size, device=device),
89
+ )
90
+
91
+ def forward(self, x: torch.Tensor, images: Optional[torch.Tensor] = None, patch_size: Optional[int] = None) -> torch.Tensor:
92
+ x = self.pre_norm(x)
93
+ x = self.fc1(x)
94
+ for block in self.blocks:
95
+ x = x + block(x)
96
+ x = self.final(x)
97
+
98
+ if self.upsample_factor > 1:
99
+ if images is None:
100
+ raise ValueError(f'`images` cannot be `None` when the head\'s `upsample_factor > 1`!')
101
+ if patch_size is None:
102
+ raise ValueError(f'`patch_size` cannot be `None` when the head\'s `upsample_factor > 1`!')
103
+ h, w = tuple(d // patch_size for d in images.shape[-2:])
104
+ x = rearrange(x, 'b (h w) (u1 u2 c) -> b (h u1 w u2) c',
105
+ h=h, w=w, u1=self.upsample_factor, u2=self.upsample_factor,
106
+ c=self._real_output_dim)
107
+
108
+ return x
109
+
110
+
111
+ MLP_FACTORY = {
112
+ 'v1': MLP,
113
+ 'v2': MLP2,
114
+ }
115
+
116
+
117
+ def strip_prefix(state: Dict[str, torch.Tensor], prefix: str):
118
+ state = {
119
+ k[len(prefix):]: v
120
+ for k, v in state.items()
121
+ if k.startswith(prefix)
122
+ }
123
+ return state
124
+
125
+
126
+ def get_mlp_info_from_state(version: str, state: Dict[str, torch.Tensor], prefix: str = '', spectral_weights: bool = False):
127
+ state = strip_prefix(state, prefix)
128
+
129
+ weight_suffix = 'weight' if not spectral_weights else 'parametrizations.weight.original'
130
+
131
+ if version == 'v1':
132
+ hidden_dim, input_dim = state[f'fc1.{weight_suffix}'].shape
133
+ output_dim = state[f'fc2.{weight_suffix}'].shape[0]
134
+
135
+ for num_inner in range(1000):
136
+ k = f'inner.{num_inner}.0.weight'
137
+ if k not in state:
138
+ break
139
+ elif version == 'v2':
140
+ hidden_dim, input_dim = state[f'fc1.{weight_suffix}'].shape
141
+ output_dim = state[f'final.2.{weight_suffix}'].shape[0]
142
+
143
+ for num_inner in range(1000):
144
+ k = f'blocks.{num_inner}.0.weight'
145
+ if k not in state:
146
+ break
147
+ else:
148
+ raise ValueError(f'Unsupported MLP version: {version}')
149
+
150
+ return input_dim, hidden_dim, output_dim, num_inner
151
+
152
+
153
+ def create_mlp_from_config(version: str, input_dim: int, hidden_dim: int, output_dim: int, num_inner: int, **kwargs):
154
+ ret: nn.Module = MLP_FACTORY[version](input_dim, hidden_dim, output_dim, num_inner, from_config=True, **kwargs)
155
+
156
+ return ret
157
+
158
+
159
+ def create_mlp_from_state(version: str, state: Dict[str, torch.Tensor], prefix: str = '', spectral_weights: bool = False, **kwargs):
160
+ state = strip_prefix(state, prefix)
161
+
162
+ input_dim, hidden_dim, output_dim, num_inner = get_mlp_info_from_state(version, state, spectral_weights=spectral_weights)
163
+
164
+ ret: nn.Module = create_mlp_from_config(version, input_dim, hidden_dim, output_dim, num_inner, **kwargs)
165
+
166
+ if spectral_weights:
167
+ enable_spectral_reparam(ret, init_norm_to_current=False, state_dict_guidance=state)
168
+
169
+ ret.load_state_dict(state)
170
+
171
+ if spectral_weights:
172
+ disable_spectral_reparam(ret)
173
+
174
+ return ret
adaptor_registry.py ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) 2024, NVIDIA CORPORATION. All rights reserved.
2
+ #
3
+ # NVIDIA CORPORATION and its licensors retain all intellectual property
4
+ # and proprietary rights in and to this software, related documentation
5
+ # and any modifications thereto. Any use, reproduction, disclosure or
6
+ # distribution of this software and related documentation without an express
7
+ # license agreement from NVIDIA CORPORATION is strictly prohibited.
8
+ from argparse import Namespace
9
+ from typing import Dict, Any
10
+
11
+ import torch
12
+
13
+ from .adaptor_generic import GenericAdaptor, AdaptorBase
14
+
15
+ dict_t = Dict[str, Any]
16
+ state_t = Dict[str, torch.Tensor]
17
+
18
+
19
+ class AdaptorRegistry:
20
+ def __init__(self):
21
+ self._registry = {}
22
+
23
+ def register_adaptor(self, name):
24
+ def decorator(factory_function):
25
+ if name in self._registry:
26
+ raise ValueError(f"Model '{name}' already registered")
27
+ self._registry[name] = factory_function
28
+ return factory_function
29
+ return decorator
30
+
31
+ def create_adaptor(self, name, main_config: Namespace, adaptor_config: dict_t, state: state_t) -> AdaptorBase:
32
+ if name not in self._registry:
33
+ return GenericAdaptor(main_config, adaptor_config, state)
34
+ return self._registry[name](main_config, adaptor_config, state)
35
+
36
+ # Creating an instance of the registry
37
+ adaptor_registry = AdaptorRegistry()
cls_token.py ADDED
@@ -0,0 +1,59 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) 2023-2024, NVIDIA CORPORATION. All rights reserved.
2
+ #
3
+ # NVIDIA CORPORATION and its licensors retain all intellectual property
4
+ # and proprietary rights in and to this software, related documentation
5
+ # and any modifications thereto. Any use, reproduction, disclosure or
6
+ # distribution of this software and related documentation without an express
7
+ # license agreement from NVIDIA CORPORATION is strictly prohibited.
8
+ from typing import Optional
9
+
10
+ import torch
11
+ from torch import nn
12
+
13
+
14
+ class ClsToken(nn.Module):
15
+ def __init__(self, ndim: int,
16
+ num_tokens: int = 1,
17
+ enabled: bool = True,
18
+ register_multiple: Optional[int] = None,
19
+ num_registers: Optional[int] = None,
20
+ ):
21
+ super().__init__()
22
+
23
+ self.ndim = ndim
24
+ self.enabled = enabled
25
+ self.num_registers = 0
26
+ self.num_tokens = num_tokens
27
+ if enabled:
28
+ if num_registers:
29
+ self.num_registers = num_registers
30
+ elif register_multiple:
31
+ self.num_registers = register_multiple - (num_tokens % register_multiple)
32
+
33
+ scale = ndim ** -0.5
34
+ self.token = nn.Parameter(torch.randn(num_tokens + self.num_registers, ndim) * scale)
35
+ else:
36
+ self.token = None
37
+
38
+ self.num_patches = self.num_tokens + self.num_registers
39
+
40
+ def disable(self):
41
+ self.token = None
42
+ self.enabled = False
43
+
44
+ def forward(self, x: torch.Tensor):
45
+ if self.token is None:
46
+ return x
47
+
48
+ token = self.token.unsqueeze(0).expand(x.shape[0], -1, -1)
49
+ x = torch.cat([
50
+ token,
51
+ x,
52
+ ], dim=1)
53
+
54
+ return x
55
+
56
+ def no_weight_decay(self):
57
+ return [
58
+ 'token',
59
+ ]
common.py ADDED
@@ -0,0 +1,108 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) 2024, NVIDIA CORPORATION. All rights reserved.
2
+ #
3
+ # NVIDIA CORPORATION and its licensors retain all intellectual property
4
+ # and proprietary rights in and to this software, related documentation
5
+ # and any modifications thereto. Any use, reproduction, disclosure or
6
+ # distribution of this software and related documentation without an express
7
+ # license agreement from NVIDIA CORPORATION is strictly prohibited.
8
+
9
+ from dataclasses import dataclass
10
+ from typing import Optional
11
+
12
+ from .radio_model import Resolution
13
+
14
+
15
+ @dataclass
16
+ class RadioResource:
17
+ url: str
18
+ patch_size: int
19
+ max_resolution: int
20
+ preferred_resolution: Resolution
21
+ vitdet_num_windowed: Optional[int] = None
22
+ vitdet_num_global: Optional[int] = None
23
+
24
+
25
+ RESOURCE_MAP = {
26
+ # RADIOv2.5
27
+ "radio_v2.5-b": RadioResource(
28
+ "https://huggingface.co/nvidia/RADIO/resolve/main/radio-v2.5-b_half.pth.tar?download=true",
29
+ patch_size=16,
30
+ max_resolution=2048,
31
+ preferred_resolution=(768, 768),
32
+ vitdet_num_global=4,
33
+ ),
34
+ "radio_v2.5-l": RadioResource(
35
+ "https://huggingface.co/nvidia/RADIO/resolve/main/radio-v2.5-l_half.pth.tar?download=true",
36
+ patch_size=16,
37
+ max_resolution=2048,
38
+ preferred_resolution=(768, 768),
39
+ vitdet_num_global=4,
40
+ ),
41
+ "radio_v2.5-h": RadioResource(
42
+ "https://huggingface.co/nvidia/RADIO/resolve/main/radio_v2.5-h.pth.tar?download=true",
43
+ patch_size=16,
44
+ max_resolution=2048,
45
+ preferred_resolution=(768, 768),
46
+ vitdet_num_global=4,
47
+ ),
48
+ "radio_v2.5-h-norm": RadioResource(
49
+ "https://huggingface.co/nvidia/RADIO/resolve/main/radio_v2.5-h-norm.pth.tar?download=true",
50
+ patch_size=16,
51
+ max_resolution=2048,
52
+ preferred_resolution=(768, 768),
53
+ vitdet_num_global=4,
54
+ ),
55
+ "radio_v2.5-g": RadioResource(
56
+ "https://huggingface.co/nvidia/RADIO/resolve/main/radio_v2.5-g.pth.tar?download=true",
57
+ patch_size=14,
58
+ max_resolution=1792,
59
+ preferred_resolution=(896, 896),
60
+ vitdet_num_global=8,
61
+ ),
62
+ # RADIO
63
+ "radio_v2.1": RadioResource(
64
+ "https://huggingface.co/nvidia/RADIO/resolve/main/radio_v2.1_bf16.pth.tar?download=true",
65
+ patch_size=16,
66
+ max_resolution=2048,
67
+ preferred_resolution=Resolution(432, 432),
68
+ vitdet_num_windowed=5,
69
+ ),
70
+ "radio_v2": RadioResource(
71
+ "https://huggingface.co/nvidia/RADIO/resolve/main/radio_v2.pth.tar?download=true",
72
+ patch_size=16,
73
+ max_resolution=2048,
74
+ preferred_resolution=Resolution(432, 432),
75
+ vitdet_num_windowed=5,
76
+ ),
77
+ "radio_v1": RadioResource(
78
+ "https://huggingface.co/nvidia/RADIO/resolve/main/radio_v1.pth.tar?download=true",
79
+ patch_size=14,
80
+ max_resolution=1050,
81
+ preferred_resolution=Resolution(378, 378),
82
+ ),
83
+ # E-RADIO
84
+ "e-radio_v2": RadioResource(
85
+ "https://huggingface.co/nvidia/RADIO/resolve/main/eradio_v2.pth.tar?download=true",
86
+ patch_size=16,
87
+ max_resolution=2048,
88
+ preferred_resolution=Resolution(512, 512),
89
+ ),
90
+ # C-RADIO
91
+ "c-radio_v2.5-g": RadioResource(
92
+ "https://huggingface.co/nvidia/C-RADIOv2-g/resolve/main/c-radio_v2-g_half.pth.tar",
93
+ patch_size=16,
94
+ max_resolution=2048,
95
+ preferred_resolution=(768, 768),
96
+ vitdet_num_global=8,
97
+ ),
98
+ "c-radio_v3-l": RadioResource(
99
+ # NOTE: Currently, this model cannot be loaded via TorchHub. Instead, use the transformers API at https://huggingface.co/nvidia/C-RADIOv3-L
100
+ # and accept the license terms.
101
+ "https://huggingface.co/nvidia/C-RADIOv3-L/resolve/main/c-radio-v3_l_half.pth.tar?download=true",
102
+ patch_size=16,
103
+ max_resolution=2048,
104
+ preferred_resolution=Resolution(512, 512),
105
+ ),
106
+ }
107
+
108
+ DEFAULT_VERSION = "radio_v2.5-h"
config.json ADDED
@@ -0,0 +1,228 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "adaptor_configs": {},
3
+ "adaptor_names": null,
4
+ "architectures": [
5
+ "RADIOModel"
6
+ ],
7
+ "args": {
8
+ "aa": null,
9
+ "amp": true,
10
+ "amp_dtype": "bfloat16",
11
+ "amp_impl": "native",
12
+ "aug_repeats": 0,
13
+ "aug_splits": 0,
14
+ "bn_eps": null,
15
+ "bn_momentum": null,
16
+ "cache_dir": null,
17
+ "channels_last": false,
18
+ "checkpoint_hist": 10,
19
+ "chk_keep_forever": 100,
20
+ "class_map": "",
21
+ "clip_grad": null,
22
+ "clip_mode": "norm",
23
+ "cls_token_per_teacher": true,
24
+ "coco_annotations_file": "/datasets/coco2017-adlsa/annotations/captions_val2017.json",
25
+ "coco_image_dir": "/datasets/coco2017-adlsa/val2017",
26
+ "color_jitter": 0.4,
27
+ "cooldown_epochs": 0,
28
+ "cpe_max_size": 2048,
29
+ "crd_loss": false,
30
+ "crd_loss_weight": 0.8,
31
+ "crop_pct": null,
32
+ "cutmix": 0.0,
33
+ "cutmix_minmax": null,
34
+ "dataset_download": false,
35
+ "debug_full_knn": false,
36
+ "decay_epochs": 90,
37
+ "decay_milestones": [
38
+ 90,
39
+ 180,
40
+ 270
41
+ ],
42
+ "decay_rate": 0.1,
43
+ "depchain": true,
44
+ "dist_bn": "reduce",
45
+ "dist_norm_weight": 0.0,
46
+ "distributed": true,
47
+ "drop": 0.0,
48
+ "drop_block": null,
49
+ "drop_connect": null,
50
+ "drop_path": null,
51
+ "dtype": "float32",
52
+ "epoch_repeats": 0.0,
53
+ "eval": false,
54
+ "eval_metric": "knn_top1",
55
+ "eval_teacher": false,
56
+ "eval_teacher_only": false,
57
+ "eval_throughput": false,
58
+ "fast_norm": false,
59
+ "fd_loss_fn": "MSE",
60
+ "feature_normalization": "SHIP_NORM",
61
+ "feature_summarizer": "cls_token",
62
+ "feature_upscale_factor": null,
63
+ "force_new_wandb_id": false,
64
+ "force_spectral_reparam": true,
65
+ "freeze_bn": false,
66
+ "fsdp": false,
67
+ "fuser": "",
68
+ "gp": null,
69
+ "grad_accum_steps": 1,
70
+ "grad_checkpointing": false,
71
+ "head_init_bias": null,
72
+ "head_init_scale": null,
73
+ "head_warmup": 5,
74
+ "head_weight_decay": 0.001,
75
+ "hflip": 0.5,
76
+ "img_size": null,
77
+ "in_chans": null,
78
+ "initial_checkpoint": null,
79
+ "input_size": null,
80
+ "interpolation": "",
81
+ "layer_decay": null,
82
+ "local_rank": 0,
83
+ "log_interval": 50,
84
+ "log_mlflow": false,
85
+ "log_wandb": true,
86
+ "loss_auto_balance": false,
87
+ "lr_base": 0.1,
88
+ "lr_base_scale": "",
89
+ "lr_base_size": 256,
90
+ "lr_cycle_decay": 0.5,
91
+ "lr_cycle_limit": 1,
92
+ "lr_cycle_mul": 1.0,
93
+ "lr_k_decay": 1.0,
94
+ "lr_noise": null,
95
+ "lr_noise_pct": 0.67,
96
+ "lr_noise_std": 1.0,
97
+ "mean": null,
98
+ "mesa": false,
99
+ "min_lr": 0,
100
+ "mixup": 0.0,
101
+ "mixup_mode": "batch",
102
+ "mixup_off_epoch": 0,
103
+ "mixup_prob": 1.0,
104
+ "mixup_switch_prob": 0.5,
105
+ "mlp_hidden_size": 1520,
106
+ "mlp_num_inner": 3,
107
+ "mlp_version": "v2",
108
+ "model": "vit_huge_patch16_224",
109
+ "model_kwargs": {},
110
+ "model_norm": false,
111
+ "momentum": 0.9,
112
+ "no_aug": false,
113
+ "no_ddp_bb": true,
114
+ "no_prefetcher": false,
115
+ "no_resume_opt": false,
116
+ "num_classes": null,
117
+ "opt_betas": null,
118
+ "opt_eps": null,
119
+ "patience_epochs": 10,
120
+ "pin_mem": false,
121
+ "prefetcher": true,
122
+ "pretrained": false,
123
+ "rank": 0,
124
+ "ratio": [
125
+ 0.75,
126
+ 1.3333333333333333
127
+ ],
128
+ "recount": 1,
129
+ "recovery_interval": 0,
130
+ "register_multiple": 8,
131
+ "remode": "pixel",
132
+ "reprob": 0.0,
133
+ "reset_loss_state": false,
134
+ "resplit": false,
135
+ "save_images": false,
136
+ "scale": [
137
+ 0.5,
138
+ 1.0
139
+ ],
140
+ "sched": "cosine",
141
+ "seed": 42,
142
+ "smoothing": 0.1,
143
+ "spectral_heads": false,
144
+ "spectral_reparam": false,
145
+ "split_bn": false,
146
+ "start_epoch": null,
147
+ "std": null,
148
+ "stream_teachers": true,
149
+ "sync_bn": false,
150
+ "synchronize_step": false,
151
+ "teachers": [
152
+ {
153
+ "fd_normalize": false,
154
+ "feature_distillation": true,
155
+ "input_size": 378,
156
+ "model": "ViT-H-14-378-quickgelu",
157
+ "name": "clip",
158
+ "pretrained": "dfn5b",
159
+ "type": "open_clip",
160
+ "use_summary": true
161
+ },
162
+ {
163
+ "fd_normalize": false,
164
+ "feature_distillation": true,
165
+ "input_size": 378,
166
+ "model": "ViT-SO400M-14-SigLIP-384",
167
+ "name": "siglip",
168
+ "pretrained": "webli",
169
+ "type": "open_clip",
170
+ "use_summary": true
171
+ },
172
+ {
173
+ "fd_normalize": false,
174
+ "feature_distillation": true,
175
+ "input_size": 378,
176
+ "model": "dinov2_vitg14_reg",
177
+ "name": "dino_v2",
178
+ "type": "dino_v2",
179
+ "use_summary": true
180
+ },
181
+ {
182
+ "fd_normalize": false,
183
+ "feature_distillation": true,
184
+ "input_size": 1024,
185
+ "model": "vit-h",
186
+ "name": "sam",
187
+ "type": "sam",
188
+ "use_summary": false
189
+ }
190
+ ],
191
+ "torchcompile": null,
192
+ "torchscript": false,
193
+ "train_interpolation": "random",
194
+ "train_split": "train",
195
+ "tta": 0,
196
+ "use_coco": false,
197
+ "use_multi_epochs_loader": false,
198
+ "val_ema_only": false,
199
+ "val_split": "val",
200
+ "vflip": 0.0,
201
+ "vitdet_version": 1,
202
+ "wandb_entity": "",
203
+ "wandb_job_type": "",
204
+ "wandb_name": "",
205
+ "wandb_project": "",
206
+ "warmup_lr": 1e-05,
207
+ "warmup_prefix": false,
208
+ "worker_seeding": "all",
209
+ "workers": 8,
210
+ "world_size": 256
211
+ },
212
+ "auto_map": {
213
+ "AutoConfig": "hf_model.RADIOConfig",
214
+ "AutoModel": "hf_model.RADIOModel"
215
+ },
216
+ "feature_normalizer_config": null,
217
+ "inter_feature_normalizer_config": null,
218
+ "max_resolution": 2048,
219
+ "patch_size": 16,
220
+ "preferred_resolution": [
221
+ 768,
222
+ 768
223
+ ],
224
+ "torch_dtype": "float32",
225
+ "transformers_version": "4.47.0.dev0",
226
+ "version": "radio_v2.5-h",
227
+ "vitdet_window_size": null
228
+ }
dinov2_arch.py ADDED
@@ -0,0 +1,1016 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) Meta Platforms, Inc. and affiliates.
2
+ #
3
+ # This source code is licensed under the Apache License, Version 2.0
4
+ # found in the LICENSE file in the root directory of this source tree.
5
+
6
+ # References:
7
+ # https://github.com/facebookresearch/dino/blob/master/vision_transformer.py
8
+ # https://github.com/rwightman/pytorch-image-models/tree/master/timm/models/vision_transformer.py
9
+
10
+ # Nvidia
11
+ # NOTE: We re-define this model architecture primarily so that we don't have to worry about version compatibility breaking,
12
+ # but also because Huggingface does a string replace of `gamma` to something else when loading the model state,
13
+ # and this breaks loading of this model.
14
+
15
+ from enum import Enum
16
+ from functools import partial
17
+ import logging
18
+ import math
19
+ import os
20
+ import sys
21
+ from typing import Any, Callable, Dict, List, Optional, Sequence, Tuple, Union
22
+ import warnings
23
+
24
+ import torch
25
+ from torch import nn
26
+ from torch.nn import functional as F
27
+ from torch.nn.init import trunc_normal_
28
+
29
+ _torch_has_sdpa = hasattr(F, 'scaled_dot_product_attention')
30
+
31
+
32
+ XFORMERS_ENABLED = os.environ.get("XFORMERS_DISABLED") is None
33
+ try:
34
+ if XFORMERS_ENABLED:
35
+ from xformers.ops import fmha, scaled_index_add, index_select_cat, SwiGLU, memory_efficient_attention, unbind
36
+
37
+ XFORMERS_AVAILABLE = True
38
+ else:
39
+ raise ImportError
40
+ except ImportError:
41
+ XFORMERS_AVAILABLE = False
42
+
43
+
44
+ def make_2tuple(x):
45
+ if isinstance(x, tuple):
46
+ assert len(x) == 2
47
+ return x
48
+
49
+ assert isinstance(x, int)
50
+ return (x, x)
51
+
52
+
53
+ class PatchEmbed(nn.Module):
54
+ """
55
+ 2D image to patch embedding: (B,C,H,W) -> (B,N,D)
56
+
57
+ Args:
58
+ img_size: Image size.
59
+ patch_size: Patch token size.
60
+ in_chans: Number of input image channels.
61
+ embed_dim: Number of linear projection output channels.
62
+ norm_layer: Normalization layer.
63
+ """
64
+
65
+ def __init__(
66
+ self,
67
+ img_size: Union[int, Tuple[int, int]] = 224,
68
+ patch_size: Union[int, Tuple[int, int]] = 16,
69
+ in_chans: int = 3,
70
+ embed_dim: int = 768,
71
+ norm_layer: Optional[Callable] = None,
72
+ flatten_embedding: bool = True,
73
+ ) -> None:
74
+ super().__init__()
75
+
76
+ image_HW = make_2tuple(img_size)
77
+ patch_HW = make_2tuple(patch_size)
78
+ patch_grid_size = (
79
+ image_HW[0] // patch_HW[0],
80
+ image_HW[1] // patch_HW[1],
81
+ )
82
+
83
+ self.img_size = image_HW
84
+ self.patch_size = patch_HW
85
+ self.patches_resolution = patch_grid_size
86
+ self.num_patches = patch_grid_size[0] * patch_grid_size[1]
87
+
88
+ self.in_chans = in_chans
89
+ self.embed_dim = embed_dim
90
+
91
+ self.flatten_embedding = flatten_embedding
92
+
93
+ self.proj = nn.Conv2d(in_chans, embed_dim, kernel_size=patch_HW, stride=patch_HW)
94
+ self.norm = norm_layer(embed_dim) if norm_layer else nn.Identity()
95
+
96
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
97
+ _, _, H, W = x.shape
98
+ patch_H, patch_W = self.patch_size
99
+
100
+ assert H % patch_H == 0, f"Input image height {H} is not a multiple of patch height {patch_H}"
101
+ assert W % patch_W == 0, f"Input image width {W} is not a multiple of patch width: {patch_W}"
102
+
103
+ x = self.proj(x) # B C H W
104
+ H, W = x.size(2), x.size(3)
105
+ x = x.flatten(2).transpose(1, 2) # B HW C
106
+ x = self.norm(x)
107
+ if not self.flatten_embedding:
108
+ x = x.reshape(-1, H, W, self.embed_dim) # B H W C
109
+ return x
110
+
111
+ def flops(self) -> float:
112
+ Ho, Wo = self.patches_resolution
113
+ flops = Ho * Wo * self.embed_dim * self.in_chans * (self.patch_size[0] * self.patch_size[1])
114
+ if self.norm is not None:
115
+ flops += Ho * Wo * self.embed_dim
116
+ return flops
117
+
118
+
119
+ class Attention(nn.Module):
120
+ def __init__(
121
+ self,
122
+ dim: int,
123
+ num_heads: int = 8,
124
+ qkv_bias: bool = False,
125
+ proj_bias: bool = True,
126
+ attn_drop: float = 0.0,
127
+ proj_drop: float = 0.0,
128
+ ) -> None:
129
+ super().__init__()
130
+ self.num_heads = num_heads
131
+ head_dim = dim // num_heads
132
+ self.scale = head_dim**-0.5
133
+
134
+ self.qkv = nn.Linear(dim, dim * 3, bias=qkv_bias)
135
+ self.attn_drop = nn.Dropout(attn_drop)
136
+ self.proj = nn.Linear(dim, dim, bias=proj_bias)
137
+ self.proj_drop = nn.Dropout(proj_drop)
138
+
139
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
140
+ B, N, C = x.shape
141
+ qkv = self.qkv(x).reshape(B, N, 3, self.num_heads, C // self.num_heads).permute(2, 0, 3, 1, 4)
142
+
143
+ q, k, v = qkv[0], qkv[1], qkv[2]
144
+ if _torch_has_sdpa:
145
+ x = F.scaled_dot_product_attention(
146
+ q, k, v,
147
+ is_causal=False,
148
+ dropout_p=self.attn_drop.p if self.training else 0.,
149
+ scale=self.scale,
150
+ )
151
+ else:
152
+ q = q * self.scale
153
+ attn = q @ k.transpose(-2, -1)
154
+
155
+ attn = attn.softmax(dim=-1)
156
+ attn = self.attn_drop(attn)
157
+ x = attn @ v
158
+
159
+ x = x.transpose(1, 2).reshape(B, N, C)
160
+ x = self.proj(x)
161
+ x = self.proj_drop(x)
162
+ return x
163
+
164
+
165
+ class MemEffAttention(Attention):
166
+ def forward(self, x: torch.Tensor, attn_bias=None) -> torch.Tensor:
167
+ if not XFORMERS_AVAILABLE:
168
+ if attn_bias is not None:
169
+ raise AssertionError("xFormers is required for using nested tensors")
170
+ return super().forward(x)
171
+
172
+ B, N, C = x.shape
173
+ qkv = self.qkv(x).reshape(B, N, 3, self.num_heads, C // self.num_heads)
174
+
175
+ q, k, v = unbind(qkv, 2)
176
+
177
+ x = memory_efficient_attention(q, k, v, attn_bias=attn_bias)
178
+ x = x.reshape([B, N, C])
179
+
180
+ x = self.proj(x)
181
+ x = self.proj_drop(x)
182
+ return x
183
+
184
+
185
+ class Mlp(nn.Module):
186
+ def __init__(
187
+ self,
188
+ in_features: int,
189
+ hidden_features: Optional[int] = None,
190
+ out_features: Optional[int] = None,
191
+ act_layer: Callable[..., nn.Module] = nn.GELU,
192
+ drop: float = 0.0,
193
+ bias: bool = True,
194
+ ) -> None:
195
+ super().__init__()
196
+ out_features = out_features or in_features
197
+ hidden_features = hidden_features or in_features
198
+ self.fc1 = nn.Linear(in_features, hidden_features, bias=bias)
199
+ self.act = act_layer()
200
+ self.fc2 = nn.Linear(hidden_features, out_features, bias=bias)
201
+ self.drop = nn.Dropout(drop)
202
+
203
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
204
+ x = self.fc1(x)
205
+ x = self.act(x)
206
+ x = self.drop(x)
207
+ x = self.fc2(x)
208
+ x = self.drop(x)
209
+ return x
210
+
211
+
212
+ class SwiGLUFFN(nn.Module):
213
+ def __init__(
214
+ self,
215
+ in_features: int,
216
+ hidden_features: Optional[int] = None,
217
+ out_features: Optional[int] = None,
218
+ act_layer: Callable[..., nn.Module] = None,
219
+ drop: float = 0.0,
220
+ bias: bool = True,
221
+ ) -> None:
222
+ super().__init__()
223
+ out_features = out_features or in_features
224
+ hidden_features = hidden_features or in_features
225
+ self.w12 = nn.Linear(in_features, 2 * hidden_features, bias=bias)
226
+ self.w3 = nn.Linear(hidden_features, out_features, bias=bias)
227
+
228
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
229
+ x12 = self.w12(x)
230
+ x1, x2 = x12.chunk(2, dim=-1)
231
+ hidden = F.silu(x1) * x2
232
+ return self.w3(hidden)
233
+
234
+
235
+ if not XFORMERS_AVAILABLE:
236
+ SwiGLU = SwiGLUFFN
237
+
238
+
239
+ class SwiGLUFFNFused(SwiGLU):
240
+ def __init__(
241
+ self,
242
+ in_features: int,
243
+ hidden_features: Optional[int] = None,
244
+ out_features: Optional[int] = None,
245
+ act_layer: Callable[..., nn.Module] = None,
246
+ drop: float = 0.0,
247
+ bias: bool = True,
248
+ ) -> None:
249
+ out_features = out_features or in_features
250
+ hidden_features = hidden_features or in_features
251
+ hidden_features = (int(hidden_features * 2 / 3) + 7) // 8 * 8
252
+ super().__init__(
253
+ in_features=in_features,
254
+ hidden_features=hidden_features,
255
+ out_features=out_features,
256
+ bias=bias,
257
+ )
258
+
259
+
260
+ def drop_path(x, drop_prob: float = 0.0, training: bool = False):
261
+ if drop_prob == 0.0 or not training:
262
+ return x
263
+ keep_prob = 1 - drop_prob
264
+ shape = (x.shape[0],) + (1,) * (x.ndim - 1) # work with diff dim tensors, not just 2D ConvNets
265
+ random_tensor = x.new_empty(shape).bernoulli_(keep_prob)
266
+ if keep_prob > 0.0:
267
+ random_tensor.div_(keep_prob)
268
+ output = x * random_tensor
269
+ return output
270
+
271
+
272
+ class DropPath(nn.Module):
273
+ """Drop paths (Stochastic Depth) per sample (when applied in main path of residual blocks)."""
274
+
275
+ def __init__(self, drop_prob=None):
276
+ super(DropPath, self).__init__()
277
+ self.drop_prob = drop_prob
278
+
279
+ def forward(self, x):
280
+ return drop_path(x, self.drop_prob, self.training)
281
+
282
+
283
+ class LayerScale(nn.Module):
284
+ def __init__(
285
+ self,
286
+ dim: int,
287
+ init_values: Union[float, torch.Tensor] = 1e-5,
288
+ inplace: bool = False,
289
+ ) -> None:
290
+ super().__init__()
291
+ self.inplace = inplace
292
+ self.grandma = nn.Parameter(init_values * torch.ones(dim))
293
+
294
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
295
+ return x.mul_(self.grandma) if self.inplace else x * self.grandma
296
+
297
+ def _load_from_state_dict(self, state_dict, prefix, local_metadata, strict, missing_keys, unexpected_keys, error_msgs):
298
+ # Huggingface is absurd and it will rename strings that contain `gamma`, which means that the normal DINO implementation
299
+ # of LayerScale won't work with HFHub. So we rename the variable to 'grandma', and support loading checkpoints in either
300
+ # format
301
+ key_a = f'{prefix}gamma'
302
+ key_b = f'{prefix}grandma'
303
+ if key_a in state_dict:
304
+ gamma = state_dict[key_a]
305
+ elif key_b in state_dict:
306
+ gamma = state_dict[key_b]
307
+ else:
308
+ if strict:
309
+ raise KeyError(f"Couldn't find the key {key_a} nor {key_b} in the state dict!")
310
+ else:
311
+ missing_keys.append(key_a)
312
+ missing_keys.append(key_b)
313
+ unexpected_keys.extend(state_dict.keys())
314
+ gamma = None
315
+
316
+ if gamma is not None:
317
+ self.grandma.data.copy_(gamma)
318
+
319
+ # return super()._load_from_state_dict(state_dict, prefix, local_metadata, strict, missing_keys, unexpected_keys, error_msgs)
320
+
321
+
322
+ class Block(nn.Module):
323
+ def __init__(
324
+ self,
325
+ dim: int,
326
+ num_heads: int,
327
+ mlp_ratio: float = 4.0,
328
+ qkv_bias: bool = False,
329
+ proj_bias: bool = True,
330
+ ffn_bias: bool = True,
331
+ drop: float = 0.0,
332
+ attn_drop: float = 0.0,
333
+ init_values=None,
334
+ drop_path: float = 0.0,
335
+ act_layer: Callable[..., nn.Module] = nn.GELU,
336
+ norm_layer: Callable[..., nn.Module] = nn.LayerNorm,
337
+ attn_class: Callable[..., nn.Module] = Attention,
338
+ ffn_layer: Callable[..., nn.Module] = Mlp,
339
+ ) -> None:
340
+ super().__init__()
341
+ # print(f"biases: qkv: {qkv_bias}, proj: {proj_bias}, ffn: {ffn_bias}")
342
+ self.norm1 = norm_layer(dim)
343
+ self.attn = attn_class(
344
+ dim,
345
+ num_heads=num_heads,
346
+ qkv_bias=qkv_bias,
347
+ proj_bias=proj_bias,
348
+ attn_drop=attn_drop,
349
+ proj_drop=drop,
350
+ )
351
+ self.ls1 = LayerScale(dim, init_values=init_values) if init_values else nn.Identity()
352
+ self.drop_path1 = DropPath(drop_path) if drop_path > 0.0 else nn.Identity()
353
+
354
+ self.norm2 = norm_layer(dim)
355
+ mlp_hidden_dim = int(dim * mlp_ratio)
356
+ self.mlp = ffn_layer(
357
+ in_features=dim,
358
+ hidden_features=mlp_hidden_dim,
359
+ act_layer=act_layer,
360
+ drop=drop,
361
+ bias=ffn_bias,
362
+ )
363
+ self.ls2 = LayerScale(dim, init_values=init_values) if init_values else nn.Identity()
364
+ self.drop_path2 = DropPath(drop_path) if drop_path > 0.0 else nn.Identity()
365
+
366
+ self.sample_drop_ratio = drop_path
367
+
368
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
369
+ def attn_residual_func(x: torch.Tensor) -> torch.Tensor:
370
+ return self.ls1(self.attn(self.norm1(x)))
371
+
372
+ def ffn_residual_func(x: torch.Tensor) -> torch.Tensor:
373
+ return self.ls2(self.mlp(self.norm2(x)))
374
+
375
+ if self.training and self.sample_drop_ratio > 0.1:
376
+ # the overhead is compensated only for a drop path rate larger than 0.1
377
+ x = drop_add_residual_stochastic_depth(
378
+ x,
379
+ residual_func=attn_residual_func,
380
+ sample_drop_ratio=self.sample_drop_ratio,
381
+ )
382
+ x = drop_add_residual_stochastic_depth(
383
+ x,
384
+ residual_func=ffn_residual_func,
385
+ sample_drop_ratio=self.sample_drop_ratio,
386
+ )
387
+ elif self.training and self.sample_drop_ratio > 0.0:
388
+ x = x + self.drop_path1(attn_residual_func(x))
389
+ x = x + self.drop_path1(ffn_residual_func(x)) # FIXME: drop_path2
390
+ else:
391
+ x = x + attn_residual_func(x)
392
+ x = x + ffn_residual_func(x)
393
+ return x
394
+
395
+
396
+ class NestedTensorBlock(Block):
397
+ def forward_nested(self, x_list: List[torch.Tensor]) -> List[torch.Tensor]:
398
+ """
399
+ x_list contains a list of tensors to nest together and run
400
+ """
401
+ assert isinstance(self.attn, MemEffAttention)
402
+
403
+ if self.training and self.sample_drop_ratio > 0.0:
404
+
405
+ def attn_residual_func(x: torch.Tensor, attn_bias=None) -> torch.Tensor:
406
+ return self.attn(self.norm1(x), attn_bias=attn_bias)
407
+
408
+ def ffn_residual_func(x: torch.Tensor, attn_bias=None) -> torch.Tensor:
409
+ return self.mlp(self.norm2(x))
410
+
411
+ x_list = drop_add_residual_stochastic_depth_list(
412
+ x_list,
413
+ residual_func=attn_residual_func,
414
+ sample_drop_ratio=self.sample_drop_ratio,
415
+ scaling_vector=self.ls1.grandma if isinstance(self.ls1, LayerScale) else None,
416
+ )
417
+ x_list = drop_add_residual_stochastic_depth_list(
418
+ x_list,
419
+ residual_func=ffn_residual_func,
420
+ sample_drop_ratio=self.sample_drop_ratio,
421
+ scaling_vector=self.ls2.grandma if isinstance(self.ls1, LayerScale) else None,
422
+ )
423
+ return x_list
424
+ else:
425
+
426
+ def attn_residual_func(x: torch.Tensor, attn_bias=None) -> torch.Tensor:
427
+ return self.ls1(self.attn(self.norm1(x), attn_bias=attn_bias))
428
+
429
+ def ffn_residual_func(x: torch.Tensor, attn_bias=None) -> torch.Tensor:
430
+ return self.ls2(self.mlp(self.norm2(x)))
431
+
432
+ attn_bias, x = get_attn_bias_and_cat(x_list)
433
+ x = x + attn_residual_func(x, attn_bias=attn_bias)
434
+ x = x + ffn_residual_func(x)
435
+ return attn_bias.split(x)
436
+
437
+ def forward(self, x_or_x_list):
438
+ if isinstance(x_or_x_list, torch.Tensor):
439
+ return super().forward(x_or_x_list)
440
+ elif isinstance(x_or_x_list, list):
441
+ if not XFORMERS_AVAILABLE:
442
+ raise AssertionError("xFormers is required for using nested tensors")
443
+ return self.forward_nested(x_or_x_list)
444
+ else:
445
+ raise AssertionError
446
+
447
+
448
+ def drop_add_residual_stochastic_depth(
449
+ x: torch.Tensor,
450
+ residual_func: Callable[[torch.Tensor], torch.Tensor],
451
+ sample_drop_ratio: float = 0.0,
452
+ ) -> torch.Tensor:
453
+ # 1) extract subset using permutation
454
+ b, n, d = x.shape
455
+ sample_subset_size = max(int(b * (1 - sample_drop_ratio)), 1)
456
+ brange = (torch.randperm(b, device=x.device))[:sample_subset_size]
457
+ x_subset = x[brange]
458
+
459
+ # 2) apply residual_func to get residual
460
+ residual = residual_func(x_subset)
461
+
462
+ x_flat = x.flatten(1)
463
+ residual = residual.flatten(1)
464
+
465
+ residual_scale_factor = b / sample_subset_size
466
+
467
+ # 3) add the residual
468
+ x_plus_residual = torch.index_add(x_flat, 0, brange, residual.to(dtype=x.dtype), alpha=residual_scale_factor)
469
+ return x_plus_residual.view_as(x)
470
+
471
+
472
+ def get_branges_scales(x, sample_drop_ratio=0.0):
473
+ b, n, d = x.shape
474
+ sample_subset_size = max(int(b * (1 - sample_drop_ratio)), 1)
475
+ brange = (torch.randperm(b, device=x.device))[:sample_subset_size]
476
+ residual_scale_factor = b / sample_subset_size
477
+ return brange, residual_scale_factor
478
+
479
+
480
+ def add_residual(x, brange, residual, residual_scale_factor, scaling_vector=None):
481
+ if scaling_vector is None:
482
+ x_flat = x.flatten(1)
483
+ residual = residual.flatten(1)
484
+ x_plus_residual = torch.index_add(x_flat, 0, brange, residual.to(dtype=x.dtype), alpha=residual_scale_factor)
485
+ else:
486
+ x_plus_residual = scaled_index_add(
487
+ x, brange, residual.to(dtype=x.dtype), scaling=scaling_vector, alpha=residual_scale_factor
488
+ )
489
+ return x_plus_residual
490
+
491
+
492
+ attn_bias_cache: Dict[Tuple, Any] = {}
493
+
494
+
495
+ def get_attn_bias_and_cat(x_list, branges=None):
496
+ """
497
+ this will perform the index select, cat the tensors, and provide the attn_bias from cache
498
+ """
499
+ batch_sizes = [b.shape[0] for b in branges] if branges is not None else [x.shape[0] for x in x_list]
500
+ all_shapes = tuple((b, x.shape[1]) for b, x in zip(batch_sizes, x_list))
501
+ if all_shapes not in attn_bias_cache.keys():
502
+ seqlens = []
503
+ for b, x in zip(batch_sizes, x_list):
504
+ for _ in range(b):
505
+ seqlens.append(x.shape[1])
506
+ attn_bias = fmha.BlockDiagonalMask.from_seqlens(seqlens)
507
+ attn_bias._batch_sizes = batch_sizes
508
+ attn_bias_cache[all_shapes] = attn_bias
509
+
510
+ if branges is not None:
511
+ cat_tensors = index_select_cat([x.flatten(1) for x in x_list], branges).view(1, -1, x_list[0].shape[-1])
512
+ else:
513
+ tensors_bs1 = tuple(x.reshape([1, -1, *x.shape[2:]]) for x in x_list)
514
+ cat_tensors = torch.cat(tensors_bs1, dim=1)
515
+
516
+ return attn_bias_cache[all_shapes], cat_tensors
517
+
518
+
519
+ def drop_add_residual_stochastic_depth_list(
520
+ x_list: List[torch.Tensor],
521
+ residual_func: Callable[[torch.Tensor, Any], torch.Tensor],
522
+ sample_drop_ratio: float = 0.0,
523
+ scaling_vector=None,
524
+ ) -> torch.Tensor:
525
+ # 1) generate random set of indices for dropping samples in the batch
526
+ branges_scales = [get_branges_scales(x, sample_drop_ratio=sample_drop_ratio) for x in x_list]
527
+ branges = [s[0] for s in branges_scales]
528
+ residual_scale_factors = [s[1] for s in branges_scales]
529
+
530
+ # 2) get attention bias and index+concat the tensors
531
+ attn_bias, x_cat = get_attn_bias_and_cat(x_list, branges)
532
+
533
+ # 3) apply residual_func to get residual, and split the result
534
+ residual_list = attn_bias.split(residual_func(x_cat, attn_bias=attn_bias)) # type: ignore
535
+
536
+ outputs = []
537
+ for x, brange, residual, residual_scale_factor in zip(x_list, branges, residual_list, residual_scale_factors):
538
+ outputs.append(add_residual(x, brange, residual, residual_scale_factor, scaling_vector).view_as(x))
539
+ return outputs
540
+
541
+
542
+ def named_apply(fn: Callable, module: nn.Module, name="", depth_first=True, include_root=False) -> nn.Module:
543
+ if not depth_first and include_root:
544
+ fn(module=module, name=name)
545
+ for child_name, child_module in module.named_children():
546
+ child_name = ".".join((name, child_name)) if name else child_name
547
+ named_apply(fn=fn, module=child_module, name=child_name, depth_first=depth_first, include_root=True)
548
+ if depth_first and include_root:
549
+ fn(module=module, name=name)
550
+ return module
551
+
552
+
553
+ class BlockChunk(nn.ModuleList):
554
+ def forward(self, x):
555
+ for b in self:
556
+ x = b(x)
557
+ return x
558
+
559
+
560
+ class DinoVisionTransformer(nn.Module):
561
+ def __init__(
562
+ self,
563
+ img_size=224,
564
+ patch_size=16,
565
+ in_chans=3,
566
+ embed_dim=768,
567
+ depth=12,
568
+ num_heads=12,
569
+ mlp_ratio=4.0,
570
+ qkv_bias=True,
571
+ ffn_bias=True,
572
+ proj_bias=True,
573
+ drop_path_rate=0.0,
574
+ drop_path_uniform=False,
575
+ init_values=None, # for layerscale: None or 0 => no layerscale
576
+ embed_layer=PatchEmbed,
577
+ act_layer=nn.GELU,
578
+ block_fn=Block,
579
+ ffn_layer="mlp",
580
+ block_chunks=1,
581
+ num_register_tokens=0,
582
+ interpolate_antialias=False,
583
+ interpolate_offset=0.1,
584
+ ):
585
+ """
586
+ Args:
587
+ img_size (int, tuple): input image size
588
+ patch_size (int, tuple): patch size
589
+ in_chans (int): number of input channels
590
+ embed_dim (int): embedding dimension
591
+ depth (int): depth of transformer
592
+ num_heads (int): number of attention heads
593
+ mlp_ratio (int): ratio of mlp hidden dim to embedding dim
594
+ qkv_bias (bool): enable bias for qkv if True
595
+ proj_bias (bool): enable bias for proj in attn if True
596
+ ffn_bias (bool): enable bias for ffn if True
597
+ drop_path_rate (float): stochastic depth rate
598
+ drop_path_uniform (bool): apply uniform drop rate across blocks
599
+ weight_init (str): weight init scheme
600
+ init_values (float): layer-scale init values
601
+ embed_layer (nn.Module): patch embedding layer
602
+ act_layer (nn.Module): MLP activation layer
603
+ block_fn (nn.Module): transformer block class
604
+ ffn_layer (str): "mlp", "swiglu", "swiglufused" or "identity"
605
+ block_chunks: (int) split block sequence into block_chunks units for FSDP wrap
606
+ num_register_tokens: (int) number of extra cls tokens (so-called "registers")
607
+ interpolate_antialias: (str) flag to apply anti-aliasing when interpolating positional embeddings
608
+ interpolate_offset: (float) work-around offset to apply when interpolating positional embeddings
609
+ """
610
+ super().__init__()
611
+ norm_layer = partial(nn.LayerNorm, eps=1e-6)
612
+
613
+ self.num_features = self.embed_dim = embed_dim # num_features for consistency with other models
614
+ self.num_tokens = 1
615
+ self.n_blocks = depth
616
+ self.num_heads = num_heads
617
+ self.patch_size = patch_size
618
+ self.num_register_tokens = num_register_tokens
619
+ self.interpolate_antialias = interpolate_antialias
620
+ self.interpolate_offset = interpolate_offset
621
+
622
+ self.patch_embed = embed_layer(img_size=img_size, patch_size=patch_size, in_chans=in_chans, embed_dim=embed_dim)
623
+ num_patches = self.patch_embed.num_patches
624
+
625
+ self.cls_token = nn.Parameter(torch.zeros(1, 1, embed_dim))
626
+ self.pos_embed = nn.Parameter(torch.zeros(1, num_patches + self.num_tokens, embed_dim))
627
+ assert num_register_tokens >= 0
628
+ self.register_tokens = (
629
+ nn.Parameter(torch.zeros(1, num_register_tokens, embed_dim)) if num_register_tokens else None
630
+ )
631
+
632
+ if drop_path_uniform is True:
633
+ dpr = [drop_path_rate] * depth
634
+ else:
635
+ dpr = [x.item() for x in torch.linspace(0, drop_path_rate, depth)] # stochastic depth decay rule
636
+
637
+ if ffn_layer == "mlp":
638
+ ffn_layer = Mlp
639
+ elif ffn_layer == "swiglufused" or ffn_layer == "swiglu":
640
+ ffn_layer = SwiGLUFFNFused
641
+ elif ffn_layer == "identity":
642
+ def f(*args, **kwargs):
643
+ return nn.Identity()
644
+
645
+ ffn_layer = f
646
+ else:
647
+ raise NotImplementedError
648
+
649
+ blocks_list = [
650
+ block_fn(
651
+ dim=embed_dim,
652
+ num_heads=num_heads,
653
+ mlp_ratio=mlp_ratio,
654
+ qkv_bias=qkv_bias,
655
+ proj_bias=proj_bias,
656
+ ffn_bias=ffn_bias,
657
+ drop_path=dpr[i],
658
+ norm_layer=norm_layer,
659
+ act_layer=act_layer,
660
+ ffn_layer=ffn_layer,
661
+ init_values=init_values,
662
+ )
663
+ for i in range(depth)
664
+ ]
665
+ if block_chunks > 0:
666
+ self.chunked_blocks = True
667
+ chunked_blocks = []
668
+ chunksize = depth // block_chunks
669
+ for i in range(0, depth, chunksize):
670
+ # this is to keep the block index consistent if we chunk the block list
671
+ chunked_blocks.append([nn.Identity()] * i + blocks_list[i : i + chunksize])
672
+ self.blocks = nn.ModuleList([BlockChunk(p) for p in chunked_blocks])
673
+ else:
674
+ self.chunked_blocks = False
675
+ self.blocks = nn.ModuleList(blocks_list)
676
+
677
+ self.norm = norm_layer(embed_dim)
678
+ self.head = nn.Identity()
679
+
680
+ self.mask_token = nn.Parameter(torch.zeros(1, embed_dim))
681
+
682
+ def interpolate_pos_encoding(self, x, w, h):
683
+ previous_dtype = x.dtype
684
+ npatch = x.shape[1] - 1
685
+ N = self.pos_embed.shape[1] - 1
686
+ if npatch == N and w == h:
687
+ return self.pos_embed
688
+ pos_embed = self.pos_embed.float()
689
+ class_pos_embed = pos_embed[:, 0]
690
+ patch_pos_embed = pos_embed[:, 1:]
691
+ dim = x.shape[-1]
692
+ w0 = w // self.patch_size
693
+ h0 = h // self.patch_size
694
+ M = int(math.sqrt(N)) # Recover the number of patches in each dimension
695
+ assert N == M * M
696
+ kwargs = {}
697
+ if self.interpolate_offset:
698
+ # Historical kludge: add a small number to avoid floating point error in the interpolation, see https://github.com/facebookresearch/dino/issues/8
699
+ # Note: still needed for backward-compatibility, the underlying operators are using both output size and scale factors
700
+ sx = float(w0 + self.interpolate_offset) / M
701
+ sy = float(h0 + self.interpolate_offset) / M
702
+ kwargs["scale_factor"] = (sx, sy)
703
+ else:
704
+ # Simply specify an output size instead of a scale factor
705
+ kwargs["size"] = (w0, h0)
706
+ patch_pos_embed = nn.functional.interpolate(
707
+ patch_pos_embed.reshape(1, M, M, dim).permute(0, 3, 1, 2),
708
+ mode="bicubic",
709
+ antialias=self.interpolate_antialias,
710
+ **kwargs,
711
+ )
712
+ assert (w0, h0) == patch_pos_embed.shape[-2:]
713
+ patch_pos_embed = patch_pos_embed.permute(0, 2, 3, 1).view(1, -1, dim)
714
+ return torch.cat((class_pos_embed.unsqueeze(0), patch_pos_embed), dim=1).to(previous_dtype)
715
+
716
+ def prepare_tokens_with_masks(self, x, masks=None):
717
+ B, nc, w, h = x.shape
718
+ x = self.patch_embed(x)
719
+ if masks is not None:
720
+ x = torch.where(masks.unsqueeze(-1), self.mask_token.to(x.dtype).unsqueeze(0), x)
721
+
722
+ x = torch.cat((self.cls_token.expand(x.shape[0], -1, -1), x), dim=1)
723
+ x = x + self.interpolate_pos_encoding(x, w, h)
724
+
725
+ if self.register_tokens is not None:
726
+ x = torch.cat(
727
+ (
728
+ x[:, :1],
729
+ self.register_tokens.expand(x.shape[0], -1, -1),
730
+ x[:, 1:],
731
+ ),
732
+ dim=1,
733
+ )
734
+
735
+ return x
736
+
737
+ def forward_features_list(self, x_list, masks_list):
738
+ x = [self.prepare_tokens_with_masks(x, masks) for x, masks in zip(x_list, masks_list)]
739
+ for blk in self.blocks:
740
+ x = blk(x)
741
+
742
+ all_x = x
743
+ output = []
744
+ for x, masks in zip(all_x, masks_list):
745
+ x_norm = self.norm(x)
746
+ output.append(
747
+ {
748
+ "x_norm_clstoken": x_norm[:, 0],
749
+ "x_norm_regtokens": x_norm[:, 1 : self.num_register_tokens + 1],
750
+ "x_norm_patchtokens": x_norm[:, self.num_register_tokens + 1 :],
751
+ "x_prenorm": x,
752
+ "masks": masks,
753
+ }
754
+ )
755
+ return output
756
+
757
+ def forward_features(self, x, masks=None):
758
+ if isinstance(x, list):
759
+ return self.forward_features_list(x, masks)
760
+
761
+ x = self.prepare_tokens_with_masks(x, masks)
762
+
763
+ for blk in self.blocks:
764
+ x = blk(x)
765
+
766
+ x_norm = self.norm(x)
767
+ return {
768
+ "x_norm_clstoken": x_norm[:, 0],
769
+ "x_norm_regtokens": x_norm[:, 1 : self.num_register_tokens + 1],
770
+ "x_norm_patchtokens": x_norm[:, self.num_register_tokens + 1 :],
771
+ "x_prenorm": x,
772
+ "masks": masks,
773
+ }
774
+
775
+ def _get_intermediate_layers_not_chunked(self, x, n=1):
776
+ x = self.prepare_tokens_with_masks(x)
777
+ # If n is an int, take the n last blocks. If it's a list, take them
778
+ output, total_block_len = [], len(self.blocks)
779
+ blocks_to_take = range(total_block_len - n, total_block_len) if isinstance(n, int) else n
780
+ for i, blk in enumerate(self.blocks):
781
+ x = blk(x)
782
+ if i in blocks_to_take:
783
+ output.append(x)
784
+ assert len(output) == len(blocks_to_take), f"only {len(output)} / {len(blocks_to_take)} blocks found"
785
+ return output
786
+
787
+ def _get_intermediate_layers_chunked(self, x, n=1):
788
+ x = self.prepare_tokens_with_masks(x)
789
+ output, i, total_block_len = [], 0, len(self.blocks[-1])
790
+ # If n is an int, take the n last blocks. If it's a list, take them
791
+ blocks_to_take = range(total_block_len - n, total_block_len) if isinstance(n, int) else n
792
+ for block_chunk in self.blocks:
793
+ for blk in block_chunk[i:]: # Passing the nn.Identity()
794
+ x = blk(x)
795
+ if i in blocks_to_take:
796
+ output.append(x)
797
+ i += 1
798
+ assert len(output) == len(blocks_to_take), f"only {len(output)} / {len(blocks_to_take)} blocks found"
799
+ return output
800
+
801
+ def get_intermediate_layers(
802
+ self,
803
+ x: torch.Tensor,
804
+ n: Union[int, Sequence] = 1, # Layers or n last layers to take
805
+ reshape: bool = False,
806
+ return_class_token: bool = False,
807
+ norm=True,
808
+ ) -> Tuple[Union[torch.Tensor, Tuple[torch.Tensor]]]:
809
+ if self.chunked_blocks:
810
+ outputs = self._get_intermediate_layers_chunked(x, n)
811
+ else:
812
+ outputs = self._get_intermediate_layers_not_chunked(x, n)
813
+ if norm:
814
+ outputs = [self.norm(out) for out in outputs]
815
+ class_tokens = [out[:, 0] for out in outputs]
816
+ outputs = [out[:, 1 + self.num_register_tokens :] for out in outputs]
817
+ if reshape:
818
+ B, _, w, h = x.shape
819
+ outputs = [
820
+ out.reshape(B, w // self.patch_size, h // self.patch_size, -1).permute(0, 3, 1, 2).contiguous()
821
+ for out in outputs
822
+ ]
823
+ if return_class_token:
824
+ return tuple(zip(outputs, class_tokens))
825
+ return tuple(outputs)
826
+
827
+ def forward(self, *args, is_training=False, **kwargs):
828
+ ret = self.forward_features(*args, **kwargs)
829
+ if is_training:
830
+ return ret
831
+ else:
832
+ return self.head(ret["x_norm_clstoken"])
833
+
834
+
835
+ def vit_small(patch_size=16, num_register_tokens=0, **kwargs):
836
+ model = DinoVisionTransformer(
837
+ patch_size=patch_size,
838
+ embed_dim=384,
839
+ depth=12,
840
+ num_heads=6,
841
+ mlp_ratio=4,
842
+ block_fn=partial(Block, attn_class=MemEffAttention),
843
+ num_register_tokens=num_register_tokens,
844
+ **kwargs,
845
+ )
846
+ return model
847
+
848
+
849
+ def vit_base(patch_size=16, num_register_tokens=0, **kwargs):
850
+ model = DinoVisionTransformer(
851
+ patch_size=patch_size,
852
+ embed_dim=768,
853
+ depth=12,
854
+ num_heads=12,
855
+ mlp_ratio=4,
856
+ block_fn=partial(Block, attn_class=MemEffAttention),
857
+ num_register_tokens=num_register_tokens,
858
+ **kwargs,
859
+ )
860
+ return model
861
+
862
+
863
+ def vit_large(patch_size=16, num_register_tokens=0, **kwargs):
864
+ model = DinoVisionTransformer(
865
+ patch_size=patch_size,
866
+ embed_dim=1024,
867
+ depth=24,
868
+ num_heads=16,
869
+ mlp_ratio=4,
870
+ block_fn=partial(Block, attn_class=MemEffAttention),
871
+ num_register_tokens=num_register_tokens,
872
+ **kwargs,
873
+ )
874
+ return model
875
+
876
+
877
+ def vit_giant2(patch_size=16, num_register_tokens=0, **kwargs):
878
+ """
879
+ Close to ViT-giant, with embed-dim 1536 and 24 heads => embed-dim per head 64
880
+ """
881
+ model = DinoVisionTransformer(
882
+ patch_size=patch_size,
883
+ embed_dim=1536,
884
+ depth=40,
885
+ num_heads=24,
886
+ mlp_ratio=4,
887
+ block_fn=partial(Block, attn_class=MemEffAttention),
888
+ num_register_tokens=num_register_tokens,
889
+ **kwargs,
890
+ )
891
+ return model
892
+
893
+
894
+ class Weights(Enum):
895
+ LVD142M = "LVD142M"
896
+
897
+
898
+ def _make_dinov2_model(
899
+ *,
900
+ arch_name: str = "vit_large",
901
+ img_size: int = 518,
902
+ patch_size: int = 14,
903
+ init_values: float = 1.0,
904
+ ffn_layer: str = "mlp",
905
+ block_chunks: int = 0,
906
+ num_register_tokens: int = 0,
907
+ interpolate_antialias: bool = False,
908
+ interpolate_offset: float = 0.1,
909
+ weights: Union[Weights, str] = Weights.LVD142M,
910
+ **kwargs,
911
+ ):
912
+ if isinstance(weights, str):
913
+ try:
914
+ weights = Weights[weights]
915
+ except KeyError:
916
+ raise AssertionError(f"Unsupported weights: {weights}")
917
+
918
+ vit_kwargs = dict(
919
+ img_size=img_size,
920
+ patch_size=patch_size,
921
+ init_values=init_values,
922
+ ffn_layer=ffn_layer,
923
+ block_chunks=block_chunks,
924
+ num_register_tokens=num_register_tokens,
925
+ interpolate_antialias=interpolate_antialias,
926
+ interpolate_offset=interpolate_offset,
927
+ )
928
+ vit_kwargs.update(**kwargs)
929
+ model = sys.modules[__name__].__dict__[arch_name](**vit_kwargs)
930
+
931
+ return model
932
+
933
+
934
+ def dinov2_vits14(**kwargs):
935
+ """
936
+ DINOv2 ViT-S/14 model (optionally) pretrained on the LVD-142M dataset.
937
+ """
938
+ return _make_dinov2_model(arch_name="vit_small", **kwargs)
939
+
940
+
941
+ def dinov2_vitb14(**kwargs):
942
+ """
943
+ DINOv2 ViT-B/14 model (optionally) pretrained on the LVD-142M dataset.
944
+ """
945
+ return _make_dinov2_model(arch_name="vit_base", **kwargs)
946
+
947
+
948
+ def dinov2_vitl14(**kwargs):
949
+ """
950
+ DINOv2 ViT-L/14 model (optionally) pretrained on the LVD-142M dataset.
951
+ """
952
+ return _make_dinov2_model(arch_name="vit_large", **kwargs)
953
+
954
+
955
+ def dinov2_vitg14(**kwargs):
956
+ """
957
+ DINOv2 ViT-g/14 model (optionally) pretrained on the LVD-142M dataset.
958
+ """
959
+ return _make_dinov2_model(
960
+ arch_name="vit_giant2",
961
+ ffn_layer="swiglufused",
962
+ **kwargs,
963
+ )
964
+
965
+
966
+ def dinov2_vits14_reg(**kwargs):
967
+ """
968
+ DINOv2 ViT-S/14 model with registers (optionally) pretrained on the LVD-142M dataset.
969
+ """
970
+ return _make_dinov2_model(
971
+ arch_name="vit_small",
972
+ num_register_tokens=4,
973
+ interpolate_antialias=True,
974
+ interpolate_offset=0.0,
975
+ **kwargs,
976
+ )
977
+
978
+
979
+ def dinov2_vitb14_reg(**kwargs):
980
+ """
981
+ DINOv2 ViT-B/14 model with registers (optionally) pretrained on the LVD-142M dataset.
982
+ """
983
+ return _make_dinov2_model(
984
+ arch_name="vit_base",
985
+ num_register_tokens=4,
986
+ interpolate_antialias=True,
987
+ interpolate_offset=0.0,
988
+ **kwargs,
989
+ )
990
+
991
+
992
+ def dinov2_vitl14_reg(**kwargs):
993
+ """
994
+ DINOv2 ViT-L/14 model with registers (optionally) pretrained on the LVD-142M dataset.
995
+ """
996
+ return _make_dinov2_model(
997
+ arch_name="vit_large",
998
+ num_register_tokens=4,
999
+ interpolate_antialias=True,
1000
+ interpolate_offset=0.0,
1001
+ **kwargs,
1002
+ )
1003
+
1004
+
1005
+ def dinov2_vitg14_reg(**kwargs):
1006
+ """
1007
+ DINOv2 ViT-g/14 model with registers (optionally) pretrained on the LVD-142M dataset.
1008
+ """
1009
+ return _make_dinov2_model(
1010
+ arch_name="vit_giant2",
1011
+ ffn_layer="swiglufused",
1012
+ num_register_tokens=4,
1013
+ interpolate_antialias=True,
1014
+ interpolate_offset=0.0,
1015
+ **kwargs,
1016
+ )
dual_hybrid_vit.py ADDED
@@ -0,0 +1,213 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from logging import getLogger
2
+ from typing import Tuple
3
+
4
+ import torch
5
+ from torch import nn
6
+ from torch.nn import functional as F
7
+
8
+ from timm.models import register_model
9
+ from timm.models import vision_transformer as tvit
10
+ from timm.models import convnext as tconv
11
+
12
+ from einops import rearrange
13
+
14
+ from . import extra_timm_models as et
15
+
16
+
17
+ class Fuser(nn.Module):
18
+ def __init__(self, src_dim: int, tgt_dim: int, gated: bool = True):
19
+ super().__init__()
20
+ self.gated = gated
21
+
22
+ mid_dim = max(src_dim, tgt_dim) * 2
23
+
24
+ self.fwd = nn.Sequential(
25
+ nn.Conv2d(src_dim, mid_dim, kernel_size=3, stride=1, padding=1),
26
+ nn.GELU(),
27
+ nn.Conv2d(mid_dim, tgt_dim * (2 if gated else 1), kernel_size=3, stride=1, padding=1),
28
+ )
29
+
30
+ def forward(self, src: torch.Tensor, tgt: torch.Tensor) -> torch.Tensor:
31
+ if src.ndim == 3:
32
+ shape = tgt.shape[-2:]
33
+ else:
34
+ shape = src.shape[-2:]
35
+
36
+ nd = shape[0] * shape[1]
37
+
38
+ if src.ndim == 3:
39
+ src = src[:, -nd:].reshape(src.shape[0], src.shape[2], *shape)
40
+
41
+ if tgt.ndim == 3:
42
+ tgt_pre = tgt[:, :-nd]
43
+ tgt = tgt[:, -nd:].reshape(tgt.shape[0], tgt.shape[2], *shape)
44
+ else:
45
+ tgt_pre = None
46
+
47
+ pred = self.fwd(src)
48
+
49
+ if self.gated:
50
+ g, pred = torch.chunk(pred, 2, dim=1)
51
+
52
+ g = F.sigmoid(g)
53
+
54
+ pred = g * pred
55
+
56
+ tgt = tgt + pred
57
+
58
+ if tgt_pre is not None:
59
+ tgt = rearrange(tgt, 'b c h w -> b (h w) c')
60
+ tgt = torch.cat([tgt_pre, tgt], dim=1)
61
+
62
+ return tgt
63
+
64
+
65
+ class AttnDownsample(nn.Module):
66
+ def __init__(self, dim: int, window_size: int, num_heads: int = 16):
67
+ super().__init__()
68
+ self.q = nn.Parameter(torch.randn(1, num_heads, 1, dim // num_heads) * 0.01)
69
+ self.kv = nn.Linear(dim, dim * 2)
70
+ self.proj = nn.Linear(dim, dim)
71
+ self.window_size = window_size
72
+ self.num_heads = num_heads
73
+ self.head_dim = dim // num_heads
74
+ self.scale = self.head_dim ** -0.5
75
+
76
+ def forward(self, x: torch.Tensor, twod_shape: Tuple[int, int]) -> torch.Tensor:
77
+ ntok = twod_shape[0] * twod_shape[1]
78
+ x_pre = x[:, :-ntok]
79
+
80
+ B = x.shape[0]
81
+ ds_hw = tuple(s // self.window_size for s in twod_shape)
82
+
83
+ x_spat = rearrange(
84
+ x[:, -ntok:],
85
+ 'b (h d1 w d2) c -> (b h w) (d1 d2) c',
86
+ h=ds_hw[0], w=ds_hw[1],
87
+ d1=self.window_size, d2=self.window_size,
88
+ )
89
+
90
+ B, N, C = x_spat.shape
91
+
92
+ k, v = self.kv(x_spat).reshape(B, N, 2, self.num_heads, self.head_dim).permute(2, 0, 3, 1, 4)
93
+
94
+ q = (self.q * self.scale).expand(B, -1, -1, -1)
95
+ attn = q @ k.transpose(-2, -1)
96
+ attn = F.softmax(attn, dim=-1)
97
+ x = attn @ v
98
+
99
+ x = x.transpose(1, 2).reshape(B, C)
100
+ x = self.proj(x)
101
+
102
+ x = rearrange(x, '(b h w) c -> b (h w) c', b=x_pre.shape[0], h=ds_hw[0], w=ds_hw[1])
103
+
104
+ x = torch.cat([x_pre, x], dim=1)
105
+ return x
106
+
107
+
108
+ class HybridModel(nn.Module):
109
+ def __init__(self, vit: tvit.VisionTransformer, conv: tconv.ConvNeXt, pretrained: bool = False,
110
+ concatenate: bool = False, **kwargs):
111
+ super().__init__()
112
+ self.conv = conv
113
+ self.vit = vit
114
+ self.concatenate = concatenate
115
+
116
+ conv.stages = nn.ModuleList(conv.stages)
117
+ vit.blocks = nn.ModuleList(vit.blocks)
118
+
119
+ self._half_vit_idx = len(vit.blocks) // 2 + 1
120
+
121
+ self._half_conv_idx = None
122
+ x = torch.empty(1, 3, 256, 256)
123
+ x = self.conv.stem(x)
124
+ for i in range(len(conv.stages)):
125
+ x = conv.stages[i](x)
126
+ if self._half_conv_idx is None and x.shape[-2:] == (16, 16):
127
+ self._half_conv_idx = i + 1
128
+ half_conv_dim = x.shape[1]
129
+ final_conv_dim = x.shape[1]
130
+
131
+ self.vit_to_conv_fusion = Fuser(vit.embed_dim, half_conv_dim)
132
+ self.conv_to_vit_fusion = Fuser(half_conv_dim, vit.embed_dim)
133
+ self.vit_ds = AttnDownsample(vit.embed_dim, window_size=2)
134
+
135
+ embed_dim = vit.embed_dim + (final_conv_dim if concatenate else 0)
136
+ if not concatenate:
137
+ self.final_fuse = Fuser(final_conv_dim, vit.embed_dim, gated=False)
138
+ self.final_block = tvit.Block(embed_dim, num_heads=16)
139
+
140
+ self.embed_dim = embed_dim
141
+
142
+ @property
143
+ def patch_size(self):
144
+ return 32
145
+
146
+ @property
147
+ def no_fsdp_wrap_types(self):
148
+ return {tvit.VisionTransformer, tconv.ConvNeXt}
149
+
150
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
151
+ return self.forward_features(x)
152
+
153
+ def forward_features(self, x: torch.Tensor) -> torch.Tensor:
154
+ y_vit = self.vit.patch_generator(x)
155
+
156
+ for i in range(self._half_vit_idx):
157
+ y_vit = self.vit.blocks[i](y_vit)
158
+
159
+ y_conv = self.conv.stem(x)
160
+ for i in range(self._half_conv_idx):
161
+ y_conv = self.conv.stages[i](y_conv)
162
+
163
+ y_vit, y_conv = self.conv_to_vit_fusion(y_conv, y_vit), self.vit_to_conv_fusion(y_vit, y_conv)
164
+
165
+ y_vit = self.vit_ds(y_vit, y_conv.shape[-2:])
166
+
167
+ for i in range(self._half_vit_idx, len(self.vit.blocks)):
168
+ y_vit = self.vit.blocks[i](y_vit)
169
+
170
+ for i in range(self._half_conv_idx, len(self.conv.stages)):
171
+ y_conv = self.conv.stages[i](y_conv)
172
+
173
+ if self.concatenate:
174
+ y_conv = rearrange(y_conv, 'b c h w -> b (h w) c')
175
+ # Average pool across the board, and replicate for each cls/register token
176
+ conv_summary = y_conv.mean(dim=1, keepdim=True).expand(-1, self.vit.patch_generator.num_cls_patches, -1)
177
+ y_conv = torch.cat([conv_summary, y_conv], dim=1)
178
+ y = torch.cat([y_vit, y_conv], dim=2)
179
+ else:
180
+ y = self.final_fuse(y_conv, y_vit)
181
+ y = self.final_block(y)
182
+
183
+ summary = y[:, :self.vit.patch_generator.num_cls_tokens]
184
+ features = y[:, self.vit.patch_generator.num_cls_patches:]
185
+
186
+ return summary, features
187
+
188
+
189
+ @register_model
190
+ def hybrid_base(pretrained=False, concatenate: bool = False, weight_init: str = 'skip', **kwargs):
191
+ cfg = dict(num_classes=0, **kwargs)
192
+ conv = tconv.convnextv2_base(pretrained=pretrained, **cfg)
193
+ vit = tvit.vit_base_patch16_224(pretrained=pretrained, weight_init=weight_init, **cfg)
194
+
195
+ return HybridModel(vit, conv, pretrained, concatenate=concatenate)
196
+
197
+
198
+ @register_model
199
+ def hybrid_large(pretrained=False, concatenate: bool = False, weight_init: str = 'skip', **kwargs):
200
+ cfg = dict(num_classes=0, **kwargs)
201
+ conv = tconv.convnextv2_large(pretrained=pretrained, **cfg)
202
+ vit = tvit.vit_large_patch16_224(pretrained=pretrained, weight_init=weight_init, **cfg)
203
+
204
+ return HybridModel(vit, conv, pretrained, concatenate=concatenate)
205
+
206
+
207
+ @register_model
208
+ def hybrid_huge(pretrained=False, concatenate: bool = False, weight_init: str = 'skip', **kwargs):
209
+ cfg = dict(num_classes=0, **kwargs)
210
+ conv = tconv.convnextv2_huge(pretrained=pretrained, **cfg)
211
+ vit = et.vit_huge_patch16_224(pretrained=pretrained, weight_init=weight_init, **cfg)
212
+
213
+ return HybridModel(vit, conv, pretrained, concatenate=concatenate)
enable_cpe_support.py ADDED
@@ -0,0 +1,170 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) 2023-2024, NVIDIA CORPORATION. All rights reserved.
2
+ #
3
+ # NVIDIA CORPORATION and its licensors retain all intellectual property
4
+ # and proprietary rights in and to this software, related documentation
5
+ # and any modifications thereto. Any use, reproduction, disclosure or
6
+ # distribution of this software and related documentation without an express
7
+ # license agreement from NVIDIA CORPORATION is strictly prohibited.
8
+
9
+ from typing import List, Optional, Set, Tuple, Union
10
+ from types import MethodType
11
+
12
+ import torch
13
+ from torch import nn
14
+
15
+ from timm.models import VisionTransformer, checkpoint_seq
16
+
17
+ from .feature_normalizer import IntermediateFeatureNormalizerBase, NullIntermediateFeatureNormalizer
18
+
19
+ from .extra_models import DinoWrapper
20
+ from .vit_patch_generator import ViTPatchGenerator
21
+ from .forward_intermediates import forward_intermediates
22
+ from .dual_hybrid_vit import HybridModel
23
+
24
+
25
+ def _forward_cpe(self: VisionTransformer, x: torch.Tensor) -> torch.Tensor:
26
+ x = self.patch_generator(x)
27
+ if getattr(self, 'grad_checkpointing', False) and not torch.jit.is_scripting():
28
+ x = checkpoint_seq(self.blocks, x)
29
+ else:
30
+ x = self.blocks(x)
31
+ x = self.norm(x)
32
+ return x
33
+
34
+
35
+ def _take_indices(
36
+ num_blocks: int,
37
+ n: Optional[Union[int, List[int], Tuple[int]]],
38
+ ) -> Tuple[Set[int], int]:
39
+ if isinstance(n, int):
40
+ assert n >= 0
41
+ take_indices = {x for x in range(num_blocks - n, num_blocks)}
42
+ else:
43
+ take_indices = {num_blocks + idx if idx < 0 else idx for idx in n}
44
+ return take_indices, max(take_indices)
45
+
46
+
47
+ def _forward_intermediates_cpe(
48
+ self,
49
+ x: torch.Tensor,
50
+ norm: bool = False,
51
+ **kwargs,
52
+ ) -> Union[List[torch.Tensor], Tuple[torch.Tensor, List[torch.Tensor]]]:
53
+ return forward_intermediates(
54
+ self,
55
+ patch_extractor=self.patch_generator,
56
+ num_summary_tokens=self.patch_generator.num_skip,
57
+ num_cls_tokens=self.patch_generator.num_cls_tokens,
58
+ norm=self.norm if norm else lambda y: y,
59
+ x=x,
60
+ **kwargs,
61
+ )
62
+
63
+
64
+ def _forward_cpe_dinov2(self: DinoWrapper, x: torch.Tensor) -> torch.Tensor:
65
+ y = _forward_cpe(self.inner, x)
66
+
67
+ return y[:, 0], y[:, self.num_summary_tokens:]
68
+
69
+
70
+ def _forward_intermediates_cpe_dinov2(self: DinoWrapper, *args, **kwargs):
71
+ return _forward_intermediates_cpe(self.inner, *args, **kwargs)
72
+
73
+
74
+ def _enable_cpe_for_timm_vit(model: VisionTransformer,
75
+ max_img_size: Union[int, Tuple[int, int]] = 1024,
76
+ num_cls_tokens: int = 1,
77
+ pos_dropout: float = 0.1,
78
+ register_multiple: int = Optional[None],
79
+ num_registers: int = Optional[None],
80
+ ):
81
+ if not isinstance(model, VisionTransformer):
82
+ raise ValueError("CPE only support for VisionTransformer models!")
83
+
84
+ patch_size = model.patch_embed.patch_size[0]
85
+ embed_dim = model.embed_dim
86
+ input_dims = model.patch_embed.img_size
87
+ normalize_patches = not isinstance(model.patch_embed.norm, nn.Identity)
88
+ cls_token = model.cls_token is not None
89
+
90
+ max_img_size = int(round(max_img_size / patch_size) * patch_size)
91
+
92
+ patch_generator = ViTPatchGenerator(
93
+ patch_size=patch_size,
94
+ embed_dim=embed_dim,
95
+ input_dims=input_dims,
96
+ normalize_patches=normalize_patches,
97
+ cls_token=cls_token,
98
+ max_input_dims=max_img_size,
99
+ pos_dropout=pos_dropout,
100
+ num_cls_tokens=num_cls_tokens,
101
+ register_multiple=register_multiple,
102
+ num_registers=num_registers,
103
+ )
104
+
105
+ model.patch_generator = patch_generator
106
+ model.patch_embed = None
107
+ model.cls_token = None
108
+ model.pos_embed = None
109
+ model.pos_drop = None
110
+ model.patch_size = patch_size
111
+ model.num_cls_tokens = num_cls_tokens
112
+ model.num_registers = patch_generator.num_registers
113
+
114
+ model.forward_features = MethodType(_forward_cpe, model)
115
+ model.forward_intermediates = MethodType(_forward_intermediates_cpe, model)
116
+
117
+
118
+ def _enable_cpe_for_dv2_reg_vit(model: DinoWrapper,
119
+ max_img_size: Union[int, Tuple[int, int]] = 1024,
120
+ num_cls_tokens: int = 1,
121
+ pos_dropout: float = 0.1,
122
+ register_multiple: int = Optional[None],
123
+ num_registers: int = Optional[None],
124
+ ):
125
+ patch_size = model.patch_size
126
+ embed_dim = model.embed_dim
127
+ input_dims = model.inner.patch_embed.patches_resolution
128
+ normalize_patches = not isinstance(model.inner.patch_embed.norm, nn.Identity)
129
+ cls_token = True
130
+
131
+ max_img_size = int(round(max_img_size / patch_size) * patch_size)
132
+
133
+ patch_generator = ViTPatchGenerator(
134
+ patch_size=patch_size,
135
+ embed_dim=embed_dim,
136
+ input_dims=input_dims,
137
+ normalize_patches=normalize_patches,
138
+ cls_token=cls_token,
139
+ max_input_dims=max_img_size,
140
+ pos_dropout=pos_dropout,
141
+ num_cls_tokens=num_cls_tokens,
142
+ register_multiple=register_multiple,
143
+ num_registers=num_registers,
144
+ patch_bias=True,
145
+ )
146
+
147
+ inner = model.inner
148
+ inner.patch_generator = patch_generator
149
+ inner.patch_embed = None
150
+ inner.cls_token = None
151
+ inner.pos_embed = None
152
+ inner.register_tokens = None
153
+ inner.patch_size = patch_size
154
+
155
+ model.forward_features = MethodType(_forward_cpe_dinov2, model)
156
+ model.forward_intermediates = MethodType(_forward_intermediates_cpe_dinov2, model)
157
+
158
+
159
+ def enable_cpe(model: nn.Module,
160
+ *args,
161
+ **kwargs,
162
+ ):
163
+ if isinstance(model, VisionTransformer):
164
+ _enable_cpe_for_timm_vit(model, *args, **kwargs)
165
+ elif isinstance(model, DinoWrapper):
166
+ _enable_cpe_for_dv2_reg_vit(model, *args, **kwargs)
167
+ elif isinstance(model, HybridModel):
168
+ _enable_cpe_for_timm_vit(model.vit, *args, **kwargs)
169
+ else:
170
+ raise ValueError(f'CPE not supported for this model type: {type(model)}')
enable_spectral_reparam.py ADDED
@@ -0,0 +1,277 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) 2023-2024, NVIDIA CORPORATION. All rights reserved.
2
+ #
3
+ # NVIDIA CORPORATION and its licensors retain all intellectual property
4
+ # and proprietary rights in and to this software, related documentation
5
+ # and any modifications thereto. Any use, reproduction, disclosure or
6
+ # distribution of this software and related documentation without an express
7
+ # license agreement from NVIDIA CORPORATION is strictly prohibited.
8
+
9
+ from logging import getLogger
10
+ import math
11
+ import os
12
+ from typing import Dict, List, Optional, Union, Tuple
13
+ from types import MethodType
14
+
15
+ import torch
16
+ from torch import nn
17
+ from torch.nn import functional as F
18
+ from torch.nn.utils import parametrize
19
+ from torch.nn.utils.parametrizations import _SpectralNorm
20
+
21
+ from timm.models.vision_transformer import Attention, Mlp
22
+
23
+ _EPS = 1e-5
24
+
25
+
26
+ class _SNReweight(_SpectralNorm):
27
+ def __init__(self, weight: torch.Tensor, *args, init_norm_to_current: bool = False, alpha: float = 0.05, version: int = 2, **kwargs):
28
+ super().__init__(weight, *args, **kwargs)
29
+
30
+ self.alpha = alpha
31
+ self.version = version
32
+ self.register_buffer('_sn_version', torch.tensor(version))
33
+
34
+ if init_norm_to_current:
35
+ # This will set the numerator to match the denominator, which should preserve the original values
36
+ init_scale = self._get_sigma(weight, n_power_iterations=20).item()
37
+ else:
38
+ init_scale = 1.0
39
+
40
+ if version == 1:
41
+ init_value = init_scale
42
+ elif version == 2:
43
+ t = init_scale - alpha
44
+ if t < _EPS:
45
+ getLogger("spectral_reparam").warn(f'The initialized spectral norm {init_scale} is too small to be represented. Setting to {_EPS} instead.')
46
+ t = _EPS
47
+
48
+ init_value = math.log(math.exp(t) - 1)
49
+ else:
50
+ raise ValueError(f'Unsupported version: {version}')
51
+
52
+ # Make 2D so that weight decay gets applied
53
+ self.scale = nn.Parameter(torch.tensor([[init_value]], dtype=torch.float32, device=weight.device))
54
+
55
+ # Re-implementing this because we need to make division by sigma safe
56
+ def _get_sigma(self, weight: torch.Tensor, n_power_iterations: int = None) -> torch.Tensor:
57
+ if not n_power_iterations:
58
+ n_power_iterations = self.n_power_iterations
59
+ if weight.ndim == 1:
60
+ # Faster and more exact path, no need to approximate anything
61
+ sigma = weight.norm()
62
+ else:
63
+ weight_mat = self._reshape_weight_to_matrix(weight)
64
+ if self.training:
65
+ self._power_method(weight_mat, n_power_iterations)
66
+ # See above on why we need to clone
67
+ u = self._u.clone(memory_format=torch.contiguous_format)
68
+ v = self._v.clone(memory_format=torch.contiguous_format)
69
+ # The proper way of computing this should be through F.bilinear, but
70
+ # it seems to have some efficiency issues:
71
+ # https://github.com/pytorch/pytorch/issues/58093
72
+ sigma = torch.dot(u, torch.mv(weight_mat, v))
73
+
74
+ return sigma + self.eps
75
+
76
+ def forward(self, weight: torch.Tensor, *args, **kwargs):
77
+ dtype = weight.dtype
78
+ sigma = self._get_sigma(weight, *args, **kwargs)
79
+
80
+ if self.version == 1:
81
+ scale = self.scale
82
+ elif self.version == 2:
83
+ scale = F.softplus(self.scale) + self.alpha
84
+ else:
85
+ raise ValueError(f'Unsupported version: {self.version}')
86
+
87
+ scale = scale.float() / sigma.float()
88
+
89
+ y = weight * scale
90
+
91
+ if dtype in (torch.float16, torch.bfloat16):
92
+ y = y.to(dtype)
93
+ return y
94
+
95
+ def _load_from_state_dict(self, state_dict, prefix, local_metadata, strict, missing_keys, unexpected_keys, error_msgs):
96
+ version_key = f'{prefix}_sn_version'
97
+ if version_key not in state_dict:
98
+ self.version = 1
99
+ state_dict[version_key] = torch.tensor(1)
100
+ return super()._load_from_state_dict(state_dict, prefix, local_metadata, strict, missing_keys, unexpected_keys, error_msgs)
101
+
102
+
103
+ class _ChunkedSNReweight(nn.Module):
104
+ def __init__(self, weight: torch.Tensor, num_chunks: int, *args, init_norm_to_current: bool = False, **kwargs):
105
+ super().__init__()
106
+
107
+ self.num_chunks = num_chunks
108
+ parts = weight.split(weight.shape[0] // num_chunks, dim=0)
109
+
110
+ self.parts = nn.ModuleList([
111
+ _SNReweight(p, *args, init_norm_to_current=init_norm_to_current, **kwargs)
112
+ for p in parts
113
+ ])
114
+
115
+ def forward(self, weight: torch.Tensor, *args, **kwargs):
116
+ parts = weight.split(weight.shape[0] // self.num_chunks, dim=0)
117
+
118
+ parts = [
119
+ fn(p)
120
+ for fn, p in zip(self.parts, parts)
121
+ ]
122
+
123
+ return torch.cat(parts, dim=0)
124
+
125
+
126
+ class _AttnSNReweight(_ChunkedSNReweight):
127
+ def __init__(self, weight: torch.Tensor, *args, init_norm_to_current: bool = False, renorm_values: bool = False, **kwargs):
128
+ super().__init__(weight, 3, *args, init_norm_to_current=init_norm_to_current, **kwargs)
129
+
130
+ if not renorm_values:
131
+ self.parts[2] = nn.Identity()
132
+
133
+
134
+ def enable_spectral_reparam(model: Union[nn.Module, List[nn.Module]],
135
+ n_power_iterations: int = 1,
136
+ eps: float = 1e-6,
137
+ init_norm_to_current: bool = False,
138
+ renorm_values: bool = True,
139
+ renorm_mlp: bool = True,
140
+ state_dict_guidance: Optional[Dict[str, torch.Tensor]] = None):
141
+ if isinstance(model, (list, tuple)):
142
+ for i, sub in enumerate(model):
143
+ sub_sd = state_dict_guidance[i] if isinstance(state_dict_guidance, (list, tuple)) else state_dict_guidance
144
+ enable_spectral_reparam(sub, n_power_iterations=n_power_iterations, eps=eps,
145
+ init_norm_to_current=init_norm_to_current, renorm_values=renorm_values,
146
+ renorm_mlp=renorm_mlp, state_dict_guidance=sub_sd)
147
+ return
148
+
149
+ print('Enabling spectral reparametrization')
150
+ args = dict(n_power_iterations=n_power_iterations, dim=0, eps=eps, init_norm_to_current=init_norm_to_current)
151
+ visited_prefixes = set()
152
+
153
+ def is_guidance_parametrized(name: str):
154
+ if state_dict_guidance is None:
155
+ return True
156
+
157
+ p_name = f'{name}.parametrizations'
158
+ is_prm = any(k for k in state_dict_guidance if k.startswith(p_name) and k.endswith('_sn_version'))
159
+ return is_prm
160
+
161
+ def parametrize_linear(linear: nn.Linear):
162
+ parametrize.register_parametrization(
163
+ linear,
164
+ 'weight',
165
+ _SNReweight(linear.weight, **args)
166
+ )
167
+
168
+ for name, mod in model.named_modules():
169
+ pref = '.'.join(name.split('.')[:-1])
170
+ if pref in visited_prefixes:
171
+ continue
172
+
173
+ if isinstance(mod, Attention) or name.endswith('.attn'):
174
+ if is_guidance_parametrized(f'{name}.qkv'):
175
+ parametrize.register_parametrization(
176
+ mod.qkv,
177
+ 'weight',
178
+ _AttnSNReweight(mod.qkv.weight, renorm_values=renorm_values, **args),
179
+ )
180
+ if hasattr(mod, 'proj') and is_guidance_parametrized(f'{name}.proj'):
181
+ parametrize_linear(mod.proj)
182
+ visited_prefixes.add(name)
183
+ elif name.endswith('mlp') and renorm_mlp and hasattr(mod, 'w12'):
184
+ if is_guidance_parametrized(f'{name}.w12'):
185
+ parametrize.register_parametrization(
186
+ mod.w12,
187
+ 'weight',
188
+ _ChunkedSNReweight(mod.w12.weight, num_chunks=2, **args),
189
+ )
190
+ if is_guidance_parametrized(f'{name}.w3'):
191
+ parametrize_linear(mod.w3)
192
+ visited_prefixes.add(name)
193
+ elif isinstance(mod, nn.Linear) and 'patch_generator' not in name and is_guidance_parametrized(name):
194
+ parametrize_linear(mod)
195
+
196
+
197
+ def configure_spectral_reparam_from_args(model: nn.Module, args, state_dict_guidance: Optional[Dict[str, torch.Tensor]] = None):
198
+ spectral_reparam = getattr(args, 'spectral_reparam', False)
199
+ if isinstance(spectral_reparam, bool) and spectral_reparam:
200
+ enable_spectral_reparam(model, init_norm_to_current=True, state_dict_guidance=state_dict_guidance)
201
+ elif isinstance(spectral_reparam, dict):
202
+ enable_spectral_reparam(
203
+ model,
204
+ n_power_iterations=spectral_reparam.get('n_power_iterations', 1),
205
+ eps=spectral_reparam.get('eps', 1e-12),
206
+ init_norm_to_current=True,
207
+ state_dict_guidance=state_dict_guidance,
208
+ )
209
+
210
+
211
+ def disable_spectral_reparam(model: nn.Module):
212
+ print('Disabling spectral reparametrization')
213
+ for name, mod in model.named_modules():
214
+ if parametrize.is_parametrized(mod):
215
+ parametrize.remove_parametrizations(mod, 'weight')
216
+ pass
217
+
218
+
219
+
220
+ if __name__ == '__main__':
221
+ import argparse
222
+ from . import radio_model as create_model
223
+
224
+ parser = argparse.ArgumentParser(description='Remove parametrization from state dict')
225
+ parser.add_argument('--checkpoint', type=str, required=True, help='The checkpoint to load')
226
+ parser.add_argument('--output', type=str, default='', help='Where to store the checkpoint')
227
+ parser.add_argument('--release', default=False, action='store_true', help='Prune extraneous checkpoint fields')
228
+ parser.add_argument('--strict', default=False, action='store_true', help='Strictly load the state dict')
229
+
230
+ args = parser.parse_args()
231
+
232
+ if not args.output:
233
+ chk_dir, chk_name = os.path.split(args.checkpoint)
234
+ args.output = os.path.join(chk_dir, f'clean_{chk_name}')
235
+ print(f'Set output to "{args.output}"')
236
+
237
+ chk = torch.load(args.checkpoint, map_location='cpu', mmap=True)
238
+
239
+ model = create_model.create_model_from_args(chk['args'])
240
+
241
+ key = 'base_model.'
242
+ mod_state = dict()
243
+ extra_state = dict()
244
+ for k, v in chk['state_dict'].items():
245
+ if k.startswith(key):
246
+ mod_state[k[len(key):]] = v
247
+ else:
248
+ extra_state[k] = v
249
+
250
+ chk_load_info = model.load_state_dict(mod_state, strict=args.strict)
251
+ if chk_load_info.unexpected_keys or chk_load_info.missing_keys:
252
+ print(chk_load_info)
253
+
254
+ if chk['args'].spectral_reparam:
255
+ disable_spectral_reparam(model)
256
+
257
+ if hasattr(chk['args'], 'dtype'):
258
+ model.to(dtype=chk['args'].dtype)
259
+
260
+ mod_state = model.state_dict()
261
+ final_state = dict()
262
+ final_state.update({f'{key}{k}': v for k, v in mod_state.items()})
263
+ final_state.update(extra_state)
264
+
265
+ chk['state_dict'] = final_state
266
+ chk['args'].spectral_reparam = False
267
+
268
+ if args.release:
269
+ chk = {
270
+ 'arch': chk['arch'],
271
+ 'epoch': chk['epoch'],
272
+ 'state_dict': chk['state_dict'],
273
+ 'args': chk['args'],
274
+ }
275
+
276
+ torch.save(chk, args.output)
277
+ pass
eradio_model.py ADDED
@@ -0,0 +1,1392 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+
3
+ # Copyright (c) 2024, NVIDIA CORPORATION. All rights reserved.
4
+ #
5
+ # NVIDIA CORPORATION and its licensors retain all intellectual property
6
+ # and proprietary rights in and to this software, related documentation
7
+ # and any modifications thereto. Any use, reproduction, disclosure or
8
+ # distribution of this software and related documentation without an express
9
+ # license agreement from NVIDIA CORPORATION is strictly prohibited.
10
+
11
+ # E-RADIO model from
12
+ # Mike Ranzinger, Greg Heinrich, Jan Kautz, and Pavlo Molchanov. "AM-RADIO: Agglomerative Model--Reduce All Domains Into One." arXiv preprint arXiv:2312.06709 (2023).
13
+
14
+ # based on FasterViT, Swin Transformer, YOLOv8
15
+
16
+ # FasterViT:
17
+ # Ali Hatamizadeh, Greg Heinrich, Hongxu Yin, Andrew Tao, Jose M. Alvarez, Jan Kautz, and Pavlo Molchanov. "FasterViT: Fast Vision Transformers with Hierarchical Attention." arXiv preprint arXiv:2306.06189 (2023).
18
+
19
+ import timm
20
+ import torch
21
+ import torch.nn as nn
22
+ from timm.models.registry import register_model
23
+
24
+ from timm.models.layers import trunc_normal_, DropPath, LayerNorm2d
25
+ import numpy as np
26
+ import torch.nn.functional as F
27
+ import math
28
+ import warnings
29
+
30
+ #######################
31
+ ## Codebase from YOLOv8
32
+ ## BEGINNING
33
+ #######################
34
+
35
+ class C2f(nn.Module):
36
+ """Faster Implementation of CSP Bottleneck with 2 convolutions."""
37
+ """From YOLOv8 codebase"""
38
+ def __init__(self, c1, c2, n=1, shortcut=False, g=1, e=0.5, drop_path=None): # ch_in, ch_out, number, shortcut, groups, expansion
39
+ super().__init__()
40
+ if drop_path is None:
41
+ drop_path = [0.0] * n
42
+
43
+ self.c = int(c2 * e) # hidden channels
44
+ self.cv1 = Conv(c1, 2 * self.c, 1, 1)
45
+ self.cv2 = Conv((2 + n) * self.c, c2, 1) # optional act=FReLU(c2)
46
+ self.m = nn.ModuleList(Bottleneck(self.c, self.c, shortcut, g, k=((3, 3), (3, 3)), e=1.0, drop_path=drop_path[i]) for i in range(n))
47
+
48
+ def forward(self, x):
49
+ """Forward pass through C2f layer."""
50
+ y = list(self.cv1(x).chunk(2, 1))
51
+ y.extend(m(y[-1]) for m in self.m)
52
+ return self.cv2(torch.cat(y, 1))
53
+
54
+ def forward_split(self, x):
55
+ """Forward pass using split() instead of chunk()."""
56
+ y = list(self.cv1(x).split((self.c, self.c), 1))
57
+ y.extend(m(y[-1]) for m in self.m)
58
+ return self.cv2(torch.cat(y, 1))
59
+
60
+ class Bottleneck(nn.Module):
61
+ """Standard bottleneck."""
62
+
63
+ def __init__(self, c1, c2, shortcut=True, g=1, k=(3, 3), e=0.5, drop_path=0.0): # ch_in, ch_out, shortcut, groups, kernels, expand
64
+ super().__init__()
65
+ c_ = int(c2 * e) # hidden channels
66
+ self.cv1 = Conv(c1, c_, k[0], 1)
67
+ self.cv2 = Conv(c_, c2, k[1], 1, g=g)
68
+ self.add = shortcut and c1 == c2
69
+ self.drop_path1 = DropPath(drop_path) if drop_path > 0. else nn.Identity()
70
+
71
+ def forward(self, x):
72
+ """'forward()' applies the YOLOv5 FPN to input data."""
73
+ return x + self.drop_path1(self.cv2(self.cv1(x))) if self.add else self.cv2(self.cv1(x))
74
+
75
+
76
+ class Conv(nn.Module):
77
+ """Modified to support layer fusion"""
78
+ default_act = nn.SiLU() # default activation
79
+
80
+ def __init__(self, a, b, kernel_size=1, stride=1, padding=None, g=1, dilation=1, bn_weight_init=1, bias=False, act=True):
81
+ super().__init__()
82
+
83
+ self.conv = torch.nn.Conv2d(a, b, kernel_size, stride, autopad(kernel_size, padding, dilation), dilation, g, bias=False)
84
+ if 1:
85
+ self.bn = torch.nn.BatchNorm2d(b)
86
+ torch.nn.init.constant_(self.bn.weight, bn_weight_init)
87
+ torch.nn.init.constant_(self.bn.bias, 0)
88
+ self.act = self.default_act if act is True else act if isinstance(act, nn.Module) else nn.Identity()
89
+
90
+
91
+ def forward(self,x):
92
+ x = self.conv(x)
93
+ x = self.bn(x)
94
+ x = self.act(x)
95
+ return x
96
+
97
+ @torch.no_grad()
98
+ def switch_to_deploy(self):
99
+ # return 1
100
+ if not isinstance(self.bn, nn.Identity):
101
+ c, bn = self.conv, self.bn
102
+ w = bn.weight / (bn.running_var + bn.eps) ** 0.5
103
+ w = c.weight * w[:, None, None, None]
104
+ b = bn.bias - bn.running_mean * bn.weight / \
105
+ (bn.running_var + bn.eps)**0.5
106
+
107
+ self.conv.weight.data.copy_(w)
108
+ self.conv.bias = nn.Parameter(b)
109
+
110
+ self.bn = nn.Identity()
111
+
112
+ def autopad(k, p=None, d=1): # kernel, padding, dilation
113
+ """Pad to 'same' shape outputs."""
114
+ if d > 1:
115
+ k = d * (k - 1) + 1 if isinstance(k, int) else [d * (x - 1) + 1 for x in k] # actual kernel-size
116
+ if p is None:
117
+ p = k // 2 if isinstance(k, int) else [x // 2 for x in k] # auto-pad
118
+ return p
119
+
120
+
121
+ #######################
122
+ ## Codebase from YOLOv8
123
+ ## END
124
+ #######################
125
+
126
+ def pixel_unshuffle(data, factor=2):
127
+ # performs nn.PixelShuffle(factor) in reverse, torch has some bug for ONNX and TRT, so doing it manually
128
+ B, C, H, W = data.shape
129
+ return data.view(B, C, factor, H//factor, factor, W//factor).permute(0,1,2,4,3,5).reshape(B, -1, H//factor, W//factor)
130
+
131
+ class SwiGLU(nn.Module):
132
+ # should be more advanced, but doesnt improve results so far
133
+ def forward(self, x):
134
+ x, gate = x.chunk(2, dim=-1)
135
+ return F.silu(gate) * x
136
+
137
+
138
+ def window_partition(x, window_size):
139
+ """
140
+ Function for partitioning image into windows and later do windowed attention
141
+ Args:
142
+ x: (B, C, H, W)
143
+ window_size: window size
144
+ Returns:
145
+ windows - local window features (num_windows*B, window_size*window_size, C)
146
+ (Hp, Wp) - the size of the padded image
147
+ """
148
+ B, C, H, W = x.shape
149
+
150
+ if window_size == 0 or (window_size==H and window_size==W):
151
+ windows = x.flatten(2).transpose(1, 2)
152
+ Hp, Wp = H, W
153
+ else:
154
+ pad_h = (window_size - H % window_size) % window_size
155
+ pad_w = (window_size - W % window_size) % window_size
156
+ if pad_h > 0 or pad_w > 0:
157
+ x = F.pad(x, (0, pad_w, 0, pad_h), mode="reflect")
158
+ Hp, Wp = H + pad_h, W + pad_w
159
+
160
+ x = x.view(B, C, Hp // window_size, window_size, Wp // window_size, window_size)
161
+ windows = x.permute(0, 2, 4, 3, 5, 1).reshape(-1, window_size*window_size, C)
162
+
163
+ return windows, (Hp, Wp)
164
+
165
+ class Conv2d_BN(nn.Module):
166
+ '''
167
+ Conv2d + BN layer with folding capability to speed up inference
168
+ Can be merged with Conv() function with additional arguments
169
+ '''
170
+ def __init__(self, a, b, kernel_size=1, stride=1, padding=0, dilation=1, groups=1, bn_weight_init=1, bias=False):
171
+ super().__init__()
172
+ self.conv = torch.nn.Conv2d(a, b, kernel_size, stride, padding, dilation, groups, bias=False)
173
+ if 1:
174
+ self.bn = torch.nn.BatchNorm2d(b)
175
+ torch.nn.init.constant_(self.bn.weight, bn_weight_init)
176
+ torch.nn.init.constant_(self.bn.bias, 0)
177
+
178
+ def forward(self,x):
179
+ x = self.conv(x)
180
+ x = self.bn(x)
181
+ return x
182
+
183
+ @torch.no_grad()
184
+ def switch_to_deploy(self):
185
+ if not isinstance(self.bn, nn.Identity):
186
+ c, bn = self.conv, self.bn
187
+ w = bn.weight / (bn.running_var + bn.eps) ** 0.5
188
+ w = c.weight * w[:, None, None, None]
189
+ b = bn.bias - bn.running_mean * bn.weight / \
190
+ (bn.running_var + bn.eps)**0.5
191
+ self.conv.weight.data.copy_(w)
192
+ self.conv.bias = nn.Parameter(b)
193
+ self.bn = nn.Identity()
194
+
195
+
196
+
197
+ def window_reverse(windows, window_size, H, W, pad_hw):
198
+ """
199
+ Windows to the full feature map
200
+ Args:
201
+ windows: local window features (num_windows*B, window_size, window_size, C)
202
+ window_size: Window size
203
+ H: Height of image
204
+ W: Width of image
205
+ pad_w - a tuple of image passing used in windowing step
206
+ Returns:
207
+ x: (B, C, H, W)
208
+
209
+ """
210
+ # print(f"window_reverse, windows.shape {windows.shape}")
211
+ Hp, Wp = pad_hw
212
+ if window_size == 0 or (window_size==H and window_size==W):
213
+ B = int(windows.shape[0] / (Hp * Wp / window_size / window_size))
214
+ x = windows.transpose(1, 2).view(B, -1, H, W)
215
+ else:
216
+ B = int(windows.shape[0] / (Hp * Wp / window_size / window_size))
217
+ x = windows.view(B, Hp // window_size, Wp // window_size, window_size, window_size, -1)
218
+ x = x.permute(0, 5, 1, 3, 2, 4).reshape(B,windows.shape[2], Hp, Wp)
219
+
220
+ if Hp > H or Wp > W:
221
+ x = x[:, :, :H, :W, ].contiguous()
222
+
223
+ return x
224
+
225
+
226
+
227
+ class PosEmbMLPSwinv2D(nn.Module):
228
+ """
229
+ 2D positional embedding from Swin Transformer v2
230
+ Added functionality to store the positional embedding in the model and not recompute it every time
231
+ """
232
+ def __init__(
233
+ self, window_size, pretrained_window_size, num_heads, seq_length, no_log=False, cpb_mlp_hidden=512,
234
+ ):
235
+ super().__init__()
236
+ self.window_size = window_size
237
+ self.num_heads = num_heads
238
+ # mlp to generate continuous relative position bias
239
+ self.cpb_mlp = nn.Sequential(
240
+ nn.Linear(2, cpb_mlp_hidden, bias=True),
241
+ nn.ReLU(inplace=True),
242
+ nn.Linear(cpb_mlp_hidden, num_heads, bias=False),
243
+ )
244
+
245
+ self.grid_exists = False
246
+ self.seq_length = seq_length
247
+ self.deploy = False
248
+ self.num_heads = num_heads
249
+ self.no_log = no_log
250
+ self.pretrained_window_size = pretrained_window_size
251
+ self.relative_bias_window_size = window_size
252
+
253
+ relative_coords_table, relative_position_index, relative_bias = self.relative_bias_initialization(window_size, num_heads,
254
+ pretrained_window_size, seq_length,
255
+ no_log)
256
+
257
+ self.register_buffer("relative_coords_table", relative_coords_table)
258
+ self.register_buffer("relative_position_index", relative_position_index)
259
+ self.register_buffer("relative_bias", relative_bias) # for EMA
260
+
261
+ def relative_bias_initialization(self, window_size, num_heads, pretrained_window_size, seq_length, no_log):
262
+ # as in separate function to support window size chage after model weights loading
263
+ relative_coords_h = torch.arange(
264
+ -(window_size[0] - 1), window_size[0], dtype=torch.float32
265
+ )
266
+ relative_coords_w = torch.arange(
267
+ -(window_size[1] - 1), window_size[1], dtype=torch.float32
268
+ )
269
+ relative_coords_table = (
270
+ torch.stack(torch.meshgrid([relative_coords_h, relative_coords_w]))
271
+ .permute(1, 2, 0)
272
+ .contiguous()
273
+ .unsqueeze(0)
274
+ ) # 1, 2*Wh-1, 2*Ww-1, 2
275
+ if pretrained_window_size[0] > 0:
276
+ relative_coords_table[:, :, :, 0] /= pretrained_window_size[0] - 1
277
+ relative_coords_table[:, :, :, 1] /= pretrained_window_size[1] - 1
278
+ else:
279
+ relative_coords_table[:, :, :, 0] /= self.window_size[0] - 1
280
+ relative_coords_table[:, :, :, 1] /= self.window_size[1] - 1
281
+
282
+ if not no_log:
283
+ relative_coords_table *= 8 # normalize to -8, 8
284
+ relative_coords_table = (
285
+ torch.sign(relative_coords_table)
286
+ * torch.log2(torch.abs(relative_coords_table) + 1.0)
287
+ / np.log2(8)
288
+ )
289
+
290
+ # get pair-wise relative position index for each token inside the window
291
+ coords_h = torch.arange(self.window_size[0])
292
+ coords_w = torch.arange(self.window_size[1])
293
+ coords = torch.stack(torch.meshgrid([coords_h, coords_w])) # 2, Wh, Ww
294
+ coords_flatten = torch.flatten(coords, 1) # 2, Wh*Ww
295
+ relative_coords = (
296
+ coords_flatten[:, :, None] - coords_flatten[:, None, :]
297
+ ) # 2, Wh*Ww, Wh*Ww
298
+ relative_coords = relative_coords.permute(
299
+ 1, 2, 0
300
+ ).contiguous() # Wh*Ww, Wh*Ww, 2
301
+ relative_coords[:, :, 0] += self.window_size[0] - 1 # shift to start from 0
302
+ relative_coords[:, :, 1] += self.window_size[1] - 1
303
+ relative_coords[:, :, 0] *= 2 * self.window_size[1] - 1
304
+ relative_position_index = relative_coords.sum(-1) # Wh*Ww, Wh*Ww
305
+
306
+ relative_bias = torch.zeros(1, num_heads, seq_length, seq_length)
307
+
308
+ self.relative_bias_window_size = window_size
309
+
310
+ return relative_coords_table, relative_position_index, relative_bias
311
+
312
+
313
+ def switch_to_deploy(self):
314
+ self.deploy = True
315
+ self.grid_exists = True
316
+
317
+ def forward(self, input_tensor):
318
+ # for efficiency, we want this forward to be folded into a single operation (sum)
319
+ # if resolution stays the same, then we dont need to recompute MLP layers
320
+
321
+ if not self.deploy or self.training:
322
+ self.grid_exists = False
323
+
324
+ #compare if all elements in self.window_size list match those in self.relative_bias_window_size
325
+ if not all([self.window_size[i] == self.relative_bias_window_size[i] for i in range(len(self.window_size))]):
326
+ relative_coords_table, relative_position_index, relative_bias = self.relative_bias_initialization(self.window_size, self.num_heads,
327
+ self.pretrained_window_size, self.seq_length,
328
+ self.no_log)
329
+
330
+ self.relative_coords_table = relative_coords_table.to(self.relative_coords_table.device)
331
+ self.relative_position_index = relative_position_index.to(self.relative_position_index.device)
332
+ self.relative_bias = relative_bias.to(self.relative_bias.device)
333
+
334
+ if self.deploy and self.grid_exists:
335
+ input_tensor = input_tensor + self.relative_bias
336
+ return input_tensor
337
+
338
+ if 1:
339
+ self.grid_exists = True
340
+
341
+ relative_position_bias_table = self.cpb_mlp(
342
+ self.relative_coords_table
343
+ ).view(-1, self.num_heads)
344
+ relative_position_bias = relative_position_bias_table[
345
+ self.relative_position_index.view(-1)
346
+ ].view(
347
+ self.window_size[0] * self.window_size[1],
348
+ self.window_size[0] * self.window_size[1],
349
+ -1,
350
+ ) # Wh*Ww,Wh*Ww,nH
351
+
352
+ relative_position_bias = relative_position_bias.permute(
353
+ 2, 0, 1
354
+ ).contiguous() # nH, Wh*Ww, Wh*Ww
355
+ relative_position_bias = 16 * torch.sigmoid(relative_position_bias)
356
+
357
+ self.relative_bias = relative_position_bias.unsqueeze(0)
358
+
359
+ input_tensor = input_tensor + self.relative_bias
360
+ return input_tensor
361
+
362
+
363
+ class GRAAttentionBlock(nn.Module):
364
+ def __init__(self, window_size, dim_in, dim_out,
365
+ num_heads, drop_path=0., qk_scale=None, qkv_bias=False,
366
+ norm_layer=nn.LayerNorm, layer_scale=None,
367
+ use_swiglu=True,
368
+ subsample_ratio=1, dim_ratio=1, conv_base=False,
369
+ do_windowing=True, multi_query=False, use_shift=0,
370
+ cpb_mlp_hidden=512, conv_groups_ratio=0):
371
+ '''
372
+ Global Resolution Attention Block , see README for details
373
+ Attention with subsampling to get a bigger receptive field for attention
374
+ conv_base - use conv2d instead of avgpool2d for downsample / upsample
375
+
376
+
377
+ '''
378
+ super().__init__()
379
+
380
+ self.shift_size=window_size//2 if use_shift else 0
381
+
382
+ self.do_windowing = do_windowing
383
+ self.subsample_ratio = subsample_ratio
384
+
385
+
386
+
387
+ if do_windowing:
388
+ if conv_base:
389
+ self.downsample_op = nn.Conv2d(dim_in, dim_out, kernel_size=subsample_ratio, stride=subsample_ratio) if subsample_ratio > 1 else nn.Identity()
390
+
391
+
392
+ self.downsample_mixer = nn.Identity()
393
+ self.upsample_mixer = nn.Identity()
394
+ self.upsample_op = nn.ConvTranspose2d(dim_in, dim_out, kernel_size=subsample_ratio, stride=subsample_ratio) if subsample_ratio > 1 else nn.Identity()
395
+ else:
396
+ self.downsample_op = nn.AvgPool2d(kernel_size=subsample_ratio, stride=subsample_ratio) if subsample_ratio > 1 else nn.Identity()
397
+ self.downsample_mixer = Conv2d_BN(dim_in, dim_out, kernel_size=1, stride=1) if subsample_ratio > 1 else nn.Identity()
398
+ self.upsample_mixer = nn.Upsample(scale_factor=subsample_ratio, mode='nearest') if subsample_ratio > 1 else nn.Identity()
399
+ self.upsample_op = Conv2d_BN(dim_in, dim_out, kernel_size=1, stride=1, padding=0, bias=False) if subsample_ratio > 1 else nn.Identity()
400
+
401
+
402
+ # in case there is no downsampling conv we want to have it separately
403
+ # will help with information propagation between windows
404
+ if subsample_ratio == 1:
405
+ # conv_groups_ratio=0
406
+ self.pre_conv = Conv2d_BN(dim_in, dim_in, kernel_size=3, stride=1, padding=1, groups=max(1,int(conv_groups_ratio*dim_in)), bias=False)
407
+ # self.pre_conv = nn.Conv2d(dim_in, dim_in, kernel_size=3, stride=1, padding=1, groups=max(1,int(conv_groups_ratio*dim_in)), bias=False)
408
+ # self.pre_conv_act = nn.ReLU6()
409
+ #for simplicity:
410
+ self.pre_conv_act = nn.Identity()
411
+ if conv_groups_ratio == -1:
412
+ self.pre_conv = nn.Identity()
413
+ self.pre_conv_act = nn.Identity()
414
+
415
+ self.window_size = window_size
416
+
417
+ self.norm1 = norm_layer(dim_in)
418
+
419
+ self.attn = WindowAttention(
420
+ dim_in,
421
+ num_heads=num_heads, qkv_bias=qkv_bias, qk_scale=qk_scale,
422
+ resolution=window_size,
423
+ seq_length=window_size**2, dim_out=dim_in, multi_query=multi_query,
424
+ shift_size=self.shift_size, cpb_mlp_hidden=cpb_mlp_hidden)
425
+
426
+ self.drop_path1 = DropPath(drop_path) if drop_path > 0. else nn.Identity()
427
+
428
+ use_layer_scale = layer_scale is not None and type(layer_scale) in [int, float]
429
+ self.gamma1 = nn.Parameter(layer_scale * torch.ones(dim_in)) if use_layer_scale else 1
430
+
431
+ ### mlp layer
432
+ mlp_ratio = 4
433
+ self.norm2 = norm_layer(dim_in)
434
+ mlp_hidden_dim = int(dim_in * mlp_ratio)
435
+
436
+ activation = nn.GELU if not use_swiglu else SwiGLU
437
+ mlp_hidden_dim = int((4 * dim_in * 1 / 2) / 64) * 64 if use_swiglu else mlp_hidden_dim
438
+
439
+ self.mlp = Mlp(in_features=dim_in, hidden_features=mlp_hidden_dim, act_layer=activation, use_swiglu=use_swiglu)
440
+
441
+ self.gamma2 = nn.Parameter(layer_scale * torch.ones(dim_in)) if layer_scale else 1
442
+ self.drop_path2=DropPath(drop_path) if drop_path > 0. else nn.Identity()
443
+
444
+
445
+ def forward(self, x):
446
+ skip_connection = x
447
+ attn_mask = None
448
+
449
+ # in case there is no downsampling conv we want to have it separately
450
+ # will help with information propagation
451
+ if self.subsample_ratio == 1:
452
+ x = self.pre_conv_act(self.pre_conv(x)) + skip_connection
453
+
454
+ if self.do_windowing:
455
+ # performing windowing if required
456
+ x = self.downsample_op(x)
457
+ x = self.downsample_mixer(x)
458
+
459
+ if self.window_size>0:
460
+ H, W = x.shape[2], x.shape[3]
461
+
462
+ if self.shift_size > 0 and H>self.window_size and W>self.window_size:
463
+ # @swin like cyclic shift, doesnt show better performance
464
+ x = torch.roll(x, shifts=(-self.shift_size, -self.shift_size), dims=(2, 3))
465
+
466
+ x, pad_hw = window_partition(x, self.window_size)
467
+
468
+ if self.shift_size > 0 and H>self.window_size and W>self.window_size:
469
+ # set atten matrix to have -100 and the top right square
470
+ # attn[:, :, :-self.shift_size, -self.shift_size:] = -100.0
471
+ # calculate attention mask for SW-MSA
472
+ # not used in final version, can be useful for some cases especially for high res
473
+ H, W = pad_hw
474
+ img_mask = torch.zeros((1, H, W, 1), device=x.device) # 1 H W 1
475
+ h_slices = (slice(0, -self.window_size),
476
+ slice(-self.window_size, -self.shift_size),
477
+ slice(-self.shift_size, None))
478
+ w_slices = (slice(0, -self.window_size),
479
+ slice(-self.window_size, -self.shift_size),
480
+ slice(-self.shift_size, None))
481
+ cnt = 0
482
+ for h in h_slices:
483
+ for w in w_slices:
484
+ img_mask[:, h, w, :] = cnt
485
+ cnt += 1
486
+ img_mask = img_mask.transpose(1,2).transpose(1,3)
487
+ mask_windows = window_partition(img_mask, self.window_size) # nW, window_size, window_size, 1
488
+
489
+ mask_windows = mask_windows[0].view(-1, self.window_size * self.window_size)
490
+ attn_mask = mask_windows.unsqueeze(1) - mask_windows.unsqueeze(2)
491
+ attn_mask = attn_mask.masked_fill(attn_mask != 0, float(-100.0)).masked_fill(attn_mask == 0, float(0.0))
492
+
493
+ # window attention
494
+ x = x + self.drop_path1(self.gamma1*self.attn(self.norm1(x), attn_mask=attn_mask)) # or pass H,W
495
+ # mlp layer
496
+ x = x + self.drop_path2(self.gamma2*self.mlp(self.norm2(x)))
497
+
498
+ if self.do_windowing:
499
+ if self.window_size > 0:
500
+ x = window_reverse(x, self.window_size, H, W, pad_hw)
501
+
502
+ # reverse cyclic shift
503
+ if self.shift_size > 0 and H>self.window_size and W>self.window_size:
504
+ # @swin like cyclic shift, not tested
505
+ x = torch.roll(x, shifts=(self.shift_size, self.shift_size), dims=(2, 3))
506
+
507
+ x = self.upsample_mixer(x)
508
+ x = self.upsample_op(x)
509
+
510
+
511
+ if x.shape[2] != skip_connection.shape[2] or x.shape[3] != skip_connection.shape[3]:
512
+ x = torch.nn.functional.pad(x, ( 0, -x.shape[3] + skip_connection.shape[3], 0, -x.shape[2] + skip_connection.shape[2]), mode="reflect")
513
+ # need to add skip connection because downsampling and upsampling will break residual connection
514
+ # 0.5 is needed to make sure that the skip connection is not too strong
515
+ # in case of no downsample / upsample we can show that 0.5 compensates for the residual connection
516
+ x = 0.5 * x + 0.5 * skip_connection
517
+ return x
518
+
519
+
520
+
521
+
522
+ class MultiResolutionAttention(nn.Module):
523
+ """
524
+ MultiResolutionAttention (MRA) module
525
+ The idea is to use multiple attention blocks with different resolution
526
+ Feature maps are downsampled / upsampled for each attention block on different blocks
527
+ Every attention block supports windowing
528
+ """
529
+
530
+ def __init__(self, window_size, sr_ratio,
531
+ dim, dim_ratio, num_heads,
532
+ do_windowing=True,
533
+ layer_scale=1e-5, norm_layer=nn.LayerNorm,
534
+ drop_path = 0, qkv_bias=False, qk_scale=1.0,
535
+ use_swiglu=True, multi_query=False, conv_base=False,
536
+ use_shift=0, cpb_mlp_hidden=512, conv_groups_ratio=0) -> None:
537
+ """
538
+ Args:
539
+ input_resolution: input image resolution
540
+ window_size: window size
541
+ compression_ratio: compression ratio
542
+ max_depth: maximum depth of the GRA module
543
+ use_shift: do window shifting
544
+ """
545
+ super().__init__()
546
+
547
+ depth = len(sr_ratio)
548
+
549
+ self.attention_blocks = nn.ModuleList()
550
+
551
+
552
+ for i in range(depth):
553
+ subsample_ratio = sr_ratio[i]
554
+ if len(window_size) > i:
555
+ window_size_local = window_size[i]
556
+ else:
557
+ window_size_local = window_size[0]
558
+
559
+ self.attention_blocks.append(GRAAttentionBlock(window_size=window_size_local,
560
+ dim_in=dim, dim_out=dim, num_heads=num_heads,
561
+ qkv_bias=qkv_bias, qk_scale=qk_scale, norm_layer=norm_layer,
562
+ layer_scale=layer_scale, drop_path=drop_path,
563
+ use_swiglu=use_swiglu, subsample_ratio=subsample_ratio, dim_ratio=dim_ratio,
564
+ do_windowing=do_windowing, multi_query=multi_query, conv_base=conv_base,
565
+ use_shift=use_shift, cpb_mlp_hidden=cpb_mlp_hidden, conv_groups_ratio=conv_groups_ratio),
566
+ )
567
+
568
+ def forward(self, x):
569
+
570
+ for attention_block in self.attention_blocks:
571
+ x = attention_block(x)
572
+
573
+ return x
574
+
575
+
576
+
577
+ class Mlp(nn.Module):
578
+ """
579
+ Multi-Layer Perceptron (MLP) block
580
+ """
581
+
582
+ def __init__(self,
583
+ in_features,
584
+ hidden_features=None,
585
+ out_features=None,
586
+ act_layer=nn.GELU,
587
+ use_swiglu=True,
588
+ drop=0.):
589
+ """
590
+ Args:
591
+ in_features: input features dimension.
592
+ hidden_features: hidden features dimension.
593
+ out_features: output features dimension.
594
+ act_layer: activation function.
595
+ drop: dropout rate.
596
+ """
597
+
598
+ super().__init__()
599
+ out_features = out_features or in_features
600
+ hidden_features = hidden_features or in_features
601
+ self.fc1 = nn.Linear(in_features, hidden_features * (2 if use_swiglu else 1), bias=False)
602
+ self.act = act_layer()
603
+ self.fc2 = nn.Linear(hidden_features, out_features, bias=False)
604
+
605
+ def forward(self, x):
606
+ x_size = x.size()
607
+ x = x.view(-1, x_size[-1])
608
+ x = self.fc1(x)
609
+ x = self.act(x)
610
+ x = self.fc2(x)
611
+ x = x.view(x_size)
612
+ return x
613
+
614
+ class Downsample(nn.Module):
615
+ """
616
+ Down-sampling block
617
+ Pixel Unshuffle is used for down-sampling, works great accuracy - wise but takes 10% more TRT time
618
+ """
619
+
620
+ def __init__(self,
621
+ dim,
622
+ shuffle = False,
623
+ ):
624
+ """
625
+ Args:
626
+ dim: feature size dimension.
627
+ shuffle: idea with
628
+ keep_dim: bool argument for maintaining the resolution.
629
+ """
630
+
631
+ super().__init__()
632
+ dim_out = 2 * dim
633
+
634
+ if shuffle:
635
+ self.norm = lambda x: pixel_unshuffle(x, factor=2)
636
+ self.reduction = Conv2d_BN(dim*4, dim_out, 1, 1, 0, bias=False)
637
+ # pixel unshuffleging works well but doesnt provide any speedup
638
+ else:
639
+ # removed layer norm for better, in this formulation we are getting 10% better speed
640
+ # LayerNorm for high resolution inputs will be a pain as it pools over the entire spatial dimension
641
+ # therefore we remove it compared to the original implementation in FasterViT
642
+ self.norm = nn.Identity()
643
+ self.reduction = Conv2d_BN(dim, dim_out, 3, 2, 1, bias=False)
644
+
645
+
646
+ def forward(self, x):
647
+ x = self.norm(x)
648
+ x = self.reduction(x)
649
+ return x
650
+
651
+
652
+ class PatchEmbed(nn.Module):
653
+ """
654
+ Patch embedding block
655
+ Used to convert image into an initial set of feature maps with lower resolution
656
+ """
657
+
658
+ def __init__(self, in_chans=3, in_dim=64, dim=96, shuffle_down=False):
659
+ """
660
+ Args:
661
+ in_chans: number of input channels.
662
+ in_dim: intermediate feature size dimension to speed up stem.
663
+ dim: final stem channel number
664
+ shuffle_down: use PixelUnshuffle for down-sampling, effectively increases the receptive field
665
+ """
666
+
667
+ super().__init__()
668
+ # shuffle_down = False
669
+ if not shuffle_down:
670
+ self.proj = nn.Identity()
671
+ self.conv_down = nn.Sequential(
672
+ Conv2d_BN(in_chans, in_dim, 3, 2, 1, bias=False),
673
+ nn.ReLU(),
674
+ Conv2d_BN(in_dim, dim, 3, 2, 1, bias=False),
675
+ nn.ReLU()
676
+ )
677
+ else:
678
+ self.proj = lambda x: pixel_unshuffle(x, factor=4)
679
+ self.conv_down = nn.Sequential(Conv2d_BN(in_chans*16, dim, 3, 1, 1),
680
+ nn.ReLU(),
681
+ )
682
+
683
+ def forward(self, x):
684
+ x = self.proj(x)
685
+ x = self.conv_down(x)
686
+ return x
687
+
688
+
689
+
690
+ class ConvBlock(nn.Module):
691
+ """
692
+ Convolutional block, used in first couple of stages
693
+ Experimented with plan resnet-18 like modules, they are the best in terms of throughput
694
+ Finally, YOLOv8 idea seem to work fine (resnet-18 like block with squeezed feature dimension, and feature concatendation at the end)
695
+ """
696
+ def __init__(self, dim,
697
+ drop_path=0.,
698
+ layer_scale=None,
699
+ kernel_size=3,
700
+ ):
701
+ super().__init__()
702
+
703
+ self.conv1 = Conv2d_BN(dim, dim, kernel_size=kernel_size, stride=1, padding=1)
704
+ self.act1 = nn.GELU()
705
+
706
+ self.conv2 = Conv2d_BN(dim, dim, kernel_size=kernel_size, stride=1, padding=1)
707
+
708
+ self.layer_scale = layer_scale
709
+ if layer_scale is not None and type(layer_scale) in [int, float]:
710
+ self.gamma = nn.Parameter(layer_scale * torch.ones(dim))
711
+ self.layer_scale = True
712
+ else:
713
+ self.layer_scale = False
714
+ self.drop_path = DropPath(drop_path) if drop_path > 0. else nn.Identity()
715
+
716
+ def forward(self, x):
717
+ input = x
718
+
719
+ x = self.conv1(x)
720
+ x = self.act1(x)
721
+ x = self.conv2(x)
722
+
723
+ if self.layer_scale:
724
+ x = x * self.gamma.view(1, -1, 1, 1)
725
+ x = input + self.drop_path(x)
726
+ return x
727
+
728
+
729
+ class WindowAttention(nn.Module):
730
+ # Windowed Attention from SwinV2
731
+ # use a MLP trick to deal with various input image resolutions, then fold it to improve speed
732
+
733
+ def __init__(self, dim, num_heads=8, qkv_bias=False, qk_scale=None, resolution=0,
734
+ seq_length=0, dim_out=None, multi_query=False, shift_size=0, cpb_mlp_hidden=512):
735
+ # taken from EdgeViT and tweaked with attention bias.
736
+ super().__init__()
737
+ if not dim_out: dim_out = dim
738
+ self.shift_size = shift_size
739
+ self.multi_query = multi_query
740
+ self.num_heads = num_heads
741
+ head_dim = dim // num_heads
742
+ self.head_dim = dim // num_heads
743
+
744
+ self.dim_internal = dim
745
+
746
+ self.scale = qk_scale or head_dim ** -0.5
747
+ if not multi_query:
748
+ self.qkv = nn.Linear(dim, dim * 3, bias=qkv_bias)
749
+ else:
750
+ self.qkv = nn.Linear(dim, dim + 2*self.head_dim, bias=qkv_bias)
751
+
752
+ self.proj = nn.Linear(dim, dim_out, bias=False)
753
+ # attention positional bias
754
+ self.pos_emb_funct = PosEmbMLPSwinv2D(window_size=[resolution, resolution],
755
+ pretrained_window_size=[resolution, resolution],
756
+ num_heads=num_heads,
757
+ seq_length=seq_length,
758
+ cpb_mlp_hidden=cpb_mlp_hidden)
759
+
760
+ self.resolution = resolution
761
+
762
+ def forward(self, x, attn_mask = None):
763
+ B, N, C = x.shape
764
+
765
+ if not self.multi_query:
766
+ qkv = self.qkv(x).reshape(B, -1, 3, self.num_heads, C // self.num_heads).permute(2, 0, 3, 1, 4)
767
+ q, k, v = qkv[0], qkv[1], qkv[2]
768
+ else:
769
+ qkv = self.qkv(x)
770
+ (q, k, v) = qkv.split([self.dim_internal, self.head_dim, self.head_dim], dim=2)
771
+
772
+ q = q.reshape(B, -1, self.num_heads, C // self.num_heads).permute(0, 2, 1, 3)
773
+ k = k.reshape(B, -1, 1, C // self.num_heads).permute(0, 2, 1, 3)
774
+ v = v.reshape(B, -1, 1, C // self.num_heads).permute(0, 2, 1, 3)
775
+
776
+ attn = (q @ k.transpose(-2, -1)) * self.scale
777
+
778
+ attn = self.pos_emb_funct(attn)
779
+
780
+ #add window shift
781
+ if attn_mask is not None:
782
+ nW = attn_mask.shape[0]
783
+ attn = attn.view(B // nW, nW, self.num_heads, N, N) + attn_mask.unsqueeze(1).unsqueeze(0)
784
+ attn = attn.view(-1, self.num_heads, N, N)
785
+
786
+ attn = attn.softmax(dim=-1)
787
+ x = (attn @ v).transpose(1, 2).reshape(B, -1, C)
788
+ x = self.proj(x)
789
+ return x
790
+
791
+
792
+
793
+ class ERADIOLayer(nn.Module):
794
+ """
795
+ E-RADIO Layer
796
+ """
797
+
798
+ def __init__(self,
799
+ dim,
800
+ depth,
801
+ num_heads,
802
+ window_size,
803
+ conv=False,
804
+ downsample=True,
805
+ mlp_ratio=4.,
806
+ qkv_bias=False,
807
+ qk_scale=None,
808
+ norm_layer=nn.LayerNorm,
809
+ drop_path=0.,
810
+ layer_scale=None,
811
+ layer_scale_conv=None,
812
+ sr_dim_ratio=1,
813
+ sr_ratio=1,
814
+ multi_query=False,
815
+ use_swiglu=True,
816
+ yolo_arch=False,
817
+ downsample_shuffle=False,
818
+ conv_base=False,
819
+ use_shift=False,
820
+ cpb_mlp_hidden=512,
821
+ conv_groups_ratio=0,
822
+ verbose: bool = True,
823
+
824
+ ):
825
+ """
826
+ Args:
827
+ dim: feature size dimension.
828
+ depth: number of layers in each stage.
829
+ input_resolution: input image resolution.
830
+ window_size: window size in each stage.
831
+ downsample: bool argument for down-sampling.
832
+ mlp_ratio: MLP ratio.
833
+ num_heads: number of heads in each stage.
834
+ qkv_bias: bool argument for query, key, value learnable bias.
835
+ qk_scale: bool argument to scaling query, key.
836
+ drop: dropout rate.
837
+ attn_drop: attention dropout rate.
838
+ drop_path: drop path rate.
839
+ norm_layer: normalization layer.
840
+ layer_scale: layer scaling coefficient.
841
+ use_shift: SWIN like window shifting for half the window size for every alternating layer (considering multi-resolution)
842
+ conv_groups_ratio: group ratio for conv when no subsampling in multi-res attention
843
+ """
844
+
845
+ super().__init__()
846
+ self.conv = conv
847
+ self.yolo_arch=False
848
+ self.verbose = verbose
849
+ if conv:
850
+ if not yolo_arch:
851
+ self.blocks = nn.ModuleList([
852
+ ConvBlock(dim=dim,
853
+ drop_path=drop_path[i] if isinstance(drop_path, list) else drop_path,
854
+ layer_scale=layer_scale_conv)
855
+ for i in range(depth)])
856
+ self.blocks = nn.Sequential(*self.blocks)
857
+ else:
858
+ self.blocks = C2f(dim,dim,n=depth,shortcut=True,e=0.5)
859
+ self.yolo_arch=True
860
+ else:
861
+ if not isinstance(window_size, list): window_size = [window_size]
862
+ self.window_size = window_size[0]
863
+ self.do_single_windowing = True
864
+ if not isinstance(sr_ratio, list): sr_ratio = [sr_ratio]
865
+ self.sr_ratio = sr_ratio
866
+ if any([sr!=1 for sr in sr_ratio]) or len(set(window_size))>1:
867
+ self.do_single_windowing = False
868
+ do_windowing = True
869
+ else:
870
+ self.do_single_windowing = True
871
+ do_windowing = False
872
+
873
+ #for v2_2
874
+ if conv_groups_ratio != -1:
875
+ self.do_single_windowing = False
876
+ do_windowing = True
877
+
878
+ self.blocks = nn.ModuleList()
879
+ for i in range(depth):
880
+ self.blocks.append(
881
+ MultiResolutionAttention(window_size=window_size,
882
+ sr_ratio=sr_ratio,
883
+ dim=dim,
884
+ dim_ratio = sr_dim_ratio,
885
+ num_heads=num_heads,
886
+ norm_layer=norm_layer,
887
+ drop_path=drop_path[i] if isinstance(drop_path, list) else drop_path,
888
+ layer_scale=layer_scale,
889
+ qkv_bias=qkv_bias,
890
+ qk_scale=qk_scale,
891
+ use_swiglu=use_swiglu,
892
+ do_windowing=do_windowing,
893
+ multi_query=multi_query,
894
+ conv_base=conv_base,
895
+ cpb_mlp_hidden=cpb_mlp_hidden,
896
+ use_shift =0 if ((not use_shift) or ((i) % 2 == 0)) else True ,
897
+ conv_groups_ratio=conv_groups_ratio,
898
+ ))
899
+ self.blocks = nn.Sequential(*self.blocks)
900
+
901
+ self.transformer = not conv
902
+ self.downsample = None if not downsample else Downsample(dim=dim, shuffle=downsample_shuffle)
903
+
904
+
905
+ def forward(self, x):
906
+ B, C, H, W = x.shape
907
+
908
+ # do padding for transforemr
909
+ interpolate = True
910
+ if self.transformer and interpolate:
911
+ # Windowed Attention will split feature map into windows with the size of window_size x window_size
912
+ # if the resolution is not divisible by window_size, we need to interpolate the feature map
913
+ # can be done via padding, but doing so after training hurts the model performance.
914
+ # interpolation affects the performance as well, but not as much as padding
915
+ if isinstance(self.window_size, list) or isinstance(self.window_size, tuple):
916
+ current_max_window_size = max(self.window_size)
917
+ else:
918
+ current_max_window_size = self.window_size
919
+
920
+ max_window_size = max([res_upsample*current_max_window_size for res_upsample in self.sr_ratio])
921
+ if H % max_window_size != 0 or W % max_window_size != 0:
922
+ new_h = int(np.ceil(H/max_window_size)*max_window_size)
923
+ new_w = int(np.ceil(W/max_window_size)*max_window_size)
924
+ x = F.interpolate(x, size=(new_h, new_w), mode='nearest')
925
+ if self.verbose:
926
+ warnings.warn(f"Choosen window size is not optimal for given resolution. Interpolation of features maps will be done and it can affect the performance. Max window size is {max_window_size}, feature map size is {H}x{W}, interpolated feature map size is {new_h}x{new_w}.")
927
+
928
+
929
+ if self.transformer and self.do_single_windowing:
930
+ H, W = x.shape[2], x.shape[3]
931
+ x, pad_hw = window_partition(x, self.window_size)
932
+
933
+ #run main blocks
934
+ x = self.blocks(x)
935
+
936
+ if self.transformer and self.do_single_windowing:
937
+ x = window_reverse(x, self.window_size, H, W, pad_hw)
938
+
939
+ if self.transformer and interpolate:
940
+ #lets keep original resolution, might be not ideal, but for the upsampling tower we need to keep the expected resolution.
941
+ x = F.interpolate(x, size=(H, W), mode='nearest')
942
+
943
+ if self.downsample is None:
944
+ return x, x
945
+
946
+ return self.downsample(x), x # changing to output pre downsampled features
947
+
948
+
949
+ class InterpolateLayer(nn.Module):
950
+ def __init__(self, size=None, scale_factor=None, mode='nearest'):
951
+ super(InterpolateLayer, self).__init__()
952
+ self.size = size
953
+ self.scale_factor = scale_factor
954
+ self.mode = mode
955
+
956
+ def forward(self, x):
957
+ return F.interpolate(x, size=self.size, scale_factor=self.scale_factor, mode=self.mode)
958
+
959
+
960
+ class HiResNeck(nn.Module):
961
+ """
962
+ The block is used to output dense features from all stages
963
+ Otherwise, by default, only the last stage features are returned with E-RADIO
964
+ """
965
+ def __init__(self, dim, depths, neck_start_stage, full_features_head_dim, downsample_enabled):
966
+
967
+ '''
968
+ Hi Resolution neck to support output of high res features that are useful for dense tasks.
969
+ depths - total number of layers in the base model
970
+ neck_start_stage - when to start the neck, 0 - start from the first stage, 1 - start from the second stage etc.
971
+ earlier layers result in higher resolution features at the cost of compute
972
+ full_features_head_dim - number of channels in the dense features head
973
+ '''
974
+ super().__init__()
975
+ # create feature projection layers for segmentation output
976
+ self.neck_features_proj = nn.ModuleList()
977
+ self.neck_start_stage = neck_start_stage
978
+ upsample_ratio = 1
979
+ for i in range(len(depths)):
980
+ level_n_features_output = int(dim * 2 ** i)
981
+
982
+ if self.neck_start_stage > i: continue
983
+
984
+ if (upsample_ratio > 1) or full_features_head_dim!=level_n_features_output:
985
+ feature_projection = nn.Sequential()
986
+ if False:
987
+ feature_projection.add_module("norm",nn.BatchNorm2d(level_n_features_output)) #fast, but worse
988
+ feature_projection.add_module("dconv", nn.ConvTranspose2d(level_n_features_output,
989
+ full_features_head_dim, kernel_size=upsample_ratio, stride=upsample_ratio))
990
+ else:
991
+ # B, in_channels, H, W -> B, in_channels, H*upsample_ratio, W*upsample_ratio
992
+ # print("upsample ratio", upsample_ratio, level_n_features_output, level_n_features_output)
993
+ feature_projection.add_module("upsample", InterpolateLayer(scale_factor=upsample_ratio, mode='nearest'))
994
+ feature_projection.add_module("conv1", nn.Conv2d(level_n_features_output, level_n_features_output, kernel_size=3, stride=1, padding=1, groups=level_n_features_output))
995
+ feature_projection.add_module("norm",nn.BatchNorm2d(level_n_features_output))
996
+ # B, in_channels, H*upsample_ratio, W*upsample_ratio -> B, full_features_head_dim, H*upsample_ratio, W*upsample_ratio
997
+ feature_projection.add_module("conv2", nn.Conv2d(level_n_features_output, full_features_head_dim, kernel_size=1, stride=1, padding=0))
998
+ else:
999
+ feature_projection = nn.Sequential()
1000
+
1001
+ self.neck_features_proj.append(feature_projection)
1002
+
1003
+ if i>0 and downsample_enabled[i]:
1004
+ upsample_ratio *= 2
1005
+
1006
+ def forward(self, x, il_level=-1, full_features=None):
1007
+ if self.neck_start_stage > il_level:
1008
+ return full_features
1009
+
1010
+ if full_features is None:
1011
+ full_features = self.neck_features_proj[il_level - self.neck_start_stage](x)
1012
+ else:
1013
+ #upsample torch tensor x to match full_features size, and add to full_features
1014
+ feature_projection = self.neck_features_proj[il_level - self.neck_start_stage](x)
1015
+ if feature_projection.shape[2] != full_features.shape[2] or feature_projection.shape[3] != full_features.shape[3]:
1016
+ feature_projection = torch.nn.functional.pad(feature_projection, ( 0, -feature_projection.shape[3] + full_features.shape[3], 0, -feature_projection.shape[2] + full_features.shape[2]))
1017
+ full_features = full_features + feature_projection
1018
+ return full_features
1019
+
1020
+ class ERADIO(nn.Module):
1021
+ """
1022
+ Efficient RADIO
1023
+ """
1024
+
1025
+ def __init__(self,
1026
+ dim,
1027
+ in_dim,
1028
+ depths,
1029
+ window_size,
1030
+ mlp_ratio,
1031
+ num_heads,
1032
+ drop_path_rate=0.2,
1033
+ in_chans=3,
1034
+ num_classes=1000,
1035
+ qkv_bias=False,
1036
+ qk_scale=None,
1037
+ layer_scale=None,
1038
+ layer_scale_conv=None,
1039
+ layer_norm_last=False,
1040
+ sr_ratio = [1, 1, 1, 1],
1041
+ max_depth = -1,
1042
+ conv_base=False,
1043
+ use_swiglu=False,
1044
+ multi_query=False,
1045
+ norm_layer=nn.LayerNorm,
1046
+ drop_uniform=False,
1047
+ yolo_arch=False,
1048
+ shuffle_down=False,
1049
+ downsample_shuffle=False,
1050
+ return_full_features=False,
1051
+ full_features_head_dim=128,
1052
+ neck_start_stage=1,
1053
+ use_neck=False,
1054
+ use_shift=False,
1055
+ cpb_mlp_hidden=512,
1056
+ conv_groups_ratio=0,
1057
+ verbose: bool = False,
1058
+ **kwargs):
1059
+ """
1060
+ Args:
1061
+ dim: feature size dimension.
1062
+ depths: number of layers in each stage.
1063
+ window_size: window size in each stage.
1064
+ mlp_ratio: MLP ratio.
1065
+ num_heads: number of heads in each stage.
1066
+ drop_path_rate: drop path rate.
1067
+ in_chans: number of input channels.
1068
+ num_classes: number of classes.
1069
+ qkv_bias: bool argument for query, key, value learnable bias.
1070
+ qk_scale: bool argument to scaling query, key.
1071
+ drop_rate: dropout rate.
1072
+ attn_drop_rate: attention dropout rate.
1073
+ norm_layer: normalization layer.
1074
+ layer_scale: layer scaling coefficient.
1075
+ return_full_features: output dense features as well as logits
1076
+ full_features_head_dim: number of channels in the dense features head
1077
+ neck_start_stage: a stage id to start full feature neck. Model has 4 stages, indix starts with 0
1078
+ for 224 resolution, the output of the stage before downsample:
1079
+ stage 0: 56x56, stage 1: 28x28, stage 2: 14x14, stage 3: 7x7
1080
+ use_neck: even for summarization embedding use neck
1081
+ use_shift: SWIN like window shifting but without masking attention
1082
+ conv_groups_ratio: will be used for conv blocks where there is no multires attention,
1083
+ if 0 then normal conv,
1084
+ if 1 then channels are independent,
1085
+ if -1 then no conv at all
1086
+
1087
+ """
1088
+ super().__init__()
1089
+
1090
+ num_features = int(dim * 2 ** (len(depths) - 1))
1091
+ self.num_classes = num_classes
1092
+ self.patch_embed = PatchEmbed(in_chans=in_chans, in_dim=in_dim, dim=dim, shuffle_down=shuffle_down)
1093
+ # set return_full_features true if we want to return full features from all stages
1094
+ self.return_full_features = return_full_features
1095
+ self.use_neck = use_neck
1096
+
1097
+ dpr = [x.item() for x in torch.linspace(0, drop_path_rate, sum(depths))]
1098
+ if drop_uniform:
1099
+ dpr = [drop_path_rate for x in range(sum(depths))]
1100
+
1101
+ if not isinstance(max_depth, list): max_depth = [max_depth] * len(depths)
1102
+
1103
+ self.levels = nn.ModuleList()
1104
+ for i in range(len(depths)):
1105
+ conv = True if (i == 0 or i == 1) else False
1106
+
1107
+ level = ERADIOLayer(dim=int(dim * 2 ** i),
1108
+ depth=depths[i],
1109
+ num_heads=num_heads[i],
1110
+ window_size=window_size[i],
1111
+ mlp_ratio=mlp_ratio,
1112
+ qkv_bias=qkv_bias,
1113
+ qk_scale=qk_scale,
1114
+ conv=conv,
1115
+ drop_path=dpr[sum(depths[:i]):sum(depths[:i + 1])],
1116
+ downsample=(i < len(depths) - 1),
1117
+ layer_scale=layer_scale,
1118
+ layer_scale_conv=layer_scale_conv,
1119
+ sr_ratio=sr_ratio[i],
1120
+ use_swiglu=use_swiglu,
1121
+ multi_query=multi_query,
1122
+ norm_layer=norm_layer,
1123
+ yolo_arch=yolo_arch,
1124
+ downsample_shuffle=downsample_shuffle,
1125
+ conv_base=conv_base,
1126
+ cpb_mlp_hidden=cpb_mlp_hidden,
1127
+ use_shift=use_shift,
1128
+ conv_groups_ratio=conv_groups_ratio,
1129
+ verbose=verbose)
1130
+
1131
+ self.levels.append(level)
1132
+
1133
+ if self.return_full_features or self.use_neck:
1134
+ #num_heads
1135
+ downsample_enabled = [self.levels[i-1].downsample is not None for i in range(len(self.levels))]
1136
+ self.high_res_neck = HiResNeck(dim, depths, neck_start_stage, full_features_head_dim, downsample_enabled)
1137
+
1138
+ self.switched_to_deploy = False
1139
+
1140
+ self.norm = LayerNorm2d(num_features) if layer_norm_last else nn.BatchNorm2d(num_features)
1141
+ self.avgpool = nn.AdaptiveAvgPool2d(1)
1142
+ self.head = nn.Linear(num_features, num_classes) if num_classes > 0 else nn.Identity()
1143
+ self.apply(self._init_weights)
1144
+
1145
+ def _init_weights(self, m):
1146
+ if isinstance(m, nn.Linear):
1147
+ trunc_normal_(m.weight, std=.02)
1148
+ if isinstance(m, nn.Linear) and m.bias is not None:
1149
+ nn.init.constant_(m.bias, 0)
1150
+ elif isinstance(m, nn.LayerNorm):
1151
+ nn.init.constant_(m.bias, 0)
1152
+ nn.init.constant_(m.weight, 1.0)
1153
+ elif isinstance(m, LayerNorm2d):
1154
+ nn.init.constant_(m.bias, 0)
1155
+ nn.init.constant_(m.weight, 1.0)
1156
+ elif isinstance(m, nn.BatchNorm2d):
1157
+ nn.init.ones_(m.weight)
1158
+ nn.init.zeros_(m.bias)
1159
+
1160
+ @torch.jit.ignore
1161
+ def no_weight_decay_keywords(self):
1162
+ return {'rpb'}
1163
+
1164
+ def forward_features(self, x):
1165
+ _, _, H, W = x.shape
1166
+ if H % 32 != 0 or W % 32 != 0:
1167
+ raise ValueError(f"E-RADIO requires input dimensions to be divisible by 32 but got H x W: {H} x {W}")
1168
+ x = self.patch_embed(x)
1169
+ full_features = None
1170
+ for il, level in enumerate(self.levels):
1171
+ x, pre_downsample_x = level(x)
1172
+
1173
+ if self.return_full_features or self.use_neck:
1174
+ full_features = self.high_res_neck(pre_downsample_x, il, full_features)
1175
+
1176
+ # x = self.norm(full_features if (self.return_full_features or self.use_neck) else x)
1177
+ x = self.norm(x) # new version for
1178
+
1179
+ if not self.return_full_features:
1180
+ return x, None
1181
+
1182
+ return x, full_features
1183
+
1184
+ def forward(self, x):
1185
+ x, full_features = self.forward_features(x)
1186
+
1187
+ x = self.avgpool(x)
1188
+ x = torch.flatten(x, 1)
1189
+
1190
+ x = self.head(x)
1191
+ if full_features is not None:
1192
+ return x, full_features
1193
+ return x
1194
+
1195
+ def switch_to_deploy(self):
1196
+ '''
1197
+ A method to perform model self-compression
1198
+ merges BN into conv layers
1199
+ converts MLP relative positional bias into precomputed buffers
1200
+ '''
1201
+ if not self.switched_to_deploy:
1202
+ for level in [self.patch_embed, self.levels, self.head]:
1203
+ for module in level.modules():
1204
+ if hasattr(module, 'switch_to_deploy'):
1205
+ module.switch_to_deploy()
1206
+ self.switched_to_deploy = True
1207
+
1208
+
1209
+ def change_window_size(self, new_window_size):
1210
+ """
1211
+ E-RADIO employs windowed attention, which may be sensitive to the choice of this parameter,
1212
+ especially in cases of uneven partitioning of the feature maps.
1213
+ E-RADIO allows for the adjustment of the window size after training,
1214
+ making it adaptable to different input image resolutions.
1215
+ The recommended values for window size based on input resolution are as follows:
1216
+
1217
+ Input Resolution | Window Size
1218
+ 224 | 7
1219
+ 256 | 8
1220
+ 386 | 12
1221
+ 512 | 16
1222
+ Ideally, the window size should be a factor of the input resolution. In the third stage, we divide the resolution by 16, so the window size should be
1223
+ img_res/16/2
1224
+ for the third stage and img_res/32 for the last stage. While this can be applied in a brute-force manner, a better way is to do model.change_window_size.
1225
+ Manual way to change resolution -> model.change_window_size(resolution)
1226
+ """
1227
+ window_size = new_window_size
1228
+ print(f"Setting window size to {window_size}")
1229
+ for module in self.modules():
1230
+ if hasattr(module, "window_size"):
1231
+ # check if tuple or a number
1232
+ if isinstance(module.window_size, tuple):
1233
+ if module.window_size[0] != window_size:
1234
+ module.window_size = (window_size, window_size)
1235
+ elif isinstance(module.window_size, list):
1236
+ if module.window_size[0] != window_size:
1237
+ module.window_size = [window_size, window_size]
1238
+ else:
1239
+ module.window_size = window_size
1240
+
1241
+
1242
+ def set_optimal_window_size(self, image_dim, max_window_size = 16):
1243
+ """
1244
+ Using hand picked window size for various resolutions.
1245
+
1246
+ E-RADIO employs windowed attention, which may be sensitive to the choice of this parameter,
1247
+ especially in cases of uneven partitioning of the feature maps.
1248
+ E-RADIO allows for the adjustment of the window size after training,
1249
+ making it adaptable to different input image resolutions.
1250
+ The recommended values for window size based on input resolution are as follows:
1251
+
1252
+ Input Resolution | Window Size
1253
+ 224 | 7
1254
+ 256 | 8
1255
+ 386 | 12
1256
+ 512 | 16
1257
+ Ideally, the window size should be a factor of the input resolution. In the third stage, we divide the resolution by 16, so the window size should be
1258
+ img_res/16/2
1259
+ for the third stage and img_res/32 for the last stage. While this can be applied in a brute-force manner, a better way is to do model.change_window_size.
1260
+ Manual way to change resolution -> model.change_window_size(resolution)
1261
+
1262
+ """
1263
+ # import math
1264
+
1265
+ def divisorGenerator(n):
1266
+ large_divisors = []
1267
+ for i in range(1, int(math.sqrt(n) + 1)):
1268
+ if n % i == 0:
1269
+ yield i
1270
+ if i*i != n:
1271
+ large_divisors.append(n / i)
1272
+ for divisor in reversed(large_divisors):
1273
+ yield divisor
1274
+
1275
+ if isinstance(image_dim, list) or isinstance(image_dim, tuple):
1276
+ image_dim = min(image_dim)
1277
+
1278
+ # we do windowed attention in the 3rd stage for the first time, therefore //16,
1279
+ # we do subsampled attention with downsample by 2 so need to get //32 actually
1280
+ # ideally we should rewrite this to be dependent on the structure of the model like what if subsampled is removed etc
1281
+ all_divisors = np.array(list(divisorGenerator(image_dim//32)))
1282
+ new_window_size = int(min(all_divisors[all_divisors <= max_window_size][-1], max_window_size))
1283
+
1284
+ # for image_dim in [128, 224, 256, 384, 512, 768, 1024]:
1285
+ # all_divisors = np.array(list(divisorGenerator(image_dim//32)))
1286
+ # new_window_size = int(min(all_divisors[all_divisors <= max_window_size][-1], max_window_size))
1287
+ # print(f"Setting window size to {new_window_size} for image resolution {image_dim}")
1288
+
1289
+ self.change_window_size(new_window_size = new_window_size)
1290
+
1291
+
1292
+ @register_model
1293
+ def eradio_large_fullres_ws16(pretrained=False, **kwargs):
1294
+ model = ERADIO(
1295
+ depths=[3, 3, 5, 5],
1296
+ num_heads=[2, 4, 8, 16],
1297
+ window_size=[None, None, [16, 16], 16],
1298
+ dim=192,
1299
+ in_dim=64,
1300
+ mlp_ratio=4,
1301
+ drop_path_rate=0.0,
1302
+ sr_ratio=[1, 1, [2, 1], 1],
1303
+ use_swiglu=False,
1304
+ yolo_arch=True,
1305
+ shuffle_down=False,
1306
+ conv_base=True,
1307
+ use_neck=True,
1308
+ full_features_head_dim=1536,
1309
+ neck_start_stage=2,
1310
+ **kwargs,
1311
+ )
1312
+ if pretrained:
1313
+ model.load_state_dict(torch.load(pretrained)["state_dict"])
1314
+ return model
1315
+
1316
+
1317
+ @register_model
1318
+ def eradio_xxxtiny(pretrained=False, **kwargs): # ,
1319
+ model = ERADIO(
1320
+ depths=[1, 3, 4, 5],
1321
+ num_heads=[2, 4, 8, 16],
1322
+ window_size=[None, None, [16, 16], 16],
1323
+ dim=32,
1324
+ in_dim=32,
1325
+ mlp_ratio=4,
1326
+ drop_path_rate=0.0,
1327
+ sr_ratio=[1, 1, [2, 1], 1],
1328
+ use_swiglu=False,
1329
+ yolo_arch=True,
1330
+ shuffle_down=False,
1331
+ conv_base=True,
1332
+ use_neck=True,
1333
+ full_features_head_dim=256,
1334
+ neck_start_stage=2,
1335
+ **kwargs,
1336
+ )
1337
+ if pretrained:
1338
+ model.load_state_dict(torch.load(pretrained))
1339
+ return model
1340
+
1341
+ @register_model
1342
+ def eradio_xxxtiny_8x_ws12(pretrained=False, **kwargs):
1343
+ model = ERADIO(depths=[1, 3, 4, 5],
1344
+ num_heads=[2, 4, 8, 16],
1345
+ window_size=[None, None, [12, 12], 12],
1346
+ dim=32,
1347
+ in_dim=32,
1348
+ mlp_ratio=4,
1349
+ drop_path_rate=0.0,
1350
+ sr_ratio=[1, 1, [2, 1], 1],
1351
+ use_swiglu=False,
1352
+ downsample_shuffle=False,
1353
+ yolo_arch=True,
1354
+ shuffle_down=False,
1355
+ cpb_mlp_hidden=64,
1356
+ use_neck=True,
1357
+ full_features_head_dim=256,
1358
+ neck_start_stage=2,
1359
+ conv_groups_ratio = 1,
1360
+ **kwargs)
1361
+ if pretrained:
1362
+ model.load_state_dict(torch.load(pretrained)["state_dict"])
1363
+ return model
1364
+
1365
+
1366
+ @register_model
1367
+ def eradio_xxxtiny_8x_ws16(pretrained=False, **kwargs):
1368
+ model = ERADIO(depths=[1, 3, 4, 5],
1369
+ num_heads=[2, 4, 8, 16],
1370
+ window_size=[None, None, [16, 16], 16],
1371
+ dim=32,
1372
+ in_dim=32,
1373
+ mlp_ratio=4,
1374
+ drop_path_rate=0.0,
1375
+ sr_ratio=[1, 1, [2, 1], 1],
1376
+ use_swiglu=False,
1377
+ downsample_shuffle=False,
1378
+ yolo_arch=True,
1379
+ shuffle_down=False,
1380
+ cpb_mlp_hidden=64,
1381
+ use_neck=True,
1382
+ full_features_head_dim=256,
1383
+ neck_start_stage=1,
1384
+ conv_groups_ratio = 1,
1385
+ **kwargs)
1386
+ if pretrained:
1387
+ model.load_state_dict(torch.load(pretrained)["state_dict"])
1388
+ return model
1389
+
1390
+ @register_model
1391
+ def eradio(pretrained=False, **kwargs):
1392
+ return eradio_large_fullres_ws16(pretrained=pretrained, **kwargs)
extra_models.py ADDED
@@ -0,0 +1,206 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from distutils.version import LooseVersion
2
+ from types import MethodType
3
+ from typing import List, Optional, Tuple, Union
4
+ import warnings
5
+
6
+ import torch
7
+ from torch import nn
8
+ import torch.nn.functional as F
9
+
10
+ from timm.models.registry import register_model
11
+ from timm.data.constants import IMAGENET_DEFAULT_MEAN, IMAGENET_DEFAULT_STD
12
+
13
+ from .forward_intermediates import forward_intermediates
14
+ from .input_conditioner import InputConditioner
15
+
16
+ _has_torch_sdpa = hasattr(F, 'scaled_dot_product_attention')
17
+
18
+
19
+ class PaliGemmaWrapper(nn.Module):
20
+ def __init__(self, vis_model: nn.Module, embed_dim: int):
21
+ super().__init__()
22
+
23
+ self.vis_model = vis_model
24
+ self.embed_dim = embed_dim
25
+
26
+ @property
27
+ def patch_size(self):
28
+ return self.vis_model.embeddings.patch_size
29
+
30
+ @property
31
+ def blocks(self):
32
+ return self.vis_model.encoder.layers
33
+
34
+ @property
35
+ def embed_dim(self):
36
+ return self.vis_model.embeddings.embed_dim
37
+
38
+ def forward(self, x: torch.Tensor):
39
+ outputs = self.vis_model(
40
+ x,
41
+ return_dict=False,
42
+ interpolate_pos_encoding=True,
43
+ )
44
+
45
+ features = outputs[0].to(torch.float32)
46
+
47
+ summary = features.mean(dim=1)
48
+
49
+ return summary, features
50
+
51
+ def forward_features(self, x: torch.Tensor):
52
+ return self(x)
53
+
54
+
55
+ def _get_paligemma_model(repo: str, embed_dim: int = None, dtype: torch.dtype = torch.bfloat16):
56
+ from transformers import PaliGemmaForConditionalGeneration, __version__ as tx_version
57
+
58
+ if LooseVersion(tx_version) > LooseVersion('4.44.2'):
59
+ warnings.warn(f'Your transformers version "{tx_version}" is higher than 4.44.2, and for whatever reason, PaliGemma might be broken.')
60
+
61
+ extra_args = dict()
62
+
63
+ if dtype is not None:
64
+ extra_args['torch_dtype'] = dtype
65
+ rev = str(dtype).split('.')[-1]
66
+ extra_args['revision'] = rev
67
+
68
+ model = PaliGemmaForConditionalGeneration.from_pretrained(repo, **extra_args)
69
+
70
+ vis_model = model.vision_tower.vision_model
71
+
72
+ vis_model = PaliGemmaWrapper(vis_model, embed_dim)
73
+
74
+ return vis_model
75
+
76
+ @register_model
77
+ def paligemma_896_student(**kwargs):
78
+ model = _get_paligemma_model('google/paligemma-3b-pt-896', embed_dim=1152, dtype=None)
79
+
80
+ return model
81
+
82
+
83
+ def dv2_sdpa(self, x: torch.Tensor) -> torch.Tensor:
84
+ B, N, C = x.shape
85
+ qkv = self.qkv(x).reshape(B, N, 3, self.num_heads, C // self.num_heads).permute(2, 0, 3, 1, 4)
86
+
87
+ q, k, v = qkv[0], qkv[1], qkv[2]
88
+ x = F.scaled_dot_product_attention(
89
+ q, k, v,
90
+ is_causal=False,
91
+ dropout_p=self.attn_drop.p if self.training else 0.,
92
+ scale=self.scale,
93
+ )
94
+ x = x.transpose(1, 2).reshape(B, N, C)
95
+ x = self.proj(x)
96
+ x = self.proj_drop(x)
97
+ return x
98
+
99
+ def _load_dino_v2(dino_v2_model, cache_dir: Optional[str] = None, pretrained=True, **kwargs):
100
+ if cache_dir:
101
+ torch.hub.set_dir(cache_dir)
102
+ model: nn.Module = torch.hub.load(
103
+ 'facebookresearch/dinov2',
104
+ dino_v2_model,
105
+ pretrained=pretrained,
106
+ # **kwargs,
107
+ )
108
+
109
+ if _has_torch_sdpa:
110
+ for n, m in model.named_modules():
111
+ if n.endswith('.attn'):
112
+ m.forward = MethodType(dv2_sdpa, m)
113
+
114
+ return model
115
+
116
+ class DinoWrapper(nn.Module):
117
+ def __init__(self, dino_model: nn.Module):
118
+ super().__init__()
119
+
120
+ self.inner = dino_model
121
+ dino_model.blocks = nn.Sequential(*dino_model.blocks)
122
+
123
+ @property
124
+ def embed_dim(self):
125
+ return self.inner.embed_dim
126
+
127
+ @property
128
+ def patch_size(self):
129
+ return self.inner.patch_size
130
+
131
+ @property
132
+ def num_cls_tokens(self):
133
+ return getattr(self.inner, 'num_tokens', 1)
134
+
135
+ @property
136
+ def num_registers(self):
137
+ return getattr(self.inner, 'num_register_tokens', 0)
138
+
139
+ @property
140
+ def num_summary_tokens(self):
141
+ return self.num_cls_tokens + self.num_registers
142
+
143
+ @property
144
+ def blocks(self):
145
+ return self.inner.blocks
146
+
147
+ def forward(self, *args, **kwargs) -> Tuple[torch.Tensor, torch.Tensor]:
148
+ parts = self.inner.forward_features(*args, **kwargs)
149
+
150
+ cls_token = parts['x_norm_clstoken']
151
+ features = parts['x_norm_patchtokens']
152
+
153
+ return cls_token, features
154
+
155
+ def forward_features(self, x: torch.Tensor):
156
+ x = self.inner.prepare_tokens_with_masks(x)
157
+ x = self.inner.blocks(x)
158
+ x_norm = self.inner.norm(x)
159
+
160
+ return x_norm[:, 0], x_norm[:, self.num_summary_tokens:]
161
+
162
+ def patchify(self, x: torch.Tensor) -> torch.Tensor:
163
+ return self.inner.prepare_tokens_with_masks(x)
164
+
165
+ def forward_intermediates(self,
166
+ x: torch.Tensor,
167
+ norm: bool = False,
168
+ **kwargs,
169
+ ) -> Union[List[torch.Tensor], Tuple[torch.Tensor, List[torch.Tensor]]]:
170
+ return forward_intermediates(
171
+ self,
172
+ patch_extractor=self.inner.prepare_tokens_with_masks,
173
+ num_summary_tokens=self.num_summary_tokens,
174
+ num_cls_tokens=self.num_cls_tokens,
175
+ norm=self.inner.norm if norm else lambda y: y,
176
+ x=x,
177
+ **kwargs,
178
+ )
179
+
180
+
181
+ def _dino_student(arch: str, **kwargs):
182
+ from . import dinov2_arch
183
+
184
+ factory = getattr(dinov2_arch, arch)
185
+ model = factory()
186
+
187
+ model = DinoWrapper(model)
188
+
189
+ conditioner = InputConditioner(
190
+ input_scale=1.0,
191
+ norm_mean=IMAGENET_DEFAULT_MEAN,
192
+ norm_std=IMAGENET_DEFAULT_STD,
193
+ )
194
+
195
+ model.input_conditioner = conditioner
196
+
197
+ return model
198
+
199
+
200
+ @register_model
201
+ def dino_v2_l_student(**kwargs):
202
+ return _dino_student('dinov2_vitl14_reg', **kwargs)
203
+
204
+ @register_model
205
+ def dino_v2_g_student(**kwargs):
206
+ return _dino_student('dinov2_vitg14_reg', **kwargs)
extra_timm_models.py ADDED
@@ -0,0 +1,206 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) 2023-2024, NVIDIA CORPORATION. All rights reserved.
2
+ #
3
+ # NVIDIA CORPORATION and its licensors retain all intellectual property
4
+ # and proprietary rights in and to this software, related documentation
5
+ # and any modifications thereto. Any use, reproduction, disclosure or
6
+ # distribution of this software and related documentation without an express
7
+ # license agreement from NVIDIA CORPORATION is strictly prohibited.
8
+
9
+ import math
10
+ import warnings
11
+
12
+ import torch
13
+ from torch import nn
14
+ from torch.nn import functional as F
15
+
16
+ from timm.models import register_model
17
+ from timm.models.vision_transformer import (
18
+ VisionTransformer,
19
+ _create_vision_transformer as _timm_create_vision_transformer,
20
+ Mlp,
21
+ Block,
22
+ LayerScale as TIMMLayerScale,
23
+ )
24
+
25
+ # Import these to also register them
26
+ from . import dinov2_arch
27
+
28
+
29
+ @register_model
30
+ def vit_tiny_patch14_224(pretrained=False, **kwargs) -> VisionTransformer:
31
+ """ ViT-Tiny (Vit-Ti/16)
32
+ """
33
+ model_args = dict(patch_size=14, embed_dim=192, depth=12, num_heads=3)
34
+ model = _create_vision_transformer('vit_tiny_patch14_224', pretrained=pretrained, **dict(model_args, **kwargs))
35
+ return model
36
+
37
+
38
+ @register_model
39
+ def vit_small_patch14_224(pretrained=False, **kwargs) -> VisionTransformer:
40
+ """ ViT-Small (ViT-S/16)
41
+ """
42
+ model_args = dict(patch_size=14, embed_dim=384, depth=12, num_heads=6)
43
+ model = _create_vision_transformer('vit_small_patch16_224', pretrained=pretrained, **dict(model_args, **kwargs))
44
+ return model
45
+
46
+
47
+ @register_model
48
+ def vit_base_patch14_224(pretrained=False, **kwargs) -> VisionTransformer:
49
+ """ ViT-Base (ViT-B/14) from original paper (https://arxiv.org/abs/2010.11929).
50
+ ImageNet-1k weights fine-tuned from in21k @ 224x224, source https://github.com/google-research/vision_transformer.
51
+ """
52
+ model_args = dict(patch_size=14, embed_dim=768, depth=12, num_heads=12)
53
+ model = _create_vision_transformer('vit_base_patch14_224', pretrained=pretrained, **dict(model_args, **kwargs))
54
+ return model
55
+
56
+
57
+ @register_model
58
+ def vit_base_patch16_v2_224(pretrained=False, **kwargs) -> VisionTransformer:
59
+ """ ViT-Base (ViT-B/16) from original paper (https://arxiv.org/abs/2010.11929).
60
+ ImageNet-1k weights fine-tuned from in21k @ 224x224, source https://github.com/google-research/vision_transformer.
61
+ """
62
+ model_args = dict(
63
+ patch_size=16, embed_dim=768, depth=12, num_heads=12, init_values=1e-5,
64
+ reg_tokens=4, no_embed_class=True, img_size=518 * 16 // 14
65
+ )
66
+ model = _create_vision_transformer(
67
+ 'vit_base_patch14_reg4_dinov2', pretrained=pretrained, **dict(model_args, **kwargs))
68
+ return model
69
+
70
+
71
+ @register_model
72
+ def vit_large_patch16_v2_224(pretrained: bool = False, **kwargs) -> VisionTransformer:
73
+ """ ViT-Large model (ViT-L/16) from original paper (https://arxiv.org/abs/2010.11929).
74
+ ImageNet-1k weights fine-tuned from in21k @ 224x224, source https://github.com/google-research/vision_transformer.
75
+ """
76
+ name = 'vit_large_patch14_reg4_dinov2'
77
+ model_args = dict(
78
+ patch_size=16, embed_dim=1024, depth=24, num_heads=16, init_values=1e-5,
79
+ reg_tokens=4, no_embed_class=True, img_size=518 * 16 // 14
80
+ )
81
+ model = _create_vision_transformer(name, pretrained=pretrained, **dict(model_args, **kwargs))
82
+
83
+ return model
84
+
85
+ @register_model
86
+ def vit_huge_patch16_224(pretrained=False, **kwargs) -> VisionTransformer:
87
+ """ ViT-Huge model (ViT-H/16) from original paper (https://arxiv.org/abs/2010.11929).
88
+ """
89
+ model_args = dict(patch_size=16, embed_dim=1280, depth=32, num_heads=16)
90
+ if pretrained:
91
+ # There is no pretrained version of ViT-H/16, but we can adapt a ViT-H/14 for this purpose
92
+ model = _create_vision_transformer('vit_huge_patch14_224', pretrained=True, **dict(model_args, **kwargs))
93
+ else:
94
+ model = _create_vision_transformer('vit_huge_patch16_224', pretrained=False, **dict(model_args, **kwargs))
95
+ return model
96
+
97
+
98
+ @register_model
99
+ def vit_huge_patch16_224_mlpnorm(pretrained=False, **kwargs) -> VisionTransformer:
100
+ """ ViT-Huge model (ViT-H/16) from original paper (https://arxiv.org/abs/2010.11929).
101
+ """
102
+ model = vit_huge_patch16_224(pretrained=pretrained, **kwargs)
103
+
104
+ for m in model.modules():
105
+ if isinstance(m, Mlp) and not isinstance(m.norm, nn.LayerNorm):
106
+ m.norm = nn.LayerNorm(m.fc1.out_features)
107
+
108
+ return model
109
+
110
+
111
+ @register_model
112
+ def vit_giant_patch16_224(pretrained=False, scaled_ln: bool = False, **kwargs) -> VisionTransformer:
113
+ """ ViT-giant model (ViT-g/16) from original paper (https://arxiv.org/abs/2010.11929).
114
+ """
115
+ model_args = dict(patch_size=16, embed_dim=1536, depth=40, num_heads=24)
116
+ model = _create_vision_transformer('vit_giant_patch16_224', pretrained=False, **dict(model_args, **kwargs))
117
+ if scaled_ln:
118
+ _apply_scaled_ln(model)
119
+ return model
120
+
121
+
122
+ @register_model
123
+ def vit_bigG_patch14_224(pretrained=False, **kwargs) -> VisionTransformer:
124
+ model_args = dict(patch_size=14, embed_dim=1664, depth=48, num_heads=16, init_values=1e-6)
125
+ model = _create_vision_transformer('vit_bigG_patch14', pretrained=False, **dict(model_args, **kwargs))
126
+ return model
127
+
128
+
129
+ def _create_vision_transformer(*args, **kwargs):
130
+ model = _timm_create_vision_transformer(*args, **kwargs)
131
+ _patch_layer_scale(model)
132
+ return model
133
+
134
+
135
+ def _patch_layer_scale(model: VisionTransformer):
136
+ def replace_ls(old_ls: TIMMLayerScale):
137
+ new_ls = dinov2_arch.LayerScale(old_ls.gamma.shape[0], inplace=old_ls.inplace)
138
+ new_ls.load_state_dict(old_ls.state_dict())
139
+ return new_ls
140
+
141
+ # Monkey patch: Replace TIMM's LayerScale with our modified DINOv2 one, that uses a param name
142
+ # other than gamma, so that HFHub doesn't mess with it!
143
+ for mod in model.modules():
144
+ if isinstance(mod, Block):
145
+ if isinstance(mod.ls1, TIMMLayerScale):
146
+ mod.ls1 = replace_ls(mod.ls1)
147
+ if isinstance(mod.ls2, TIMMLayerScale):
148
+ mod.ls2 = replace_ls(mod.ls2)
149
+ pass
150
+
151
+
152
+ class ScaledLayerNorm(nn.LayerNorm):
153
+ '''
154
+ https://arxiv.org/pdf/2502.05795v1
155
+ '''
156
+ def __init__(self, ln_base: nn.LayerNorm, depth: int = 0):
157
+ super().__init__(ln_base.normalized_shape, eps=ln_base.eps, elementwise_affine=ln_base.elementwise_affine)
158
+ self.load_state_dict(ln_base.state_dict())
159
+ self.register_buffer('ln_scale', torch.tensor(1.0 / math.sqrt(depth)), persistent=False)
160
+
161
+ def forward(self, x):
162
+ y = super().forward(x)
163
+ y = y * self.ln_scale
164
+ return y
165
+
166
+
167
+ class DyT(nn.Module):
168
+ def __init__(self, C: int, init_alpha: float):
169
+ super().__init__()
170
+ self.alpha = nn.Parameter(torch.full((1,), init_alpha))
171
+ self.gamma = nn.Parameter(torch.ones(C))
172
+ self.beta = nn.Parameter(torch.zeros(C))
173
+
174
+ def forward(self, x: torch.Tensor):
175
+ x = F.tanh(self.alpha * x)
176
+ return self.gamma * x + self.beta
177
+
178
+ @register_model
179
+ def vit_large_dyt_patch16_224(pretrained: bool = False, **kwargs) -> VisionTransformer:
180
+ """ ViT-Large model (ViT-L/16) from original paper (https://arxiv.org/abs/2010.11929).
181
+ ImageNet-1k weights fine-tuned from in21k @ 224x224, source https://github.com/google-research/vision_transformer.
182
+ """
183
+ model_args = dict(patch_size=16, embed_dim=1024, depth=24, num_heads=16)
184
+ model = _create_vision_transformer('vit_large_dyt_patch16_224', pretrained=pretrained, **dict(model_args, **kwargs))
185
+
186
+ def _replace_ln_with_dyt(ln: nn.LayerNorm, depth: int):
187
+ return DyT(ln.normalized_shape[0], init_alpha=0.9)
188
+ _replace_ln(model, _replace_ln_with_dyt)
189
+
190
+ return model
191
+
192
+
193
+ def _apply_scaled_ln(model: VisionTransformer):
194
+ warnings.warn('Post-LayerNorm scaling activated!')
195
+
196
+ _replace_ln(model, lambda ln, depth: ScaledLayerNorm(ln, depth=depth))
197
+
198
+ def _replace_ln(model: VisionTransformer, fn):
199
+ def _inner_replace_ln(block: Block, depth: int, key: str):
200
+ prev = getattr(block, key)
201
+ if isinstance(prev, nn.LayerNorm):
202
+ setattr(block, key, fn(prev, depth=depth))
203
+
204
+ for i, block in enumerate(model.blocks):
205
+ _inner_replace_ln(block, i + 1, 'norm1')
206
+ _inner_replace_ln(block, i + 1, 'norm2')
feature_normalizer.py ADDED
@@ -0,0 +1,111 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) 2023-2024, NVIDIA CORPORATION. All rights reserved.
2
+ #
3
+ # NVIDIA CORPORATION and its licensors retain all intellectual property
4
+ # and proprietary rights in and to this software, related documentation
5
+ # and any modifications thereto. Any use, reproduction, disclosure or
6
+ # distribution of this software and related documentation without an express
7
+ # license agreement from NVIDIA CORPORATION is strictly prohibited.
8
+ from collections import namedtuple
9
+ from typing import NamedTuple, Optional, Tuple
10
+ import torch
11
+ from torch import nn
12
+
13
+
14
+ def _run_kernel(x: torch.Tensor, mean: torch.Tensor, tx: torch.Tensor):
15
+ if x.ndim <= 3:
16
+ x = x - mean
17
+ x = x @ tx.T
18
+ elif x.ndim == 4:
19
+ x = x - mean.reshape(1, -1, 1, 1)
20
+ kernel = tx.reshape(*tx.shape, 1, 1)
21
+ x = torch.nn.functional.conv2d(x, weight=kernel, bias=None, stride=1, padding=0)
22
+ else:
23
+ raise ValueError(f'Unsupported input dimension: {x.ndim}, shape: {x.shape}')
24
+ return x
25
+
26
+
27
+ class FeatureNormalizer(nn.Module):
28
+ def __init__(self, embed_dim: int, dtype: torch.dtype = torch.float32):
29
+ super().__init__()
30
+
31
+ self.register_buffer('mean', torch.zeros(embed_dim, dtype=dtype))
32
+ self.register_buffer('tx', torch.eye(embed_dim, dtype=dtype))
33
+
34
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
35
+ x = _run_kernel(x, self.mean, self.tx)
36
+ return x
37
+
38
+
39
+ class InterFeatState(NamedTuple):
40
+ y: torch.Tensor
41
+ alpha: torch.Tensor
42
+
43
+
44
+ class IntermediateFeatureNormalizerBase(nn.Module):
45
+ def forward(self, x: torch.Tensor, index: int, rot_index: int = None, skip: Optional[int] = None) -> InterFeatState:
46
+ raise NotImplementedError()
47
+
48
+
49
+ class IntermediateFeatureNormalizer(IntermediateFeatureNormalizerBase):
50
+ def __init__(self, num_intermediates: int, embed_dim: int, rot_per_layer: bool = False, dtype: torch.dtype = torch.float32):
51
+ super().__init__()
52
+ self.register_buffer('alphas', torch.ones(num_intermediates, dtype=dtype))
53
+
54
+ rot = torch.eye(embed_dim, dtype=dtype)
55
+ if rot_per_layer:
56
+ rot = rot.unsqueeze(0).repeat(num_intermediates, 1, 1)
57
+
58
+ self.register_buffer('rotation', rot.contiguous())
59
+ self.register_buffer('means', torch.zeros(num_intermediates, embed_dim, dtype=dtype))
60
+
61
+ def forward(self, x: torch.Tensor, index: int, rot_index: int = None, skip: Optional[int] = None) -> InterFeatState:
62
+ if rot_index is None:
63
+ rot_index = index
64
+
65
+ if skip:
66
+ assert x.ndim == 3, f'Cannot use the `skip` parameter when the `x` tensor isn\'t 3-dimensional.'
67
+ prefix, x = x[:, :skip], x[:, skip:]
68
+
69
+ rotation = self._get_rotation(rot_index)
70
+ y = _run_kernel(x, self.means[index], rotation)
71
+
72
+ alpha = self.alphas[index]
73
+ if skip:
74
+ alpha = torch.cat([
75
+ torch.ones(skip, dtype=alpha.dtype, device=alpha.device),
76
+ alpha[None].expand(y.shape[1]),
77
+ ]).reshape(1, -1, 1)
78
+ y = torch.cat([prefix, y], dim=1)
79
+ else:
80
+ if x.ndim == 3:
81
+ alpha = alpha.reshape(1, 1, 1).expand(1, y.shape[1], 1)
82
+ elif x.ndim == 4:
83
+ alpha = alpha.reshape(1, 1, 1, 1).expand(1, 1, *y.shape[2:])
84
+ else:
85
+ raise ValueError(f'Unsupported input dimension: {x.ndim}')
86
+
87
+ return InterFeatState(y, alpha)
88
+
89
+ def _get_rotation(self, rot_index: int) -> torch.Tensor:
90
+ if self.rotation.ndim == 2:
91
+ return self.rotation
92
+ return self.rotation[rot_index]
93
+
94
+
95
+ class NullIntermediateFeatureNormalizer(IntermediateFeatureNormalizerBase):
96
+ instances = dict()
97
+
98
+ def __init__(self, dtype: torch.dtype, device: torch.device):
99
+ super().__init__()
100
+ self.register_buffer('alpha', torch.tensor(1, dtype=dtype, device=device))
101
+
102
+ @staticmethod
103
+ def get_instance(dtype: torch.dtype, device: torch.device):
104
+ instance = NullIntermediateFeatureNormalizer.instances.get((dtype, device), None)
105
+ if instance is None:
106
+ instance = NullIntermediateFeatureNormalizer(dtype, device)
107
+ NullIntermediateFeatureNormalizer.instances[(dtype, device)] = instance
108
+ return instance
109
+
110
+ def forward(self, x: torch.Tensor, index: int, rot_index: int = None, skip: Optional[int] = None) -> InterFeatState:
111
+ return InterFeatState(x, self.alpha)
forward_intermediates.py ADDED
@@ -0,0 +1,138 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) 2023-2024, NVIDIA CORPORATION. All rights reserved.
2
+ #
3
+ # NVIDIA CORPORATION and its licensors retain all intellectual property
4
+ # and proprietary rights in and to this software, related documentation
5
+ # and any modifications thereto. Any use, reproduction, disclosure or
6
+ # distribution of this software and related documentation without an express
7
+ # license agreement from NVIDIA CORPORATION is strictly prohibited.
8
+
9
+ from typing import Callable, Dict, List, Optional, Set, Tuple, Union, Any, Iterable
10
+ from types import MethodType
11
+
12
+ import torch
13
+ from torch import nn
14
+
15
+ from .feature_normalizer import IntermediateFeatureNormalizerBase, NullIntermediateFeatureNormalizer
16
+
17
+
18
+ def _take_indices(
19
+ num_blocks: int,
20
+ n: Optional[Union[int, List[int], Tuple[int]]],
21
+ ) -> Tuple[Set[int], int]:
22
+ if isinstance(n, int):
23
+ assert n >= 0
24
+ take_indices = {x for x in range(num_blocks - n, num_blocks)}
25
+ else:
26
+ take_indices = {num_blocks + idx if idx < 0 else idx for idx in n}
27
+ return take_indices, max(take_indices)
28
+
29
+
30
+ def forward_intermediates(
31
+ model: nn.Module,
32
+ patch_extractor: Callable[[torch.Tensor], torch.Tensor],
33
+ norm: nn.Module,
34
+ num_summary_tokens: int,
35
+ num_cls_tokens: int,
36
+ x: torch.Tensor,
37
+ indices: Optional[Union[int, List[int], Tuple[int]]] = None,
38
+ return_prefix_tokens: bool = False,
39
+ stop_early: bool = False,
40
+ output_fmt: str = 'NCHW',
41
+ intermediates_only: bool = False,
42
+ aggregation: Optional[str] = "sparse",
43
+ inter_feature_normalizer: Optional[IntermediateFeatureNormalizerBase] = None,
44
+ norm_alpha_scheme = "post-alpha",
45
+ block_kwargs: Dict = None,
46
+ ) -> Union[List[torch.Tensor], Tuple[torch.Tensor, List[torch.Tensor]]]:
47
+ """ Forward features that returns intermediates.
48
+
49
+ The Dense layer aggregation method is inspired from the paper: "Dense Connector for MLLMs"
50
+ by Yao, Huanjin et al. (2024). arXiv preprint arXiv:2405.13800}
51
+
52
+ Args:
53
+ x: Input image tensor
54
+ indices: Take last n blocks if int, select matching indices if sequence
55
+ return_prefix_tokens: Return both prefix and spatial intermediate tokens
56
+ norm: Apply norm layer to all intermediates
57
+ stop_early: Stop iterating over blocks when last desired intermediate hit
58
+ output_fmt: Shape of intermediate feature outputs
59
+ intermediates_only: Only return intermediate features
60
+ aggregation: intermediate layer aggregation method (sparse or dense)
61
+ norm_alpha_scheme: apply alpha before ("pre-alpha") or after accumulation ("post-alpha")
62
+ Returns:
63
+ """
64
+ assert output_fmt in ('NCHW', 'NLC'), 'Output format must be one of NCHW or NLC.'
65
+ assert aggregation in ('sparse', 'dense'), 'Aggregation must be one of sparse or dense.'
66
+ reshape = output_fmt == 'NCHW'
67
+ intermediates = []
68
+
69
+ block_kwargs = block_kwargs or dict()
70
+
71
+ blocks = model.blocks
72
+
73
+ take_indices, max_index = _take_indices(len(blocks), indices)
74
+ take_indices = sorted(take_indices)
75
+ # forward pass
76
+ B, _, height, width = x.shape
77
+
78
+ x = patch_extractor(x)
79
+
80
+ if stop_early:
81
+ blocks = blocks[:max_index + 1]
82
+
83
+ if inter_feature_normalizer is None or norm_alpha_scheme == 'none':
84
+ inter_feature_normalizer = NullIntermediateFeatureNormalizer.get_instance(x.dtype, x.device)
85
+
86
+ assert norm_alpha_scheme in ('none', 'pre-alpha', 'post-alpha'), f'Unsupported alpha scheme: {norm_alpha_scheme}'
87
+ post_alpha_scheme = norm_alpha_scheme == 'post-alpha'
88
+
89
+ accumulator = 0
90
+ alpha_sum = 0
91
+ num_accumulated = 0
92
+
93
+ take_off = 0
94
+
95
+ for i, blk in enumerate(blocks):
96
+ x = blk(x, **block_kwargs)
97
+ if aggregation == "dense":
98
+ # Arbitrarily use the rotation matrix from the final layer in the dense group
99
+ y, alpha = inter_feature_normalizer(x, i, rot_index=take_indices[take_off], skip=num_summary_tokens)
100
+ if post_alpha_scheme:
101
+ accumulator = accumulator + y
102
+ alpha_sum = alpha_sum + alpha
103
+ else:
104
+ accumulator = accumulator + (alpha * y)
105
+ alpha_sum += 1
106
+ num_accumulated += 1
107
+ if i == take_indices[take_off]:
108
+ if aggregation == "dense":
109
+ alpha = alpha_sum / num_accumulated
110
+ x_ = alpha * accumulator / num_accumulated
111
+ num_accumulated = 0
112
+ accumulator = 0
113
+ alpha_sum = 0
114
+ else:
115
+ y, alpha = inter_feature_normalizer(x, i, skip=num_summary_tokens)
116
+ x_ = alpha * y
117
+ # normalize intermediates with final norm layer if enabled
118
+ intermediates.append(norm(x_))
119
+ take_off = min(take_off + 1, len(take_indices) - 1)
120
+
121
+ # process intermediates
122
+
123
+ # split prefix (e.g. class, distill) and spatial feature tokens
124
+ prefix_tokens = [y[:, :num_cls_tokens] for y in intermediates]
125
+ intermediates = [y[:, num_summary_tokens:] for y in intermediates]
126
+
127
+ if reshape:
128
+ # reshape to BCHW output format
129
+ H = height // model.patch_size
130
+ W = width // model.patch_size
131
+ intermediates = [y.reshape(B, H, W, -1).permute(0, 3, 1, 2).contiguous() for y in intermediates]
132
+ if not torch.jit.is_scripting() and return_prefix_tokens:
133
+ # return_prefix not support in torchscript due to poor type handling
134
+ intermediates = list(zip(prefix_tokens, intermediates))
135
+ if intermediates_only:
136
+ return intermediates
137
+ x = norm(x)
138
+ return x, intermediates
hf_model.py ADDED
@@ -0,0 +1,204 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) 2023-2024, NVIDIA CORPORATION. All rights reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ from collections import namedtuple
15
+ from typing import Callable, Dict, Optional, List, Union
16
+
17
+ from timm.models import VisionTransformer
18
+ import torch
19
+ from torch import nn
20
+ from transformers import PretrainedConfig, PreTrainedModel
21
+
22
+
23
+ from .common import RESOURCE_MAP, DEFAULT_VERSION
24
+
25
+ # Import all required modules.
26
+ from .adaptor_base import AdaptorBase, RadioOutput, AdaptorInput
27
+ from .adaptor_generic import GenericAdaptor, AdaptorBase
28
+ from .adaptor_mlp import create_mlp_from_config
29
+ from .adaptor_registry import adaptor_registry
30
+ from .cls_token import ClsToken
31
+ from .dinov2_arch import dinov2_vitg14_reg
32
+ from .enable_cpe_support import enable_cpe
33
+ from .enable_spectral_reparam import configure_spectral_reparam_from_args
34
+ from .eradio_model import eradio
35
+ from .feature_normalizer import FeatureNormalizer, IntermediateFeatureNormalizer
36
+ from .forward_intermediates import forward_intermediates
37
+ from .radio_model import create_model_from_args
38
+ from .radio_model import RADIOModel as RADIOModelBase, Resolution
39
+ from .input_conditioner import get_default_conditioner, InputConditioner
40
+ from .open_clip_adaptor import OpenCLIP_RADIO
41
+ from .vit_patch_generator import ViTPatchGenerator
42
+ from .vitdet import apply_vitdet_arch, VitDetArgs
43
+
44
+ # Register extra models
45
+ from .extra_timm_models import *
46
+ from .extra_models import *
47
+
48
+
49
+ class RADIOConfig(PretrainedConfig):
50
+ """Pretrained Hugging Face configuration for RADIO models."""
51
+
52
+ def __init__(
53
+ self,
54
+ args: Optional[dict] = None,
55
+ version: Optional[str] = DEFAULT_VERSION,
56
+ patch_size: Optional[int] = None,
57
+ max_resolution: Optional[int] = None,
58
+ preferred_resolution: Optional[Resolution] = None,
59
+ adaptor_names: Union[str, List[str]] = None,
60
+ adaptor_configs: Dict[str, Dict[str, int]] = None,
61
+ vitdet_window_size: Optional[int] = None,
62
+ feature_normalizer_config: Optional[dict] = None,
63
+ inter_feature_normalizer_config: Optional[dict] = None,
64
+ **kwargs,
65
+ ):
66
+ self.args = args
67
+ for field in ["dtype", "amp_dtype"]:
68
+ if self.args is not None and field in self.args:
69
+ # Convert to a string in order to make it serializable.
70
+ # For example for torch.float32 we will store "float32",
71
+ # for "bfloat16" we will store "bfloat16".
72
+ self.args[field] = str(args[field]).split(".")[-1]
73
+ self.version = version
74
+ resource = RESOURCE_MAP[version]
75
+ self.patch_size = patch_size or resource.patch_size
76
+ self.max_resolution = max_resolution or resource.max_resolution
77
+ self.preferred_resolution = (
78
+ preferred_resolution or resource.preferred_resolution
79
+ )
80
+ self.adaptor_names = adaptor_names
81
+ self.adaptor_configs = adaptor_configs
82
+ self.vitdet_window_size = vitdet_window_size
83
+ self.feature_normalizer_config = feature_normalizer_config
84
+ self.inter_feature_normalizer_config = inter_feature_normalizer_config
85
+ super().__init__(**kwargs)
86
+
87
+
88
+
89
+ class RADIOModel(PreTrainedModel):
90
+ """Pretrained Hugging Face model for RADIO.
91
+
92
+ This class inherits from PreTrainedModel, which provides
93
+ HuggingFace's functionality for loading and saving models.
94
+ """
95
+
96
+ config_class = RADIOConfig
97
+
98
+ def __init__(self, config: RADIOConfig):
99
+ super().__init__(config)
100
+ if hasattr(super(), "post_init"):
101
+ super().post_init()
102
+
103
+ RADIOArgs = namedtuple("RADIOArgs", config.args.keys())
104
+ args = RADIOArgs(**config.args)
105
+ self.config = config
106
+
107
+ model = create_model_from_args(args)
108
+ input_conditioner: InputConditioner = get_default_conditioner()
109
+
110
+ dtype = getattr(args, "dtype", torch.float32)
111
+ if isinstance(dtype, str):
112
+ # Convert the dtype's string representation back to a dtype.
113
+ dtype = getattr(torch, dtype)
114
+ model.to(dtype=dtype)
115
+ input_conditioner.dtype = dtype
116
+
117
+ summary_idxs = torch.tensor(
118
+ [i for i, t in enumerate(args.teachers) if t.get("use_summary", True)],
119
+ dtype=torch.int64,
120
+ )
121
+
122
+ adaptor_configs = config.adaptor_configs
123
+ adaptor_names = config.adaptor_names or []
124
+
125
+ adaptors = dict()
126
+ for adaptor_name in adaptor_names:
127
+ mlp_config = adaptor_configs[adaptor_name]
128
+ adaptor = GenericAdaptor(args, None, None, mlp_config)
129
+ adaptor.head_idx = mlp_config["head_idx"]
130
+ adaptors[adaptor_name] = adaptor
131
+
132
+ feature_normalizer = None
133
+ if config.feature_normalizer_config is not None:
134
+ # Actual normalization values will be restored when loading checkpoint weights.
135
+ feature_normalizer = FeatureNormalizer(config.feature_normalizer_config["embed_dim"])
136
+
137
+ inter_feature_normalizer = None
138
+ if config.inter_feature_normalizer_config is not None:
139
+ inter_feature_normalizer = IntermediateFeatureNormalizer(
140
+ config.inter_feature_normalizer_config["num_intermediates"],
141
+ config.inter_feature_normalizer_config["embed_dim"],
142
+ rot_per_layer=config.inter_feature_normalizer_config["rot_per_layer"],
143
+ dtype=dtype)
144
+
145
+ self.radio_model = RADIOModelBase(
146
+ model,
147
+ input_conditioner,
148
+ summary_idxs=summary_idxs,
149
+ patch_size=config.patch_size,
150
+ max_resolution=config.max_resolution,
151
+ window_size=config.vitdet_window_size,
152
+ preferred_resolution=config.preferred_resolution,
153
+ adaptors=adaptors,
154
+ feature_normalizer=feature_normalizer,
155
+ inter_feature_normalizer=inter_feature_normalizer,
156
+ )
157
+
158
+ @property
159
+ def adaptors(self) -> nn.ModuleDict:
160
+ return self.radio_model.adaptors
161
+
162
+ @property
163
+ def model(self) -> VisionTransformer:
164
+ return self.radio_model.model
165
+
166
+ @property
167
+ def input_conditioner(self) -> InputConditioner:
168
+ return self.radio_model.input_conditioner
169
+
170
+ @property
171
+ def num_summary_tokens(self) -> int:
172
+ return self.radio_model.num_summary_tokens
173
+
174
+ @property
175
+ def patch_size(self) -> int:
176
+ return self.radio_model.patch_size
177
+
178
+ @property
179
+ def max_resolution(self) -> int:
180
+ return self.radio_model.max_resolution
181
+
182
+ @property
183
+ def preferred_resolution(self) -> Resolution:
184
+ return self.radio_model.preferred_resolution
185
+
186
+ @property
187
+ def window_size(self) -> int:
188
+ return self.radio_model.window_size
189
+
190
+ @property
191
+ def min_resolution_step(self) -> int:
192
+ return self.radio_model.min_resolution_step
193
+
194
+ def make_preprocessor_external(self) -> Callable[[torch.Tensor], torch.Tensor]:
195
+ return self.radio_model.make_preprocessor_external()
196
+
197
+ def get_nearest_supported_resolution(self, height: int, width: int) -> Resolution:
198
+ return self.radio_model.get_nearest_supported_resolution(height, width)
199
+
200
+ def switch_to_deploy(self):
201
+ return self.radio_model.switch_to_deploy()
202
+
203
+ def forward(self, x: torch.Tensor):
204
+ return self.radio_model.forward(x)
input_conditioner.py ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) 2023-2024, NVIDIA CORPORATION. All rights reserved.
2
+ #
3
+ # NVIDIA CORPORATION and its licensors retain all intellectual property
4
+ # and proprietary rights in and to this software, related documentation
5
+ # and any modifications thereto. Any use, reproduction, disclosure or
6
+ # distribution of this software and related documentation without an express
7
+ # license agreement from NVIDIA CORPORATION is strictly prohibited.
8
+
9
+ from typing import Union, Tuple
10
+
11
+ import torch
12
+ from torch import nn
13
+
14
+
15
+ norm_t = Union[Tuple[float, float, float], torch.Tensor]
16
+
17
+ class InputConditioner(nn.Module):
18
+ def __init__(self,
19
+ input_scale: float,
20
+ norm_mean: norm_t,
21
+ norm_std: norm_t,
22
+ dtype: torch.dtype = None,
23
+ ):
24
+ super().__init__()
25
+
26
+ self.dtype = dtype
27
+
28
+ self.register_buffer("norm_mean", _to_tensor(norm_mean) / input_scale)
29
+ self.register_buffer("norm_std", _to_tensor(norm_std) / input_scale)
30
+
31
+ def forward(self, x: torch.Tensor):
32
+ y = (x - self.norm_mean) / self.norm_std
33
+ if self.dtype is not None:
34
+ y = y.to(self.dtype)
35
+ return y
36
+
37
+
38
+ def get_default_conditioner():
39
+ from timm.data.constants import OPENAI_CLIP_MEAN, OPENAI_CLIP_STD
40
+
41
+ return InputConditioner(
42
+ input_scale=1.0,
43
+ norm_mean=OPENAI_CLIP_MEAN,
44
+ norm_std=OPENAI_CLIP_STD,
45
+ )
46
+
47
+
48
+ def _to_tensor(v: norm_t):
49
+ return torch.as_tensor(v, dtype=torch.float32).view(-1, 1, 1)
model.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:6b97a28721e2f319103d29e4df2552d3a46c25c2c70beb59305cc671adeebfc4
3
+ size 2606616136
open_clip_adaptor.py ADDED
@@ -0,0 +1,41 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) 2024, NVIDIA CORPORATION. All rights reserved.
2
+ #
3
+ # NVIDIA CORPORATION and its licensors retain all intellectual property
4
+ # and proprietary rights in and to this software, related documentation
5
+ # and any modifications thereto. Any use, reproduction, disclosure or
6
+ # distribution of this software and related documentation without an express
7
+ # license agreement from NVIDIA CORPORATION is strictly prohibited.
8
+ from argparse import Namespace
9
+
10
+ import torch
11
+ from torch import nn
12
+ import torch.nn.functional as F
13
+
14
+ from .adaptor_registry import adaptor_registry, dict_t, state_t
15
+
16
+ from .adaptor_generic import GenericAdaptor
17
+
18
+
19
+ class OpenCLIP_RADIO(GenericAdaptor):
20
+ def __init__(self, main_config: Namespace, adaptor_config: dict_t, state: state_t):
21
+ super().__init__(main_config, adaptor_config, state)
22
+
23
+ import open_clip
24
+
25
+ self.oc_model = open_clip.create_model_from_pretrained(
26
+ model_name=adaptor_config['model'],
27
+ pretrained=adaptor_config['pretrained'],
28
+ return_transform=False,
29
+ )
30
+ # Unload these parameters
31
+ self.oc_model.visual = None
32
+
33
+ self.tokenizer = open_clip.get_tokenizer(model_name=adaptor_config['model'])
34
+
35
+ def encode_text(self, text, normalize: bool = False):
36
+ return self.oc_model.encode_text(text, normalize=normalize)
37
+
38
+
39
+ @adaptor_registry.register_adaptor("open_clip")
40
+ def create_open_clip_adaptor(main_config: Namespace, adaptor_config: dict_t, state: state_t):
41
+ return OpenCLIP_RADIO(main_config, adaptor_config, state)
preprocessor_config.json ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "crop_size": {
3
+ "height": 432,
4
+ "width": 432
5
+ },
6
+ "do_center_crop": false,
7
+ "do_convert_rgb": true,
8
+ "do_normalize": false,
9
+ "do_rescale": true,
10
+ "do_resize": true,
11
+ "image_processor_type": "CLIPImageProcessor",
12
+ "processor_class": "CLIPProcessor",
13
+ "resample": 3,
14
+ "size": {
15
+ "shortest_edge": 432
16
+ }
17
+ }
radio_model.py ADDED
@@ -0,0 +1,343 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) 2023-2024, NVIDIA CORPORATION. All rights reserved.
2
+ #
3
+ # NVIDIA CORPORATION and its licensors retain all intellectual property
4
+ # and proprietary rights in and to this software, related documentation
5
+ # and any modifications thereto. Any use, reproduction, disclosure or
6
+ # distribution of this software and related documentation without an express
7
+ # license agreement from NVIDIA CORPORATION is strictly prohibited.
8
+ from typing import Callable, Dict, Iterable, List, NamedTuple, Optional, Tuple, Union
9
+
10
+ import torch
11
+ from torch import nn
12
+
13
+ from timm.models import create_model, VisionTransformer
14
+
15
+ from .enable_cpe_support import enable_cpe
16
+ from .input_conditioner import InputConditioner
17
+ from .adaptor_base import AdaptorBase, RadioOutput, AdaptorInput
18
+ from . import eradio_model
19
+ from .enable_spectral_reparam import configure_spectral_reparam_from_args
20
+ from .feature_normalizer import FeatureNormalizer, IntermediateFeatureNormalizer
21
+ from . import dual_hybrid_vit
22
+
23
+
24
+ class Resolution(NamedTuple):
25
+ height: int
26
+ width: int
27
+
28
+
29
+ class RADIOModel(nn.Module):
30
+ def __init__(
31
+ self,
32
+ model: nn.Module,
33
+ input_conditioner: InputConditioner,
34
+ patch_size: int,
35
+ max_resolution: int,
36
+ preferred_resolution: Resolution,
37
+ summary_idxs: Optional[torch.Tensor] = None,
38
+ window_size: int = None,
39
+ adaptors: Dict[str, AdaptorBase] = None,
40
+ feature_normalizer: Optional[FeatureNormalizer] = None,
41
+ inter_feature_normalizer: Optional[IntermediateFeatureNormalizer] = None,
42
+ ):
43
+ super().__init__()
44
+
45
+ self.model = model
46
+ self.input_conditioner = input_conditioner
47
+ if summary_idxs is not None:
48
+ self.register_buffer('summary_idxs', summary_idxs)
49
+ else:
50
+ self.summary_idxs = None
51
+
52
+ self._preferred_resolution = preferred_resolution
53
+ self._patch_size = patch_size
54
+ self._max_resolution = max_resolution
55
+ self._window_size = window_size
56
+
57
+ adaptors = adaptors or dict()
58
+ self.adaptors = nn.ModuleDict(adaptors)
59
+
60
+ if feature_normalizer is None:
61
+ feature_normalizer = nn.Identity()
62
+ self.feature_normalizer = feature_normalizer
63
+ self.inter_feature_normalizer = inter_feature_normalizer
64
+
65
+ @property
66
+ def num_summary_tokens(self) -> int:
67
+ if hasattr(self.model, 'num_summary_tokens'):
68
+ return self.model.num_summary_tokens
69
+
70
+ patch_gen = getattr(self.model, "patch_generator", None)
71
+ if patch_gen is not None:
72
+ return patch_gen.num_skip
73
+ elif getattr(self.model, 'global_pool', None) == 'avg':
74
+ return 0
75
+ return 1
76
+
77
+ @property
78
+ def num_cls_tokens(self) -> int:
79
+ if hasattr(self.model, 'num_cls_tokens'):
80
+ return self.model.num_cls_tokens
81
+
82
+ patch_gen = getattr(self.model, 'patch_generator', None)
83
+ if patch_gen is not None:
84
+ return patch_gen.num_cls_tokens
85
+ elif getattr(self.model, 'global_pool', None) == 'avg':
86
+ return 0
87
+ return 1
88
+
89
+ @property
90
+ def patch_size(self) -> int:
91
+ if self._patch_size is not None:
92
+ return self._patch_size
93
+ if hasattr(self.model, "patch_size"):
94
+ return self.model.patch_size
95
+ patch_gen = getattr(self.model, "patch_generator", None)
96
+ if patch_gen is not None:
97
+ return patch_gen.patch_size
98
+ return None
99
+
100
+ @property
101
+ def max_resolution(self) -> int:
102
+ return self._max_resolution
103
+
104
+ @property
105
+ def preferred_resolution(self) -> Resolution:
106
+ return self._preferred_resolution
107
+
108
+ @property
109
+ def window_size(self) -> int:
110
+ return self._window_size
111
+
112
+ @property
113
+ def min_resolution_step(self) -> int:
114
+ res = self.patch_size
115
+ if self.window_size is not None:
116
+ res *= self.window_size
117
+ return res
118
+
119
+ @property
120
+ def blocks(self) -> Iterable[nn.Module]:
121
+ blocks = getattr(self.model, 'blocks', None)
122
+ if blocks is not None:
123
+ return blocks
124
+ return None
125
+
126
+ @property
127
+ def embed_dim(self) -> int:
128
+ return self.model.embed_dim
129
+
130
+ def make_preprocessor_external(self) -> Callable[[torch.Tensor], torch.Tensor]:
131
+ ret = self.input_conditioner
132
+ self.input_conditioner = nn.Identity()
133
+ return ret
134
+
135
+ def get_nearest_supported_resolution(self, height: int, width: int) -> Resolution:
136
+ height = int(round(height / self.min_resolution_step) * self.min_resolution_step)
137
+ width = int(round(width / self.min_resolution_step) * self.min_resolution_step)
138
+
139
+ height = max(height, self.min_resolution_step)
140
+ width = max(width, self.min_resolution_step)
141
+
142
+ return Resolution(height=height, width=width)
143
+
144
+ def switch_to_deploy(self):
145
+ fn = getattr(self.model, 'switch_to_deploy', None)
146
+ if fn is not None:
147
+ fn()
148
+
149
+ def forward(self, x: torch.Tensor, feature_fmt: str = 'NLC') -> Union[torch.Tensor, Tuple[torch.Tensor, torch.Tensor]]:
150
+ '''
151
+ Forward process for model.
152
+ Args:
153
+ x: Input tensor. Unless `make_preprocessor_external` has been called, then the dynamic range of `x` is expected to be `[0, 1]`,
154
+ otherwise `x` is expected to be mean centered with unit standard deviation.
155
+ feature_format: ['NLC', 'NCHW'] - The output format for the features.
156
+ '''
157
+ res_step = self.min_resolution_step
158
+ if res_step is not None and (x.shape[-2] % res_step != 0 or x.shape[-1] % res_step != 0):
159
+ raise ValueError('The input resolution must be a multiple of `self.min_resolution_step`. '
160
+ '`self.get_nearest_supported_resolution(<height>, <width>) is provided as a convenience API. '
161
+ f'Input: {x.shape[-2:]}, Nearest: {self.get_nearest_supported_resolution(*x.shape[-2:])}')
162
+
163
+ x = self.input_conditioner(x)
164
+ y = self.model.forward_features(x)
165
+ ret = self._extract_final(x, y, feature_fmt=feature_fmt)
166
+ return ret
167
+
168
+ def _extract_final(self, x: torch.Tensor, y: torch.Tensor, feature_fmt: str = 'NLC'):
169
+ if isinstance(self.model, VisionTransformer):
170
+ patch_gen = getattr(self.model, "patch_generator", None)
171
+ if patch_gen is not None:
172
+ all_summary = y[:, : patch_gen.num_cls_tokens]
173
+ if self.summary_idxs is not None:
174
+ bb_summary = all_summary[:, self.summary_idxs]
175
+ else:
176
+ bb_summary = all_summary
177
+ all_feat = y[:, patch_gen.num_skip :]
178
+ elif self.model.global_pool == "avg":
179
+ all_summary = y[:, self.model.num_prefix_tokens :].mean(dim=1)
180
+ bb_summary = all_summary
181
+ all_feat = y
182
+ else:
183
+ all_summary = y[:, 0]
184
+ bb_summary = all_summary
185
+ all_feat = y[:, 1:]
186
+ elif isinstance(self.model, eradio_model.ERADIO):
187
+ _, f = y
188
+ all_feat = f.flatten(2).transpose(1, 2)
189
+ all_summary = all_feat.mean(dim=1)
190
+ bb_summary = all_summary
191
+ elif isinstance(y, (list, tuple)):
192
+ all_summary, all_feat = y
193
+ bb_summary = all_summary
194
+ else:
195
+ all_summary = y[:, :self.num_cls_tokens]
196
+ if self.summary_idxs is not None and all_summary.shape[1] > 1:
197
+ if all_summary.shape[1] == 1:
198
+ # Create dummy duplicates
199
+ all_summary = all_summary.expand(-1, 128, -1)
200
+ bb_summary = all_summary[:, self.summary_idxs]
201
+ else:
202
+ bb_summary = all_summary
203
+ all_feat = y[:, self.num_summary_tokens:]
204
+
205
+ all_feat = self.feature_normalizer(all_feat)
206
+
207
+ if feature_fmt == 'NCHW':
208
+ fmt_feat = (all_feat.reshape(all_feat.shape[0], x.shape[-2] // self.patch_size, x.shape[-1] // self.patch_size, all_feat.shape[2])
209
+ .permute(0, 3, 1, 2)
210
+ )
211
+ elif feature_fmt == 'NLC':
212
+ fmt_feat = all_feat
213
+ else:
214
+ raise ValueError(f'Unsupported feature_fmt: {feature_fmt}. Must be one of ["NLC", "NCHW"]')
215
+
216
+ ret = RadioOutput(bb_summary.flatten(1), fmt_feat)
217
+
218
+ if self.adaptors:
219
+ ret = dict(backbone=ret)
220
+ for name, adaptor in self.adaptors.items():
221
+ if all_summary.ndim == 3:
222
+ if all_summary.shape[1] == 1:
223
+ summary = all_summary[:, 0]
224
+ else:
225
+ summary = all_summary[:, adaptor.head_idx]
226
+ else:
227
+ summary = all_summary
228
+ ada_input = AdaptorInput(images=x, summary=summary.float(), features=all_feat, feature_fmt=feature_fmt, patch_size=self.patch_size)
229
+ v = adaptor(ada_input).to(torch.float32)
230
+ ret[name] = v
231
+
232
+ return ret
233
+
234
+ def forward_intermediates(
235
+ self,
236
+ x: torch.Tensor,
237
+ indices: Optional[Union[int, List[int], Tuple[int]]] = None,
238
+ return_prefix_tokens: bool = False,
239
+ norm: bool = False,
240
+ stop_early: bool = False,
241
+ output_fmt: str = 'NCHW',
242
+ intermediates_only: bool = False,
243
+ aggregation: Optional[str] = "sparse",
244
+ norm_alpha_scheme: Optional[str] = "post-alpha",
245
+ ) -> List[RadioOutput]:
246
+ """ Forward features that returns intermediates.
247
+ Args:
248
+ x: Input image tensor
249
+ indices: Take last n blocks if int, select matching indices if sequence
250
+ return_prefix_tokens: Return both prefix and spatial intermediate tokens
251
+ norm: Apply norm layer to all intermediates
252
+ stop_early: Stop iterating over blocks when last desired intermediate hit
253
+ output_fmt: Shape of intermediate feature outputs. Options: NCHW, NLC
254
+ intermediates_only: Only return intermediate features
255
+ aggregation: intermediate layer aggregation method (sparse or dense).
256
+ Dense accumulation is done by averaging the features in each group.
257
+ norm_alpha_scheme: apply alpha before ("pre-alpha") or after accumulation ("post-alpha"), or don't normalize ("none")
258
+ Only affects dense aggregation
259
+ Returns:
260
+ List of RadioOutput objects.
261
+ """
262
+ x = self.input_conditioner(x)
263
+ intermediates = self.model.forward_intermediates(
264
+ x,
265
+ indices=indices,
266
+ return_prefix_tokens=return_prefix_tokens,
267
+ norm=norm,
268
+ stop_early=stop_early,
269
+ output_fmt=output_fmt,
270
+ intermediates_only=intermediates_only,
271
+ aggregation=aggregation,
272
+ inter_feature_normalizer=self.inter_feature_normalizer,
273
+ norm_alpha_scheme=norm_alpha_scheme,
274
+ )
275
+
276
+ if not intermediates_only:
277
+ final, intermediates = intermediates
278
+
279
+ def prepare_summary(summ: Optional[torch.Tensor]):
280
+ if summ is None:
281
+ return summ
282
+ if self.summary_idxs is not None and summ.shape[1] > 1:
283
+ summ = summ[:, self.summary_idxs]
284
+ return summ.flatten(1)
285
+
286
+ if return_prefix_tokens:
287
+ radio_outputs = [
288
+ RadioOutput(prepare_summary(summary), features)
289
+ for summary, features in intermediates
290
+ ]
291
+ else:
292
+ radio_outputs = intermediates
293
+
294
+ if intermediates_only:
295
+ return radio_outputs
296
+ else:
297
+ final = self._extract_final(x, final, feature_fmt=output_fmt)
298
+ return final, radio_outputs
299
+
300
+
301
+ def create_model_from_args(args) -> nn.Module:
302
+ in_chans = 3
303
+ if args.in_chans is not None:
304
+ in_chans = args.in_chans
305
+ elif args.input_size is not None:
306
+ in_chans = args.input_size[0]
307
+
308
+ # Skip weight initialization unless it's explicitly requested.
309
+ weight_init = args.model_kwargs.pop("weight_init", "skip")
310
+
311
+ model = create_model(
312
+ args.model,
313
+ pretrained=args.pretrained,
314
+ in_chans=in_chans,
315
+ num_classes=args.num_classes,
316
+ drop_rate=args.drop,
317
+ drop_path_rate=args.drop_path,
318
+ drop_block_rate=args.drop_block,
319
+ global_pool=args.gp,
320
+ bn_momentum=args.bn_momentum,
321
+ bn_eps=args.bn_eps,
322
+ scriptable=args.torchscript,
323
+ checkpoint_path=args.initial_checkpoint,
324
+ weight_init=weight_init,
325
+ **args.model_kwargs,
326
+ )
327
+
328
+ if hasattr(model, 'norm') and not getattr(args, 'model_norm', False):
329
+ model.norm = nn.Identity()
330
+
331
+ model.head = nn.Identity()
332
+
333
+ if args.cpe_max_size is not None:
334
+ uq_teachers = set(t['name'] for t in args.teachers)
335
+ enable_cpe(
336
+ model,
337
+ args.cpe_max_size,
338
+ num_cls_tokens=len(uq_teachers) if args.cls_token_per_teacher else 1,
339
+ register_multiple=getattr(args, 'register_multiple', None),
340
+ num_registers=getattr(args, 'cpe_num_registers', None),
341
+ )
342
+
343
+ return model
vit_patch_generator.py ADDED
@@ -0,0 +1,287 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright (c) 2023-2024, NVIDIA CORPORATION. All rights reserved.
2
+ #
3
+ # NVIDIA CORPORATION and its licensors retain all intellectual property
4
+ # and proprietary rights in and to this software, related documentation
5
+ # and any modifications thereto. Any use, reproduction, disclosure or
6
+ # distribution of this software and related documentation without an express
7
+ # license agreement from NVIDIA CORPORATION is strictly prohibited.
8
+
9
+ import math
10
+ from typing import Union, Tuple, Optional
11
+
12
+ import torch
13
+ import torch.nn.functional as F
14
+ from torch import nn
15
+ from einops import rearrange
16
+
17
+ from .cls_token import ClsToken
18
+
19
+ input_dim_t = Union[int, Tuple[int, int]]
20
+
21
+ try:
22
+ # raise ImportError()
23
+ from indirect_grid_sample import indirect_grid_sample
24
+ except ImportError:
25
+ indirect_grid_sample = None
26
+
27
+ class ViTPatchGenerator(nn.Module):
28
+ def __init__(self,
29
+ patch_size: int,
30
+ embed_dim: int,
31
+ input_dims: input_dim_t,
32
+ abs_pos: bool = True,
33
+ normalize_patches: bool = False,
34
+ cls_token: bool = False,
35
+ max_input_dims: Optional[input_dim_t] = None,
36
+ pos_dropout: float = 0.0,
37
+ return_pos_enc: bool = False,
38
+ num_cls_tokens: int = 1,
39
+ register_multiple: Optional[int] = None,
40
+ num_registers: Optional[int] = None,
41
+ patch_bias: bool = False,
42
+ device=None, dtype=None,
43
+ ):
44
+ super().__init__()
45
+
46
+ if isinstance(input_dims, int):
47
+ input_dims = (input_dims, input_dims)
48
+
49
+ if max_input_dims is None:
50
+ max_input_dims = input_dims
51
+ if isinstance(max_input_dims, int):
52
+ max_input_dims = (max_input_dims, max_input_dims)
53
+
54
+ max_input_dims = tuple(
55
+ int(math.ceil(d / patch_size) * patch_size)
56
+ for d in max_input_dims
57
+ )
58
+
59
+ self.cpe_mode = max_input_dims != input_dims
60
+ self.pos_dropout = pos_dropout
61
+ self.return_pos_enc = return_pos_enc
62
+
63
+ factory = dict(device=device, dtype=dtype)
64
+
65
+ self.patch_size = patch_size
66
+ self.abs_pos = abs_pos
67
+ self.embed_dim = embed_dim
68
+
69
+ self.num_rows = max_input_dims[0] // patch_size
70
+ self.num_cols = max_input_dims[1] // patch_size
71
+ self.input_dims = tuple(d // patch_size for d in input_dims)
72
+ self.num_patches = self.num_rows * self.num_cols
73
+ self.max_input_dims = max_input_dims
74
+
75
+ self.im_to_patches = Im2Patches(patch_size)
76
+ self.embedder = ViTPatchLinear(patch_size, embed_dim, bias=patch_bias, **factory)
77
+
78
+ if abs_pos:
79
+ scale = embed_dim ** -0.5
80
+ self.pos_embed = nn.Parameter(torch.randn(1, self.num_patches, embed_dim, **factory) * scale)
81
+
82
+ self.cls_token = ClsToken(
83
+ embed_dim,
84
+ num_tokens=num_cls_tokens,
85
+ enabled=cls_token,
86
+ register_multiple=register_multiple,
87
+ num_registers=num_registers,
88
+ )
89
+
90
+ self.patch_normalizer = nn.LayerNorm(embed_dim) if normalize_patches else nn.Identity()
91
+
92
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
93
+ patches = self.embed_patches(x)
94
+ patches, pos_enc = self.apply_pos_enc(patches, input_size=x.shape[2:])
95
+ patches = self.cls_token(patches)
96
+ patches = self.patch_normalizer(patches)
97
+ if self.return_pos_enc:
98
+ return patches, pos_enc
99
+ return patches
100
+
101
+ @property
102
+ def apply_cls_token(self):
103
+ return self.cls_token.enabled
104
+
105
+ @property
106
+ def num_cls_tokens(self):
107
+ return self.cls_token.num_tokens
108
+
109
+ @property
110
+ def num_cls_patches(self):
111
+ return self.cls_token.num_patches
112
+
113
+ @property
114
+ def num_registers(self):
115
+ return self.cls_token.num_registers
116
+
117
+ @property
118
+ def num_skip(self):
119
+ return self.num_cls_tokens + self.num_registers
120
+
121
+ def no_weight_decay(self):
122
+ return [
123
+ 'pos_embed',
124
+ ]
125
+
126
+ def _load_embed(self, src_embed: torch.Tensor, targ_embed: nn.Parameter):
127
+ if src_embed.shape != targ_embed.shape:
128
+ src_size = int(math.sqrt(src_embed.shape[1]))
129
+
130
+ assert src_size ** 2 == src_embed.shape[1], 'Unable to interpolate non-square embedding'
131
+
132
+ src_embed = rearrange(src_embed, 'b (h w) c -> b c h w', h=src_size, w=src_size)
133
+ src_embed = F.interpolate(src_embed, size=(self.num_rows, self.num_cols), mode='bicubic', align_corners=True, antialias=False)
134
+ src_embed = rearrange(src_embed, 'b c h w -> b (h w) c')
135
+ targ_embed.data.copy_(src_embed)
136
+
137
+ def _load_projection(self, src_proj_weight: torch.Tensor, targ_proj_weight: torch.Tensor):
138
+ if src_proj_weight.shape != targ_proj_weight.shape:
139
+ src_patch_size = int(math.sqrt(src_proj_weight.shape[1] // 3))
140
+
141
+ assert (src_patch_size ** 2) * 3 == src_proj_weight.shape[1], 'Unable to interpolate non-square patch size'
142
+
143
+ src_proj_weight = rearrange(src_proj_weight, 'b (c h w) -> b c h w', c=3, h=src_patch_size, w=src_patch_size)
144
+ src_proj_weight = F.interpolate(src_proj_weight, size=(self.patch_size, self.patch_size), mode='bicubic', align_corners=True, antialias=False)
145
+ src_proj_weight = rearrange(src_proj_weight, 'b c h w -> b (c h w)')
146
+ targ_proj_weight.data.copy_(src_proj_weight)
147
+
148
+ def embed_patches(self, x: torch.Tensor) -> torch.Tensor:
149
+ patches = self.im_to_patches(x)
150
+ patches = self.embedder(patches)
151
+ return patches
152
+
153
+ def apply_pos_enc(self,
154
+ patches: torch.Tensor,
155
+ patch_idxs: Optional[torch.Tensor] = None,
156
+ input_size: Optional[Tuple[int, int]] = None,
157
+ ) -> torch.Tensor:
158
+ if not self.abs_pos:
159
+ return patches
160
+
161
+ pos_enc = self.get_pos_enc(patches.shape[0], patch_idxs, input_size)
162
+
163
+ if self.training and self.pos_dropout > 0:
164
+ keeps = torch.rand(patches.shape[0], 1, 1, dtype=pos_enc.dtype, device=pos_enc.device) > self.pos_dropout
165
+ pos_enc_drop = torch.where(keeps, pos_enc, 0)
166
+ else:
167
+ pos_enc_drop = pos_enc
168
+
169
+ return patches + pos_enc_drop, pos_enc
170
+
171
+ def get_pos_enc(self,
172
+ batch_size: int,
173
+ patch_idxs: Optional[torch.Tensor] = None,
174
+ input_size: Optional[Tuple[int, int]] = None,
175
+ ) -> torch.Tensor:
176
+ if input_size is None:
177
+ input_dims = self.input_dims
178
+ else:
179
+ input_dims = tuple(d // self.patch_size for d in input_size)
180
+
181
+ pos_embed = self._get_pos_embeddings(batch_size, input_dims)
182
+
183
+ if patch_idxs is None:
184
+ return pos_embed
185
+
186
+ exp_patch_idxs = patch_idxs.unsqueeze(-1).expand(-1, -1, pos_embed.shape[-1])
187
+
188
+ pos_embed = torch.gather(pos_embed.expand(patch_idxs.shape[0], -1, -1), dim=1, index=exp_patch_idxs)
189
+ return pos_embed
190
+
191
+
192
+ def _get_pos_embeddings(self, batch_size: int, input_dims: Tuple[int, int]):
193
+ if (self.num_rows, self.num_cols) == input_dims:
194
+ return self.pos_embed
195
+
196
+ pos_embed = self.pos_embed.reshape(1, self.num_rows, self.num_cols, -1).permute(0, 3, 1, 2)
197
+
198
+ def window_select(pos_embed):
199
+ if input_dims[0] < pos_embed.shape[-2]:
200
+ pos_embed = pos_embed[..., :input_dims[0], :]
201
+ if input_dims[1] < pos_embed.shape[-1]:
202
+ pos_embed = pos_embed[..., :, :input_dims[1]]
203
+ return pos_embed
204
+
205
+ if self.cpe_mode:
206
+ if self.training:
207
+ min_scale = math.sqrt(0.1)
208
+ scale = torch.rand(batch_size, 1, 1, device=pos_embed.device) * (1 - min_scale) + min_scale
209
+ aspect_min = math.log(3 / 4)
210
+ aspect_max = -aspect_min
211
+ aspect = torch.exp(torch.rand(batch_size, 1, 1, device=pos_embed.device) * (aspect_max - aspect_min) + aspect_min)
212
+
213
+ scale_x = scale * aspect
214
+ scale_y = scale * (1 / aspect)
215
+ scale_xy = torch.stack([scale_x, scale_y], dim=-1).clamp_(0, 1)
216
+
217
+ pos_xy = torch.rand(batch_size, 1, 1, 2, device=pos_embed.device) * (1 - scale_xy)
218
+
219
+ lin_x = torch.linspace(0, 1, steps=input_dims[1], device=pos_embed.device)[None, None].expand(batch_size, input_dims[0], -1)
220
+ lin_y = torch.linspace(0, 1, steps=input_dims[0], device=pos_embed.device)[None, :, None].expand(batch_size, -1, input_dims[1])
221
+
222
+ lin_xy = torch.stack([lin_x, lin_y], dim=-1)
223
+
224
+ grid_xy = lin_xy * scale_xy + pos_xy
225
+
226
+ # Convert to [-1, 1] range
227
+ grid_xy.mul_(2).sub_(1)
228
+
229
+ pos_embed = F.grid_sample(
230
+ pos_embed.float().expand(batch_size, -1, -1, -1),
231
+ grid=grid_xy,
232
+ mode='bilinear',
233
+ padding_mode='zeros',
234
+ align_corners=True,
235
+ ).to(pos_embed.dtype)
236
+ else:
237
+ # i_rows, i_cols = input_dims
238
+ # p_rows, p_cols = pos_embed.shape[2:]
239
+ # if i_rows <= p_rows and i_cols <= p_cols:
240
+ # left = (p_cols - i_cols) // 2
241
+ # top = (p_rows - i_rows) // 2
242
+ # pos_embed = pos_embed[..., top:top+i_rows, left:left+i_cols]
243
+ # else:
244
+ max_dim = max(input_dims)
245
+ pos_embed = F.interpolate(pos_embed.float(), size=(max_dim, max_dim), align_corners=True, mode='bilinear').to(pos_embed.dtype)
246
+
247
+ pos_embed = window_select(pos_embed)
248
+ else:
249
+ pos_embed = window_select(pos_embed)
250
+
251
+ if pos_embed.shape[-2:] != input_dims:
252
+ pos_embed = F.interpolate(pos_embed.float(), size=input_dims, align_corners=True, mode='bilinear').to(pos_embed.dtype)
253
+
254
+ pos_embed = pos_embed.flatten(2).permute(0, 2, 1)
255
+
256
+ return pos_embed
257
+
258
+
259
+ class Im2Patches(nn.Module):
260
+ def __init__(self, patch_size: int):
261
+ super().__init__()
262
+ self.patch_size = patch_size
263
+
264
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
265
+ if self.patch_size == 1:
266
+ patches = x.flatten(2)
267
+ patches = patches.permute(0, 2, 1)
268
+ return patches
269
+
270
+ py = x.shape[-2] // self.patch_size
271
+ px = x.shape[-1] // self.patch_size
272
+ patches = rearrange(x, 'b c (py yy) (px xx) -> b (py px) (c yy xx)',
273
+ py=py, yy=self.patch_size,
274
+ px=px, xx=self.patch_size,
275
+ )
276
+ return patches
277
+
278
+
279
+ class ViTPatchLinear(nn.Linear):
280
+ def __init__(self, patch_size: int, embed_dim: int, bias: bool = False, **factory):
281
+ super().__init__(
282
+ 3 * (patch_size ** 2),
283
+ embed_dim,
284
+ bias=bias,
285
+ **factory
286
+ )
287
+ self.patch_size = patch_size
vitdet.py ADDED
@@ -0,0 +1,188 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from collections import defaultdict
2
+ from contextlib import contextmanager
3
+ from logging import getLogger
4
+ import math
5
+ import sys
6
+ from typing import List, Union, Iterable
7
+
8
+ import numpy as np
9
+ import torch
10
+ from torch import nn
11
+
12
+ from timm.models import VisionTransformer
13
+ from einops import rearrange
14
+
15
+ from .extra_models import DinoWrapper
16
+
17
+ DEFAULT_NUM_WINDOWED = 5
18
+ DEFAULT_NUM_GLOBAL = 4
19
+
20
+
21
+ class VitDetArgs:
22
+ def __init__(self,
23
+ window_size: int,
24
+ num_summary_tokens: int,
25
+ num_windowed: int = None,
26
+ num_global: int = None,
27
+ ):
28
+ self.window_size = window_size
29
+ self.num_summary_tokens = num_summary_tokens
30
+ self.num_windowed = num_windowed
31
+ self.num_global = num_global
32
+
33
+
34
+ def apply_vitdet_arch(model: Union[VisionTransformer, DinoWrapper], args: VitDetArgs):
35
+ if isinstance(model, VisionTransformer):
36
+ patch_embed = getattr(model, 'patch_generator', model.patch_embed)
37
+
38
+ return ViTDetHook(patch_embed, model.blocks, args)
39
+ elif isinstance(model, DinoWrapper):
40
+ inner = model.inner
41
+
42
+ patch_embed = getattr(inner, 'patch_generator', inner.patch_embed)
43
+ return ViTDetHook(patch_embed, inner.blocks, args)
44
+ else:
45
+ print(f'Warning: Unable to apply VitDet aug!', file=sys.stderr)
46
+
47
+
48
+ class ViTDetHook:
49
+ def __init__(self,
50
+ embedder: nn.Module,
51
+ blocks: nn.Sequential,
52
+ args: VitDetArgs,
53
+ ):
54
+ self.blocks = blocks
55
+ self.num_summary_tokens = args.num_summary_tokens
56
+ self.window_size = args.window_size
57
+
58
+ self._input_resolution = None
59
+ self._num_windows = None
60
+ self._cls_patch = None
61
+ self._order_cache = dict()
62
+
63
+ embedder.register_forward_pre_hook(self._enter_model)
64
+
65
+ # This will decide if we window-fy the patches
66
+ # and enable vit-det for this iteration, and if so,
67
+ # rearrange the patches for efficient mode switching
68
+ blocks.register_forward_pre_hook(self._enter_blocks)
69
+
70
+ is_global = True
71
+ if args.num_windowed is not None:
72
+ period = args.num_windowed + 1
73
+ else:
74
+ num_global = args.num_global or DEFAULT_NUM_GLOBAL
75
+ period = max(len(blocks) // num_global, 1)
76
+
77
+ for i, layer in enumerate(blocks[:-1]):
78
+ ctr = i % period
79
+ if ctr == 0:
80
+ layer.register_forward_pre_hook(self._to_windows)
81
+ is_global = False
82
+ elif ctr == period - 1:
83
+ layer.register_forward_pre_hook(self._to_global)
84
+ is_global = True
85
+
86
+ # Always ensure the final layer is a global layer
87
+ if not is_global:
88
+ blocks[-1].register_forward_pre_hook(self._to_global)
89
+
90
+ blocks.register_forward_hook(self._exit_model)
91
+
92
+ def _enter_model(self, _, input: List[torch.Tensor]):
93
+ self._input_resolution = input[0].shape[-2:]
94
+
95
+ def _enter_blocks(self, _, input: List[torch.Tensor]):
96
+ # print(f'{get_rank()} - ViTDet Window Size: {self._window_size}', file=sys.stderr)
97
+
98
+ patches = input[0]
99
+ patches = self._rearrange_patches(patches)
100
+
101
+ return (patches,) + input[1:]
102
+
103
+ def _to_windows(self, _, input: List[torch.Tensor]):
104
+ patches = input[0]
105
+
106
+ if self.num_summary_tokens:
107
+ self._cls_patch = patches[:, :self.num_summary_tokens]
108
+ patches = patches[:, self.num_summary_tokens:]
109
+
110
+ patches = rearrange(
111
+ patches, 'b (p t) c -> (b p) t c',
112
+ p=self._num_windows, t=self.window_size ** 2,
113
+ )
114
+
115
+ return (patches,) + input[1:]
116
+
117
+ def _to_global(self, _, input: List[torch.Tensor]):
118
+ patches = input[0]
119
+
120
+ patches = rearrange(
121
+ patches, '(b p) t c -> b (p t) c',
122
+ p=self._num_windows, t=self.window_size ** 2,
123
+ b=patches.shape[0] // self._num_windows,
124
+ )
125
+
126
+ if self.num_summary_tokens:
127
+ patches = torch.cat([
128
+ self._cls_patch,
129
+ patches,
130
+ ], dim=1)
131
+
132
+ return (patches,) + input[1:]
133
+
134
+ def _exit_model(self, _, inputs: List[torch.Tensor], patches: torch.Tensor):
135
+ # Return patches to their original order
136
+ patch_order = self._order_cache[self._input_resolution][0]
137
+ patch_order = patch_order.reshape(1, -1, 1).expand_as(patches)
138
+
139
+ ret_patches = torch.empty_like(patches)
140
+ ret_patches = torch.scatter(
141
+ ret_patches,
142
+ dim=1,
143
+ index=patch_order,
144
+ src=patches,
145
+ )
146
+
147
+ return ret_patches
148
+
149
+ def _rearrange_patches(self, patches: torch.Tensor):
150
+ # We rearrange the patches so that we can efficiently
151
+ # switch between windowed and global mode by just
152
+ # reshaping the tensor
153
+
154
+ patch_order, self._num_windows = self._order_cache.get(self._input_resolution, (None, None))
155
+ if patch_order is None:
156
+ num_feat_patches = patches.shape[1] - self.num_summary_tokens
157
+ num_pixels = self._input_resolution[0] * self._input_resolution[1]
158
+
159
+ patch_size = int(round(math.sqrt(num_pixels / num_feat_patches)))
160
+ rows = self._input_resolution[-2] // patch_size
161
+ cols = self._input_resolution[-1] // patch_size
162
+
163
+ w_rows = rows // self.window_size
164
+ w_cols = cols // self.window_size
165
+
166
+ patch_order = torch.arange(0, num_feat_patches, device=patches.device)
167
+
168
+ patch_order = rearrange(
169
+ patch_order, '(wy py wx px) -> (wy wx py px)',
170
+ wy=w_rows, wx=w_cols,
171
+ py=self.window_size, px=self.window_size,
172
+ )
173
+
174
+ if self.num_summary_tokens:
175
+ patch_order = torch.cat([
176
+ torch.arange(self.num_summary_tokens, dtype=patch_order.dtype, device=patch_order.device),
177
+ patch_order + self.num_summary_tokens,
178
+ ])
179
+
180
+ self._num_windows = w_rows * w_cols
181
+ self._order_cache[self._input_resolution] = (
182
+ patch_order,
183
+ self._num_windows,
184
+ )
185
+
186
+ patch_order = patch_order.reshape(1, -1, 1).expand_as(patches)
187
+ patches = torch.gather(patches, dim=1, index=patch_order)
188
+ return patches