t29mato Claude Opus 4.6 commited on
Commit
0b92003
·
1 Parent(s): 4e247c3

Implement full CPU forward for MultiScaleDeformableAttention

Browse files

Replace NotImplementedError stub with the actual pure-Python CPU
implementation using F.grid_sample bilinear interpolation, matching
the upstream mmcv multi_scale_deformable_attn_pytorch logic.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

Files changed (1) hide show
  1. setup_mmcv_shim.py +100 -6
setup_mmcv_shim.py CHANGED
@@ -170,35 +170,129 @@ class ConcatCell:
170
  def __init__(self, *args, **kwargs): raise NotImplementedError
171
  ''')
172
 
173
- # Create multi_scale_deform_attn stub with registry registration
174
  with open(os.path.join(ops_dir, 'multi_scale_deform_attn.py'), 'w') as f:
175
- f.write('''import warnings
 
176
  import torch
177
  import torch.nn as nn
 
 
178
  from mmcv.cnn.bricks.registry import ATTENTION
179
  from mmcv.runner import BaseModule
180
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
181
  @ATTENTION.register_module()
182
  class MultiScaleDeformableAttention(BaseModule):
183
  def __init__(self, embed_dims=256, num_heads=8, num_levels=4, num_points=4,
184
  im2col_step=64, dropout=0.1, batch_first=False, norm_cfg=None,
185
  init_cfg=None, **kwargs):
186
  super().__init__(init_cfg)
 
 
 
 
 
 
 
187
  self.embed_dims = embed_dims
188
- self.num_heads = num_heads
189
  self.num_levels = num_levels
 
190
  self.num_points = num_points
191
- self.batch_first = batch_first
192
  self.sampling_offsets = nn.Linear(embed_dims, num_heads * num_levels * num_points * 2)
193
  self.attention_weights = nn.Linear(embed_dims, num_heads * num_levels * num_points)
194
  self.value_proj = nn.Linear(embed_dims, embed_dims)
195
  self.output_proj = nn.Linear(embed_dims, embed_dims)
196
- self.dropout = nn.Dropout(dropout)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
197
 
198
  def forward(self, query, key=None, value=None, identity=None,
199
  query_pos=None, key_padding_mask=None, reference_points=None,
200
  spatial_shapes=None, level_start_index=None, **kwargs):
201
- raise NotImplementedError("MultiScaleDeformableAttention CPU shim - forward not implemented")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
202
  ''')
203
 
204
  print('mmcv.ops shim created successfully')
 
170
  def __init__(self, *args, **kwargs): raise NotImplementedError
171
  ''')
172
 
173
+ # Create multi_scale_deform_attn with full CPU implementation
174
  with open(os.path.join(ops_dir, 'multi_scale_deform_attn.py'), 'w') as f:
175
+ f.write('''import math
176
+ import warnings
177
  import torch
178
  import torch.nn as nn
179
+ import torch.nn.functional as F
180
+ from mmcv.cnn import constant_init, xavier_init
181
  from mmcv.cnn.bricks.registry import ATTENTION
182
  from mmcv.runner import BaseModule
183
 
184
+
185
+ def multi_scale_deformable_attn_pytorch(value, value_spatial_shapes,
186
+ sampling_locations, attention_weights):
187
+ """CPU version of multi-scale deformable attention."""
188
+ bs, _, num_heads, embed_dims = value.shape
189
+ _, num_queries, num_heads, num_levels, num_points, _ = sampling_locations.shape
190
+ value_list = value.split([int(H_ * W_) for H_, W_ in value_spatial_shapes], dim=1)
191
+ sampling_grids = 2 * sampling_locations - 1
192
+ sampling_value_list = []
193
+ for level, (H_, W_) in enumerate(value_spatial_shapes):
194
+ value_l_ = value_list[level].flatten(2).transpose(1, 2).reshape(
195
+ bs * num_heads, embed_dims, int(H_), int(W_))
196
+ sampling_grid_l_ = sampling_grids[:, :, :, level].transpose(1, 2).flatten(0, 1)
197
+ sampling_value_l_ = F.grid_sample(
198
+ value_l_, sampling_grid_l_, mode=\'bilinear\', padding_mode=\'zeros\',
199
+ align_corners=False)
200
+ sampling_value_list.append(sampling_value_l_)
201
+ attention_weights = attention_weights.transpose(1, 2).reshape(
202
+ bs * num_heads, 1, num_queries, num_levels * num_points)
203
+ output = (torch.stack(sampling_value_list, dim=-2).flatten(-2) *
204
+ attention_weights).sum(-1).view(bs, num_heads * embed_dims, num_queries)
205
+ return output.transpose(1, 2).contiguous()
206
+
207
+
208
  @ATTENTION.register_module()
209
  class MultiScaleDeformableAttention(BaseModule):
210
  def __init__(self, embed_dims=256, num_heads=8, num_levels=4, num_points=4,
211
  im2col_step=64, dropout=0.1, batch_first=False, norm_cfg=None,
212
  init_cfg=None, **kwargs):
213
  super().__init__(init_cfg)
214
+ if embed_dims % num_heads != 0:
215
+ raise ValueError(f\'embed_dims must be divisible by num_heads, \'
216
+ f\'but got {embed_dims} and {num_heads}\')
217
+ self.norm_cfg = norm_cfg
218
+ self.dropout = nn.Dropout(dropout)
219
+ self.batch_first = batch_first
220
+ self.im2col_step = im2col_step
221
  self.embed_dims = embed_dims
 
222
  self.num_levels = num_levels
223
+ self.num_heads = num_heads
224
  self.num_points = num_points
 
225
  self.sampling_offsets = nn.Linear(embed_dims, num_heads * num_levels * num_points * 2)
226
  self.attention_weights = nn.Linear(embed_dims, num_heads * num_levels * num_points)
227
  self.value_proj = nn.Linear(embed_dims, embed_dims)
228
  self.output_proj = nn.Linear(embed_dims, embed_dims)
229
+ self.init_weights()
230
+
231
+ def init_weights(self):
232
+ constant_init(self.sampling_offsets, 0.)
233
+ device = next(self.parameters()).device
234
+ thetas = torch.arange(self.num_heads, dtype=torch.float32, device=device) * (2.0 * math.pi / self.num_heads)
235
+ grid_init = torch.stack([thetas.cos(), thetas.sin()], -1)
236
+ grid_init = (grid_init / grid_init.abs().max(-1, keepdim=True)[0]).view(
237
+ self.num_heads, 1, 1, 2).repeat(1, self.num_levels, self.num_points, 1)
238
+ for i in range(self.num_points):
239
+ grid_init[:, :, i, :] *= i + 1
240
+ self.sampling_offsets.bias.data = grid_init.view(-1)
241
+ constant_init(self.attention_weights, val=0., bias=0.)
242
+ xavier_init(self.value_proj, distribution=\'uniform\', bias=0.)
243
+ xavier_init(self.output_proj, distribution=\'uniform\', bias=0.)
244
+ self._is_init = True
245
 
246
  def forward(self, query, key=None, value=None, identity=None,
247
  query_pos=None, key_padding_mask=None, reference_points=None,
248
  spatial_shapes=None, level_start_index=None, **kwargs):
249
+ if value is None:
250
+ value = query
251
+ if identity is None:
252
+ identity = query
253
+ if query_pos is not None:
254
+ query = query + query_pos
255
+ if not self.batch_first:
256
+ query = query.permute(1, 0, 2)
257
+ value = value.permute(1, 0, 2)
258
+
259
+ bs, num_query, _ = query.shape
260
+ bs, num_value, _ = value.shape
261
+
262
+ value = self.value_proj(value)
263
+ if key_padding_mask is not None:
264
+ value = value.masked_fill(key_padding_mask[..., None], 0.0)
265
+ value = value.view(bs, num_value, self.num_heads, -1)
266
+
267
+ sampling_offsets = self.sampling_offsets(query).view(
268
+ bs, num_query, self.num_heads, self.num_levels, self.num_points, 2)
269
+ attention_weights = self.attention_weights(query).view(
270
+ bs, num_query, self.num_heads, self.num_levels * self.num_points)
271
+ attention_weights = attention_weights.softmax(-1)
272
+ attention_weights = attention_weights.view(
273
+ bs, num_query, self.num_heads, self.num_levels, self.num_points)
274
+
275
+ if reference_points.shape[-1] == 2:
276
+ offset_normalizer = torch.stack(
277
+ [spatial_shapes[..., 1], spatial_shapes[..., 0]], -1)
278
+ sampling_locations = reference_points[:, :, None, :, None, :] \\
279
+ + sampling_offsets / offset_normalizer[None, None, None, :, None, :]
280
+ elif reference_points.shape[-1] == 4:
281
+ sampling_locations = reference_points[:, :, None, :, None, :2] \\
282
+ + sampling_offsets / self.num_points \\
283
+ * reference_points[:, :, None, :, None, 2:] * 0.5
284
+ else:
285
+ raise ValueError(f\'Last dim of reference_points must be 2 or 4, \'
286
+ f\'but get {reference_points.shape[-1]} instead.\')
287
+
288
+ output = multi_scale_deformable_attn_pytorch(
289
+ value, spatial_shapes, sampling_locations, attention_weights)
290
+ output = self.output_proj(output)
291
+
292
+ if not self.batch_first:
293
+ output = output.permute(1, 0, 2)
294
+
295
+ return self.dropout(output) + identity
296
  ''')
297
 
298
  print('mmcv.ops shim created successfully')