Skip to content

Commit 896bb1c

Browse files
authored
Fix meshgrid converter to handle non-1D inputs (apple#2665)
The meshgrid op converter raises ValueError when inputs have rank > 1. This happens when converting models that use torch.linspace(...) / scalar before torch.meshgrid(), because the MIL real_div op can broadcast the 1D linspace result with a 0D scalar divisor to produce a higher-rank tensor. This pattern is common in deformable attention modules (DINO, Deformable-DETR, RF-DETR) where coordinate grids are created via: grid_y = torch.linspace(0.5, h - 0.5, steps=h) / h grid_x = torch.linspace(0.5, w - 0.5, steps=w) / w grid_y, grid_x = torch.meshgrid(grid_y, grid_x, indexing='ij') Instead of rejecting non-1D inputs, flatten them to 1D with mb.reshape(shape=[-1]) before meshgrid processing.
1 parent 01eedb0 commit 896bb1c

2 files changed

Lines changed: 65 additions & 2 deletions

File tree

coremltools/converters/mil/frontend/torch/ops.py

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6457,15 +6457,30 @@ def _check_args(tensor_inputs, indexing) -> None:
64576457
assert isinstance(tensor_inputs, (list, tuple))
64586458
if len(tensor_inputs) < 2:
64596459
raise ValueError("Requires >= 2 tensor inputs.")
6460-
if any(tensor_input.rank > 1 for tensor_input in tensor_inputs):
6461-
raise ValueError("meshgrid received non-1d tensor.")
64626460

64636461
if indexing not in ("ij", "xy"):
64646462
raise ValueError(f"indexing mode {indexing} not supported")
64656463

6464+
def _flatten_inputs(tensor_inputs):
6465+
"""Flatten non-1D inputs to 1D.
6466+
6467+
PyTorch JIT tracing can produce tensors with shape (N, 1) instead of (N,)
6468+
for ops like torch.linspace. Since meshgrid expects 1D inputs, we reshape
6469+
them here.
6470+
"""
6471+
flattened = []
6472+
for i, t in enumerate(tensor_inputs):
6473+
if t.rank > 1:
6474+
t = mb.reshape(
6475+
x=t, shape=[-1], name=node.name + "_flatten_" + str(i)
6476+
)
6477+
flattened.append(t)
6478+
return flattened
6479+
64666480
tensor_inputs, indexing = _parse_positional_args(context, node)
64676481
indexing = _parse_keyword_args(context, node, indexing)
64686482
_check_args(tensor_inputs, indexing)
6483+
tensor_inputs = _flatten_inputs(tensor_inputs)
64696484

64706485
result_symbolic_shape = [tensor_input.shape[0] for tensor_input in tensor_inputs]
64716486
result_shape = _utils.maybe_replace_symbols_with_source_tensor_shape_variables(

coremltools/converters/mil/frontend/torch/test/test_torch_ops.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11312,6 +11312,54 @@ def forward(self, x, y, z):
1131211312
compute_unit=compute_unit,
1131311313
)
1131411314

11315+
@pytest.mark.parametrize(
11316+
"compute_unit, backend, indexing",
11317+
itertools.product(
11318+
compute_units,
11319+
backends,
11320+
["ij", "xy"],
11321+
),
11322+
)
11323+
def test_meshgrid_non_1d_inputs(self, compute_unit, backend, indexing):
11324+
"""Test meshgrid where inputs become non-1D during conversion.
11325+
11326+
When a 1D tensor is divided by a 0D scalar (e.g. from a dynamic shape),
11327+
the MIL converter may produce a higher-rank result. The meshgrid converter
11328+
should flatten these to 1D before processing rather than raising an error.
11329+
11330+
This pattern occurs in deformable attention (DINO/Deformable-DETR/RF-DETR)
11331+
where coordinate grids are created via:
11332+
grid_y = torch.linspace(..., steps=h) / h
11333+
grid_x = torch.linspace(..., steps=w) / w
11334+
grid_y, grid_x = torch.meshgrid(grid_y, grid_x, indexing='ij')
11335+
"""
11336+
11337+
class TestModel(nn.Module):
11338+
def forward(self, feat):
11339+
h, w = feat.shape[2], feat.shape[3]
11340+
grid_y = torch.linspace(
11341+
0.5, h - 0.5, steps=h, dtype=feat.dtype, device=feat.device
11342+
) / h
11343+
grid_x = torch.linspace(
11344+
0.5, w - 0.5, steps=w, dtype=feat.dtype, device=feat.device
11345+
) / w
11346+
grid_y, grid_x = torch.meshgrid(grid_y, grid_x, indexing=indexing)
11347+
return torch.stack([grid_x, grid_y], dim=-1)
11348+
11349+
inputs = (torch.randn(1, 64, 4, 6),)
11350+
model = TestModel().eval()
11351+
expected_results = model(*inputs)
11352+
11353+
self.run_compare_torch(
11354+
inputs,
11355+
model,
11356+
expected_results,
11357+
input_as_shape=False,
11358+
frontend=TorchFrontend.TORCHSCRIPT,
11359+
backend=backend,
11360+
compute_unit=compute_unit,
11361+
)
11362+
1131511363

1131611364
class TestAddmm(TorchBaseTest):
1131711365
@pytest.mark.parametrize(

0 commit comments

Comments
 (0)