| 文件 | 最后提交记录 | 最后更新时间 |
|---|---|---|
feat(sgd): 新增SGD优化器算子arch35实现(Ascend 950PR/950DT,GE图模式) Co-authored-by: raoliang_sac<raoliang4@huawei.com> # message auto-generated for no-merge-commit merge: !8155 merge SGD-810 into master feat(sgd): 新增SGD优化器算子arch35实现(Ascend 950PR/950DT,GE图模式) Created-by: raoliang_sac Commit-by: raoliang_sac Merged-by: cann-robot Description: ## 描述 本 PR 为 optim/sgd 新增 **SGD(带动量的随机梯度下降)优化器算子**在 <term>Ascend 950PR/Ascend 950DT</term>(arch35 / DAV_3510 / regbase)上的 Ascend C 实现,共 22 个文件、+2719 行,**纯新增,不修改任何存量文件**(唯一的非新增改动是在 docs/zh/op_list.md 中追加一行算子登记)。 ### 改动原因 optim 目录下已有 fused_sgd,但那是语义不同的另一个算子;本仓此前**不存在** SGD 算子,Ascend 950 上缺失该优化器的 Ascend C 实现。910B/910C 上 SGD 由 CANN 内置的 TBE 实现承载,Ascend 950 需要在本仓补齐。 ### 改动方法 **1. 计算语义** —— 与 910B/910C 基线(canndev nn_training_ops.h 的 REG_OP(SGD) 与 aic-ascend910b-ops-info.ini 的 [SGD] 段)逐字对齐: grad = (wd != 0) ? gradient + parameters * wd : gradient accum_t = accum * m + grad // 无条件计算 accum_t -= (d != 0) ? grad * (1 - stat) * d : 0 parameters -= nesterov ? (grad * lr + accum_t * m * lr) : (accum_t * lr) if (m != 0) { accum = accum_t; stat = 0; } // 回写掩码 - 六输入 parameters / gradient / learning_rate / accum / momentum / stat,图上**只声明一个输出** parameters —— accum 与 stat 靠覆写输入 GM **原地回写**,与 A2 形态一致(A2 的 TBE 实现声明 reuse=('accum','parameters','stat'))。 - momentum == 0(含 -0.0)时 accum / stat **完全不写**、逐位保持输入值;parameters 照常更新。 - 与 PyTorch 的**真实分歧**已在 sgd_proto.h 与 README 显式记录:PyTorch 把 dampening 放在 momentum 块**内部**,本算子(同 A2)放在**外部**,故 m==0 && d>0 && stat==0 时结果差 (1-d) 倍。这是对齐 A2 的有意选择,不是缺陷。 **2. Kernel 实现** —— op_kernel/arch35/,基于 ATVOSS DAG + ElementwiseSch: - sgd_dag.h 用算子级 DAG 描述上述公式;sgd.cpp 在运行期按 momentum == 0 选择两套 DAG(**该掩码不是 TilingKey 维度**:momentum 是 Device 侧 [1] 张量,Host Tiling 阶段拿不到其数值,只能做运行期分支,binary 数量不变)。 - TilingKey 四维模板参数 schMode / useNesterov / hasWeightDecay / hasDampening。非法组合 nesterov==1 && dampening!=0 由 ASCENDC_TPL_SEL 两组 ARGS_SEL 在编译期剪除,**不生成对应 binary**;Host 侧 InferShape 与 Tiling 亦对该组合报 attribute_value_out_of_range。 - 组合数:业务模板 2×2×2 − 2 = 6(K0~K5)→ TPL_SEL 展开 12 → binary 12 × 3 dtype = 36。 **3. Host 实现** —— op_host/: - sgd_def.cpp:dtype FLOAT / FLOAT16 / BFLOAT16,format 仅 ND,DynamicShapeSupportFlag(true) / DynamicRankSupportFlag(true) / PrecisionReduceFlag(false)(对齐 A2 precision_reduce.flag=false);**只 AddConfig("ascend950"),不触碰任何 A2 配置**。format 只做 ND 是跟随本仓 arch35 全族 optim 算子(apply_momentum / apply_ftrl / apply_adam_w_v2 / apply_adamax / apply_centered_rms_prop)的既定约定 —— 这些算子的 def.cpp 一律只声明 ge::FORMAT_ND,无一例声明私有 format。 - sgd_infershape.cpp:校验 nesterov==true 时 dampening 必须为 0、weight_decay >= 0;rank 限 [1,8](rank-0 拒绝);空 tensor(任一轴为 0)拒绝为 null_input;-1(UNKNOWN_DIM)支持、-2(UNKNOWN_RANK)透传。 - arch35/sgd_tiling.cpp:基于 ElewiseBaseTiling。 **4. 交付形态:GE 图模式,不提供 aclnn 接口。** sgd_def.cpp 保持 ACLNNTYPE aclnn_exclude。依据:CANN 9.1.0 的 include/aclnnop/ 无 aclnn_sgd.h、libopapi.so 未导出任何 aclnnSgd* 符号(阴性对照:同库确实导出 aclnnFusedSgd,证明查法有效)、canndev 全仓无 aclnn_sgd 定义 —— SGD 在上游本就是纯图模式算子,本算子与之对齐。故 docs/zh/op_list.md 中 op_api 列填 ✗,docs/zh/op_api_list.md 不涉及。 ## 关联的Issue 关联Issue #4554 —— https://gitcode.com/cann/ops-nn/issues/4554 ## 测试 全部在 Ascend950PR 真机 + CANN 9.1.0 上实测,合计 **1081 项、0 失败**: | 测试腿 | 规模 | 结果 | |---|---|---| | 功能泛化(TTK kernel 模式) | 800 例 | **800/800 PASS**;bin_precision 100.0%、memory_oob_status 全 PASS;6 个 tilingKey × 3 dtype 零缺失 | | 三方精度对比 | 50 例 | **50/50 PASS**;NPU vs CPU-fp64 golden vs A100-GPU 三方比对,ratio_mare 恒为 1.000,最大 MERE 1.553e-03 | | Host UT | 31 例 | **31/31 PASS**(infershape 11 + arch35 tiling 20) | | GE 图模式冒烟 | 2 分支 | examples/test_geir_sgd.cpp,accum / stat 两条回写分支各 **60/60 元素**逐一核对 | | 性能对标 A100 | 200 例 | **200/200 NPU 更快** | **边界覆盖**:空 tensor(1D [0]、2D [0,C]/[N,0]、多轴 [0,0]、高 rank 混合)逐形态拒绝;rank-0 与 rank-9 两端拒绝;-2 动态 rank 透传;inf/nan 按 IEEE 语义无条件传播(判定方式是按 NaN/+Inf/-Inf 三张掩码逐位 array_equal,不套相对误差)。 **性能判据说明(供评审取用)**:按仓内 verification.md §7.1「mean ≥ 1.2 且 min ≥ 0.5」口径为 200/200 PASS;按测试侧 test-bot-dev 的芯片对阈值((950PR, A100) = 0.5,逐例 npu/gpu ≤ 0.5)为 191/200,9 条超阈值全部落在 numel = 4M 档、其中 7 条是 momentum == 0。成因是**访存量不对等而非实现慢**:m == 0 时 torch 不需要 momentum buffer,而本算子的契约要求无条件读 accum;最差的一条 NPU 搬运 1.67 倍数据、耗时反而更短,按达成带宽折算 NPU/GPU = 2.03x。对等访存量下 NPU 仍显著更快。 **本地 CI 复现**:单算子包 @ascend950、910b、910_93、整仓 JIT(默认 soc,2144 targets)、整仓 A5@950(2527 targets)共 5 项编译全过、0 error。其中 910b 一项专门验证 sgd_infershape.cpp 在 A2 上仍参与编译(kernel/tiling 由 On [ascend910b], [sgd] not supported. 排除,infershape 不排除),确认未引入会破坏 A2 的 arch35-only 头文件。 ## 文档更新 - 新增 optim/sgd/README.md(185 行):产品支持情况、计算公式、参数说明、约束与注意事项。产品支持表按 SGD 在各产品形态上的**可得性**填写,并在表下显式声明「本仓的 Ascend C 实现只适配 Ascend 950PR/950DT,其余形态由 CANN 内置 TBE 实现提供」。 - docs/zh/op_list.md 新增一行 optim / sgd 登记,op_kernel ✓ / op_host ✓ / op_api ✗ / op_graph ✓。 - docs/zh/op_api_list.md 不涉及(本算子不提供 aclnn 接口)。 ## 类型标签 <!-- [x] 表示选中 --> - [ ] Bug修复 - [x] 新特性 - [ ] 性能优化 - [ ] 文档更新 - [ ] 其他,请描述: ## AI/Agent生成声明 <!-- [x] 表示选中 --> - [x] AI辅助编写 See merge request: cann/ops-nn!8155 | 1 个月前 | |
fix(in_training_reduce_v2/sgd/normalize_bbox): 修复原型重定义与InferDataType放置,落地三算子检视整改 Co-authored-by: raoliang_sac<raoliang4@huawei.com> # message auto-generated for no-merge-commit merge: !8402 merge fix/three-ops-review-issue-4553 into master fix(in_training_reduce_v2/sgd/normalize_bbox): 修复原型重定义与InferDataType放置,落地三算子检视整改 Created-by: raoliang_sac Commit-by: raoliang_sac Merged-by: cann-robot Description: ## 描述 本 PR 一次性落地两块内容:Issue #4553 提出的交付件划分/原型重定义问题,以及 INTrainingReduceV2、SGD、NormalizeBBox 三个算子的代码检视意见。 ### 一、Issue #4553 **1. 原型重定义防护** 三个算子的原型都是从 canndev built-in 头( reduce_ops.h / nn_detect_ops.h / nn_training_ops.h,迁移后落在 ops_proto_legacy.h)挪出来的,两侧头文件被同一编译单元同时包含时会 REG_OP 重定义。已给三个 proto.h 的 REG_OP 补上防重定义宏,宏名沿用 ops_proto_legacy.h 自身的 OPS_PROTO_DEF_<OPTYPE> 约定: - OPS_PROTO_DEF_INTRAININGREDUCEV2 - OPS_PROTO_DEF_NORMALIZEBBOX - OPS_PROTO_DEF_SGD **2. InferDataType 交付件划分** InferDataType 仅图场景使用,从 op_host/*_infershape.cpp 挪到 op_graph/*_graph_infer.cpp(IMPL_OP 注册);InferShape 图与单算子共用,保留在 op_host(IMPL_OP_INFERSHAPE)。对齐仓内 norm/bn_infer_grad、norm/lp_norm_update、norm/in_infer_v2 的既有做法。add_graph_plugin_sources() 按 *_graph*.cpp 通配收集,无需改 CMakeLists。 ### 二、检视意见整改 **[高] INTrainingReduceV2 sub-R 路径 UB 越界** DoSubRTiling 只按 usable / 8 固定预留部分和缓存,算出 numChunks 后从不回验总占用。R 特别大时实际会撑破预留。按 Ascend950 实测参数(Ascend950*.ini 的 ub_size=253952,减 RESERVE_FOR_ALIGN=512 得 usable=253440B,VL_FP32=64): | dtype | 越界起点 R | 首版公式的 rFactor / numChunks | 实际申请 | 越界量 | |---|---|---|---|---| | fp32 | 109707265(上一个安全值 109707264) | 27648 / 3969 | **253568B** | 128B | | fp16 | 219668481(上一个安全值 219668480) | 55360 / 3969 | **253824B** | 384B | 越界可能导致 InitBuffer 失败或 Buffer 互相踩踏。 > 早先版本的本节按 UB=245760 举了 [1, 1, 104439809] 这个算例,该值取自仓内 arch35 UT 的 compile_info(activation/、norm/ 下几十个算子沿用的同一份旧模板),**不是 Ascend950 的真实 UB**。按真实 UB 复核,那个 shape 只占 252032B ≤ 253440B,并不越界(实测其 rFactor=27648 就是首版公式值,联合求解根本没触发)。缺陷成立,但触发点在上表,算例已更正。 修法不是简单加校验后拒绝(那会把本来能算的 shape 变成不支持),而是联合求解:用与 Kernel InitSubR() 逐项一致的公式(新增 CalcSubRUbBytes())复核总占用,超了就按实际 partial 占用回缩 rFactor 并重算 numChunks。rFactor 变小时输入双缓冲省下的字节多于 numChunks 增加所需的槽位,故迭代单调收敛(fp32 R=109707265 一轮收敛到 rFactor=27584 / numChunks=3978 / 253056B)。确实无解的超大 R 则明确返回失败,不再下发越界 tiling。 迭代上限 SUB_R_SOLVE_MAX_ITER 取 **32**。最初取 8 并注释为"正常 1~2 轮收敛,只作死循环兜底",但那只对 R 不太大时成立:R 逼近 UB 容量上限时 rFactor 已被 partial 挤得很小,每轮只能再缩一点点,轮数急剧上升。穷举实测收敛所需轮数峰值为 fp32 19 轮 / fp16 24 轮,出现在真正无解点(fp32 R≈2.50e8、fp16 R≈5.01e8,即 0.93GiB 输入)附近。上限取 8 会产生一段**误拒区间**——明明有可行解却报 cannot fit UB 拒绝下发:fp32 R ∈ [237502465, 249892865)、fp16 R ∈ [473001985, 500797441)。失败方向是安全的(拒绝而非下发越界 tiling),但仍是错判,故提到 32 并补 UT 016 锁死(该 UT 在上限为 8 时会 FAIL,已实测验证)。 **[高] INTrainingReduceV2 空 tensor 契约冲突** 图原型声明"支持空 tensor,允许空间规约轴为 0",但唯一注册的 AR_FULL_REDUCE 模板在 r <= 0 时直接 return false(代码注释亦写明"R=0 由后续 REDUCE_EMPTY 承接,本迭代不含")。本迭代确定不支持,故按实现修正 proto 与 README 表述,并补空间轴为 0 的拒绝 UT。 **[中] sub-R 路径 64→32 位收窄保护** Kernel ProcessSubR() 把 numN / numC / numR / perCoreCnt 收窄成 uint32_t,Host 侧此前未证明其落在 32 位内。新增 CheckSubRNarrowable() 显式校验 N / C / R / N*C。同时修正 in_training_reduce_v2_sub_r.h 中 static_cast<uint64_t>(numN_ * numC_)——这是先做 uint32 乘法再转宽,回绕后再 cast 已经晚了,改为两个操作数各自提升 64 位再相乘。 **[合规] SGD 注释命名红线 R8** 源码注释中的团队私下叫法 A2 / A5 改为规范名 ascend910b / ascend950,涉及 sgd_def.cpp、sgd_infershape.cpp、sgd_tiling.cpp、sgd_tiling_key.h。README 产品表中的正式产品名"Atlas A2"属于规范用法,未改动。 **[中] NormalizeBBox golden 不支持 batch=0** golden.py 的 shape_hw.reshape(batch, -1) 在 batch=0 时 torch 无法推导 -1,直接抛 RuntimeError;而 Host Tiling 有 batch == 0 的空 tensor 快速路径、UT 中也有对应用例,golden 与算子支持域不一致。改为直接索引契约已限定为二维 (batch, 3) 的 shape_hw。 **[中] SGD 标量 shape 实现与文档不一致** CheckScalarShape 实际接受任意 numel == 1 的 shape([1,1]、[1,1,1] 均通过,错误文案本身也写的是 "scalar(0D) or have shape size 1"),而 README 写死"只能是 [1] 或 0 维标量"。选择按实现放宽文档而非收紧代码:收紧会拒掉 [1,1] 等已有图中可能存在的写法,属破坏性变更;放宽文档零风险。README 与 proto 表述已同步,并补上 0D / [1,1] / [1,1,1] 的接受 UT。 **[低] ParseShapeByFormat 超 CodeCheck 行数阈值** ParseShapeByFormat() 实测 51 个有效代码行,超本地 CodeCheck 的 50 行阈值。抽出三分支共用的 ParseAndCheckNC(),主函数降至 39 行。 **其他** INTrainingReduceV2 与 NormalizeBBox 的 README 补充"本算子不提供 aclnn 单算子接口,仅支持 GE 图模式调用",减少后续扫描与评审歧义。 ### 三、评估后未采纳的检视建议 - **NormalizeBBox TilingData 的 usedCoreNum 字段**:该字段已注释声明为诊断用途(Host 用于设置 blockDim),且是 20 余条 Host UT 的核心观测点。为节省 8 字节而重写大量断言,代价大于收益。 - **两个 GEIR 样例的 10 行重复代码**:为 10 行样板在 norm/ 与 vfusion/ 之间新建跨目录共享头,维护代价大于收益;样例代码应保持自包含可读。 ## 关联的Issue 关联Issue #4553 ## 测试 - **NormalizeBBox golden 实跑验证**:fp16/fp32 × normal/reversed × batch=0/batch=2 共 8 组组合全部通过;修改前 batch=0 确认抛 RuntimeError: cannot reshape tensor of 0 elements into shape [0, -1]。 - **新增 UT 用例**: - tiling_ar_full_reduce_nchw_r0_empty_rejected_012:空间轴为 0 时 Tiling 明确失败。 - tiling_ar_full_reduce_sub_r_partial_buf_fits_ub_013:原越界 shape 下断言分块参数自洽,且按 Kernel 公式复算的 UB 占用不越界。 - tiling_ar_full_reduce_sub_r_unfittable_rejected_014:任何 rFactor 都放不下的超大 R 被明确拒绝。 - tiling_ar_full_reduce_sub_r_r_over_uint32_rejected_015:R 超 UINT32_MAX 被 Host 拒绝。 - sgd_tiling_accept_scalar_shape_any_single_element:0D / [1,1] / [1,1,1] 标量入参被接受。 - **静态检查**:Ruff 检查(check + format)通过;改动行宽全部 ≤ 120 列(含中文按显示宽度计)。 - **未执行项(需 CI 覆盖)**:本地无 CANN 构建链,C++ 未编译、UT 未实跑;本地 clang-format 仅 21.x 而仓库固定 18.1.8,为避免引入无关重排未执行格式化。 ### 需评审关注 InferDataType 挪到 op_graph 后,原有的 InferDataType UT 用例已下线。原因是 cmake/ut.cmake 中 op_graph UT 模块只链接 graph_plugin_obj,不含 tests/ut/common 的 infershape 公共对象(InferDataTypeTest / InferDataTypeContextFaker 位于仅 OP_HOST_UT 才编译的 _common_obj),用例挪过去无法链接。仓内所有把 InferDataType 放 op_graph 的算子目前均无此层覆盖。若需保住覆盖,需改动全仓共用的 add_op_graph_ut_modules,已在各 UT 文件头注明原因,未在本 PR 中改动构建基础设施。 ## 文档更新 - 更新 norm/in_training_reduce_v2/README.md:补充不支持空 tensor 的约束、无 aclnn 接口说明。 - 更新 optim/sgd/README.md:learning_rate / momentum 的 shape 约束表述与实现对齐。 - 更新 vfusion/normalize_bbox/README.md:补充无 aclnn 接口说明。 - 更新三个算子 proto.h 中的接口注释(空 tensor 支持面、标量 shape 接受面)。 ## 类型标签 <!-- [x] 表示选中 --> - [x] Bug修复 - [ ] 新特性 - [ ] 性能优化 - [x] 文档更新 - [ ] 其他,请描述: ## AI/Agent生成声明 <!-- [x] 表示选中 --> - [x] AI辅助编写 See merge request: cann/ops-nn!8402 | 29 天前 | |
fix(in_training_reduce_v2/sgd/normalize_bbox): 修复原型重定义与InferDataType放置,落地三算子检视整改 Co-authored-by: raoliang_sac<raoliang4@huawei.com> # message auto-generated for no-merge-commit merge: !8402 merge fix/three-ops-review-issue-4553 into master fix(in_training_reduce_v2/sgd/normalize_bbox): 修复原型重定义与InferDataType放置,落地三算子检视整改 Created-by: raoliang_sac Commit-by: raoliang_sac Merged-by: cann-robot Description: ## 描述 本 PR 一次性落地两块内容:Issue #4553 提出的交付件划分/原型重定义问题,以及 INTrainingReduceV2、SGD、NormalizeBBox 三个算子的代码检视意见。 ### 一、Issue #4553 **1. 原型重定义防护** 三个算子的原型都是从 canndev built-in 头( reduce_ops.h / nn_detect_ops.h / nn_training_ops.h,迁移后落在 ops_proto_legacy.h)挪出来的,两侧头文件被同一编译单元同时包含时会 REG_OP 重定义。已给三个 proto.h 的 REG_OP 补上防重定义宏,宏名沿用 ops_proto_legacy.h 自身的 OPS_PROTO_DEF_<OPTYPE> 约定: - OPS_PROTO_DEF_INTRAININGREDUCEV2 - OPS_PROTO_DEF_NORMALIZEBBOX - OPS_PROTO_DEF_SGD **2. InferDataType 交付件划分** InferDataType 仅图场景使用,从 op_host/*_infershape.cpp 挪到 op_graph/*_graph_infer.cpp(IMPL_OP 注册);InferShape 图与单算子共用,保留在 op_host(IMPL_OP_INFERSHAPE)。对齐仓内 norm/bn_infer_grad、norm/lp_norm_update、norm/in_infer_v2 的既有做法。add_graph_plugin_sources() 按 *_graph*.cpp 通配收集,无需改 CMakeLists。 ### 二、检视意见整改 **[高] INTrainingReduceV2 sub-R 路径 UB 越界** DoSubRTiling 只按 usable / 8 固定预留部分和缓存,算出 numChunks 后从不回验总占用。R 特别大时实际会撑破预留。按 Ascend950 实测参数(Ascend950*.ini 的 ub_size=253952,减 RESERVE_FOR_ALIGN=512 得 usable=253440B,VL_FP32=64): | dtype | 越界起点 R | 首版公式的 rFactor / numChunks | 实际申请 | 越界量 | |---|---|---|---|---| | fp32 | 109707265(上一个安全值 109707264) | 27648 / 3969 | **253568B** | 128B | | fp16 | 219668481(上一个安全值 219668480) | 55360 / 3969 | **253824B** | 384B | 越界可能导致 InitBuffer 失败或 Buffer 互相踩踏。 > 早先版本的本节按 UB=245760 举了 [1, 1, 104439809] 这个算例,该值取自仓内 arch35 UT 的 compile_info(activation/、norm/ 下几十个算子沿用的同一份旧模板),**不是 Ascend950 的真实 UB**。按真实 UB 复核,那个 shape 只占 252032B ≤ 253440B,并不越界(实测其 rFactor=27648 就是首版公式值,联合求解根本没触发)。缺陷成立,但触发点在上表,算例已更正。 修法不是简单加校验后拒绝(那会把本来能算的 shape 变成不支持),而是联合求解:用与 Kernel InitSubR() 逐项一致的公式(新增 CalcSubRUbBytes())复核总占用,超了就按实际 partial 占用回缩 rFactor 并重算 numChunks。rFactor 变小时输入双缓冲省下的字节多于 numChunks 增加所需的槽位,故迭代单调收敛(fp32 R=109707265 一轮收敛到 rFactor=27584 / numChunks=3978 / 253056B)。确实无解的超大 R 则明确返回失败,不再下发越界 tiling。 迭代上限 SUB_R_SOLVE_MAX_ITER 取 **32**。最初取 8 并注释为"正常 1~2 轮收敛,只作死循环兜底",但那只对 R 不太大时成立:R 逼近 UB 容量上限时 rFactor 已被 partial 挤得很小,每轮只能再缩一点点,轮数急剧上升。穷举实测收敛所需轮数峰值为 fp32 19 轮 / fp16 24 轮,出现在真正无解点(fp32 R≈2.50e8、fp16 R≈5.01e8,即 0.93GiB 输入)附近。上限取 8 会产生一段**误拒区间**——明明有可行解却报 cannot fit UB 拒绝下发:fp32 R ∈ [237502465, 249892865)、fp16 R ∈ [473001985, 500797441)。失败方向是安全的(拒绝而非下发越界 tiling),但仍是错判,故提到 32 并补 UT 016 锁死(该 UT 在上限为 8 时会 FAIL,已实测验证)。 **[高] INTrainingReduceV2 空 tensor 契约冲突** 图原型声明"支持空 tensor,允许空间规约轴为 0",但唯一注册的 AR_FULL_REDUCE 模板在 r <= 0 时直接 return false(代码注释亦写明"R=0 由后续 REDUCE_EMPTY 承接,本迭代不含")。本迭代确定不支持,故按实现修正 proto 与 README 表述,并补空间轴为 0 的拒绝 UT。 **[中] sub-R 路径 64→32 位收窄保护** Kernel ProcessSubR() 把 numN / numC / numR / perCoreCnt 收窄成 uint32_t,Host 侧此前未证明其落在 32 位内。新增 CheckSubRNarrowable() 显式校验 N / C / R / N*C。同时修正 in_training_reduce_v2_sub_r.h 中 static_cast<uint64_t>(numN_ * numC_)——这是先做 uint32 乘法再转宽,回绕后再 cast 已经晚了,改为两个操作数各自提升 64 位再相乘。 **[合规] SGD 注释命名红线 R8** 源码注释中的团队私下叫法 A2 / A5 改为规范名 ascend910b / ascend950,涉及 sgd_def.cpp、sgd_infershape.cpp、sgd_tiling.cpp、sgd_tiling_key.h。README 产品表中的正式产品名"Atlas A2"属于规范用法,未改动。 **[中] NormalizeBBox golden 不支持 batch=0** golden.py 的 shape_hw.reshape(batch, -1) 在 batch=0 时 torch 无法推导 -1,直接抛 RuntimeError;而 Host Tiling 有 batch == 0 的空 tensor 快速路径、UT 中也有对应用例,golden 与算子支持域不一致。改为直接索引契约已限定为二维 (batch, 3) 的 shape_hw。 **[中] SGD 标量 shape 实现与文档不一致** CheckScalarShape 实际接受任意 numel == 1 的 shape([1,1]、[1,1,1] 均通过,错误文案本身也写的是 "scalar(0D) or have shape size 1"),而 README 写死"只能是 [1] 或 0 维标量"。选择按实现放宽文档而非收紧代码:收紧会拒掉 [1,1] 等已有图中可能存在的写法,属破坏性变更;放宽文档零风险。README 与 proto 表述已同步,并补上 0D / [1,1] / [1,1,1] 的接受 UT。 **[低] ParseShapeByFormat 超 CodeCheck 行数阈值** ParseShapeByFormat() 实测 51 个有效代码行,超本地 CodeCheck 的 50 行阈值。抽出三分支共用的 ParseAndCheckNC(),主函数降至 39 行。 **其他** INTrainingReduceV2 与 NormalizeBBox 的 README 补充"本算子不提供 aclnn 单算子接口,仅支持 GE 图模式调用",减少后续扫描与评审歧义。 ### 三、评估后未采纳的检视建议 - **NormalizeBBox TilingData 的 usedCoreNum 字段**:该字段已注释声明为诊断用途(Host 用于设置 blockDim),且是 20 余条 Host UT 的核心观测点。为节省 8 字节而重写大量断言,代价大于收益。 - **两个 GEIR 样例的 10 行重复代码**:为 10 行样板在 norm/ 与 vfusion/ 之间新建跨目录共享头,维护代价大于收益;样例代码应保持自包含可读。 ## 关联的Issue 关联Issue #4553 ## 测试 - **NormalizeBBox golden 实跑验证**:fp16/fp32 × normal/reversed × batch=0/batch=2 共 8 组组合全部通过;修改前 batch=0 确认抛 RuntimeError: cannot reshape tensor of 0 elements into shape [0, -1]。 - **新增 UT 用例**: - tiling_ar_full_reduce_nchw_r0_empty_rejected_012:空间轴为 0 时 Tiling 明确失败。 - tiling_ar_full_reduce_sub_r_partial_buf_fits_ub_013:原越界 shape 下断言分块参数自洽,且按 Kernel 公式复算的 UB 占用不越界。 - tiling_ar_full_reduce_sub_r_unfittable_rejected_014:任何 rFactor 都放不下的超大 R 被明确拒绝。 - tiling_ar_full_reduce_sub_r_r_over_uint32_rejected_015:R 超 UINT32_MAX 被 Host 拒绝。 - sgd_tiling_accept_scalar_shape_any_single_element:0D / [1,1] / [1,1,1] 标量入参被接受。 - **静态检查**:Ruff 检查(check + format)通过;改动行宽全部 ≤ 120 列(含中文按显示宽度计)。 - **未执行项(需 CI 覆盖)**:本地无 CANN 构建链,C++ 未编译、UT 未实跑;本地 clang-format 仅 21.x 而仓库固定 18.1.8,为避免引入无关重排未执行格式化。 ### 需评审关注 InferDataType 挪到 op_graph 后,原有的 InferDataType UT 用例已下线。原因是 cmake/ut.cmake 中 op_graph UT 模块只链接 graph_plugin_obj,不含 tests/ut/common 的 infershape 公共对象(InferDataTypeTest / InferDataTypeContextFaker 位于仅 OP_HOST_UT 才编译的 _common_obj),用例挪过去无法链接。仓内所有把 InferDataType 放 op_graph 的算子目前均无此层覆盖。若需保住覆盖,需改动全仓共用的 add_op_graph_ut_modules,已在各 UT 文件头注明原因,未在本 PR 中改动构建基础设施。 ## 文档更新 - 更新 norm/in_training_reduce_v2/README.md:补充不支持空 tensor 的约束、无 aclnn 接口说明。 - 更新 optim/sgd/README.md:learning_rate / momentum 的 shape 约束表述与实现对齐。 - 更新 vfusion/normalize_bbox/README.md:补充无 aclnn 接口说明。 - 更新三个算子 proto.h 中的接口注释(空 tensor 支持面、标量 shape 接受面)。 ## 类型标签 <!-- [x] 表示选中 --> - [x] Bug修复 - [ ] 新特性 - [ ] 性能优化 - [x] 文档更新 - [ ] 其他,请描述: ## AI/Agent生成声明 <!-- [x] 表示选中 --> - [x] AI辅助编写 See merge request: cann/ops-nn!8402 | 29 天前 | |
fix(in_training_reduce_v2/sgd/normalize_bbox): 修复原型重定义与InferDataType放置,落地三算子检视整改 Co-authored-by: raoliang_sac<raoliang4@huawei.com> # message auto-generated for no-merge-commit merge: !8402 merge fix/three-ops-review-issue-4553 into master fix(in_training_reduce_v2/sgd/normalize_bbox): 修复原型重定义与InferDataType放置,落地三算子检视整改 Created-by: raoliang_sac Commit-by: raoliang_sac Merged-by: cann-robot Description: ## 描述 本 PR 一次性落地两块内容:Issue #4553 提出的交付件划分/原型重定义问题,以及 INTrainingReduceV2、SGD、NormalizeBBox 三个算子的代码检视意见。 ### 一、Issue #4553 **1. 原型重定义防护** 三个算子的原型都是从 canndev built-in 头( reduce_ops.h / nn_detect_ops.h / nn_training_ops.h,迁移后落在 ops_proto_legacy.h)挪出来的,两侧头文件被同一编译单元同时包含时会 REG_OP 重定义。已给三个 proto.h 的 REG_OP 补上防重定义宏,宏名沿用 ops_proto_legacy.h 自身的 OPS_PROTO_DEF_<OPTYPE> 约定: - OPS_PROTO_DEF_INTRAININGREDUCEV2 - OPS_PROTO_DEF_NORMALIZEBBOX - OPS_PROTO_DEF_SGD **2. InferDataType 交付件划分** InferDataType 仅图场景使用,从 op_host/*_infershape.cpp 挪到 op_graph/*_graph_infer.cpp(IMPL_OP 注册);InferShape 图与单算子共用,保留在 op_host(IMPL_OP_INFERSHAPE)。对齐仓内 norm/bn_infer_grad、norm/lp_norm_update、norm/in_infer_v2 的既有做法。add_graph_plugin_sources() 按 *_graph*.cpp 通配收集,无需改 CMakeLists。 ### 二、检视意见整改 **[高] INTrainingReduceV2 sub-R 路径 UB 越界** DoSubRTiling 只按 usable / 8 固定预留部分和缓存,算出 numChunks 后从不回验总占用。R 特别大时实际会撑破预留。按 Ascend950 实测参数(Ascend950*.ini 的 ub_size=253952,减 RESERVE_FOR_ALIGN=512 得 usable=253440B,VL_FP32=64): | dtype | 越界起点 R | 首版公式的 rFactor / numChunks | 实际申请 | 越界量 | |---|---|---|---|---| | fp32 | 109707265(上一个安全值 109707264) | 27648 / 3969 | **253568B** | 128B | | fp16 | 219668481(上一个安全值 219668480) | 55360 / 3969 | **253824B** | 384B | 越界可能导致 InitBuffer 失败或 Buffer 互相踩踏。 > 早先版本的本节按 UB=245760 举了 [1, 1, 104439809] 这个算例,该值取自仓内 arch35 UT 的 compile_info(activation/、norm/ 下几十个算子沿用的同一份旧模板),**不是 Ascend950 的真实 UB**。按真实 UB 复核,那个 shape 只占 252032B ≤ 253440B,并不越界(实测其 rFactor=27648 就是首版公式值,联合求解根本没触发)。缺陷成立,但触发点在上表,算例已更正。 修法不是简单加校验后拒绝(那会把本来能算的 shape 变成不支持),而是联合求解:用与 Kernel InitSubR() 逐项一致的公式(新增 CalcSubRUbBytes())复核总占用,超了就按实际 partial 占用回缩 rFactor 并重算 numChunks。rFactor 变小时输入双缓冲省下的字节多于 numChunks 增加所需的槽位,故迭代单调收敛(fp32 R=109707265 一轮收敛到 rFactor=27584 / numChunks=3978 / 253056B)。确实无解的超大 R 则明确返回失败,不再下发越界 tiling。 迭代上限 SUB_R_SOLVE_MAX_ITER 取 **32**。最初取 8 并注释为"正常 1~2 轮收敛,只作死循环兜底",但那只对 R 不太大时成立:R 逼近 UB 容量上限时 rFactor 已被 partial 挤得很小,每轮只能再缩一点点,轮数急剧上升。穷举实测收敛所需轮数峰值为 fp32 19 轮 / fp16 24 轮,出现在真正无解点(fp32 R≈2.50e8、fp16 R≈5.01e8,即 0.93GiB 输入)附近。上限取 8 会产生一段**误拒区间**——明明有可行解却报 cannot fit UB 拒绝下发:fp32 R ∈ [237502465, 249892865)、fp16 R ∈ [473001985, 500797441)。失败方向是安全的(拒绝而非下发越界 tiling),但仍是错判,故提到 32 并补 UT 016 锁死(该 UT 在上限为 8 时会 FAIL,已实测验证)。 **[高] INTrainingReduceV2 空 tensor 契约冲突** 图原型声明"支持空 tensor,允许空间规约轴为 0",但唯一注册的 AR_FULL_REDUCE 模板在 r <= 0 时直接 return false(代码注释亦写明"R=0 由后续 REDUCE_EMPTY 承接,本迭代不含")。本迭代确定不支持,故按实现修正 proto 与 README 表述,并补空间轴为 0 的拒绝 UT。 **[中] sub-R 路径 64→32 位收窄保护** Kernel ProcessSubR() 把 numN / numC / numR / perCoreCnt 收窄成 uint32_t,Host 侧此前未证明其落在 32 位内。新增 CheckSubRNarrowable() 显式校验 N / C / R / N*C。同时修正 in_training_reduce_v2_sub_r.h 中 static_cast<uint64_t>(numN_ * numC_)——这是先做 uint32 乘法再转宽,回绕后再 cast 已经晚了,改为两个操作数各自提升 64 位再相乘。 **[合规] SGD 注释命名红线 R8** 源码注释中的团队私下叫法 A2 / A5 改为规范名 ascend910b / ascend950,涉及 sgd_def.cpp、sgd_infershape.cpp、sgd_tiling.cpp、sgd_tiling_key.h。README 产品表中的正式产品名"Atlas A2"属于规范用法,未改动。 **[中] NormalizeBBox golden 不支持 batch=0** golden.py 的 shape_hw.reshape(batch, -1) 在 batch=0 时 torch 无法推导 -1,直接抛 RuntimeError;而 Host Tiling 有 batch == 0 的空 tensor 快速路径、UT 中也有对应用例,golden 与算子支持域不一致。改为直接索引契约已限定为二维 (batch, 3) 的 shape_hw。 **[中] SGD 标量 shape 实现与文档不一致** CheckScalarShape 实际接受任意 numel == 1 的 shape([1,1]、[1,1,1] 均通过,错误文案本身也写的是 "scalar(0D) or have shape size 1"),而 README 写死"只能是 [1] 或 0 维标量"。选择按实现放宽文档而非收紧代码:收紧会拒掉 [1,1] 等已有图中可能存在的写法,属破坏性变更;放宽文档零风险。README 与 proto 表述已同步,并补上 0D / [1,1] / [1,1,1] 的接受 UT。 **[低] ParseShapeByFormat 超 CodeCheck 行数阈值** ParseShapeByFormat() 实测 51 个有效代码行,超本地 CodeCheck 的 50 行阈值。抽出三分支共用的 ParseAndCheckNC(),主函数降至 39 行。 **其他** INTrainingReduceV2 与 NormalizeBBox 的 README 补充"本算子不提供 aclnn 单算子接口,仅支持 GE 图模式调用",减少后续扫描与评审歧义。 ### 三、评估后未采纳的检视建议 - **NormalizeBBox TilingData 的 usedCoreNum 字段**:该字段已注释声明为诊断用途(Host 用于设置 blockDim),且是 20 余条 Host UT 的核心观测点。为节省 8 字节而重写大量断言,代价大于收益。 - **两个 GEIR 样例的 10 行重复代码**:为 10 行样板在 norm/ 与 vfusion/ 之间新建跨目录共享头,维护代价大于收益;样例代码应保持自包含可读。 ## 关联的Issue 关联Issue #4553 ## 测试 - **NormalizeBBox golden 实跑验证**:fp16/fp32 × normal/reversed × batch=0/batch=2 共 8 组组合全部通过;修改前 batch=0 确认抛 RuntimeError: cannot reshape tensor of 0 elements into shape [0, -1]。 - **新增 UT 用例**: - tiling_ar_full_reduce_nchw_r0_empty_rejected_012:空间轴为 0 时 Tiling 明确失败。 - tiling_ar_full_reduce_sub_r_partial_buf_fits_ub_013:原越界 shape 下断言分块参数自洽,且按 Kernel 公式复算的 UB 占用不越界。 - tiling_ar_full_reduce_sub_r_unfittable_rejected_014:任何 rFactor 都放不下的超大 R 被明确拒绝。 - tiling_ar_full_reduce_sub_r_r_over_uint32_rejected_015:R 超 UINT32_MAX 被 Host 拒绝。 - sgd_tiling_accept_scalar_shape_any_single_element:0D / [1,1] / [1,1,1] 标量入参被接受。 - **静态检查**:Ruff 检查(check + format)通过;改动行宽全部 ≤ 120 列(含中文按显示宽度计)。 - **未执行项(需 CI 覆盖)**:本地无 CANN 构建链,C++ 未编译、UT 未实跑;本地 clang-format 仅 21.x 而仓库固定 18.1.8,为避免引入无关重排未执行格式化。 ### 需评审关注 InferDataType 挪到 op_graph 后,原有的 InferDataType UT 用例已下线。原因是 cmake/ut.cmake 中 op_graph UT 模块只链接 graph_plugin_obj,不含 tests/ut/common 的 infershape 公共对象(InferDataTypeTest / InferDataTypeContextFaker 位于仅 OP_HOST_UT 才编译的 _common_obj),用例挪过去无法链接。仓内所有把 InferDataType 放 op_graph 的算子目前均无此层覆盖。若需保住覆盖,需改动全仓共用的 add_op_graph_ut_modules,已在各 UT 文件头注明原因,未在本 PR 中改动构建基础设施。 ## 文档更新 - 更新 norm/in_training_reduce_v2/README.md:补充不支持空 tensor 的约束、无 aclnn 接口说明。 - 更新 optim/sgd/README.md:learning_rate / momentum 的 shape 约束表述与实现对齐。 - 更新 vfusion/normalize_bbox/README.md:补充无 aclnn 接口说明。 - 更新三个算子 proto.h 中的接口注释(空 tensor 支持面、标量 shape 接受面)。 ## 类型标签 <!-- [x] 表示选中 --> - [x] Bug修复 - [ ] 新特性 - [ ] 性能优化 - [x] 文档更新 - [ ] 其他,请描述: ## AI/Agent生成声明 <!-- [x] 表示选中 --> - [x] AI辅助编写 See merge request: cann/ops-nn!8402 | 29 天前 | |
test(golden): 升级三个算子的 TestSpec Co-authored-by: raoliang_sac<raoliang4@huawei.com> # message auto-generated for no-merge-commit merge: !8493 merge fix/three-ops-golden-spec-update into master test(golden): 升级三个算子的 TestSpec Created-by: raoliang_sac Commit-by: raoliang_sac Merged-by: cann-robot Description: ## 描述 本次仅更新以下三个算子的 tests/assets/golden.py: - INTrainingReduceV2 - SGD - NormalizeBBox 主要改动: - 引入 __spec__ TestSpec 注册,kernel 与 GEIR 共用 snake_case 算子键和 Spec。 - 按新 golden 规范补充 Promote 感知的 CPU true-value、独立 Torch third-party composition 以及 cross_check 容差配置。 - 保留兼容旧加载器的 __golden__,并与 TestSpec 复用同一计算核心。 - INTrainingReduceV2 使用 FP64 true-value 处理大归约;SGD 对齐 arch35 DAG 的 Nesterov 运算顺序并保持 IEEE/动量写回语义;NormalizeBBox 对齐 dtype cast 链并保留 batch=0 行为。 - 三个需求均明确不支持 ACLNN 接口,因此不注册 ACLNN 或 e2e 通路。 ## 关联的Issue 关联 Issue #4679:https://gitcode.com/cann/ops-nn/issues/4679 ## 测试 - 三个目标文件执行 pre-commit run --files:通过。 - ops-test-kit TestSpec loader/validator:通过。 - 本地语义回归:NormalizeBBox legacy/batch=0/Promote/Compose、SGD 多 dtype 与六种分支组合及特殊位模式、INTrainingReduceV2 输出契约与高精度消去场景均通过。 - 未执行依赖 NPU/GPU 的完整 TTK cross-check。 ## 文档更新 无,本次仅更新 golden 测试资产。 ## 类型标签 - [ ] Bug修复 - [ ] 新特性 - [ ] 性能优化 - [ ] 文档更新 - [x] 其他,请描述:测试 golden 规范升级 ## AI/Agent生成声明 - [x] AI辅助编写 See merge request: cann/ops-nn!8493 | 29 天前 | |
feat(sgd): 新增SGD优化器算子arch35实现(Ascend 950PR/950DT,GE图模式) Co-authored-by: raoliang_sac<raoliang4@huawei.com> # message auto-generated for no-merge-commit merge: !8155 merge SGD-810 into master feat(sgd): 新增SGD优化器算子arch35实现(Ascend 950PR/950DT,GE图模式) Created-by: raoliang_sac Commit-by: raoliang_sac Merged-by: cann-robot Description: ## 描述 本 PR 为 optim/sgd 新增 **SGD(带动量的随机梯度下降)优化器算子**在 <term>Ascend 950PR/Ascend 950DT</term>(arch35 / DAV_3510 / regbase)上的 Ascend C 实现,共 22 个文件、+2719 行,**纯新增,不修改任何存量文件**(唯一的非新增改动是在 docs/zh/op_list.md 中追加一行算子登记)。 ### 改动原因 optim 目录下已有 fused_sgd,但那是语义不同的另一个算子;本仓此前**不存在** SGD 算子,Ascend 950 上缺失该优化器的 Ascend C 实现。910B/910C 上 SGD 由 CANN 内置的 TBE 实现承载,Ascend 950 需要在本仓补齐。 ### 改动方法 **1. 计算语义** —— 与 910B/910C 基线(canndev nn_training_ops.h 的 REG_OP(SGD) 与 aic-ascend910b-ops-info.ini 的 [SGD] 段)逐字对齐: grad = (wd != 0) ? gradient + parameters * wd : gradient accum_t = accum * m + grad // 无条件计算 accum_t -= (d != 0) ? grad * (1 - stat) * d : 0 parameters -= nesterov ? (grad * lr + accum_t * m * lr) : (accum_t * lr) if (m != 0) { accum = accum_t; stat = 0; } // 回写掩码 - 六输入 parameters / gradient / learning_rate / accum / momentum / stat,图上**只声明一个输出** parameters —— accum 与 stat 靠覆写输入 GM **原地回写**,与 A2 形态一致(A2 的 TBE 实现声明 reuse=('accum','parameters','stat'))。 - momentum == 0(含 -0.0)时 accum / stat **完全不写**、逐位保持输入值;parameters 照常更新。 - 与 PyTorch 的**真实分歧**已在 sgd_proto.h 与 README 显式记录:PyTorch 把 dampening 放在 momentum 块**内部**,本算子(同 A2)放在**外部**,故 m==0 && d>0 && stat==0 时结果差 (1-d) 倍。这是对齐 A2 的有意选择,不是缺陷。 **2. Kernel 实现** —— op_kernel/arch35/,基于 ATVOSS DAG + ElementwiseSch: - sgd_dag.h 用算子级 DAG 描述上述公式;sgd.cpp 在运行期按 momentum == 0 选择两套 DAG(**该掩码不是 TilingKey 维度**:momentum 是 Device 侧 [1] 张量,Host Tiling 阶段拿不到其数值,只能做运行期分支,binary 数量不变)。 - TilingKey 四维模板参数 schMode / useNesterov / hasWeightDecay / hasDampening。非法组合 nesterov==1 && dampening!=0 由 ASCENDC_TPL_SEL 两组 ARGS_SEL 在编译期剪除,**不生成对应 binary**;Host 侧 InferShape 与 Tiling 亦对该组合报 attribute_value_out_of_range。 - 组合数:业务模板 2×2×2 − 2 = 6(K0~K5)→ TPL_SEL 展开 12 → binary 12 × 3 dtype = 36。 **3. Host 实现** —— op_host/: - sgd_def.cpp:dtype FLOAT / FLOAT16 / BFLOAT16,format 仅 ND,DynamicShapeSupportFlag(true) / DynamicRankSupportFlag(true) / PrecisionReduceFlag(false)(对齐 A2 precision_reduce.flag=false);**只 AddConfig("ascend950"),不触碰任何 A2 配置**。format 只做 ND 是跟随本仓 arch35 全族 optim 算子(apply_momentum / apply_ftrl / apply_adam_w_v2 / apply_adamax / apply_centered_rms_prop)的既定约定 —— 这些算子的 def.cpp 一律只声明 ge::FORMAT_ND,无一例声明私有 format。 - sgd_infershape.cpp:校验 nesterov==true 时 dampening 必须为 0、weight_decay >= 0;rank 限 [1,8](rank-0 拒绝);空 tensor(任一轴为 0)拒绝为 null_input;-1(UNKNOWN_DIM)支持、-2(UNKNOWN_RANK)透传。 - arch35/sgd_tiling.cpp:基于 ElewiseBaseTiling。 **4. 交付形态:GE 图模式,不提供 aclnn 接口。** sgd_def.cpp 保持 ACLNNTYPE aclnn_exclude。依据:CANN 9.1.0 的 include/aclnnop/ 无 aclnn_sgd.h、libopapi.so 未导出任何 aclnnSgd* 符号(阴性对照:同库确实导出 aclnnFusedSgd,证明查法有效)、canndev 全仓无 aclnn_sgd 定义 —— SGD 在上游本就是纯图模式算子,本算子与之对齐。故 docs/zh/op_list.md 中 op_api 列填 ✗,docs/zh/op_api_list.md 不涉及。 ## 关联的Issue 关联Issue #4554 —— https://gitcode.com/cann/ops-nn/issues/4554 ## 测试 全部在 Ascend950PR 真机 + CANN 9.1.0 上实测,合计 **1081 项、0 失败**: | 测试腿 | 规模 | 结果 | |---|---|---| | 功能泛化(TTK kernel 模式) | 800 例 | **800/800 PASS**;bin_precision 100.0%、memory_oob_status 全 PASS;6 个 tilingKey × 3 dtype 零缺失 | | 三方精度对比 | 50 例 | **50/50 PASS**;NPU vs CPU-fp64 golden vs A100-GPU 三方比对,ratio_mare 恒为 1.000,最大 MERE 1.553e-03 | | Host UT | 31 例 | **31/31 PASS**(infershape 11 + arch35 tiling 20) | | GE 图模式冒烟 | 2 分支 | examples/test_geir_sgd.cpp,accum / stat 两条回写分支各 **60/60 元素**逐一核对 | | 性能对标 A100 | 200 例 | **200/200 NPU 更快** | **边界覆盖**:空 tensor(1D [0]、2D [0,C]/[N,0]、多轴 [0,0]、高 rank 混合)逐形态拒绝;rank-0 与 rank-9 两端拒绝;-2 动态 rank 透传;inf/nan 按 IEEE 语义无条件传播(判定方式是按 NaN/+Inf/-Inf 三张掩码逐位 array_equal,不套相对误差)。 **性能判据说明(供评审取用)**:按仓内 verification.md §7.1「mean ≥ 1.2 且 min ≥ 0.5」口径为 200/200 PASS;按测试侧 test-bot-dev 的芯片对阈值((950PR, A100) = 0.5,逐例 npu/gpu ≤ 0.5)为 191/200,9 条超阈值全部落在 numel = 4M 档、其中 7 条是 momentum == 0。成因是**访存量不对等而非实现慢**:m == 0 时 torch 不需要 momentum buffer,而本算子的契约要求无条件读 accum;最差的一条 NPU 搬运 1.67 倍数据、耗时反而更短,按达成带宽折算 NPU/GPU = 2.03x。对等访存量下 NPU 仍显著更快。 **本地 CI 复现**:单算子包 @ascend950、910b、910_93、整仓 JIT(默认 soc,2144 targets)、整仓 A5@950(2527 targets)共 5 项编译全过、0 error。其中 910b 一项专门验证 sgd_infershape.cpp 在 A2 上仍参与编译(kernel/tiling 由 On [ascend910b], [sgd] not supported. 排除,infershape 不排除),确认未引入会破坏 A2 的 arch35-only 头文件。 ## 文档更新 - 新增 optim/sgd/README.md(185 行):产品支持情况、计算公式、参数说明、约束与注意事项。产品支持表按 SGD 在各产品形态上的**可得性**填写,并在表下显式声明「本仓的 Ascend C 实现只适配 Ascend 950PR/950DT,其余形态由 CANN 内置 TBE 实现提供」。 - docs/zh/op_list.md 新增一行 optim / sgd 登记,op_kernel ✓ / op_host ✓ / op_api ✗ / op_graph ✓。 - docs/zh/op_api_list.md 不涉及(本算子不提供 aclnn 接口)。 ## 类型标签 <!-- [x] 表示选中 --> - [ ] Bug修复 - [x] 新特性 - [ ] 性能优化 - [ ] 文档更新 - [ ] 其他,请描述: ## AI/Agent生成声明 <!-- [x] 表示选中 --> - [x] AI辅助编写 See merge request: cann/ops-nn!8155 | 1 个月前 | |
docs(sgd): fix non-standard scalar shape description in README Co-authored-by: raoliang_sac<raoliang4@huawei.com> # message auto-generated for no-merge-commit merge: !8512 merge fix/issue-4680-sgd-readme into master docs(sgd): fix non-standard scalar shape description in README Created-by: raoliang_sac Commit-by: raoliang_sac Merged-by: cann-robot Description: ## 描述 修改 optim/sgd/README.md 中 learning_rate 和 momentum 参数的 shape 描述,将不符合文档标准的 "shape须为0维标量,或元素总数为1的tensor(如[1]、[1,1])" 改为标准的 "标量(元素个数为1)",与仓库其他算子的描述风格对齐。 ## 关联的Issue Fixes #4680 ## 测试 仅文档修改,无代码逻辑变更,无需测试。 ## 文档更新 更新了 optim/sgd/README.md 中 learning_rate 和 momentum 参数的 shape 约束描述。 ## 类型标签 - [ ] Bug修复 - [ ] 新特性 - [ ] 性能优化 - [x] 文档更新 - [ ] 其他,请描述: ## AI/Agent生成声明 - [x] AI辅助编写 See merge request: cann/ops-nn!8512 | 29 天前 |
SGD
产品支持情况
| 产品 | 是否支持 |
|---|---|
| Ascend 950PR/Ascend 950DT | √ |
| Atlas A3 训练系列产品/Atlas A3 推理系列产品 | √ |
| Atlas A2 训练系列产品/Atlas A2 推理系列产品 | √ |
| Atlas 200I/500 A2 推理产品 | √ |
| Atlas 推理系列产品 | √ |
| Atlas 训练系列产品 | √ |
上表写的是SGD在各产品形态上的可得性,不是本次交付的架构范围。本仓的Ascend C实现只适配 Ascend 950PR/Ascend 950DT(
sgd_def.cpp中仅AddConfig("ascend950"));其余产品形态上的SGD由CANN内置的TBE实现提供,语义一致,但不由本算子承载。
功能说明
-
算子功能:带动量的随机梯度下降(SGD)优化器更新算子,训练迭代中就地更新一组权重。
-
计算公式:
记ddd为
dampening、wdwdwd为weightDecay、lrlrlr为learningRate[0]、mmm为momentum[0],逐元素计算:步骤一 权重衰减(仅wd≠0wd \neq 0wd=0时执行,否则grad=gradientgrad = gradientgrad=gradient):
grad=gradient+parameters×wdgrad = gradient + parameters \times wd grad=gradient+parameters×wd
步骤二 动量累积(无条件执行):
accumt=accum×m+gradaccum_t = accum \times m + grad accumt=accum×m+grad
步骤三 阻尼修正(仅d≠0d \neq 0d=0时执行)。statstatstat是逐元素的首步标记,取值1表示该元素处于首步、不施加阻尼:
accumt=accumt−grad×(1−stat)×daccum_t = accum_t - grad \times (1 - stat) \times d accumt=accumt−grad×(1−stat)×d
步骤四 权重更新(无条件写出):
parametersout={parameters−(grad×lr+accumt×m×lr),nesterov=trueparameters−accumt×lr,nesterov=falseparameters_{out} = \begin{cases} parameters - (grad \times lr + accum_t \times m \times lr), & nesterov = true \\ parameters - accum_t \times lr, & nesterov = false \end{cases} parametersout={parameters−(grad×lr+accumt×m×lr),parameters−accumt×lr,nesterov=truenesterov=false
步骤五 动量与标记回写,受m≠0m \neq 0m=0掩码控制:
accumout,statout={accumt, 0,m≠0保持输入原值(不回写),m=0accum_{out}, stat_{out} = \begin{cases} accum_t,\ 0, & m \neq 0 \\ \text{保持输入原值(不回写)}, & m = 0 \end{cases} accumout,statout={accumt, 0,保持输入原值(不回写),m=0m=0
-
计算精度:中间计算在float32域进行,结果按就近偶数舍入(round-half-to-even)回目标数据类型。
learningRate与momentum与parameters同数据类型,故float16/bfloat16下这两个标量本身已被量化。
参数说明
| 参数名 | 输入/输出/属性 | 描述 | 数据类型 | 数据格式 |
|---|---|---|---|---|
| parameters | 输入 / 输出(原地) | 待更新的权重。无条件被改写。维度数(rank)须在[1, 8]内;不支持空tensor。 |
FLOAT、FLOAT16、BFLOAT16 | ND |
| gradient | 输入 | 梯度。shape与数据类型须与parameters一致。 | FLOAT、FLOAT16、BFLOAT16 | ND |
| learning_rate | 输入 | 学习率。标量(元素个数为1),数据类型须与parameters一致。 | FLOAT、FLOAT16、BFLOAT16 | ND |
| accum | 输入 / 输出(原地) | 动量累积量。仅momentum ≠ 0时被改写;momentum = 0时逐位保持原值。shape与数据类型须与parameters一致。 | FLOAT、FLOAT16、BFLOAT16 | ND |
| momentum | 输入 | 动量因子。标量(元素个数为1),数据类型须与parameters一致。取值为0(含-0.0)时触发“不回写”语义。 |
FLOAT、FLOAT16、BFLOAT16 | ND |
| stat | 输入 / 输出(原地) | 逐元素首步标记,取值1表示该元素处于首步、不施加阻尼。仅momentum ≠ 0时被改写为0;momentum = 0时逐位保持原值。shape与数据类型须与parameters一致。 | FLOAT、FLOAT16、BFLOAT16 | ND |
| dampening | 属性 | 动量阻尼系数,默认值0.0。nesterov为true时必须为0。 | FLOAT | - |
| weight_decay | 属性 | 权重衰减系数,默认值0.0。必须大于或等于0。 | FLOAT | - |
| nesterov | 属性 | 是否启用Nesterov动量,默认值false。 | BOOL | - |
| parameters | 输出 | 更新后的权重,与输入parameters为同一块内存。shape、数据类型、数据格式均与输入parameters一致。 | FLOAT、FLOAT16、BFLOAT16 | ND |
约束说明
-
三路原地回写,但图上仅声明1个输出:算子实际就地更新
parameters、accum、stat三个张量,而图原型只声明parameters一个输出,accum与stat通过覆写其输入内存返回。调用方必须把这三者都视为可写。此形态与CANN内置实现一致。 -
momentum = 0时的回写语义:
momentum为0(含-0.0)时,accum与stat完全不被写入,逐位保持输入原值(包括NaN的具体位模式、±inf、-0.0);parameters不受该掩码影响,任何momentum取值下都照常计算并写出。momentum为极小非零值(如1e-8、1e-30)时按非零处理,正常回写。 -
⚠️ 从PyTorch迁移的差异告警:本算子
momentum = 0时的“不回写”方向与torch.optim.SGD一致(PyTorch在momentum == 0时整块跳过动量更新)。但 PyTorch的dampening施加在该判断之内,本算子(与CANN内置实现一致)施加在判断之外。因此当momentum = 0且dampening > 0且stat = 0时,parameters的更新量与PyTorch相差(1−dampening)(1 - dampening)(1−dampening)倍。仅当dampening = 0或stat = 1时两者一致。从PyTorch迁移的调用方须感知此差异。 -
rank与空tensor:
parameters的维度数须在[1, 8]内,0维标量被拒绝。不支持空tensor —— 任意一轴或多轴为0均判为非法并返回错误码,不存在“空进空出”语义(accum/stat的原地回写在元素数为0时无定义)。 -
属性取值:
nesterov = true时dampening必须为0;weight_decay必须大于或等于0。违反者返回参数非法错误码。 -
inf/NaN:按IEEE 754语义传播,不做钳制或特判。特别地,
accum含±inf且momentum = 0时,accum×momentumaccum \times momentumaccum×momentum产生的NaN会按IEEE语义传播进parameters。 -
确定性:输出逐位可复现。算子为纯逐元素计算、无跨元素累加,多核切分不改变任一元素的计算顺序。
-
张量连续性:所有输入须为连续张量。本算子不提供aclnn接口,无接口层做转连续/回填,非连续视图由调用方(GE图编译期)负责处理。
调用说明
不提供aclnn单算子接口。 SGD在CANN上游本就是纯图模式算子:CANN 9.1.0的
include/aclnnop/下无aclnn_sgd.h(只有语义不同的aclnn_fused_sgd.h),libopapi.so未导出aclnnSgd*符号,canndev全仓亦无aclnn_sgd定义。 本算子与之对齐,只支持GE图模式下发。
| 调用方式 | 样例代码 | 说明 |
|---|---|---|
| 图模式 | test_geir_sgd.cpp | 通过GE图方式调用SGD算子。 |
参考资源
- 《Ascend C算子开发》:算子开发的概念原理与编程模型。
- 算子列表:本项目全部算子的分类、调用方式与功能说明。
- 算子调用快速入门:算子样例的编译与运行步骤。
- apply_momentum:同族的动量优化器算子,与本算子结构最相近,可对照阅读。
- fused_sgd:语义不同的另一个算子(多TensorList融合、
dampening施加在momentum分支之内、无stat)。名称相近,请勿混用。