Skip to content

[Relax][ONNX] Add roi_pool op and MaxRoiPool frontend support#18952

Merged
tlopex merged 2 commits intoapache:mainfrom
LudovicoYIN:maxroipool-onnx-relax
Mar 29, 2026
Merged

[Relax][ONNX] Add roi_pool op and MaxRoiPool frontend support#18952
tlopex merged 2 commits intoapache:mainfrom
LudovicoYIN:maxroipool-onnx-relax

Conversation

@LudovicoYIN
Copy link
Copy Markdown
Contributor

Summary

Add Relax roi_pool support and wire it through the ONNX frontend for MaxRoiPool.

Changes

  • add relax.vision.roi_pool, including attrs, Python wrapper, struct info inference, and legalization
  • add TOPI roi_pool compute for NCHW layout
  • support ONNX MaxRoiPool in the Relax ONNX frontend
  • handle empty / out-of-bound pooled bins according to ONNX/reference semantics, returning 0 instead of propagating invalid reductions
  • add regression tests for Relax op inference, legalization, and ONNX frontend import
  • add out-of-bound ROI coverage to make sure fully invalid pooled bins still match ONNX Runtime

Validation

  • pytest tests/python/relax/test_op_vision.py -k roi_pool
  • pytest tests/python/relax/test_frontend_onnx.py -k 'max_roi_pool'

This PR completes the MaxRoiPool portion of the Relax ONNX frontend operator work tracked in #18945.

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request implements the roi_pool operator in Relax, providing the necessary attributes, ONNX frontend support, and TOPI implementation for NCHW layout. It also includes structural info inference, legalization rules, and comprehensive tests. Additionally, the PR updates the handling of __slots__ in TVMDerivedObject within the runtime and meta-schedule utilities. Feedback was provided to optimize the TOPI implementation of roi_pool by avoiding redundant bin boundary calculations through the use of the -inf sentinel value to identify empty bins.

Comment on lines +82 to +87
return te.compute(
(num_roi, channel, pooled_size_h, pooled_size_w),
lambda i, c, ph, pw: tvm.tirx.if_then_else(
_is_empty(i, ph, pw), zero, pooled[i, c, ph, pw]
),
)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The current implementation computes bin boundaries twice: once in _sample (within the pooled compute) and again in _is_empty (in the final compute). This is inefficient.

This can be optimized by checking if the result of the max-pooling is -inf, which indicates an empty bin, as -inf is used as a sentinel value in _sample. This avoids recomputing the bounds.

With this change, the _is_empty function (lines 70-72) is no longer needed and can be removed.

Note: This change in logic assumes that the input data does not contain -inf. If a non-empty bin contains only values less than or equal to -inf, the original code would return -inf, while this suggested change would return 0. Given that roi_pool is typically used on feature maps from CNNs, this assumption is likely safe.

Suggested change
return te.compute(
(num_roi, channel, pooled_size_h, pooled_size_w),
lambda i, c, ph, pw: tvm.tirx.if_then_else(
_is_empty(i, ph, pw), zero, pooled[i, c, ph, pw]
),
)
return te.compute(
(num_roi, channel, pooled_size_h, pooled_size_w),
lambda i, c, ph, pw: tvm.tirx.if_then_else(
pooled[i, c, ph, pw] == neg_inf, zero, pooled[i, c, ph, pw]
),
)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I kept the explicit empty-bin check here because using -inf as the final sentinel could mis-handle valid non-empty bins whose max value is actually -inf. This is a bit more work, but it preserves the intended semantics without assuming anything about the input values.

@LudovicoYIN
Copy link
Copy Markdown
Contributor Author

hi @tlopex. i also made a small compatibility fix in
python/tvm/runtime/support.py and
python/tvm/s_tir/meta_schedule/utils.py.

Without this change, import tvm fails in environment with:

TypeError: __dict__ slot disallowed: we already got one

This is not specific to MaxRoiPool itself. I added it only to unblock local testing.
If preferred, I can move this part into a separate PR.

"Unique": Unique,
"NonZero": NonZero,
# "MaxRoiPool": MaxRoiPool,
"If": If,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove this line
The If operator is handled via directly_handled_ops, not through the converter map

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, you’re right. That line was added by mistake during rebase conflict resolution. I’ve removed it in the latest update.

@LudovicoYIN LudovicoYIN requested a review from tlopex March 29, 2026 11:27
Copy link
Copy Markdown
Member

@tlopex tlopex left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@tlopex tlopex merged commit 4df6b17 into apache:main Mar 29, 2026
10 checks passed
@LudovicoYIN
Copy link
Copy Markdown
Contributor Author

Thanks your review

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants