Columba1198 commited on
Commit
e508ca5
Β·
verified Β·
1 Parent(s): 65820b1

Upload folder using huggingface_hub

Browse files
config.json ADDED
@@ -0,0 +1,89 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "_comment": "EgaraNet: DINOv3 ViT-L backbone + StyleNet (Transposed Transformer Block) composite model config.",
3
+
4
+ "model_type": "egara_net",
5
+ "architectures": ["EgaraNetModel"],
6
+ "torch_dtype": "float32",
7
+ "transformers_version": "4.56.0.dev0",
8
+
9
+ "auto_map": {
10
+ "AutoConfig": "configuration_egara_net.EgaraNetConfig",
11
+ "AutoModel": "modeling_egara_net.EgaraNetModel"
12
+ },
13
+
14
+ "_section_backbone": "--- DINOv3 ViT Backbone (nested sub-config) ---",
15
+ "backbone_config": {
16
+ "model_type": "dinov3_vit",
17
+ "architectures": ["DINOv3ViTModel"],
18
+
19
+ "hidden_size": 1024,
20
+ "num_hidden_layers": 24,
21
+ "num_attention_heads": 16,
22
+ "intermediate_size": 4096,
23
+ "hidden_act": "gelu",
24
+
25
+ "image_size": 224,
26
+ "patch_size": 16,
27
+ "num_channels": 3,
28
+ "num_register_tokens": 4,
29
+
30
+ "attention_dropout": 0.0,
31
+ "drop_path_rate": 0.0,
32
+ "layer_norm_eps": 1e-05,
33
+ "layerscale_value": 1.0,
34
+
35
+ "pos_embed_rescale": 2.0,
36
+ "pos_embed_jitter": null,
37
+ "pos_embed_shift": null,
38
+ "rope_theta": 100.0,
39
+
40
+ "key_bias": false,
41
+ "query_bias": true,
42
+ "value_bias": true,
43
+ "proj_bias": true,
44
+ "mlp_bias": true,
45
+ "use_gated_mlp": false,
46
+
47
+ "initializer_range": 0.02,
48
+ "torch_dtype": "float32",
49
+ "transformers_version": "4.56.0.dev0"
50
+ },
51
+
52
+ "_section_stylenet": "--- StyleNet: Transposed Transformer Block (TTB) head ---",
53
+
54
+ "ttb_input_dim": null,
55
+ "_ttb_input_dim_note": "null = auto-inferred from backbone_config.hidden_size at model init",
56
+
57
+ "ttb_hidden_dim": 1024,
58
+ "_ttb_hidden_dim_note": "Internal channel dimension of TTB layers. Matches backbone hidden_size for ViT-L.",
59
+
60
+ "ttb_output_dim": 1024,
61
+ "_ttb_output_dim_note": "Final L2-normalised style vector dimension.",
62
+
63
+ "ttb_num_layers": 3,
64
+ "_ttb_num_layers_note": "Number of stacked TransposedTransformerBlock layers.",
65
+
66
+ "ttb_num_heads": 16,
67
+ "_ttb_num_heads_note": "Number of attention heads in TTB. Must divide ttb_hidden_dim evenly. (1024 / 16 = 64)",
68
+
69
+ "_section_ttb_internals": "--- TTB internals (derived / documented for reference) ---",
70
+ "ttb_rms_norm_eps": 1e-05,
71
+ "ttb_swiglu_multiple": 64,
72
+ "_ttb_swiglu_note": "SwiGLU hidden = round_up(floor(hidden_dim * 8/3), multiple). E.g. 768->2048, 1024->2752.",
73
+
74
+ "_section_attnpool": "--- Attention Pooling ---",
75
+ "attn_pool_num_heads": 8,
76
+ "_attn_pool_note": "nn.MultiheadAttention heads used in AttentionPooling. Must divide ttb_hidden_dim.",
77
+
78
+ "_section_head": "--- Projection Head (hidden -> output) ---",
79
+ "head_act": "silu",
80
+ "_head_note": "Linear(hidden) -> SiLU -> Linear(output). Output is L2-normalised.",
81
+
82
+ "_section_preprocessing": "--- Default inference preprocessing ---",
83
+ "image_size": 512,
84
+ "keep_aspect_ratio": true,
85
+ "_keep_aspect_ratio_note": "true = MaxResizeMod16(image_size); false = square resize.",
86
+ "image_mean": [0.485, 0.456, 0.406],
87
+ "image_std": [0.229, 0.224, 0.225],
88
+ "_image_stats_note": "ImageNet stats; must match backbone preprocessor_config.json."
89
+ }
configuration_egara_net.py ADDED
@@ -0,0 +1,307 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ configuration_egara_net.py
3
+ ─────────────────────────
4
+ Hugging Face PretrainedConfig for the EgaraNet composite model.
5
+
6
+ Model structure
7
+ ───────────────
8
+ EgaraNetModel
9
+ β”œbackbone : DINOv3 ViT (frozen or fine-tuned)
10
+ β”‚ config stored under `backbone_config`
11
+ β””style_net : StyleNet
12
+ β”œinput_proj : Identity or Linear (input_dim β†’ hidden_dim)
13
+ β”œttb_layers [Γ—N] : TransposedTransformerBlock
14
+ β”‚ β”œRMSNorm + TTB attention (cross-covariance / transposed)
15
+ β”‚ β””RMSNorm + SwiGLU FFN
16
+ β”œattn_pool : AttentionPooling (learned query β†’ MHA)
17
+ β””head : Linear β†’ SiLU β†’ Linear (L2-normalised output)
18
+
19
+ Usage
20
+ ─────
21
+ from configuration_egara_net import EgaraNetConfig
22
+
23
+ # Build from scratch
24
+ cfg = EgaraNetConfig(
25
+ ttb_hidden_dim=1024,
26
+ ttb_output_dim=1024,
27
+ ttb_num_layers=3,
28
+ ttb_num_heads=16,
29
+ )
30
+ cfg.save_pretrained("./my_egara_net/")
31
+
32
+ # Load from directory
33
+ cfg = EgaraNetConfig.from_pretrained("./my_egara_net/")
34
+
35
+ # Load backbone config separately
36
+ backbone_cfg = cfg.backbone_config # PretrainedConfig instance
37
+ """
38
+
39
+ from __future__ import annotations
40
+
41
+ import math
42
+ from typing import Any
43
+
44
+ from transformers import AutoConfig, PretrainedConfig
45
+
46
+
47
+ # ──────────────────────────────────────────────────────────────────────────────
48
+ # Helpers
49
+ # ──────────────────────────────────────────────────────────────────────────────
50
+
51
+ def _swiglu_hidden_dim(dim: int, multiple: int = 64) -> int:
52
+ """Compute SwiGLU hidden dimension: round_up(floor(dim * 8/3), multiple)."""
53
+ raw = int(dim * 8 / 3)
54
+ return ((raw + multiple - 1) // multiple) * multiple
55
+
56
+
57
+ # ──────────────────────────────────────────────────────────────────────────────
58
+ # Config
59
+ # ──────────────────────────────────────────────────────────────────────────────
60
+
61
+ class EgaraNetConfig(PretrainedConfig):
62
+ r"""
63
+ Configuration for the EgaraNet style extractor.
64
+
65
+ Args
66
+ ────
67
+ backbone_config (dict | PretrainedConfig | None):
68
+ Config for the DINOv3 ViT backbone. A dict is automatically
69
+ converted to the appropriate ``PretrainedConfig`` subclass via
70
+ ``AutoConfig``. If ``None``, a default ``dinov3_vit`` config is
71
+ loaded from ``backbone_model_id``.
72
+
73
+ backbone_model_id (str):
74
+ HF hub ID used when ``backbone_config`` is ``None``, e.g.
75
+ ``"facebook/dinov3-vitl16-pretrain-lvd1689m"``. Ignored when
76
+ ``backbone_config`` is provided explicitly.
77
+
78
+ ttb_input_dim (int | None):
79
+ Input channel dimension fed into StyleNet. ``None`` (default)
80
+ means it is inferred automatically from
81
+ ``backbone_config.hidden_size`` at model initialisation.
82
+
83
+ ttb_hidden_dim (int):
84
+ Internal channel width of every TTB layer and the AttentionPooling
85
+ query. Should match ``backbone_config.hidden_size`` (1024 for ViT-L).
86
+
87
+ ttb_output_dim (int):
88
+ Dimension of the final L2-normalised style vector.
89
+
90
+ ttb_num_layers (int):
91
+ Number of stacked ``TransposedTransformerBlock`` layers.
92
+
93
+ ttb_num_heads (int):
94
+ Number of attention heads inside each TTB.
95
+ Must satisfy ``ttb_hidden_dim % ttb_num_heads == 0``.
96
+
97
+ ttb_rms_norm_eps (float):
98
+ Ξ΅ used in all ``RMSNorm`` layers.
99
+
100
+ ttb_swiglu_multiple (int):
101
+ SwiGLU hidden dim is rounded up to the nearest multiple of this
102
+ value. ``hidden = round_up(floor(ttb_hidden_dim * 8/3), multiple)``.
103
+
104
+ attn_pool_num_heads (int):
105
+ Number of heads for ``nn.MultiheadAttention`` inside
106
+ ``AttentionPooling``. Must divide ``ttb_hidden_dim``.
107
+
108
+ head_act (str):
109
+ Activation between the two linear layers in the projection head.
110
+ Currently only ``"silu"`` is supported.
111
+
112
+ image_size (int):
113
+ Default long-edge size used at inference time.
114
+
115
+ keep_aspect_ratio (bool):
116
+ ``True`` β†’ ``MaxResizeMod16(image_size)`` (preserves aspect ratio,
117
+ snaps to multiples of 16).
118
+ ``False`` β†’ square ``Resize((image_size, image_size))``.
119
+
120
+ image_mean / image_std (list[float]):
121
+ Per-channel normalisation statistics. Must match the backbone's
122
+ ``preprocessor_config.json``. Defaults to ImageNet statistics.
123
+ """
124
+
125
+ model_type = "egara_net"
126
+ is_composition = True # tells HF this is a composite config
127
+
128
+ def __init__(
129
+ self,
130
+ # Backbone
131
+ backbone_config: dict | PretrainedConfig | None = None,
132
+ backbone_model_id: str = "facebook/dinov3-vitl16-pretrain-lvd1689m",
133
+ # StyleNet / TTB
134
+ ttb_input_dim: int | None = None,
135
+ ttb_hidden_dim: int = 1024,
136
+ ttb_output_dim: int = 1024,
137
+ ttb_num_layers: int = 3,
138
+ ttb_num_heads: int = 16,
139
+ ttb_rms_norm_eps: float = 1e-5,
140
+ ttb_swiglu_multiple: int = 64,
141
+ # Attention Pooling
142
+ attn_pool_num_heads: int = 8,
143
+ # Projection Head
144
+ head_act: str = "silu",
145
+ # Default Inference Preprocessing
146
+ image_size: int = 512,
147
+ keep_aspect_ratio: bool = True,
148
+ image_mean: list[float] | None = None,
149
+ image_std: list[float] | None = None,
150
+ **kwargs: Any,
151
+ ) -> None:
152
+ # Validate constraints
153
+ if ttb_hidden_dim % ttb_num_heads != 0:
154
+ raise ValueError(
155
+ f"ttb_hidden_dim ({ttb_hidden_dim}) must be divisible by "
156
+ f"ttb_num_heads ({ttb_num_heads})."
157
+ )
158
+ if ttb_hidden_dim % attn_pool_num_heads != 0:
159
+ raise ValueError(
160
+ f"ttb_hidden_dim ({ttb_hidden_dim}) must be divisible by "
161
+ f"attn_pool_num_heads ({attn_pool_num_heads})."
162
+ )
163
+ if ttb_num_layers < 1:
164
+ raise ValueError(f"ttb_num_layers must be >= 1, got {ttb_num_layers}.")
165
+
166
+ # Backbone config
167
+ if isinstance(backbone_config, dict):
168
+ # AutoConfig dispatches on "model_type" key
169
+ backbone_config = AutoConfig.for_model(**backbone_config)
170
+ self.backbone_config: PretrainedConfig | None = backbone_config
171
+ self.backbone_model_id: str = backbone_model_id
172
+
173
+ # StyleNet params
174
+ self.ttb_input_dim: int | None = ttb_input_dim
175
+ self.ttb_hidden_dim: int = ttb_hidden_dim
176
+ self.ttb_output_dim: int = ttb_output_dim
177
+ self.ttb_num_layers: int = ttb_num_layers
178
+ self.ttb_num_heads: int = ttb_num_heads
179
+ self.ttb_rms_norm_eps: float = ttb_rms_norm_eps
180
+ self.ttb_swiglu_multiple: int = ttb_swiglu_multiple
181
+
182
+ # Attention Pooling
183
+ self.attn_pool_num_heads: int = attn_pool_num_heads
184
+
185
+ # Projection Head
186
+ self.head_act: str = head_act
187
+
188
+ # Preprocessing defaults
189
+ self.image_size: int = image_size
190
+ self.keep_aspect_ratio: bool = keep_aspect_ratio
191
+ self.image_mean: list[float] = image_mean or [0.485, 0.456, 0.406]
192
+ self.image_std: list[float] = image_std or [0.229, 0.224, 0.225]
193
+
194
+ super().__init__(**kwargs)
195
+
196
+ # Derived / read-only properties
197
+
198
+ @property
199
+ def backbone_hidden_size(self) -> int:
200
+ """Backbone output channel width (inferred from backbone_config)."""
201
+ if self.backbone_config is not None:
202
+ return self.backbone_config.hidden_size
203
+ raise AttributeError(
204
+ "backbone_config is not set; cannot infer backbone_hidden_size."
205
+ )
206
+
207
+ @property
208
+ def effective_ttb_input_dim(self) -> int:
209
+ """Actual input_dim used by StyleNet input_proj."""
210
+ return self.ttb_input_dim if self.ttb_input_dim is not None \
211
+ else self.backbone_hidden_size
212
+
213
+ @property
214
+ def ttb_swiglu_hidden_dim(self) -> int:
215
+ """Derived SwiGLU intermediate dimension (not stored in JSON)."""
216
+ return _swiglu_hidden_dim(self.ttb_hidden_dim, self.ttb_swiglu_multiple)
217
+
218
+ @property
219
+ def ttb_head_dim(self) -> int:
220
+ """Per-head dimension inside TTB attention."""
221
+ return self.ttb_hidden_dim // self.ttb_num_heads
222
+
223
+ # Serialisation
224
+
225
+ def to_dict(self) -> dict[str, Any]:
226
+ output = super().to_dict()
227
+ # Serialise nested backbone config as a plain dict
228
+ if self.backbone_config is not None:
229
+ output["backbone_config"] = self.backbone_config.to_dict()
230
+ return output
231
+
232
+ @classmethod
233
+ def from_dict(cls, config_dict: dict[str, Any], **kwargs) -> "EgaraNetConfig":
234
+ # AutoConfig.for_model is invoked inside __init__ for backbone_config
235
+ return super().from_dict(config_dict, **kwargs)
236
+
237
+ # Pretty repr
238
+
239
+ def __repr__(self) -> str:
240
+ backbone_name = (
241
+ getattr(self.backbone_config, "model_type", "?")
242
+ if self.backbone_config else self.backbone_model_id
243
+ )
244
+ return (
245
+ f"EgaraNetConfig(\n"
246
+ f" backbone : {backbone_name}"
247
+ f" (hidden={getattr(self.backbone_config, 'hidden_size', '?')})\n"
248
+ f" ttb_input_dim : {self.effective_ttb_input_dim} "
249
+ f"({'auto' if self.ttb_input_dim is None else 'explicit'})\n"
250
+ f" ttb_hidden_dim : {self.ttb_hidden_dim}\n"
251
+ f" ttb_output_dim : {self.ttb_output_dim}\n"
252
+ f" ttb_num_layers : {self.ttb_num_layers}\n"
253
+ f" ttb_num_heads : {self.ttb_num_heads} "
254
+ f"(head_dim={self.ttb_head_dim})\n"
255
+ f" ttb_swiglu_hidden : {self.ttb_swiglu_hidden_dim}\n"
256
+ f" attn_pool_heads : {self.attn_pool_num_heads}\n"
257
+ f" head_act : {self.head_act}\n"
258
+ f" image_size : {self.image_size} "
259
+ f"keep_ratio={self.keep_aspect_ratio}\n"
260
+ f")"
261
+ )
262
+
263
+
264
+ # ──────────────────────────────────────────────────────────────────────────────
265
+ # Auto-registration (call once at import time, safe to repeat)
266
+ # ──────────────────────────────────────────────────────────────────────────────
267
+ try:
268
+ AutoConfig.register("egara_net", EgaraNetConfig)
269
+ except ValueError:
270
+ pass # already registered (e.g. module reimported in notebook)
271
+
272
+
273
+ # ──────────────────────────────────────────────────────────────────────────────
274
+ # Quick self-test
275
+ # ──────────────────────────────────────────────────────────────────────────────
276
+ if __name__ == "__main__":
277
+ import json, pathlib
278
+
279
+ here = pathlib.Path(__file__).parent
280
+
281
+ # Build from the JSON files supplied with the model
282
+ backbone_dict = json.loads((here / "config.json").read_text())
283
+ backbone_cfg = backbone_dict.get("backbone_config", backbone_dict)
284
+
285
+ cfg = EgaraNetConfig(
286
+ backbone_config=backbone_cfg,
287
+ ttb_hidden_dim=backbone_cfg.get("hidden_size", 1024),
288
+ ttb_output_dim=backbone_cfg.get("hidden_size", 1024),
289
+ ttb_num_layers=3,
290
+ ttb_num_heads=16,
291
+ attn_pool_num_heads=8,
292
+ )
293
+ print(cfg)
294
+
295
+ # Round-trip JSON serialisation
296
+ tmp = pathlib.Path("/tmp/egara_net_test")
297
+ cfg.save_pretrained(tmp)
298
+ cfg2 = EgaraNetConfig.from_pretrained(tmp)
299
+ assert cfg.ttb_hidden_dim == cfg2.ttb_hidden_dim
300
+ assert cfg.ttb_output_dim == cfg2.ttb_output_dim
301
+ assert cfg.ttb_num_layers == cfg2.ttb_num_layers
302
+ print("Round-trip OK βœ“")
303
+
304
+ # Derived properties
305
+ print(f"SwiGLU hidden dim : {cfg.ttb_swiglu_hidden_dim}")
306
+ print(f"TTB head dim : {cfg.ttb_head_dim}")
307
+ print(f"Effective input : {cfg.effective_ttb_input_dim}")
model.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:b9b0d29d8f3bca26aa2429d3a14684e90b4298aafb8f039dc078d16518f8c073
3
+ size 1389584856
modeling_egara_net.py ADDED
@@ -0,0 +1,259 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ modeling_egara_net.py
3
+ ────────────────────
4
+ Hugging Face PreTrainedModel implementation for EgaraNet.
5
+
6
+ EgaraNet is a composite model:
7
+ backbone : DINOv3 ViT (frozen feature extractor)
8
+ style_net : StyleNet (Transposed Transformer Block head)
9
+
10
+ Usage:
11
+ from modeling_egara_net import EgaraNetModel
12
+ model = EgaraNetModel.from_pretrained("path/to/model_dir", trust_remote_code=True)
13
+ # or after uploading to Hub:
14
+ model = EgaraNetModel.from_pretrained("user/egara-net", trust_remote_code=True)
15
+ """
16
+
17
+ from __future__ import annotations
18
+
19
+ import math
20
+ from dataclasses import dataclass
21
+ from typing import Optional
22
+
23
+ import torch
24
+ import torch.nn as nn
25
+ import torch.nn.functional as F
26
+ from transformers import AutoModel, PreTrainedModel
27
+ from transformers.modeling_outputs import ModelOutput
28
+
29
+ from .configuration_egara_net import EgaraNetConfig
30
+
31
+
32
+ # ──────────────────────────────────────────────────────────────────────────────
33
+ # Output dataclass
34
+ # ──────────────────────────────────────────────────────────────────────────────
35
+
36
+ @dataclass
37
+ class EgaraNetOutput(ModelOutput):
38
+ """
39
+ Output of EgaraNetModel.
40
+
41
+ Attributes:
42
+ style_embedding: L2-normalised style vector [B, output_dim]
43
+ backbone_features: raw backbone output [B, N, hidden_size] (optional)
44
+ """
45
+ style_embedding: torch.FloatTensor = None
46
+ backbone_features: Optional[torch.FloatTensor] = None
47
+
48
+
49
+ # ──────────────────────────────────────────────────────────────────────────────
50
+ # Building blocks
51
+ # ──────────────────────────────────────────────────────────────────────────────
52
+
53
+ class RMSNorm(nn.Module):
54
+ """Root Mean Square Layer Normalization."""
55
+
56
+ def __init__(self, dim: int, eps: float = 1e-5):
57
+ super().__init__()
58
+ self.weight = nn.Parameter(torch.ones(dim))
59
+ self.eps = eps
60
+
61
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
62
+ orig_dtype = x.dtype
63
+ x_fp32 = x.to(torch.float32)
64
+ rms = torch.sqrt(x_fp32.pow(2).mean(-1, keepdim=True) + self.eps)
65
+ out = (x_fp32 / rms) * self.weight.to(torch.float32)
66
+ return out.to(orig_dtype)
67
+
68
+
69
+ class SwiGLU(nn.Module):
70
+ """SwiGLU Feed-Forward Network."""
71
+
72
+ def __init__(self, dim: int, multiple: int = 64):
73
+ super().__init__()
74
+ hidden_dim = int(dim * 8 / 3)
75
+ hidden_dim = ((hidden_dim + multiple - 1) // multiple) * multiple
76
+ self.w_gate = nn.Linear(dim, hidden_dim, bias=False)
77
+ self.w_up = nn.Linear(dim, hidden_dim, bias=False)
78
+ self.w_down = nn.Linear(hidden_dim, dim, bias=False)
79
+
80
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
81
+ return self.w_down(F.silu(self.w_gate(x)) * self.w_up(x))
82
+
83
+
84
+ class TransposedTransformerBlock(nn.Module):
85
+ """
86
+ TTB β€” Transposed Transformer Block.
87
+ Cross-covariance attention in (HeadDim Γ— HeadDim) space.
88
+ """
89
+
90
+ def __init__(self, dim: int, num_heads: int, eps: float = 1e-5,
91
+ swiglu_multiple: int = 64):
92
+ super().__init__()
93
+ self.num_heads = num_heads
94
+ self.head_dim = dim // num_heads
95
+ self.scale = self.head_dim ** -0.5
96
+
97
+ self.norm = RMSNorm(dim, eps=eps)
98
+ self.qkv = nn.Linear(dim, dim * 3, bias=False)
99
+ self.q_norm = RMSNorm(self.head_dim, eps=eps)
100
+ self.k_norm = RMSNorm(self.head_dim, eps=eps)
101
+ self.proj = nn.Linear(dim, dim)
102
+
103
+ self.norm_ffn = RMSNorm(dim, eps=eps)
104
+ self.ffn = SwiGLU(dim, multiple=swiglu_multiple)
105
+
106
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
107
+ B, N, C = x.shape
108
+ shortcut = x
109
+ x = self.norm(x)
110
+
111
+ qkv = (self.qkv(x)
112
+ .reshape(B, N, 3, self.num_heads, self.head_dim)
113
+ .permute(2, 0, 3, 1, 4))
114
+ q, k, v = qkv.unbind(0)
115
+
116
+ q = self.q_norm(q)
117
+ k = self.k_norm(k)
118
+
119
+ # Transposed attention: (HeadDim, N) @ (N, HeadDim) β†’ (HeadDim, HeadDim)
120
+ q = q.transpose(-2, -1)
121
+ attn = (q @ k) * self.scale
122
+ attn = attn.softmax(dim=-1)
123
+
124
+ v = v.transpose(-2, -1)
125
+ x = attn @ v # (HeadDim, HeadDim) @ (HeadDim, N) β†’ (HeadDim, N)
126
+
127
+ x = x.transpose(-2, -1).reshape(B, N, C)
128
+ x = self.proj(x)
129
+ x = x + shortcut
130
+
131
+ x = x + self.ffn(self.norm_ffn(x))
132
+ return x
133
+
134
+
135
+ class AttentionPooling(nn.Module):
136
+ """Learned-query attention pooling: [B, N, C] β†’ [B, C]."""
137
+
138
+ def __init__(self, dim: int, num_heads: int = 8):
139
+ super().__init__()
140
+ self.pool_query = nn.Parameter(torch.randn(1, 1, dim))
141
+ self.pool_attn = nn.MultiheadAttention(
142
+ embed_dim=dim, num_heads=num_heads, batch_first=True
143
+ )
144
+
145
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
146
+ B = x.size(0)
147
+ query = self.pool_query.expand(B, -1, -1)
148
+ out, _ = self.pool_attn(query, x, x)
149
+ return out.squeeze(1)
150
+
151
+
152
+ class StyleNet(nn.Module):
153
+ """
154
+ StyleNet head: TTB layers β†’ AttentionPooling β†’ Projection β†’ L2 norm.
155
+ """
156
+
157
+ def __init__(self, config: EgaraNetConfig):
158
+ super().__init__()
159
+ input_dim = config.effective_ttb_input_dim
160
+ hidden_dim = config.ttb_hidden_dim
161
+
162
+ self.input_proj = (
163
+ nn.Identity()
164
+ if input_dim == hidden_dim
165
+ else nn.Linear(input_dim, hidden_dim)
166
+ )
167
+
168
+ self.tab_layers = nn.ModuleList([
169
+ TransposedTransformerBlock(
170
+ dim=hidden_dim,
171
+ num_heads=config.ttb_num_heads,
172
+ eps=config.ttb_rms_norm_eps,
173
+ swiglu_multiple=config.ttb_swiglu_multiple,
174
+ )
175
+ for _ in range(config.ttb_num_layers)
176
+ ])
177
+
178
+ self.attn_pool = AttentionPooling(
179
+ dim=hidden_dim, num_heads=config.attn_pool_num_heads
180
+ )
181
+
182
+ self.head = nn.Sequential(
183
+ nn.Linear(hidden_dim, hidden_dim),
184
+ nn.SiLU(),
185
+ nn.Linear(hidden_dim, config.ttb_output_dim),
186
+ )
187
+
188
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
189
+ x = self.input_proj(x)
190
+ for layer in self.tab_layers:
191
+ x = layer(x)
192
+ x = self.attn_pool(x)
193
+ x = self.head(x)
194
+ return F.normalize(x, p=2, dim=-1)
195
+
196
+
197
+ # ──────────────────────────────────────────────────────────────────────────────
198
+ # EgaraNet Model
199
+ # ──────────────────────────────────────────────────────────────────────────────
200
+
201
+ class EgaraNetModel(PreTrainedModel):
202
+ """
203
+ EgaraNet: DINOv3 ViT backbone + StyleNet head.
204
+
205
+ Accepts ``pixel_values`` [B, C, H, W] and returns an
206
+ ``EgaraNetOutput`` containing the L2-normalised style embedding.
207
+ """
208
+
209
+ config_class = EgaraNetConfig
210
+ supports_gradient_checkpointing = False
211
+
212
+ def __init__(self, config: EgaraNetConfig):
213
+ super().__init__(config)
214
+
215
+ # Backbone
216
+ if config.backbone_config is not None:
217
+ self.backbone = AutoModel.from_config(config.backbone_config)
218
+ else:
219
+ self.backbone = AutoModel.from_pretrained(config.backbone_model_id)
220
+
221
+ # StyleNet head
222
+ self.style_net = StyleNet(config)
223
+
224
+ # Post-init (weight init for any uninitialised params)
225
+ self.post_init()
226
+
227
+ def forward(
228
+ self,
229
+ pixel_values: torch.Tensor,
230
+ output_backbone_features: bool = False,
231
+ ) -> EgaraNetOutput:
232
+ """
233
+ Args:
234
+ pixel_values: [B, C, H, W] β€” preprocessed image tensor.
235
+ output_backbone_features: if True, also return raw backbone features.
236
+
237
+ Returns:
238
+ EgaraNetOutput with ``style_embedding`` [B, output_dim].
239
+ """
240
+ backbone_out = self.backbone(pixel_values=pixel_values)
241
+ features = backbone_out.last_hidden_state # [B, N, hidden_size]
242
+
243
+ style_emb = self.style_net(features) # [B, output_dim]
244
+
245
+ return EgaraNetOutput(
246
+ style_embedding=style_emb,
247
+ backbone_features=features if output_backbone_features else None,
248
+ )
249
+
250
+
251
+ # ──────────────────────────────────────────────────────────────────────────────
252
+ # Auto-registration
253
+ # ──────────────────────────────────────────────────────────────────────────────
254
+
255
+ try:
256
+ from transformers import AutoModel as _AM
257
+ _AM.register(EgaraNetConfig, EgaraNetModel)
258
+ except (ValueError, ImportError):
259
+ pass # already registered or not available