| @@ -280,6 +280,41 @@ constexpr __aicore__ static inline T AfInfinity() { | |||
| 280 | return T(); | 280 | return T(); |
| 281 | } | 281 | } |
| 282 | 282 | ||
| 283 | +inline __aicore__ bool IsIntegerWithinUint32Magnitude(float value) { | ||
| 284 | + constexpr uint32_t kExponentMask = 0x7F800000U; | ||
| 285 | + constexpr uint32_t kFractionMask = 0x007FFFFFU; | ||
| 286 | + constexpr uint32_t kExponentBias = 127U; | ||
| 287 | + constexpr uint32_t kFractionBits = 23U; | ||
| 288 | + constexpr uint32_t kUint32Exponent = 32U; | ||
| 289 | + | ||
| 290 | + const uint32_t bits = GetScalarValueByBitCode<float, uint32_t>(value); | ||
| 291 | + const uint32_t exponent = (bits & kExponentMask) >> kFractionBits; | ||
| 292 | + const uint32_t fraction = bits & kFractionMask; | ||
| 293 | + | ||
| 294 | + // NaN and infinity are not integers. | ||
| 295 | + if (exponent == 0xFFU) { | ||
| 296 | + return false; | ||
| 297 | + } | ||
| 298 | + | ||
| 299 | + // Ignore the sign bit and limit the absolute value to the uint32 range. | ||
| 300 | + if (exponent >= kExponentBias + kUint32Exponent) { | ||
| 301 | + return false; | ||
| 302 | + } | ||
| 303 | + | ||
| 304 | + // Zero and non-zero values with an absolute value less than one. | ||
| 305 | + if (exponent < kExponentBias) { | ||
| 306 | + return exponent == 0U && fraction == 0U; | ||
| 307 | + } | ||
| 308 | + | ||
| 309 | + const uint32_t integer_bits = exponent - kExponentBias; | ||
| 310 | + if (integer_bits >= kFractionBits) { | ||
| 311 | + return true; | ||
| 312 | + } | ||
| 313 | + | ||
| 314 | + const uint32_t fractional_mask = (1U << (kFractionBits - integer_bits)) - 1U; | ||
| 315 | + return (fraction & fractional_mask) == 0U; | ||
| 316 | +} | ||
| 317 | + | ||
| 283 | static constexpr float ROUND_TO_NEAREST_INT_BIAS = 0.5f; | 318 | static constexpr float ROUND_TO_NEAREST_INT_BIAS = 0.5f; |
| 284 | template <typename T> | 319 | template <typename T> |
| 285 | inline __aicore__ uint32_t ConvertToUint32(T value) { | 320 | inline __aicore__ uint32_t ConvertToUint32(T value) { |
| @@ -47,6 +47,27 @@ TEST(TestUtilsApi, Floor_ShouldReturnInt_WhenNumIsUnsignedInt) { | |||
| 47 | ASSERT_EQ(Floor(0U), 0); | 47 | ASSERT_EQ(Floor(0U), 0); |
| 48 | } | 48 | } |
| 49 | 49 | ||
| 50 | +TEST(TestUtilsApi, IsInteger_ShouldCheckExactFloatValue) { | ||
| 51 | + EXPECT_TRUE(IsIntegerWithinUint32Magnitude(0.0f)); | ||
| 52 | + EXPECT_TRUE(IsIntegerWithinUint32Magnitude(-0.0f)); | ||
| 53 | + EXPECT_TRUE(IsIntegerWithinUint32Magnitude(2.0f)); | ||
| 54 | + EXPECT_TRUE(IsIntegerWithinUint32Magnitude(-2.0f)); | ||
| 55 | + EXPECT_TRUE(IsIntegerWithinUint32Magnitude(16777216.0f)); | ||
| 56 | + EXPECT_TRUE(IsIntegerWithinUint32Magnitude(4294967040.0f)); | ||
| 57 | + EXPECT_TRUE(IsIntegerWithinUint32Magnitude(-4294967040.0f)); | ||
| 58 | + | ||
| 59 | + EXPECT_FALSE(IsIntegerWithinUint32Magnitude(0.5f)); | ||
| 60 | + EXPECT_FALSE(IsIntegerWithinUint32Magnitude(-2.25f)); | ||
| 61 | + EXPECT_FALSE(IsIntegerWithinUint32Magnitude(123456.75f)); | ||
| 62 | + EXPECT_FALSE(IsIntegerWithinUint32Magnitude(4294967296.0f)); | ||
| 63 | + EXPECT_FALSE(IsIntegerWithinUint32Magnitude(-4294967296.0f)); | ||
| 64 | +} | ||
| 65 | + | ||
| 66 | +TEST(TestUtilsApi, IsInteger_ShouldRejectNanAndInfinity) { | ||
| 67 | + EXPECT_FALSE(IsIntegerWithinUint32Magnitude(AfInfinity<float>())); | ||
| 68 | + EXPECT_FALSE(IsIntegerWithinUint32Magnitude(GetScalarValueByBitCode<uint32_t, float>(0x7FC00000U))); | ||
| 69 | +} | ||
| 70 | + | ||
| 50 | TEST(TestUtilsApi, TestFindNearestPower2) { | 71 | TEST(TestUtilsApi, TestFindNearestPower2) { |
| 51 | uint64_t n1 = KernelUtils::FindNearestPower2(0); | 72 | uint64_t n1 = KernelUtils::FindNearestPower2(0); |
| 52 | ASSERT_EQ(n1, 0); | 73 | ASSERT_EQ(n1, 0); |
| @@ -59,7 +59,24 @@ inline __aicore__ void Pow(const AscendC::LocalTensor<T> &dst, const AscendC::Lo | |||||||||||
| 59 | return; | 59 | return; | ||||||||
| 60 | } | 60 | } | ||||||||
| 61 | } else if constexpr (std::is_same_v<T, float> || std::is_same_v<T, bfloat16_t>) { | 61 | } else if constexpr (std::is_same_v<T, float> || std::is_same_v<T, bfloat16_t>) { | ||||||||
| 62 | - Power<T, false, pow_config>(dst, src1, src2, tmp_buf, calCount); | 62 | + if (IsIntegerWithinUint32Magnitude(src2)) { | ||||||||
| 63 | + const bool is_negative = src2 < 0.0f; | ||||||||||
| 64 | + const float abs_src2 = is_negative ? -src2 : src2; | ||||||||||
| 65 | + uint32_t src_u32 = static_cast<uint32_t>(abs_src2); | ||||||||||
| 66 | + Duplicate(dst, static_cast<float>(1.0), calCount); | ||||||||||
| 67 | + while (src_u32 > 0) { | ||||||||||
| 68 | + if (src_u32 & 0x01) { | ||||||||||
| 69 | + Mul(dst, dst, src1, calCount); | ||||||||||
| 70 | + } | ||||||||||
| 71 | + src_u32 >>= 1; | ||||||||||
| 72 | + Mul(src1, src1, src1, calCount); | ||||||||||
| 73 | + } | ||||||||||
🟠 High Priority 变更行:pow.h 62-73 行新增的整数指数快速幂分支。 影响/契约:第 63 行 失败模式:当 src2 为负数且幅值在 uint32 范围内(如 -2.0f、-3.0f、-4.0f 等;-1.0f/-0.5f 已被前面的分支拦截)时,IsIntegerWithinUint32Magnitude(-2.0f) 返回 true 进入快速路径,输出 x² 而非数学上正确的 1/x²,与基线 Power(exp2/log2 路径)语义不一致,静默产生错误数值。该问题对 T=float 与 T=bfloat16_t 均存在。 修复:仅对非负整数指数启用快速路径,其余(含所有负指数)回退到 Power<T, false, pow_config>。最小改动:把进入条件改为 IsIntegerWithinUint32Magnitude(src2) && !(src2 < 0.0f),并删除未使用的 is_negative/abs_src2。 建议:快速路径只应处理非负整数指数:将条件改为 改动建议
![]() ![]() 不准确? 🟡 Medium Priority 变更行:pow.h 62-73 行新增的整数指数快速路径(本次 PR 的核心变更)。 影响/契约:本次改动触及 Pow 的核心计算路径——新增按位快速幂分支(改变计算结果来源)、原地改写输入张量 src1、并对负指数引入了语义变更;但同目录的 test_pow.cpp(UT 中唯一调用该 Pow 重载的用例文件)未做任何修改,新增测试只覆盖了 utils.h 的 IsInteger 函数。 失败模式:上述两个真实缺陷(负整数指数输出 x^|n| 而非 1/x^|n|;src1 被原地平方污染、dst==src1 时结果恒为 1.0)没有任何用例覆盖——只要新增一个 Pow(x, -2.0f) 或 Pow 原地调用用例即可暴露,当前改动即便存在这些回归也能通过 UT。这是高风险路径(复杂分支、数值语义、输入张量副作用)缺少关键测试。 修复:在 test_pow.cpp 中为 float/bfloat16 补充快速路径用例,至少覆盖:(1) 正整数指数(如 4、5、大指数)与数学期望/基线 Power 对比;(2) 负整数指数(如 -2、-3)验证 1/x^|n|;(3) 调用后复用 src1 以及 dst==src1 别名场景,防止输入张量被污染。 建议:在 test_pow.cpp 中补充对新增快速路径的用例:正/负整数指数结果验证、调用后 src1 复用与 dst==src1 别名场景,覆盖负数指数与输入污染这两类回归。 ![]() ![]() 不准确? | |||||||||||
| 74 | + if (is_negative) { | ||||||||||
| 75 | + Reciprocal(dst, dst, calCount); | ||||||||||
| 76 | + } | ||||||||||
| 77 | + } else { | ||||||||||
| 78 | + Power<T, false, pow_config>(dst, src1, src2, tmp_buf, calCount); | ||||||||||
| 79 | + } | ||||||||||
| 63 | return; | 80 | return; | ||||||||
| 64 | } | 81 | } | ||||||||
| 65 | Power(dst, src1, src2, tmp_buf, calCount); | 82 | Power(dst, src1, src2, tmp_buf, calCount); | ||||||||
| @@ -122,7 +139,24 @@ inline __aicore__ void Pow(const AscendC::LocalTensor<T> &dst, const T &src1, co | |||||||||||
| 122 | need_power = false; | 139 | need_power = false; | ||||||||
| 123 | } | 140 | } | ||||||||
| 124 | } else if constexpr (std::is_same_v<T, float> || std::is_same_v<T, bfloat16_t>) { | 141 | } else if constexpr (std::is_same_v<T, float> || std::is_same_v<T, bfloat16_t>) { | ||||||||
| 125 | - Power<T, false, pow_config>(dst_buf, src1_buf, src2, tmp_buf, block_cnt); | 142 | + if (IsIntegerWithinUint32Magnitude(src2)) { | ||||||||
| 143 | + const bool is_negative = src2 < 0.0f; | ||||||||||
| 144 | + const float abs_src2 = is_negative ? -src2 : src2; | ||||||||||
| 145 | + uint32_t src_u32 = static_cast<uint32_t>(abs_src2); | ||||||||||
| 146 | + Duplicate(dst_buf, static_cast<float>(1.0), block_cnt); | ||||||||||
| 147 | + while (src_u32 > 0) { | ||||||||||
| 148 | + if (src_u32 & 0x01) { | ||||||||||
| 149 | + Mul(dst_buf, dst_buf, src1_buf, block_cnt); | ||||||||||
| 150 | + } | ||||||||||
| 151 | + src_u32 >>= 1; | ||||||||||
| 152 | + Mul(src1_buf, src1_buf, src1_buf, block_cnt); | ||||||||||
| 153 | + } | ||||||||||
| 154 | + if (is_negative) { | ||||||||||
| 155 | + Reciprocal(dst_buf, dst_buf, block_cnt); | ||||||||||
| 156 | + } | ||||||||||
| 157 | + } else { | ||||||||||
| 158 | + Power<T, false, pow_config>(dst_buf, src1_buf, src2, tmp_buf, block_cnt); | ||||||||||
| 159 | + } | ||||||||||
| 126 | need_power = false; | 160 | need_power = false; | ||||||||
| 127 | } | 161 | } | ||||||||
| 128 | if (need_power) { | 162 | if (need_power) { | ||||||||


🟠 High Priority
changed line → affected behavior/contract → failure mode → suggested fix
本 PR 在
autofuse/v35/ascendc/api_regbase/pow.h的标量指数 Pow 重载中新增了整数指数的快速路径(第 62-73 行)。第 63 行计算了is_negative = src2 < 0.0f,第 64-65 行取绝对值abs_src2并转为uint32_t src_u32,随后第 67-73 行循环只用src_u32做快速幂累乘,is_negative变量被算出后从未使用,循环结束后直接return(第 77 行),没有任何求倒数/回退逻辑。新增的
IsIntegerWithinUint32Magnitude明确忽略符号位(utils.h 第 299-302 行注释 "Ignore the sign bit"),因此负整数指数(如 -2.0f、-3.0f,对 bfloat16_t 还包括 -1.0f、-0.5f 因第 35/50 行特殊分支仅覆盖 float/half)会命中该快速路径。例如Pow(dst, src1, -2.0f, ...)应得 x^(-2)=1/x²,实际得到 x²;-1.0f 的 bfloat16 场景应得 1/x,实际得到 x。失败模式:任何调用方传入负整数标量指数时,kernel 静默产出错误数值,无任何告警,属本次变更引入的回归(改动前该分支直接走
Power通用实现,结果正确)。指数为 0.5/-0.5 等非整数不会命中此路径(IsIntegerWithinUint32Magnitude(±0.5)返回 false),故该错误仅影响负整数指数,触发条件明确。建议:在进入快速路径前增加非负判断,负指数回退到 Power 通用路径,例如将第 62 行改为
if (src2 >= 0.0f && IsIntegerWithinUint32Magnitude(src2)),并删除未使用的 is_negative/abs_src2 逻辑(或改为循环后对 dst 求倒数)。