Merged
fix(lp_norm_reduce): 加固 Ascend950 边界与极大 p 性能 #11238
raoliang_sacAdd on Sep 27
fix(lp_norm_reduce): 加固 Ascend950 边界与极大 p 性能 #11238
Merged
Total: 9 files changed+252-15
| @@ -27,7 +27,7 @@ add_modules_sources( | |||
| 27 | target_include_directories(${GRAPH_PLUGIN_NAME}_obj PRIVATE ${ASCEND_GRAPH_CONF_DST}) | 27 | target_include_directories(${GRAPH_PLUGIN_NAME}_obj PRIVATE ${ASCEND_GRAPH_CONF_DST}) |
| 28 | 28 | ||
| 29 | add_kernel_sources( | 29 | add_kernel_sources( |
| 30 | - KERNEL_SRC lp_norm_reduce_apt.cpp | 30 | + KERNEL_SRC lp_norm_reduce.cpp |
| 31 | COMPUTE_UNITS ascend950 | 31 | COMPUTE_UNITS ascend950 |
| 32 | AUTO_SYNC false | 32 | AUTO_SYNC false |
| 33 | ) | 33 | ) |
| @@ -70,10 +70,11 @@ | |||
| 70 | - 本算子只输出Lp范数的归约结果,不做$\frac{1}{p}$次开方;FLOAT16/FLOAT完整范数可再串联匹配的LpNormUpdate算子。当前旧链LpNormUpdate不支持BFLOAT16。 | 70 | - 本算子只输出Lp范数的归约结果,不做$\frac{1}{p}$次开方;FLOAT16/FLOAT完整范数可再串联匹配的LpNormUpdate算子。当前旧链LpNormUpdate不支持BFLOAT16。 |
| 71 | - 以下输入边界描述<term>Ascend 950PR/Ascend 950DT</term>实现。 | 71 | - 以下输入边界描述<term>Ascend 950PR/Ascend 950DT</term>实现。 |
| 72 | - 输入rank范围为0到8。rank-0输入只允许空`axes`;已知rank下越界轴会报错,重复轴按首次出现去重。 | 72 | - 输入rank范围为0到8。rank-0输入只允许空`axes`;已知rank下越界轴会报错,重复轴按首次出现去重。 |
| 73 | -- 图推导阶段允许unknown rank和取值为-1的动态维,其他负维非法。执行期`x`的逻辑shape与storage shape都必须具体;非标量的rank及每一维必须完全相同,rank-0标量允许storage shape为`[]`或`[1]`。执行期`y`的逻辑shape与storage shape必须与`axes`、`keepdim`推导结果逐维一致且具体;标量输出的逻辑/storage shape兼容`[]`和`[1]`两种物化形式。为保证归约分段规模可表示,`x`所有非零维度的乘积不得超过INT64_MAX(含空Tensor)。 | 73 | +- 图推导阶段允许unknown rank和取值为-1的动态维,其他负维非法。执行期`x`的逻辑shape与storage shape都必须具体;非标量的rank及每一维必须完全相同,rank-0标量允许storage shape为`[]`或`[1]`。执行期`y`的逻辑shape与storage shape必须与`axes`、`keepdim`推导结果逐维一致且具体;标量输出的逻辑/storage shape兼容`[]`和`[1]`两种物化形式。为保证字节偏移及对齐计算可表示,`x`所有非零维度的乘积不得超过`floor((INT64_MAX-4096)/4)`(含空Tensor)。实际可运行shape还受设备与主机内存容量限制。 |
| 74 | - 有限`p`的归约域为空时输出0;若零长度维仅位于非归约轴,输出为空Tensor。正负无穷哨兵遇到空归约域时因`max`/`min`无定义而报错。 | 74 | - 有限`p`的归约域为空时输出0;若零长度维仅位于非归约轴,输出为空Tensor。正负无穷哨兵遇到空归约域时因`max`/`min`无定义而报错。 |
| 75 | - 除-2147483648哨兵外,有限负`p`会报错。2147483648及更大的非负值仍按有限整数阶处理。 | 75 | - 除-2147483648哨兵外,有限负`p`会报错。2147483648及更大的非负值仍按有限整数阶处理。 |
| 76 | - FLOAT16和BFLOAT16输入的绝对值、幂与归约累加中间过程以FLOAT完成,结束后转换回输入数据类型;输出数据类型始终与输入一致。 | 76 | - FLOAT16和BFLOAT16输入的绝对值、幂与归约累加中间过程以FLOAT完成,结束后转换回输入数据类型;输出数据类型始终与输入一致。 |
| 77 | +- 归约树各层的对齐输出行总大小不得超过16KiB缓存;超出时Host会拒绝该shape,避免设备侧缓存越界。 | ||
| 77 | - 当`axes`指定到`x`中长度为1的轴时,计算结果可能存在精度差异。 | 78 | - 当`axes`指定到`x`中长度为1的轴时,计算结果可能存在精度差异。 |
| 78 | - 仅提供GEIR/Kernel兼容路径,不新增aclnn接口。 | 79 | - 仅提供GEIR/Kernel兼容路径,不新增aclnn接口。 |
| 79 | 80 | ||
| @@ -81,4 +82,4 @@ | |||
| 81 | 82 | ||
| 82 | | 调用方式 | 样例代码 | 说明 | | 83 | | 调用方式 | 样例代码 | 说明 | |
| 83 | | -------- | -------- | ---- | | 84 | | -------- | -------- | ---- | |
| 84 | -| 图模式 | - | 通过[LpNormReduce算子IR入口](op_graph/lp_norm_reduce_proto.h)构图;该入口转发到仓库公共原型,未提供aclnn接口。 | | 85 | +| 图模式 | - | 通过[LpNormReduce算子IR入口](op_graph/lp_norm_reduce_proto.h)构图;该头文件保留带防重定义宏的REG_OP声明,未提供aclnn接口。 | |
| @@ -218,9 +218,11 @@ static ge::graphStatus GetShapeAndDtype(gert::TilingContext* context, LpNormRedu | |||
| 218 | // numel 溢出总闸(codex 审查缺陷 #2;对齐 changwei IsConcreteShape 除法式上界 | 218 | // numel 溢出总闸(codex 审查缺陷 #2;对齐 changwei IsConcreteShape 除法式上界 |
| 219 | // 保护):非零维连乘超 int64 表示域 → 干净拒绝。该乘积是全部子集乘积(aTotal/ | 219 | // 保护):非零维连乘超 int64 表示域 → 干净拒绝。该乘积是全部子集乘积(aTotal/ |
| 220 | // TotalAProd/stride 前缀积/outerAProd/outerR/FuseAxis 融合积)的上界,杜绝 | 220 | // TotalAProd/stride 前缀积/outerAProd/outerR/FuseAxis 融合积)的上界,杜绝 |
| 221 | - // signed overflow UB 流入 stride/循环数/workspace/GM 偏移计算;预留 4096 余量 | 221 | + // signed overflow UB 流入 stride/循环数/workspace/GM 偏移计算。必须同时约束 |
| 222 | - // 吸收下游 CeilDiv/CeilAlign 加法边界。 | 222 | + // 字节偏移:kernel 的 GM stride 会乘 sizeof(DT),最宽 DT 为 fp32。 |
| 223 | - constexpr int64_t kNumelLimit = std::numeric_limits<int64_t>::max() - 4096; | 223 | + // 仅按元素数留余量仍会放行 [2, (INT64_MAX-4096)/2],使 stride*4 溢出。 |
| 224 | + // 除以最大元素字节数也为下游 CeilDiv/CeilAlign/尾块端点留下足够余量。 | ||
| 225 | + constexpr int64_t kNumelLimit = (std::numeric_limits<int64_t>::max() - 4096) / FP32_BYTES; | ||
| 224 | int64_t nonZeroDimProduct = 1; | 226 | int64_t nonZeroDimProduct = 1; |
| 225 | for (size_t i = 0; i < rank; ++i) { | 227 | for (size_t i = 0; i < rank; ++i) { |
| 226 | const int64_t dim = ctx.xShape[i]; | 228 | const int64_t dim = ctx.xShape[i]; |
| @@ -231,7 +233,7 @@ static ge::graphStatus GetShapeAndDtype(gert::TilingContext* context, LpNormRedu | |||
| 231 | OP_LOGE_FOR_INVALID_SHAPEDIM_WITH_REASON( | 233 | OP_LOGE_FOR_INVALID_SHAPEDIM_WITH_REASON( |
| 232 | context->GetNodeName(), "x", std::to_string(dim), | 234 | context->GetNodeName(), "x", std::to_string(dim), |
| 233 | "dim[" + std::to_string(i) + | 235 | "dim[" + std::to_string(i) + |
| 234 | - "] makes the product of nonzero dims exceed the int64 range (tensor too large)"), | 236 | + "] makes the nonzero-dimension byte span exceed the int64 range (tensor too large)"), |
| 235 | return ge::GRAPH_FAILED); | 237 | return ge::GRAPH_FAILED); |
| 236 | nonZeroDimProduct *= dim; | 238 | nonZeroDimProduct *= dim; |
| 237 | } | 239 | } |
| @@ -1088,6 +1090,24 @@ static bool ComputeGroupSplit(LpNormReduceCtx& ctx) | |||
| 1088 | // Buffer 大小计算 + 填 TilingData(范式 §5.1 / [8.1]) | 1090 | // Buffer 大小计算 + 填 TilingData(范式 §5.1 / [8.1]) |
| 1089 | // --------------------------------------------------------------------------- | 1091 | // --------------------------------------------------------------------------- |
| 1090 | 1092 | ||
| 1093 | +// Each binary-tree level stores one block-aligned A row in the fixed cache. | ||
| 1094 | +// Group phase 1 uses the largest local R range, not the global R loop count. | ||
| 1095 | +// Check metadata before launching kernels for shapes too large to allocate here. | ||
| 1096 | +static ge::graphStatus CheckCacheCapacity(gert::TilingContext* context, const LpNormReduceCtx& ctx) | ||
| 1097 | +{ | ||
| 1098 | + const int64_t localRLoops = ctx.isGroup ? Ops::Base::CeilDiv(ctx.rLoopCntTotal, ctx.rGroupCnt) : ctx.rLoopCntTotal; | ||
| 1099 | + int64_t cacheLevels = 1; | ||
| 1100 | + for (int64_t remaining = localRLoops - 1; remaining > 1; remaining >>= 1) { | ||
| 1101 | + ++cacheLevels; | ||
| 1102 | + } | ||
| 1103 | + const int64_t levelStride = Ops::Base::CeilAlign(ctx.aUbFactor * ctx.innerAProdAlign, ctx.blockSize / FP32_BYTES); | ||
| 1104 | + OP_CHECK_IF(levelStride <= 0 || levelStride > CACHE_BUF_BYTES / FP32_BYTES / cacheLevels, | ||
| 1105 | + OP_LOGE_FOR_INVALID_SHAPEDIM_WITH_REASON(context->GetNodeName(), "x", std::to_string(localRLoops), | ||
| 1106 | + "reduction tree exceeds the fixed UB cache capacity"), | ||
| 1107 | + return ge::GRAPH_FAILED); | ||
| 1108 | + return ge::GRAPH_SUCCESS; | ||
| 1109 | +} | ||
| 1110 | + | ||
| 1091 | // 计算 UB buffer 尺寸:preBufSize = aUnit × rPaddedElems × maxDtypeSize | 1111 | // 计算 UB buffer 尺寸:preBufSize = aUnit × rPaddedElems × maxDtypeSize |
| 1092 | // (preIn / preRes / preResTail 三个 buffer 同尺寸,统一按 maxDtypeSize 预算,b16 也 | 1112 | // (preIn / preRes / preResTail 三个 buffer 同尺寸,统一按 maxDtypeSize 预算,b16 也 |
| 1093 | // 按 4B 计);postBufSize = CeilAlign(aUnit × maxDtypeSize, blockSize)(1D,block 对齐)。 | 1113 | // 按 4B 计);postBufSize = CeilAlign(aUnit × maxDtypeSize, blockSize)(1D,block 对齐)。 |
| @@ -1224,6 +1244,7 @@ static ge::graphStatus TilingFuncLpNormReduce(gert::TilingContext* context) | |||
| 1224 | OP_CHECK_IF(context->SetScheduleMode(1) != ge::GRAPH_SUCCESS, | 1244 | OP_CHECK_IF(context->SetScheduleMode(1) != ge::GRAPH_SUCCESS, |
| 1225 | OP_LOGE(context->GetNodeName(), "Failed to set ScheduleMode!"), return ge::GRAPH_FAILED); | 1245 | OP_LOGE(context->GetNodeName(), "Failed to set ScheduleMode!"), return ge::GRAPH_FAILED); |
| 1226 | } | 1246 | } |
| 1247 | + OP_CHECK_IF(CheckCacheCapacity(context, ctx) != ge::GRAPH_SUCCESS, , return ge::GRAPH_FAILED); | ||
| 1227 | ComputeUbSizes(ctx); | 1248 | ComputeUbSizes(ctx); |
| 1228 | OP_LOGD(context->GetNodeName(), "UbSizes: preBufSize=%ld postBufSize=%ld", ctx.preBufSize, ctx.postBufSize); | 1249 | OP_LOGD(context->GetNodeName(), "UbSizes: preBufSize=%ld postBufSize=%ld", ctx.preBufSize, ctx.postBufSize); |
| 1229 | 1250 | ||
| @@ -87,7 +87,7 @@ public: | |||
| 87 | .DynamicShapeSupportFlag(true) // 支持可变 shape(信息库 [-2] ND 动态口径) | 87 | .DynamicShapeSupportFlag(true) // 支持可变 shape(信息库 [-2] ND 动态口径) |
| 88 | .NeedCheckSupportFlag(false) // 跳过框架 support check(host tiling 入口已全量校验 rank/dtype/shape/p/axes) | 88 | .NeedCheckSupportFlag(false) // 跳过框架 support check(host tiling 入口已全量校验 rank/dtype/shape/p/axes) |
| 89 | .PrecisionReduceFlag(false) // 禁止降精度:fp16 输入须 fp32 累加(accumulator_dtype) | 89 | .PrecisionReduceFlag(false) // 禁止降精度:fp16 输入须 fp32 累加(accumulator_dtype) |
| 90 | - .ExtendCfgInfo("opFile.value", "lp_norm_reduce_apt"); // kernel 文件绑定:op_kernel/lp_norm_reduce_apt.cpp | 90 | + .ExtendCfgInfo("opFile.value", "lp_norm_reduce"); // kernel 文件绑定:op_kernel/lp_norm_reduce.cpp |
| 91 | // 覆盖 Ascend950PR / Ascend950DT(spec.yaml supported_chips) | 91 | // 覆盖 Ascend950PR / Ascend950DT(spec.yaml supported_chips) |
| 92 | this->AICore().AddConfig("ascend950", aicoreConfig); | 92 | this->AICore().AddConfig("ascend950", aicoreConfig); |
| 93 | } | 93 | } |
| @@ -78,6 +78,10 @@ constexpr int32_t OUTER_LOOP_AXIS_BASE = 4; | |||
| 78 | // -2147483647LL-1 避免字面量溢出) | 78 | // -2147483647LL-1 避免字面量溢出) |
| 79 | constexpr int64_t P_INF_SENTINEL = 2147483647; | 79 | constexpr int64_t P_INF_SENTINEL = 2147483647; |
| 80 | constexpr int64_t N_INF_SENTINEL = -2147483647LL - 1; | 80 | constexpr int64_t N_INF_SENTINEL = -2147483647LL - 1; |
| 81 | +// For fp32 (and narrower input types), squaring |x| at least 31 times | ||
| 82 | +// saturates every representable |x| != 1 to 0 or +Inf. This threshold is | ||
| 83 | +// above the reserved +Inf sentinel, so only finite p uses the fast path. | ||
| 84 | +constexpr int64_t HUGE_FINITE_P_THRESHOLD = 2147483648LL; | ||
| 81 | 85 | ||
| 82 | // ─── Cast trait(Kernel.md §9.1 / §9.6,cast-rules.md 口径) ─── | 86 | // ─── Cast trait(Kernel.md §9.1 / §9.6,cast-rules.md 口径) ─── |
| 83 | // 扩位(b16/int32 → fp32):CAST_NONE 直转 | 87 | // 扩位(b16/int32 → fp32):CAST_NONE 直转 |
| @@ -116,14 +120,15 @@ __aicore__ inline float PadValueOf(int64_t pOrder) | |||
| 116 | // ============================================================================= | 120 | // ============================================================================= |
| 117 | // §9.1 PreElewise —— pre-elewise Cast + Abs + p 分支预处理(VF 融合链) | 121 | // §9.1 PreElewise —— pre-elewise Cast + Abs + p 分支预处理(VF 融合链) |
| 118 | // | 122 | // |
| 119 | -// 公式对应:|x| 及其 p 分支(p=0 的 1[x≠0]、p≥2 的 |x|^p 二进制快速幂——平方-乘,≤63 轮); | 123 | +// 公式对应:|x| 及其 p 分支(p=0 的 1[x≠0]、普通有限 p≥2 的 |
| 124 | +// |x|^p 二进制快速幂、极大有限 p 的饱和快捷路径); | ||
| 120 | // p=±inf 哨兵 / p=1 直通 |x|(Reducer 在 ReduceChunk 按 pOrder 分发)。 | 125 | // p=±inf 哨兵 / p=1 直通 |x|(Reducer 在 ReduceChunk 按 pOrder 分发)。 |
| 121 | // p 分支互斥、pOrder 运行时标量选择(寄存器链不因分支断开);NaN 按 IEEE-754 | 126 | // p 分支互斥、pOrder 运行时标量选择(寄存器链不因分支断开);NaN 按 IEEE-754 |
| 122 | // 传播(|NaN|=NaN、NaN≠0 为真 → p=0 计 1,与 golden (x != 0) 一致)。 | 127 | // 传播(|NaN|=NaN、NaN≠0 为真 → p=0 计 1,与 golden (x != 0) 一致)。 |
| 123 | // padded 整 tile 覆盖:R 方向 pad 由清零 VF 在 Reduce 前清为 pad_value, | 128 | // padded 整 tile 覆盖:R 方向 pad 由清零 VF 在 Reduce 前清为 pad_value, |
| 124 | // A 方向 garbage 由 lane 隔离(Kernel.md §9.4)。 | 129 | // A 方向 garbage 由 lane 隔离(Kernel.md §9.4)。 |
| 125 | // ============================================================================= | 130 | // ============================================================================= |
| 126 | -template <typename DType> | 131 | +template <typename DType, bool HugeFiniteP> |
| 127 | __simd_vf__ inline void PreElewiseVfImpl(__ubuf__ DType* src, __ubuf__ float* dst, uint32_t totalElems, | 132 | __simd_vf__ inline void PreElewiseVfImpl(__ubuf__ DType* src, __ubuf__ float* dst, uint32_t totalElems, |
| 128 | uint16_t repeatTime, int64_t pOrder) | 133 | uint16_t repeatTime, int64_t pOrder) |
| 129 | { | 134 | { |
| @@ -151,7 +156,25 @@ __simd_vf__ inline void PreElewiseVfImpl(__ubuf__ DType* src, __ubuf__ float* ds | |||
| 151 | 156 | ||
| 152 | AscendC::Reg::Abs(f32Reg, f32Reg, mask); // |x|(fp32 计算域) | 157 | AscendC::Reg::Abs(f32Reg, f32Reg, mask); // |x|(fp32 计算域) |
| 153 | 158 | ||
| 154 | - if (pOrder == 0) { // p=0:非零计数 → 0/1 | 159 | + if constexpr (HugeFiniteP) { |
| 160 | + // The nearest fp32 values around 1 are 1-2^-24 and 1+2^-23. | ||
| 161 | + // At p>=2^31, their fp32 powers are respectively 0 and +Inf; | ||
| 162 | + // all other finite magnitudes are farther from 1. Preserve NaN | ||
| 163 | + // by retaining the original absolute value in unordered lanes. | ||
| 164 | + AscendC::Reg::RegTensor<float> zeroReg; | ||
| 165 | + AscendC::Reg::RegTensor<float> oneReg; | ||
| 166 | + AscendC::Reg::RegTensor<float> infReg; | ||
| 167 | + AscendC::Reg::RegTensor<float> afterGt; | ||
| 168 | + AscendC::Reg::MaskReg gtMask; | ||
| 169 | + AscendC::Reg::MaskReg ltMask; | ||
| 170 | + AscendC::Reg::Duplicate(zeroReg, 0.0f); | ||
| 171 | + AscendC::Reg::Duplicate(oneReg, 1.0f); | ||
| 172 | + AscendC::Reg::Duplicate(infReg, __builtin_huge_valf()); | ||
| 173 | + AscendC::Reg::Compare<float, AscendC::CMPMODE::GT>(gtMask, f32Reg, oneReg, mask); | ||
| 174 | + AscendC::Reg::Compare<float, AscendC::CMPMODE::LT>(ltMask, f32Reg, oneReg, mask); | ||
| 175 | + AscendC::Reg::Select<float>(afterGt, infReg, f32Reg, gtMask); | ||
| 176 | + AscendC::Reg::Select<float>(f32Reg, zeroReg, afterGt, ltMask); | ||
| 177 | + } else if (pOrder == 0) { // p=0:非零计数 → 0/1 | ||
| 155 | AscendC::Reg::RegTensor<float> zeroReg; | 178 | AscendC::Reg::RegTensor<float> zeroReg; |
| 156 | AscendC::Reg::RegTensor<float> oneReg; | 179 | AscendC::Reg::RegTensor<float> oneReg; |
| 157 | AscendC::Reg::MaskReg neMask; | 180 | AscendC::Reg::MaskReg neMask; |
| @@ -160,7 +183,7 @@ __simd_vf__ inline void PreElewiseVfImpl(__ubuf__ DType* src, __ubuf__ float* ds | |||
| 160 | AscendC::Reg::Compare<float, AscendC::CMPMODE::NE>(neMask, f32Reg, zeroReg, mask); | 183 | AscendC::Reg::Compare<float, AscendC::CMPMODE::NE>(neMask, f32Reg, zeroReg, mask); |
| 161 | AscendC::Reg::Select<float>(f32Reg, oneReg, zeroReg, neMask); | 184 | AscendC::Reg::Select<float>(f32Reg, oneReg, zeroReg, neMask); |
| 162 | } else if (pOrder >= 2 && pOrder != P_INF_SENTINEL) { | 185 | } else if (pOrder >= 2 && pOrder != P_INF_SENTINEL) { |
| 163 | - // p≥2(非哨兵):二进制快速幂 |x|^p(平方-乘,≤63 轮向量 Mul——对齐 | 186 | + // 2≤p<2^31(非哨兵):二进制快速幂 |x|^p(平方-乘,≤31 轮向量 Mul——对齐 |
| 164 | // changwei lp_norm_reduce_dag.h IntegerPowerSimtCompute 的算法;exponent | 187 | // changwei lp_norm_reduce_dag.h IntegerPowerSimtCompute 的算法;exponent |
| 165 | // 为运行时标量,轮数由其二进制位数决定,与 tile 元素数无关)。 | 188 | // 为运行时标量,轮数由其二进制位数决定,与 tile 元素数无关)。 |
| 166 | // ⛔ +inf 哨兵 2147483647 仍须排除——它按 §9.1 取值表走「直通 |x| + | 189 | // ⛔ +inf 哨兵 2147483647 仍须排除——它按 §9.1 取值表走「直通 |x| + |
| @@ -934,7 +957,11 @@ __aicore__ inline void LpNormReduceBaseKernel<DType>::PreElewiseVf(__ubuf__ DT* | |||
| 934 | td_->innerRProdAlign); | 957 | td_->innerRProdAlign); |
| 935 | const uint16_t repeatTime = static_cast<uint16_t>( | 958 | const uint16_t repeatTime = static_cast<uint16_t>( |
| 936 | Ops::Base::CeilDiv(totalElems, static_cast<uint32_t>(REP_F32_U16))); | 959 | Ops::Base::CeilDiv(totalElems, static_cast<uint32_t>(REP_F32_U16))); |
| 937 | - asc_vf_call<PreElewiseVfImpl<DT>>(src, dst, totalElems, repeatTime, td_->pOrder); | 960 | + if (td_->pOrder >= HUGE_FINITE_P_THRESHOLD) { |
| 961 | + asc_vf_call<PreElewiseVfImpl<DT, true>>(src, dst, totalElems, repeatTime, td_->pOrder); | ||
| 962 | + } else { | ||
| 963 | + asc_vf_call<PreElewiseVfImpl<DT, false>>(src, dst, totalElems, repeatTime, td_->pOrder); | ||
| 964 | + } | ||
| 938 | } | 965 | } |
| 939 | 966 | ||
| 940 | // §9.3 ClearChunkExtensionVf wrapper:调用前调用方已保证 rLen < rUbFactor | 967 | // §9.3 ClearChunkExtensionVf wrapper:调用前调用方已保证 rLen < rUbFactor |
| @@ -19,7 +19,7 @@ | |||
| 19 | // - (isGroup=1, isEmptyTensor=1) 互斥不实例化(tilingKey=3 为设计性空位); | 19 | // - (isGroup=1, isEmptyTensor=1) 互斥不实例化(tilingKey=3 为设计性空位); |
| 20 | // - dtype 不进 key(框架按输入名 x 以 DTYPE_X 编译期实例化 fp16/fp32/bf16 三档)、 | 20 | // - dtype 不进 key(框架按输入名 x 以 DTYPE_X 编译期实例化 fp16/fp32/bf16 三档)、 |
| 21 | // p / axisNum / keepdim / epsilon 等纯参数差异不拆 key。 | 21 | // p / axisNum / keepdim / epsilon 等纯参数差异不拆 key。 |
| 22 | -// 模板参数与 op_kernel/lp_norm_reduce_apt.cpp 的 kernel 入口签名 | 22 | +// 模板参数与 op_kernel/lp_norm_reduce.cpp 的 kernel 入口签名 |
| 23 | // template <bool isGroup, bool isEmptyTensor> 一一对应、顺序一致。 | 23 | // template <bool isGroup, bool isEmptyTensor> 一一对应、顺序一致。 |
| 24 | // | 24 | // |
| 25 | // ============================================================================= | 25 | // ============================================================================= |
Rnorm/lp_norm_reduce/op_kernel/lp_norm_reduce_apt.cpp→norm/lp_norm_reduce/op_kernel/lp_norm_reduce.cpp+1-1
| @@ -9,7 +9,7 @@ | |||
| 9 | */ | 9 | */ |
| 10 | 10 | ||
| 11 | // ============================================================================= | 11 | // ============================================================================= |
| 12 | -// LpNormReduce_package/op_kernel/lp_norm_reduce_apt.cpp | 12 | +// LpNormReduce_package/op_kernel/lp_norm_reduce.cpp |
| 13 | // ============================================================================= | 13 | // ============================================================================= |
| 14 | // | 14 | // |
| 15 | // ROLE: Ascend C kernel entry point for LpNormReduce. | 15 | // ROLE: Ascend C kernel entry point for LpNormReduce. |
| @@ -0,0 +1,175 @@ | |||
| 1 | +# Copyright (c) 2026 Huawei Technologies Co., Ltd. | ||
| 2 | +# This program is free software, you can redistribute it and/or modify it under the terms and conditions of | ||
| 3 | +# CANN Open Software License Agreement Version 2.0 (the "License"). | ||
| 4 | +# Please refer to the License for details. You may not use this file except in compliance with the License. | ||
| 5 | +# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, | ||
| 6 | +# INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE. | ||
| 7 | +# See LICENSE in the root of the software repository for the full text of the License. | ||
| 8 | +"""Independent CPU oracle and native Torch reference for LpNormReduce. | ||
| 9 | + | ||
| 10 | +Kernel/GEIR only. The oracle computes the reduction, not the final norm. | ||
| 11 | +TTK promotes inputs for mix_tolerance/cross_check; retain that promoted dtype | ||
| 12 | +on return instead of casting to the original device output dtype. | ||
| 13 | +""" | ||
| 14 | + | ||
| 15 | +import hashlib | ||
| 16 | +import importlib | ||
| 17 | +import operator | ||
| 18 | + | ||
| 19 | +import numpy as np | ||
| 20 | + | ||
| 21 | +__spec__ = { | ||
| 22 | + "lp_norm_reduce": "LpNormReduceTestSpec", | ||
| 23 | + "LpNormReduce": "LpNormReduceTestSpec", | ||
| 24 | +} | ||
| 25 | + | ||
| 26 | +P_INF = 2147483647 | ||
| 27 | +P_NEG_INF = -2147483648 | ||
| 28 | +INT64_MAX = (1 << 63) - 1 | ||
| 29 | + | ||
| 30 | + | ||
| 31 | +def _integer(value, name): | ||
| 32 | + if isinstance(value, (bool, np.bool_)): | ||
| 33 | + raise TypeError(f"{name} must be an integer, not bool") | ||
| 34 | + return operator.index(value) | ||
| 35 | + | ||
| 36 | + | ||
| 37 | +def _parameters(shape, p, axes): | ||
| 38 | + """Validate metadata only; no shared numerical implementation with Torch.""" | ||
| 39 | + p = _integer(p, "p") | ||
| 40 | + if p > INT64_MAX or (p < 0 and p != P_NEG_INF): | ||
| 41 | + raise ValueError( | ||
| 42 | + "p must be a nonnegative int64 or the negative-infinity sentinel" | ||
| 43 | + ) | ||
| 44 | + rank = len(shape) | ||
| 45 | + if rank > 8: | ||
| 46 | + raise ValueError("rank must be in [0, 8]") | ||
| 47 | + if axes is None: | ||
| 48 | + axes = () | ||
| 49 | + if not isinstance(axes, (list, tuple)): | ||
| 50 | + raise TypeError("axes must be a list or tuple of integers") | ||
| 51 | + dims = [] | ||
| 52 | + for axis in axes: | ||
| 53 | + axis = _integer(axis, "axis") | ||
| 54 | + if axis < -rank or axis >= rank: | ||
| 55 | + raise ValueError("axis outside [-rank, rank)") | ||
| 56 | + axis %= rank | ||
| 57 | + if axis not in dims: | ||
| 58 | + dims.append(axis) | ||
| 59 | + dims = tuple(dims) if axes else tuple(range(rank)) | ||
| 60 | + if p in (P_INF, P_NEG_INF) and any(shape[axis] == 0 for axis in dims): | ||
| 61 | + raise ValueError("infinite-p reduction over an empty domain is undefined") | ||
| 62 | + return p, dims | ||
| 63 | + | ||
| 64 | + | ||
| 65 | +def _customize_inputs(x, **kwargs): | ||
| 66 | + """Inject deterministic special values by testcase name. | ||
| 67 | + | ||
| 68 | + The hook runs once inside TTK input generation, so NPU, golden and the | ||
| 69 | + remote third_party all consume the same injected arrays. | ||
| 70 | + | ||
| 71 | + - ``zeros_*``: about n/3 exact zeros (plus a few -0.0). The p=0 nonzero | ||
| 72 | + count semantics is only exercised when zeros actually exist. | ||
| 73 | + - ``infnan_*``: inject +inf/-inf/NaN/±0.0 for IEEE propagation checks. | ||
| 74 | + - Other cases pass through unchanged. | ||
| 75 | + | ||
| 76 | + Seed is the first 8 hex digits of md5(testcase_name) (builtin hash() is | ||
| 77 | + not stable across processes), keeping runs reproducible. | ||
| 78 | + """ | ||
| 79 | + name = str(kwargs.get("testcase_name", "")) | ||
| 80 | + if not (name.startswith("zeros_") or name.startswith("infnan_")): | ||
| 81 | + return (x,) | ||
| 82 | + | ||
| 83 | + arr = np.asarray(x) | ||
| 84 | + if arr.size == 0: | ||
| 85 | + return (x,) | ||
| 86 | + seed = int(hashlib.md5(name.encode("utf-8")).hexdigest()[:8], 16) | ||
| 87 | + rng = np.random.default_rng(seed) | ||
| 88 | + flat = arr.reshape(-1).copy() | ||
| 89 | + n = flat.size | ||
| 90 | + | ||
| 91 | + if name.startswith("zeros_"): | ||
| 92 | + idx = rng.choice(n, size=max(1, n // 3), replace=False) | ||
| 93 | + flat[idx] = 0.0 | ||
| 94 | + # Keep a few -0.0: |-0.0| == 0 must not count as nonzero either. | ||
| 95 | + flat[idx[: max(1, idx.size // 10)]] = -0.0 | ||
| 96 | + else: | ||
| 97 | + specials = np.array([np.inf, -np.inf, np.nan, 0.0, -0.0], dtype=flat.dtype) | ||
| 98 | + k = min(n, max(specials.size, n // 20)) | ||
| 99 | + idx = rng.choice(n, size=k, replace=False) | ||
| 100 | + flat[idx] = specials[rng.integers(0, specials.size, size=k)] | ||
| 101 | + | ||
| 102 | + # ascontiguousarray promotes a scalar to (1,); restore its logical shape last. | ||
| 103 | + return (np.ascontiguousarray(flat).reshape(arr.shape),) | ||
| 104 | + | ||
| 105 | + | ||
| 106 | +class TorchLpNormReduce: | ||
| 107 | + """Native Torch composition; FP16/BF16 intermediates use FP32 per spec. | ||
| 108 | + | ||
| 109 | + This implementation is also the performance reference. It does not mimic | ||
| 110 | + the device reduction tree, tiling or integer-power implementation. | ||
| 111 | + """ | ||
| 112 | + | ||
| 113 | + def __init__(self, *, p=2, axes=None, keepdim=False, epsilon=1e-12, **kwargs): | ||
| 114 | + self.p = p | ||
| 115 | + self.axes = axes | ||
| 116 | + self.keepdim = keepdim | ||
| 117 | + | ||
| 118 | + def __call__(self, x, **kwargs): | ||
| 119 | + # Dynamic import: a literal ``import torch`` here makes TTK's worker | ||
| 120 | + # preload (pool.py:_preload_plugin_frameworks) import torch inside a | ||
| 121 | + # forkserver child, which segfaults in this environment. This class | ||
| 122 | + # only runs on the remote third_party server, which imports torch | ||
| 123 | + # itself; the local plugin never needs torch. | ||
| 124 | + torch = importlib.import_module("torch") | ||
| 125 | + | ||
| 126 | + p, dims = _parameters(x.shape, self.p, self.axes) | ||
| 127 | + calc = x.to(torch.float32) if x.dtype in (torch.float16, torch.bfloat16) else x | ||
| 128 | + a = torch.abs(calc) | ||
| 129 | + if p == P_INF: | ||
| 130 | + y = torch.amax(a, dim=dims, keepdim=self.keepdim) | ||
| 131 | + elif p == P_NEG_INF: | ||
| 132 | + y = torch.amin(a, dim=dims, keepdim=self.keepdim) | ||
| 133 | + elif p == 0: | ||
| 134 | + y = (a != 0).to(calc.dtype).sum(dim=dims, keepdim=self.keepdim) | ||
| 135 | + elif p == 1: | ||
| 136 | + y = torch.sum(a, dim=dims, keepdim=self.keepdim) | ||
| 137 | + else: | ||
| 138 | + y = torch.sum(torch.pow(a, p), dim=dims, keepdim=self.keepdim) | ||
| 139 | + return [y.to(x.dtype)] | ||
| 140 | + | ||
| 141 | + | ||
| 142 | +class LpNormReduceTestSpec: | ||
| 143 | + """CPU NumPy reference for the mathematical formula in the operator README.""" | ||
| 144 | + | ||
| 145 | + @staticmethod | ||
| 146 | + def golden(x, p=2, axes=None, keepdim=False, epsilon=1e-12, **kwargs): | ||
| 147 | + """FP64 intermediates, input dtype on return (including Promote dtype). | ||
| 148 | + | ||
| 149 | + epsilon is intentionally unused. No clamping or added epsilon: preserve | ||
| 150 | + IEEE NaN/Inf behavior. NaN counts as nonzero for p=0. Scalar shape is | ||
| 151 | + retained; zero-length finite reductions return zero. | ||
| 152 | + """ | ||
| 153 | + x = np.asarray(x) | ||
| 154 | + p, dims = _parameters(x.shape, p, axes) | ||
| 155 | + with np.errstate(over="ignore", under="ignore", invalid="ignore"): | ||
| 156 | + a = np.abs(x.astype(np.float64)) | ||
| 157 | + if p == P_INF: | ||
| 158 | + y = np.max(a, axis=dims, keepdims=keepdim) | ||
| 159 | + elif p == P_NEG_INF: | ||
| 160 | + y = np.min(a, axis=dims, keepdims=keepdim) | ||
| 161 | + elif p == 0: | ||
| 162 | + y = np.sum(a != 0, axis=dims, keepdims=keepdim, dtype=np.float64) | ||
| 163 | + elif p == 1: | ||
| 164 | + y = np.sum(a, axis=dims, keepdims=keepdim) | ||
| 165 | + else: | ||
| 166 | + y = np.sum(np.power(a, p), axis=dims, keepdims=keepdim) | ||
| 167 | + return [np.asarray(y).astype(x.dtype)] | ||
| 168 | + | ||
| 169 | + third_party = {"torch": TorchLpNormReduce} | ||
| 170 | + customize_inputs = _customize_inputs | ||
| 171 | + tolerance = { | ||
| 172 | + "float16": {"standard": "mix_tolerance"}, | ||
| 173 | + "bfloat16": {"standard": "mix_tolerance"}, | ||
| 174 | + "float32": {"standard": "mix_tolerance"}, | ||
| 175 | + } | ||
| @@ -0,0 +1,13 @@ | |||
| 1 | +testcase_name,op_name,input_shapes,input_dtypes,output_shapes,output_dtypes,attributes,input_data_ranges | ||
| 2 | +lpnorm_smoke_p0_f16,lp_norm_reduce,"((2,32),)","('float16',)","((2,),)","('float16',)","{'p':0,'axes':[1]}","((0,0),)" | ||
| 3 | +lpnorm_smoke_p1_bf16,lp_norm_reduce,"((2,32),)","('bfloat16',)","((2,),)","('bfloat16',)","{'p':1,'axes':[-1]}","((-1,-1),)" | ||
| 4 | +lpnorm_smoke_p2_f32,lp_norm_reduce,"((3,5),)","('float32',)","((1,5),)","('float32',)","{'p':2,'axes':[0,0],'keepdim':True}","((-2,-2),)" | ||
| 5 | +lpnorm_smoke_p3_f16,lp_norm_reduce,"((2,32),)","('float16',)","((2,),)","('float16',)","{'p':3,'axes':[1]}","((-2,-2),)" | ||
| 6 | +lpnorm_smoke_pmax_bf16,lp_norm_reduce,"((2,32),)","('bfloat16',)","((2,),)","('bfloat16',)","{'p':2147483647,'axes':[1]}","((-2,-2),)" | ||
| 7 | +lpnorm_smoke_pmin_f32,lp_norm_reduce,"((2,32),)","('float32',)","((2,),)","('float32',)","{'p':-2147483648,'axes':[1]}","((-2,-2),)" | ||
| 8 | +lpnorm_smoke_scalar_f16,lp_norm_reduce,"((),)","('float16',)","((),)","('float16',)","{'p':2,'axes':[],'keepdim':False}","((-2,-2),)" | ||
| 9 | +lpnorm_smoke_empty_r_f32,lp_norm_reduce,"((2,0,3),)","('float32',)","((2,3),)","('float32',)","{'p':2,'axes':[1]}","((1,1),)" | ||
| 10 | +lpnorm_smoke_empty_a_bf16,lp_norm_reduce,"((0,3),)","('bfloat16',)","((0,),)","('bfloat16',)","{'p':1,'axes':[1]}","((1,1),)" | ||
| 11 | +lpnorm_smoke_group_f16,lp_norm_reduce,"((2,131072),)","('float16',)","((2,),)","('float16',)","{'p':2147483647,'axes':[1]}","((1,1),)" | ||
| 12 | +lpnorm_smoke_group_bf16,lp_norm_reduce,"((2,131072),)","('bfloat16',)","((2,),)","('bfloat16',)","{'p':1,'axes':[1]}","((1,1),)" | ||
| 13 | +lpnorm_smoke_group_f32,lp_norm_reduce,"((2,131072),)","('float32',)","((2,),)","('float32',)","{'p':2,'axes':[1]}","((1,1),)" | ||