Skip to content

[Fix][Relax][ONNX] Correct fmod mapping in Mod constant folding - #20170

Open
shoemoney wants to merge 1 commit into
apache:mainfrom
shoemoney:fix-onnx-mod-constant-fold
Open

[Fix][Relax][ONNX] Correct fmod mapping in Mod constant folding#20170
shoemoney wants to merge 1 commit into
apache:mainfrom
shoemoney:fix-onnx-mod-constant-fold

Conversation

@shoemoney

Copy link
Copy Markdown

The Mod converter (python/tvm/relax/frontend/onnx/onnx_frontend.py:730-736 on main) assigns numpy_op with the two conventions swapped: fmod=0 (integer mod, sign follows divisor per the ONNX spec) folds with np.fmod, and fmod=1 (C fmod, sign follows dividend) folds with np.mod. The runtime path is correct (floor_mod for fmod=0, mod for fmod=1), so the bug fires exactly when both operands are constants or initializers and BinaryBase.base_impl folds the result at import time, e.g. shape-arithmetic subgraphs. Mod(-5, 3) with fmod=0 bakes R.const(-2) into the graph where onnxruntime returns 1; Mod(-5.5, 3.0) with fmod=1 folds to 0.5 where onnxruntime returns -2.5.

The suite could not catch this: the numpy reference in verify_binary_scalar (tests/python/relax/test_frontend_onnx.py:365 on main) encoded the identical swap, and the only folded value exercised was 4 mod 8, where both conventions agree.

This is a reintroduction of a fixed bug: #6160 corrected this exact fmod=0/fmod=1 mapping in the old Relay ONNX frontend (issue #6106); the Relax frontend brought it back in the numpy constant-fold path.

Changes:

  • Swap the assignments so fmod=0 pairs np.mod with relax.op.floor_mod and fmod=1 pairs np.fmod with relax.op.mod.
  • Correct the inverted test reference in verify_binary_scalar.
  • Add test_mod_constant_fold_negative_operands: Mod over two constant tensors with negative operands (int32 fmod=0, int32 fmod=1, float32 fmod=1), folded at import and checked against onnxruntime. All three cases fail before the fix and pass after; the full ONNX frontend test file shows no other delta.

Not changed: the dead class-level defaults on Mod (numpy_op = _np.mod / relax_op = relax.op.mod), which _impl_v10 always overwrites since ONNX Mod only exists from opset 10.

The Mod converter assigns numpy_op with the two conventions swapped:
fmod=0 (integer mod, sign follows divisor) folds with np.fmod, and
fmod=1 (C fmod, sign follows dividend) folds with np.mod. The runtime
path is correct (floor_mod for fmod=0, mod for fmod=1), so the bug
fires exactly when both operands are constants or initializers and
BinaryBase.base_impl folds the result at import time: Mod(-5, 3) with
fmod=0 bakes R.const(-2) into the graph where onnxruntime returns 1.

The test suite could not catch this because the numpy reference in
verify_binary_scalar encoded the identical swap, and the only folded
value exercised was 4 mod 8, where both conventions agree.

Swap the assignments so fmod=0 pairs np.mod with relax.op.floor_mod
and fmod=1 pairs np.fmod with relax.op.mod, correct the inverted test
reference, and add a folded-constant regression test with negative
operands checked against onnxruntime.

Merged PR apache#6160 fixed this exact fmod=0/fmod=1 mapping in
the old Relay ONNX frontend (issue apache#6106); the Relax frontend
reintroduced it in the numpy constant-fold path.
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.

1 participant