[Relax] Add affine_grid operator with PyTorch and ONNX frontend support#18933
[Relax] Add affine_grid operator with PyTorch and ONNX frontend support#18933tlopex merged 6 commits intoapache:mainfrom
Conversation
Add relax.image.affine_grid operator with: - C++ op function, FFI registration, and struct info inference - Python wrapper with flexible size parameter handling - Legalization to topi.image.affine_grid - Op-level tests (struct info inference + e2e numerical correctness) - Legalization test Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add converter for aten.affine_grid_generator.default with: - Layout conversion from TVM [N,2,H,W] to PyTorch [N,H,W,2] - Structural IR test and numerical correctness test - Requires run_ep_decomposition=False for single-kernel path (5.6x faster) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add AffineGrid converter with: - Support for constant size tensor [N,C,H,W] - Layout conversion from TVM [N,2,H,W] to ONNX [N,H,W,2] - End-to-end correctness test against ONNX Runtime Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Summary of ChangesHello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request introduces the Highlights
🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console. Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here. Footnotes
|
There was a problem hiding this comment.
Code Review
This pull request introduces the affine_grid operator to TVM Relax, including its Python API, C++ backend implementation, and frontend converters for ONNX and PyTorch. Comprehensive tests for the operator, its legalization, and frontend integration have also been added. Feedback from the review suggests improving input validation in the ONNX frontend by using ValueError instead of assert, addressing potential runtime errors when converting symbolic PrimExpr to int during legalization, clarifying the implicit behavior of the size parameter in the Python API's docstring, and resolving or explaining the # type: ignore comment in the Python API.
| raise NotImplementedError(f"Dynamic size of type {type(size)} is not supported") | ||
|
|
||
| # Only 2D is supported: size = [N, C, H, W] | ||
| assert len(size_vals) == 4, "Only 2D AffineGrid (size=[N,C,H,W]) is supported" |
There was a problem hiding this comment.
Using assert for input validation is generally discouraged in production code, as assertions can be optimized out, leading to unexpected behavior. It's better to raise a ValueError or TVMError to ensure consistent error handling for invalid inputs.
| assert len(size_vals) == 4, "Only 2D AffineGrid (size=[N,C,H,W]) is supported" | |
| if len(size_vals) != 4: | |
| raise ValueError("Only 2D AffineGrid (size=[N,C,H,W]) is supported") |
|
|
||
| @register_legalize("relax.image.affine_grid") | ||
| def _image_affine_grid(bb: BlockBuilder, call: Call) -> Expr: | ||
| target_shape = [int(v) for v in call.args[1].values] |
There was a problem hiding this comment.
Converting PrimExpr values to int using int(v) can lead to runtime errors if v is a symbolic tir.Var that cannot be evaluated to a concrete integer at this stage. If topi.image.affine_grid requires concrete integer shapes, a check should be added to ensure v is not symbolic, or a more robust conversion mechanism should be used if topi can handle symbolic shapes.
There was a problem hiding this comment.
int(v) will crash with an unhelpful error on symbolic shapes. Consider adding an explicit check with a clear message, e.g.:
for v in call.args[1].values:
if not isinstance(v, (int, tir.IntImm)):
raise ValueError(
"affine_grid legalization requires static target_shape, "
f"got symbolic value: {v}"
)
| if isinstance(size, int | PrimExpr): | ||
| size = (size, size) |
There was a problem hiding this comment.
The behavior of treating a single int or PrimExpr for size as (size, size) (i.e., square dimensions) is an implicit convention. It would be beneficial to explicitly document this behavior in the docstring for clarity, or add a comment here.
| if isinstance(size, int | PrimExpr): | |
| size = (size, size) | |
| if isinstance(size, int | PrimExpr): # Assume square dimensions if a single value is provided | |
| size = (size, size) |
python/tvm/relax/op/image/image.py
Outdated
| if isinstance(size, tuple | list): | ||
| size = ShapeExpr(size) | ||
|
|
||
| return _ffi_api.affine_grid(data, size) # type: ignore |
There was a problem hiding this comment.
The # type: ignore comment indicates that the type checker is reporting an issue. It's generally best to resolve type issues directly rather than ignoring them, as it can hide potential bugs. If the type system cannot express this correctly, a more specific explanation for the ignore would be helpful.
tlopex
left a comment
There was a problem hiding this comment.
Some thing you need to improve
InferStructInfoAffineGrid only checks ndim == 3 right now, but does not validate shape[1] == 2 and shape[2] == 3. Might be worth adding compile-time checks here so invalid inputs do not fail only at runtime.
align_corners=False is rejected in both ONNX and PyTorch frontends, but this is not mentioned in the Python docstring in image.py. Could we document this limitation explicitly?
The legalization test only verifies output shape at the moment. Could we also check structural equality against an expected TVMScript module, similar to test_resize2d?
| raise NotImplementedError(f"Dynamic size of type {type(size)} is not supported") | ||
|
|
||
| # Only 2D is supported: size = [N, C, H, W] | ||
| assert len(size_vals) == 4, "Only 2D AffineGrid (size=[N,C,H,W]) is supported" |
|
|
||
| @register_legalize("relax.image.affine_grid") | ||
| def _image_affine_grid(bb: BlockBuilder, call: Call) -> Expr: | ||
| target_shape = [int(v) for v in call.args[1].values] |
There was a problem hiding this comment.
int(v) will crash with an unhelpful error on symbolic shapes. Consider adding an explicit check with a clear message, e.g.:
for v in call.args[1].values:
if not isinstance(v, (int, tir.IntImm)):
raise ValueError(
"affine_grid legalization requires static target_shape, "
f"got symbolic value: {v}"
)
src/relax/op/image/resize.cc
Outdated
| .set_attr<TMixedPrecisionPolicy>("TMixedPrecisionPolicy", MixedPrecisionPolicyKind::kFollow) | ||
| .set_attr<Bool>("FPurity", Bool(true)); | ||
|
|
||
| /* relax.affine_grid */ |
There was a problem hiding this comment.
Wrong comment. Should be relax.image.affine_grid
40debf8 to
54d65d8
Compare
|
Updated in a follow-up commit.
|
|
Please make sure that CI is passed, if sometimes CI is down, you could retrigger CI. Btw, you can resolve the conflicts |
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Summary
Add
relax.image.affine_gridoperator for Spatial Transformer Networks, along with PyTorch and ONNX frontend integration.TOPI compute (
topi.image.affine_grid) already exists. This PR completes the Relax-level registration and frontend support, following the existingresize2d/grid_samplepattern.Changes
Relax op registration:
resize.h,resize.cc)image.py)topi.image.affine_gridwithPrimExpr→intconversionPyTorch frontend:
aten.affine_grid_generator.default[N,2,H,W]to PyTorch[N,H,W,2]viapermute_dimsONNX frontend:
AffineGridconverter with_impl_v20(opset 20, when the op was first introduced)[N,C,H,W][N,2,H,W]to ONNX[N,H,W,2]Limitations
align_corners=Trueis supported (matches current TOPI implementation)Validation
All 12 tests passed.