-
Notifications
You must be signed in to change notification settings - Fork 795
Description
Describe the bug
The initial value for sycl::minimum<float> is set to inf which gets turned into 0 with -ffast-math. More generally, both min and max are affected for any type which has_infinity.
The issue is here:
llvm/sycl/include/sycl/known_identity.hpp
Lines 262 to 271 in 7271d61
| /// Returns maximal possible value as identity for MIN operations. | |
| template <typename BinaryOperation, typename AccumulatorT> | |
| struct known_identity_impl<BinaryOperation, AccumulatorT, | |
| std::enable_if_t<IsMinimumIdentityOp< | |
| AccumulatorT, BinaryOperation>::value>> { | |
| static constexpr AccumulatorT value = static_cast<AccumulatorT>( | |
| std::numeric_limits<AccumulatorT>::has_infinity | |
| ? std::numeric_limits<AccumulatorT>::infinity() | |
| : (std::numeric_limits<AccumulatorT>::max)()); | |
| }; |
std::numeric_limits<T>::infinity() should not be used here when -fno-honor-infinities is enabled (e.g. by -ffast-math). This causes the value to be evaluated as zero when used (at least in some cases) and subsequently leads to incorrect results of a program. Note that std::numeric_limits<float>::has_infinity evaluates to true regardless of the honor-infinities option.
This started failing in the nightly-2024-02-16 tag. The code in question hasn't changed in months, but it seems that the behaviour of -ffast-math has changed, possibly due to 73159a9
So it seems the code has been problematic earlier, but it was hidden by the -fno-honor-infinities not being set in this context.
I think there could be three solutions:
- Just use
std::numeric_limits<T>::max()in all cases. What's the benefit of using infinity here? - Add a second condition checking whether
-fno-honor-infinitiesis used. - Make sure
std::numeric_limits<T>::has_infinityevaluates to false when-fno-honor-infinitiesis enabled.
To reproduce
This is a minimal example of sycl::joint_reduce computing the minimum for a vector of 4 positive floats:
#include <sycl/sycl.hpp>
#include <cstdio>
int main() {
sycl::queue q{};
std::vector<float> data{4.0f, 1.0f, 3.0f, 2.0f};
float result{data[0]};
{
sycl::buffer<float> inBuf{data};
sycl::buffer<float> outBuf{&result, 1};
q.submit([&inBuf, &outBuf](sycl::handler& cgh){
sycl::accessor inAcc{inBuf, cgh, sycl::read_only};
sycl::accessor outAcc{outBuf, cgh, sycl::write_only};
cgh.parallel_for(sycl::nd_range<1>{1,1}, [inAcc, outAcc](sycl::nd_item<1> item){
const float* start{&inAcc[0]};
const float* end{start + inAcc.size()};
outAcc[0] = sycl::joint_reduce(item.get_group(), start, end, sycl::minimum<>{});
});
});
}
printf("Result: %f\n", result);
return 0;
}When compiled with -O3 -ffast-math this prints 0.0 instead of the expected 1.0:
$ clang++ -fsycl -o test test.cpp -O3 -ffast-math
./test
Result: 0.000000It can be worked around with -fhonor-infinities:
$ clang++ -fsycl -o test test.cpp -O3 -ffast-math -fhonor-infinities
./test
Result: 1.000000Note this example doesn't directly use sycl::known_identity. It presents that the issue breaks a high-level feature. I didn't manage to create a small enough reproducer directly using the value, as it tends to get inlined / evaluated at compile time, and therefore not showing the issue. I have seen the value directly evaluating to zero in a larger project though.
Environment
- Linux, Ubuntu 22.04
- any target (tested Level Zero and CUDA)
- GNU libstdc++6 v12.3.0
- DPC++ tag nightly-2024-02-16 (first failing) through 2024-05-15 (latest tested)
Additional context
No response