BiliSakura commited on
Commit
042e018
·
verified ·
1 Parent(s): e7b6819

Update all files for SkySensepp

Browse files
Files changed (1) hide show
  1. sky_sensepp_impl/compat.py +865 -0
sky_sensepp_impl/compat.py ADDED
@@ -0,0 +1,865 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Pure PyTorch replacements for mmcv, mmseg, and mmcls utilities.
2
+
3
+ This module provides drop-in replacements so the codebase can run without
4
+ the mm* ecosystem installed. Every public symbol mirrors the original API
5
+ as used throughout the repository.
6
+ """
7
+
8
+ import collections.abc
9
+ import logging
10
+ import math
11
+ import warnings
12
+ from functools import partial
13
+ from typing import Optional, Sequence, Tuple, Union
14
+
15
+ import numpy as np
16
+ import torch
17
+ import torch.nn as nn
18
+ import torch.nn.functional as F
19
+ from torch.nn.modules.batchnorm import _BatchNorm
20
+ from torch.nn.modules.utils import _pair as to_2tuple
21
+
22
+ # ---------------------------------------------------------------------------
23
+ # Logging helper (replaces get_root_logger from mmseg/mmcls)
24
+ # ---------------------------------------------------------------------------
25
+
26
+ def get_root_logger(log_file=None, log_level=logging.INFO):
27
+ """Get the root logger with a StreamHandler."""
28
+ logger = logging.getLogger()
29
+ if not logger.handlers:
30
+ handler = logging.StreamHandler()
31
+ handler.setLevel(log_level)
32
+ logger.addHandler(handler)
33
+ logger.setLevel(log_level)
34
+ return logger
35
+
36
+ # ---------------------------------------------------------------------------
37
+ # DropPath (Stochastic Depth)
38
+ # ---------------------------------------------------------------------------
39
+
40
+ class DropPath(nn.Module):
41
+ """Drop paths (Stochastic Depth) per sample."""
42
+
43
+ def __init__(self, drop_prob: float = 0.0):
44
+ super().__init__()
45
+ self.drop_prob = drop_prob
46
+
47
+ def forward(self, x: torch.Tensor) -> torch.Tensor:
48
+ if self.drop_prob == 0.0 or not self.training:
49
+ return x
50
+ keep_prob = 1.0 - self.drop_prob
51
+ shape = (x.shape[0],) + (1,) * (x.ndim - 1)
52
+ random_tensor = x.new_empty(shape).bernoulli_(keep_prob)
53
+ random_tensor.div_(keep_prob)
54
+ return x * random_tensor
55
+
56
+ def extra_repr(self) -> str:
57
+ return f"drop_prob={self.drop_prob:.3f}"
58
+
59
+
60
+ def _build_dropout(cfg):
61
+ """Build a dropout layer from *cfg* dict.
62
+
63
+ Supports ``dict(type='DropPath', drop_prob=X)`` and plain
64
+ ``nn.Dropout``-style configs. Returns ``nn.Identity`` when *cfg* is
65
+ ``None`` or the drop probability is zero.
66
+ """
67
+ if cfg is None:
68
+ return nn.Identity()
69
+ cfg = cfg.copy()
70
+ tp = cfg.pop("type", "Dropout")
71
+ if tp == "DropPath":
72
+ return DropPath(drop_prob=cfg.get("drop_prob", 0.0))
73
+ if tp == "Dropout":
74
+ return nn.Dropout(p=cfg.get("p", cfg.get("drop_prob", 0.0)))
75
+ raise ValueError(f"Unsupported dropout type: {tp}")
76
+
77
+ # ---------------------------------------------------------------------------
78
+ # build_norm_layer
79
+ # ---------------------------------------------------------------------------
80
+
81
+ _NORM_ABBR = {
82
+ "LN": "ln",
83
+ "BN": "bn",
84
+ "SyncBN": "bn",
85
+ "GN": "gn",
86
+ "IN": "in",
87
+ }
88
+
89
+
90
+ def build_norm_layer(cfg: dict, num_features: int, postfix: Union[int, str] = 0):
91
+ """Build a normalization layer from a config dict.
92
+
93
+ Returns:
94
+ tuple[str, nn.Module]: ``(name, layer)``
95
+
96
+ The *name* is e.g. ``'ln1'`` for LayerNorm with postfix 1.
97
+ """
98
+ cfg = cfg.copy()
99
+ tp = cfg.pop("type")
100
+ abbr = _NORM_ABBR.get(tp, tp.lower())
101
+ name = f"{abbr}{postfix}"
102
+
103
+ if tp in ("LN", "LayerNorm"):
104
+ layer = nn.LayerNorm(num_features, **cfg)
105
+ elif tp in ("BN", "BN2d", "BatchNorm", "BatchNorm2d"):
106
+ layer = nn.BatchNorm2d(num_features, **cfg)
107
+ elif tp in ("SyncBN", "SyncBatchNorm"):
108
+ layer = nn.SyncBatchNorm(num_features, **cfg)
109
+ elif tp in ("GN", "GroupNorm"):
110
+ num_groups = cfg.pop("num_groups", 32)
111
+ layer = nn.GroupNorm(num_groups, num_features, **cfg)
112
+ elif tp in ("IN", "InstanceNorm", "InstanceNorm2d"):
113
+ layer = nn.InstanceNorm2d(num_features, **cfg)
114
+ else:
115
+ raise ValueError(f"Unsupported norm type: {tp}")
116
+
117
+ return name, layer
118
+
119
+ # ---------------------------------------------------------------------------
120
+ # Weight initialisation helpers
121
+ # ---------------------------------------------------------------------------
122
+
123
+ trunc_normal_ = nn.init.trunc_normal_
124
+
125
+
126
+ def constant_init(module: nn.Module, val: float, bias: float = 0.0):
127
+ if hasattr(module, "weight") and module.weight is not None:
128
+ nn.init.constant_(module.weight, val)
129
+ if hasattr(module, "bias") and module.bias is not None:
130
+ nn.init.constant_(module.bias, bias)
131
+
132
+
133
+ def kaiming_init(module: nn.Module, mode: str = "fan_in", bias: float = 0.0):
134
+ nn.init.kaiming_normal_(module.weight, mode=mode, nonlinearity="relu")
135
+ if hasattr(module, "bias") and module.bias is not None:
136
+ nn.init.constant_(module.bias, bias)
137
+
138
+ # ---------------------------------------------------------------------------
139
+ # BaseModule (drop-in for mmcv.runner.BaseModule)
140
+ # ---------------------------------------------------------------------------
141
+
142
+ class BaseModule(nn.Module):
143
+ """Minimal replacement for ``mmcv.runner.BaseModule``.
144
+
145
+ Accepts an optional ``init_cfg`` so that the same constructor
146
+ signatures work. ``init_weights`` is provided as a no-op that
147
+ subclasses can override.
148
+ """
149
+
150
+ def __init__(self, init_cfg=None):
151
+ super().__init__()
152
+ self.init_cfg = init_cfg
153
+
154
+ def init_weights(self):
155
+ pass
156
+
157
+
158
+ # Alias so ``from mmcv.runner.base_module import ModuleList`` works
159
+ # after rewriting the import to ``from .compat import ModuleList``.
160
+ ModuleList = nn.ModuleList
161
+
162
+ # ---------------------------------------------------------------------------
163
+ # CheckpointLoader & load_state_dict
164
+ # ---------------------------------------------------------------------------
165
+
166
+ class CheckpointLoader:
167
+ @staticmethod
168
+ def load_checkpoint(path, logger=None, map_location="cpu"):
169
+ if logger:
170
+ logger.info(f"Loading checkpoint from {path}")
171
+ return torch.load(path, map_location=map_location)
172
+
173
+
174
+ def load_state_dict(model, state_dict, strict=False, logger=None):
175
+ unexpected = []
176
+ missing = []
177
+ result = model.load_state_dict(state_dict, strict=strict)
178
+ if hasattr(result, "missing_keys"):
179
+ missing = result.missing_keys
180
+ if hasattr(result, "unexpected_keys"):
181
+ unexpected = result.unexpected_keys
182
+ if logger:
183
+ if missing:
184
+ logger.warning(f"Missing keys: {missing}")
185
+ if unexpected:
186
+ logger.warning(f"Unexpected keys: {unexpected}")
187
+
188
+ # ---------------------------------------------------------------------------
189
+ # auto_fp16 (no-op decorator)
190
+ # ---------------------------------------------------------------------------
191
+
192
+ def auto_fp16(apply_to=None, out_fp32=False):
193
+ """No-op replacement for ``mmcv.runner.auto_fp16``."""
194
+ def wrapper(old_func):
195
+ return old_func
196
+ return wrapper
197
+
198
+ # ---------------------------------------------------------------------------
199
+ # resize (replacement for mmseg.ops.resize)
200
+ # ---------------------------------------------------------------------------
201
+
202
+ def resize(input, size=None, scale_factor=None, mode="nearest",
203
+ align_corners=None, warning=True):
204
+ return F.interpolate(input, size=size, scale_factor=scale_factor,
205
+ mode=mode, align_corners=align_corners)
206
+
207
+ # ---------------------------------------------------------------------------
208
+ # FFN (Feed-Forward Network used in transformer blocks)
209
+ # ---------------------------------------------------------------------------
210
+
211
+ class FFN(nn.Module):
212
+ """Feed-Forward Network compatible with the mmcv API.
213
+
214
+ Parameters
215
+ ----------
216
+ embed_dims : int
217
+ feedforward_channels : int
218
+ num_fcs : int (default 2)
219
+ ffn_drop : float (default 0.)
220
+ dropout_layer : dict | None (``dict(type='DropPath', drop_prob=X)``)
221
+ act_cfg : dict (default ``dict(type='GELU')``)
222
+ add_identity : bool (default True)
223
+ """
224
+
225
+ _ACT = {
226
+ "GELU": nn.GELU,
227
+ "ReLU": partial(nn.ReLU, inplace=True),
228
+ "SiLU": nn.SiLU,
229
+ }
230
+
231
+ def __init__(self, embed_dims, feedforward_channels, num_fcs=2,
232
+ ffn_drop=0., dropout_layer=None, act_cfg=None,
233
+ add_identity=True, **kwargs):
234
+ super().__init__()
235
+ if act_cfg is None:
236
+ act_cfg = dict(type="GELU")
237
+ act_cls = self._ACT.get(act_cfg.get("type", "GELU"), nn.GELU)
238
+
239
+ layers = []
240
+ in_dim = embed_dims
241
+ for i in range(num_fcs - 1):
242
+ layers.append(nn.Linear(in_dim, feedforward_channels))
243
+ layers.append(act_cls())
244
+ layers.append(nn.Dropout(ffn_drop))
245
+ in_dim = feedforward_channels
246
+ layers.append(nn.Linear(feedforward_channels, embed_dims))
247
+ layers.append(nn.Dropout(ffn_drop))
248
+ self.layers = nn.Sequential(*layers)
249
+ self.dropout_layer = _build_dropout(dropout_layer)
250
+ self.add_identity = add_identity
251
+
252
+ def forward(self, x, identity=None):
253
+ out = self.layers(x)
254
+ if not isinstance(self.dropout_layer, nn.Identity):
255
+ out = self.dropout_layer(out)
256
+ if self.add_identity:
257
+ if identity is None:
258
+ identity = x
259
+ out = out + identity
260
+ return out
261
+
262
+ # ---------------------------------------------------------------------------
263
+ # MultiheadAttention (mmcv-compatible wrapper)
264
+ # ---------------------------------------------------------------------------
265
+
266
+ class MultiheadAttention(nn.Module):
267
+ """Multi-head attention compatible with mmcv's API.
268
+
269
+ Supports ``forward(query, key=None, value=None, identity=None)``.
270
+ When *key*/*value* are ``None`` they default to *query* (self-attention).
271
+ An identity residual is added by default (identity defaults to *query*).
272
+ """
273
+
274
+ def __init__(self, embed_dims, num_heads, attn_drop=0., proj_drop=0.,
275
+ batch_first=True, bias=True, dropout_layer=None, **kwargs):
276
+ super().__init__()
277
+ self.embed_dims = embed_dims
278
+ self.num_heads = num_heads
279
+ self.batch_first = batch_first
280
+ self.attn = nn.MultiheadAttention(
281
+ embed_dim=embed_dims,
282
+ num_heads=num_heads,
283
+ dropout=attn_drop,
284
+ bias=bias,
285
+ batch_first=batch_first,
286
+ )
287
+ self.proj_drop = nn.Dropout(proj_drop)
288
+ self.dropout_layer = _build_dropout(dropout_layer)
289
+
290
+ def forward(self, query, key=None, value=None, identity=None,
291
+ attn_mask=None, key_padding_mask=None, **kwargs):
292
+ if key is None:
293
+ key = query
294
+ if value is None:
295
+ value = key
296
+ if identity is None:
297
+ identity = query
298
+
299
+ out, _ = self.attn(query, key, value, attn_mask=attn_mask,
300
+ key_padding_mask=key_padding_mask)
301
+ out = self.proj_drop(out)
302
+ if not isinstance(self.dropout_layer, nn.Identity):
303
+ out = self.dropout_layer(out)
304
+ return out + identity
305
+
306
+ # ---------------------------------------------------------------------------
307
+ # ConvModule (Conv + optional Norm + optional Activation)
308
+ # ---------------------------------------------------------------------------
309
+
310
+ class ConvModule(nn.Module):
311
+ """Conv2d + optional normalization + optional activation."""
312
+
313
+ def __init__(self, in_channels, out_channels, kernel_size, stride=1,
314
+ padding=0, dilation=1, groups=1, bias="auto",
315
+ conv_cfg=None, norm_cfg=None, act_cfg=None,
316
+ inplace=True, order=("conv", "norm", "act"), **kwargs):
317
+ super().__init__()
318
+ if act_cfg is None:
319
+ act_cfg = dict(type="ReLU")
320
+
321
+ # bias defaults to True when no norm, False otherwise
322
+ if bias == "auto":
323
+ bias = norm_cfg is None
324
+
325
+ self.conv = nn.Conv2d(
326
+ in_channels, out_channels, kernel_size,
327
+ stride=stride, padding=padding, dilation=dilation,
328
+ groups=groups, bias=bias,
329
+ )
330
+
331
+ self.norm = None
332
+ if norm_cfg is not None:
333
+ _, self.norm = build_norm_layer(norm_cfg, out_channels)
334
+
335
+ self.act = None
336
+ if act_cfg is not None:
337
+ act_type = act_cfg.get("type", "ReLU")
338
+ if act_type == "ReLU":
339
+ self.act = nn.ReLU(inplace=inplace)
340
+ elif act_type == "GELU":
341
+ self.act = nn.GELU()
342
+ elif act_type == "SiLU":
343
+ self.act = nn.SiLU(inplace=inplace)
344
+ elif act_type == "LeakyReLU":
345
+ self.act = nn.LeakyReLU(
346
+ negative_slope=act_cfg.get("negative_slope", 0.01),
347
+ inplace=inplace)
348
+ else:
349
+ raise ValueError(f"Unsupported activation: {act_type}")
350
+
351
+ self.order = order
352
+
353
+ def forward(self, x):
354
+ for layer_name in self.order:
355
+ if layer_name == "conv":
356
+ x = self.conv(x)
357
+ elif layer_name == "norm" and self.norm is not None:
358
+ x = self.norm(x)
359
+ elif layer_name == "act" and self.act is not None:
360
+ x = self.act(x)
361
+ return x
362
+
363
+ # ---------------------------------------------------------------------------
364
+ # PatchEmbed (Patch Embedding via Conv2d)
365
+ # ---------------------------------------------------------------------------
366
+
367
+ class PatchEmbed(nn.Module):
368
+ """Image to patch embedding.
369
+
370
+ Returns ``(tokens, hw_shape)`` where tokens is ``(B, H*W, C)``
371
+ and ``hw_shape`` is ``(H, W)`` of the grid.
372
+
373
+ ``padding='corner'`` means zero padding (padding=0).
374
+ When *input_size* is given, ``self.init_out_size`` is set.
375
+ """
376
+
377
+ def __init__(self, in_channels=3, embed_dims=768, conv_type="Conv2d",
378
+ kernel_size=16, stride=16, padding="corner",
379
+ dilation=1, norm_cfg=None, input_size=None,
380
+ init_cfg=None, **kwargs):
381
+ super().__init__()
382
+ if isinstance(padding, str):
383
+ # 'corner' == no extra padding
384
+ padding = 0
385
+ if isinstance(kernel_size, int):
386
+ kernel_size = (kernel_size, kernel_size)
387
+ if isinstance(stride, int):
388
+ stride = (stride, stride)
389
+ if isinstance(padding, int):
390
+ padding = (padding, padding)
391
+ if isinstance(dilation, int):
392
+ dilation = (dilation, dilation)
393
+
394
+ self.projection = nn.Conv2d(
395
+ in_channels, embed_dims,
396
+ kernel_size=kernel_size, stride=stride,
397
+ padding=padding, dilation=dilation,
398
+ )
399
+
400
+ self.norm = None
401
+ if norm_cfg is not None:
402
+ _, self.norm = build_norm_layer(norm_cfg, embed_dims)
403
+
404
+ # Pre-compute output spatial size when input_size is known.
405
+ self.init_out_size = None
406
+ if input_size is not None:
407
+ if isinstance(input_size, int):
408
+ input_size = (input_size, input_size)
409
+ h_out = (input_size[0] + 2 * padding[0] - dilation[0] *
410
+ (kernel_size[0] - 1) - 1) // stride[0] + 1
411
+ w_out = (input_size[1] + 2 * padding[1] - dilation[1] *
412
+ (kernel_size[1] - 1) - 1) // stride[1] + 1
413
+ self.init_out_size = (h_out, w_out)
414
+
415
+ def forward(self, x):
416
+ x = self.projection(x) # (B, C, H, W)
417
+ hw_shape = (x.shape[2], x.shape[3])
418
+ x = x.flatten(2).transpose(1, 2) # (B, H*W, C)
419
+ if self.norm is not None:
420
+ x = self.norm(x)
421
+ return x, hw_shape
422
+
423
+ # ---------------------------------------------------------------------------
424
+ # resize_pos_embed (from mmcls)
425
+ # ---------------------------------------------------------------------------
426
+
427
+ def resize_pos_embed(pos_embed, src_shape, dst_shape, mode="bicubic",
428
+ num_extra_tokens=0):
429
+ """Resize position embeddings via interpolation.
430
+
431
+ Parameters
432
+ ----------
433
+ pos_embed : Tensor (1, L, C) or (1, extra+H*W, C)
434
+ src_shape : tuple (H_src, W_src)
435
+ dst_shape : tuple (H_dst, W_dst)
436
+ mode : str
437
+ num_extra_tokens : int (e.g. 1 for CLS token)
438
+ """
439
+ if src_shape == dst_shape:
440
+ return pos_embed
441
+
442
+ extra_tokens = pos_embed[:, :num_extra_tokens]
443
+ pos_tokens = pos_embed[:, num_extra_tokens:]
444
+
445
+ B, L, C = pos_tokens.shape
446
+ src_h, src_w = src_shape
447
+ pos_tokens = pos_tokens.reshape(B, src_h, src_w, C).permute(0, 3, 1, 2)
448
+ dst_h, dst_w = dst_shape
449
+ pos_tokens = F.interpolate(
450
+ pos_tokens.float(), size=(dst_h, dst_w), mode=mode,
451
+ align_corners=False if mode == "bicubic" else None,
452
+ )
453
+ pos_tokens = pos_tokens.permute(0, 2, 3, 1).reshape(B, -1, C)
454
+
455
+ if num_extra_tokens > 0:
456
+ pos_tokens = torch.cat([extra_tokens, pos_tokens], dim=1)
457
+ return pos_tokens
458
+
459
+ # ---------------------------------------------------------------------------
460
+ # PatchMerging (from mmcls – downsamples by merging 2×2 patches)
461
+ # ---------------------------------------------------------------------------
462
+
463
+ class PatchMerging(nn.Module):
464
+ """Merge 2×2 neighbouring patches to downsample.
465
+
466
+ Input : (B, H*W, C) + input_size (H, W)
467
+ Output: (B, H/2*W/2, out_channels) + output_size (H/2, W/2)
468
+ """
469
+
470
+ def __init__(self, in_channels, out_channels,
471
+ norm_cfg=None, is_post_norm=True, **kwargs):
472
+ super().__init__()
473
+ if norm_cfg is None:
474
+ norm_cfg = dict(type="LN")
475
+ self.in_channels = in_channels
476
+ self.out_channels = out_channels
477
+ self.is_post_norm = is_post_norm
478
+
479
+ self.reduction = nn.Linear(4 * in_channels, out_channels, bias=False)
480
+ if is_post_norm:
481
+ _, self.norm = build_norm_layer(norm_cfg, out_channels)
482
+ else:
483
+ _, self.norm = build_norm_layer(norm_cfg, 4 * in_channels)
484
+
485
+ def forward(self, x, input_size):
486
+ H, W = input_size
487
+ B, L, C = x.shape
488
+ assert L == H * W, "input feature has wrong size"
489
+
490
+ x = x.view(B, H, W, C)
491
+
492
+ # Pad if H or W is odd
493
+ pad_h = H % 2
494
+ pad_w = W % 2
495
+ if pad_h or pad_w:
496
+ x = F.pad(x, (0, 0, 0, pad_w, 0, pad_h))
497
+
498
+ x0 = x[:, 0::2, 0::2, :] # (B, H/2, W/2, C)
499
+ x1 = x[:, 1::2, 0::2, :]
500
+ x2 = x[:, 0::2, 1::2, :]
501
+ x3 = x[:, 1::2, 1::2, :]
502
+ x = torch.cat([x0, x1, x2, x3], dim=-1) # (B, H/2, W/2, 4*C)
503
+
504
+ out_h = (H + 1) // 2
505
+ out_w = (W + 1) // 2
506
+ x = x.view(B, out_h * out_w, 4 * C)
507
+
508
+ if not self.is_post_norm:
509
+ x = self.norm(x)
510
+ x = self.reduction(x)
511
+ if self.is_post_norm:
512
+ x = self.norm(x)
513
+
514
+ return x, (out_h, out_w)
515
+
516
+ # ---------------------------------------------------------------------------
517
+ # WindowMSAV2 (Window-based Multi-head Self-Attention V2)
518
+ # ---------------------------------------------------------------------------
519
+
520
+ class WindowMSAV2(nn.Module):
521
+ """Window-based Multi-head Self-Attention V2 with log-spaced continuous
522
+ position bias (cosine attention + log-CPB).
523
+
524
+ Parameters
525
+ ----------
526
+ embed_dims : int
527
+ num_heads : int
528
+ window_size : tuple[int, int]
529
+ pretrained_window_size : tuple[int, int] (default (0, 0))
530
+ """
531
+
532
+ def __init__(self, embed_dims, num_heads, window_size,
533
+ pretrained_window_size=(0, 0), qkv_bias=True,
534
+ attn_drop=0., proj_drop=0., **kwargs):
535
+ super().__init__()
536
+ self.embed_dims = embed_dims
537
+ self.num_heads = num_heads
538
+ if isinstance(window_size, int):
539
+ window_size = (window_size, window_size)
540
+ self.window_size = window_size
541
+ if isinstance(pretrained_window_size, int):
542
+ pretrained_window_size = (pretrained_window_size,
543
+ pretrained_window_size)
544
+ self.pretrained_window_size = pretrained_window_size
545
+
546
+ self.logit_scale = nn.Parameter(
547
+ torch.log(10.0 * torch.ones((num_heads, 1, 1))))
548
+
549
+ # -- Continuous Position Bias MLP (log-CPB) --
550
+ self.cpb_mlp = nn.Sequential(
551
+ nn.Linear(2, 512, bias=True),
552
+ nn.ReLU(inplace=True),
553
+ nn.Linear(512, num_heads, bias=False),
554
+ )
555
+
556
+ # Build relative coords table
557
+ self._build_relative_coords_table()
558
+ # Build relative position index
559
+ self._build_relative_position_index()
560
+
561
+ self.qkv = nn.Linear(embed_dims, embed_dims * 3, bias=False)
562
+ if qkv_bias:
563
+ self.q_bias = nn.Parameter(torch.zeros(embed_dims))
564
+ self.v_bias = nn.Parameter(torch.zeros(embed_dims))
565
+ else:
566
+ self.q_bias = None
567
+ self.v_bias = None
568
+
569
+ self.attn_drop = nn.Dropout(attn_drop)
570
+ self.proj = nn.Linear(embed_dims, embed_dims)
571
+ self.proj_drop = nn.Dropout(proj_drop)
572
+ self.softmax = nn.Softmax(dim=-1)
573
+
574
+ def _build_relative_coords_table(self):
575
+ Wh, Ww = self.window_size
576
+ coords_h = torch.arange(-(Wh - 1), Wh, dtype=torch.float32)
577
+ coords_w = torch.arange(-(Ww - 1), Ww, dtype=torch.float32)
578
+ # coords_table: (1, 2*Wh-1, 2*Ww-1, 2)
579
+ coords_table = torch.stack(
580
+ torch.meshgrid(coords_h, coords_w, indexing="ij"), dim=-1)
581
+ coords_table = coords_table.unsqueeze(0)
582
+
583
+ # Normalise
584
+ if self.pretrained_window_size[0] > 0:
585
+ coords_table[:, :, :, 0] /= (self.pretrained_window_size[0] - 1)
586
+ coords_table[:, :, :, 1] /= (self.pretrained_window_size[1] - 1)
587
+ else:
588
+ coords_table[:, :, :, 0] /= max(Wh - 1, 1)
589
+ coords_table[:, :, :, 1] /= max(Ww - 1, 1)
590
+ coords_table *= 8 # normalise to -8, 8
591
+ coords_table = (
592
+ torch.sign(coords_table)
593
+ * torch.log2(torch.abs(coords_table) + 1.0)
594
+ / math.log2(8)
595
+ )
596
+ self.register_buffer("relative_coords_table",
597
+ coords_table.view(1, -1, 2))
598
+
599
+ def _build_relative_position_index(self):
600
+ Wh, Ww = self.window_size
601
+ coords_h = torch.arange(Wh)
602
+ coords_w = torch.arange(Ww)
603
+ coords = torch.stack(torch.meshgrid(coords_h, coords_w,
604
+ indexing="ij")) # (2, Wh, Ww)
605
+ coords_flat = torch.flatten(coords, 1) # (2, Wh*Ww)
606
+ relative = coords_flat[:, :, None] - coords_flat[:, None, :]
607
+ relative = relative.permute(1, 2, 0).contiguous() # (N, N, 2)
608
+ relative[:, :, 0] += Wh - 1
609
+ relative[:, :, 1] += Ww - 1
610
+ relative[:, :, 0] *= 2 * Ww - 1
611
+ index = relative.sum(-1) # (N, N)
612
+ self.register_buffer("relative_position_index", index)
613
+
614
+ def forward(self, x, mask=None):
615
+ """
616
+ Parameters
617
+ ----------
618
+ x : Tensor (B*num_windows, N, C) where N = Wh*Ww
619
+ mask : Tensor | None
620
+ """
621
+ B_, N, C = x.shape
622
+
623
+ # QKV with optional bias
624
+ if self.q_bias is not None:
625
+ qkv_bias = torch.cat([
626
+ self.q_bias,
627
+ torch.zeros_like(self.v_bias),
628
+ self.v_bias,
629
+ ])
630
+ qkv = F.linear(x, self.qkv.weight, qkv_bias)
631
+ else:
632
+ qkv = self.qkv(x)
633
+
634
+ qkv = qkv.reshape(B_, N, 3, self.num_heads,
635
+ C // self.num_heads).permute(2, 0, 3, 1, 4)
636
+ q, k, v = qkv.unbind(0)
637
+
638
+ # Cosine attention
639
+ q = F.normalize(q, dim=-1)
640
+ k = F.normalize(k, dim=-1)
641
+ logit_scale = torch.clamp(self.logit_scale,
642
+ max=math.log(100.0)).exp()
643
+ attn = (q @ k.transpose(-2, -1)) * logit_scale
644
+
645
+ # Continuous position bias
646
+ relative_position_bias = self.cpb_mlp(
647
+ self.relative_coords_table).view(-1, self.num_heads)
648
+ index = self.relative_position_index.view(-1)
649
+ relative_position_bias = relative_position_bias[index].view(
650
+ N, N, -1)
651
+ relative_position_bias = relative_position_bias.permute(2, 0, 1)
652
+ # 16 * sigmoid for stable training
653
+ relative_position_bias = 16.0 * torch.sigmoid(
654
+ relative_position_bias)
655
+ attn = attn + relative_position_bias.unsqueeze(0)
656
+
657
+ if mask is not None:
658
+ nW = mask.shape[0]
659
+ attn = attn.view(B_ // nW, nW, self.num_heads, N, N)
660
+ attn = attn + mask.unsqueeze(1).unsqueeze(0)
661
+ attn = attn.view(-1, self.num_heads, N, N)
662
+
663
+ attn = self.softmax(attn)
664
+ attn = self.attn_drop(attn)
665
+
666
+ x = (attn @ v).transpose(1, 2).reshape(B_, N, C)
667
+ x = self.proj(x)
668
+ x = self.proj_drop(x)
669
+ return x
670
+
671
+ # ---------------------------------------------------------------------------
672
+ # ShiftWindowMSA (Shifted-Window Multi-head Self-Attention)
673
+ # ---------------------------------------------------------------------------
674
+
675
+ def _window_partition(x, window_size):
676
+ """Partition feature map into non-overlapping windows.
677
+
678
+ x : (B, H, W, C) → (B * nH * nW, window_size, window_size, C)
679
+ """
680
+ B, H, W, C = x.shape
681
+ wh, ww = window_size
682
+ x = x.view(B, H // wh, wh, W // ww, ww, C)
683
+ x = x.permute(0, 1, 3, 2, 4, 5).contiguous().view(-1, wh, ww, C)
684
+ return x
685
+
686
+
687
+ def _window_reverse(windows, window_size, H, W):
688
+ """Reverse window partitioning.
689
+
690
+ windows : (B * nH * nW, wh, ww, C) → (B, H, W, C)
691
+ """
692
+ wh, ww = window_size
693
+ B = int(windows.shape[0] / (H // wh * W // ww))
694
+ x = windows.view(B, H // wh, W // ww, wh, ww, -1)
695
+ x = x.permute(0, 1, 3, 2, 4, 5).contiguous().view(B, H, W, -1)
696
+ return x
697
+
698
+
699
+ class ShiftWindowMSA(nn.Module):
700
+ """Shifted-Window Multi-head Self-Attention.
701
+
702
+ Parameters
703
+ ----------
704
+ embed_dims : int
705
+ num_heads : int
706
+ window_size : int
707
+ shift_size : int (default 0)
708
+ dropout_layer : dict | None
709
+ pad_small_map : bool (default False)
710
+ window_msa : type (default WindowMSAV2)
711
+ msa_cfg : dict (extra kwargs forwarded to window_msa)
712
+ """
713
+
714
+ def __init__(self, embed_dims, num_heads, window_size, shift_size=0,
715
+ dropout_layer=None, pad_small_map=False,
716
+ window_msa=WindowMSAV2, msa_cfg=None, **kwargs):
717
+ super().__init__()
718
+ if msa_cfg is None:
719
+ msa_cfg = {}
720
+ if isinstance(window_size, int):
721
+ window_size = (window_size, window_size)
722
+ self.window_size = window_size
723
+ self.shift_size = shift_size
724
+ self.pad_small_map = pad_small_map
725
+
726
+ self.w_msa = window_msa(
727
+ embed_dims=embed_dims,
728
+ num_heads=num_heads,
729
+ window_size=window_size,
730
+ **msa_cfg,
731
+ )
732
+ self.drop = _build_dropout(dropout_layer)
733
+
734
+ def forward(self, query, hw_shape):
735
+ B, L, C = query.shape
736
+ H, W = hw_shape
737
+ assert L == H * W, "input feature has wrong size"
738
+
739
+ query = query.view(B, H, W, C)
740
+
741
+ wh, ww = self.window_size
742
+
743
+ # Pad feature map if smaller than window
744
+ pad_r = (ww - W % ww) % ww
745
+ pad_b = (wh - H % wh) % wh
746
+ if pad_r > 0 or pad_b > 0:
747
+ query = F.pad(query, (0, 0, 0, pad_r, 0, pad_b))
748
+ Hp, Wp = query.shape[1], query.shape[2]
749
+
750
+ # Build attention mask for shifted windows
751
+ shift_size = self.shift_size
752
+ if min(Hp, Wp) <= max(wh, ww):
753
+ # Window is larger than feature map – no shift
754
+ shift_size = 0
755
+
756
+ attn_mask = None
757
+ if shift_size > 0:
758
+ query = torch.roll(query, shifts=(-shift_size, -shift_size),
759
+ dims=(1, 2))
760
+ # Build mask
761
+ img_mask = query.new_zeros((1, Hp, Wp, 1))
762
+ h_slices = (slice(0, -wh),
763
+ slice(-wh, -shift_size),
764
+ slice(-shift_size, None))
765
+ w_slices = (slice(0, -ww),
766
+ slice(-ww, -shift_size),
767
+ slice(-shift_size, None))
768
+ cnt = 0
769
+ for h_s in h_slices:
770
+ for w_s in w_slices:
771
+ img_mask[:, h_s, w_s, :] = cnt
772
+ cnt += 1
773
+ mask_windows = _window_partition(img_mask, self.window_size)
774
+ mask_windows = mask_windows.view(-1,
775
+ wh * ww)
776
+ attn_mask = mask_windows.unsqueeze(1) - mask_windows.unsqueeze(2)
777
+ attn_mask = attn_mask.masked_fill(
778
+ attn_mask != 0, float(-100.0)).masked_fill(
779
+ attn_mask == 0, float(0.0))
780
+
781
+ # Partition into windows
782
+ x_windows = _window_partition(query, self.window_size)
783
+ x_windows = x_windows.view(-1, wh * ww, C)
784
+
785
+ # W-MSA / SW-MSA
786
+ attn_windows = self.w_msa(x_windows, mask=attn_mask)
787
+
788
+ # Merge windows back
789
+ attn_windows = attn_windows.view(-1, wh, ww, C)
790
+ x = _window_reverse(attn_windows, self.window_size, Hp, Wp)
791
+
792
+ # Reverse cyclic shift
793
+ if shift_size > 0:
794
+ x = torch.roll(x, shifts=(shift_size, shift_size), dims=(1, 2))
795
+
796
+ # Remove padding
797
+ if pad_r > 0 or pad_b > 0:
798
+ x = x[:, :H, :W, :].contiguous()
799
+
800
+ x = x.view(B, H * W, C)
801
+ x = self.drop(x)
802
+ return x
803
+
804
+ # ---------------------------------------------------------------------------
805
+ # deprecated_api_warning (no-op decorator for mmcv.utils)
806
+ # ---------------------------------------------------------------------------
807
+
808
+ def deprecated_api_warning(name_dict, cls_name=None):
809
+ """No-op decorator that simply returns the original function."""
810
+ def wrapper(old_func):
811
+ return old_func
812
+ return wrapper
813
+
814
+ # ---------------------------------------------------------------------------
815
+ # build_pixel_sampler stub (mmseg.core)
816
+ # ---------------------------------------------------------------------------
817
+
818
+ def build_pixel_sampler(cfg, context=None):
819
+ """Stub – returns None; pixel sampling is unused in this codebase."""
820
+ warnings.warn("build_pixel_sampler is a stub in compat.py; "
821
+ "returning None.")
822
+ return None
823
+
824
+ # ---------------------------------------------------------------------------
825
+ # BaseBackbone alias (mmcls.models.backbones.base_backbone)
826
+ # ---------------------------------------------------------------------------
827
+
828
+ BaseBackbone = BaseModule
829
+
830
+ # ---------------------------------------------------------------------------
831
+ # Convenience re-exports
832
+ # ---------------------------------------------------------------------------
833
+
834
+ __all__ = [
835
+ # norm / init
836
+ "build_norm_layer",
837
+ "trunc_normal_",
838
+ "constant_init",
839
+ "kaiming_init",
840
+ # modules
841
+ "BaseModule",
842
+ "BaseBackbone",
843
+ "ModuleList",
844
+ "CheckpointLoader",
845
+ "load_state_dict",
846
+ "auto_fp16",
847
+ # layers
848
+ "DropPath",
849
+ "FFN",
850
+ "MultiheadAttention",
851
+ "ConvModule",
852
+ "PatchEmbed",
853
+ "PatchMerging",
854
+ "WindowMSAV2",
855
+ "ShiftWindowMSA",
856
+ # functions
857
+ "resize",
858
+ "resize_pos_embed",
859
+ "to_2tuple",
860
+ "deprecated_api_warning",
861
+ "build_pixel_sampler",
862
+ "get_root_logger",
863
+ # types
864
+ "_BatchNorm",
865
+ ]