Skip to content

known_identity has wrong values with -ffast-math #13813

Closed
@rafbiels

Description

@rafbiels

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:

/// 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-infinities is used.
  • Make sure std::numeric_limits<T>::has_infinity evaluates to false when -fno-honor-infinities is 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.000000

It can be worked around with -fhonor-infinities:

$ clang++ -fsycl -o test test.cpp -O3 -ffast-math -fhonor-infinities
./test
Result: 1.000000

Note 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions