google / google/highway

Enhancements to support C++23 std::bfloat16_t type

Open
#1,254 2 comments 1 reaction 0 assignees View on GitHub
Dominant language
C++
Stars
5.8k
Forks
471
Avg merge
1d 6h
Merged PRs (30d)
81

Description

The upcoming C++23 standard adds support for the std::float16_t and std::bfloat16_t types, and the upcoming GCC 13 release will have support for the std::float16_t and std::bfloat16_t types with the ```-std=c++23``` option.

The hwy::bfloat16_t type should also be updated to allow for implicit conversions to/from std::bfloat16_t in C++23 or later mode on platforms that support the std::bfloat16_t type.

Here is how support for conversions between std::bfloat16_t and hwy::bfloat16_t could be implemented:
```
#if HWY_HAS_INCLUDE()
#include
#endif

#if HWY_HAS_BUILTIN(__builtin_bit_cast) || HWY_COMPILER_MSVC >= 1926
#define HWY_HAS_BUILTIN_BIT_CAST 1
#define HWY_HAS_CONSTEXPR_BIT_CAST 1
#elif defined(__cpp_lib_bit_cast) && __cpp_lib_bit_cast >= 201806L && \
HWY_HAS_INCLUDE()
#define HWY_HAS_BUILTIN_BIT_CAST 0
#define HWY_HAS_CONSTEXPR_BIT_CAST 1
#include
#else
#define HWY_HAS_BUILTIN_BIT_CAST 0
#define HWY_HAS_CONSTEXPR_BIT_CAST 0
#endif

#if __cplusplus >= 202100L && HWY_HAS_INCLUDE()
#define HWY_HAS_CXX23_STDFLOAT 1
#include
#else
#define HWY_HAS_CXX23_STDFLOAT 0
#endif

#if HWY_HAS_CXX23_STDFLOAT && defined(__STDCPP_BFLOAT16_T__)
#define HWY_HAS_CXX23_BFLOAT16_T 1
#else
#define HWY_HAS_CXX23_BFLOAT16_T 0
#endif

namespace hwy {
// ... other definitions

template
HWY_API
#if HWY_HAS_CONSTEXPR_BIT_CAST
constexpr
#endif
To ValueBitCast(const From& val) {
#if HWY_HAS_BUILTIN_BIT_CAST
return __builtin_bit_cast(To, val);
#elif defined(__cpp_lib_bit_cast) && __cpp_lib_bit_cast >= 201806L
return std::bit_cast(val);
#else
To result;
CopySameSize(&val, &result);
return result;
#endif
}

struct SpecialFloatFromBitsTag {
constexpr SpecialFloatFromBitsTag() {};
};

namespace detail {
HWY_INLINE
#if __cpp_constexpr >= 201304L
constexpr
#endif
uint16_t F32BitsToBF16Bits(uint32_t f32_bits) noexcept {
uint32_t abs_f32_bits = f32_bits & 0x7FFFFFFFu;
if(abs_f32_bits < 0x7F800000u) {
f32_bits += 0x00007FFFu + ((f32_bits >> 16) & 1u);
} else if(abs_f32_bits > 0x7F800000u) {
f32_bits |= 0x00400000u;
}
return static_cast(f32_bits >> 16);
}
}

struct bfloat16_t {
uint16_t bits;

bfloat16_t() = default;
HWY_INLINE constexpr bfloat16_t(SpecialFloatFromBitsTag /* tag */,
uint16_t bf16_bits) noexcept : bits{bf16_bits} {};

explicit
HWY_INLINE
#if __cpp_constexpr >= 201304L && HWY_HAS_CONSTEXPR_BIT_CAST
constexpr
#endif
bfloat16_t(float val) noexcept :
bits{detail::F32BitsToBF16Bits(ValueBitCast(val))} {}

#if HWY_HAS_CXX23_BFLOAT16_T
HWY_INLINE constexpr bfloat16_t(std::bfloat16_t bf16_val) noexcept :
bits{ValueBitCast(bf16_val)} {}

HWY_INLINE constexpr operator std::bfloat16_t() const noexcept {
return ValueBitCast(bits);
}
#endif

HWY_INLINE
#if HWY_HAS_CONSTEXPR_BIT_CAST
constexpr
#endif
operator float() const noexcept {
return ValueBitCast(bits << 16);
}
};

// ... other definitions

} // namespace hwy
```

The updated implementation of hwy::bfloat16_t above will compile in C++11 or later mode, including with older C++11 compilers such as g++ 4.7.1 or clang 3.4.1.

The updated implementation of hwy::bfloat16_t also adds a ```hwy::bfloat16_t(SpecialFloatFromBitsTag, uint16_t)``` constructor to distinguish between constructing a hwy::bfloat16_t from its bit representation and a conversion to hwy::bfloat16_t from floating-point types.

The behavior of code such as the function below will change with the updated hwy::bfloat16 implementation above:
```
hwy::bfloat16_t SomeFuncThatReturnsABF16() {
return hwy::bfloat16_t{0x4030};
}
```

SomeFuncThatReturnsABF16 will return a hwy::bfloat16_t with the value of 2.75 with the current implementation (which initializes a hwy::bfloat16_t from the BF16 bitwise representation of 2.75), whereas SomeFuncThatReturnsABF16 will return a hwy::bfloat16_t with the value of 16432.0 with the updated implementation of hwy::bfloat16_t (which converts the integer value of 16432.0 to a floating point).

The updated hwy::bfloat16_t implementation above will allow code such as the following in C++23 mode with GCC 13 or later (with updates to the Set function):
```
namespace example {
namespace HWY_NAMESPACE {

using namespace hwy;
using namespace hwy::HWY_NAMESPACE;

template
static HWY_INLINE auto InvertNonSignBitsIfNegative(V v) {
const DFromV d;
const RebindToSigned di;
const RebindToUnsigned du;

const auto vi = BitCast(di, v);
const auto invert_mask = BitCast(di, ShiftRight<1>(
BitCast(du, BroadcastSignBit(vi))));
return Xor(vi, invert_mask);
}

template
static HWY_INLINE V FloatMinUsingIntCompare(V a, V b) {
const DFromV d;

return BitCast(d, InvertNonSignBitsIfNegative(Min(
InvertNonSignBitsIfNegative(a),
InvertNonSignBitsIfNegative(b))));
}

template
static HWY_INLINE V FloatMaxUsingIntCompare(V a, V b) {
const DFromV d;

return BitCast(d, InvertNonSignBitsIfNegative(Max(
InvertNonSignBitsIfNegative(a),
InvertNonSignBitsIfNegative(b))));
}

void SomeFuncThatProcessesBFloat16(hwy::bfloat16_t* result_ptr,
const hwy::bfloat16_t* src_ptr) {
ScalableTag d;
const RebindToUnsigned du;
const auto v = Load(d, src_ptr);
const auto clamped = FloatMaxUsingIntCompare(
FloatMinUsingIntCompare(v, Set(du, 2.75bf16)),
Set(du, -5.375bf16));
}

}
}
```

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.