Skip to content

Commit 1bc58f6

Browse files
isaacrobclaudepre-commit-ci[bot]Bordaclaude[bot]
authored
Build spatial_shapes from symbolic Shape ops for TensorRT-safe ONNX export (roboflow#1155)
- Fixed TensorRT-safe ONNX export for Transformer `spatial_shapes`, avoiding ScatterND in shape-tensor computation. - Updated ONNX export behavior to preserve dynamic-batch compatibility across detection, segmentation, and keypoint models. - Improved Transformer export validation with coverage for dynamic batches, single-level feature maps, non-square spatial shapes, and PyTorch API compatibility. --------- Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Jirka Borovec <6035284+Borda@users.noreply.github.com> Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com>
1 parent 70d968e commit 1bc58f6

3 files changed

Lines changed: 422 additions & 6 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1010
### Fixed
1111

1212
- `HungarianMatcher.forward()` now uses the configured `focal_alpha` in the focal classification matching cost. Previously the value was hardcoded to `0.25`, silently ignoring any non-default `focal_alpha` passed to the constructor or `build_matcher`. This misaligned the bipartite matching cost with the focal classification loss in `criterion.py`, which correctly used `self.focal_alpha`. ([#1147](https://github.com/roboflow/rf-detr/pull/1147))
13+
- `spatial_shapes` in `Transformer.forward()` is now built from symbolic `Shape` ops (`torch.stack` of per-level `torch._shape_as_tensor` slices) instead of `torch.empty` + in-place index assignment. The previous pattern emitted a `ScatterND` feeding a shape tensor (`level_start_index`), which TensorRT rejected with "IScatterLayer cannot be used to compute a shape tensor". This fix is required to export any RF-DETR model to a TensorRT engine. ([#1155](https://github.com/roboflow/rf-detr/pull/1155))
1314

1415
---
1516

src/rfdetr/models/transformer.py

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -284,16 +284,10 @@ def forward(
284284
src_flatten = []
285285
mask_flatten = [] if masks is not None else None
286286
lvl_pos_embed_flatten = []
287-
# Build spatial_shapes as a tensor directly so that the ONNX tracer
288-
# can track h/w symbolically instead of baking constants into a constant
289-
# shape node.
290-
spatial_shapes = torch.empty((len(srcs), 2), device=srcs[0].device, dtype=torch.long)
291287
spatial_shapes_hw: list[tuple[int, int]] = []
292288
valid_ratios = [] if masks is not None else None
293289
for lvl, (src, pos_embed) in enumerate(zip(srcs, pos_embeds)):
294290
_, c, h, w = src.shape
295-
spatial_shapes[lvl, 0] = h
296-
spatial_shapes[lvl, 1] = w
297291
spatial_shapes_hw.append((h, w))
298292

299293
src = src.flatten(2).transpose(1, 2) # bs, hw, c
@@ -309,6 +303,18 @@ def forward(
309303
mask_flatten = torch.cat(mask_flatten, 1) # bs, \sum{hxw}
310304
valid_ratios = torch.stack([self.get_valid_ratio(m) for m in masks], 1)
311305
lvl_pos_embed_flatten = torch.cat(lvl_pos_embed_flatten, 1) # bs, \sum{hxw}, c
306+
# spatial_shapes must not be built by torch.empty(...) + in-place index assignment:
307+
# that emits a ScatterND feeding a shape tensor (level_start_index), which TensorRT
308+
# rejects ("IScatterLayer cannot be used to compute a shape tensor").
309+
# torch.as_tensor(python-int list) avoids ScatterND but bakes values as a Constant.
310+
# torch.stack of per-level torch._shape_as_tensor slices also produces a Constant
311+
# node in TorchScript ONNX export (the tracer records concrete H,W values at trace
312+
# time), but that Constant is accepted by TensorRT as a valid shape tensor source —
313+
# unlike ScatterND. torch._shape_as_tensor(t) is a private ATen op that returns a
314+
# 1-D int64 tensor of t's dimension sizes; [2:4] extracts (H, W) from NCHW.
315+
spatial_shapes = torch.stack([torch._shape_as_tensor(src)[2:4] for src in srcs]).to(
316+
device=srcs[0].device, dtype=torch.long
317+
)
312318
level_start_index = torch.cat((spatial_shapes.new_zeros((1,)), spatial_shapes.prod(1).cumsum(0)[:-1]))
313319

314320
# Flatten optional dual-projector features for keypoint-specific cross-attention.

0 commit comments

Comments
 (0)