dgenerate-ultralytics-headless 8.3.236__py3-none-any.whl → 8.3.239__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (117) hide show
  1. {dgenerate_ultralytics_headless-8.3.236.dist-info → dgenerate_ultralytics_headless-8.3.239.dist-info}/METADATA +1 -1
  2. {dgenerate_ultralytics_headless-8.3.236.dist-info → dgenerate_ultralytics_headless-8.3.239.dist-info}/RECORD +117 -105
  3. tests/test_exports.py +3 -1
  4. tests/test_python.py +2 -2
  5. tests/test_solutions.py +6 -6
  6. ultralytics/__init__.py +1 -1
  7. ultralytics/cfg/__init__.py +4 -4
  8. ultralytics/cfg/datasets/Argoverse.yaml +7 -6
  9. ultralytics/cfg/datasets/DOTAv1.5.yaml +1 -1
  10. ultralytics/cfg/datasets/DOTAv1.yaml +1 -1
  11. ultralytics/cfg/datasets/VOC.yaml +15 -16
  12. ultralytics/cfg/datasets/african-wildlife.yaml +1 -1
  13. ultralytics/cfg/datasets/coco128-seg.yaml +1 -1
  14. ultralytics/cfg/datasets/dota8-multispectral.yaml +1 -1
  15. ultralytics/cfg/datasets/dota8.yaml +2 -2
  16. ultralytics/cfg/datasets/kitti.yaml +1 -1
  17. ultralytics/cfg/datasets/xView.yaml +16 -16
  18. ultralytics/cfg/models/11/yolo11-pose.yaml +1 -1
  19. ultralytics/cfg/models/11/yoloe-11-seg.yaml +2 -2
  20. ultralytics/cfg/models/11/yoloe-11.yaml +2 -2
  21. ultralytics/cfg/models/v8/yoloe-v8-seg.yaml +9 -6
  22. ultralytics/cfg/models/v8/yoloe-v8.yaml +9 -6
  23. ultralytics/cfg/models/v8/yolov8-cls-resnet101.yaml +1 -1
  24. ultralytics/cfg/models/v8/yolov8-cls-resnet50.yaml +1 -1
  25. ultralytics/cfg/models/v8/yolov8-ghost-p2.yaml +2 -2
  26. ultralytics/cfg/models/v8/yolov8-ghost-p6.yaml +2 -2
  27. ultralytics/cfg/models/v8/yolov8-ghost.yaml +2 -2
  28. ultralytics/cfg/models/v8/yolov8-obb.yaml +1 -1
  29. ultralytics/cfg/models/v8/yolov8-p2.yaml +1 -1
  30. ultralytics/cfg/models/v8/yolov8-pose-p6.yaml +1 -1
  31. ultralytics/cfg/models/v8/yolov8-rtdetr.yaml +1 -1
  32. ultralytics/cfg/models/v8/yolov8-world.yaml +1 -1
  33. ultralytics/cfg/models/v8/yolov8-worldv2.yaml +6 -6
  34. ultralytics/data/augment.py +1 -1
  35. ultralytics/data/base.py +4 -2
  36. ultralytics/data/build.py +4 -4
  37. ultralytics/data/loaders.py +17 -12
  38. ultralytics/data/utils.py +4 -4
  39. ultralytics/engine/exporter.py +40 -25
  40. ultralytics/engine/predictor.py +8 -6
  41. ultralytics/engine/results.py +12 -13
  42. ultralytics/engine/trainer.py +10 -2
  43. ultralytics/engine/tuner.py +2 -3
  44. ultralytics/engine/validator.py +2 -2
  45. ultralytics/models/fastsam/model.py +2 -2
  46. ultralytics/models/fastsam/predict.py +2 -3
  47. ultralytics/models/fastsam/val.py +4 -4
  48. ultralytics/models/rtdetr/predict.py +2 -3
  49. ultralytics/models/rtdetr/val.py +10 -5
  50. ultralytics/models/sam/__init__.py +14 -1
  51. ultralytics/models/sam/build.py +22 -13
  52. ultralytics/models/sam/build_sam3.py +377 -0
  53. ultralytics/models/sam/model.py +13 -5
  54. ultralytics/models/sam/modules/blocks.py +20 -8
  55. ultralytics/models/sam/modules/decoders.py +2 -3
  56. ultralytics/models/sam/modules/encoders.py +4 -1
  57. ultralytics/models/sam/modules/memory_attention.py +6 -2
  58. ultralytics/models/sam/modules/sam.py +159 -10
  59. ultralytics/models/sam/modules/utils.py +134 -4
  60. ultralytics/models/sam/predict.py +2073 -139
  61. ultralytics/models/sam/sam3/__init__.py +3 -0
  62. ultralytics/models/sam/sam3/decoder.py +546 -0
  63. ultralytics/models/sam/sam3/encoder.py +535 -0
  64. ultralytics/models/sam/sam3/geometry_encoders.py +415 -0
  65. ultralytics/models/sam/sam3/maskformer_segmentation.py +286 -0
  66. ultralytics/models/sam/sam3/model_misc.py +198 -0
  67. ultralytics/models/sam/sam3/necks.py +129 -0
  68. ultralytics/models/sam/sam3/sam3_image.py +339 -0
  69. ultralytics/models/sam/sam3/text_encoder_ve.py +307 -0
  70. ultralytics/models/sam/sam3/vitdet.py +546 -0
  71. ultralytics/models/sam/sam3/vl_combiner.py +160 -0
  72. ultralytics/models/yolo/classify/val.py +1 -1
  73. ultralytics/models/yolo/detect/train.py +1 -1
  74. ultralytics/models/yolo/detect/val.py +7 -7
  75. ultralytics/models/yolo/obb/val.py +19 -8
  76. ultralytics/models/yolo/pose/val.py +1 -1
  77. ultralytics/models/yolo/segment/val.py +1 -1
  78. ultralytics/nn/autobackend.py +9 -9
  79. ultralytics/nn/modules/block.py +1 -1
  80. ultralytics/nn/modules/transformer.py +21 -1
  81. ultralytics/nn/tasks.py +3 -3
  82. ultralytics/nn/text_model.py +2 -7
  83. ultralytics/solutions/ai_gym.py +1 -1
  84. ultralytics/solutions/analytics.py +6 -6
  85. ultralytics/solutions/config.py +1 -1
  86. ultralytics/solutions/distance_calculation.py +1 -1
  87. ultralytics/solutions/object_counter.py +1 -1
  88. ultralytics/solutions/object_cropper.py +3 -6
  89. ultralytics/solutions/parking_management.py +21 -17
  90. ultralytics/solutions/queue_management.py +5 -5
  91. ultralytics/solutions/region_counter.py +2 -2
  92. ultralytics/solutions/security_alarm.py +1 -1
  93. ultralytics/solutions/solutions.py +45 -22
  94. ultralytics/solutions/speed_estimation.py +1 -1
  95. ultralytics/trackers/basetrack.py +1 -1
  96. ultralytics/trackers/bot_sort.py +4 -3
  97. ultralytics/trackers/byte_tracker.py +4 -4
  98. ultralytics/trackers/utils/gmc.py +6 -7
  99. ultralytics/trackers/utils/kalman_filter.py +2 -1
  100. ultralytics/trackers/utils/matching.py +4 -3
  101. ultralytics/utils/__init__.py +12 -3
  102. ultralytics/utils/benchmarks.py +2 -2
  103. ultralytics/utils/callbacks/tensorboard.py +19 -25
  104. ultralytics/utils/checks.py +4 -3
  105. ultralytics/utils/downloads.py +1 -1
  106. ultralytics/utils/export/tensorflow.py +16 -2
  107. ultralytics/utils/files.py +13 -12
  108. ultralytics/utils/logger.py +62 -27
  109. ultralytics/utils/metrics.py +1 -1
  110. ultralytics/utils/ops.py +7 -9
  111. ultralytics/utils/patches.py +3 -3
  112. ultralytics/utils/plotting.py +7 -12
  113. ultralytics/utils/tuner.py +1 -1
  114. {dgenerate_ultralytics_headless-8.3.236.dist-info → dgenerate_ultralytics_headless-8.3.239.dist-info}/WHEEL +0 -0
  115. {dgenerate_ultralytics_headless-8.3.236.dist-info → dgenerate_ultralytics_headless-8.3.239.dist-info}/entry_points.txt +0 -0
  116. {dgenerate_ultralytics_headless-8.3.236.dist-info → dgenerate_ultralytics_headless-8.3.239.dist-info}/licenses/LICENSE +0 -0
  117. {dgenerate_ultralytics_headless-8.3.236.dist-info → dgenerate_ultralytics_headless-8.3.239.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,535 @@
1
+ # Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license
2
+
3
+ # Copyright (c) Meta Platforms, Inc. and affiliates. All Rights Reserved
4
+ # Based on https://github.com/IDEA-Research/GroundingDINO
5
+ from __future__ import annotations
6
+
7
+ import torch
8
+ from torch import nn
9
+
10
+ from ultralytics.nn.modules.utils import _get_clones
11
+
12
+ from .model_misc import get_valid_ratio
13
+
14
+
15
+ class TransformerEncoderLayer(nn.Module):
16
+ """Transformer encoder layer that performs self-attention followed by cross-attention.
17
+
18
+ This layer was previously called TransformerDecoderLayer but was renamed to better reflect its role in the
19
+ architecture. It processes input sequences through self-attention and then cross-attention with another input
20
+ (typically image features).
21
+
22
+ The layer supports both pre-norm and post-norm configurations, as well as positional encoding at different stages of
23
+ the attention mechanism.
24
+ """
25
+
26
+ def __init__(
27
+ self,
28
+ d_model: int,
29
+ dim_feedforward: int,
30
+ dropout: float,
31
+ pos_enc_at_attn: bool,
32
+ pos_enc_at_cross_attn_keys: bool,
33
+ pos_enc_at_cross_attn_queries: bool,
34
+ pre_norm: bool,
35
+ self_attention: nn.Module = None,
36
+ cross_attention: nn.Module = None,
37
+ ):
38
+ """Initialize a transformer encoder layer.
39
+
40
+ Args:
41
+ cross_attention: Cross-attention module for attending to image features
42
+ d_model: Model dimension/hidden size
43
+ dim_feedforward: Dimension of the feedforward network
44
+ dropout: Dropout probability
45
+ pos_enc_at_attn: Whether to add positional encodings at self-attention
46
+ pos_enc_at_cross_attn_keys: Whether to add positional encodings to keys in cross-attention
47
+ pos_enc_at_cross_attn_queries: Whether to add positional encodings to queries in cross-attention
48
+ pre_norm: Whether to use pre-norm (True) or post-norm (False) architecture
49
+ self_attention: Self-attention module
50
+ """
51
+ super().__init__()
52
+ self.d_model = d_model
53
+ self.dim_feedforward = dim_feedforward
54
+ self.dropout_value = dropout
55
+ self.self_attn = self_attention or nn.MultiheadAttention(num_heads=8, dropout=0.1, embed_dim=256)
56
+ self.cross_attn_image = cross_attention or nn.MultiheadAttention(num_heads=8, dropout=0.1, embed_dim=256)
57
+
58
+ # Implementation of Feedforward model
59
+ self.linear1 = nn.Linear(d_model, dim_feedforward)
60
+ self.dropout = nn.Dropout(dropout)
61
+ self.linear2 = nn.Linear(dim_feedforward, d_model)
62
+
63
+ self.norm1 = nn.LayerNorm(d_model)
64
+ self.norm2 = nn.LayerNorm(d_model)
65
+ self.norm3 = nn.LayerNorm(d_model)
66
+ self.dropout1 = nn.Dropout(dropout)
67
+ self.dropout2 = nn.Dropout(dropout)
68
+ self.dropout3 = nn.Dropout(dropout)
69
+
70
+ self.activation = nn.ReLU()
71
+ self.pre_norm = pre_norm
72
+
73
+ self.pos_enc_at_attn = pos_enc_at_attn
74
+ self.pos_enc_at_cross_attn_queries = pos_enc_at_cross_attn_queries
75
+ self.pos_enc_at_cross_attn_keys = pos_enc_at_cross_attn_keys
76
+
77
+ self.layer_idx = None
78
+
79
+ def forward_post(
80
+ self,
81
+ tgt: torch.Tensor,
82
+ memory: torch.Tensor,
83
+ tgt_mask: torch.Tensor = None,
84
+ memory_mask: torch.Tensor = None,
85
+ tgt_key_padding_mask: torch.Tensor = None,
86
+ memory_key_padding_mask: torch.Tensor = None,
87
+ pos: torch.Tensor = None,
88
+ query_pos: torch.Tensor = None,
89
+ **kwargs,
90
+ ) -> torch.Tensor:
91
+ """Forward pass for post-norm architecture.
92
+
93
+ In post-norm architecture, normalization is applied after attention and feedforward operations.
94
+
95
+ Args:
96
+ tgt: Input tensor to be processed
97
+ memory: Memory tensor for cross-attention
98
+ tgt_mask: Mask for self-attention
99
+ memory_mask: Mask for cross-attention
100
+ tgt_key_padding_mask: Key padding mask for self-attention
101
+ memory_key_padding_mask: Key padding mask for cross-attention
102
+ pos: Positional encoding for memory
103
+ query_pos: Positional encoding for query
104
+ **kwargs: Additional keyword arguments
105
+
106
+ Returns:
107
+ Processed tensor
108
+ """
109
+ q = k = tgt + query_pos if self.pos_enc_at_attn else tgt
110
+
111
+ # Self attention
112
+ tgt2 = self.self_attn(
113
+ q, k, value=tgt, attn_mask=tgt_mask, key_padding_mask=tgt_key_padding_mask, need_weights=False
114
+ )[0]
115
+ tgt = tgt + self.dropout1(tgt2)
116
+ tgt = self.norm1(tgt)
117
+
118
+ # Cross attention to image
119
+ tgt2 = self.cross_attn_image(
120
+ query=tgt + query_pos if self.pos_enc_at_cross_attn_queries else tgt,
121
+ key=memory + pos if self.pos_enc_at_cross_attn_keys else memory,
122
+ value=memory,
123
+ attn_mask=memory_mask,
124
+ key_padding_mask=memory_key_padding_mask,
125
+ need_weights=False,
126
+ )[0]
127
+ tgt = tgt + self.dropout2(tgt2)
128
+ tgt = self.norm2(tgt)
129
+
130
+ # FFN
131
+ tgt2 = self.linear2(self.dropout(self.activation(self.linear1(tgt))))
132
+ tgt = tgt + self.dropout3(tgt2)
133
+ tgt = self.norm3(tgt)
134
+ return tgt
135
+
136
+ def forward_pre(
137
+ self,
138
+ tgt: torch.Tensor,
139
+ memory: torch.Tensor,
140
+ dac: bool = False,
141
+ tgt_mask: torch.Tensor = None,
142
+ memory_mask: torch.Tensor = None,
143
+ tgt_key_padding_mask: torch.Tensor = None,
144
+ memory_key_padding_mask: torch.Tensor = None,
145
+ pos: torch.Tensor = None,
146
+ query_pos: torch.Tensor = None,
147
+ # **kwargs,
148
+ ) -> torch.Tensor:
149
+ """Forward pass for pre-norm architecture.
150
+
151
+ In pre-norm architecture, normalization is applied before attention and feedforward operations.
152
+
153
+ Args:
154
+ tgt: Input tensor to be processed
155
+ memory: Memory tensor for cross-attention
156
+ dac: Whether to use Divide-and-Conquer attention
157
+ tgt_mask: Mask for self-attention
158
+ memory_mask: Mask for cross-attention
159
+ tgt_key_padding_mask: Key padding mask for self-attention
160
+ memory_key_padding_mask: Key padding mask for cross-attention
161
+ pos: Positional encoding for memory
162
+ query_pos: Positional encoding for query
163
+ attn_bias: Optional attention bias tensor
164
+ **kwargs: Additional keyword arguments
165
+
166
+ Returns:
167
+ Processed tensor
168
+ """
169
+ if dac:
170
+ # we only apply self attention to the first half of the queries
171
+ assert tgt.shape[0] % 2 == 0
172
+ other_tgt = tgt[tgt.shape[0] // 2 :]
173
+ tgt = tgt[: tgt.shape[0] // 2]
174
+ tgt2 = self.norm1(tgt).contiguous()
175
+ q = k = tgt2 + query_pos if self.pos_enc_at_attn else tgt2
176
+ tgt2 = self.self_attn(q, k, value=tgt2, attn_mask=tgt_mask, key_padding_mask=tgt_key_padding_mask)[0]
177
+ tgt = tgt + self.dropout1(tgt2)
178
+ if dac:
179
+ # Recombine
180
+ tgt = torch.cat((tgt, other_tgt), dim=0)
181
+ tgt2 = self.norm2(tgt)
182
+ memory = memory.to(tgt2.dtype).contiguous()
183
+ tgt2 = self.cross_attn_image(
184
+ query=tgt2 + query_pos if self.pos_enc_at_cross_attn_queries else tgt2,
185
+ key=memory + pos if self.pos_enc_at_cross_attn_keys else memory,
186
+ value=memory,
187
+ attn_mask=memory_mask,
188
+ key_padding_mask=memory_key_padding_mask,
189
+ )[0]
190
+ tgt = tgt + self.dropout2(tgt2)
191
+ tgt2 = self.norm3(tgt)
192
+ tgt2 = self.linear2(self.dropout(self.activation(self.linear1(tgt2))))
193
+ tgt = tgt + self.dropout3(tgt2)
194
+ return tgt
195
+
196
+ def forward(
197
+ self,
198
+ tgt: torch.Tensor,
199
+ memory: torch.Tensor,
200
+ dac: bool = False,
201
+ tgt_mask: torch.Tensor = None,
202
+ memory_mask: torch.Tensor = None,
203
+ tgt_key_padding_mask: torch.Tensor = None,
204
+ memory_key_padding_mask: torch.Tensor = None,
205
+ pos: torch.Tensor = None,
206
+ query_pos: torch.Tensor = None,
207
+ # **kwds: Any,
208
+ ) -> torch.Tensor:
209
+ """Forward pass for the transformer encoder layer.
210
+
211
+ Args:
212
+ tgt: Input tensor to be processed
213
+ memory: Memory tensor (e.g., image features) for cross-attention
214
+ dac: Whether to use Divide-and-Conquer attention (only apply self-attention to first half)
215
+ tgt_mask: Mask for self-attention
216
+ memory_mask: Mask for cross-attention
217
+ tgt_key_padding_mask: Key padding mask for self-attention
218
+ memory_key_padding_mask: Key padding mask for cross-attention
219
+ pos: Positional encoding for memory
220
+ query_pos: Positional encoding for query
221
+ attn_bias: Optional attention bias tensor
222
+ **kwds: Additional keyword arguments
223
+
224
+ Returns:
225
+ Processed tensor after self-attention, cross-attention, and feedforward network
226
+ """
227
+ fwd_fn = self.forward_pre if self.pre_norm else self.forward_post
228
+ return fwd_fn(
229
+ tgt,
230
+ memory,
231
+ dac=dac,
232
+ tgt_mask=tgt_mask,
233
+ memory_mask=memory_mask,
234
+ tgt_key_padding_mask=tgt_key_padding_mask,
235
+ memory_key_padding_mask=memory_key_padding_mask,
236
+ pos=pos,
237
+ query_pos=query_pos,
238
+ # attn_bias=attn_bias,
239
+ # **kwds,
240
+ )
241
+
242
+
243
+ class TransformerEncoder(nn.Module):
244
+ """Transformer encoder that processes multi-level features.
245
+
246
+ This encoder takes multi-level features (e.g., from a backbone network) and processes them through a stack of
247
+ transformer encoder layers. It supports features from multiple levels (e.g., different resolutions) and can apply
248
+ activation checkpointing for memory efficiency during training.
249
+
250
+ Args:
251
+ layer: The encoder layer to be stacked multiple times
252
+ num_layers: Number of encoder layers to stack
253
+ d_model: Model dimension/hidden size
254
+ num_feature_levels: Number of feature levels to process
255
+ frozen: Whether to freeze the parameters of this module
256
+ use_act_checkpoint: Whether to use activation checkpointing during training
257
+ """
258
+
259
+ def __init__(
260
+ self,
261
+ layer: nn.Module,
262
+ num_layers: int,
263
+ d_model: int,
264
+ num_feature_levels: int,
265
+ frozen: bool = False,
266
+ use_act_checkpoint: bool = False,
267
+ ):
268
+ """Initialize the transformer encoder."""
269
+ super().__init__()
270
+ self.layers = _get_clones(layer, num_layers)
271
+ self.num_layers = num_layers
272
+
273
+ self.num_feature_levels = num_feature_levels
274
+ self.level_embed = None
275
+ if num_feature_levels > 1:
276
+ self.level_embed = nn.Parameter(torch.Tensor(num_feature_levels, d_model))
277
+
278
+ if frozen:
279
+ for p in self.parameters():
280
+ p.requires_grad_(False)
281
+
282
+ self.use_act_checkpoint = use_act_checkpoint
283
+
284
+ # assign layer index to each layer so that some layers can decide what to do
285
+ # based on which layer index they are (e.g. cross attention to memory bank only
286
+ # in selected layers)
287
+ for layer_idx, layer in enumerate(self.layers):
288
+ layer.layer_idx = layer_idx
289
+
290
+ def _prepare_multilevel_features(self, srcs, masks, pos_embeds):
291
+ """Prepare multi-level features for transformer encoder."""
292
+ assert len(srcs) == self.num_feature_levels, "mismatch between expected and received # of feature levels"
293
+
294
+ src_flatten = []
295
+ mask_flatten = []
296
+ lvl_pos_embed_flatten = []
297
+ spatial_shapes = []
298
+ has_mask = masks is not None and masks[0] is not None
299
+ for lvl, (src, mask, pos_embed) in enumerate(zip(srcs, masks, pos_embeds)):
300
+ _, _, h, w = src.shape
301
+ spatial_shape = (h, w)
302
+ spatial_shapes.append(spatial_shape)
303
+
304
+ src = src.flatten(2).transpose(1, 2) # bs, hw, c
305
+ if has_mask:
306
+ mask = mask.flatten(1)
307
+ pos_embed = pos_embed.flatten(2).transpose(1, 2) # bs, hw, c
308
+ if self.level_embed is not None:
309
+ lvl_pos_embed = pos_embed + self.level_embed[lvl].view(1, 1, -1)
310
+ else:
311
+ lvl_pos_embed = pos_embed
312
+ lvl_pos_embed_flatten.append(lvl_pos_embed)
313
+ src_flatten.append(src)
314
+ if has_mask:
315
+ mask_flatten.append(mask)
316
+ src_flatten = torch.cat(src_flatten, 1) # bs, \sum{hxw}, c
317
+ mask_flatten = torch.cat(mask_flatten, 1) if has_mask else None # bs, \sum{hxw}
318
+ lvl_pos_embed_flatten = torch.cat(lvl_pos_embed_flatten, 1) # bs, \sum{hxw}, c
319
+ spatial_shapes = torch.tensor(spatial_shapes, dtype=torch.long, device=src_flatten.device)
320
+ level_start_index = torch.cat(
321
+ (
322
+ spatial_shapes.new_zeros((1,)),
323
+ spatial_shapes.prod(1).cumsum(0)[:-1],
324
+ )
325
+ )
326
+ if has_mask:
327
+ valid_ratios = torch.stack([get_valid_ratio(m) for m in masks], 1)
328
+ else:
329
+ valid_ratios = torch.ones(
330
+ (src_flatten.shape[0], self.num_feature_levels, 2),
331
+ device=src_flatten.device,
332
+ dtype=src_flatten.dtype,
333
+ )
334
+
335
+ return (
336
+ src_flatten,
337
+ mask_flatten,
338
+ lvl_pos_embed_flatten,
339
+ level_start_index,
340
+ valid_ratios,
341
+ spatial_shapes,
342
+ )
343
+
344
+ def forward(
345
+ self,
346
+ src: list[torch.Tensor],
347
+ src_key_padding_masks: list[torch.Tensor] | None = None,
348
+ pos: list[torch.Tensor] | None = None,
349
+ prompt: torch.Tensor = None,
350
+ prompt_key_padding_mask: torch.Tensor = None,
351
+ encoder_extra_kwargs: dict | None = None,
352
+ ) -> tuple[torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor]:
353
+ """Process multi-level features through the transformer encoder.
354
+
355
+ Args:
356
+ src: List of multi-level features, each with shape (batch_size, channels, height, width)
357
+ src_key_padding_masks: List of padding masks for each feature level, each with shape (batch_size, height,
358
+ width)
359
+ pos: List of positional embeddings for each feature level, each with shape (batch_size, channels, height,
360
+ width)
361
+ prompt: Optional text/prompt features to attend to, with shape (seq_len, batch_size, d_model)
362
+ prompt_key_padding_mask: Optional padding mask for prompt, with shape (batch_size, seq_len)
363
+ encoder_extra_kwargs: Optional additional arguments to pass to each encoder layer
364
+
365
+ Returns:
366
+ A tuple containing:
367
+ - output: Processed features with shape (seq_len, batch_size, d_model)
368
+ - key_padding_masks_flatten: Flattened padding masks
369
+ - lvl_pos_embed_flatten: Flattened positional embeddings
370
+ - level_start_index: Starting indices for each feature level
371
+ - spatial_shapes: Spatial dimensions of each feature level
372
+ - valid_ratios: Valid ratios for each feature level
373
+ """
374
+ assert len(src) == self.num_feature_levels, "must be equal to num_feature_levels"
375
+ if src_key_padding_masks is not None:
376
+ assert len(src_key_padding_masks) == self.num_feature_levels
377
+ if pos is not None:
378
+ assert len(pos) == self.num_feature_levels
379
+ # Flatten multilevel feats and add level pos embeds
380
+ (
381
+ src_flatten,
382
+ key_padding_masks_flatten,
383
+ lvl_pos_embed_flatten,
384
+ level_start_index,
385
+ valid_ratios,
386
+ spatial_shapes,
387
+ ) = self._prepare_multilevel_features(src, src_key_padding_masks, pos)
388
+
389
+ output = src_flatten
390
+ for layer in self.layers:
391
+ layer_kwargs = {}
392
+
393
+ assert isinstance(layer, TransformerEncoderLayer)
394
+ layer_kwargs["memory"] = prompt
395
+ layer_kwargs["memory_key_padding_mask"] = prompt_key_padding_mask
396
+ layer_kwargs["query_pos"] = lvl_pos_embed_flatten
397
+ layer_kwargs["tgt"] = output
398
+ layer_kwargs["tgt_key_padding_mask"] = key_padding_masks_flatten
399
+
400
+ if self.training:
401
+ assert self.use_act_checkpoint, "activation ckpt not enabled in encoder"
402
+ if encoder_extra_kwargs is not None:
403
+ layer_kwargs.update(encoder_extra_kwargs)
404
+ output = layer(**layer_kwargs)
405
+ # return as seq first
406
+ return (
407
+ output.transpose(0, 1),
408
+ (key_padding_masks_flatten.transpose(0, 1) if key_padding_masks_flatten is not None else None),
409
+ lvl_pos_embed_flatten.transpose(0, 1),
410
+ level_start_index,
411
+ spatial_shapes,
412
+ valid_ratios,
413
+ )
414
+
415
+
416
+ class TransformerEncoderFusion(TransformerEncoder):
417
+ """Transformer encoder that fuses text and image features.
418
+
419
+ This encoder extends TransformerEncoder to handle both text and image features, with the ability to add pooled text
420
+ features to image features for better cross-modal fusion. It supports torch.compile for performance optimization.
421
+
422
+ Args:
423
+ layer: The encoder layer to be stacked multiple times
424
+ num_layers: Number of encoder layers to stack
425
+ d_model: Model dimension/hidden size
426
+ num_feature_levels: Number of feature levels to process
427
+ add_pooled_text_to_img_feat: Whether to add pooled text features to image features
428
+ pool_text_with_mask: Whether to use the mask when pooling text features
429
+ compile_mode: Mode for torch.compile, or None to disable compilation
430
+ **kwargs: Additional arguments to pass to the parent class
431
+ """
432
+
433
+ def __init__(
434
+ self,
435
+ layer: nn.Module,
436
+ num_layers: int,
437
+ d_model: int,
438
+ num_feature_levels: int,
439
+ add_pooled_text_to_img_feat: bool = True,
440
+ pool_text_with_mask: bool = False,
441
+ compile_mode: str | None = None,
442
+ **kwargs,
443
+ ):
444
+ """Initialize the transformer encoder with text-image fusion."""
445
+ super().__init__(
446
+ layer,
447
+ num_layers,
448
+ d_model,
449
+ num_feature_levels,
450
+ **kwargs,
451
+ )
452
+ self.add_pooled_text_to_img_feat = add_pooled_text_to_img_feat
453
+ if self.add_pooled_text_to_img_feat:
454
+ self.text_pooling_proj = nn.Linear(d_model, d_model)
455
+ self.pool_text_with_mask = pool_text_with_mask
456
+ if compile_mode is not None:
457
+ self.forward = torch.compile(self.forward, mode=compile_mode, fullgraph=True)
458
+
459
+ def forward(
460
+ self,
461
+ src: list[torch.Tensor],
462
+ prompt: torch.Tensor,
463
+ src_key_padding_mask: list[torch.Tensor] | None = None,
464
+ src_pos: list[torch.Tensor] | None = None,
465
+ prompt_key_padding_mask: torch.Tensor = None,
466
+ feat_sizes: list[int] | None = None,
467
+ encoder_extra_kwargs: dict | None = None,
468
+ ):
469
+ """Forward pass for the transformer encoder with text-image fusion."""
470
+ # Restore spatial shapes of vision
471
+ bs = src[0].shape[1] # seq first
472
+ if feat_sizes is not None:
473
+ assert len(feat_sizes) == len(src)
474
+ if src_key_padding_mask is None:
475
+ src_key_padding_mask = [None] * len(src)
476
+ for i, (h, w) in enumerate(feat_sizes):
477
+ src[i] = src[i].reshape(h, w, bs, -1).permute(2, 3, 0, 1)
478
+ src_pos[i] = src_pos[i].reshape(h, w, bs, -1).permute(2, 3, 0, 1)
479
+ src_key_padding_mask[i] = (
480
+ src_key_padding_mask[i].reshape(h, w, bs).permute(2, 0, 1)
481
+ if src_key_padding_mask[i] is not None
482
+ else None
483
+ )
484
+ else:
485
+ assert all(x.dim == 4 for x in src), "expected list of (bs, c, h, w) tensors"
486
+
487
+ if self.add_pooled_text_to_img_feat:
488
+ # Fusion: Add mean pooled text to image features
489
+ pooled_text = pool_text_feat(prompt, prompt_key_padding_mask, self.pool_text_with_mask)
490
+ pooled_text = self.text_pooling_proj(pooled_text)[..., None, None] # prompt is seq first
491
+ src = [x.add_(pooled_text) for x in src]
492
+
493
+ (
494
+ out,
495
+ key_padding_masks_flatten,
496
+ lvl_pos_embed_flatten,
497
+ level_start_index,
498
+ spatial_shapes,
499
+ valid_ratios,
500
+ ) = super().forward(
501
+ src,
502
+ src_key_padding_masks=src_key_padding_mask,
503
+ pos=src_pos,
504
+ prompt=prompt.transpose(0, 1),
505
+ prompt_key_padding_mask=prompt_key_padding_mask,
506
+ encoder_extra_kwargs=encoder_extra_kwargs,
507
+ )
508
+
509
+ return {
510
+ "memory": out,
511
+ "padding_mask": key_padding_masks_flatten,
512
+ "pos_embed": lvl_pos_embed_flatten,
513
+ "memory_text": prompt,
514
+ "level_start_index": level_start_index,
515
+ "spatial_shapes": spatial_shapes,
516
+ "valid_ratios": valid_ratios,
517
+ }
518
+
519
+
520
+ def pool_text_feat(prompt, prompt_mask, pool_with_mask):
521
+ """Mean-pool the prompt embeddings over the valid tokens only."""
522
+ # prompt has shape (seq, bs, dim)
523
+ if not pool_with_mask:
524
+ return prompt.mean(dim=0)
525
+
526
+ # prompt_mask has shape (bs, seq), where False is valid and True is padding
527
+ assert prompt_mask.dim() == 2
528
+ # is_valid has shape (seq, bs, 1), where 1 is valid and 0 is padding
529
+ is_valid = (~prompt_mask).float().permute(1, 0)[..., None]
530
+ # num_valid has shape (bs, 1)
531
+ num_valid = torch.clamp(torch.sum(is_valid, dim=0), min=1.0)
532
+
533
+ # mean pool over all the valid tokens
534
+ pooled_text = (prompt * is_valid).sum(dim=0) / num_valid
535
+ return pooled_text