已合并
selu算子int8类型精度问题修复 #7019
yulianjie创建于 7月3日
selu算子int8类型精度问题修复 #7019
已合并
共 4 个文件变更+122-28
| @@ -18,6 +18,7 @@ | |||
| 18 | * 1. Multi-core: divide total elements evenly across AI Cores | 18 | * 1. Multi-core: divide total elements evenly across AI Cores |
| 19 | * 2. UB: divide per-core elements into UB-sized chunks | 19 | * 2. UB: divide per-core elements into UB-sized chunks |
| 20 | * 3. Buffer layout: inputQueue(1 buf) + outputQueue(1 buf) + tmpBuf1 + tmpBuf2 | 20 | * 3. Buffer layout: inputQueue(1 buf) + outputQueue(1 buf) + tmpBuf1 + tmpBuf2 |
| 21 | + * (INT8 额外一个 tmpBuf3:正分支 fp32 中间 + 溢出回绕,共 3 个 tmp buffer) | ||
| 21 | * | 22 | * |
| 22 | * 用户约定:所有非 fp32 dtype(FP16/BF16/INT32/INT8)统一走 cast-to-fp32 路径, | 23 | * 用户约定:所有非 fp32 dtype(FP16/BF16/INT32/INT8)统一走 cast-to-fp32 路径, |
| 23 | * 因此中间计算 buffer 大小始终 = ubFactor * sizeof(float)。 | 24 | * 因此中间计算 buffer 大小始终 = ubFactor * sizeof(float)。 |
| @@ -27,7 +28,7 @@ | |||
| 27 | * FLOAT16: (2*2 + 2*4) / 2 = 6 ← 由 (2*2+2*2)/2=4 改为 (2*2+2*4)/2=6(FP16 中间走 fp32) | 28 | * FLOAT16: (2*2 + 2*4) / 2 = 6 ← 由 (2*2+2*2)/2=4 改为 (2*2+2*4)/2=6(FP16 中间走 fp32) |
| 28 | * BFLOAT16: (2*2 + 2*4) / 2 = 6 | 29 | * BFLOAT16: (2*2 + 2*4) / 2 = 6 |
| 29 | * INT32: (2*4 + 2*4) / 4 = 4 | 30 | * INT32: (2*4 + 2*4) / 4 = 4 |
| 30 | - * INT8: (2*1 + 2*4) / 1 = 10 | 31 | + * INT8: (2*1 + 3*4) / 1 = 14 ← 3 个 tmp buffer |
| 31 | */ | 32 | */ |
| 32 | 33 | ||
| 33 | 34 | ||
| @@ -136,7 +137,9 @@ static ge::graphStatus ComputeTiling(gert::TilingContext* context, SeluTilingDat | |||
| 136 | int64_t blockFactor = CeilDiv(totalElements, coreNum); | 137 | int64_t blockFactor = CeilDiv(totalElements, coreNum); |
| 137 | blockFactor = ((blockFactor + ubBlockSize - 1) / ubBlockSize) * ubBlockSize; | 138 | blockFactor = ((blockFactor + ubBlockSize - 1) / ubBlockSize) * ubBlockSize; |
| 138 | int64_t usedCoreNum = CeilDiv(totalElements, blockFactor); | 139 | int64_t usedCoreNum = CeilDiv(totalElements, blockFactor); |
| 139 | - int64_t ubDivisor = (IO_BUF_NUM * typeSize + TMP_BUF_NUM * computeTypeSize) / typeSize; | 140 | + // int8 正分支(fp32 中间)与溢出回绕需额外 1 个 fp32 buffer(tmpBuf3),共 3 个 tmp buffer |
| 141 | + int64_t tmpBufNum = (dataType == ge::DT_INT8) ? (TMP_BUF_NUM + 1) : TMP_BUF_NUM; | ||
| 142 | + int64_t ubDivisor = (IO_BUF_NUM * typeSize + tmpBufNum * computeTypeSize) / typeSize; | ||
| 140 | int64_t ubFactor = FloorAlign( | 143 | int64_t ubFactor = FloorAlign( |
| 141 | FloorDiv(static_cast<int64_t>(ubSize) / typeSize, ubDivisor), | 144 | FloorDiv(static_cast<int64_t>(ubSize) / typeSize, ubDivisor), |
| 142 | ubBlockSize); | 145 | ubBlockSize); |
| @@ -24,7 +24,7 @@ | |||
| 24 | * - half: TilingKey 1: cast fp16 -> fp32 -> compute -> cast back to fp16 ← 用户约定:FP16 必须走 FP32 中间 | 24 | * - half: TilingKey 1: cast fp16 -> fp32 -> compute -> cast back to fp16 ← 用户约定:FP16 必须走 FP32 中间 |
| 25 | * - bfloat16_t: TilingKey 2: cast bf16 -> fp32 -> compute -> cast back to bf16 | 25 | * - bfloat16_t: TilingKey 2: cast bf16 -> fp32 -> compute -> cast back to bf16 |
| 26 | * - int32_t: TilingKey 3: cast int32 -> fp32 -> compute -> cast back to int32 | 26 | * - int32_t: TilingKey 3: cast int32 -> fp32 -> compute -> cast back to int32 |
| 27 | - * - int8_t: TilingKey 4: int8 -> half -> float -> compute -> half -> int8 | 27 | + * - int8_t: TilingKey 4: int8 -> half -> compute in half -> ceil negative -> int8 |
| 28 | * | 28 | * |
| 29 | * Buffer layout (single buffer): | 29 | * Buffer layout (single buffer): |
| 30 | * inputQueue(1 buf): ubFactor * sizeof(T) | 30 | * inputQueue(1 buf): ubFactor * sizeof(T) |
| @@ -61,9 +61,14 @@ using AscendC::Maxs; | |||
| 61 | using AscendC::Add; | 61 | using AscendC::Add; |
| 62 | using AscendC::Cast; | 62 | using AscendC::Cast; |
| 63 | 63 | ||
| 64 | -// SELU fixed constants(全部以 fp32 表示,FP16/BF16/INT 路径均通过 cast 到 fp32 计算后再回原 dtype) | 64 | +// SELU fixed constants |
| 65 | constexpr float ALPHA_F32 = 1.6732632423543772848170429916717f; | 65 | constexpr float ALPHA_F32 = 1.6732632423543772848170429916717f; |
| 66 | constexpr float SCALE_F32 = 1.0507009873554804934193349852946f; | 66 | constexpr float SCALE_F32 = 1.0507009873554804934193349852946f; |
| 67 | +// int8 路径使用预乘常量:SCALE_ALPHA_PRODUCT = SCALE * ALPHA | ||
| 68 | +constexpr float SCALE_ALPHA_PRODUCT = 1.75809934085f; | ||
| 69 | +// int8 正分支乘数:SCALE 的 fp16 表示(=1.05078125)提升为 fp32;fp32 乘后 CAST_TRUNC 回 fp16, | ||
| 70 | +// 实现 fp16 乘法向零截断(A5 硬件 fp16 Muls 是 RNE,若直乘会在 x=59/98/118 多 1) | ||
| 71 | +constexpr float SCALE_F16_AS_F32 = 1.05078125f; | ||
| 67 | 72 | ||
| 68 | // Compute type trait: 所有 dtype 统一用 fp32 作为中间计算类型 | 73 | // Compute type trait: 所有 dtype 统一用 fp32 作为中间计算类型 |
| 69 | template <typename T> | 74 | template <typename T> |
| @@ -88,12 +93,16 @@ private: | |||
| 88 | __aicore__ inline void ComputeFloat32(LocalTensor<float>& xFloat, | 93 | __aicore__ inline void ComputeFloat32(LocalTensor<float>& xFloat, |
| 89 | LocalTensor<float>& yFloat, | 94 | LocalTensor<float>& yFloat, |
| 90 | int64_t alignedNum); | 95 | int64_t alignedNum); |
| 91 | - // 非 fp32 dtype(half/bf16/int32/int8)统一走 cast-to-fp32 路径 | 96 | + // 非 fp32 dtype(half/bf16/int32)走 cast-to-fp32 路径 |
| 92 | template <typename SrcT> | 97 | template <typename SrcT> |
| 93 | __aicore__ inline void ComputeCastFp32(LocalTensor<SrcT>& xLocal, | 98 | __aicore__ inline void ComputeCastFp32(LocalTensor<SrcT>& xLocal, |
| 94 | LocalTensor<SrcT>& yLocal, | 99 | LocalTensor<SrcT>& yLocal, |
| 95 | int64_t currentNum, | 100 | int64_t currentNum, |
| 96 | int64_t alignedNum); | 101 | int64_t alignedNum); |
| 102 | + // int8: int8->half->compute in half->ceil negative->half->int8 | ||
| 103 | + __aicore__ inline void ComputeInt8(LocalTensor<int8_t>& xLocal, | ||
| 104 | + LocalTensor<int8_t>& yLocal, | ||
| 105 | + int64_t alignedNum); | ||
| 97 | 106 | ||
| 98 | private: | 107 | private: |
| 99 | TPipe pipe; | 108 | TPipe pipe; |
| @@ -101,6 +110,7 @@ private: | |||
| 101 | TQue<QuePosition::VECOUT, 1> outputQueue; | 110 | TQue<QuePosition::VECOUT, 1> outputQueue; |
| 102 | TBuf<QuePosition::VECCALC> tmpBuf1_; // exp/cast intermediate (fp32) | 111 | TBuf<QuePosition::VECCALC> tmpBuf1_; // exp/cast intermediate (fp32) |
| 103 | TBuf<QuePosition::VECCALC> tmpBuf2_; // max(0,x)/calc workspace (fp32) | 112 | TBuf<QuePosition::VECCALC> tmpBuf2_; // max(0,x)/calc workspace (fp32) |
| 113 | + TBuf<QuePosition::VECCALC> tmpBuf3_; // int8 正分支 RTZ 后的 fp16 结果 | ||
| 104 | 114 | ||
| 105 | GlobalTensor<T> xGM_; | 115 | GlobalTensor<T> xGM_; |
| 106 | GlobalTensor<T> yGM_; | 116 | GlobalTensor<T> yGM_; |
| @@ -142,6 +152,9 @@ __aicore__ inline void Selu<T>::Init(GM_ADDR x, GM_ADDR y, const SeluTilingData* | |||
| 142 | pipe.InitBuffer(outputQueue, 1, ubFactor_ * sizeof(T)); | 152 | pipe.InitBuffer(outputQueue, 1, ubFactor_ * sizeof(T)); |
| 143 | pipe.InitBuffer(tmpBuf1_, ubFactor_ * computeTSize); | 153 | pipe.InitBuffer(tmpBuf1_, ubFactor_ * computeTSize); |
| 144 | pipe.InitBuffer(tmpBuf2_, ubFactor_ * computeTSize); | 154 | pipe.InitBuffer(tmpBuf2_, ubFactor_ * computeTSize); |
| 155 | + if constexpr (std::is_same_v<T, int8_t>) { | ||
| 156 | + pipe.InitBuffer(tmpBuf3_, ubFactor_ * computeTSize); // int8 正分支/溢出回绕专用第 3 buffer | ||
| 157 | + } | ||
| 145 | } | 158 | } |
| 146 | 159 | ||
| 147 | // ============================================================================= | 160 | // ============================================================================= |
| @@ -204,14 +217,13 @@ __aicore__ inline void Selu<T>::ComputeFloat32(LocalTensor<float>& xFloat, | |||
| 204 | } | 217 | } |
| 205 | 218 | ||
| 206 | // ============================================================================= | 219 | // ============================================================================= |
| 207 | -// ComputeCastFp32 - 非 fp32 dtype 统一 cast 路径 | 220 | +// ComputeCastFp32 - 非 fp32 dtype 统一 cast 路径(half/bf16/int32) |
| 208 | -// FP16 / BF16 / INT32 / INT8 全部走:源 dtype -> fp32 -> compute -> 源 dtype | 221 | +// int8 已分离到 ComputeInt8 |
| 209 | // | 222 | // |
| 210 | // Hardware-supported Cast paths on arch35: | 223 | // Hardware-supported Cast paths on arch35: |
| 211 | // half <-> float (direct) | 224 | // half <-> float (direct) |
| 212 | // bfloat16 <-> float (direct) | 225 | // bfloat16 <-> float (direct) |
| 213 | // int32 <-> float (direct, CAST_TRUNC for round-to-zero) | 226 | // int32 <-> float (direct, CAST_TRUNC for round-to-zero) |
| 214 | -// int8 <-> float 需经 half 中转(int8 <-> half <-> float) | ||
| 215 | // ============================================================================= | 227 | // ============================================================================= |
| 216 | template <typename T> | 228 | template <typename T> |
| 217 | template <typename SrcT> | 229 | template <typename SrcT> |
| @@ -224,15 +236,8 @@ __aicore__ inline void Selu<T>::ComputeCastFp32(LocalTensor<SrcT>& xLocal, | |||
| 224 | LocalTensor<float> tmp2 = tmpBuf2_.template Get<float>(); | 236 | LocalTensor<float> tmp2 = tmpBuf2_.template Get<float>(); |
| 225 | 237 | ||
| 226 | // ---- Cast input to fp32 ---- | 238 | // ---- Cast input to fp32 ---- |
| 227 | - if constexpr (std::is_same_v<SrcT, int8_t>) { | 239 | + // half / bfloat16 / int32 -> float (一步) |
| 228 | - // int8 -> half -> float (两步) | 240 | + Cast(tmp1, xLocal, RoundMode::CAST_NONE, alignedNum); |
| 229 | - LocalTensor<half> tmpHalf = tmp2.template ReinterpretCast<half>(); | ||
| 230 | - Cast(tmpHalf, xLocal, RoundMode::CAST_NONE, alignedNum); | ||
| 231 | - Cast(tmp1, tmpHalf, RoundMode::CAST_NONE, alignedNum); | ||
| 232 | - } else { | ||
| 233 | - // half / bfloat16 / int32 -> float (一步) | ||
| 234 | - Cast(tmp1, xLocal, RoundMode::CAST_NONE, alignedNum); | ||
| 235 | - } | ||
| 236 | 241 | ||
| 237 | // ---- Compute SELU in fp32 ---- | 242 | // ---- Compute SELU in fp32 ---- |
| 238 | // Step 1: alpha * (exp(min(x, 0)) - 1) | 243 | // Step 1: alpha * (exp(min(x, 0)) - 1) |
| @@ -253,12 +258,7 @@ __aicore__ inline void Selu<T>::ComputeCastFp32(LocalTensor<SrcT>& xLocal, | |||
| 253 | // half: fp32 -> half (CAST_ROUND, RNE 四舍五入到最近偶数) | 258 | // half: fp32 -> half (CAST_ROUND, RNE 四舍五入到最近偶数) |
| 254 | // bf16: fp32 -> bf16 (CAST_ROUND) | 259 | // bf16: fp32 -> bf16 (CAST_ROUND) |
| 255 | // int32: fp32 -> int32 (CAST_TRUNC,向零截断,与 C++ int() 语义一致) | 260 | // int32: fp32 -> int32 (CAST_TRUNC,向零截断,与 C++ int() 语义一致) |
| 256 | - // int8: fp32 -> half -> int8 (两步,half->int8 用 CAST_TRUNC 截断到零) | 261 | + if constexpr (std::is_same_v<SrcT, int32_t>) { |
| 257 | - if constexpr (std::is_same_v<SrcT, int8_t>) { | ||
| 258 | - LocalTensor<half> tmpHalf = tmp2.template ReinterpretCast<half>(); | ||
| 259 | - Cast(tmpHalf, tmp1, RoundMode::CAST_TRUNC, alignedNum); | ||
| 260 | - Cast(yLocal, tmpHalf, RoundMode::CAST_TRUNC, alignedNum); | ||
| 261 | - } else if constexpr (std::is_same_v<SrcT, int32_t>) { | ||
| 262 | Cast(yLocal, tmp1, RoundMode::CAST_TRUNC, alignedNum); | 262 | Cast(yLocal, tmp1, RoundMode::CAST_TRUNC, alignedNum); |
| 263 | } else { | 263 | } else { |
| 264 | // half / bfloat16 都走 CAST_ROUND(最近偶数舍入) | 264 | // half / bfloat16 都走 CAST_ROUND(最近偶数舍入) |
| @@ -266,9 +266,67 @@ __aicore__ inline void Selu<T>::ComputeCastFp32(LocalTensor<SrcT>& xLocal, | |||
| 266 | } | 266 | } |
| 267 | } | 267 | } |
| 268 | 268 | ||
| 269 | +// ============================================================================= | ||
| 270 | +// ComputeInt8 - int8 实现 | ||
| 271 | +// 路径: int8 -> half -> compute in half -> ceil(negative) -> half -> int8 | ||
| 272 | +// 关键差异(vs ComputeCastFp32): | ||
| 273 | +// 1. 中间计算类型为 half (float16),不是 float32 | ||
| 274 | +// 2. 负分支结果做 ceil(向正无穷取整),仅 int8 | ||
| 275 | +// 3. half -> int8 直跳(不经 float32 中转) | ||
| 276 | +// 说明: | ||
| 277 | +// 1. 正分支乘法:需 fp16 向零截断;A5 硬件 fp16 Muls 是 RNE,故走 | ||
| 278 | +// fp32 精确乘 + CAST_TRUNC 回 fp16(否则 x=59/98/118 会多 1) | ||
| 279 | +// 2. 最终 half->int8:溢出回绕(numpy astype(int8) 语义)—— | ||
| 280 | +// 先 trunc 取整再 t-256*(t>=128) 手工回绕(硬件 cast 是饱和的,需手工回绕) | ||
| 281 | +// ============================================================================= | ||
| 282 | +template <typename T> | ||
| 283 | +__aicore__ inline void Selu<T>::ComputeInt8(LocalTensor<int8_t>& xLocal, | ||
| 284 | + LocalTensor<int8_t>& yLocal, | ||
| 285 | + int64_t alignedNum) | ||
| 286 | +{ | ||
| 287 | + // int8 -> half | ||
| 288 | + LocalTensor<half> xHalf = tmpBuf1_.template Get<half>(); | ||
| 289 | + Cast(xHalf, xLocal, RoundMode::CAST_NONE, alignedNum); | ||
| 290 | + | ||
| 291 | + // 正分支: max(x, 0) * SCALE —— fp32 乘 + CAST_TRUNC 回 fp16(fp16 向零截断) | ||
| 292 | + LocalTensor<float> posF32 = tmpBuf2_.template Get<float>(); | ||
| 293 | + Cast(posF32, xHalf, RoundMode::CAST_NONE, alignedNum); // fp16 x -> fp32 | ||
| 294 | + Maxs(posF32, posF32, 0.0f, alignedNum); // max(x, 0) | ||
| 295 | + Muls(posF32, posF32, SCALE_F16_AS_F32, alignedNum); // * fp16(SCALE),fp32 精确乘积 | ||
| 296 | + LocalTensor<half> posRes = tmpBuf3_.template Get<half>(); | ||
| 297 | + Cast(posRes, posF32, RoundMode::CAST_TRUNC, alignedNum); // fp32 -> fp16 向零截断(RTZ) | ||
| 298 | + | ||
| 299 | + // 负分支: (exp(min(x, 0)) - 1) * SCALE_ALPHA_PRODUCT | ||
| 300 | + Mins(xHalf, xHalf, static_cast<half>(0), alignedNum); | ||
| 301 | + Exp(xHalf, xHalf, alignedNum); | ||
| 302 | + Adds(xHalf, xHalf, static_cast<half>(-1), alignedNum); | ||
| 303 | + Muls(xHalf, xHalf, static_cast<half>(SCALE_ALPHA_PRODUCT), alignedNum); | ||
| 304 | + | ||
| 305 | + // 负分支 ceil(仅 int8):无同类型 ceil,通过 half->int8(CAST_CEIL)->half(CAST_NONE) 实现 | ||
| 306 | + Cast(yLocal, xHalf, RoundMode::CAST_CEIL, alignedNum); | ||
| 307 | + Cast(xHalf, yLocal, RoundMode::CAST_NONE, alignedNum); | ||
| 308 | + | ||
| 309 | + // 合并正负分支 | ||
| 310 | + Add(xHalf, xHalf, posRes, alignedNum); | ||
| 311 | + | ||
| 312 | + // half -> int8:溢出回绕(numpy astype(int8) 语义)。 | ||
| 313 | + // 硬件 fp16->int8 是饱和的,故先 trunc 取整再做 t - 256*(t>=128) 手工回绕: | ||
| 314 | + LocalTensor<int32_t> ti32 = tmpBuf2_.template Get<int32_t>(); | ||
| 315 | + Cast(ti32, xHalf, RoundMode::CAST_TRUNC, alignedNum); // val -> trunc(int32) | ||
| 316 | + LocalTensor<half> tHalf = tmpBuf3_.template Get<half>(); | ||
| 317 | + Cast(tHalf, ti32, RoundMode::CAST_NONE, alignedNum); // int32 -> fp16(整数值) | ||
| 318 | + LocalTensor<half> mask = tmpBuf2_.template Get<half>(); | ||
| 319 | + Adds(mask, tHalf, static_cast<half>(-127), alignedNum); | ||
| 320 | + Maxs(mask, mask, static_cast<half>(0), alignedNum); | ||
| 321 | + Mins(mask, mask, static_cast<half>(1), alignedNum); // mask = (t>=128)?1:0 | ||
| 322 | + Muls(mask, mask, static_cast<half>(-256), alignedNum); | ||
| 323 | + Add(tHalf, tHalf, mask, alignedNum); // t - 256*(t>=128) | ||
| 324 | + Cast(yLocal, tHalf, RoundMode::CAST_TRUNC, alignedNum); // -> int8(已在范围内,不饱和) | ||
| 325 | +} | ||
| 326 | + | ||
| 269 | // ============================================================================= | 327 | // ============================================================================= |
| 270 | // Compute - dispatch by T | 328 | // Compute - dispatch by T |
| 271 | -// 用户约定:所有非 fp32 的 dtype(含 FP16/BF16/INT*)统一走 ComputeCastFp32。 | 329 | +// float: direct fp32; int8: half path; others: cast-to-fp32 |
| 272 | // ============================================================================= | 330 | // ============================================================================= |
| 273 | template <typename T> | 331 | template <typename T> |
| 274 | __aicore__ inline void Selu<T>::Compute(int64_t currentNum) | 332 | __aicore__ inline void Selu<T>::Compute(int64_t currentNum) |
| @@ -284,8 +342,11 @@ __aicore__ inline void Selu<T>::Compute(int64_t currentNum) | |||
| 284 | 342 | ||
| 285 | if constexpr (std::is_same_v<T, float>) { | 343 | if constexpr (std::is_same_v<T, float>) { |
| 286 | ComputeFloat32(xLocal, yLocal, alignedNum); | 344 | ComputeFloat32(xLocal, yLocal, alignedNum); |
| 345 | + } else if constexpr (std::is_same_v<T, int8_t>) { | ||
| 346 | + // int8 走 half 计算路径 | ||
| 347 | + ComputeInt8(xLocal, yLocal, alignedNum); | ||
| 287 | } else { | 348 | } else { |
| 288 | - // half / bfloat16_t / int32_t / int8_t 全部走 cast-to-fp32 路径 | 349 | + // half / bfloat16_t / int32_t 走 cast-to-fp32 路径 |
| 289 | ComputeCastFp32(xLocal, yLocal, currentNum, alignedNum); | 350 | ComputeCastFp32(xLocal, yLocal, currentNum, alignedNum); |
| 290 | } | 351 | } |
| 291 | 352 | ||
| @@ -24,8 +24,38 @@ class FunctionApi(BaseApi): | |||
| 24 | def __call__(self, input_data: InputDataset, with_output: bool = False): | 24 | def __call__(self, input_data: InputDataset, with_output: bool = False): |
| 25 | input_x = input_data.kwargs['x'] | 25 | input_x = input_data.kwargs['x'] |
| 26 | 26 | ||
| 27 | - if input_x.dtype == 'int32' or input_x.dtype == 'int8': | 27 | + if input_x.dtype == 'int8': |
| 28 | + # int8 参考实现:对齐 kernel ComputeInt8(op_kernel/arch35/selu.h)的定点语义。 | ||
| 29 | + # 正分支 max(x,0)*SCALE:SCALE 取其 fp16 表示(1.05078125),fp32 精确乘后向零截断(RTZ)回 fp16 | ||
| 30 | + # (A5 硬件 fp16 Muls 为 RNE,直乘会在 x=59/98/118 多 1,故走 RTZ)。 | ||
| 31 | + # 负分支 (exp(min(x,0))-1)*SCALE_ALPHA:half 精度计算后 ceil(向 +inf 取整)。 | ||
| 32 | + # 合并后 half->int8:溢出回绕(numpy astype(int8) 语义)t-256*(t>=128),而非饱和 clip。 | ||
| 33 | + SCALE_ALPHA = 1.75809934085 # SCALE * ALPHA 预乘常量 | ||
| 34 | + SCALE_F16 = 1.05078125 # = np.float16(1.05070098736),SCALE 的 fp16 表示 | ||
| 35 | + x_np = input_x.cpu().numpy().astype(np.float16) | ||
| 36 | + | ||
| 37 | + # 负分支:half 精度计算 + ceil | ||
| 38 | + neg_res = np.minimum(x_np, np.float16(0)) | ||
| 39 | + sub_res = (np.exp(neg_res).astype(np.float16) - np.float16(1)).astype(np.float16) | ||
| 40 | + neg_muls = (sub_res * np.float16(SCALE_ALPHA)).astype(np.float16) | ||
| 41 | + neg_muls = np.ceil(neg_muls).astype(np.float16) | ||
| 42 | + | ||
| 43 | + # 正分支:fp32 精确乘 + 向零截断(RTZ)回 fp16,对齐 kernel 的 CAST_TRUNC | ||
| 44 | + pos_res = np.maximum(x_np, np.float16(0)) | ||
| 45 | + pos_f32 = pos_res.astype(np.float32) * np.float32(SCALE_F16) | ||
| 46 | + pos_muls = pos_f32.astype(np.float16) # RNE 舍入 | ||
| 47 | + overshoot = np.abs(pos_muls.astype(np.float32)) > np.abs(pos_f32) # RNE 向外舍入处 | ||
| 48 | + pos_muls = np.where(overshoot, np.nextafter(pos_muls, np.float16(0)), pos_muls).astype(np.float16) | ||
| 49 | + | ||
| 50 | + # 合并 + 溢出回绕(与 kernel 一致:先 trunc 取整,再 t-256*(t>=128)) | ||
| 51 | + merged = (neg_muls + pos_muls).astype(np.float16) | ||
| 52 | + ti32 = np.trunc(merged.astype(np.float32)).astype(np.int32) | ||
| 53 | + wrapped = ti32 - np.int32(256) * (ti32 >= np.int32(128)) | ||
| 54 | + output_tensor = torch.from_numpy(wrapped.astype(np.int8)) | ||
| 55 | + elif input_x.dtype == 'int32': | ||
| 28 | input_x = input_x.float() | 56 | input_x = input_x.float() |
| 29 | - output_tensor = torch.selu(input_x) | 57 | + output_tensor = torch.selu(input_x).to(torch.int32) |
| 58 | + else: | ||
| 59 | + output_tensor = torch.selu(input_x) | ||
| 30 | return output_tensor | 60 | return output_tensor |
| 31 | 61 | ||