Merged
fix(lp_norm_reduce): 加固 Ascend950 边界与极大 p 性能 #11238
fix(lp_norm_reduce): 加固 Ascend950 边界与极大 p 性能 #11238
Merged
raoliang_sacAdd on Sep 27
Total: 9 files changed+252-15
@@ -27,7 +27,7 @@ add_modules_sources(
27target_include_directories(${GRAPH_PLUGIN_NAME}_obj PRIVATE ${ASCEND_GRAPH_CONF_DST})27target_include_directories(${GRAPH_PLUGIN_NAME}_obj PRIVATE ${ASCEND_GRAPH_CONF_DST})
28 28 
29add_kernel_sources(29add_kernel_sources(
30- KERNEL_SRC lp_norm_reduce_apt.cpp30+ KERNEL_SRC lp_norm_reduce.cpp
31 COMPUTE_UNITS ascend95031 COMPUTE_UNITS ascend950
32 AUTO_SYNC false32 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 × maxDtypeSize1111// 计算 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.cpp90+ .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 避免字面量溢出)
79constexpr int64_t P_INF_SENTINEL = 2147483647;79constexpr int64_t P_INF_SENTINEL = 2147483647;
80constexpr int64_t N_INF_SENTINEL = -2147483647LL - 1;80constexpr 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-754126// 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/1159+ 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 的算法;exponent187 // 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 < rUbFactor967// §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.cpp12+// 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),)"