This is an automated email from the ASF dual-hosted git repository.
tlopex pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tvm.git
The following commit(s) were added to refs/heads/main by this push:
new 2a2b293c02 [BugFix] Align default (C) tirx.round lowering to
ties-to-even (#20131)
2a2b293c02 is described below
commit 2a2b293c02269f4d9f3526c5b03a7548578e78e8
Author: LngelKyo <[email protected]>
AuthorDate: Tue Sep 1 15:12:26 2026 +0900
[BugFix] Align default (C) tirx.round lowering to ties-to-even (#20131)
#19368 aligned tir.round to ties-to-even "across all backends" and moved
LLVM, CUDA, NVPTX, ROCm, Hexagon, Metal, OpenCL, SPIR-V and WebGPU. It
did not touch src/target/intrin_rule.cc, so the default.FLowerIntrinsic
rule — which the c target uses — still lowers through FloatSuffix and
emits round/roundf, i.e. ties away from zero.
This disagrees with:
- constant folding, which uses std::nearbyint (src/tirx/op/op.cc);
- the CUDA rule, whose own comment says "Use nearbyint (ties-to-even)
for round to match constant-folding semantics";
- every other backend after #19368.
**Why CI did not catch it.** test_target_codegen_c_host.py::test_round
compiles with target="c" and asserts against np.round, which is
ties-to-even — so the intended semantics was already encoded. It passes
only because its input is np.random.rand, which does not produce exact
midpoints (0 occurrences in 2,048,000 sampled values). The ties-to-even
test added by #19368 (test_tir_intrin.py::test_round_ties_to_even) only
runs target="llvm", so no test covered the C path's tie behaviour.
The first commit adds the midpoint vector — reused verbatim from
test_round_ties_to_even — to the existing C-host test and it fails:
```
E Mismatch at indices:
E [0]: 1.0 (ACTUAL), 0.0 (DESIRED)
E [2]: 3.0 (ACTUAL), 2.0 (DESIRED)
E [4]: -1.0 (ACTUAL), -0.0 (DESIRED)
E [6]: -3.0 (ACTUAL), -2.0 (DESIRED)
```
The second commit renames round→nearbyint before the float suffix,
mirroring the existing CUDA rule, and it passes.
One note for reviewers: nearbyint honours the current floating-point
environment and is ties-to-even under the default FE_TONEAREST. That is
the same guarantee LLVM, CUDA, Metal and OpenCL already rely on after
#19368; this PR does not introduce a new assumption.
---------
Co-authored-by: LngelKyo <[email protected]>
---
src/target/intrin_rule.cc | 15 ++++++++++++++-
tests/python/codegen/test_target_codegen_c_host.py | 12 +++++++++++-
2 files changed, 25 insertions(+), 2 deletions(-)
diff --git a/src/target/intrin_rule.cc b/src/target/intrin_rule.cc
index 0ab7067042..ef06f8c342 100644
--- a/src/target/intrin_rule.cc
+++ b/src/target/intrin_rule.cc
@@ -33,6 +33,18 @@ namespace codegen {
namespace intrin {
using tirx::FLowerIntrinsic;
+// `tirx.round` is ties-to-even (see include/tvm/tirx/op.h), and constant
+// folding implements it with std::nearbyint. The C library's round()/roundf()
+// is ties-AWAY-from-zero, so lowering through FloatSuffix would disagree with
+// the folder and with every other backend. Rename to nearbyint before the
+// float suffix is applied, as the CUDA rule already does.
+struct FloatSuffixTiesToEven {
+ std::string operator()(const PrimType& ty, std::string name) const {
+ if (name == "round") name = "nearbyint";
+ return FloatSuffix()(ty, name);
+ }
+};
+
TVM_REGISTER_OP("tirx.exp")
.set_attr<FLowerIntrinsic>("default.FLowerIntrinsic",
DispatchPureExtern<FloatSuffix>);
@@ -115,7 +127,8 @@ TVM_REGISTER_OP("tirx.ceil")
.set_attr<FLowerIntrinsic>("default.FLowerIntrinsic",
DispatchPureExtern<FloatSuffix>);
TVM_REGISTER_OP("tirx.round")
- .set_attr<FLowerIntrinsic>("default.FLowerIntrinsic",
DispatchPureExtern<FloatSuffix>);
+ .set_attr<FLowerIntrinsic>("default.FLowerIntrinsic",
+ DispatchPureExtern<FloatSuffixTiesToEven>);
TVM_REGISTER_OP("tirx.nearbyint")
.set_attr<FLowerIntrinsic>("default.FLowerIntrinsic",
DispatchPureExtern<FloatSuffix>);
diff --git a/tests/python/codegen/test_target_codegen_c_host.py
b/tests/python/codegen/test_target_codegen_c_host.py
index 989dc2129f..fbad838450 100644
--- a/tests/python/codegen/test_target_codegen_c_host.py
+++ b/tests/python/codegen/test_target_codegen_c_host.py
@@ -193,7 +193,17 @@ def test_round():
fround = m["test_round"]
dev = tvm.cpu(0)
n = nn
- a = tvm.runtime.tensor(np.random.rand(n).astype("float32"), dev)
+ # Exact midpoints first: this is where ties-to-even (np.round, and the
+ # semantics every other backend and the constant folder use) differs
+ # from ties-away-from-zero. np.random.rand never produces them, so the
+ # random tail alone cannot exercise the tie rule.
+ midpoints = np.array(
+ [0.5, 1.5, 2.5, 3.5, -0.5, -1.5, -2.5, -3.5], dtype="float32"
+ )
+ a_np = np.concatenate(
+ [midpoints, np.random.rand(n - len(midpoints)).astype("float32")]
+ ).astype("float32")
+ a = tvm.runtime.tensor(a_np, dev)
b = tvm.runtime.tensor(np.zeros(n, dtype="float32"), dev)
fround(a, b)
tvm.testing.assert_allclose(b.numpy(),
(np.round(a.numpy()).view("float32")))