-
Notifications
You must be signed in to change notification settings - Fork 3.8k
[Arith] Use ConstIntBound to remove negative numerator when lowering #13724
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Arith] Use ConstIntBound to remove negative numerator when lowering #13724
Conversation
Negative numerators to modulo/remainder operations are not supported by the Vulkan API. While the SPIR-V instructions [`OpSRem`](https://registry.khronos.org/SPIR-V/specs/unified1/SPIRV.html#OpSRem) and [`OpSMod`](https://registry.khronos.org/SPIR-V/specs/unified1/SPIRV.html#OpSMod) have identical semantics to `tir::Mod` and `tir::FloorMod`, respectively, use of either instruction within Vulkan results in undefined behavior. From the [Vulkan spec](https://registry.khronos.org/vulkan/specs/1.3/html/chap37.html#spirvenv-op-prec): > For the OpSRem and OpSMod instructions, if either operand is > negative the result is undefined. > > Note: While the OpSRem and OpSMod instructions are supported by the > Vulkan environment, they require non-negative values and thus do not > enable additional functionality beyond what OpUMod provides. This issue was first noticed in apache#13530, where use of integer arithmetic resulted in negative numerators. This hadn't caused issues previously, because most use of div/mod use a denominator that is a power of two. In these cases, `tir.LowerIntrin` implements floordiv and floormod using only bitwise operations. When the denominator isn't a power of two, both `tir::FloorDiv` and `tir::FloorMod` are implemented in terms of `tir::Mod`, which triggers the undefined behavior for negative numerators. This commit alters the lowering of FloorDiv/FloorMod to TruncDiv/TruncMod, in cases where the denominator is positive, the numerator is sometimes negative, and the range of the numerator is known. In these cases, the FloorDiv/FloorMod is now implemented by offsetting the numerator such that it is always positive.
|
Thanks for contributing to TVM! Please refer to the contributing guidelines https://tvm.apache.org/docs/contribute/ for useful information and tips. Please request code reviews from Reviewers by @-ing them in a comment.
Generated by tvm-bot |
2fd620f to
2d0a7cf
Compare
src/tir/transforms/lower_intrin.cc
Outdated
| arith::ConstIntBound const_int_bound = analyzer_->const_int_bound(op->a); | ||
| if (const_int_bound->min_value != arith::ConstIntBound::kNegInf && | ||
| const_int_bound->min_value < 0 && | ||
| const_int_bound->min_value > -(1LL << (op->a->dtype.bits() - 1))) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The meaning of bound -(1LL << (op->a->dtype.bits() - 1))) is sign free or not? Could we use min_value interface of op.h here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you, and op.h's min_value is exactly what I had been looking for. I had checked whether ConstIntBound::Everything(dtype) was public, but wasn't aware of min_value. PR updated to use min_value instead of doing the calculation here.
| IntImm min(op->a->dtype, const_int_bound->min_value); | ||
| PrimExpr ceildiv = truncdiv((op->b - 1) - min, op->b); | ||
| PrimExpr offset_numerator = analyzer_->Simplify(op->a + op->b * ceildiv); | ||
| return truncdiv(offset_numerator, op->b) - ceildiv; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IIUC, the rationale is:
a // b =>
(a + -min - -min) // b =>
(a + (x*b-y) - (x*b-y)) // b => ( where x is ceildiv(-min, b), x >= 0, y >= 0 )
(a + x*b) // b - x =>
(a + x*b) / b - x ( since a + x*b >= a + -min >= 0 )
Right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yup, that is the rationale. I've added the derivation in a comment, which I probably should have done from the start.
| IntImm min(op->a->dtype, const_int_bound->min_value); | ||
| PrimExpr ceildiv = truncdiv(-min + (op->b - 1), op->b); | ||
| PrimExpr offset_numerator = analyzer_->Simplify(op->a + op->b * ceildiv); | ||
| return truncmod(offset_numerator, op->b); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
floormod(a, b) =>
floormod(a + -min - -min, b) =>
floormod(a + (x*b-y) - (x*b-y)), b) => ( where x is ceildiv(-min, b), x >= 0, y >= 0 )
floormod(a + x*b, b) =>
(a + x*b) % b ( since a + x*b >= a + -min >= 0 )
Right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yup, that derivation is correct, and I've added a comment here as well.
* [TOPI] Making test_strided_set require a GPU for testing Skipping test_strided_set with ci_cpu docker image due to an issue reported in #13724.
…pache#13724) * [Arith] Use ConstIntBound to remove negative numerator when lowering Negative numerators to modulo/remainder operations are not supported by the Vulkan API. While the SPIR-V instructions [`OpSRem`](https://registry.khronos.org/SPIR-V/specs/unified1/SPIRV.html#OpSRem) and [`OpSMod`](https://registry.khronos.org/SPIR-V/specs/unified1/SPIRV.html#OpSMod) have identical semantics to `tir::Mod` and `tir::FloorMod`, respectively, use of either instruction within Vulkan results in undefined behavior. From the [Vulkan spec](https://registry.khronos.org/vulkan/specs/1.3/html/chap37.html#spirvenv-op-prec): > For the OpSRem and OpSMod instructions, if either operand is > negative the result is undefined. > > Note: While the OpSRem and OpSMod instructions are supported by the > Vulkan environment, they require non-negative values and thus do not > enable additional functionality beyond what OpUMod provides. This issue was first noticed in apache#13530, where use of integer arithmetic resulted in negative numerators. This hadn't caused issues previously, because most use of div/mod use a denominator that is a power of two. In these cases, `tir.LowerIntrin` implements floordiv and floormod using only bitwise operations. When the denominator isn't a power of two, both `tir::FloorDiv` and `tir::FloorMod` are implemented in terms of `tir::Mod`, which triggers the undefined behavior for negative numerators. This commit alters the lowering of FloorDiv/FloorMod to TruncDiv/TruncMod, in cases where the denominator is positive, the numerator is sometimes negative, and the range of the numerator is known. In these cases, the FloorDiv/FloorMod is now implemented by offsetting the numerator such that it is always positive. * Add check to avoid -INT32_MIN * Updated to use `tvm::min_value(DataType)` * Added derivation for floordiv/floormod in terms of truncdiv/trundmod
* [TOPI] Making test_strided_set require a GPU for testing Skipping test_strided_set with ci_cpu docker image due to an issue reported in apache#13724.
Negative numerators to modulo/remainder operations are not supported by the Vulkan API. While the SPIR-V instructions
OpSRemandOpSModhave identical semantics totir::Modandtir::FloorMod, respectively, use of either instruction within Vulkan results in undefined behavior. From the Vulkan spec:This issue was first noticed in #13530, where use of integer arithmetic resulted in negative numerators. This hadn't caused issues previously, because most use of div/mod use a denominator that is a power of two. In these cases,
tir.LowerIntrinimplements floordiv and floormod using only bitwise operations. When the denominator isn't a power of two, bothtir::FloorDivandtir::FloorModare implemented in terms oftir::Mod, which triggers the undefined behavior for negative numerators.This commit alters the lowering of FloorDiv/FloorMod to TruncDiv/TruncMod, in cases where the denominator is positive, the numerator is sometimes negative, and the range of the numerator is known. In these cases, the FloorDiv/FloorMod is now implemented by offsetting the numerator such that it is always positive.