已合并
selu算子int8类型精度问题修复 #7019
yulianjie创建于 7月3日
selu算子int8类型精度问题修复 #7019
已合并
yulianjie创建于 7月3日
4 个文件变更+122-28
Mactivation/selu/op_host/arch35/selu_tiling_arch35.cpp+5-2
@@ -18,6 +18,7 @@
18 * 1. Multi-core: divide total elements evenly across AI Cores18 * 1. Multi-core: divide total elements evenly across AI Cores
19 * 2. UB: divide per-core elements into UB-sized chunks19 * 2. UB: divide per-core elements into UB-sized chunks
20 * 3. Buffer layout: inputQueue(1 buf) + outputQueue(1 buf) + tmpBuf1 + tmpBuf220 * 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 = 629 * BFLOAT16: (2*2 + 2*4) / 2 = 6
29 * INT32: (2*4 + 2*4) / 4 = 430 * INT32: (2*4 + 2*4) / 4 = 4
30- * INT8: (2*1 + 2*4) / 1 = 1031+ * INT8: (2*1 + 3*4) / 1 = 14 ← 3 个 tmp buffer
31 */32 */
32 33 
33#include "register/op_def_registry.h"34#include "register/op_def_registry.h"
@@ -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);
Mactivation/selu/op_kernel/arch35/selu.h+84-23
@@ -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 bf1625 * - bfloat16_t: TilingKey 2: cast bf16 -> fp32 -> compute -> cast back to bf16
26 * - int32_t: TilingKey 3: cast int32 -> fp32 -> compute -> cast back to int3226 * - int32_t: TilingKey 3: cast int32 -> fp32 -> compute -> cast back to int32
27- * - int8_t: TilingKey 4: int8 -> half -> float -> compute -> half -> int827+ * - 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;
61using AscendC::Add;61using AscendC::Add;
62using AscendC::Cast;62using AscendC::Cast;
63 63 
64-// SELU fixed constants(全部以 fp32 表示,FP16/BF16/INT 路径均通过 cast 到 fp32 计算后再回原 dtype)64+// SELU fixed constants
65constexpr float ALPHA_F32 = 1.6732632423543772848170429916717f;65constexpr float ALPHA_F32 = 1.6732632423543772848170429916717f;
66constexpr float SCALE_F32 = 1.0507009873554804934193349852946f;66constexpr 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 作为中间计算类型
69template <typename T>74template <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 
98private:107private:
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 -> 源 dtype221+// 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// =============================================================================
216template <typename T>228template <typename T>
217template <typename SrcT>229template <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 T328// Compute - dispatch by T
271-// 用户约定:所有非 fp32 dtype(含 FP16/BF16/INT*)统一走 ComputeCastFp32。329+// float: direct fp32; int8: half path; others: cast-to-fp32
272// =============================================================================330// =============================================================================
273template <typename T>331template <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 
Mactivation/selu/tests/st/aclnnSelu/atk_aclnnSelu.json+1-1
Mactivation/selu/tests/st/aclnnSelu/executor_aclnnSelu.py+32-2
@@ -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_tensor60 return output_tensor
31 61