From bc61454a0c9c3567e30a2302b52f78ad30720135 Mon Sep 17 00:00:00 2001 From: Sergei Shliapnikov Date: Tue, 30 Dec 2025 01:09:54 -0600 Subject: [PATCH] [HIP] Fix double to E8M0 conversion underflow (avoid unsigned exp wraparound) --- .../include/hip/amd_detail/amd_hip_fp8.h | 17 ++++++++++--- .../catch/unit/deviceLib/fp8_e8m0.cc | 25 ++++++++++++++++--- 2 files changed, 35 insertions(+), 7 deletions(-) diff --git a/projects/clr/hipamd/include/hip/amd_detail/amd_hip_fp8.h b/projects/clr/hipamd/include/hip/amd_detail/amd_hip_fp8.h index 519a8aad6149..fb4a97ca994b 100644 --- a/projects/clr/hipamd/include/hip/amd_detail/amd_hip_fp8.h +++ b/projects/clr/hipamd/include/hip/amd_detail/amd_hip_fp8.h @@ -987,11 +987,20 @@ __FP8_HOST_DEVICE_STATIC__ __hip_fp8_storage_t __hip_cvt_double_to_e8m0( __hip_fp8_storage_t e8m0; if (double_exp == 0x0U) { e8m0 = 0x0U; - } else if ((double_exp - 0x0380U) > 0x00FF) { // if double is NaN/Inf/or too large - e8m0 = hip_detail::e8m0_NaN; } else { - e8m0 = - double_exp - 0x0380U; // shift due to bias difference between double and single precision + // Use signed arithmetic to avoid unsigned underflow for small normal exponents + // 0x0380 is a bias difference between double and single precision (1023 - 127) + const int float_exp = static_cast(double_exp) - 0x0380; + if (float_exp <= 0) { + // Underflow to subnormal/zero in float domain + e8m0 = 0x0U; + } else if (float_exp > 0x00FF) { + // NaN/Inf or overflow beyond 8-bit exponent range + e8m0 = hip_detail::e8m0_NaN; + } else { + // Valid finite range [1..254], 0xFF yields NaN and is later handled by saturation + e8m0 = static_cast<__hip_fp8_storage_t>(float_exp); + } } // If there is a mantissa and the exp wont overflow round up diff --git a/projects/hip-tests/catch/unit/deviceLib/fp8_e8m0.cc b/projects/hip-tests/catch/unit/deviceLib/fp8_e8m0.cc index e094ed2bb290..373a3b5e44f0 100644 --- a/projects/hip-tests/catch/unit/deviceLib/fp8_e8m0.cc +++ b/projects/hip-tests/catch/unit/deviceLib/fp8_e8m0.cc @@ -23,6 +23,7 @@ THE SOFTWARE. #include #include #include +#include void host_cvt_bfloat16raw_to_e8m0(const std::vector<__hip_bfloat16>& in, std::vector& out, __hip_saturation_t sat, @@ -249,6 +250,22 @@ TEST_CASE("Unit__hip_cvt_double_to_e8m0") { -8.0, 1e38, -1e38, + // tiny underflow example -> underflows to 0x00 + // rounding-to-+Inf promotes to 0x01 + 1e-50, + -1e-50, + // smallest double-subnormal (denorm_min) -> underflows to 0x00; + // rounding-to-+Inf does not promote (magnitude below half-step) + std::numeric_limits::denorm_min(), + -std::numeric_limits::denorm_min(), + // smallest double-normal -> underflows to 0x00; + // rounding-to-+Inf does not promote (fraction == 0) + std::numeric_limits::min(), + -std::numeric_limits::min(), + // smallest float-normal (cast to double) -> E8M0 code 0x01; + // rounding-to-+Inf does not promote (fraction == 0) + static_cast(std::numeric_limits::min()), + -static_cast(std::numeric_limits::min()), std::nan("1"), -std::nan("1"), std::numeric_limits::infinity(), @@ -259,9 +276,11 @@ TEST_CASE("Unit__hip_cvt_double_to_e8m0") { std::vector out(exp.size()); if (rounding == hipRoundPosInf) { - exp = {0x00U, 0x7EU, 0x7FU, 0x81U, 0x82U, 0x82U, 0x7EU, 0x7FU, 0x81U, 0x82U, 0x82U, 0xFE, 0xFE}; + exp = {0x00U, 0x7EU, 0x7FU, 0x81U, 0x82U, 0x82U, 0x7EU, 0x7FU, 0x81U, 0x82U, 0x82U, 0xFE, 0xFE, + 0x01U, 0x01U, 0x00U, 0x00U, 0x00U, 0x00U, 0x01U, 0x01U}; } else { - exp = {0x00U, 0x7EU, 0x7EU, 0x81U, 0x81U, 0x82U, 0x7EU, 0x7EU, 0x81U, 0x81U, 0x82U, 0xFD, 0xFD}; + exp = {0x00U, 0x7EU, 0x7EU, 0x81U, 0x81U, 0x82U, 0x7EU, 0x7EU, 0x81U, 0x81U, 0x82U, 0xFD, 0xFD, + 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x01U, 0x01U}; } if (saturation == __HIP_NOSAT) { std::vector exp_append = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; @@ -337,7 +356,7 @@ TEST_CASE("Unit__hip_cvt_e8m0_to_bf16raw") { if (std::isnan(exp[i])) { REQUIRE(std::isnan(out[i])); } else { - REQUIRE_THAT(out[i], Catch::WithinAbs(exp[i], 1e-6f) || Catch::WithinRel(exp[i], 1e-3f)); + REQUIRE_THAT(out[i], Catch::Matchers::WithinAbs(exp[i], 1e-6f) || Catch::Matchers::WithinRel(exp[i], 1e-3f)); } } }