已合并
feat(optim): lamb 七算子 arch35 适配,系数输入由标量放宽为可广播 Tensor #10786
zl_hw创建于 7 天前
feat(optim): lamb 七算子 arch35 适配,系数输入由标量放宽为可广播 Tensor #10786
已合并
共 66 个文件变更+2499-1105
| @@ -0,0 +1,15 @@ | |||
| 1 | +# ---------------------------------------------------------------------------- | ||
| 2 | +# Copyright (c) 2026 Huawei Technologies Co., Ltd. | ||
| 3 | +# This program is free software, you can redistribute it and/or modify it under the terms and conditions of | ||
| 4 | +# CANN Open Software License Agreement Version 2.0 (the "License"). | ||
| 5 | +# Please refer to the License for details. You may not use this file except in compliance with the License. | ||
| 6 | +# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, | ||
| 7 | +# INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE. | ||
| 8 | +# See LICENSE in the root of the software repository for the full text of the License. | ||
| 9 | +# ---------------------------------------------------------------------------- | ||
| 10 | + | ||
| 11 | +# 共用目录(非独立算子): 注册后 op_kernel/ 才会被拷入 build/tbe/ascendc/lamb_apply_common, | ||
| 12 | +# 供各 lamb 算子以 "../lamb_apply_common/arch35/xxx.h" 引用(同 index/scatter_reduce_common)。 | ||
| 13 | +set(SUPPORT_COMPUTE_UNIT "ascend950") | ||
| 14 | +set(SUPPORT_TILING_DIR "arch35") | ||
| 15 | +add_modules_sources(HOSTNAME ${OPHOST_NAME} MODE PRIVATE DIR ${CMAKE_CURRENT_SOURCE_DIR} COMPUTE_UNIT ${SUPPORT_COMPUTE_UNIT} TILING_DIR ${SUPPORT_TILING_DIR} DISABLE_IN_OPP TRUE) | ||
Roptim/lamb_apply_common/lamb_apply_check_util.h→optim/lamb_apply_common/op_host/arch35/lamb_apply_check_util.h+45-14
| @@ -10,19 +10,21 @@ | |||
| 10 | 10 | ||
| 11 | /*! | 11 | /*! |
| 12 | * \file lamb_apply_check_util.h | 12 | * \file lamb_apply_check_util.h |
| 13 | - * \brief lamb_apply_optimizer_assign / lamb_apply_weight_assign 复用的输入校验(dtype 一致性、标量非空)。 | 13 | + * \brief lamb_apply_optimizer_assign / lamb_apply_weight_assign 复用的输入校验(dtype 一致性、ref 输出形状)。 |
| 14 | */ | 14 | */ |
| 15 | 15 | ||
| 16 | 16 | ||
| 17 | 17 | ||
| 18 | 18 | ||
| 19 | 19 | ||
| 20 | + | ||
| 20 | 21 | ||
| 22 | + | ||
| 23 | + | ||
| 21 | 24 | ||
| 22 | 25 | ||
| 23 | namespace optiling { | 26 | namespace optiling { |
| 24 | 27 | ||
| 25 | -// 所有输入(1..inputNum-1)、输出(0..outputNum-1)的 dtype 必须与 input0 一致。 | ||
| 26 | inline ge::graphStatus CheckLambApplyDtypeConsistency(gert::TilingContext* context, int32_t inputNum, | 28 | inline ge::graphStatus CheckLambApplyDtypeConsistency(gert::TilingContext* context, int32_t inputNum, |
| 27 | const char* const inputNames[], int32_t outputNum, | 29 | const char* const inputNames[], int32_t outputNum, |
| 28 | const char* const outputNames[]) | 30 | const char* const outputNames[]) |
| @@ -57,20 +59,49 @@ inline ge::graphStatus CheckLambApplyDtypeConsistency(gert::TilingContext* conte | |||
| 57 | return ge::GRAPH_SUCCESS; | 59 | return ge::GRAPH_SUCCESS; |
| 58 | } | 60 | } |
| 59 | 61 | ||
| 60 | -// 标量类输入(系数)为每元素计算所必需, 空Tensor 视为缺失必选值(畸形输入), 不支持。 | 62 | +// ref(原地写回)输出所绑定的输入, 其形状必须恰好等于全部输入广播的结果: |
| 61 | -inline ge::graphStatus CheckLambApplyScalarNotEmpty(gert::TilingContext* context, const int32_t* scalarIdx, | 63 | +// 内核按广播后的完整网格计算并写回该输入的 buffer, 形状不等就会越过它的显存边界。 |
| 62 | - size_t scalarCount, const char* const inputNames[]) | 64 | +inline ge::graphStatus CheckLambApplyBroadcastIntoRef(gert::TilingContext* context, int32_t inputNum, int32_t refIdx, |
| 65 | + const char* refName) | ||
| 63 | { | 66 | { |
| 64 | - for (size_t i = 0; i < scalarCount; i++) { | 67 | + std::vector<const gert::Shape*> inShapes; |
| 65 | - int32_t idx = scalarIdx[i]; | 68 | + inShapes.reserve(static_cast<size_t>(inputNum)); |
| 66 | - auto scalarShape = context->GetInputShape(idx); | 69 | + for (int32_t i = 0; i < inputNum; i++) { |
| 67 | - OP_CHECK_NULL_WITH_CONTEXT(context, scalarShape); | 70 | + auto inShape = context->GetInputShape(i); |
| 68 | - if (scalarShape->GetStorageShape().GetShapeSize() == 0) { | 71 | + OP_CHECK_NULL_WITH_CONTEXT(context, inShape); |
| 69 | - OP_LOGE_FOR_INVALID_SHAPE_WITH_REASON(context->GetNodeName(), inputNames[idx], | 72 | + inShapes.push_back(&Ops::Base::EnsureNotScalar(inShape->GetStorageShape())); |
| 70 | - Ops::Base::ToString(scalarShape->GetStorageShape()).c_str(), | 73 | + } |
| 71 | - "scalar input does not support empty tensor"); | 74 | + // 本地折叠广播, 不依赖 Ops::Base::BroadcastShape —— 该符号由 libops_base.so 导出, 但自定义 |
| 72 | - return ge::GRAPH_FAILED; | 75 | + // vendor 包的 tiling 库不链它, 装包后 so 带未解析符号(ldd -r 可见)。带未解析符号的 so 早期 |
| 76 | + // dlopen 会失败、被退到内置之后加载, 导致本算子的 tiling 模板抢不到注册槽位(实测 tilingKey | ||
| 77 | + // 拿到的是内置 ATVOSS 的值)。改为本地实现后 so 无未解析符号。 | ||
| 78 | + gert::Shape bcShape = *inShapes[0]; | ||
| 79 | + for (size_t i = 1; i < inShapes.size(); i++) { | ||
| 80 | + const gert::Shape& rhs = *inShapes[i]; | ||
| 81 | + size_t lr = bcShape.GetDimNum(); | ||
| 82 | + size_t rr = rhs.GetDimNum(); | ||
| 83 | + size_t rank = (lr > rr) ? lr : rr; | ||
| 84 | + gert::Shape tmpShape; | ||
| 85 | + tmpShape.SetDimNum(rank); | ||
| 86 | + for (size_t d = 0; d < rank; d++) { | ||
| 87 | + // 右对齐: 缺的高位维按 1 处理 | ||
| 88 | + int64_t a = (d + lr >= rank) ? bcShape.GetDim(d + lr - rank) : 1; | ||
| 89 | + int64_t b = (d + rr >= rank) ? rhs.GetDim(d + rr - rank) : 1; | ||
| 90 | + if (a != b && a != 1 && b != 1) { | ||
| 91 | + OP_LOGE(context->GetNodeName(), "input shapes cannot broadcast together"); | ||
| 92 | + return ge::GRAPH_FAILED; | ||
| 93 | + } | ||
| 94 | + // 取"非 1 的那个", 不能取 max —— 空 Tensor 场景下 0 与 1 广播应得 0。 | ||
| 95 | + tmpShape.SetDim(d, (a == 1) ? b : a); | ||
| 73 | } | 96 | } |
| 97 | + bcShape = tmpShape; | ||
| 98 | + } | ||
| 99 | + const gert::Shape& refShape = *inShapes[refIdx]; | ||
| 100 | + if (!(bcShape == refShape)) { | ||
| 101 | + OP_LOGE_FOR_INVALID_SHAPE_WITH_REASON( | ||
| 102 | + context->GetNodeName(), refName, Ops::Base::ToString(refShape).c_str(), | ||
| 103 | + "it is an in-place(ref) output, so the broadcast shape of all inputs must equal it"); | ||
| 104 | + return ge::GRAPH_FAILED; | ||
| 74 | } | 105 | } |
| 75 | return ge::GRAPH_SUCCESS; | 106 | return ge::GRAPH_SUCCESS; |
| 76 | } | 107 | } |
| @@ -0,0 +1,320 @@ | |||
| 1 | +/** | ||
| 2 | + * Copyright (c) 2026 Huawei Technologies Co., Ltd. | ||
| 3 | + * This program is free software, you can redistribute it and/or modify it under the terms and conditions of | ||
| 4 | + * CANN Open Software License Agreement Version 2.0 (the "License"). | ||
| 5 | + * Please refer to the License for details. You may not use this file except in compliance with the License. | ||
| 6 | + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, | ||
| 7 | + * INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE. | ||
| 8 | + * See LICENSE in the root of the software repository for the full text of the License. | ||
| 9 | + */ | ||
| 10 | + | ||
| 11 | +/*! | ||
| 12 | + * \file lamb_brc_tiling_plan.h | ||
| 13 | + * \brief LAMB 族「多入多出 + 任意 numpy 广播」逐元素算子的共用形状规划(host 侧) | ||
| 14 | + * | ||
| 15 | + * 折叠 -> 分类 -> 由 ubSize 反解分片 -> 选平铺/分块。全程无经验阈值: | ||
| 16 | + * 分片长度是 ubSize 除以槽位总字节解出来的; 行块轴是"能装下"这个条件由内向外推出来的, | ||
| 17 | + * 退化到最内层必然成立, 因此不存在因尺寸拒收的分支。 | ||
| 18 | + */ | ||
| 19 | + | ||
| 20 | + | ||
| 21 | + | ||
| 22 | + | ||
| 23 | + | ||
| 24 | + | ||
| 25 | + | ||
| 26 | + | ||
| 27 | + | ||
| 28 | + | ||
| 29 | +namespace optiling { | ||
| 30 | + | ||
| 31 | +// --------------------------------------------------------------------------------------------- | ||
| 32 | +// BuildLambBrcPlan 的分步实现: 读形状 -> 折叠轴 -> 分类输入 -> 解分片 -> 平铺分核 / | ||
| 33 | +// 分块(选行块轴 -> 分核 -> 算步长)。拆开是为了每段都短到能一眼读完、各自可单独复核。 | ||
| 34 | +// --------------------------------------------------------------------------------------------- | ||
| 35 | + | ||
| 36 | +// 读出各输入形状, 右对齐补维, 并校验可广播性。 | ||
| 37 | +template <uint32_t NIN> | ||
| 38 | +ge::graphStatus LambBrcReadShapes(gert::TilingContext* context, size_t rank, const std::vector<int64_t>& rawOut, | ||
| 39 | + std::vector<std::vector<int64_t>>& rawIn) | ||
| 40 | +{ | ||
| 41 | + for (uint32_t i = 0; i < NIN; i++) { | ||
| 42 | + auto sp = context->GetInputShape(i); | ||
| 43 | + OP_CHECK_NULL_WITH_CONTEXT(context, sp); | ||
| 44 | + const gert::Shape& s = sp->GetStorageShape(); | ||
| 45 | + size_t r = s.GetDimNum(); | ||
| 46 | + OP_CHECK_IF(r > rank, OP_LOGE(context->GetNodeName(), "input %u rank %zu exceeds output rank %zu", i, r, rank), | ||
| 47 | + return ge::GRAPH_FAILED); | ||
| 48 | + for (size_t d = 0; d < r; d++) { // 右对齐补维 | ||
| 49 | + rawIn[i][rank - r + d] = s.GetDim(d); | ||
| 50 | + } | ||
| 51 | + for (size_t d = 0; d < rank; d++) { | ||
| 52 | + OP_CHECK_IF(rawIn[i][d] != 1 && rawIn[i][d] != rawOut[d], | ||
| 53 | + OP_LOGE(context->GetNodeName(), "input %u dim %zu (%ld) not broadcastable to %ld", i, d, | ||
| 54 | + rawIn[i][d], rawOut[d]), | ||
| 55 | + return ge::GRAPH_FAILED); | ||
| 56 | + } | ||
| 57 | + } | ||
| 58 | + return ge::GRAPH_SUCCESS; | ||
| 59 | +} | ||
| 60 | + | ||
| 61 | +// 折叠: 丢掉输出为 1 的轴, 再合并广播状态一致的相邻轴。 | ||
| 62 | +template <uint32_t NIN> | ||
| 63 | +void LambBrcCollapse(const std::vector<int64_t>& rawOut, const std::vector<std::vector<int64_t>>& rawIn, size_t rank, | ||
| 64 | + std::vector<int64_t>& co, std::vector<std::vector<int64_t>>& ci) | ||
| 65 | +{ | ||
| 66 | + // 折叠: 丢掉输出为 1 的轴, 再合并广播状态一致的相邻轴。 | ||
| 67 | + for (size_t d = 0; d < rank; d++) { | ||
| 68 | + if (rawOut[d] == 1) { | ||
| 69 | + continue; | ||
| 70 | + } | ||
| 71 | + bool merge = !co.empty(); | ||
| 72 | + for (uint32_t i = 0; i < NIN && merge; i++) { | ||
| 73 | + merge = ((ci[i].back() == 1) == (rawIn[i][d] == 1)); | ||
| 74 | + } | ||
| 75 | + if (merge) { | ||
| 76 | + co.back() *= rawOut[d]; | ||
| 77 | + for (uint32_t i = 0; i < NIN; i++) { | ||
| 78 | + ci[i].back() *= rawIn[i][d]; | ||
| 79 | + } | ||
| 80 | + } else { | ||
| 81 | + co.push_back(rawOut[d]); | ||
| 82 | + for (uint32_t i = 0; i < NIN; i++) { | ||
| 83 | + ci[i].push_back(rawIn[i][d]); | ||
| 84 | + } | ||
| 85 | + } | ||
| 86 | + } | ||
| 87 | + if (co.empty()) { // 全 1 形状 | ||
| 88 | + co.push_back(1); | ||
| 89 | + for (uint32_t i = 0; i < NIN; i++) { | ||
| 90 | + ci[i].push_back(1); | ||
| 91 | + } | ||
| 92 | + } | ||
| 93 | +} | ||
| 94 | + | ||
| 95 | +// 给每个输入定性: 标量 / 与输出同形 / 需要广播。返回是否存在需要广播的输入。 | ||
| 96 | +template <uint32_t NIN, uint32_t NOUT> | ||
| 97 | +bool LambBrcClassify(const std::vector<int64_t>& co, const std::vector<std::vector<int64_t>>& ci, uint32_t rc, | ||
| 98 | + LambBrcTilingData<NIN, NOUT>& td) | ||
| 99 | +{ | ||
| 100 | + bool anyBrc = false; | ||
| 101 | + for (uint32_t i = 0; i < NIN; i++) { | ||
| 102 | + uint64_t numel = 1; | ||
| 103 | + bool brc = false; | ||
| 104 | + for (uint32_t d = 0; d < rc; d++) { | ||
| 105 | + td.inShape[i * LAMB_BRC_MAX_DIM + d] = static_cast<uint32_t>(ci[i][d]); | ||
| 106 | + numel *= static_cast<uint64_t>(ci[i][d]); | ||
| 107 | + if (ci[i][d] != co[d]) { | ||
| 108 | + brc = true; | ||
| 109 | + } | ||
| 110 | + } | ||
| 111 | + if (numel == 1) { | ||
| 112 | + td.inKind[i] = LAMB_BRC_KIND_SCALAR; | ||
| 113 | + } else if (!brc) { | ||
| 114 | + td.inKind[i] = LAMB_BRC_KIND_SAME; | ||
| 115 | + } else { | ||
| 116 | + td.inKind[i] = LAMB_BRC_KIND_BRC; | ||
| 117 | + anyBrc = true; | ||
| 118 | + } | ||
| 119 | + } | ||
| 120 | + return anyBrc; | ||
| 121 | +} | ||
| 122 | + | ||
| 123 | +// 由 ubSize 反解分片长度。 | ||
| 124 | +template <uint32_t NIN, uint32_t NOUT> | ||
| 125 | +uint32_t LambBrcSolveTileLen(uint64_t ubSize, uint32_t dtSize) | ||
| 126 | +{ | ||
| 127 | + // 分片长度: UB 总字节 / 槽位数 / 元素字节, 向下对齐到 256B(保证每槽首址对齐)。 | ||
| 128 | + constexpr uint32_t slotNum = LambBrcTilingData<NIN, NOUT>::SlotNum(); | ||
| 129 | + uint32_t alignElems = LAMB_BRC_VREG_BYTES / dtSize; | ||
| 130 | + // 从 ubSize 反解, 再向下对齐到一个向量寄存器; 解不出整数倍时兜底取一个对齐单位 —— | ||
| 131 | + // 不做"装不下就拒收"的判断, 装不下就继续切, 这是切分问题不是能力问题。 | ||
| 132 | + uint64_t bytesPerElem = static_cast<uint64_t>(slotNum) * dtSize + 4U * sizeof(float); | ||
| 133 | + uint32_t tileLen = static_cast<uint32_t>((ubSize / bytesPerElem / alignElems) * alignElems); | ||
| 134 | + if (tileLen == 0) { | ||
| 135 | + tileLen = alignElems; | ||
| 136 | + } | ||
| 137 | + return tileLen; | ||
| 138 | +} | ||
| 139 | + | ||
| 140 | +// 无广播输入: 按元素平铺分核。 | ||
| 141 | +template <uint32_t NIN, uint32_t NOUT> | ||
| 142 | +void LambBrcPlanFlat(uint64_t coreNum, uint32_t dtSize, uint64_t total, uint32_t tileLen, | ||
| 143 | + LambBrcTilingData<NIN, NOUT>& td) | ||
| 144 | +{ | ||
| 145 | + // 分核粒度必须让每个核的输出起始落在 32B 边界上: 否则相邻核的 DataCopyPad 会写到 | ||
| 146 | + // 同一个 32 字节块里互相覆盖。 | ||
| 147 | + const uint64_t gmAlign = LAMB_BRC_GM_BLOCK_BYTES / dtSize; | ||
| 148 | + | ||
| 149 | + td.tilingKey = LAMB_BRC_KEY_FLAT; | ||
| 150 | + uint64_t tiles = (total + tileLen - 1) / tileLen; | ||
| 151 | + uint64_t cores = std::min<uint64_t>(coreNum, std::max<uint64_t>(tiles, 1)); | ||
| 152 | + uint64_t perCore = (total + cores - 1) / cores; | ||
| 153 | + perCore = ((perCore + gmAlign - 1) / gmAlign) * gmAlign; // 对齐到 32B | ||
| 154 | + td.perCoreElems = perCore; | ||
| 155 | + td.usedCoreNum = static_cast<uint32_t>((total + perCore - 1) / perCore); | ||
| 156 | +} | ||
| 157 | + | ||
| 158 | +// 分块: 选行块轴(splitAxis)与块长(blockLen)。 | ||
| 159 | +template <uint32_t NIN, uint32_t NOUT> | ||
| 160 | +void LambBrcChooseSplit(const std::vector<int64_t>& co, const std::vector<std::vector<int64_t>>& ci, uint32_t rc, | ||
| 161 | + uint32_t tileLen, LambBrcTilingData<NIN, NOUT>& td, uint32_t& splitAxis, uint64_t& blockLen) | ||
| 162 | +{ | ||
| 163 | + splitAxis = rc; | ||
| 164 | + blockLen = 1; | ||
| 165 | + // 块内只允许"尾轴广播"这一种形态(那条 2D Broadcast 路径是验证过的)。任何非尾轴的广播轴 | ||
| 166 | + // 都把 splitAxis 推到它之后, 使其变成行维、由 effStride=0 处理 —— 否则块内要在任意元素偏移 | ||
| 167 | + // 上做 UB->UB 复制, 而向量指令对起始地址有对齐要求。 | ||
| 168 | + uint32_t splitFloor = 0; | ||
| 169 | + for (uint32_t i = 0; i < NIN; i++) { | ||
| 170 | + if (td.inKind[i] != LAMB_BRC_KIND_BRC) { | ||
| 171 | + continue; | ||
| 172 | + } | ||
| 173 | + for (uint32_t d = 0; d + 1 < rc; d++) { // 只看非尾轴 | ||
| 174 | + if (ci[i][d] == 1 && co[d] != 1 && d + 1 > splitFloor) { | ||
| 175 | + splitFloor = d + 1; | ||
| 176 | + } | ||
| 177 | + } | ||
| 178 | + } | ||
| 179 | + | ||
| 180 | + // 分块: 由内向外找最小的前导行维个数(不低于 splitFloor), 使一个行块装得下分片。 | ||
| 181 | + for (int32_t k = static_cast<int32_t>(rc); k >= static_cast<int32_t>(splitFloor); k--) { | ||
| 182 | + uint64_t len = 1; | ||
| 183 | + for (uint32_t d = static_cast<uint32_t>(k); d < rc; d++) { | ||
| 184 | + len *= static_cast<uint64_t>(co[d]); | ||
| 185 | + } | ||
| 186 | + if (len > tileLen) { | ||
| 187 | + break; | ||
| 188 | + } | ||
| 189 | + splitAxis = static_cast<uint32_t>(k); | ||
| 190 | + blockLen = len; | ||
| 191 | + } | ||
| 192 | + // 最内轴自己就装不下一个分片时(blockLen 被逼到 1), 不新增轴, 而是在最内轴内部切段: | ||
| 193 | + // 行索引的最低位变成"段号", 块长取最内轴不超过分片长度的最大因子。轴数不变, 不设上限。 | ||
| 194 | + if (blockLen == 1 && rc > 0 && static_cast<uint64_t>(co[rc - 1]) > 1) { | ||
| 195 | + int64_t d = co[rc - 1]; | ||
| 196 | + int64_t chunk = 1; | ||
| 197 | + for (int64_t c = static_cast<int64_t>(tileLen); c >= 1; c--) { | ||
| 198 | + if (d % c == 0) { | ||
| 199 | + chunk = c; | ||
| 200 | + break; | ||
| 201 | + } | ||
| 202 | + } | ||
| 203 | + // 最内轴为大质数时取不到 >1 的因子, 此时保持逐元素: 结果仍正确(行批处理保证搬出连续), | ||
| 204 | + // 只是 DMA 次数多, 属性能退化, 不拒收。 | ||
| 205 | + if (chunk > 1) { | ||
| 206 | + splitAxis = rc - 1; | ||
| 207 | + blockLen = static_cast<uint64_t>(chunk); | ||
| 208 | + td.innerChunk = static_cast<uint32_t>(chunk); | ||
| 209 | + td.innerChunkCnt = static_cast<uint32_t>(d / chunk); | ||
| 210 | + } | ||
| 211 | + } | ||
| 212 | +} | ||
| 213 | + | ||
| 214 | +// 分块模式的分核: 行批大小与每核行数。 | ||
| 215 | +template <uint32_t NIN, uint32_t NOUT> | ||
| 216 | +void LambBrcCoreSplit(uint64_t coreNum, uint32_t dtSize, uint64_t total, uint32_t tileLen, uint32_t splitAxis, | ||
| 217 | + uint64_t blockLen, LambBrcTilingData<NIN, NOUT>& td) | ||
| 218 | +{ | ||
| 219 | + const uint64_t gmAlign = LAMB_BRC_GM_BLOCK_BYTES / dtSize; | ||
| 220 | + td.tilingKey = LAMB_BRC_KEY_BLOCK; | ||
| 221 | + td.splitAxis = splitAxis; | ||
| 222 | + td.blockLen = static_cast<uint32_t>(blockLen); | ||
| 223 | + // 一个分片内批量攒多行再一次性搬出: 逐行搬出会退化成 4/2 字节的 DataCopyPad, | ||
| 224 | + // 同一个 32 字节块内的多次写互相覆盖。 | ||
| 225 | + // 行批处理要求每行在 UB 里的起始偏移 j*blockLen 落在 32 字节边界上, 否则 DataCopyPad 的 | ||
| 226 | + // UB 侧地址不对齐。blockLen 不是 32 字节整数倍时退回每次一行(偏移恒为 0)。 | ||
| 227 | + // 跨核不冲突由 rowsPerCore 的 32 字节对齐保证, 与本项无关。 | ||
| 228 | + bool blockAligned = ((blockLen * dtSize) % LAMB_BRC_GM_BLOCK_BYTES) == 0; | ||
| 229 | + td.rowsPerTile = blockAligned ? static_cast<uint32_t>(std::max<uint64_t>(tileLen / blockLen, 1)) : 1U; | ||
| 230 | + td.totalRows = total / blockLen; | ||
| 231 | + // 每核行数取到 rowsPerGroup 的整数倍, 使 rowsPerCore*blockLen 是 32B 的整数倍。 | ||
| 232 | + uint64_t g = blockLen % gmAlign; | ||
| 233 | + uint64_t a = gmAlign; | ||
| 234 | + while (g != 0) { | ||
| 235 | + uint64_t t2 = a % g; | ||
| 236 | + a = g; | ||
| 237 | + g = t2; | ||
| 238 | + } // gcd(blockLen, gmAlign) | ||
| 239 | + uint64_t rowsPerGroup = gmAlign / a; | ||
| 240 | + uint64_t cores = std::min<uint64_t>(coreNum, std::max<uint64_t>(td.totalRows, 1)); | ||
| 241 | + uint64_t rowsPerCore = (td.totalRows + cores - 1) / cores; | ||
| 242 | + rowsPerCore = ((rowsPerCore + rowsPerGroup - 1) / rowsPerGroup) * rowsPerGroup; | ||
| 243 | + td.rowsPerCore = rowsPerCore; | ||
| 244 | + td.usedCoreNum = static_cast<uint32_t>((td.totalRows + rowsPerCore - 1) / rowsPerCore); | ||
| 245 | +} | ||
| 246 | + | ||
| 247 | +// 每个输入的块内长度与行维步长(广播轴步长为 0)。 | ||
| 248 | +template <uint32_t NIN, uint32_t NOUT> | ||
| 249 | +void LambBrcStrides(const std::vector<std::vector<int64_t>>& ci, uint32_t rc, uint32_t splitAxis, | ||
| 250 | + LambBrcTilingData<NIN, NOUT>& td) | ||
| 251 | +{ | ||
| 252 | + for (uint32_t i = 0; i < NIN; i++) { | ||
| 253 | + uint64_t srcBlock = 1; | ||
| 254 | + for (uint32_t d = splitAxis; d < rc; d++) { | ||
| 255 | + srcBlock *= static_cast<uint64_t>(ci[i][d]); | ||
| 256 | + } | ||
| 257 | + if (td.innerChunkCnt > 1) { | ||
| 258 | + // 块只覆盖最内轴的一段: 该输入在这一轴上要么是 1(广播), 要么与输出等长。 | ||
| 259 | + srcBlock = (ci[i][rc - 1] == 1) ? 1 : static_cast<uint64_t>(td.innerChunk); | ||
| 260 | + } | ||
| 261 | + td.srcBlockLen[i] = srcBlock; | ||
| 262 | + for (uint32_t j = 0; j < splitAxis; j++) { | ||
| 263 | + if (ci[i][j] == 1) { | ||
| 264 | + td.effStride[i * LAMB_BRC_MAX_DIM + j] = 0; // 广播轴不推进源地址 | ||
| 265 | + continue; | ||
| 266 | + } | ||
| 267 | + uint64_t st = 1; | ||
| 268 | + for (uint32_t d = j + 1; d < rc; d++) { | ||
| 269 | + st *= static_cast<uint64_t>(ci[i][d]); | ||
| 270 | + } | ||
| 271 | + td.effStride[i * LAMB_BRC_MAX_DIM + j] = st; | ||
| 272 | + } | ||
| 273 | + } | ||
| 274 | +} | ||
| 275 | + | ||
| 276 | +template <uint32_t NIN, uint32_t NOUT> | ||
| 277 | +ge::graphStatus BuildLambBrcPlan(gert::TilingContext* context, uint64_t coreNum, uint64_t ubSize, uint32_t dtSize, | ||
| 278 | + LambBrcTilingData<NIN, NOUT>& td) | ||
| 279 | +{ | ||
| 280 | + auto outShapePtr = context->GetOutputShape(0); | ||
| 281 | + OP_CHECK_NULL_WITH_CONTEXT(context, outShapePtr); | ||
| 282 | + const gert::Shape& outShape = outShapePtr->GetStorageShape(); | ||
| 283 | + size_t rank = outShape.GetDimNum(); | ||
| 284 | + std::vector<int64_t> rawOut(rank); | ||
| 285 | + for (size_t d = 0; d < rank; d++) { | ||
| 286 | + rawOut[d] = outShape.GetDim(d); | ||
| 287 | + } | ||
| 288 | + std::vector<std::vector<int64_t>> rawIn(NIN, std::vector<int64_t>(rank, 1)); | ||
| 289 | + OP_CHECK_IF(LambBrcReadShapes<NIN>(context, rank, rawOut, rawIn) != ge::GRAPH_SUCCESS, | ||
| 290 | + OP_LOGE(context->GetNodeName(), "read input shapes failed"), return ge::GRAPH_FAILED); | ||
| 291 | + | ||
| 292 | + std::vector<int64_t> co; | ||
| 293 | + std::vector<std::vector<int64_t>> ci(NIN); | ||
| 294 | + LambBrcCollapse<NIN>(rawOut, rawIn, rank, co, ci); | ||
| 295 | + uint32_t rc = static_cast<uint32_t>(co.size()); | ||
| 296 | + td.collapsedRank = rc; | ||
| 297 | + uint64_t total = 1; | ||
| 298 | + for (uint32_t d = 0; d < rc; d++) { | ||
| 299 | + td.outShape[d] = static_cast<uint32_t>(co[d]); | ||
| 300 | + total *= static_cast<uint64_t>(co[d]); | ||
| 301 | + } | ||
| 302 | + td.totalNum = total; | ||
| 303 | + | ||
| 304 | + bool anyBrc = LambBrcClassify<NIN, NOUT>(co, ci, rc, td); | ||
| 305 | + uint32_t tileLen = LambBrcSolveTileLen<NIN, NOUT>(ubSize, dtSize); | ||
| 306 | + td.tileLen = tileLen; | ||
| 307 | + if (!anyBrc) { | ||
| 308 | + LambBrcPlanFlat<NIN, NOUT>(coreNum, dtSize, total, tileLen, td); | ||
| 309 | + return ge::GRAPH_SUCCESS; | ||
| 310 | + } | ||
| 311 | + | ||
| 312 | + uint32_t splitAxis = rc; | ||
| 313 | + uint64_t blockLen = 1; | ||
| 314 | + LambBrcChooseSplit<NIN, NOUT>(co, ci, rc, tileLen, td, splitAxis, blockLen); | ||
| 315 | + LambBrcCoreSplit<NIN, NOUT>(coreNum, dtSize, total, tileLen, splitAxis, blockLen, td); | ||
| 316 | + LambBrcStrides<NIN, NOUT>(ci, rc, splitAxis, td); | ||
| 317 | + return ge::GRAPH_SUCCESS; | ||
| 318 | +} | ||
| 319 | +} // namespace optiling | ||
| 320 | + | ||
| @@ -0,0 +1,346 @@ | |||
| 1 | +/** | ||
| 2 | + * Copyright (c) 2026 Huawei Technologies Co., Ltd. | ||
| 3 | + * This program is free software, you can redistribute it and/or modify it under the terms and conditions of | ||
| 4 | + * CANN Open Software License Agreement Version 2.0 (the "License"). | ||
| 5 | + * Please refer to the License for details. You may not use this file except in compliance with the License. | ||
| 6 | + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, | ||
| 7 | + * INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE. | ||
| 8 | + * See LICENSE in the root of the software repository for the full text of the License. | ||
| 9 | + */ | ||
| 10 | + | ||
| 11 | +/*! | ||
| 12 | + * \file lamb_brc_kernel.h | ||
| 13 | + * \brief LAMB 族「多入多出 + 任意 numpy 广播」逐元素算子的共用搬运/分核/广播骨架 | ||
| 14 | + * | ||
| 15 | + * 为什么不走 ATVOSS: 本族算子入参 12~13 个, DAGSch 在 | ||
| 16 | + * (mte2Count + mte3Count) * BUF_PING_PONG + tempBufCount <= 32 上编不过; | ||
| 17 | + * 且 ATVOSS 的 Vec::Brc 未实现, UB 广播档不可用, 只剩 NDDMA 档, 铺不平 | ||
| 18 | + * 「尾轴 1->n」与「低秩补维」两类广播。 | ||
| 19 | + * | ||
| 20 | + * 本骨架: | ||
| 21 | + * - 广播在搬入阶段铺平, 计算段只看逐元素数据; 铺平只用平台已验证的 2D | ||
| 22 | + * AscendC::Broadcast 由内向外迭代展开, 因此支持任意 numpy 广播形态。 | ||
| 23 | + * - 分片长度/行块轴由 host 从 ubSize 反解下发, 内核不做"算完再跟 UB 比大小"的判断, | ||
| 24 | + * 也没有经验阈值; 行块轴退化到最内层必然成立, 故不存在因尺寸拒收的分支。 | ||
| 25 | + * - 计算段由算子自己以 Compute::Run(in[], out[], count) 提供。 | ||
| 26 | + */ | ||
| 27 | + | ||
| 28 | + | ||
| 29 | + | ||
| 30 | + | ||
| 31 | + | ||
| 32 | + | ||
| 33 | + | ||
| 34 | + | ||
| 35 | +namespace LambBrc { | ||
| 36 | +using namespace AscendC; | ||
| 37 | + | ||
| 38 | +static constexpr Reg::CastTrait LAMB_CAST_UP = {Reg::RegLayout::ZERO, Reg::SatMode::UNKNOWN, | ||
| 39 | + Reg::MaskMergeMode::ZEROING, RoundMode::UNKNOWN}; | ||
| 40 | +static constexpr Reg::CastTrait LAMB_CAST_DOWN = {Reg::RegLayout::ZERO, Reg::SatMode::NO_SAT, | ||
| 41 | + Reg::MaskMergeMode::ZEROING, RoundMode::CAST_RINT}; | ||
| 42 | + | ||
| 43 | +constexpr uint32_t LAMB_VF_LEN = Ops::Base::GetVRegSize() / sizeof(float); | ||
| 44 | + | ||
| 45 | +// host 侧无法调用 __aicore__ 的平台接口, 只能在共用头里写常量; 这里回连校验, 防止两侧走偏。 | ||
| 46 | +static_assert(Ops::Base::GetUbBlockSize() == LAMB_BRC_GM_BLOCK_BYTES, "UB block size mismatch"); | ||
| 47 | +static_assert(Ops::Base::GetVRegSize() == LAMB_BRC_VREG_BYTES, "vector register size mismatch"); | ||
| 48 | + | ||
| 49 | +// 精度档: 默认的 DivAlgo/SqrtAlgo::INTRINSIC 是硬件近似实现, 且按 FTZ(非规格数归零)处理 —— | ||
| 50 | +// 非规格中间量被冲刷成 ±0 后, sqrt(-tiny) 应得的 NaN 会退化成 sqrt(-0)=-0, 再除即成 inf, | ||
| 51 | +// 与 CPU 参照定性不符。取仓内通行的 0ULP + FTZ_FALSE 档(见 index/scatter_reduce_common、 | ||
| 52 | +// loss/soft_margin_loss_grad 等), 由库完成非规格数的完整处理。 | ||
| 53 | +static constexpr AscendC::Reg::DivSpecificMode LAMB_PRECISE_DIV = {AscendC::Reg::MaskMergeMode::ZEROING, false, | ||
| 54 | + AscendC::DivAlgo::PRECISION_0ULP_FTZ_FALSE}; | ||
| 55 | +static constexpr AscendC::Reg::SqrtSpecificMode LAMB_PRECISE_SQRT = {AscendC::Reg::MaskMergeMode::ZEROING, false, | ||
| 56 | + AscendC::SqrtAlgo::PRECISION_0ULP_FTZ_FALSE}; | ||
| 57 | +static constexpr AscendC::Reg::ExpSpecificMode LAMB_PRECISE_EXP = {AscendC::Reg::MaskMergeMode::ZEROING, | ||
| 58 | + AscendC::ExpAlgo::PRECISION_1ULP_FTZ_FALSE}; | ||
| 59 | +static constexpr AscendC::Reg::LnSpecificMode LAMB_PRECISE_LN = {AscendC::Reg::MaskMergeMode::ZEROING, | ||
| 60 | + AscendC::LnAlgo::PRECISION_1ULP_FTZ_FALSE}; | ||
| 61 | +// FTZ_TRUE 档: exp 的下溢结果按冲刷到 ±0 处理。ln(b)*steps 小于 fp32 exp 的下溢边界时, | ||
| 62 | +// b^steps 的数学真值本就小于最小非规格数, 冲刷到 0 即是正确结果(b_corr=1)。 | ||
| 63 | +static constexpr AscendC::Reg::ExpSpecificMode LAMB_PRECISE_EXP_FTZT = {AscendC::Reg::MaskMergeMode::ZEROING, | ||
| 64 | + AscendC::ExpAlgo::PRECISION_1ULP_FTZ_TRUE}; | ||
| 65 | +static constexpr AscendC::Reg::LnSpecificMode LAMB_PRECISE_LN_FTZT = {AscendC::Reg::MaskMergeMode::ZEROING, | ||
| 66 | + AscendC::LnAlgo::PRECISION_1ULP_FTZ_TRUE}; | ||
| 67 | + | ||
| 68 | +// corr = 1 - exp(x)。x -> 0 时 exp(x) -> 1, 直写 1-exp(x) 是灾难性抵消(fp32 在 1.0 附近 | ||
| 69 | +// 间距 6e-8), 与 exp 实现多准无关。故 |x| < 0.1 走 5 项 Horner 多项式直算 expm1(x) 绕开该减法, | ||
| 70 | +// 否则直算 exp(x)-1。同式同阈值见 activation/elu。 | ||
| 71 | +__aicore__ inline void OneMinusExp(Reg::RegTensor<float>& dst, Reg::RegTensor<float>& x, Reg::RegTensor<float>& t1, | ||
| 72 | + Reg::RegTensor<float>& t2, Reg::MaskReg& cmp, Reg::MaskReg& preg) | ||
| 73 | +{ | ||
| 74 | + Reg::Muls(t1, x, 1.0f / 120.0f, preg); | ||
| 75 | + Reg::Adds(t1, t1, 1.0f / 24.0f, preg); | ||
| 76 | + Reg::Mul(t1, t1, x, preg); | ||
| 77 | + Reg::Adds(t1, t1, 1.0f / 6.0f, preg); | ||
| 78 | + Reg::Mul(t1, t1, x, preg); | ||
| 79 | + Reg::Adds(t1, t1, 0.5f, preg); | ||
| 80 | + Reg::Mul(t1, t1, x, preg); | ||
| 81 | + Reg::Adds(t1, t1, 1.0f, preg); | ||
| 82 | + Reg::Mul(t1, t1, x, preg); // t1 = expm1(x), 多项式支 | ||
| 83 | + Reg::Exp<float, &LAMB_PRECISE_EXP_FTZT>(t2, x, preg); | ||
| 84 | + Reg::Adds(t2, t2, -1.0f, preg); // t2 = expm1(x), exp 支 | ||
| 85 | + Reg::Abs(dst, x, preg); // x 之后不再用, dst 可与 x 同寄存器 | ||
| 86 | + Reg::Compares<float, CMPMODE::LT>(cmp, dst, 0.1f, preg); | ||
| 87 | + Reg::Select<float>(dst, t1, t2, cmp); | ||
| 88 | + Reg::Muls(dst, dst, -1.0f, preg); // 1 - exp(x) = -expm1(x) | ||
| 89 | +} | ||
| 90 | + | ||
| 91 | +template <typename T> | ||
| 92 | +__aicore__ inline void Load(__local_mem__ T* base, Reg::RegTensor<float>& dst, Reg::MaskReg& preg, uint32_t offset) | ||
| 93 | +{ | ||
| 94 | + if constexpr (std::is_same_v<T, float>) { | ||
| 95 | + Reg::LoadAlign<float, Reg::LoadDist::DIST_NORM>(dst, base + offset); | ||
| 96 | + } else { | ||
| 97 | + Reg::RegTensor<T> narrow; | ||
| 98 | + Reg::LoadAlign<T, Reg::LoadDist::DIST_UNPACK_B16>(narrow, base + offset); | ||
| 99 | + Reg::Cast<float, T, LAMB_CAST_UP>(dst, narrow, preg); | ||
| 100 | + } | ||
| 101 | +} | ||
| 102 | + | ||
| 103 | +template <typename T> | ||
| 104 | +__aicore__ inline void Store(__local_mem__ T* base, Reg::RegTensor<float>& src, Reg::MaskReg& preg, uint32_t offset) | ||
| 105 | +{ | ||
| 106 | + if constexpr (std::is_same_v<T, float>) { | ||
| 107 | + Reg::StoreAlign<float, Reg::StoreDist::DIST_NORM>(base + offset, src, preg); | ||
| 108 | + } else { | ||
| 109 | + Reg::RegTensor<T> narrow; | ||
| 110 | + Reg::Cast<T, float, LAMB_CAST_DOWN>(narrow, src, preg); | ||
| 111 | + Reg::StoreAlign<T, Reg::StoreDist::DIST_PACK_B32>(base + offset, narrow, preg); | ||
| 112 | + } | ||
| 113 | +} | ||
| 114 | + | ||
| 115 | +template <typename T, uint32_t NIN, uint32_t NOUT, class Compute> | ||
| 116 | +class BrcElementwiseKernel { | ||
| 117 | +public: | ||
| 118 | + using Tiling = LambBrcTilingData<NIN, NOUT>; | ||
| 119 | + static constexpr uint32_t SLOT_NUM = NIN + NOUT + 1; | ||
| 120 | + static constexpr uint32_t TMP_SLOT = SLOT_NUM - 1; | ||
| 121 | + | ||
| 122 | + __aicore__ inline BrcElementwiseKernel() {} | ||
| 123 | + | ||
| 124 | + __aicore__ inline void Init(GM_ADDR (&inAddr)[NIN], GM_ADDR (&outAddr)[NOUT], const Tiling* tiling, TPipe* pipe) | ||
| 125 | + { | ||
| 126 | + tiling_ = tiling; | ||
| 127 | + blockIdx_ = GetBlockIdx(); | ||
| 128 | + for (uint32_t i = 0; i < NIN; i++) { | ||
| 129 | + inGm_[i].SetGlobalBuffer((__gm__ T*)inAddr[i]); | ||
| 130 | + } | ||
| 131 | + for (uint32_t o = 0; o < NOUT; o++) { | ||
| 132 | + outGm_[o].SetGlobalBuffer((__gm__ T*)outAddr[o]); | ||
| 133 | + } | ||
| 134 | + // 槽位 + fp32 暂存区(4*tileLen): 计算段过长时可拆成两个 __VEC_SCOPE__, | ||
| 135 | + // 中间量用 fp32 暂存传递, 与单段版数值完全一致(不经 T 的舍入)。 | ||
| 136 | + pipe->InitBuffer(ubBuf_, SLOT_NUM * tiling_->tileLen * sizeof(T) + 4U * tiling_->tileLen * sizeof(float)); | ||
| 137 | + LocalTensor<T> all = ubBuf_.Get<T>(); | ||
| 138 | + for (uint32_t s = 0; s < SLOT_NUM; s++) { | ||
| 139 | + slot_[s] = all[s * tiling_->tileLen]; | ||
| 140 | + } | ||
| 141 | + scratch_ = (__local_mem__ float*)((__local_mem__ uint8_t*)all.GetPhyAddr() + | ||
| 142 | + SLOT_NUM * tiling_->tileLen * sizeof(T)); | ||
| 143 | + FillScalarSlots(); | ||
| 144 | + } | ||
| 145 | + | ||
| 146 | + __aicore__ inline void Process() | ||
| 147 | + { | ||
| 148 | + if (blockIdx_ >= tiling_->usedCoreNum) { | ||
| 149 | + return; | ||
| 150 | + } | ||
| 151 | + if (tiling_->tilingKey == LAMB_BRC_KEY_FLAT) { | ||
| 152 | + ProcessFlat(); | ||
| 153 | + } else { | ||
| 154 | + ProcessBlock(); | ||
| 155 | + } | ||
| 156 | + } | ||
| 157 | + | ||
| 158 | +private: | ||
| 159 | + // 单元素输入是常量, 只铺一次, 后续分片不再重复搬运。 | ||
| 160 | + __aicore__ inline void FillScalarSlots() | ||
| 161 | + { | ||
| 162 | + event_t e2s = static_cast<event_t>(GetTPipePtr()->AllocEventID<HardEvent::MTE2_S>()); | ||
| 163 | + event_t s2v = static_cast<event_t>(GetTPipePtr()->AllocEventID<HardEvent::S_V>()); | ||
| 164 | + event_t v2m2 = static_cast<event_t>(GetTPipePtr()->AllocEventID<HardEvent::V_MTE2>()); | ||
| 165 | + for (uint32_t i = 0; i < NIN; i++) { | ||
| 166 | + if (tiling_->inKind[i] != LAMB_BRC_KIND_SCALAR) { | ||
| 167 | + continue; | ||
| 168 | + } | ||
| 169 | + DataCopyExtParams cp{1, static_cast<uint32_t>(sizeof(T)), 0, 0, 0}; | ||
| 170 | + DataCopyPadExtParams<T> pad{false, 0, 0, 0}; | ||
| 171 | + DataCopyPad(slot_[TMP_SLOT], inGm_[i], cp, pad); | ||
| 172 | + SetFlag<HardEvent::MTE2_S>(e2s); | ||
| 173 | + WaitFlag<HardEvent::MTE2_S>(e2s); | ||
| 174 | + T v = slot_[TMP_SLOT].GetValue(0); | ||
| 175 | + SetFlag<HardEvent::S_V>(s2v); | ||
| 176 | + WaitFlag<HardEvent::S_V>(s2v); | ||
| 177 | + Duplicate(slot_[i], v, static_cast<int32_t>(tiling_->tileLen)); | ||
| 178 | + // 暂存槽下一轮会被 MTE2 覆写, 需等本轮标量读取(S)与 Duplicate(V) 完成。 | ||
| 179 | + SetFlag<HardEvent::V_MTE2>(v2m2); | ||
| 180 | + WaitFlag<HardEvent::V_MTE2>(v2m2); | ||
| 181 | + } | ||
| 182 | + GetTPipePtr()->ReleaseEventID<HardEvent::MTE2_S>(e2s); | ||
| 183 | + GetTPipePtr()->ReleaseEventID<HardEvent::S_V>(s2v); | ||
| 184 | + GetTPipePtr()->ReleaseEventID<HardEvent::V_MTE2>(v2m2); | ||
| 185 | + PipeBarrier<PIPE_V>(); | ||
| 186 | + } | ||
| 187 | + | ||
| 188 | + __aicore__ inline void ProcessFlat() | ||
| 189 | + { | ||
| 190 | + uint64_t total = tiling_->totalNum; | ||
| 191 | + uint64_t perCore = tiling_->perCoreElems; | ||
| 192 | + uint64_t begin = perCore * blockIdx_; | ||
| 193 | + uint64_t end = (begin + perCore < total) ? (begin + perCore) : total; | ||
| 194 | + for (uint64_t off = begin; off < end; off += tiling_->tileLen) { | ||
| 195 | + uint32_t len = static_cast<uint32_t>(((end - off) < tiling_->tileLen) ? (end - off) : tiling_->tileLen); | ||
| 196 | + for (uint32_t i = 0; i < NIN; i++) { | ||
| 197 | + if (tiling_->inKind[i] == LAMB_BRC_KIND_SAME) { | ||
| 198 | + CopyInRun(i, off, len); | ||
| 199 | + } | ||
| 200 | + } | ||
| 201 | + ComputeAndOut(off, len); | ||
| 202 | + } | ||
| 203 | + } | ||
| 204 | + | ||
| 205 | + __aicore__ inline void ProcessBlock() | ||
| 206 | + { | ||
| 207 | + uint64_t rows = tiling_->totalRows; | ||
| 208 | + uint64_t perCore = tiling_->rowsPerCore; | ||
| 209 | + uint64_t begin = perCore * blockIdx_; | ||
| 210 | + uint64_t end = (begin + perCore < rows) ? (begin + perCore) : rows; | ||
| 211 | + uint32_t blockLen = tiling_->blockLen; | ||
| 212 | + uint32_t rowsPerTile = tiling_->rowsPerTile; | ||
| 213 | + | ||
| 214 | + for (uint64_t r0 = begin; r0 < end; r0 += rowsPerTile) { | ||
| 215 | + uint64_t batch = ((end - r0) < rowsPerTile) ? (end - r0) : rowsPerTile; | ||
| 216 | + for (uint64_t j = 0; j < batch; j++) { | ||
| 217 | + uint32_t dstOff = static_cast<uint32_t>(j) * blockLen; | ||
| 218 | + for (uint32_t i = 0; i < NIN; i++) { | ||
| 219 | + uint32_t kind = tiling_->inKind[i]; | ||
| 220 | + if (kind == LAMB_BRC_KIND_SCALAR) { | ||
| 221 | + continue; // 标量槽已整片铺好, 各行共用 | ||
| 222 | + } | ||
| 223 | + uint64_t srcOff = RowSrcOffset(i, r0 + j); | ||
| 224 | + if (kind == LAMB_BRC_KIND_SAME) { | ||
| 225 | + CopyInRun(i, srcOff, blockLen, dstOff); | ||
| 226 | + } else { | ||
| 227 | + CopyInBrc(i, srcOff, dstOff); | ||
| 228 | + } | ||
| 229 | + } | ||
| 230 | + } | ||
| 231 | + ComputeAndOut(r0 * blockLen, static_cast<uint32_t>(batch) * blockLen); | ||
| 232 | + } | ||
| 233 | + } | ||
| 234 | + | ||
| 235 | + // 行索引按输出前导维分解, 再乘各输入的有效步长(广播轴步长为 0)。 | ||
| 236 | + __aicore__ inline uint64_t RowSrcOffset(uint32_t i, uint64_t r) | ||
| 237 | + { | ||
| 238 | + uint64_t off = 0; | ||
| 239 | + uint64_t rem = r; | ||
| 240 | + if (tiling_->innerChunkCnt > 1) { // 行索引最低位是最内轴的段号 | ||
| 241 | + uint64_t seg = rem % tiling_->innerChunkCnt; | ||
| 242 | + rem /= tiling_->innerChunkCnt; | ||
| 243 | + if (tiling_->inShape[i * LAMB_BRC_MAX_DIM + tiling_->collapsedRank - 1] != 1) { | ||
| 244 | + off += seg * tiling_->innerChunk; | ||
| 245 | + } | ||
| 246 | + } | ||
| 247 | + for (int32_t j = static_cast<int32_t>(tiling_->splitAxis) - 1; j >= 0; j--) { | ||
| 248 | + uint64_t d = tiling_->outShape[j]; | ||
| 249 | + off += (rem % d) * tiling_->effStride[i * LAMB_BRC_MAX_DIM + j]; | ||
| 250 | + rem /= d; | ||
| 251 | + } | ||
| 252 | + return off; | ||
| 253 | + } | ||
| 254 | + | ||
| 255 | + __aicore__ inline void CopyInRun(uint32_t i, uint64_t srcOff, uint32_t len, uint32_t dstOff = 0) | ||
| 256 | + { | ||
| 257 | + DataCopyExtParams cp{1, static_cast<uint32_t>(len * sizeof(T)), 0, 0, 0}; | ||
| 258 | + DataCopyPadExtParams<T> pad{false, 0, 0, 0}; | ||
| 259 | + DataCopyPad(slot_[i][dstOff], inGm_[i][srcOff], cp, pad); | ||
| 260 | + } | ||
| 261 | + | ||
| 262 | + // 块内只可能是"尾轴 1->n"广播(host 的 splitFloor 保证), 或完全不需要展开。 | ||
| 263 | + __aicore__ inline void CopyInBrc(uint32_t i, uint64_t srcOff, uint32_t dstOff = 0) | ||
| 264 | + { | ||
| 265 | + uint32_t blockLen = tiling_->blockLen; | ||
| 266 | + uint32_t srcLen = static_cast<uint32_t>(tiling_->srcBlockLen[i]); | ||
| 267 | + if (srcLen == blockLen) { // 块内无广播: 直接搬入, 不经暂存 | ||
| 268 | + CopyInRun(i, srcOff, blockLen, dstOff); | ||
| 269 | + return; | ||
| 270 | + } | ||
| 271 | + // 上一次(同一行的前一个广播输入, 或前一行)的 Broadcast 还在读 TMP_SLOT, 必须等它读完 | ||
| 272 | + // 再往里搬下一份 —— 否则 MTE2 的写越过 V 的读(WAR), 结果随机少量出错。 | ||
| 273 | + event_t v2m2 = static_cast<event_t>(GetTPipePtr()->AllocEventID<HardEvent::V_MTE2>()); | ||
| 274 | + SetFlag<HardEvent::V_MTE2>(v2m2); | ||
| 275 | + WaitFlag<HardEvent::V_MTE2>(v2m2); | ||
| 276 | + GetTPipePtr()->ReleaseEventID<HardEvent::V_MTE2>(v2m2); | ||
| 277 | + | ||
| 278 | + DataCopyExtParams cp{1, static_cast<uint32_t>(srcLen * sizeof(T)), 0, 0, 0}; | ||
| 279 | + DataCopyPadExtParams<T> pad{false, 0, 0, 0}; | ||
| 280 | + DataCopyPad(slot_[TMP_SLOT], inGm_[i][srcOff], cp, pad); | ||
| 281 | + | ||
| 282 | + event_t e2v = static_cast<event_t>(GetTPipePtr()->AllocEventID<HardEvent::MTE2_V>()); | ||
| 283 | + SetFlag<HardEvent::MTE2_V>(e2v); | ||
| 284 | + WaitFlag<HardEvent::MTE2_V>(e2v); | ||
| 285 | + GetTPipePtr()->ReleaseEventID<HardEvent::MTE2_V>(e2v); | ||
| 286 | + | ||
| 287 | + // [srcLen, 1] -> [srcLen, d]: 尾轴放大 d 倍 | ||
| 288 | + uint32_t d = blockLen / srcLen; | ||
| 289 | + uint32_t dstShape[2] = {srcLen, d}; | ||
| 290 | + uint32_t srcShape[2] = {srcLen, 1}; | ||
| 291 | + Broadcast<T, 2, 1>(slot_[i][dstOff], slot_[TMP_SLOT], dstShape, srcShape); | ||
| 292 | + PipeBarrier<PIPE_V>(); | ||
| 293 | + } | ||
| 294 | + | ||
| 295 | + __aicore__ inline void ComputeAndOut(uint64_t dstOff, uint32_t len) | ||
| 296 | + { | ||
| 297 | + event_t e2v = static_cast<event_t>(GetTPipePtr()->AllocEventID<HardEvent::MTE2_V>()); | ||
| 298 | + SetFlag<HardEvent::MTE2_V>(e2v); | ||
| 299 | + WaitFlag<HardEvent::MTE2_V>(e2v); | ||
| 300 | + GetTPipePtr()->ReleaseEventID<HardEvent::MTE2_V>(e2v); | ||
| 301 | + | ||
| 302 | + __local_mem__ T* inPtr[NIN]; | ||
| 303 | + __local_mem__ T* outPtr[NOUT]; | ||
| 304 | + for (uint32_t i = 0; i < NIN; i++) { | ||
| 305 | + inPtr[i] = (__local_mem__ T*)slot_[i].GetPhyAddr(); | ||
| 306 | + } | ||
| 307 | + for (uint32_t o = 0; o < NOUT; o++) { | ||
| 308 | + outPtr[o] = (__local_mem__ T*)slot_[NIN + o].GetPhyAddr(); | ||
| 309 | + } | ||
| 310 | + // 暂存区按 tileLen 跨距切分: tileLen 已对齐到向量寄存器长度, 保证 Load/StoreAlign 的 | ||
| 311 | + // 地址对齐要求; 若按当前分片长度 len 切分, len=1 这类用例会得到 4 字节偏移而触发对齐错误。 | ||
| 312 | + __local_mem__ float* sc[4] = {scratch_, scratch_ + tiling_->tileLen, scratch_ + 2U * tiling_->tileLen, | ||
| 313 | + scratch_ + 3U * tiling_->tileLen}; | ||
| 314 | + Compute::template Run<T>(inPtr, outPtr, sc, len); | ||
| 315 | + | ||
| 316 | + event_t v2m3 = static_cast<event_t>(GetTPipePtr()->AllocEventID<HardEvent::V_MTE3>()); | ||
| 317 | + SetFlag<HardEvent::V_MTE3>(v2m3); | ||
| 318 | + WaitFlag<HardEvent::V_MTE3>(v2m3); | ||
| 319 | + GetTPipePtr()->ReleaseEventID<HardEvent::V_MTE3>(v2m3); | ||
| 320 | + | ||
| 321 | + DataCopyExtParams cp{1, static_cast<uint32_t>(len * sizeof(T)), 0, 0, 0}; | ||
| 322 | + for (uint32_t o = 0; o < NOUT; o++) { | ||
| 323 | + DataCopyPad(outGm_[o][dstOff], slot_[NIN + o], cp); | ||
| 324 | + } | ||
| 325 | + | ||
| 326 | + // 搬出未完成前不得让下轮的 V/S 覆写输出槽(MTE3->MTE2 挡不住 V), 也不得让 MTE2 覆写输入槽。 | ||
| 327 | + event_t m32v = static_cast<event_t>(GetTPipePtr()->AllocEventID<HardEvent::MTE3_V>()); | ||
| 328 | + SetFlag<HardEvent::MTE3_V>(m32v); | ||
| 329 | + WaitFlag<HardEvent::MTE3_V>(m32v); | ||
| 330 | + GetTPipePtr()->ReleaseEventID<HardEvent::MTE3_V>(m32v); | ||
| 331 | + event_t m32m2 = static_cast<event_t>(GetTPipePtr()->AllocEventID<HardEvent::MTE3_MTE2>()); | ||
| 332 | + SetFlag<HardEvent::MTE3_MTE2>(m32m2); | ||
| 333 | + WaitFlag<HardEvent::MTE3_MTE2>(m32m2); | ||
| 334 | + GetTPipePtr()->ReleaseEventID<HardEvent::MTE3_MTE2>(m32m2); | ||
| 335 | + } | ||
| 336 | + | ||
| 337 | + const Tiling* tiling_ = nullptr; | ||
| 338 | + uint32_t blockIdx_ = 0; | ||
| 339 | + GlobalTensor<T> inGm_[NIN]; | ||
| 340 | + GlobalTensor<T> outGm_[NOUT]; | ||
| 341 | + TBuf<TPosition::VECCALC> ubBuf_; | ||
| 342 | + LocalTensor<T> slot_[SLOT_NUM]; | ||
| 343 | + __local_mem__ float* scratch_ = nullptr; | ||
| 344 | +}; | ||
| 345 | +} // namespace LambBrc | ||
| 346 | + | ||
| @@ -0,0 +1,65 @@ | |||
| 1 | +/** | ||
| 2 | + * Copyright (c) 2026 Huawei Technologies Co., Ltd. | ||
| 3 | + * This program is free software, you can redistribute it and/or modify it under the terms and conditions of | ||
| 4 | + * CANN Open Software License Agreement Version 2.0 (the "License"). | ||
| 5 | + * Please refer to the License for details. You may not use this file except in compliance with the License. | ||
| 6 | + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, | ||
| 7 | + * INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE. | ||
| 8 | + * See LICENSE in the root of the software repository for the full text of the License. | ||
| 9 | + */ | ||
| 10 | + | ||
| 11 | +/*! | ||
| 12 | + * \file lamb_brc_tiling_data.h | ||
| 13 | + * \brief LAMB 族「多入多出 + 任意 numpy 广播」逐元素算子的共用 tiling 数据 | ||
| 14 | + * | ||
| 15 | + * host 与 kernel 引用同一个模板实例, 布局天然一致(host 侧 memcpy 整个 POD 下发)。 | ||
| 16 | + */ | ||
| 17 | + | ||
| 18 | + | ||
| 19 | + | ||
| 20 | + | ||
| 21 | + | ||
| 22 | + | ||
| 23 | +// GE 原型最大轴数(与 silu_grad / reverse_v2 等同仓算子一致)。规划过程只做轴合并与块内切分, | ||
| 24 | +// 不会增加轴数, 因此这个尺寸对任何合法输入都够用 —— 不存在"超了就拒收"的分支。 | ||
| 25 | +constexpr uint32_t LAMB_BRC_MAX_DIM = 8; | ||
| 26 | + | ||
| 27 | +// 硬件常量(非经验阈值): UB 搬运的最小块 32B、向量寄存器 256B。kernel 侧用平台接口 | ||
| 28 | +// GetUbBlockSize()/GetVRegSize() 静态校验二者一致, 避免两侧取值走偏。 | ||
| 29 | +constexpr uint32_t LAMB_BRC_GM_BLOCK_BYTES = 32; | ||
| 30 | +constexpr uint32_t LAMB_BRC_VREG_BYTES = 256; | ||
| 31 | + | ||
| 32 | +// inKind 取值 | ||
| 33 | +constexpr uint32_t LAMB_BRC_KIND_SCALAR = 0; // 单元素, UB 槽位铺一次后常驻 | ||
| 34 | +constexpr uint32_t LAMB_BRC_KIND_SAME = 1; // 与输出同形, 连续搬入 | ||
| 35 | +constexpr uint32_t LAMB_BRC_KIND_BRC = 2; // 行块内需展开广播 | ||
| 36 | + | ||
| 37 | +// tilingKey(数据内的分支, 与 binary 的 tilingKey 无关) | ||
| 38 | +constexpr uint32_t LAMB_BRC_KEY_FLAT = 0; // 无需广播的输入 -> 纯线性分片, 不受行块大小约束 | ||
| 39 | +constexpr uint32_t LAMB_BRC_KEY_BLOCK = 1; // 有需广播的输入 -> 按输出行块处理 | ||
| 40 | + | ||
| 41 | +template <uint32_t NIN, uint32_t NOUT> | ||
| 42 | +struct LambBrcTilingData { | ||
| 43 | + uint64_t totalNum = 0; // 输出元素总数 | ||
| 44 | + uint64_t totalRows = 0; // 分块模式下的行块总数 | ||
| 45 | + uint64_t effStride[NIN * LAMB_BRC_MAX_DIM] = {}; // [i][j] 输入i在外层轴j的源步长(广播轴为0) | ||
| 46 | + uint64_t srcBlockLen[NIN] = {}; // 输入i在一个行块内的源元素数 | ||
| 47 | + uint64_t perCoreElems = 0; // 平铺模式每核元素数(已对齐 32B) | ||
| 48 | + uint64_t rowsPerCore = 0; // 分块模式每核行块数(已对齐 32B) | ||
| 49 | + uint32_t tileLen = 0; // 由 ubSize 反解的分片元素数 | ||
| 50 | + uint32_t blockLen = 0; // 一个输出行块的元素数 | ||
| 51 | + uint32_t rowsPerTile = 0; // 一个分片内批量处理的行块数 | ||
| 52 | + uint32_t innerChunk = 0; // 最内轴装不下时的块内切分长度(0=不切) | ||
| 53 | + uint32_t innerChunkCnt = 0; // 最内轴被切成几段 | ||
| 54 | + uint32_t usedCoreNum = 0; | ||
| 55 | + uint32_t splitAxis = 0; // 前导行维个数 | ||
| 56 | + uint32_t collapsedRank = 0; | ||
| 57 | + uint32_t tilingKey = 0; | ||
| 58 | + uint32_t inKind[NIN] = {}; | ||
| 59 | + uint32_t outShape[LAMB_BRC_MAX_DIM] = {}; | ||
| 60 | + uint32_t inShape[NIN * LAMB_BRC_MAX_DIM] = {}; | ||
| 61 | + | ||
| 62 | + static constexpr uint32_t SlotNum() { return NIN + NOUT + 1; } // 入 + 出 + 1 广播暂存 | ||
| 63 | +}; | ||
| 64 | + | ||
| 65 | + | ||
| @@ -0,0 +1,187 @@ | |||
| 1 | +/** | ||
| 2 | + * Copyright (c) 2026 Huawei Technologies Co., Ltd. | ||
| 3 | + * This program is free software, you can redistribute it and/or modify it under the terms and conditions of | ||
| 4 | + * CANN Open Software License Agreement Version 2.0 (the "License"). | ||
| 5 | + * Please refer to the License for details. You may not use this file except in compliance with the License. | ||
| 6 | + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, | ||
| 7 | + * INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE. | ||
| 8 | + * See LICENSE in the root of the software repository for the full text of the License. | ||
| 9 | + */ | ||
| 10 | + | ||
| 11 | +/*! | ||
| 12 | + * \file lamb_next_mv_vf.h | ||
| 13 | + * \brief LambNextMV 的 regbase 计算段(13 入 4 出), 搬运/广播/分核见 lamb_apply_common/lamb_brc_kernel.h | ||
| 14 | + * | ||
| 15 | + * 与 golden 同运算序(先乘后加两步, 不用 FMA 融合形式): | ||
| 16 | + * next_v = v*b2 + g2*(1-b2) -> y3 | ||
| 17 | + * next_m = m*b1 + g *(1-b1) -> y2 | ||
| 18 | + * v_unb = next_v/rd1 ; m_unb = next_m/rd0 | ||
| 19 | + * y1 = param*wd + m_unb / sqrt(v_unb + eps) | ||
| 20 | + * y4 = m_unb / (sqrt(v_unb) + eps) | ||
| 21 | + * 中间一律 fp32(A2 的 TBE compute dtype='float32'), 出口窄回 T。 | ||
| 22 | + */ | ||
| 23 | + | ||
| 24 | + | ||
| 25 | + | ||
| 26 | + | ||
| 27 | + | ||
| 28 | + | ||
| 29 | +namespace LambNextMVOp { | ||
| 30 | +using namespace AscendC; | ||
| 31 | + | ||
| 32 | +// 输入下标(与原型入参顺序一致) | ||
| 33 | +enum : uint32_t { | ||
| 34 | + IN_G2 = 0, // input_mul3 = g^2 | ||
| 35 | + IN_V = 1, // input_mul2 = v | ||
| 36 | + IN_RD1 = 2, // input_realdiv1 = 1 - b2^t | ||
| 37 | + IN_G = 3, // input_mul1 = g | ||
| 38 | + IN_M = 4, // input_mul0 = m | ||
| 39 | + IN_RD0 = 5, // input_realdiv0 = 1 - b1^t | ||
| 40 | + IN_PARAM = 6, // input_mul4 = param | ||
| 41 | + IN_B1 = 7, | ||
| 42 | + IN_OMB1 = 8, | ||
| 43 | + IN_B2 = 9, | ||
| 44 | + IN_OMB2 = 10, | ||
| 45 | + IN_WD = 11, | ||
| 46 | + IN_EPS = 12 | ||
| 47 | +}; | ||
| 48 | + | ||
| 49 | +template <bool WITH_DECAY> | ||
| 50 | +struct LambNextMVVf { | ||
| 51 | + template <typename T> | ||
| 52 | + static __aicore__ inline void Run(__local_mem__ T** in, __local_mem__ T** out, __local_mem__ float** scratch, | ||
| 53 | + uint32_t count) | ||
| 54 | + { | ||
| 55 | + uint16_t vfTimes = static_cast<uint16_t>(count / LambBrc::LAMB_VF_LEN); | ||
| 56 | + uint32_t tail = count % LambBrc::LAMB_VF_LEN; | ||
| 57 | + uint16_t tailTimes = (tail > 0) ? 1 : 0; | ||
| 58 | + | ||
| 59 | + __local_mem__ T* pG2 = in[IN_G2]; | ||
| 60 | + __local_mem__ T* pV = in[IN_V]; | ||
| 61 | + __local_mem__ T* pRd1 = in[IN_RD1]; | ||
| 62 | + __local_mem__ T* pG = in[IN_G]; | ||
| 63 | + __local_mem__ T* pM = in[IN_M]; | ||
| 64 | + __local_mem__ T* pRd0 = in[IN_RD0]; | ||
| 65 | + __local_mem__ T* pParam = in[IN_PARAM]; | ||
| 66 | + __local_mem__ T* pB1 = in[IN_B1]; | ||
| 67 | + __local_mem__ T* pOmB1 = in[IN_OMB1]; | ||
| 68 | + __local_mem__ T* pB2 = in[IN_B2]; | ||
| 69 | + __local_mem__ T* pOmB2 = in[IN_OMB2]; | ||
| 70 | + __local_mem__ T* pWd = in[IN_WD]; | ||
| 71 | + __local_mem__ T* pEps = in[IN_EPS]; | ||
| 72 | + __local_mem__ T* y1 = out[0]; | ||
| 73 | + __local_mem__ T* y2 = out[1]; | ||
| 74 | + __local_mem__ T* y3 = out[2]; | ||
| 75 | + __local_mem__ T* y4 = out[3]; | ||
| 76 | + // 中间量以 fp32 暂存, 不经 T 的舍入 —— 与单段实现数值完全一致。 | ||
| 77 | + __local_mem__ float* sNextV = scratch[0]; | ||
| 78 | + __local_mem__ float* sNextM = scratch[1]; | ||
| 79 | + | ||
| 80 | + __local_mem__ float* sVUnb = scratch[2]; | ||
| 81 | + __local_mem__ float* sMUnb = scratch[3]; | ||
| 82 | + | ||
| 83 | + // 分 4 段: 全量精确档(0ULP/FTZ_FALSE)展开后指令很多, 单段的循环回边偏移会超出 | ||
| 84 | + // scbzi 立即数范围 [-512,511](--cce-long-scbz=true 压不住)。每段最多 2 个精确档调用, | ||
| 85 | + // 中间量一律以 fp32 暂存传递, 不经 T 的舍入, 与单段实现数值完全一致。 | ||
| 86 | + | ||
| 87 | + // ---- 段1: next_v(y3) / next_m(y2) ---- | ||
| 88 | + __VEC_SCOPE__ | ||
| 89 | + { | ||
| 90 | + Reg::RegTensor<float> vA, vB, vNextV, vNextM; | ||
| 91 | + Reg::MaskReg maskAll = Reg::CreateMask<float, Reg::MaskPattern::ALL>(); | ||
| 92 | + Reg::MaskReg maskT = Reg::UpdateMask<float>(tail); | ||
| 93 | + for (uint16_t vfIdx = 0; vfIdx < vfTimes + tailTimes; vfIdx++) { | ||
| 94 | + uint32_t off = vfIdx * LambBrc::LAMB_VF_LEN; | ||
| 95 | + Reg::MaskReg preg = (vfIdx < vfTimes) ? maskAll : maskT; | ||
| 96 | + LambBrc::Load<T>(pV, vA, preg, off); | ||
| 97 | + LambBrc::Load<T>(pB2, vB, preg, off); | ||
| 98 | + Reg::Mul(vNextV, vA, vB, preg); | ||
| 99 | + LambBrc::Load<T>(pG2, vA, preg, off); | ||
| 100 | + LambBrc::Load<T>(pOmB2, vB, preg, off); | ||
| 101 | + Reg::Mul(vA, vA, vB, preg); | ||
| 102 | + Reg::Add(vNextV, vNextV, vA, preg); | ||
| 103 | + LambBrc::Store<T>(y3, vNextV, preg, off); | ||
| 104 | + Reg::StoreAlign<float, Reg::StoreDist::DIST_NORM>(sNextV + off, vNextV, preg); | ||
| 105 | + LambBrc::Load<T>(pM, vA, preg, off); | ||
| 106 | + LambBrc::Load<T>(pB1, vB, preg, off); | ||
| 107 | + Reg::Mul(vNextM, vA, vB, preg); | ||
| 108 | + LambBrc::Load<T>(pG, vA, preg, off); | ||
| 109 | + LambBrc::Load<T>(pOmB1, vB, preg, off); | ||
| 110 | + Reg::Mul(vA, vA, vB, preg); | ||
| 111 | + Reg::Add(vNextM, vNextM, vA, preg); | ||
| 112 | + LambBrc::Store<T>(y2, vNextM, preg, off); | ||
| 113 | + Reg::StoreAlign<float, Reg::StoreDist::DIST_NORM>(sNextM + off, vNextM, preg); | ||
| 114 | + } | ||
| 115 | + } | ||
| 116 | + | ||
| 117 | + // ---- 段2: 去偏 (2 次精确除法) ---- | ||
| 118 | + __VEC_SCOPE__ | ||
| 119 | + { | ||
| 120 | + Reg::RegTensor<float> vA, vT, vU; | ||
| 121 | + Reg::MaskReg maskAll = Reg::CreateMask<float, Reg::MaskPattern::ALL>(); | ||
| 122 | + Reg::MaskReg maskT = Reg::UpdateMask<float>(tail); | ||
| 123 | + for (uint16_t vfIdx = 0; vfIdx < vfTimes + tailTimes; vfIdx++) { | ||
| 124 | + uint32_t off = vfIdx * LambBrc::LAMB_VF_LEN; | ||
| 125 | + Reg::MaskReg preg = (vfIdx < vfTimes) ? maskAll : maskT; | ||
| 126 | + Reg::LoadAlign<float, Reg::LoadDist::DIST_NORM>(vT, sNextV + off); | ||
| 127 | + LambBrc::Load<T>(pRd1, vA, preg, off); | ||
| 128 | + Reg::Div<float, &LambBrc::LAMB_PRECISE_DIV>(vU, vT, vA, preg); | ||
| 129 | + Reg::StoreAlign<float, Reg::StoreDist::DIST_NORM>(sVUnb + off, vU, preg); | ||
| 130 | + Reg::LoadAlign<float, Reg::LoadDist::DIST_NORM>(vT, sNextM + off); | ||
| 131 | + LambBrc::Load<T>(pRd0, vA, preg, off); | ||
| 132 | + Reg::Div<float, &LambBrc::LAMB_PRECISE_DIV>(vU, vT, vA, preg); | ||
| 133 | + Reg::StoreAlign<float, Reg::StoreDist::DIST_NORM>(sMUnb + off, vU, preg); | ||
| 134 | + } | ||
| 135 | + } | ||
| 136 | + | ||
| 137 | + // ---- 段3: y1 = param*wd + m_unb / sqrt(v_unb + eps) ---- | ||
| 138 | + __VEC_SCOPE__ | ||
| 139 | + { | ||
| 140 | + Reg::RegTensor<float> vA, vB, vPw, vVU, vMU; | ||
| 141 | + Reg::MaskReg maskAll = Reg::CreateMask<float, Reg::MaskPattern::ALL>(); | ||
| 142 | + Reg::MaskReg maskT = Reg::UpdateMask<float>(tail); | ||
| 143 | + for (uint16_t vfIdx = 0; vfIdx < vfTimes + tailTimes; vfIdx++) { | ||
| 144 | + uint32_t off = vfIdx * LambBrc::LAMB_VF_LEN; | ||
| 145 | + Reg::MaskReg preg = (vfIdx < vfTimes) ? maskAll : maskT; | ||
| 146 | + Reg::LoadAlign<float, Reg::LoadDist::DIST_NORM>(vVU, sVUnb + off); | ||
| 147 | + Reg::LoadAlign<float, Reg::LoadDist::DIST_NORM>(vMU, sMUnb + off); | ||
| 148 | + LambBrc::Load<T>(pParam, vA, preg, off); | ||
| 149 | + LambBrc::Load<T>(pWd, vB, preg, off); | ||
| 150 | + Reg::Mul(vPw, vA, vB, preg); | ||
| 151 | + LambBrc::Load<T>(pEps, vB, preg, off); | ||
| 152 | + Reg::Add(vA, vVU, vB, preg); | ||
| 153 | + Reg::Sqrt<float, &LambBrc::LAMB_PRECISE_SQRT>(vA, vA, preg); | ||
| 154 | + Reg::Div<float, &LambBrc::LAMB_PRECISE_DIV>(vA, vMU, vA, preg); | ||
| 155 | + Reg::Add(vA, vPw, vA, preg); | ||
| 156 | + LambBrc::Store<T>(y1, vA, preg, off); | ||
| 157 | + } | ||
| 158 | + } | ||
| 159 | + | ||
| 160 | + // ---- 段4: y4 = [param*wd +] m_unb / (sqrt(v_unb) + eps) ---- | ||
| 161 | + __VEC_SCOPE__ | ||
| 162 | + { | ||
| 163 | + Reg::RegTensor<float> vA, vB, vPw, vVU, vMU; | ||
| 164 | + Reg::MaskReg maskAll = Reg::CreateMask<float, Reg::MaskPattern::ALL>(); | ||
| 165 | + Reg::MaskReg maskT = Reg::UpdateMask<float>(tail); | ||
| 166 | + for (uint16_t vfIdx = 0; vfIdx < vfTimes + tailTimes; vfIdx++) { | ||
| 167 | + uint32_t off = vfIdx * LambBrc::LAMB_VF_LEN; | ||
| 168 | + Reg::MaskReg preg = (vfIdx < vfTimes) ? maskAll : maskT; | ||
| 169 | + Reg::LoadAlign<float, Reg::LoadDist::DIST_NORM>(vVU, sVUnb + off); | ||
| 170 | + Reg::LoadAlign<float, Reg::LoadDist::DIST_NORM>(vMU, sMUnb + off); | ||
| 171 | + Reg::Sqrt<float, &LambBrc::LAMB_PRECISE_SQRT>(vA, vVU, preg); | ||
| 172 | + LambBrc::Load<T>(pEps, vB, preg, off); | ||
| 173 | + Reg::Add(vA, vA, vB, preg); | ||
| 174 | + Reg::Div<float, &LambBrc::LAMB_PRECISE_DIV>(vA, vMU, vA, preg); | ||
| 175 | + if constexpr (WITH_DECAY) { | ||
| 176 | + LambBrc::Load<T>(pParam, vPw, preg, off); | ||
| 177 | + LambBrc::Load<T>(pWd, vB, preg, off); | ||
| 178 | + Reg::Mul(vPw, vPw, vB, preg); | ||
| 179 | + Reg::Add(vA, vPw, vA, preg); | ||
| 180 | + } | ||
| 181 | + LambBrc::Store<T>(y4, vA, preg, off); | ||
| 182 | + } | ||
| 183 | + } | ||
| 184 | + } | ||
| 185 | +}; | ||
| 186 | +} // namespace LambNextMVOp | ||
| 187 | + | ||
| @@ -1,9 +1,9 @@ | |||
| 1 | # ----------------------------------------------------------------------------------------------------------- | 1 | # ----------------------------------------------------------------------------------------------------------- |
| 2 | # Copyright (c) 2026 Huawei Technologies Co., Ltd. | 2 | # Copyright (c) 2026 Huawei Technologies Co., Ltd. |
| 3 | -# This program is free software, you can redistribute it and/or modify it under the terms and conditions of | 3 | +# This program is free software, you can redistribute it and/or modify it under the terms and conditions of |
| 4 | # CANN Open Software License Agreement Version 2.0 (the "License"). | 4 | # CANN Open Software License Agreement Version 2.0 (the "License"). |
| 5 | # Please refer to the License for details. You may not use this file except in compliance with the License. | 5 | # Please refer to the License for details. You may not use this file except in compliance with the License. |
| 6 | -# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, | 6 | +# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, |
| 7 | # INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE. | 7 | # INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE. |
| 8 | # See LICENSE in the root of the software repository for the full text of the License. | 8 | # See LICENSE in the root of the software repository for the full text of the License. |
| 9 | # ----------------------------------------------------------------------------------------------------------- | 9 | # ----------------------------------------------------------------------------------------------------------- |
| @@ -13,4 +13,4 @@ set(SUPPORT_COMPUTE_UNIT "ascend950") | |||
| 13 | # 设置每种芯片类型对应的tiling文件目录,即采用op_host目录下哪个文件夹下的tiling文件编译 | 13 | # 设置每种芯片类型对应的tiling文件目录,即采用op_host目录下哪个文件夹下的tiling文件编译 |
| 14 | set(SUPPORT_TILING_DIR "arch35") | 14 | set(SUPPORT_TILING_DIR "arch35") |
| 15 | 15 | ||
| 16 | -add_modules_sources(HOSTNAME ${OPHOST_NAME} MODE PRIVATE DIR ${CMAKE_CURRENT_SOURCE_DIR} OPTYPE lamb_apply_optimizer_assign ACLNNTYPE aclnn_exclude COMPUTE_UNIT ${SUPPORT_COMPUTE_UNIT} TILING_DIR ${SUPPORT_TILING_DIR} DISABLE_IN_OPP TRUE) | 16 | +add_modules_sources(HOSTNAME ${OPHOST_NAME} MODE PRIVATE DIR ${CMAKE_CURRENT_SOURCE_DIR} OPTYPE lamb_apply_optimizer_assign ACLNNTYPE aclnn_exclude COMPUTE_UNIT ${SUPPORT_COMPUTE_UNIT} TILING_DIR ${SUPPORT_TILING_DIR} DEPENDENCIES lamb_apply_common DISABLE_IN_OPP TRUE) |
| @@ -67,63 +67,63 @@ | |||
| 67 | <tr> | 67 | <tr> |
| 68 | <td>input3</td> | 68 | <td>input3</td> |
| 69 | <td>输入</td> | 69 | <td>输入</td> |
| 70 | - <td>支持空Tensor。公式中的input3(参与权重衰减的参数)。<b>唯一参与广播的输入</b>:按右对齐broadcast规则向inputv对齐,维度数可少于inputv(不可多于),对应维需相等或为1。</td> | 70 | + <td>支持空Tensor。公式中的input3(参与权重衰减的参数),shape需与其他输入满足broadcast关系。</td> |
| 71 | <td>FLOAT16、FLOAT</td> | 71 | <td>FLOAT16、FLOAT</td> |
| 72 | <td>ND</td> | 72 | <td>ND</td> |
| 73 | </tr> | 73 | </tr> |
| 74 | <tr> | 74 | <tr> |
| 75 | <td>mul0_x</td> | 75 | <td>mul0_x</td> |
| 76 | <td>输入</td> | 76 | <td>输入</td> |
| 77 | - <td>不支持空Tensor。公式中的mul0_x(beta1),标量。</td> | 77 | + <td>支持空Tensor。公式中的mul0_x(beta1),shape支持1-8维,需与其他输入满足broadcast规则(右对齐,对应维相等或为1)。</td> |
| 78 | <td>FLOAT16、FLOAT</td> | 78 | <td>FLOAT16、FLOAT</td> |
| 79 | <td>ND</td> | 79 | <td>ND</td> |
| 80 | </tr> | 80 | </tr> |
| 81 | <tr> | 81 | <tr> |
| 82 | <td>mul1_x</td> | 82 | <td>mul1_x</td> |
| 83 | <td>输入</td> | 83 | <td>输入</td> |
| 84 | - <td>不支持空Tensor。公式中的mul1_x(1-beta1),标量。</td> | 84 | + <td>支持空Tensor。公式中的mul1_x(1-beta1),shape支持1-8维,需与其他输入满足broadcast规则(右对齐,对应维相等或为1)。</td> |
| 85 | <td>FLOAT16、FLOAT</td> | 85 | <td>FLOAT16、FLOAT</td> |
| 86 | <td>ND</td> | 86 | <td>ND</td> |
| 87 | </tr> | 87 | </tr> |
| 88 | <tr> | 88 | <tr> |
| 89 | <td>mul2_x</td> | 89 | <td>mul2_x</td> |
| 90 | <td>输入</td> | 90 | <td>输入</td> |
| 91 | - <td>不支持空Tensor。公式中的mul2_x(beta2),标量。</td> | 91 | + <td>支持空Tensor。公式中的mul2_x(beta2),shape支持1-8维,需与其他输入满足broadcast规则(右对齐,对应维相等或为1)。</td> |
| 92 | <td>FLOAT16、FLOAT</td> | 92 | <td>FLOAT16、FLOAT</td> |
| 93 | <td>ND</td> | 93 | <td>ND</td> |
| 94 | </tr> | 94 | </tr> |
| 95 | <tr> | 95 | <tr> |
| 96 | <td>mul3_x</td> | 96 | <td>mul3_x</td> |
| 97 | <td>输入</td> | 97 | <td>输入</td> |
| 98 | - <td>不支持空Tensor。公式中的mul3_x(1-beta2),标量。</td> | 98 | + <td>支持空Tensor。公式中的mul3_x(1-beta2),shape支持1-8维,需与其他输入满足broadcast规则(右对齐,对应维相等或为1)。</td> |
| 99 | <td>FLOAT16、FLOAT</td> | 99 | <td>FLOAT16、FLOAT</td> |
| 100 | <td>ND</td> | 100 | <td>ND</td> |
| 101 | </tr> | 101 | </tr> |
| 102 | <tr> | 102 | <tr> |
| 103 | <td>add2_y</td> | 103 | <td>add2_y</td> |
| 104 | <td>输入</td> | 104 | <td>输入</td> |
| 105 | - <td>不支持空Tensor。公式中的add2_y(epsilon),标量。</td> | 105 | + <td>支持空Tensor。公式中的add2_y(epsilon),shape支持1-8维,需与其他输入满足broadcast规则(右对齐,对应维相等或为1)。</td> |
| 106 | <td>FLOAT16、FLOAT</td> | 106 | <td>FLOAT16、FLOAT</td> |
| 107 | <td>ND</td> | 107 | <td>ND</td> |
| 108 | </tr> | 108 | </tr> |
| 109 | <tr> | 109 | <tr> |
| 110 | <td>steps</td> | 110 | <td>steps</td> |
| 111 | <td>输入</td> | 111 | <td>输入</td> |
| 112 | - <td>不支持空Tensor。公式中的steps(步数),标量。</td> | 112 | + <td>支持空Tensor。公式中的steps(步数),shape支持1-8维,需与其他输入满足broadcast规则(右对齐,对应维相等或为1)。</td> |
| 113 | <td>FLOAT16、FLOAT</td> | 113 | <td>FLOAT16、FLOAT</td> |
| 114 | <td>ND</td> | 114 | <td>ND</td> |
| 115 | </tr> | 115 | </tr> |
| 116 | <tr> | 116 | <tr> |
| 117 | <td>do_use_weight</td> | 117 | <td>do_use_weight</td> |
| 118 | <td>输入</td> | 118 | <td>输入</td> |
| 119 | - <td>不支持空Tensor。公式中的do_use_weight(是否使用权重衰减),标量。</td> | 119 | + <td>支持空Tensor。公式中的do_use_weight(是否使用权重衰减),shape支持1-8维,需与其他输入满足broadcast规则(右对齐,对应维相等或为1)。</td> |
| 120 | <td>FLOAT16、FLOAT</td> | 120 | <td>FLOAT16、FLOAT</td> |
| 121 | <td>ND</td> | 121 | <td>ND</td> |
| 122 | </tr> | 122 | </tr> |
| 123 | <tr> | 123 | <tr> |
| 124 | <td>weight_decay_rate</td> | 124 | <td>weight_decay_rate</td> |
| 125 | <td>输入</td> | 125 | <td>输入</td> |
| 126 | - <td>不支持空Tensor。公式中的weight_decay_rate(权重衰减率),标量。</td> | 126 | + <td>支持空Tensor。公式中的weight_decay_rate(权重衰减率),shape支持1-8维,需与其他输入满足broadcast规则(右对齐,对应维相等或为1)。</td> |
| 127 | <td>FLOAT16、FLOAT</td> | 127 | <td>FLOAT16、FLOAT</td> |
| 128 | <td>ND</td> | 128 | <td>ND</td> |
| 129 | </tr> | 129 | </tr> |
| @@ -152,9 +152,9 @@ | |||
| 152 | 152 | ||
| 153 | ## 约束说明 | 153 | ## 约束说明 |
| 154 | 154 | ||
| 155 | -- shape约束:`grad`、`inputv`、`inputm`三者shape必须**完全相同**,并直接决定三个输出的shape。`inputv`与`inputm`是原地更新的动量输出,不会被广播放大;`grad`同样不参与广播。**广播只发生在`input3`上**:`input3`按右对齐broadcast规则向`inputv`对齐,维度数可少于`inputv`(含标量),对应维需与`inputv`相等或为1;`input3`的维度数大于`inputv`、或某一维大于`inputv`对应维,均不被支持。 | 155 | +- shape约束:所有输入的shape需两两满足broadcast规则(右对齐,对应维相等或为1)。`inputv`与`inputm`是原地(in-place)更新的动量输出,两者shape必须**完全相同**,且全部输入的broadcast结果必须恰好等于该shape(否则原地写回会越界);三个输出的shape均取该shape。其余输入(含`grad`)可向其广播。 |
| 156 | +- 所有输入及输出的维度数不超过8。 | ||
| 156 | 157 | ||
| 157 | -- `mul0_x`、`mul1_x`、`mul2_x`、`mul3_x`、`add2_y`、`steps`、`do_use_weight`、`weight_decay_rate`这8个标量输入不支持空Tensor;`grad`、`inputv`、`inputm`、`input3`支持空Tensor。 | ||
| 158 | 158 | ||
| 159 | - 所有输入的数据类型必须一致,同为FLOAT16或同为FLOAT。 | 159 | - 所有输入的数据类型必须一致,同为FLOAT16或同为FLOAT。 |
| 160 | 160 | ||
Moptim/lamb_apply_optimizer_assign/op_host/arch35/lamb_apply_optimizer_assign_tiling_arch35.cpp+53-77
| @@ -14,26 +14,29 @@ | |||
| 14 | */ | 14 | */ |
| 15 | 15 | ||
| 16 | 16 | ||
| 17 | -#include "../../../lamb_apply_common/lamb_apply_check_util.h" | 17 | +#include "../../../lamb_apply_common/op_host/arch35/lamb_apply_check_util.h" |
| 18 | 18 | ||
| 19 | + | ||
| 20 | + | ||
| 19 | 21 | ||
| 20 | 22 | ||
| 21 | - | ||
| 22 | - | ||
| 23 | 23 | ||
| 24 | 24 | ||
| 25 | 25 | ||
| 26 | 26 | ||
| 27 | 27 | ||
| 28 | 28 | ||
| 29 | -using namespace AscendC; | ||
| 30 | using namespace ge; | 29 | using namespace ge; |
| 31 | 30 | ||
| 32 | namespace optiling { | 31 | namespace optiling { |
| 33 | 32 | ||
| 34 | constexpr static uint64_t LAMB_APPLY_OPTIMIZER_ASSIGN_TILING_PRIORITY = 0; | 33 | constexpr static uint64_t LAMB_APPLY_OPTIMIZER_ASSIGN_TILING_PRIORITY = 0; |
| 34 | +constexpr static uint64_t TILING_KEY_FP32 = 100; | ||
| 35 | +constexpr static uint64_t TILING_KEY_FP16 = 200; | ||
| 35 | constexpr static int32_t INPUT_NUM = 12; | 36 | constexpr static int32_t INPUT_NUM = 12; |
| 36 | constexpr static int32_t OUTPUT_NUM = 3; | 37 | constexpr static int32_t OUTPUT_NUM = 3; |
| 38 | +constexpr static int32_t INPUTV_IDX = 1; // inputv: ref(原地)输出 | ||
| 39 | +constexpr static int32_t INPUTM_IDX = 2; // inputm: ref(原地)输出 | ||
| 37 | static const char* const kInputNames[] = {"grad", "inputv", "inputm", "input3", "mul0_x", "mul1_x", | 40 | static const char* const kInputNames[] = {"grad", "inputv", "inputm", "input3", "mul0_x", "mul1_x", |
| 38 | "mul2_x", "mul3_x", "add2_y", "steps", "do_use_weight", "weight_decay_rate"}; | 41 | "mul2_x", "mul3_x", "add2_y", "steps", "do_use_weight", "weight_decay_rate"}; |
| 39 | static const char* const kOutputNames[] = {"output0", "inputv", "inputm"}; | 42 | static const char* const kOutputNames[] = {"output0", "inputv", "inputm"}; |
| @@ -52,112 +55,75 @@ static ge::graphStatus TilingPrepareForLambApplyOptimizerAssign(gert::TilingPars | |||
| 52 | 55 | ||
| 53 | ge::graphStatus LambApplyOptimizerAssignTiling::GetShapeAttrsInfo() | 56 | ge::graphStatus LambApplyOptimizerAssignTiling::GetShapeAttrsInfo() |
| 54 | { | 57 | { |
| 55 | - static const int32_t kScalarInputIdx[] = {4, 5, 6, 7, 8, 9, 10, 11}; | ||
| 56 | if (CheckLambApplyDtypeConsistency(context_, INPUT_NUM, kInputNames, OUTPUT_NUM, kOutputNames) != | 58 | if (CheckLambApplyDtypeConsistency(context_, INPUT_NUM, kInputNames, OUTPUT_NUM, kOutputNames) != |
| 57 | ge::GRAPH_SUCCESS) { | 59 | ge::GRAPH_SUCCESS) { |
| 58 | return ge::GRAPH_FAILED; | 60 | return ge::GRAPH_FAILED; |
| 59 | } | 61 | } |
| 60 | - if (CheckLambApplyScalarNotEmpty(context_, kScalarInputIdx, sizeof(kScalarInputIdx) / sizeof(kScalarInputIdx[0]), | ||
| 61 | - kInputNames) != ge::GRAPH_SUCCESS) { | ||
| 62 | - return ge::GRAPH_FAILED; | ||
| 63 | - } | ||
| 64 | return CheckInplaceShapeConstraint(); | 62 | return CheckInplaceShapeConstraint(); |
| 65 | } | 63 | } |
| 66 | 64 | ||
| 67 | // inputv、inputm 是 in-place 更新的动量输出(next_v/next_m 原地写回它们的输入 buffer,见 proto "(in-place)"), | 65 | // inputv、inputm 是 in-place 更新的动量输出(next_v/next_m 原地写回它们的输入 buffer,见 proto "(in-place)"), |
| 68 | -// 输出形状由它们决定,故 inputv、inputm 必须同形状。 | 66 | +// 两者形状必须相同, 且必须 == 全部输入广播的完整网格。其余输入(含绑在 In0 的 grad)可广播进这个网格。 |
| 69 | -// grad 绑定在广播 DAG 的 In0 位,底层 Ops::Base 广播模板(DoDimensionCollapse)不支持对 In0 做广播: | ||
| 70 | -// grad 为标量时 EnsureNotScalar 只抬到 {1}、不会左补 1 对齐输出 rank,直接撞 "dim num is not same"; | ||
| 71 | -// grad 与输出同 rank 但某维为 1 时同样被拒("dim index is not same with out")。故 grad 必须与 inputv 等形。 | ||
| 72 | -// 仅 input3 参与广播(右对齐,维度数可少于 inputv,含标量),这是实测支持的形态。 | ||
| 73 | -// 若后续 ops-base 放开 In0 广播,此处与 infershape 需同步放宽。 | ||
| 74 | ge::graphStatus LambApplyOptimizerAssignTiling::CheckInplaceShapeConstraint() | 67 | ge::graphStatus LambApplyOptimizerAssignTiling::CheckInplaceShapeConstraint() |
| 75 | { | 68 | { |
| 76 | - auto gradShape = context_->GetInputShape(0); | 69 | + auto inputvShape = context_->GetInputShape(INPUTV_IDX); |
| 77 | - auto inputvShape = context_->GetInputShape(1); | 70 | + auto inputmShape = context_->GetInputShape(INPUTM_IDX); |
| 78 | - auto inputmShape = context_->GetInputShape(2); | ||
| 79 | - auto input3Shape = context_->GetInputShape(3); | ||
| 80 | - OP_CHECK_NULL_WITH_CONTEXT(context_, gradShape); | ||
| 81 | OP_CHECK_NULL_WITH_CONTEXT(context_, inputvShape); | 71 | OP_CHECK_NULL_WITH_CONTEXT(context_, inputvShape); |
| 82 | OP_CHECK_NULL_WITH_CONTEXT(context_, inputmShape); | 72 | OP_CHECK_NULL_WITH_CONTEXT(context_, inputmShape); |
| 83 | - OP_CHECK_NULL_WITH_CONTEXT(context_, input3Shape); | ||
| 84 | - const auto& gs = gradShape->GetStorageShape(); | ||
| 85 | const auto& vs = inputvShape->GetStorageShape(); | 73 | const auto& vs = inputvShape->GetStorageShape(); |
| 86 | const auto& ms = inputmShape->GetStorageShape(); | 74 | const auto& ms = inputmShape->GetStorageShape(); |
| 87 | - const auto& ps = input3Shape->GetStorageShape(); | ||
| 88 | - gert::Shape bcShape; | ||
| 89 | if (!(vs == ms)) { | 75 | if (!(vs == ms)) { |
| 90 | OP_LOGE_FOR_INVALID_SHAPES_WITH_REASON( | 76 | OP_LOGE_FOR_INVALID_SHAPES_WITH_REASON( |
| 91 | context_->GetNodeName(), "inputv and inputm", | 77 | context_->GetNodeName(), "inputv and inputm", |
| 92 | (Ops::Base::ToString(vs) + " and " + Ops::Base::ToString(ms)).c_str(), | 78 | (Ops::Base::ToString(vs) + " and " + Ops::Base::ToString(ms)).c_str(), |
| 93 | - "inputv and inputm are in-place updated moments and must have the same shape equal to the broadcast " | 79 | + "inputv and inputm are in-place updated moments and must have the same shape"); |
| 94 | - "output shape"); | ||
| 95 | return ge::GRAPH_FAILED; | 80 | return ge::GRAPH_FAILED; |
| 96 | } | 81 | } |
| 97 | - // grad 不参与广播,必须与 inputv/inputm 等形 | 82 | + return CheckLambApplyBroadcastIntoRef(context_, INPUT_NUM, INPUTV_IDX, "inputv"); |
| 98 | - if (!(gs == vs)) { | ||
| 99 | - OP_LOGE_FOR_INVALID_SHAPE_WITH_REASON( | ||
| 100 | - context_->GetNodeName(), "grad", Ops::Base::ToString(gs).c_str(), | ||
| 101 | - "grad does not support broadcast and must have exactly the same shape as inputv/inputm"); | ||
| 102 | - return ge::GRAPH_FAILED; | ||
| 103 | - } | ||
| 104 | - // input3 能广播进 inputv <=> broadcast(input3, inputv) == inputv | ||
| 105 | - if (!Ops::Base::BroadcastShape(&ps, &vs, &bcShape) || !(bcShape == vs)) { | ||
| 106 | - OP_LOGE_FOR_INVALID_SHAPE_WITH_REASON( | ||
| 107 | - context_->GetNodeName(), "input3", Ops::Base::ToString(ps).c_str(), | ||
| 108 | - "input3 must be broadcastable into the in-place moment shape inputv/inputm"); | ||
| 109 | - return ge::GRAPH_FAILED; | ||
| 110 | - } | ||
| 111 | - return ge::GRAPH_SUCCESS; | ||
| 112 | } | 83 | } |
| 113 | 84 | ||
| 114 | bool LambApplyOptimizerAssignTiling::IsCapable() { return true; } | 85 | bool LambApplyOptimizerAssignTiling::IsCapable() { return true; } |
| 115 | 86 | ||
| 116 | ge::graphStatus LambApplyOptimizerAssignTiling::DoOpTiling() | 87 | ge::graphStatus LambApplyOptimizerAssignTiling::DoOpTiling() |
| 117 | { | 88 | { |
| 118 | - // 空 tensor 应对(空进空出): 输出为空(0元素)时设 1 核(空转), 配合全0 tiling 数据(blockFormer=0)使 kernel 空转退出, | 89 | + auto rawTilingData = context_->GetRawTilingData(); |
| 119 | - // 直接成功。 | 90 | + OP_CHECK_NULL_WITH_CONTEXT(context_, rawTilingData); |
| 120 | - auto emptyTensorOutShape0 = context_->GetOutputShape(0); | ||
| 121 | - if (emptyTensorOutShape0 != nullptr && emptyTensorOutShape0->GetStorageShape().GetShapeSize() == 0) { | ||
| 122 | - auto emptyRawTiling = context_->GetRawTilingData(); | ||
| 123 | - if (emptyRawTiling != nullptr && emptyRawTiling->GetData() != nullptr) { | ||
| 124 | - size_t emptyCap = emptyRawTiling->GetCapacity(); | ||
| 125 | - uint8_t* emptyPtr = static_cast<uint8_t*>(emptyRawTiling->GetData()); | ||
| 126 | - for (size_t emptyIdx = 0; emptyIdx < emptyCap; ++emptyIdx) { | ||
| 127 | - emptyPtr[emptyIdx] = 0; | ||
| 128 | - } | ||
| 129 | - emptyRawTiling->SetDataSize(emptyCap); | ||
| 130 | - } | ||
| 131 | - size_t* emptyWs = context_->GetWorkspaceSizes(1); | ||
| 132 | - if (emptyWs != nullptr) { | ||
| 133 | - emptyWs[0] = 0; | ||
| 134 | - } | ||
| 135 | - context_->SetBlockDim(1); | ||
| 136 | - tilingKey = GET_TPL_TILING_KEY(1); // schMode=1(已编译), 配合全0 tiling(blockFormer=0)空转 | ||
| 137 | - return ge::GRAPH_SUCCESS; | ||
| 138 | - } | ||
| 139 | auto input0Desc = context_->GetInputDesc(0); | 91 | auto input0Desc = context_->GetInputDesc(0); |
| 140 | OP_CHECK_NULL_WITH_CONTEXT(context_, input0Desc); | 92 | OP_CHECK_NULL_WITH_CONTEXT(context_, input0Desc); |
| 93 | + | ||
| 141 | ge::DataType input0DType = input0Desc->GetDataType(); | 94 | ge::DataType input0DType = input0Desc->GetDataType(); |
| 142 | - if (input0DType == ge::DT_FLOAT16) { | 95 | + uint32_t dtSize = 0; |
| 143 | - BroadcastBaseTiling<LambApplyOptimizerAssignOp::LambApplyOptimizerAssignCompute<half, float>::OpDag> | 96 | + if (input0DType == ge::DT_FLOAT) { |
| 144 | - brcBaseTiling(context_, static_cast<uint32_t>(BROADCAST_KERNEL_TYPE::KERNEL_TYPE_NDDMA)); | 97 | + tilingKey = TILING_KEY_FP32; |
| 145 | - OP_CHECK_IF(brcBaseTiling.DoTiling() == ge::GRAPH_FAILED, | 98 | + dtSize = sizeof(float); |
| 146 | - OP_LOGE(context_->GetNodeName(), "Do tiling failed. Please check the detailed log."), | 99 | + } else if (input0DType == ge::DT_FLOAT16) { |
| 147 | - return ge::GRAPH_FAILED); | 100 | + tilingKey = TILING_KEY_FP16; |
| 148 | - tilingKey = GET_TPL_TILING_KEY(brcBaseTiling.GetSchMode()); | 101 | + dtSize = sizeof(uint16_t); |
| 149 | - } else if (input0DType == ge::DT_FLOAT) { | ||
| 150 | - BroadcastBaseTiling<LambApplyOptimizerAssignOp::LambApplyOptimizerAssignCompute<float, float>::OpDag> | ||
| 151 | - brcBaseTiling(context_, static_cast<uint32_t>(BROADCAST_KERNEL_TYPE::KERNEL_TYPE_NDDMA)); | ||
| 152 | - OP_CHECK_IF(brcBaseTiling.DoTiling() == ge::GRAPH_FAILED, | ||
| 153 | - OP_LOGE(context_->GetNodeName(), "Do tiling failed. Please check the detailed log."), | ||
| 154 | - return ge::GRAPH_FAILED); | ||
| 155 | - tilingKey = GET_TPL_TILING_KEY(brcBaseTiling.GetSchMode()); | ||
| 156 | } else { | 102 | } else { |
| 157 | OP_LOGE_FOR_INVALID_DTYPE(context_->GetNodeName(), "grad", Ops::Base::ToString(input0DType).c_str(), | 103 | OP_LOGE_FOR_INVALID_DTYPE(context_->GetNodeName(), "grad", Ops::Base::ToString(input0DType).c_str(), |
| 158 | "fp16 or fp32"); | 104 | "fp16 or fp32"); |
| 159 | return ge::GRAPH_FAILED; | 105 | return ge::GRAPH_FAILED; |
| 160 | } | 106 | } |
| 107 | + | ||
| 108 | + using PlanTiling = LambBrcTilingData<12, 3>; | ||
| 109 | + td_ = PlanTiling{}; | ||
| 110 | + // 空进空出: 输出 0 元素时 tiling 全 0, kernel 按 usedCoreNum=0 直接退出。 | ||
| 111 | + auto outShape0 = context_->GetOutputShape(0); | ||
| 112 | + if (outShape0 == nullptr || outShape0->GetStorageShape().GetShapeSize() != 0) { | ||
| 113 | + // 先取返回值再判: 模板实参里的逗号会被预处理器当成 OP_CHECK_IF 的参数分隔符。 | ||
| 114 | + ge::graphStatus planRet = BuildLambBrcPlan<12, 3>(context_, coreNum_, ubSize_, dtSize, td_); | ||
| 115 | + OP_CHECK_IF(planRet != ge::GRAPH_SUCCESS, OP_LOGE(context_->GetNodeName(), "build broadcast plan failed"), | ||
| 116 | + return ge::GRAPH_FAILED); | ||
| 117 | + } | ||
| 118 | + | ||
| 119 | + auto ret = memcpy_s(rawTilingData->GetData(), rawTilingData->GetCapacity(), &td_, sizeof(td_)); | ||
| 120 | + OP_CHECK_IF(ret != EOK, OP_LOGE(context_->GetNodeName(), "copy tiling data failed, ret %d", ret), | ||
| 121 | + return ge::GRAPH_FAILED); | ||
| 122 | + rawTilingData->SetDataSize(sizeof(td_)); | ||
| 123 | + context_->SetBlockDim(std::max<uint32_t>(td_.usedCoreNum, 1)); | ||
| 124 | + size_t* ws = context_->GetWorkspaceSizes(1); | ||
| 125 | + OP_CHECK_NULL_WITH_CONTEXT(context_, ws); | ||
| 126 | + ws[0] = 0U; | ||
| 161 | return ge::GRAPH_SUCCESS; | 127 | return ge::GRAPH_SUCCESS; |
| 162 | } | 128 | } |
| 163 | 129 | ||
| @@ -169,7 +135,17 @@ ge::graphStatus LambApplyOptimizerAssignTiling::GetWorkspaceSize() { return ge:: | |||
| 169 | 135 | ||
| 170 | ge::graphStatus LambApplyOptimizerAssignTiling::PostTiling() { return ge::GRAPH_SUCCESS; } | 136 | ge::graphStatus LambApplyOptimizerAssignTiling::PostTiling() { return ge::GRAPH_SUCCESS; } |
| 171 | 137 | ||
| 172 | -ge::graphStatus LambApplyOptimizerAssignTiling::GetPlatformInfo() { return ge::GRAPH_SUCCESS; } | 138 | +ge::graphStatus LambApplyOptimizerAssignTiling::GetPlatformInfo() |
| 139 | +{ | ||
| 140 | + auto compileInfo = static_cast<const LambApplyOptimizerAssignCompileInfo*>(context_->GetCompileInfo()); | ||
| 141 | + OP_CHECK_NULL_WITH_CONTEXT(context_, compileInfo); | ||
| 142 | + coreNum_ = compileInfo->coreNum; | ||
| 143 | + ubSize_ = compileInfo->ubSize; | ||
| 144 | + OP_CHECK_IF(coreNum_ == 0 || ubSize_ == 0, | ||
| 145 | + OP_LOGE(context_->GetNodeName(), "invalid platform info: coreNum %lu ubSize %lu", coreNum_, ubSize_), | ||
| 146 | + return ge::GRAPH_FAILED); | ||
| 147 | + return ge::GRAPH_SUCCESS; | ||
| 148 | +} | ||
| 173 | 149 | ||
| 174 | static ge::graphStatus TilingForLambApplyOptimizerAssign(gert::TilingContext* context) | 150 | static ge::graphStatus TilingForLambApplyOptimizerAssign(gert::TilingContext* context) |
| 175 | { | 151 | { |
| @@ -16,7 +16,8 @@ | |||
| 16 | 16 | ||
| 17 | 17 | ||
| 18 | 18 | ||
| 19 | -#include "../../op_kernel/arch35/lamb_apply_optimizer_assign_tiling_key.h" | 19 | +#include "../lamb_apply_optimizer_assign_tiling_def.h" |
| 20 | + | ||
| 20 | 21 | ||
| 21 | 22 | ||
| 22 | using namespace Ops::NN::Optiling; | 23 | using namespace Ops::NN::Optiling; |
| @@ -44,9 +45,12 @@ protected: | |||
| 44 | 45 | ||
| 45 | private: | 46 | private: |
| 46 | // 校验 in-place 更新的动量输入(inputv/inputm)形状 == 全广播输出网格(grad/input3 可向上广播)。 | 47 | // 校验 in-place 更新的动量输入(inputv/inputm)形状 == 全广播输出网格(grad/input3 可向上广播)。 |
| 47 | - // dtype 一致性、标量非空的通用校验见 lamb_apply_common/lamb_apply_check_util.h。 | 48 | + // dtype 一致性、标量非空的通用校验见 lamb_apply_common/op_host/arch35/lamb_apply_check_util.h。 |
| 48 | ge::graphStatus CheckInplaceShapeConstraint(); | 49 | ge::graphStatus CheckInplaceShapeConstraint(); |
| 49 | uint64_t tilingKey = 0; | 50 | uint64_t tilingKey = 0; |
| 51 | + uint64_t coreNum_ = 0; | ||
| 52 | + uint64_t ubSize_ = 0; | ||
| 53 | + LambBrcTilingData<12, 3> td_; | ||
| 50 | }; | 54 | }; |
| 51 | 55 | ||
| 52 | } // namespace optiling | 56 | } // namespace optiling |
| @@ -13,6 +13,7 @@ | |||
| 13 | * \brief | 13 | * \brief |
| 14 | */ | 14 | */ |
| 15 | 15 | ||
| 16 | + | ||
| 16 | 17 | ||
| 17 | 18 | ||
| 18 | 19 | ||
| @@ -20,65 +21,71 @@ | |||
| 20 | using namespace Ops::Base; | 21 | using namespace Ops::Base; |
| 21 | using namespace ge; | 22 | using namespace ge; |
| 22 | namespace ops { | 23 | namespace ops { |
| 23 | -constexpr size_t GRAD_IDX = 0; | 24 | +// A2 语义: 本族算子的所有输入都是可广播的 ND Tensor(见 canndev |
| 25 | +// ops/built-in/tbe/impl/lamb_*.py, 每一步 mul/sub/div 都先 shape_util.broadcast_shapes | ||
| 26 | +// 再 tbe.broadcast), 输出形状为全部输入广播的结果。A2 的 op_proto 只声明了其中两个输入, | ||
| 27 | +// 属声明宽松, 不作为支持面依据。 | ||
| 28 | +// inputv/inputm 同时是 ref 输出(原地更新的动量), 故广播结果必须恰好等于它们的形状, | ||
| 29 | +// 否则原地写回会越过其显存边界。其余输入(含 In0 的 grad)可广播进这个形状。 | ||
| 30 | +constexpr size_t IN_NUM = 12; | ||
| 24 | constexpr size_t INPUTV_IDX = 1; | 31 | constexpr size_t INPUTV_IDX = 1; |
| 25 | constexpr size_t INPUTM_IDX = 2; | 32 | constexpr size_t INPUTM_IDX = 2; |
| 26 | -constexpr size_t INPUT3_IDX = 3; | 33 | +constexpr size_t OUT_NUM = 3; |
| 27 | -constexpr size_t OUTPUT0_IDX = 0; | ||
| 28 | -constexpr size_t OUTPUTV_IDX = 1; | ||
| 29 | -constexpr size_t OUTPUTM_IDX = 2; | ||
| 30 | 34 | ||
| 31 | static ge::graphStatus InferShape4LambApplyOptimizerAssign(gert::InferShapeContext* context) | 35 | static ge::graphStatus InferShape4LambApplyOptimizerAssign(gert::InferShapeContext* context) |
| 32 | { | 36 | { |
| 33 | - auto grad_shape = context->GetInputShape(GRAD_IDX); | 37 | + std::vector<const gert::Shape*> inShapes; |
| 34 | - OP_CHECK_NULL_WITH_CONTEXT(context, grad_shape); | 38 | + inShapes.reserve(IN_NUM); |
| 35 | - auto inputv_shape = context->GetInputShape(INPUTV_IDX); | 39 | + for (size_t i = 0; i < IN_NUM; i++) { |
| 36 | - OP_CHECK_NULL_WITH_CONTEXT(context, inputv_shape); | 40 | + auto in = context->GetInputShape(i); |
| 37 | - auto inputm_shape = context->GetInputShape(INPUTM_IDX); | 41 | + OP_CHECK_NULL_WITH_CONTEXT(context, in); |
| 38 | - OP_CHECK_NULL_WITH_CONTEXT(context, inputm_shape); | 42 | + inShapes.push_back(in); |
| 39 | - auto input3_shape = context->GetInputShape(INPUT3_IDX); | 43 | + } |
| 40 | - OP_CHECK_NULL_WITH_CONTEXT(context, input3_shape); | 44 | + gert::Shape bcShape; |
| 41 | - auto output0_shape = context->GetOutputShape(OUTPUT0_IDX); | 45 | + // 逐对折叠广播: 只用两参数重载。vector 重载虽在 op_common/op_host/infershape_broadcast_util.h |
| 42 | - OP_CHECK_NULL_WITH_CONTEXT(context, output0_shape); | 46 | + // 以 Ops::Base 声明, 但 libops_base.so 只导出两参数版, vector 版的实现在 libop_common.so 的 |
| 43 | - auto outputv_shape = context->GetOutputShape(OUTPUTV_IDX); | 47 | + // 小写 ops 命名空间下 —— 声明与实现命名空间不一致, 用它会编译期通过、加载期 |
| 44 | - OP_CHECK_NULL_WITH_CONTEXT(context, outputv_shape); | 48 | + // undefined symbol 而装不上包。 |
| 45 | - auto outputm_shape = context->GetOutputShape(OUTPUTM_IDX); | 49 | + bcShape = *inShapes[0]; |
| 46 | - OP_CHECK_NULL_WITH_CONTEXT(context, outputm_shape); | 50 | + for (size_t i = 1; i < inShapes.size(); i++) { |
| 47 | - | 51 | + gert::Shape tmp; |
| 48 | - // grad、inputv、inputm 三者形状必须完全相同:inputv/inputm 是原地更新的动量输出, | 52 | + OP_CHECK_IF(!BroadcastShape(&bcShape, inShapes[i], &tmp), |
| 49 | - // 输出形状由它们决定;grad 绑定在广播 DAG 的 In0 位,底层 Ops::Base 广播模板 | 53 | + OP_LOGE(context->GetNodeName(), "input shapes cannot broadcast together"), return ge::GRAPH_FAILED); |
| 50 | - // (DoDimensionCollapse) 不支持对 In0 做广播——实测 grad 为标量、或任一维为 1 时 | 54 | + bcShape = tmp; |
| 51 | - // 均在 tiling 阶段被拒(“dim num is not same”/“dim index is not same with out”)。 | 55 | + } |
| 52 | - // 故此处直接按等形拒绝,避免放行后到 tiling 才抛 E90003。 | 56 | + // 标量归一: 全标量输入 broadcast 得 0 维空 shape (), 与 A2 的 shape_util.scalar2tensor_one |
| 53 | - // 仅 input3 参与广播(右对齐,维度数可少于 inputv)。 | 57 | + // 对齐, 归一为 (1,)。否则动态 shape 编译期 DFX 生成会对空 shape 做 reduce 连乘(无初值)而报 |
| 54 | - // 此处的判定与 tiling 的 CheckInplaceShapeConstraint 保持一致,避免两个 host | 58 | + // TypeError 编译失败。 |
| 55 | - // 阶段对同一组合给出不同结论。 | 59 | + if (bcShape.GetDimNum() == 0) { |
| 56 | - OP_CHECK_IF(!(*inputv_shape == *inputm_shape), | 60 | + bcShape.SetDimNum(1); |
| 57 | - OP_LOGE_FOR_INVALID_SHAPES_WITH_REASON( | 61 | + bcShape.SetDim(0, 1); |
| 58 | - context->GetNodeName(), "inputv and inputm", | 62 | + } |
| 59 | - (ToString(*inputv_shape) + " and " + ToString(*inputm_shape)).c_str(), | 63 | + const gert::Shape& vShape = *inShapes[INPUTV_IDX]; |
| 60 | - "inputv and inputm are in-place updated moments and must have the same shape"), | 64 | + const gert::Shape& mShape = *inShapes[INPUTM_IDX]; |
| 61 | - return ge::GRAPH_FAILED); | 65 | + OP_CHECK_IF( |
| 62 | - | 66 | + !(vShape == mShape), |
| 63 | - gert::Shape broadcast_shape; | 67 | + OP_LOGE_FOR_INVALID_SHAPES_WITH_REASON( |
| 64 | - OP_CHECK_IF(!(*grad_shape == *inputv_shape), | 68 | + context->GetNodeName(), "inputv and inputm", (ToString(vShape) + " and " + ToString(mShape)).c_str(), |
| 69 | + "inputv and inputm are in-place updated moments and must have the same shape"), | ||
| 70 | + return ge::GRAPH_FAILED); | ||
| 71 | + gert::Shape normV = vShape; | ||
| 72 | + if (normV.GetDimNum() == 0) { | ||
| 73 | + normV.SetDimNum(1); | ||
| 74 | + normV.SetDim(0, 1); | ||
| 75 | + } | ||
| 76 | + OP_CHECK_IF(!(bcShape == normV), | ||
| 65 | OP_LOGE_FOR_INVALID_SHAPE_WITH_REASON( | 77 | OP_LOGE_FOR_INVALID_SHAPE_WITH_REASON( |
| 66 | - context->GetNodeName(), "grad", ToString(*grad_shape).c_str(), | 78 | + context->GetNodeName(), "inputv", ToString(vShape).c_str(), |
| 67 | - "grad does not support broadcast and must have exactly the same shape as inputv/inputm"), | 79 | + "inputv/inputm are in-place(ref) outputs, so the broadcast shape of all inputs must equal them"), |
| 68 | return ge::GRAPH_FAILED); | 80 | return ge::GRAPH_FAILED); |
| 69 | - | 81 | + for (size_t i = 0; i < OUT_NUM; i++) { |
| 70 | - OP_CHECK_IF(!BroadcastShape(input3_shape, inputv_shape, &broadcast_shape) || !(broadcast_shape == *inputv_shape), | 82 | + auto out = context->GetOutputShape(i); |
| 71 | - OP_LOGE_FOR_INVALID_SHAPE_WITH_REASON( | 83 | + OP_CHECK_NULL_WITH_CONTEXT(context, out); |
| 72 | - context->GetNodeName(), "input3", ToString(*input3_shape).c_str(), | 84 | + *out = normV; |
| 73 | - "input3 must be broadcastable into the in-place moment shape inputv/inputm"), | 85 | + } |
| 74 | - return ge::GRAPH_FAILED); | ||
| 75 | - | ||
| 76 | - *output0_shape = *inputv_shape; | ||
| 77 | - *outputv_shape = *inputv_shape; | ||
| 78 | - *outputm_shape = *inputm_shape; | ||
| 79 | - | ||
| 80 | return GRAPH_SUCCESS; | 86 | return GRAPH_SUCCESS; |
| 81 | } | 87 | } |
| 88 | + | ||
| 82 | static ge::graphStatus InferDataType4LambApplyOptimizerAssign(gert::InferDataTypeContext* context) | 89 | static ge::graphStatus InferDataType4LambApplyOptimizerAssign(gert::InferDataTypeContext* context) |
| 83 | { | 90 | { |
| 84 | if (context == nullptr) { | 91 | if (context == nullptr) { |
| @@ -0,0 +1,52 @@ | |||
| 1 | +/** | ||
| 2 | + * Copyright (c) 2026 Huawei Technologies Co., Ltd. | ||
| 3 | + * This program is free software, you can redistribute it and/or modify it under the terms and conditions of | ||
| 4 | + * CANN Open Software License Agreement Version 2.0 (the "License"). | ||
| 5 | + * Please refer to the License for details. You may not use this file except in compliance with the License. | ||
| 6 | + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, | ||
| 7 | + * INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE. | ||
| 8 | + * See LICENSE in the root of the software repository for the full text of the License. | ||
| 9 | + */ | ||
| 10 | + | ||
| 11 | +/*! | ||
| 12 | + * \file lamb_apply_optimizer_assign_tiling_def.h | ||
| 13 | + * \brief LambApplyOptimizerAssign tiling data definition (arch35 手写内核) | ||
| 14 | + * | ||
| 15 | + * 只用于给 framework 定 raw tiling buffer 的容量; 实际下发由 host 侧 | ||
| 16 | + * memcpy 整个 LambBrcTilingData<12, 3> POD 完成, 故字段与 POD 严格一一对应。 | ||
| 17 | + */ | ||
| 18 | + | ||
| 19 | + | ||
| 20 | + | ||
| 21 | + | ||
| 22 | + | ||
| 23 | + | ||
| 24 | + | ||
| 25 | +namespace optiling { | ||
| 26 | +constexpr int32_t LAMB_APPLY_OPTIMIZER_ASSIGN_IN_NUM = 12; | ||
| 27 | +constexpr int32_t LAMB_APPLY_OPTIMIZER_ASSIGN_MAX_DIM = 8; | ||
| 28 | +constexpr int32_t LAMB_APPLY_OPTIMIZER_ASSIGN_STRIDE_NUM = LAMB_APPLY_OPTIMIZER_ASSIGN_IN_NUM * | ||
| 29 | + LAMB_APPLY_OPTIMIZER_ASSIGN_MAX_DIM; | ||
| 30 | + | ||
| 31 | +BEGIN_TILING_DATA_DEF(LambApplyOptimizerAssignTilingData) | ||
| 32 | +TILING_DATA_FIELD_DEF(uint64_t, totalNum); | ||
| 33 | +TILING_DATA_FIELD_DEF(uint64_t, totalRows); | ||
| 34 | +TILING_DATA_FIELD_DEF_ARR(uint64_t, LAMB_APPLY_OPTIMIZER_ASSIGN_STRIDE_NUM, effStride); | ||
| 35 | +TILING_DATA_FIELD_DEF_ARR(uint64_t, LAMB_APPLY_OPTIMIZER_ASSIGN_IN_NUM, srcBlockLen); | ||
| 36 | +TILING_DATA_FIELD_DEF(uint64_t, perCoreElems); | ||
| 37 | +TILING_DATA_FIELD_DEF(uint64_t, rowsPerCore); | ||
| 38 | +TILING_DATA_FIELD_DEF(uint32_t, tileLen); | ||
| 39 | +TILING_DATA_FIELD_DEF(uint32_t, blockLen); | ||
| 40 | +TILING_DATA_FIELD_DEF(uint32_t, rowsPerTile); | ||
| 41 | +TILING_DATA_FIELD_DEF(uint32_t, usedCoreNum); | ||
| 42 | +TILING_DATA_FIELD_DEF(uint32_t, splitAxis); | ||
| 43 | +TILING_DATA_FIELD_DEF(uint32_t, collapsedRank); | ||
| 44 | +TILING_DATA_FIELD_DEF(uint32_t, tilingKey); | ||
| 45 | +TILING_DATA_FIELD_DEF_ARR(uint32_t, LAMB_APPLY_OPTIMIZER_ASSIGN_IN_NUM, inKind); | ||
| 46 | +TILING_DATA_FIELD_DEF_ARR(uint32_t, LAMB_APPLY_OPTIMIZER_ASSIGN_MAX_DIM, outShape); | ||
| 47 | +TILING_DATA_FIELD_DEF_ARR(uint32_t, LAMB_APPLY_OPTIMIZER_ASSIGN_STRIDE_NUM, inShape); | ||
| 48 | +END_TILING_DATA_DEF; | ||
| 49 | + | ||
| 50 | +REGISTER_TILING_DATA_CLASS(LambApplyOptimizerAssign, LambApplyOptimizerAssignTilingData) | ||
| 51 | +} // namespace optiling | ||
| 52 | + | ||
| @@ -0,0 +1,16 @@ | |||
| 1 | +# ---------------------------------------------------------------------------- | ||
| 2 | +# Copyright (c) 2026 Huawei Technologies Co., Ltd. | ||
| 3 | +# This program is free software, you can redistribute it and/or modify it under the terms and conditions of | ||
| 4 | +# CANN Open Software License Agreement Version 2.0 (the "License"). | ||
| 5 | +# Please refer to the License for details. You may not use this file except in compliance with the License. | ||
| 6 | +# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, | ||
| 7 | +# INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE. | ||
| 8 | +# See LICENSE in the root of the software repository for the full text of the License. | ||
| 9 | +# ---------------------------------------------------------------------------- | ||
| 10 | + | ||
| 11 | +# 手写 regbase 内核自行下发 SetFlag/WaitFlag, 关闭自动插同步。 | ||
| 12 | +add_kernel_sources( | ||
| 13 | + KERNEL_SRC arch35/lamb_apply_optimizer_assign.cpp | ||
| 14 | + COMPUTE_UNITS ascend950 | ||
| 15 | + AUTO_SYNC false | ||
| 16 | +) | ||
| @@ -0,0 +1,48 @@ | |||
| 1 | +/** | ||
| 2 | + * Copyright (c) 2026 Huawei Technologies Co., Ltd. | ||
| 3 | + * This program is free software, you can redistribute it and/or modify it under the terms and conditions of | ||
| 4 | + * CANN Open Software License Agreement Version 2.0 (the "License"). | ||
| 5 | + * Please refer to the License for details. You may not use this file except in compliance with the License. | ||
| 6 | + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, | ||
| 7 | + * INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE. | ||
| 8 | + * See LICENSE in the root of the software repository for the full text of the License. | ||
| 9 | + */ | ||
| 10 | + | ||
| 11 | +/*! | ||
| 12 | + * \file lamb_apply_optimizer_assign.cpp | ||
| 13 | + * \brief LambApplyOptimizerAssign arch35 (Ascend950) kernel entry | ||
| 14 | + * | ||
| 15 | + * 手写 regbase 实现, 不走 ATVOSS: 12 入 3 出在 DAGSch 的 32 buffer 预算上编不过, | ||
| 16 | + * 且 ATVOSS 的 Vec::Brc 未实现导致 UB 广播档不可用(铺不平尾轴 1->n 与低秩补维)。 | ||
| 17 | + * 搬运/广播/分核骨架见 lamb_apply_common/op_kernel/arch35/lamb_brc_kernel.h。 | ||
| 18 | + * TilingKey: fp32=100 / fp16=200。 | ||
| 19 | + */ | ||
| 20 | + | ||
| 21 | + | ||
| 22 | + | ||
| 23 | + | ||
| 24 | +// 宏展开处需要一个文件作用域可见的具体类型名, 不能用函数内的局部别名。 | ||
| 25 | +using LambApplyOptimizerAssignTilingType = LambBrcTilingData<12, 3>; | ||
| 26 | + | ||
| 27 | +extern "C" __global__ __aicore__ void lamb_apply_optimizer_assign( | ||
| 28 | + GM_ADDR grad, GM_ADDR inputv, GM_ADDR inputm, GM_ADDR input4, GM_ADDR mul0_x, GM_ADDR mul1_x, GM_ADDR mul2_x, | ||
| 29 | + GM_ADDR mul3_x, GM_ADDR add2_y, GM_ADDR steps, GM_ADDR do_use_weight, GM_ADDR weight_decay_rate, GM_ADDR output0, | ||
| 30 | + GM_ADDR inputv_ref, GM_ADDR inputm_ref, GM_ADDR workspace, GM_ADDR tiling) | ||
| 31 | +{ | ||
| 32 | + KERNEL_TASK_TYPE_DEFAULT(KERNEL_TYPE_AIV_ONLY); | ||
| 33 | + REGISTER_TILING_DEFAULT(LambApplyOptimizerAssignTilingType); | ||
| 34 | + GET_TILING_DATA_WITH_STRUCT(LambApplyOptimizerAssignTilingType, tilingData, tiling); | ||
| 35 | + GM_ADDR inAddr[12] = {grad, inputv, inputm, input4, mul0_x, mul1_x, | ||
| 36 | + mul2_x, mul3_x, add2_y, steps, do_use_weight, weight_decay_rate}; | ||
| 37 | + GM_ADDR outAddr[3] = {output0, inputv_ref, inputm_ref}; | ||
| 38 | + AscendC::TPipe pipe; | ||
| 39 | + if (TILING_KEY_IS(100)) { | ||
| 40 | + LambBrc::BrcElementwiseKernel<float, 12, 3, LambApplyOptimizerAssignOp::LambApplyOptimizerAssignVf> op; | ||
| 41 | + op.Init(inAddr, outAddr, &tilingData, &pipe); | ||
| 42 | + op.Process(); | ||
| 43 | + } else if (TILING_KEY_IS(200)) { | ||
| 44 | + LambBrc::BrcElementwiseKernel<half, 12, 3, LambApplyOptimizerAssignOp::LambApplyOptimizerAssignVf> op; | ||
| 45 | + op.Init(inAddr, outAddr, &tilingData, &pipe); | ||
| 46 | + op.Process(); | ||
| 47 | + } | ||
| 48 | +} | ||
| @@ -1,108 +0,0 @@ | |||
| 1 | -/** | ||
| 2 | - * Copyright (c) 2026 Huawei Technologies Co., Ltd. | ||
| 3 | - * This program is free software, you can redistribute it and/or modify it under the terms and conditions of | ||
| 4 | - * CANN Open Software License Agreement Version 2.0 (the "License"). | ||
| 5 | - * Please refer to the License for details. You may not use this file except in compliance with the License. | ||
| 6 | - * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, | ||
| 7 | - * INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE. | ||
| 8 | - * See LICENSE in the root of the software repository for the full text of the License. | ||
| 9 | - */ | ||
| 10 | - | ||
| 11 | -/*! | ||
| 12 | - * \file lamb_apply_optimizer_assign_dag.h | ||
| 13 | - * \brief lamb_apply_optimizer_assign_dag head file | ||
| 14 | - */ | ||
| 15 | - | ||
| 16 | - | ||
| 17 | - | ||
| 18 | - | ||
| 19 | - | ||
| 20 | - | ||
| 21 | - | ||
| 22 | -namespace LambApplyOptimizerAssignOp { | ||
| 23 | -using namespace AscendC; | ||
| 24 | -using namespace Ops::Base; | ||
| 25 | -// In : 0 grad, 1 inputv(v), 2 inputm(m), 3 input3(param), 4 mul0_x(b1), 5 mul1_x(1-b1), | ||
| 26 | -// 6 mul2_x(b2), 7 mul3_x(1-b2), 8 add2_y(eps), 9 steps, 10 do_use_weight, 11 weight_decay_rate | ||
| 27 | -// Out: 0 update, 1 inputv(next_v, in-place), 2 inputm(next_m, in-place) | ||
| 28 | -// Compute in U (=float): half casts to float (div/sqrt/log/exp need float precision); fp32 native. | ||
| 29 | -template <typename T, typename U> | ||
| 30 | -struct LambApplyOptimizerAssignCompute { | ||
| 31 | - using CNegOne = MAKE_CONST(U, -1.0); | ||
| 32 | - using COne = MAKE_CONST(U, 1.0); | ||
| 33 | - | ||
| 34 | - using InGrad = Bind<Vec::CopyInBrc<T>, Placeholder::In0<T>>; | ||
| 35 | - using InV = Bind<Vec::CopyInBrc<T>, Placeholder::In1<T>>; | ||
| 36 | - using InM = Bind<Vec::CopyInBrc<T>, Placeholder::In2<T>>; | ||
| 37 | - using InParam = Bind<Vec::CopyInBrc<T>, Placeholder::In3<T>>; | ||
| 38 | - // 8 scalar [1] inputs: ScalarAttr + Duplicate (broadcast scalar to a tensor without a | ||
| 39 | - // double-buffered mte2 buffer) so downstream tensor ops match and we stay in buffer budget. | ||
| 40 | - using InB1 = Bind<Vec::Duplicate<T>, Placeholder::In4<T, Placeholder::ScalarAttr<true>>>; | ||
| 41 | - using InOmB1 = Bind<Vec::Duplicate<T>, Placeholder::In5<T, Placeholder::ScalarAttr<true>>>; | ||
| 42 | - using InB2 = Bind<Vec::Duplicate<T>, Placeholder::In6<T, Placeholder::ScalarAttr<true>>>; | ||
| 43 | - using InOmB2 = Bind<Vec::Duplicate<T>, Placeholder::In7<T, Placeholder::ScalarAttr<true>>>; | ||
| 44 | - using InEps = Bind<Vec::Duplicate<T>, Placeholder::In8<T, Placeholder::ScalarAttr<true>>>; | ||
| 45 | - using InSteps = Bind<Vec::Duplicate<T>, Placeholder::In9<T, Placeholder::ScalarAttr<true>>>; | ||
| 46 | - using InDoUse = Bind<Vec::Duplicate<T>, Placeholder::In10<T, Placeholder::ScalarAttr<true>>>; | ||
| 47 | - using InWd = Bind<Vec::Duplicate<T>, Placeholder::In11<T, Placeholder::ScalarAttr<true>>>; | ||
| 48 | - | ||
| 49 | - using Grad = Bind<Vec::Cast<U, T, 0>, InGrad>; | ||
| 50 | - using V = Bind<Vec::Cast<U, T, 0>, InV>; | ||
| 51 | - using M = Bind<Vec::Cast<U, T, 0>, InM>; | ||
| 52 | - using Param = Bind<Vec::Cast<U, T, 0>, InParam>; | ||
| 53 | - using B1 = Bind<Vec::Cast<U, T, 0>, InB1>; | ||
| 54 | - using OmB1 = Bind<Vec::Cast<U, T, 0>, InOmB1>; | ||
| 55 | - using B2 = Bind<Vec::Cast<U, T, 0>, InB2>; | ||
| 56 | - using OmB2 = Bind<Vec::Cast<U, T, 0>, InOmB2>; | ||
| 57 | - using Eps = Bind<Vec::Cast<U, T, 0>, InEps>; | ||
| 58 | - using Steps = Bind<Vec::Cast<U, T, 0>, InSteps>; | ||
| 59 | - using DoUse = Bind<Vec::Cast<U, T, 0>, InDoUse>; | ||
| 60 | - using Wd = Bind<Vec::Cast<U, T, 0>, InWd>; | ||
| 61 | - | ||
| 62 | - // next_v = g^2 * (1 - b2) + v * b2 | ||
| 63 | - using G2 = Bind<Vec::Mul<U>, Grad, Grad>; | ||
| 64 | - using NV1 = Bind<Vec::Mul<U>, G2, OmB2>; | ||
| 65 | - using NV2 = Bind<Vec::Mul<U>, V, B2>; | ||
| 66 | - using NextV = Bind<Vec::Add<U>, NV1, NV2>; | ||
| 67 | - using NextVCast = Bind<Vec::Cast<T, U, 1>, NextV>; | ||
| 68 | - | ||
| 69 | - // next_m = m * b1 + g * (1 - b1) | ||
| 70 | - using NM1 = Bind<Vec::Mul<U>, M, B1>; | ||
| 71 | - using NM2 = Bind<Vec::Mul<U>, Grad, OmB1>; | ||
| 72 | - using NextM = Bind<Vec::Add<U>, NM1, NM2>; | ||
| 73 | - using NextMCast = Bind<Vec::Cast<T, U, 1>, NextM>; | ||
| 74 | - | ||
| 75 | - // bias correction: b_corr = 1 - exp(steps * ln(b)) (b1/b2/steps are scalar [1]) | ||
| 76 | - using LnB1 = Bind<Vec::Log<U>, B1>; | ||
| 77 | - using ExpArg1 = Bind<Vec::Mul<U>, LnB1, Steps>; | ||
| 78 | - using B1Steps = Bind<Vec::Exp<U>, ExpArg1>; | ||
| 79 | - using NegB1Steps = Bind<Vec::Muls<U>, B1Steps, CNegOne>; | ||
| 80 | - using B1corr = Bind<Vec::Adds<U>, NegB1Steps, COne>; | ||
| 81 | - using LnB2 = Bind<Vec::Log<U>, B2>; | ||
| 82 | - using ExpArg2 = Bind<Vec::Mul<U>, LnB2, Steps>; | ||
| 83 | - using B2Steps = Bind<Vec::Exp<U>, ExpArg2>; | ||
| 84 | - using NegB2Steps = Bind<Vec::Muls<U>, B2Steps, CNegOne>; | ||
| 85 | - using B2corr = Bind<Vec::Adds<U>, NegB2Steps, COne>; | ||
| 86 | - | ||
| 87 | - // update = (next_m / b1corr) / (sqrt(next_v / b2corr) + eps) + param * wd * do_use_weight | ||
| 88 | - using MUnb = Bind<Vec::Div<U>, NextM, B1corr>; | ||
| 89 | - using VUnb = Bind<Vec::Div<U>, NextV, B2corr>; | ||
| 90 | - using SqrtVUnb = Bind<Vec::Sqrt<U>, VUnb>; | ||
| 91 | - using Den = Bind<Vec::Add<U>, SqrtVUnb, Eps>; | ||
| 92 | - using Upd0 = Bind<Vec::Div<U>, MUnb, Den>; | ||
| 93 | - using WdMul = Bind<Vec::Mul<U>, Param, Wd>; | ||
| 94 | - using Wdec = Bind<Vec::Mul<U>, WdMul, DoUse>; | ||
| 95 | - using Update = Bind<Vec::Add<U>, Wdec, Upd0>; | ||
| 96 | - using UpdateCast = Bind<Vec::Cast<T, U, 1>, Update>; | ||
| 97 | - | ||
| 98 | - using OpCopyOut0 = Bind<Vec::CopyOut<T>, Placeholder::Out0<T>, UpdateCast>; | ||
| 99 | - using OpCopyOut1 = Bind<Vec::CopyOut<T>, Placeholder::Out1<T>, NextVCast>; | ||
| 100 | - using OpCopyOut2 = Bind<Vec::CopyOut<T>, Placeholder::Out2<T>, NextMCast>; | ||
| 101 | - | ||
| 102 | - using Outputs = Elems<OpCopyOut0, OpCopyOut1, OpCopyOut2>; | ||
| 103 | - // 12 inputs + 3 outputs exceed the LEVEL_2 (double-buffered) 32-buffer budget; LEVEL_1 fits. | ||
| 104 | - using MemCfg = MemOptCfg<MemLevel::LEVEL_1>; | ||
| 105 | - using OpDag = DAGSch<Outputs, void, MemCfg>; | ||
| 106 | -}; | ||
| 107 | -} // namespace LambApplyOptimizerAssignOp | ||
| 108 | - | ||
| @@ -1,27 +0,0 @@ | |||
| 1 | -/** | ||
| 2 | - * Copyright (c) 2026 Huawei Technologies Co., Ltd. | ||
| 3 | - * This program is free software, you can redistribute it and/or modify it under the terms and conditions of | ||
| 4 | - * CANN Open Software License Agreement Version 2.0 (the "License"). | ||
| 5 | - * Please refer to the License for details. You may not use this file except in compliance with the License. | ||
| 6 | - * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, | ||
| 7 | - * INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE. | ||
| 8 | - * See LICENSE in the root of the software repository for the full text of the License. | ||
| 9 | - */ | ||
| 10 | - | ||
| 11 | -/*! | ||
| 12 | - * \file lamb_apply_optimizer_assign_tiling_key.h | ||
| 13 | - * \brief lamb_apply_optimizer_assign_tiling_key head file | ||
| 14 | - */ | ||
| 15 | - | ||
| 16 | - | ||
| 17 | - | ||
| 18 | - | ||
| 19 | - | ||
| 20 | - | ||
| 21 | -using namespace Ops::Base; | ||
| 22 | -// 算子自定义的tiling key字段 | ||
| 23 | -ASCENDC_TPL_ARGS_DECL(LambApplyOptimizerAssign, BRC_NDDMA_SCH_MODE_KEY_DECL(schMode)); | ||
| 24 | - | ||
| 25 | -ASCENDC_TPL_SEL(ASCENDC_TPL_ARGS_SEL(BRC_NDDMA_SCH_MODE_KEY_SEL(schMode))); | ||
| 26 | - | ||
| 27 | - | ||
| @@ -0,0 +1,152 @@ | |||
| 1 | +/** | ||
| 2 | + * Copyright (c) 2026 Huawei Technologies Co., Ltd. | ||
| 3 | + * This program is free software, you can redistribute it and/or modify it under the terms and conditions of | ||
| 4 | + * CANN Open Software License Agreement Version 2.0 (the "License"). | ||
| 5 | + * Please refer to the License for details. You may not use this file except in compliance with the License. | ||
| 6 | + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, | ||
| 7 | + * INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE. | ||
| 8 | + * See LICENSE in the root of the software repository for the full text of the License. | ||
| 9 | + */ | ||
| 10 | + | ||
| 11 | +/*! | ||
| 12 | + * \file lamb_apply_optimizer_assign_vf.h | ||
| 13 | + * \brief LambApplyOptimizerAssign 的 regbase 计算段(12 入 3 出) | ||
| 14 | + * | ||
| 15 | + * 与 golden 同运算序: | ||
| 16 | + * next_v = v*b2 + (g*g)*(1-b2) -> out1 | ||
| 17 | + * next_m = m*b1 + g*(1-b1) -> out2 | ||
| 18 | + * b_corr = 1 + (-1)*exp(ln(b)*steps) (Log/Mul/Exp/Muls/Adds 五步, 不用幂运算: | ||
| 19 | + * b 接近 1 时 ln(b) 有相消损失, 直接幂得不到同一中间量) | ||
| 20 | + * update = (next_m/b1corr)/(sqrt(next_v/b2corr)+eps) + param*wd*do_use -> out0 | ||
| 21 | + */ | ||
| 22 | + | ||
| 23 | + | ||
| 24 | + | ||
| 25 | + | ||
| 26 | + | ||
| 27 | + | ||
| 28 | +namespace LambApplyOptimizerAssignOp { | ||
| 29 | +using namespace AscendC; | ||
| 30 | + | ||
| 31 | +enum : uint32_t { | ||
| 32 | + IN_GRAD = 0, | ||
| 33 | + IN_V = 1, | ||
| 34 | + IN_M = 2, | ||
| 35 | + IN_PARAM = 3, | ||
| 36 | + IN_B1 = 4, | ||
| 37 | + IN_OMB1 = 5, | ||
| 38 | + IN_B2 = 6, | ||
| 39 | + IN_OMB2 = 7, | ||
| 40 | + IN_EPS = 8, | ||
| 41 | + IN_STEPS = 9, | ||
| 42 | + IN_DOUSE = 10, | ||
| 43 | + IN_WD = 11 | ||
| 44 | +}; | ||
| 45 | + | ||
| 46 | +struct LambApplyOptimizerAssignVf { | ||
| 47 | + template <typename T> | ||
| 48 | + static __aicore__ inline void Run(__local_mem__ T** in, __local_mem__ T** out, __local_mem__ float** scratch, | ||
| 49 | + uint32_t count) | ||
| 50 | + { | ||
| 51 | + uint16_t vfTimes = static_cast<uint16_t>(count / LambBrc::LAMB_VF_LEN); | ||
| 52 | + uint32_t tail = count % LambBrc::LAMB_VF_LEN; | ||
| 53 | + uint16_t tailTimes = (tail > 0) ? 1 : 0; | ||
| 54 | + | ||
| 55 | + __local_mem__ T* pG = in[IN_GRAD]; | ||
| 56 | + __local_mem__ T* pV = in[IN_V]; | ||
| 57 | + __local_mem__ T* pM = in[IN_M]; | ||
| 58 | + __local_mem__ T* pParam = in[IN_PARAM]; | ||
| 59 | + __local_mem__ T* pB1 = in[IN_B1]; | ||
| 60 | + __local_mem__ T* pOmB1 = in[IN_OMB1]; | ||
| 61 | + __local_mem__ T* pB2 = in[IN_B2]; | ||
| 62 | + __local_mem__ T* pOmB2 = in[IN_OMB2]; | ||
| 63 | + __local_mem__ T* pEps = in[IN_EPS]; | ||
| 64 | + __local_mem__ T* pSteps = in[IN_STEPS]; | ||
| 65 | + __local_mem__ T* pDoUse = in[IN_DOUSE]; | ||
| 66 | + __local_mem__ T* pWd = in[IN_WD]; | ||
| 67 | + __local_mem__ T* update = out[0]; | ||
| 68 | + __local_mem__ T* nextVOut = out[1]; | ||
| 69 | + __local_mem__ T* nextMOut = out[2]; | ||
| 70 | + // 中间量以 fp32 暂存, 不经 T 的舍入 —— 与单段实现数值完全一致。 | ||
| 71 | + __local_mem__ float* sNextV = scratch[0]; | ||
| 72 | + __local_mem__ float* sNextM = scratch[1]; | ||
| 73 | + | ||
| 74 | + // ---- 段1: next_v / next_m ---- | ||
| 75 | + // 拆成两段是因为全量精确档(0ULP/FTZ_FALSE)展开后单段指令过多, 循环回边偏移超出 | ||
| 76 | + // scbzi 立即数范围 [-512,511]。拆段后每段都在范围内, 且不必降低任何一处的精度档。 | ||
| 77 | + __VEC_SCOPE__ | ||
| 78 | + { | ||
| 79 | + Reg::RegTensor<float> vA, vB, vC, vNextV, vNextM; | ||
| 80 | + Reg::MaskReg maskAll = Reg::CreateMask<float, Reg::MaskPattern::ALL>(); | ||
| 81 | + Reg::MaskReg maskT = Reg::UpdateMask<float>(tail); | ||
| 82 | + for (uint16_t vfIdx = 0; vfIdx < vfTimes + tailTimes; vfIdx++) { | ||
| 83 | + uint32_t off = vfIdx * LambBrc::LAMB_VF_LEN; | ||
| 84 | + Reg::MaskReg preg = (vfIdx < vfTimes) ? maskAll : maskT; | ||
| 85 | + // next_v = v*b2 + (g*g)*(1-b2) | ||
| 86 | + LambBrc::Load<T>(pV, vA, preg, off); | ||
| 87 | + LambBrc::Load<T>(pB2, vB, preg, off); | ||
| 88 | + Reg::Mul(vNextV, vA, vB, preg); | ||
| 89 | + LambBrc::Load<T>(pG, vA, preg, off); | ||
| 90 | + Reg::Mul(vC, vA, vA, preg); | ||
| 91 | + LambBrc::Load<T>(pOmB2, vB, preg, off); | ||
| 92 | + Reg::Mul(vC, vC, vB, preg); | ||
| 93 | + Reg::Add(vNextV, vNextV, vC, preg); | ||
| 94 | + LambBrc::Store<T>(nextVOut, vNextV, preg, off); | ||
| 95 | + Reg::StoreAlign<float, Reg::StoreDist::DIST_NORM>(sNextV + off, vNextV, preg); | ||
| 96 | + // next_m = m*b1 + g*(1-b1) (vA 仍是 g) | ||
| 97 | + LambBrc::Load<T>(pOmB1, vB, preg, off); | ||
| 98 | + Reg::Mul(vC, vA, vB, preg); | ||
| 99 | + LambBrc::Load<T>(pM, vA, preg, off); | ||
| 100 | + LambBrc::Load<T>(pB1, vB, preg, off); | ||
| 101 | + Reg::Mul(vNextM, vA, vB, preg); | ||
| 102 | + Reg::Add(vNextM, vNextM, vC, preg); | ||
| 103 | + LambBrc::Store<T>(nextMOut, vNextM, preg, off); | ||
| 104 | + Reg::StoreAlign<float, Reg::StoreDist::DIST_NORM>(sNextM + off, vNextM, preg); | ||
| 105 | + } | ||
| 106 | + } | ||
| 107 | + | ||
| 108 | + // ---- 段2: 偏差校正 + update ---- | ||
| 109 | + __VEC_SCOPE__ | ||
| 110 | + { | ||
| 111 | + Reg::RegTensor<float> vA, vB, vC, vNextV, vNextM, vB1c, vB2c, vT1, vT2; | ||
| 112 | + Reg::MaskReg cmpExpm1; | ||
| 113 | + Reg::MaskReg maskAll = Reg::CreateMask<float, Reg::MaskPattern::ALL>(); | ||
| 114 | + Reg::MaskReg maskT = Reg::UpdateMask<float>(tail); | ||
| 115 | + for (uint16_t vfIdx = 0; vfIdx < vfTimes + tailTimes; vfIdx++) { | ||
| 116 | + uint32_t off = vfIdx * LambBrc::LAMB_VF_LEN; | ||
| 117 | + Reg::MaskReg preg = (vfIdx < vfTimes) ? maskAll : maskT; | ||
| 118 | + Reg::LoadAlign<float, Reg::LoadDist::DIST_NORM>(vNextV, sNextV + off); | ||
| 119 | + Reg::LoadAlign<float, Reg::LoadDist::DIST_NORM>(vNextM, sNextM + off); | ||
| 120 | + // Ln/Exp 取 1ULP + FTZ_TRUE: 默认 INTRINSIC 是硬件近似档, 而 b_corr 的抵消会把 exp 的误差 | ||
| 121 | + // 放大 1/(1-exp) 倍; 取 FTZ_TRUE 是因为 ln(b)*steps 低于 exp 下溢边界时真值本就应为 0。 | ||
| 122 | + // 1 - b^steps 由 OneMinusExp 计算(见 lamb_brc_kernel.h)。 | ||
| 123 | + // b1corr = 1 - exp(ln(b1)*steps) | ||
| 124 | + LambBrc::Load<T>(pSteps, vC, preg, off); | ||
| 125 | + LambBrc::Load<T>(pB1, vB, preg, off); | ||
| 126 | + Ln<float, &LambBrc::LAMB_PRECISE_LN_FTZT>(vB1c, vB, preg); | ||
| 127 | + Reg::Mul(vB1c, vB1c, vC, preg); | ||
| 128 | + LambBrc::OneMinusExp(vB1c, vB1c, vT1, vT2, cmpExpm1, preg); | ||
| 129 | + LambBrc::Load<T>(pB2, vB, preg, off); | ||
| 130 | + Ln<float, &LambBrc::LAMB_PRECISE_LN_FTZT>(vB2c, vB, preg); | ||
| 131 | + Reg::Mul(vB2c, vB2c, vC, preg); | ||
| 132 | + LambBrc::OneMinusExp(vB2c, vB2c, vT1, vT2, cmpExpm1, preg); | ||
| 133 | + // update = (next_m/b1corr)/(sqrt(next_v/b2corr)+eps) + param*wd*do_use | ||
| 134 | + Reg::Div<float, &LambBrc::LAMB_PRECISE_DIV>(vA, vNextM, vB1c, preg); | ||
| 135 | + Reg::Div<float, &LambBrc::LAMB_PRECISE_DIV>(vB, vNextV, vB2c, preg); | ||
| 136 | + Reg::Sqrt<float, &LambBrc::LAMB_PRECISE_SQRT>(vB, vB, preg); | ||
| 137 | + LambBrc::Load<T>(pEps, vC, preg, off); | ||
| 138 | + Reg::Add(vB, vB, vC, preg); | ||
| 139 | + Reg::Div<float, &LambBrc::LAMB_PRECISE_DIV>(vA, vA, vB, preg); | ||
| 140 | + LambBrc::Load<T>(pParam, vB, preg, off); | ||
| 141 | + LambBrc::Load<T>(pWd, vC, preg, off); | ||
| 142 | + Reg::Mul(vB, vB, vC, preg); | ||
| 143 | + LambBrc::Load<T>(pDoUse, vC, preg, off); | ||
| 144 | + Reg::Mul(vB, vB, vC, preg); | ||
| 145 | + Reg::Add(vA, vA, vB, preg); | ||
| 146 | + LambBrc::Store<T>(update, vA, preg, off); | ||
| 147 | + } | ||
| 148 | + } | ||
| 149 | + } | ||
| 150 | +}; | ||
| 151 | +} // namespace LambApplyOptimizerAssignOp | ||
| 152 | + | ||
| @@ -1,44 +0,0 @@ | |||
| 1 | -/** | ||
| 2 | - * Copyright (c) 2026 Huawei Technologies Co., Ltd. | ||
| 3 | - * This program is free software, you can redistribute it and/or modify it under the terms and conditions of | ||
| 4 | - * CANN Open Software License Agreement Version 2.0 (the "License"). | ||
| 5 | - * Please refer to the License for details. You may not use this file except in compliance with the License. | ||
| 6 | - * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, | ||
| 7 | - * INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE. | ||
| 8 | - * See LICENSE in the root of the software repository for the full text of the License. | ||
| 9 | - */ | ||
| 10 | - | ||
| 11 | -/*! | ||
| 12 | - * \file lamb_apply_optimizer_assign.cpp | ||
| 13 | - * \brief lamb_apply_optimizer_assign.cpp | ||
| 14 | - */ | ||
| 15 | - | ||
| 16 | - | ||
| 17 | - | ||
| 18 | - | ||
| 19 | - | ||
| 20 | - | ||
| 21 | -using namespace AscendC; | ||
| 22 | -using namespace Ops::Base; | ||
| 23 | - | ||
| 24 | -template <uint64_t schMode> | ||
| 25 | -__global__ __aicore__ void lamb_apply_optimizer_assign(GM_ADDR grad, GM_ADDR inputv, GM_ADDR inputm, GM_ADDR input3, | ||
| 26 | - GM_ADDR mul0_x, GM_ADDR mul1_x, GM_ADDR mul2_x, GM_ADDR mul3_x, | ||
| 27 | - GM_ADDR add2_y, GM_ADDR steps, GM_ADDR do_use_weight, | ||
| 28 | - GM_ADDR weight_decay_rate, GM_ADDR output0, GM_ADDR output1, | ||
| 29 | - GM_ADDR output2, GM_ADDR workspace, GM_ADDR tiling) | ||
| 30 | -{ | ||
| 31 | - KERNEL_TASK_TYPE_DEFAULT(KERNEL_TYPE_AIV_ONLY); | ||
| 32 | - // half/fp32 both compute in float (div/sqrt/log/exp need float precision). | ||
| 33 | - if constexpr (std::is_same<DTYPE_GRAD, half>::value) { | ||
| 34 | - using OpDag = LambApplyOptimizerAssignOp::LambApplyOptimizerAssignCompute<half, float>::OpDag; | ||
| 35 | - BroadcastSch<schMode, OpDag> sch(tiling); | ||
| 36 | - sch.Process(grad, inputv, inputm, input3, mul0_x, mul1_x, mul2_x, mul3_x, add2_y, steps, do_use_weight, | ||
| 37 | - weight_decay_rate, output0, output1, output2); | ||
| 38 | - } else { | ||
| 39 | - using OpDag = LambApplyOptimizerAssignOp::LambApplyOptimizerAssignCompute<float, float>::OpDag; | ||
| 40 | - BroadcastSch<schMode, OpDag> sch(tiling); | ||
| 41 | - sch.Process(grad, inputv, inputm, input3, mul0_x, mul1_x, mul2_x, mul3_x, add2_y, steps, do_use_weight, | ||
| 42 | - weight_decay_rate, output0, output1, output2); | ||
| 43 | - } | ||
| 44 | -} | ||
| @@ -28,13 +28,17 @@ __golden__ = { | |||
| 28 | 28 | ||
| 29 | def _scalars(*xs): | 29 | def _scalars(*xs): |
| 30 | """取标量并落到 float32 torch 标量张量上。不能返回 Python float——那是 fp64,标量 | 30 | """取标量并落到 float32 torch 标量张量上。不能返回 Python float——那是 fp64,标量 |
| 31 | - 运算会被抬到双精度,而算子在 fp32 上算(A2 的 TBE compute 里 dtype='float32', | 31 | + 运算会被抬到双精度,而 arch35 内核的计算类型 U = float(fp16 输入 unpack 成 fp32 再算)。 |
| 32 | + 注:A2 并**不**升精度 —— canndev 的 tbe impl 是 tvm.placeholder(dtype=input_dtype),全程无 cast_to, | ||
| 33 | + fp16 输入就在 fp16 上做 vmul/vdiv/vsqrt。此处跟随 arch35 的计算类型,不跟随 A2( | ||
| 32 | arch35 DAG 的计算类型 U = float)。numpy 只用于取值与 dtype 转换。""" | 34 | arch35 DAG 的计算类型 U = float)。numpy 只用于取值与 dtype 转换。""" |
| 33 | # 标量落成 **0 维 float64**: torch 类型提升里 0 维不抬 dim>0 张量的档, | 35 | # 标量落成 **0 维 float64**: torch 类型提升里 0 维不抬 dim>0 张量的档, |
| 34 | # 故数据 fp32 时结果仍 fp32、被 Promote 成 fp64 时标量自动跟到 fp64。 | 36 | # 故数据 fp32 时结果仍 fp32、被 Promote 成 fp64 时标量自动跟到 fp64。 |
| 35 | - return tuple( | 37 | + # A2 语义: 这些"系数"输入是**可广播的 ND Tensor**(canndev ops/built-in/tbe/impl/lamb_*.py |
| 36 | - torch.from_numpy(np.asarray(x, "float64").reshape(-1)[:1])[0] for x in xs | 38 | + # 每步 mul/sub/div 都先 shape_util.broadcast_shapes 再 tbe.broadcast)。原先 reshape(-1)[:1] |
| 37 | - ) | 39 | + # 只取首元素, 传多元素张量时静默按首元素计算 —— 与内核的广播实现不一致, 广播档必然假红。 |
| 40 | + # 改为返回完整张量交给 torch 自然广播: 形状 (1,) 的行为与原标量完全一致, 故常规档不变。 | ||
| 41 | + return tuple(_t(x) for x in xs) | ||
| 38 | 42 | ||
| 39 | 43 | ||
| 40 | def _t(x): | 44 | def _t(x): |
| @@ -69,7 +73,7 @@ def lamb_apply_optimizer_assign_golden( | |||
| 69 | ): | 73 | ): |
| 70 | """Golden for LambApplyOptimizerAssign. Params follow lamb_apply_optimizer_assign_def.cpp (without outputs). All inputs are numpy.ndarray. | 74 | """Golden for LambApplyOptimizerAssign. Params follow lamb_apply_optimizer_assign_def.cpp (without outputs). All inputs are numpy.ndarray. |
| 71 | 75 | ||
| 72 | - Computed by composing torch tensor ops (torch.add/torch.addcmul/torch.sqrt) instead of a | 76 | + Computed by composing torch tensor ops (torch.mul/torch.add/torch.sqrt/torch.expm1) instead of a |
| 73 | hand-written numpy formula: red line R3 requires the golden to be a competitor-operator | 77 | hand-written numpy formula: red line R3 requires the golden to be a competitor-operator |
| 74 | composition, and a naive numpy expression tends to make exactly the same rounding mistakes | 78 | composition, and a naive numpy expression tends to make exactly the same rounding mistakes |
| 75 | as the kernel under test, which would disguise a precision shortfall as a pass. | 79 | as the kernel under test, which would disguise a precision shortfall as a pass. |
| @@ -83,13 +87,16 @@ def lamb_apply_optimizer_assign_golden( | |||
| 83 | # 与算子定义的「Muls 再 Add」两步舍入不是同一个运算序列。golden 要如实转写定义。 | 87 | # 与算子定义的「Muls 再 Add」两步舍入不是同一个运算序列。golden 要如实转写定义。 |
| 84 | next_v = v * b2 + (g * g) * omb2 | 88 | next_v = v * b2 + (g * g) * omb2 |
| 85 | next_m = m * b1 + g * omb1 | 89 | next_m = m * b1 + g * omb1 |
| 86 | - # 偏差校正按内核的算法写:arch35 DAG 用 Log/Mul/Exp 三条指令实现幂 | 90 | + # 偏差校正 corr = 1 - b^steps。**不能**照抄内核的 `1 + (-1)*exp(ln(b)*steps)` 指令序列: |
| 87 | - # LnB1=Log(B1); ExpArg1=Mul(LnB1,Steps); B1Steps=Exp(ExpArg1) | 91 | + # 当 ln(b)*steps -> 0 时 exp(.) -> 1,该式是灾难性抵消 —— fp32 在 1.0 附近的间距是 6e-8, |
| 88 | - # NegB1Steps=Muls(B1Steps,-1); B1corr=Adds(NegB1Steps,1) | 92 | + # 结果为 1e-6 量级时只剩 4 位有效位。照抄会让 golden 和被测一样失准,失去参照资格 |
| 89 | - # 而不是 b1 ** t。两者数学等价、浮点下不等价:Log/Exp 各是标称 1 ULP 的单指令, | 93 | + # (精度标准 §4.5 要求"以更高精度的 CPU 实现为真值";本文件 R3 注释也写明 golden 不得 |
| 90 | - # 且 b1 接近 1 时 ln(b1) 有相消损失,直接幂运算得不到同一个中间量。 | 94 | + # 复制被测的舍入错误)。 |
| 91 | - b1_corr = 1.0 + (-1.0) * torch.exp(torch.log(b1) * t) | 95 | + # 用 expm1 在 fp64 上算再落回工作精度:fp32 能精确表示 1e-6 量级,丢精度的是那步减法, |
| 92 | - b2_corr = 1.0 + (-1.0) * torch.exp(torch.log(b2) * t) | 96 | + # 不是表示能力。内核侧同样绕开该减法(小 |x| 走 Taylor 支),两边才在同一个真值上。 |
| 97 | + _hi = torch.float64 | ||
| 98 | + b1_corr = (-torch.expm1(torch.log(b1.to(_hi)) * t.to(_hi))).to(b1.dtype) | ||
| 99 | + b2_corr = (-torch.expm1(torch.log(b2.to(_hi)) * t.to(_hi))).to(b2.dtype) | ||
| 93 | update = (next_m / b1_corr) / (torch.sqrt(next_v / b2_corr) + eps) + w * wd * du | 100 | update = (next_m / b1_corr) / (torch.sqrt(next_v / b2_corr) + eps) + w * wd * du |
| 94 | return [ | 101 | return [ |
| 95 | update.numpy().astype(dt), | 102 | update.numpy().astype(dt), |
| @@ -125,8 +132,32 @@ def _tp_t(x): | |||
| 125 | ) | 132 | ) |
| 126 | 133 | ||
| 127 | 134 | ||
| 135 | +def _tp_widen(t): | ||
| 136 | + """按 NPU 的加宽行为把三方入参落到**内核计算类型 U = float32**。 | ||
| 137 | + | ||
| 138 | + 规范依据 ttk_golden_logic.md §四/§五「浮点 + 三方」一格: 三方腿"按 NPU 加宽行为同步 cast"。 | ||
| 139 | + 内核对 fp16 输入 unpack 成 fp32 计算(dag.h 的 `Cast<U, T, 0>`, U = float), 对 fp32 输入 | ||
| 140 | + 原生 fp32, 两种情况计算类型都是 fp32, 故此处一律落到 fp32。 | ||
| 141 | + | ||
| 142 | + **不能只处理 fp16**: cross_check 下 TTK 会把 fp32 Promote 成 fp64 下发, 原样透传会让三方腿 | ||
| 143 | + 在 fp64 上算 —— 内核 fp32 溢出/下溢的地方它都不溢出, 两条腿不在同一精度上, 比值无意义。 | ||
| 144 | + 整型不动: 内核对 int 是原生/int32 累加, 走 fp32 会抹掉 >2^24 的低位。 | ||
| 145 | + """ | ||
| 146 | + return t.to(torch.float32) if t.is_floating_point() else t | ||
| 147 | + | ||
| 148 | + | ||
| 149 | +def _tp_narrow(outs, dt): | ||
| 150 | + """出口复刻内核的 `Cast<T, U, 1>`: 窄回算子**声明**的 dtype T(不是 Promote 后的 dtype)。 | ||
| 151 | + | ||
| 152 | + 少了这一步或窄错目标, 三方腿会与走 Promote 的 golden 逐位相等 —— 双标杆塌成单标杆, | ||
| 153 | + 三比值分母被 safe_div 的 small_value 夹底, mare/rmse 恒为 1.0, 阈值永不触发。 | ||
| 154 | + """ | ||
| 155 | + return [o.to(dt) if o.is_floating_point() else o for o in outs] | ||
| 156 | + | ||
| 157 | + | ||
| 128 | def _tp_s(x): | 158 | def _tp_s(x): |
| 129 | - return _tp_t(x).reshape(-1)[0] | 159 | + # 同 _scalars: 三方腿也必须广播, 不能只取首元素 |
| 160 | + return _tp_t(x) | ||
| 130 | 161 | ||
| 131 | 162 | ||
| 132 | class _LambApplyOptimizerAssignCompose: | 163 | class _LambApplyOptimizerAssignCompose: |
| @@ -164,8 +195,11 @@ class _LambApplyOptimizerAssignCompose: | |||
| 164 | # 不能用 addcmul 的 FMA 单次舍入, 否则竞品凭空更准, ratio 误判内核。 | 195 | # 不能用 addcmul 的 FMA 单次舍入, 否则竞品凭空更准, ratio 误判内核。 |
| 165 | next_v = v * b2 + (g * g) * omb2 | 196 | next_v = v * b2 + (g * g) * omb2 |
| 166 | next_m = m * b1 + g * omb1 | 197 | next_m = m * b1 + g * omb1 |
| 167 | - b1_corr = 1.0 - torch.pow(b1, t) | 198 | + # 竞品的精确写法: torch 自带 expm1, 1-b^t 用 -expm1(log(b)*t) 避开对 1.0 的减法。 |
| 168 | - b2_corr = 1.0 - torch.pow(b2, t) | 199 | + # 若写成 1-pow(b,t), b^t->1 时抵消归零 -> 除零 -> 整片 NaN, 三方腿会比 NPU 和 golden |
| 200 | + # 都差, 成为稻草人标杆 —— 比值 err_npu/err_third 反而更易通过, 是放水不是严格。 | ||
| 201 | + b1_corr = -torch.expm1(torch.log(b1) * t) | ||
| 202 | + b2_corr = -torch.expm1(torch.log(b2) * t) | ||
| 169 | update = (next_m / b1_corr) / (torch.sqrt(next_v / b2_corr) + eps) + w * wd * du | 203 | update = (next_m / b1_corr) / (torch.sqrt(next_v / b2_corr) + eps) + w * wd * du |
| 170 | return [update, next_v, next_m] | 204 | return [update, next_v, next_m] |
| 171 | 205 | ||
| @@ -184,6 +218,16 @@ class _LambApplyOptimizerAssignCompose: | |||
| 184 | 218 | ||
| 185 | 219 | ||
| 186 | def _kf_widen(a, seen): | 220 | def _kf_widen(a, seen): |
| 221 | + """按 NPU 的加宽行为把三方入参加宽, 并记下算子声明的 dtype T 供出口窄回。 | ||
| 222 | + | ||
| 223 | + 三方腿拿到的是**原始 dtype T**(TTK 的 Promote 只作用于 golden, 见 profiling.py 的 | ||
| 224 | + golden_mode_override; 三方腿走 _xpu_inputs -> original_input_arrays), 故这里按 T 判断: | ||
| 225 | + - T = fp16/bf16: 内核 unpack 成 fp32 全程不落回(dag.h 的 Cast<U, T, 0>, U = float), | ||
| 226 | + 而 torch 只在单个算子内部用 opmath=float、算子之间每步落回 T。不加宽就等于拿 | ||
| 227 | + "逐步截断的实现"当竞品, 与被测内核不是同一个算法 -> 加宽到 fp32, 出口窄回 T。 | ||
| 228 | + - T = fp32: 内核计算类型 U 即 fp32, **不加宽**; torch 同样原生 fp32 -> 原样不动。 | ||
| 229 | + - 整型: 内核原生/int32 累加, 走 fp32 会抹掉 >2^24 的低位 -> 不动。 | ||
| 230 | + """ | ||
| 187 | if isinstance(a, (list, tuple)): | 231 | if isinstance(a, (list, tuple)): |
| 188 | return type(a)(_kf_widen(x, seen) for x in a) | 232 | return type(a)(_kf_widen(x, seen) for x in a) |
| 189 | if isinstance(a, torch.Tensor): | 233 | if isinstance(a, torch.Tensor): |
| @@ -206,6 +250,7 @@ def _kf_widen(a, seen): | |||
| 206 | 250 | ||
| 207 | 251 | ||
| 208 | def _kf_narrow(o, dt): | 252 | def _kf_narrow(o, dt): |
| 253 | + """出口复刻内核的 `Cast<T, U, 1>`: 窄回算子声明的 dtype T。T = fp32 时无需窄回。""" | ||
| 209 | if isinstance(o, (list, tuple)): | 254 | if isinstance(o, (list, tuple)): |
| 210 | return type(o)(_kf_narrow(x, dt) for x in o) | 255 | return type(o)(_kf_narrow(x, dt) for x in o) |
| 211 | if isinstance(o, torch.Tensor) and o.is_floating_point(): | 256 | if isinstance(o, torch.Tensor) and o.is_floating_point(): |
Moptim/lamb_apply_optimizer_assign/tests/ut/op_host/test_lamb_apply_optimizer_assign_infershape.cpp+27-5
| @@ -107,14 +107,15 @@ namespace { | |||
| 107 | // 按 grad / inputv / inputm / input3 四个张量的形状跑一次 infershape, | 107 | // 按 grad / inputv / inputm / input3 四个张量的形状跑一次 infershape, |
| 108 | // 标量输入统一用 {1}。用于核对 infershape 与 tiling 的支持范围是否一致。 | 108 | // 标量输入统一用 {1}。用于核对 infershape 与 tiling 的支持范围是否一致。 |
| 109 | ge::graphStatus RunInferShape(const gert::Shape& grad, const gert::Shape& inputv, const gert::Shape& inputm, | 109 | ge::graphStatus RunInferShape(const gert::Shape& grad, const gert::Shape& inputv, const gert::Shape& inputm, |
| 110 | - const gert::Shape& input3, gert::Shape* out0, gert::Shape* out1, gert::Shape* out2) | 110 | + const gert::Shape& input3, gert::Shape* out0, gert::Shape* out1, gert::Shape* out2, |
| 111 | + const gert::Shape& scalar = gert::Shape({1})) | ||
| 111 | { | 112 | { |
| 112 | auto inferShapeFunc = gert::OpImplRegistry::GetInstance().GetOpImpl("LambApplyOptimizerAssign")->infer_shape; | 113 | auto inferShapeFunc = gert::OpImplRegistry::GetInstance().GetOpImpl("LambApplyOptimizerAssign")->infer_shape; |
| 113 | gert::Shape g = grad; | 114 | gert::Shape g = grad; |
| 114 | gert::Shape v = inputv; | 115 | gert::Shape v = inputv; |
| 115 | gert::Shape m = inputm; | 116 | gert::Shape m = inputm; |
| 116 | gert::Shape p = input3; | 117 | gert::Shape p = input3; |
| 117 | - gert::Shape s = {1}; | 118 | + gert::Shape s = scalar; |
| 118 | auto holder = gert::InferShapeContextFaker() | 119 | auto holder = gert::InferShapeContextFaker() |
| 119 | .NodeIoNum(12, 3) | 120 | .NodeIoNum(12, 3) |
| 120 | .IrInstanceNum({1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}) | 121 | .IrInstanceNum({1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}) |
| @@ -159,11 +160,15 @@ TEST_F(LambApplyOptimizerAssignProtoTest, moment_shape_decides_output_shape) | |||
| 159 | ASSERT_EQ(Ops::Base::ToString(o2), Ops::Base::ToString(expect)); | 160 | ASSERT_EQ(Ops::Base::ToString(o2), Ops::Base::ToString(expect)); |
| 160 | } | 161 | } |
| 161 | 162 | ||
| 162 | -// grad 不参与广播:小于动量形状同样拒收(底层广播模板不支持对 In0 广播)。 | 163 | +// grad 与其余输入一样参与广播:小于动量形状时按右对齐广播进动量形状,须放行。 |
| 163 | -TEST_F(LambApplyOptimizerAssignProtoTest, grad_smaller_than_moment_is_rejected) | 164 | +TEST_F(LambApplyOptimizerAssignProtoTest, grad_broadcast_into_moment_is_accepted) |
| 164 | { | 165 | { |
| 165 | gert::Shape o0 = {}, o1 = {}, o2 = {}; | 166 | gert::Shape o0 = {}, o1 = {}, o2 = {}; |
| 166 | - ASSERT_EQ(RunInferShape({1, 1024}, {512, 1024}, {512, 1024}, {512, 1024}, &o0, &o1, &o2), ge::GRAPH_FAILED); | 167 | + gert::Shape expect = {512, 1024}; |
| 168 | + ASSERT_EQ(RunInferShape({1, 1024}, {512, 1024}, {512, 1024}, {512, 1024}, &o0, &o1, &o2), ge::GRAPH_SUCCESS); | ||
| 169 | + ASSERT_EQ(Ops::Base::ToString(o0), Ops::Base::ToString(expect)); | ||
| 170 | + ASSERT_EQ(Ops::Base::ToString(o1), Ops::Base::ToString(expect)); | ||
| 171 | + ASSERT_EQ(Ops::Base::ToString(o2), Ops::Base::ToString(expect)); | ||
| 167 | } | 172 | } |
| 168 | 173 | ||
| 169 | // input3 是唯一参与广播的输入:小于动量形状时按右对齐广播,须放行。 | 174 | // input3 是唯一参与广播的输入:小于动量形状时按右对齐广播,须放行。 |
| @@ -196,6 +201,23 @@ TEST_F(LambApplyOptimizerAssignProtoTest, inputv_inputm_mismatch_is_rejected) | |||
| 196 | ASSERT_EQ(RunInferShape({512, 1024}, {512, 1024}, {1, 1024}, {512, 1024}, &o0, &o1, &o2), ge::GRAPH_FAILED); | 201 | ASSERT_EQ(RunInferShape({512, 1024}, {512, 1024}, {1, 1024}, {512, 1024}, &o0, &o1, &o2), ge::GRAPH_FAILED); |
| 197 | } | 202 | } |
| 198 | 203 | ||
| 204 | +// 系数输入不再限定为标量: 与动量同形的系数张量须放行(对齐 A2 的可广播 ND Tensor 声明)。 | ||
| 205 | +TEST_F(LambApplyOptimizerAssignProtoTest, non_scalar_coefficient_is_accepted) | ||
| 206 | +{ | ||
| 207 | + gert::Shape o0 = {}, o1 = {}, o2 = {}; | ||
| 208 | + gert::Shape expect = {512, 1024}; | ||
| 209 | + ASSERT_EQ(RunInferShape({512, 1024}, {512, 1024}, {512, 1024}, {512, 1024}, &o0, &o1, &o2, {512, 1024}), | ||
| 210 | + ge::GRAPH_SUCCESS); | ||
| 211 | + ASSERT_EQ(Ops::Base::ToString(o0), Ops::Base::ToString(expect)); | ||
| 212 | +} | ||
| 213 | + | ||
| 214 | +// 系数输入大于动量形状: 广播结果无处容纳(动量是原地输出), 须拒收。 | ||
| 215 | +TEST_F(LambApplyOptimizerAssignProtoTest, coefficient_larger_than_moment_is_rejected) | ||
| 216 | +{ | ||
| 217 | + gert::Shape o0 = {}, o1 = {}, o2 = {}; | ||
| 218 | + ASSERT_EQ(RunInferShape({1, 1024}, {1, 1024}, {1, 1024}, {1, 1024}, &o0, &o1, &o2, {512, 1024}), ge::GRAPH_FAILED); | ||
| 219 | +} | ||
| 220 | + | ||
| 199 | TEST_F(LambApplyOptimizerAssignProtoTest, lambapplyoptimizerassign_infer_datatype) | 221 | TEST_F(LambApplyOptimizerAssignProtoTest, lambapplyoptimizerassign_infer_datatype) |
| 200 | { | 222 | { |
| 201 | ASSERT_NE(gert::OpImplRegistry::GetInstance().GetOpImpl("LambApplyOptimizerAssign"), nullptr); | 223 | ASSERT_NE(gert::OpImplRegistry::GetInstance().GetOpImpl("LambApplyOptimizerAssign"), nullptr); |
| @@ -1,9 +1,9 @@ | |||
| 1 | # ----------------------------------------------------------------------------------------------------------- | 1 | # ----------------------------------------------------------------------------------------------------------- |
| 2 | # Copyright (c) 2026 Huawei Technologies Co., Ltd. | 2 | # Copyright (c) 2026 Huawei Technologies Co., Ltd. |
| 3 | -# This program is free software, you can redistribute it and/or modify it under the terms and conditions of | 3 | +# This program is free software, you can redistribute it and/or modify it under the terms and conditions of |
| 4 | # CANN Open Software License Agreement Version 2.0 (the "License"). | 4 | # CANN Open Software License Agreement Version 2.0 (the "License"). |
| 5 | # Please refer to the License for details. You may not use this file except in compliance with the License. | 5 | # Please refer to the License for details. You may not use this file except in compliance with the License. |
| 6 | -# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, | 6 | +# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, |
| 7 | # INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE. | 7 | # INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE. |
| 8 | # See LICENSE in the root of the software repository for the full text of the License. | 8 | # See LICENSE in the root of the software repository for the full text of the License. |
| 9 | # ----------------------------------------------------------------------------------------------------------- | 9 | # ----------------------------------------------------------------------------------------------------------- |
| @@ -13,4 +13,4 @@ set(SUPPORT_COMPUTE_UNIT "ascend950") | |||
| 13 | # 设置每种芯片类型对应的tiling文件目录,即采用op_host目录下哪个文件夹下的tiling文件编译 | 13 | # 设置每种芯片类型对应的tiling文件目录,即采用op_host目录下哪个文件夹下的tiling文件编译 |
| 14 | set(SUPPORT_TILING_DIR "arch35") | 14 | set(SUPPORT_TILING_DIR "arch35") |
| 15 | 15 | ||
| 16 | -add_modules_sources(HOSTNAME ${OPHOST_NAME} MODE PRIVATE DIR ${CMAKE_CURRENT_SOURCE_DIR} OPTYPE lamb_apply_weight_assign ACLNNTYPE aclnn_exclude COMPUTE_UNIT ${SUPPORT_COMPUTE_UNIT} TILING_DIR ${SUPPORT_TILING_DIR} DISABLE_IN_OPP TRUE) | 16 | +add_modules_sources(HOSTNAME ${OPHOST_NAME} MODE PRIVATE DIR ${CMAKE_CURRENT_SOURCE_DIR} OPTYPE lamb_apply_weight_assign ACLNNTYPE aclnn_exclude COMPUTE_UNIT ${SUPPORT_COMPUTE_UNIT} TILING_DIR ${SUPPORT_TILING_DIR} DEPENDENCIES lamb_apply_common DISABLE_IN_OPP TRUE) |
| @@ -44,42 +44,42 @@ | |||
| 44 | <tr> | 44 | <tr> |
| 45 | <td>input0</td> | 45 | <td>input0</td> |
| 46 | <td>输入</td> | 46 | <td>输入</td> |
| 47 | - <td>不支持空Tensor。公式中的input0(权重范数),标量。</td> | 47 | + <td>支持空Tensor。公式中的input0(权重范数),shape支持1-8维,需与其他输入满足broadcast规则(右对齐,对应维相等或为1)。</td> |
| 48 | <td>FLOAT16、FLOAT</td> | 48 | <td>FLOAT16、FLOAT</td> |
| 49 | <td>ND</td> | 49 | <td>ND</td> |
| 50 | </tr> | 50 | </tr> |
| 51 | <tr> | 51 | <tr> |
| 52 | <td>input1</td> | 52 | <td>input1</td> |
| 53 | <td>输入</td> | 53 | <td>输入</td> |
| 54 | - <td>不支持空Tensor。公式中的input1(梯度范数),标量。</td> | 54 | + <td>支持空Tensor。公式中的input1(梯度范数),shape支持1-8维,需与其他输入满足broadcast规则(右对齐,对应维相等或为1)。</td> |
| 55 | <td>FLOAT16、FLOAT</td> | 55 | <td>FLOAT16、FLOAT</td> |
| 56 | <td>ND</td> | 56 | <td>ND</td> |
| 57 | </tr> | 57 | </tr> |
| 58 | <tr> | 58 | <tr> |
| 59 | <td>input2</td> | 59 | <td>input2</td> |
| 60 | <td>输入</td> | 60 | <td>输入</td> |
| 61 | - <td>不支持空Tensor。公式中的input2(学习率),标量。</td> | 61 | + <td>支持空Tensor。公式中的input2(学习率),shape支持1-8维,需与其他输入满足broadcast规则(右对齐,对应维相等或为1)。</td> |
| 62 | <td>FLOAT16、FLOAT</td> | 62 | <td>FLOAT16、FLOAT</td> |
| 63 | <td>ND</td> | 63 | <td>ND</td> |
| 64 | </tr> | 64 | </tr> |
| 65 | <tr> | 65 | <tr> |
| 66 | <td>input3</td> | 66 | <td>input3</td> |
| 67 | <td>输入</td> | 67 | <td>输入</td> |
| 68 | - <td>支持空Tensor。公式中的input3(update)。允许小于input_param并向上广播,但其shape必须能broadcast进input_param的shape。</td> | 68 | + <td>支持空Tensor。公式中的input3(update),shape需与其他输入满足broadcast关系。</td> |
| 69 | <td>FLOAT16、FLOAT</td> | 69 | <td>FLOAT16、FLOAT</td> |
| 70 | <td>ND</td> | 70 | <td>ND</td> |
| 71 | </tr> | 71 | </tr> |
| 72 | <tr> | 72 | <tr> |
| 73 | <td>input_param</td> | 73 | <td>input_param</td> |
| 74 | <td>输入</td> | 74 | <td>输入</td> |
| 75 | - <td>支持空Tensor。公式中的input_param(参数)。input_param为**原地(in-place)更新**输出,其shape必须等于input3与input_param广播后的完整输出shape(即input3须能broadcast进input_param)。</td> | 75 | + <td>支持空Tensor。公式中的input_param(参数)。input_param为<b>原地(in-place)更新</b>输出,其shape必须等于全部输入的broadcast结果(即其余输入均须能broadcast进input_param)。</td> |
| 76 | <td>FLOAT16、FLOAT</td> | 76 | <td>FLOAT16、FLOAT</td> |
| 77 | <td>ND</td> | 77 | <td>ND</td> |
| 78 | </tr> | 78 | </tr> |
| 79 | <tr> | 79 | <tr> |
| 80 | <td>input_param</td> | 80 | <td>input_param</td> |
| 81 | <td>输出</td> | 81 | <td>输出</td> |
| 82 | - <td>支持空Tensor。更新后的input_param(原地更新),shape取input3与input_param的broadcast结果。</td> | 82 | + <td>支持空Tensor。更新后的input_param(原地更新),shape取全部输入的broadcast结果,等于input_param的shape。</td> |
| 83 | <td>FLOAT16、FLOAT</td> | 83 | <td>FLOAT16、FLOAT</td> |
| 84 | <td>ND</td> | 84 | <td>ND</td> |
| 85 | </tr> | 85 | </tr> |
| @@ -87,6 +87,8 @@ | |||
| 87 | 87 | ||
| 88 | ## 约束说明 | 88 | ## 约束说明 |
| 89 | 89 | ||
| 90 | +- 所有输入的shape需两两满足broadcast规则(右对齐,对应维相等或为1)。`input_param`是原地(in-place)更新的输出,故全部输入的broadcast结果必须恰好等于`input_param`的shape,否则原地写回会越界。 | ||
| 91 | +- 所有输入及输出的维度数不超过8。 | ||
| 90 | - 所有输入的数据类型必须一致,同为FLOAT16或同为FLOAT。 | 92 | - 所有输入的数据类型必须一致,同为FLOAT16或同为FLOAT。 |
| 91 | 93 | ||
| 92 | ## 调用说明 | 94 | ## 调用说明 |
| @@ -14,7 +14,7 @@ | |||
| 14 | */ | 14 | */ |
| 15 | 15 | ||
| 16 | 16 | ||
| 17 | -#include "../../../lamb_apply_common/lamb_apply_check_util.h" | 17 | +#include "../../../lamb_apply_common/op_host/arch35/lamb_apply_check_util.h" |
| 18 | 18 | ||
| 19 | 19 | ||
| 20 | 20 | ||
| @@ -34,6 +34,7 @@ namespace optiling { | |||
| 34 | constexpr static uint64_t LAMB_APPLY_WEIGHT_ASSIGN_TILING_PRIORITY = 0; | 34 | constexpr static uint64_t LAMB_APPLY_WEIGHT_ASSIGN_TILING_PRIORITY = 0; |
| 35 | constexpr static int32_t INPUT_NUM = 5; | 35 | constexpr static int32_t INPUT_NUM = 5; |
| 36 | constexpr static int32_t OUTPUT_NUM = 1; | 36 | constexpr static int32_t OUTPUT_NUM = 1; |
| 37 | +constexpr static int32_t PARAM_IDX = 4; // input_param: ref(原地)输出 | ||
| 37 | static const char* const kInputNames[] = {"input0", "input1", "input2", "input3", "input_param"}; | 38 | static const char* const kInputNames[] = {"input0", "input1", "input2", "input3", "input_param"}; |
| 38 | static const char* const kOutputNames[] = {"input_param"}; | 39 | static const char* const kOutputNames[] = {"input_param"}; |
| 39 | 40 | ||
| @@ -51,39 +52,18 @@ static ge::graphStatus TilingPrepareForLambApplyWeightAssign(gert::TilingParseCo | |||
| 51 | 52 | ||
| 52 | ge::graphStatus LambApplyWeightAssignTiling::GetShapeAttrsInfo() | 53 | ge::graphStatus LambApplyWeightAssignTiling::GetShapeAttrsInfo() |
| 53 | { | 54 | { |
| 54 | - static const int32_t kScalarInputIdx[] = {0, 1, 2}; | ||
| 55 | if (CheckLambApplyDtypeConsistency(context_, INPUT_NUM, kInputNames, OUTPUT_NUM, kOutputNames) != | 55 | if (CheckLambApplyDtypeConsistency(context_, INPUT_NUM, kInputNames, OUTPUT_NUM, kOutputNames) != |
| 56 | ge::GRAPH_SUCCESS) { | 56 | ge::GRAPH_SUCCESS) { |
| 57 | return ge::GRAPH_FAILED; | 57 | return ge::GRAPH_FAILED; |
| 58 | } | 58 | } |
| 59 | - if (CheckLambApplyScalarNotEmpty(context_, kScalarInputIdx, sizeof(kScalarInputIdx) / sizeof(kScalarInputIdx[0]), | ||
| 60 | - kInputNames) != ge::GRAPH_SUCCESS) { | ||
| 61 | - return ge::GRAPH_FAILED; | ||
| 62 | - } | ||
| 63 | return CheckInplaceShapeConstraint(); | 59 | return CheckInplaceShapeConstraint(); |
| 64 | } | 60 | } |
| 65 | 61 | ||
| 66 | // input_param 是 in-place 更新的参数输出(next_param 原地写回其输入 buffer,见 proto "(in-place)"), | 62 | // input_param 是 in-place 更新的参数输出(next_param 原地写回其输入 buffer,见 proto "(in-place)"), |
| 67 | -// 内核按 broadcast(input3, input_param) 的完整网格计算并写回,故 input_param 形状必须 == 该网格。 | 63 | +// 内核按全部输入广播的完整网格计算并写回,故 input_param 形状必须 == 该网格。 |
| 68 | -// 等价充要条件:input3 能广播进 input_param。 | ||
| 69 | ge::graphStatus LambApplyWeightAssignTiling::CheckInplaceShapeConstraint() | 64 | ge::graphStatus LambApplyWeightAssignTiling::CheckInplaceShapeConstraint() |
| 70 | { | 65 | { |
| 71 | - auto input3Shape = context_->GetInputShape(3); | 66 | + return CheckLambApplyBroadcastIntoRef(context_, INPUT_NUM, PARAM_IDX, "input_param"); |
| 72 | - auto inputParamShape = context_->GetInputShape(4); | ||
| 73 | - OP_CHECK_NULL_WITH_CONTEXT(context_, input3Shape); | ||
| 74 | - OP_CHECK_NULL_WITH_CONTEXT(context_, inputParamShape); | ||
| 75 | - const auto& i3s = input3Shape->GetStorageShape(); | ||
| 76 | - const auto& ips = inputParamShape->GetStorageShape(); | ||
| 77 | - // input3 能广播进 input_param <=> broadcast(input3, input_param) == input_param | ||
| 78 | - gert::Shape bcShape; | ||
| 79 | - if (!Ops::Base::BroadcastShape(&i3s, &ips, &bcShape) || !(bcShape == ips)) { | ||
| 80 | - OP_LOGE_FOR_INVALID_SHAPE_WITH_REASON( | ||
| 81 | - context_->GetNodeName(), "input3", Ops::Base::ToString(i3s).c_str(), | ||
| 82 | - "input3 must be broadcastable into the in-place param shape input_param (input_param is updated " | ||
| 83 | - "in-place and must equal the broadcast output shape)"); | ||
| 84 | - return ge::GRAPH_FAILED; | ||
| 85 | - } | ||
| 86 | - return ge::GRAPH_SUCCESS; | ||
| 87 | } | 67 | } |
| 88 | 68 | ||
| 89 | bool LambApplyWeightAssignTiling::IsCapable() { return true; } | 69 | bool LambApplyWeightAssignTiling::IsCapable() { return true; } |
| @@ -44,7 +44,7 @@ protected: | |||
| 44 | 44 | ||
| 45 | private: | 45 | private: |
| 46 | // 校验 in-place 更新的 input_param 形状 == broadcast(input3, input_param)(input3 可向上广播)。 | 46 | // 校验 in-place 更新的 input_param 形状 == broadcast(input3, input_param)(input3 可向上广播)。 |
| 47 | - // dtype 一致性、标量非空的通用校验见 lamb_apply_common/lamb_apply_check_util.h。 | 47 | + // dtype 一致性、标量非空的通用校验见 lamb_apply_common/op_host/arch35/lamb_apply_check_util.h。 |
| 48 | ge::graphStatus CheckInplaceShapeConstraint(); | 48 | ge::graphStatus CheckInplaceShapeConstraint(); |
| 49 | uint64_t tilingKey = 0; | 49 | uint64_t tilingKey = 0; |
| 50 | }; | 50 | }; |
Moptim/lamb_apply_weight_assign/op_host/lamb_apply_weight_assign_infershape.cpp+48-20文件内容审核中,请稍后刷新重试
| @@ -32,24 +32,27 @@ def _scalars(*xs): | |||
| 32 | # 标量落成 **0 维 float64** 张量: torch 的类型提升里 0 维张量不会把 dim>0 的张量抬档, | 32 | # 标量落成 **0 维 float64** 张量: torch 的类型提升里 0 维张量不会把 dim>0 的张量抬档, |
| 33 | # 所以数据是 fp32 时结果仍是 fp32(与改动前一致),数据被 Promote 成 fp64 时标量自动 | 33 | # 所以数据是 fp32 时结果仍是 fp32(与改动前一致),数据被 Promote 成 fp64 时标量自动 |
| 34 | # 跟到 fp64,不会用一个先降到 fp32 的标量去污染高精度真值。 | 34 | # 跟到 fp64,不会用一个先降到 fp32 的标量去污染高精度真值。 |
| 35 | - return tuple( | 35 | + # A2 语义: 这些"系数"输入是**可广播的 ND Tensor**(canndev ops/built-in/tbe/impl/lamb_*.py |
| 36 | - torch.from_numpy(np.asarray(x, "float64").reshape(-1)[:1])[0] for x in xs | 36 | + # 每步 mul/sub/div 都先 shape_util.broadcast_shapes 再 tbe.broadcast)。原先 reshape(-1)[:1] |
| 37 | - ) | 37 | + # 只取首元素, 传多元素张量时静默按首元素计算 —— 与内核的广播实现不一致, 广播档必然假红。 |
| 38 | - | 38 | + # 改为返回完整张量交给 torch 自然广播: 形状 (1,) 的行为与原标量完全一致, 故常规档不变。 |
| 39 | - | 39 | + return tuple(_t(x) for x in xs) |
| 40 | -_F32_TINY = torch.tensor(float(np.finfo(np.float32).tiny), dtype=torch.float32) | ||
| 41 | 40 | ||
| 42 | 41 | ||
| 43 | def _div_ftz(a, b): | 42 | def _div_ftz(a, b): |
| 44 | - """torch.div 的 fp32 除法 + FTZ,对齐算子两代共同的行为。 | 43 | + """fp32 除法,**不做 FTZ**。 |
| 45 | 44 | ||
| 46 | - A2(910B) 的 Div 没有 config 参数(asc-devkit Div.md 里带 config 的原型对 Atlas A2 | 45 | + 原先这里模拟了 FTZ(把落在 [1e-45, 1.1754944e-38) 的商冲刷成 0),依据是 arch35 的 |
| 47 | - 标注"不支持"),只有单指令一条路,Subnormal 必然 FTZ;arch35 的 Vec::Div 用默认 | 46 | + Vec::Div 走默认 DivConfig{DivAlgo::INTRINSIC}、该档 Subnormal 均被 FTZ。 |
| 48 | - DivConfig{DivAlgo::INTRINSIC},文档写明该档"Subnormal 均被 FTZ"。CPU 默认不 FTZ, | 47 | + 但实测该档把**非规格化操作数**也冲成 0:w_norm=1.93e-39 / g_norm=5.91e-39(商 0.327 |
| 49 | - 所以要显式补这一步,否则商落入 [1e-45, 1.1754944e-38) 时 golden 会给出算子实际 | 48 | + 是完全正常的值)时内核输出 3159/3159 全为 NaN(0/0)。这是缺陷不是语义,已把内核的 |
| 50 | - 不会产出的值。""" | 49 | + Div 改成 PRECISION_0ULP_FTZ_FALSE(见 lamb_apply_weight_assign_dag.h 的 DivFtzFalse)。 |
| 51 | - q = torch.div(a, b) | 50 | + |
| 52 | - return torch.where((q != 0) & (q.abs() < _F32_TINY), torch.zeros_like(q), q) | 51 | + 精度判据以竞品为准:实测 torch 2.11 在 CPU 与 A100 上,fp32 非规格化数在存取/乘/加/减/除 |
| 52 | + 各环节全程保留(CUDA 默认 -ftz=false,A100 对 fp32 subnormal 是全速硬件支持)。 | ||
| 53 | + 故 golden 也不冲刷。函数保留是为了留住这段结论,除法本身就是 torch.div。 | ||
| 54 | + """ | ||
| 55 | + return torch.div(a, b) | ||
| 53 | 56 | ||
| 54 | 57 | ||
| 55 | def _t(x): | 58 | def _t(x): |
| @@ -88,8 +91,14 @@ def lamb_apply_weight_assign_golden( | |||
| 88 | wn, gn, lr = _scalars(input0, input1, input2) | 91 | wn, gn, lr = _scalars(input0, input1, input2) |
| 89 | upd, param = _t(input3), _t(input_param) | 92 | upd, param = _t(input3), _t(input_param) |
| 90 | one = torch.tensor(1.0, dtype=torch.float32) | 93 | one = torch.tensor(1.0, dtype=torch.float32) |
| 91 | - inner = _div_ftz(wn, gn) if gn > 0 else one | 94 | + # 逐元素选择: 输入改为可广播张量后, `if gn > 0` 这类标量控制流会对整个张量只判一次, |
| 92 | - ratio = inner if wn > 0 else one | 95 | + # 与内核的逐元素语义(A2 亦然)不符, 广播档必然假红。用 torch.where 逐元素选。 |
| 96 | + # 注意 where 的两个分支都会被求值, _div_ftz 对 gn==0 必须自身安全(不产生 inf/nan 污染)。 | ||
| 97 | + safe_gn = torch.where(gn > 0, gn, torch.ones_like(gn)) | ||
| 98 | + inner = torch.where( | ||
| 99 | + gn > 0, _div_ftz(wn, safe_gn), one.expand_as(safe_gn) if safe_gn.dim() else one | ||
| 100 | + ) | ||
| 101 | + ratio = torch.where(wn > 0, inner, torch.ones_like(inner)) | ||
| 93 | # 运算序列对齐实现:A2 (tbe.vmul(update, lr) 再 vmul(ratio, ·)) 与 arch35 DAG | 102 | # 运算序列对齐实现:A2 (tbe.vmul(update, lr) 再 vmul(ratio, ·)) 与 arch35 DAG |
| 94 | # (UpdLr = Update*Lr; RatioUpdLr = Ratio*UpdLr) 都是先算 update*lr。按 README 原先的 | 103 | # (UpdLr = Update*Lr; RatioUpdLr = Ratio*UpdLr) 都是先算 update*lr。按 README 原先的 |
| 95 | # 字面顺序写成 (lr*ratio)*upd 在浮点下不等价:update 与 lr 同时较大时实现会先溢出成 inf, | 104 | # 字面顺序写成 (lr*ratio)*upd 在浮点下不等价:update 与 lr 同时较大时实现会先溢出成 inf, |
| @@ -137,8 +146,32 @@ def _tp_t(x): | |||
| 137 | return torch.from_numpy(a) | 146 | return torch.from_numpy(a) |
| 138 | 147 | ||
| 139 | 148 | ||
| 149 | +def _tp_widen(t): | ||
| 150 | + """按 NPU 的加宽行为把三方入参落到**内核计算类型 U = float32**。 | ||
| 151 | + | ||
| 152 | + 规范依据 ttk_golden_logic.md §四/§五「浮点 + 三方」一格: 三方腿"按 NPU 加宽行为同步 cast"。 | ||
| 153 | + 内核对 fp16 输入 unpack 成 fp32 计算(dag.h 的 `Cast<U, T, 0>`, U = float), 对 fp32 输入 | ||
| 154 | + 原生 fp32, 两种情况计算类型都是 fp32, 故此处一律落到 fp32。 | ||
| 155 | + | ||
| 156 | + **不能只处理 fp16**: cross_check 下 TTK 会把 fp32 Promote 成 fp64 下发, 原样透传会让三方腿 | ||
| 157 | + 在 fp64 上算 —— 内核 fp32 溢出/下溢的地方它都不溢出, 两条腿不在同一精度上, 比值无意义。 | ||
| 158 | + 整型不动: 内核对 int 是原生/int32 累加, 走 fp32 会抹掉 >2^24 的低位。 | ||
| 159 | + """ | ||
| 160 | + return t.to(torch.float32) if t.is_floating_point() else t | ||
| 161 | + | ||
| 162 | + | ||
| 163 | +def _tp_narrow(outs, dt): | ||
| 164 | + """出口复刻内核的 `Cast<T, U, 1>`: 窄回算子**声明**的 dtype T(不是 Promote 后的 dtype)。 | ||
| 165 | + | ||
| 166 | + 少了这一步或窄错目标, 三方腿会与走 Promote 的 golden 逐位相等 —— 双标杆塌成单标杆, | ||
| 167 | + 三比值分母被 safe_div 的 small_value 夹底, mare/rmse 恒为 1.0, 阈值永不触发。 | ||
| 168 | + """ | ||
| 169 | + return [o.to(dt) if o.is_floating_point() else o for o in outs] | ||
| 170 | + | ||
| 171 | + | ||
| 140 | def _tp_s(x): | 172 | def _tp_s(x): |
| 141 | - return _tp_t(x).reshape(-1)[0] | 173 | + # 同 _scalars: 三方腿也必须广播, 不能只取首元素 |
| 174 | + return _tp_t(x) | ||
| 142 | 175 | ||
| 143 | 176 | ||
| 144 | class _LambApplyWeightAssignCompose: | 177 | class _LambApplyWeightAssignCompose: |
| @@ -146,8 +179,10 @@ class _LambApplyWeightAssignCompose: | |||
| 146 | wn, gn, lr = (_tp_s(v_) for v_ in (input0, input1, input2)) | 179 | wn, gn, lr = (_tp_s(v_) for v_ in (input0, input1, input2)) |
| 147 | upd, param = _tp_t(input3), _tp_t(input_param) | 180 | upd, param = _tp_t(input3), _tp_t(input_param) |
| 148 | one = torch.ones((), dtype=torch.float32, device=upd.device) | 181 | one = torch.ones((), dtype=torch.float32, device=upd.device) |
| 149 | - inner = torch.div(wn, gn) if bool(gn > 0) else one | 182 | + # 逐元素选择(理由同 golden 主体): 三方腿也必须按元素选, 不能整张量只判一次 |
| 150 | - ratio = inner if bool(wn > 0) else one | 183 | + safe_gn = torch.where(gn > 0, gn, torch.ones_like(gn)) |
| 184 | + inner = torch.where(gn > 0, torch.div(wn, safe_gn), one) | ||
| 185 | + ratio = torch.where(wn > 0, inner, one) | ||
| 151 | return [param - ratio * (upd * lr)] | 186 | return [param - ratio * (upd * lr)] |
| 152 | 187 | ||
| 153 | 188 | ||
| @@ -165,6 +200,16 @@ class _LambApplyWeightAssignCompose: | |||
| 165 | 200 | ||
| 166 | 201 | ||
| 167 | def _kf_widen(a, seen): | 202 | def _kf_widen(a, seen): |
| 203 | + """按 NPU 的加宽行为把三方入参加宽, 并记下算子声明的 dtype T 供出口窄回。 | ||
| 204 | + | ||
| 205 | + 三方腿拿到的是**原始 dtype T**(TTK 的 Promote 只作用于 golden, 见 profiling.py 的 | ||
| 206 | + golden_mode_override; 三方腿走 _xpu_inputs -> original_input_arrays), 故这里按 T 判断: | ||
| 207 | + - T = fp16/bf16: 内核 unpack 成 fp32 全程不落回(dag.h 的 Cast<U, T, 0>, U = float), | ||
| 208 | + 而 torch 只在单个算子内部用 opmath=float、算子之间每步落回 T。不加宽就等于拿 | ||
| 209 | + "逐步截断的实现"当竞品, 与被测内核不是同一个算法 -> 加宽到 fp32, 出口窄回 T。 | ||
| 210 | + - T = fp32: 内核计算类型 U 即 fp32, **不加宽**; torch 同样原生 fp32 -> 原样不动。 | ||
| 211 | + - 整型: 内核原生/int32 累加, 走 fp32 会抹掉 >2^24 的低位 -> 不动。 | ||
| 212 | + """ | ||
| 168 | if isinstance(a, (list, tuple)): | 213 | if isinstance(a, (list, tuple)): |
| 169 | return type(a)(_kf_widen(x, seen) for x in a) | 214 | return type(a)(_kf_widen(x, seen) for x in a) |
| 170 | if isinstance(a, torch.Tensor): | 215 | if isinstance(a, torch.Tensor): |
| @@ -187,6 +232,7 @@ def _kf_widen(a, seen): | |||
| 187 | 232 | ||
| 188 | 233 | ||
| 189 | def _kf_narrow(o, dt): | 234 | def _kf_narrow(o, dt): |
| 235 | + """出口复刻内核的 `Cast<T, U, 1>`: 窄回算子声明的 dtype T。T = fp32 时无需窄回。""" | ||
| 190 | if isinstance(o, (list, tuple)): | 236 | if isinstance(o, (list, tuple)): |
| 191 | return type(o)(_kf_narrow(x, dt) for x in o) | 237 | return type(o)(_kf_narrow(x, dt) for x in o) |
| 192 | if isinstance(o, torch.Tensor) and o.is_floating_point(): | 238 | if isinstance(o, torch.Tensor) and o.is_floating_point(): |
| @@ -1,9 +1,9 @@ | |||
| 1 | # ----------------------------------------------------------------------------------------------------------- | 1 | # ----------------------------------------------------------------------------------------------------------- |
| 2 | # Copyright (c) 2026 Huawei Technologies Co., Ltd. | 2 | # Copyright (c) 2026 Huawei Technologies Co., Ltd. |
| 3 | -# This program is free software, you can redistribute it and/or modify it under the terms and conditions of | 3 | +# This program is free software, you can redistribute it and/or modify it under the terms and conditions of |
| 4 | # CANN Open Software License Agreement Version 2.0 (the "License"). | 4 | # CANN Open Software License Agreement Version 2.0 (the "License"). |
| 5 | # Please refer to the License for details. You may not use this file except in compliance with the License. | 5 | # Please refer to the License for details. You may not use this file except in compliance with the License. |
| 6 | -# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, | 6 | +# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, |
| 7 | # INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE. | 7 | # INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE. |
| 8 | # See LICENSE in the root of the software repository for the full text of the License. | 8 | # See LICENSE in the root of the software repository for the full text of the License. |
| 9 | # ----------------------------------------------------------------------------------------------------------- | 9 | # ----------------------------------------------------------------------------------------------------------- |
| @@ -13,4 +13,4 @@ set(SUPPORT_COMPUTE_UNIT "ascend950") | |||
| 13 | # 设置每种芯片类型对应的tiling文件目录,即采用op_host目录下哪个文件夹下的tiling文件编译 | 13 | # 设置每种芯片类型对应的tiling文件目录,即采用op_host目录下哪个文件夹下的tiling文件编译 |
| 14 | set(SUPPORT_TILING_DIR "arch35") | 14 | set(SUPPORT_TILING_DIR "arch35") |
| 15 | 15 | ||
| 16 | -add_modules_sources(HOSTNAME ${OPHOST_NAME} MODE PRIVATE DIR ${CMAKE_CURRENT_SOURCE_DIR} OPTYPE lamb_next_m_v ACLNNTYPE aclnn_exclude COMPUTE_UNIT ${SUPPORT_COMPUTE_UNIT} TILING_DIR ${SUPPORT_TILING_DIR} DISABLE_IN_OPP TRUE) | 16 | +add_modules_sources(HOSTNAME ${OPHOST_NAME} MODE PRIVATE DIR ${CMAKE_CURRENT_SOURCE_DIR} OPTYPE lamb_next_m_v ACLNNTYPE aclnn_exclude COMPUTE_UNIT ${SUPPORT_COMPUTE_UNIT} TILING_DIR ${SUPPORT_TILING_DIR} DEPENDENCIES lamb_apply_common DISABLE_IN_OPP TRUE) |
| @@ -48,7 +48,7 @@ | |||
| 48 | <tr> | 48 | <tr> |
| 49 | <td>input_mul3</td> | 49 | <td>input_mul3</td> |
| 50 | <td>输入</td> | 50 | <td>输入</td> |
| 51 | - <td>支持空Tensor。公式中的input_mul3(g^2),主张量,shape需与input_mul0满足broadcast关系。</td> | 51 | + <td>支持空Tensor。公式中的input_mul3(g^2),主张量,shape需与其他输入满足broadcast关系。</td> |
| 52 | <td>FLOAT16、FLOAT</td> | 52 | <td>FLOAT16、FLOAT</td> |
| 53 | <td>ND</td> | 53 | <td>ND</td> |
| 54 | </tr> | 54 | </tr> |
| @@ -76,7 +76,7 @@ | |||
| 76 | <tr> | 76 | <tr> |
| 77 | <td>input_mul0</td> | 77 | <td>input_mul0</td> |
| 78 | <td>输入</td> | 78 | <td>输入</td> |
| 79 | - <td>支持空Tensor。公式中的input_mul0(一阶矩m),主张量,shape需与input_mul3满足broadcast关系,其broadcast结果决定各输出的shape。</td> | 79 | + <td>支持空Tensor。公式中的input_mul0(一阶矩m),主张量,shape需与其他输入满足broadcast关系,全部输入的broadcast结果决定各输出的shape。</td> |
| 80 | <td>FLOAT16、FLOAT</td> | 80 | <td>FLOAT16、FLOAT</td> |
| 81 | <td>ND</td> | 81 | <td>ND</td> |
| 82 | </tr> | 82 | </tr> |
| @@ -97,70 +97,70 @@ | |||
| 97 | <tr> | 97 | <tr> |
| 98 | <td>mul0_x</td> | 98 | <td>mul0_x</td> |
| 99 | <td>输入</td> | 99 | <td>输入</td> |
| 100 | - <td>不支持空Tensor。公式中的mul0_x(beta1),标量。</td> | 100 | + <td>支持空Tensor。公式中的mul0_x(beta1),shape支持1-8维,需与其他输入满足broadcast规则(右对齐,对应维相等或为1)。</td> |
| 101 | <td>FLOAT16、FLOAT</td> | 101 | <td>FLOAT16、FLOAT</td> |
| 102 | <td>ND</td> | 102 | <td>ND</td> |
| 103 | </tr> | 103 | </tr> |
| 104 | <tr> | 104 | <tr> |
| 105 | <td>mul1_sub</td> | 105 | <td>mul1_sub</td> |
| 106 | <td>输入</td> | 106 | <td>输入</td> |
| 107 | - <td>不支持空Tensor。公式中的mul1_sub(1-beta1),标量。</td> | 107 | + <td>支持空Tensor。公式中的mul1_sub(1-beta1),shape支持1-8维,需与其他输入满足broadcast规则(右对齐,对应维相等或为1)。</td> |
| 108 | <td>FLOAT16、FLOAT</td> | 108 | <td>FLOAT16、FLOAT</td> |
| 109 | <td>ND</td> | 109 | <td>ND</td> |
| 110 | </tr> | 110 | </tr> |
| 111 | <tr> | 111 | <tr> |
| 112 | <td>mul2_x</td> | 112 | <td>mul2_x</td> |
| 113 | <td>输入</td> | 113 | <td>输入</td> |
| 114 | - <td>不支持空Tensor。公式中的mul2_x(beta2),标量。</td> | 114 | + <td>支持空Tensor。公式中的mul2_x(beta2),shape支持1-8维,需与其他输入满足broadcast规则(右对齐,对应维相等或为1)。</td> |
| 115 | <td>FLOAT16、FLOAT</td> | 115 | <td>FLOAT16、FLOAT</td> |
| 116 | <td>ND</td> | 116 | <td>ND</td> |
| 117 | </tr> | 117 | </tr> |
| 118 | <tr> | 118 | <tr> |
| 119 | <td>mul3_sub1</td> | 119 | <td>mul3_sub1</td> |
| 120 | <td>输入</td> | 120 | <td>输入</td> |
| 121 | - <td>不支持空Tensor。公式中的mul3_sub1(1-beta2),标量。</td> | 121 | + <td>支持空Tensor。公式中的mul3_sub1(1-beta2),shape支持1-8维,需与其他输入满足broadcast规则(右对齐,对应维相等或为1)。</td> |
| 122 | <td>FLOAT16、FLOAT</td> | 122 | <td>FLOAT16、FLOAT</td> |
| 123 | <td>ND</td> | 123 | <td>ND</td> |
| 124 | </tr> | 124 | </tr> |
| 125 | <tr> | 125 | <tr> |
| 126 | <td>mul4_x</td> | 126 | <td>mul4_x</td> |
| 127 | <td>输入</td> | 127 | <td>输入</td> |
| 128 | - <td>不支持空Tensor。公式中的mul4_x(权重衰减系数),标量。</td> | 128 | + <td>支持空Tensor。公式中的mul4_x(权重衰减系数),shape支持1-8维,需与其他输入满足broadcast规则(右对齐,对应维相等或为1)。</td> |
| 129 | <td>FLOAT16、FLOAT</td> | 129 | <td>FLOAT16、FLOAT</td> |
| 130 | <td>ND</td> | 130 | <td>ND</td> |
| 131 | </tr> | 131 | </tr> |
| 132 | <tr> | 132 | <tr> |
| 133 | <td>add2_y</td> | 133 | <td>add2_y</td> |
| 134 | <td>输入</td> | 134 | <td>输入</td> |
| 135 | - <td>不支持空Tensor。公式中的add2_y(epsilon),标量。</td> | 135 | + <td>支持空Tensor。公式中的add2_y(epsilon),shape支持1-8维,需与其他输入满足broadcast规则(右对齐,对应维相等或为1)。</td> |
| 136 | <td>FLOAT16、FLOAT</td> | 136 | <td>FLOAT16、FLOAT</td> |
| 137 | <td>ND</td> | 137 | <td>ND</td> |
| 138 | </tr> | 138 | </tr> |
| 139 | <tr> | 139 | <tr> |
| 140 | <td>y1</td> | 140 | <td>y1</td> |
| 141 | <td>输出</td> | 141 | <td>输出</td> |
| 142 | - <td>支持空Tensor。公式中的y1(update),shape取input_mul3与input_mul0的broadcast结果。</td> | 142 | + <td>支持空Tensor。公式中的y1(update),shape取全部输入的broadcast结果。</td> |
| 143 | <td>FLOAT16、FLOAT</td> | 143 | <td>FLOAT16、FLOAT</td> |
| 144 | <td>ND</td> | 144 | <td>ND</td> |
| 145 | </tr> | 145 | </tr> |
| 146 | <tr> | 146 | <tr> |
| 147 | <td>y2</td> | 147 | <td>y2</td> |
| 148 | <td>输出</td> | 148 | <td>输出</td> |
| 149 | - <td>支持空Tensor。公式中的y2(next_m),shape取input_mul3与input_mul0的broadcast结果。</td> | 149 | + <td>支持空Tensor。公式中的y2(next_m),shape取全部输入的broadcast结果。</td> |
| 150 | <td>FLOAT16、FLOAT</td> | 150 | <td>FLOAT16、FLOAT</td> |
| 151 | <td>ND</td> | 151 | <td>ND</td> |
| 152 | </tr> | 152 | </tr> |
| 153 | <tr> | 153 | <tr> |
| 154 | <td>y3</td> | 154 | <td>y3</td> |
| 155 | <td>输出</td> | 155 | <td>输出</td> |
| 156 | - <td>支持空Tensor。公式中的y3(next_v),shape取input_mul3与input_mul0的broadcast结果。</td> | 156 | + <td>支持空Tensor。公式中的y3(next_v),shape取全部输入的broadcast结果。</td> |
| 157 | <td>FLOAT16、FLOAT</td> | 157 | <td>FLOAT16、FLOAT</td> |
| 158 | <td>ND</td> | 158 | <td>ND</td> |
| 159 | </tr> | 159 | </tr> |
| 160 | <tr> | 160 | <tr> |
| 161 | <td>y4</td> | 161 | <td>y4</td> |
| 162 | <td>输出</td> | 162 | <td>输出</td> |
| 163 | - <td>支持空Tensor。公式中的y4,shape取input_mul3与input_mul0的broadcast结果。</td> | 163 | + <td>支持空Tensor。公式中的y4,shape取全部输入的broadcast结果。</td> |
| 164 | <td>FLOAT16、FLOAT</td> | 164 | <td>FLOAT16、FLOAT</td> |
| 165 | <td>ND</td> | 165 | <td>ND</td> |
| 166 | </tr> | 166 | </tr> |
| @@ -168,8 +168,10 @@ | |||
| 168 | 168 | ||
| 169 | ## 约束说明 | 169 | ## 约束说明 |
| 170 | 170 | ||
| 171 | +- 所有输入的shape需两两满足broadcast规则(右对齐,对应维相等或为1),输出shape为全部输入的broadcast结果。 | ||
| 172 | +- 所有输入及输出的维度数不超过8。 | ||
| 171 | - 所有输入的数据类型必须一致,同为FLOAT16或同为FLOAT。 | 173 | - 所有输入的数据类型必须一致,同为FLOAT16或同为FLOAT。 |
| 172 | -- input_mul0/input_mul1/input_mul2/input_mul3/input_mul4 为主张量,其shape需保持一致(或可相互广播到同一shape);各输出y1/y2/y3/y4的shape均取该广播结果(实现以input_mul3与input_mul0的broadcast结果为准)。 | 174 | +- 各输出y1/y2/y3/y4的shape均取全部输入的broadcast结果。 |
| 173 | 175 | ||
| 174 | ## 调用说明 | 176 | ## 调用说明 |
| 175 | 177 | ||
| @@ -15,20 +15,24 @@ | |||
| 15 | 15 | ||
| 16 | 16 | ||
| 17 | 17 | ||
| 18 | -#include "../../op_kernel/arch35/lamb_next_m_v_dag.h" | 18 | +#include <securec.h> |
| 19 | -#include "atvoss/broadcast/broadcast_tiling.h" | 19 | +#include <algorithm> |
| 20 | + | ||
| 20 | 21 | ||
| 21 | 22 | ||
| 22 | 23 | ||
| 23 | 24 | ||
| 24 | 25 | ||
| 25 | 26 | ||
| 26 | -using namespace AscendC; | ||
| 27 | using namespace ge; | 27 | using namespace ge; |
| 28 | 28 | ||
| 29 | namespace optiling { | 29 | namespace optiling { |
| 30 | 30 | ||
| 31 | constexpr static uint64_t LAMB_NEXT_M_V_TILING_PRIORITY = 0; | 31 | constexpr static uint64_t LAMB_NEXT_M_V_TILING_PRIORITY = 0; |
| 32 | +constexpr static uint64_t LNMV_TILING_KEY_FP32 = 100; | ||
| 33 | +constexpr static uint64_t LNMV_TILING_KEY_FP16 = 200; | ||
| 34 | +constexpr static uint64_t TILING_KEY_FP32 = 100; | ||
| 35 | +constexpr static uint64_t TILING_KEY_FP16 = 200; | ||
| 32 | constexpr static int32_t INPUT_NUM = 13; | 36 | constexpr static int32_t INPUT_NUM = 13; |
| 33 | constexpr static int32_t OUTPUT_NUM = 4; | 37 | constexpr static int32_t OUTPUT_NUM = 4; |
| 34 | 38 | ||
| @@ -80,18 +84,6 @@ ge::graphStatus LambNextMVTiling::GetShapeAttrsInfo() | |||
| 80 | return ge::GRAPH_FAILED; | 84 | return ge::GRAPH_FAILED; |
| 81 | } | 85 | } |
| 82 | } | 86 | } |
| 83 | - // 标量类输入为每元素计算所必需的系数, 空Tensor 视为缺失必选值(畸形输入), 不支持。 | ||
| 84 | - static const int32_t kScalarInputIdx[] = {7, 8, 9, 10, 11, 12}; | ||
| 85 | - for (int32_t scalarIdx : kScalarInputIdx) { | ||
| 86 | - auto scalarShape = context_->GetInputShape(scalarIdx); | ||
| 87 | - OP_CHECK_NULL_WITH_CONTEXT(context_, scalarShape); | ||
| 88 | - if (scalarShape->GetStorageShape().GetShapeSize() == 0) { | ||
| 89 | - OP_LOGE_FOR_INVALID_SHAPE_WITH_REASON(context_->GetNodeName(), kInputNames[scalarIdx], | ||
| 90 | - Ops::Base::ToString(scalarShape->GetStorageShape()).c_str(), | ||
| 91 | - "scalar input does not support empty tensor"); | ||
| 92 | - return ge::GRAPH_FAILED; | ||
| 93 | - } | ||
| 94 | - } | ||
| 95 | return ge::GRAPH_SUCCESS; | 87 | return ge::GRAPH_SUCCESS; |
| 96 | } | 88 | } |
| 97 | 89 | ||
| @@ -99,49 +91,44 @@ bool LambNextMVTiling::IsCapable() { return true; } | |||
| 99 | 91 | ||
| 100 | ge::graphStatus LambNextMVTiling::DoOpTiling() | 92 | ge::graphStatus LambNextMVTiling::DoOpTiling() |
| 101 | { | 93 | { |
| 102 | - // 空 tensor 应对(空进空出): 输出为空(0元素)时设 1 核(空转), 配合全0 tiling 数据(blockFormer=0)使 kernel 空转退出, | 94 | + auto rawTilingData = context_->GetRawTilingData(); |
| 103 | - // 直接成功。 | 95 | + OP_CHECK_NULL_WITH_CONTEXT(context_, rawTilingData); |
| 104 | - auto emptyTensorOutShape0 = context_->GetOutputShape(0); | ||
| 105 | - if (emptyTensorOutShape0 != nullptr && emptyTensorOutShape0->GetStorageShape().GetShapeSize() == 0) { | ||
| 106 | - auto emptyRawTiling = context_->GetRawTilingData(); | ||
| 107 | - if (emptyRawTiling != nullptr && emptyRawTiling->GetData() != nullptr) { | ||
| 108 | - size_t emptyCap = emptyRawTiling->GetCapacity(); | ||
| 109 | - uint8_t* emptyPtr = static_cast<uint8_t*>(emptyRawTiling->GetData()); | ||
| 110 | - for (size_t emptyIdx = 0; emptyIdx < emptyCap; ++emptyIdx) { | ||
| 111 | - emptyPtr[emptyIdx] = 0; | ||
| 112 | - } | ||
| 113 | - emptyRawTiling->SetDataSize(emptyCap); | ||
| 114 | - } | ||
| 115 | - size_t* emptyWs = context_->GetWorkspaceSizes(1); | ||
| 116 | - if (emptyWs != nullptr) { | ||
| 117 | - emptyWs[0] = 0; | ||
| 118 | - } | ||
| 119 | - context_->SetBlockDim(1); | ||
| 120 | - tilingKey = GET_TPL_TILING_KEY(1); // schMode=1(已编译), 配合全0 tiling(blockFormer=0)空转 | ||
| 121 | - return ge::GRAPH_SUCCESS; | ||
| 122 | - } | ||
| 123 | auto input0Desc = context_->GetInputDesc(0); | 96 | auto input0Desc = context_->GetInputDesc(0); |
| 124 | OP_CHECK_NULL_WITH_CONTEXT(context_, input0Desc); | 97 | OP_CHECK_NULL_WITH_CONTEXT(context_, input0Desc); |
| 98 | + | ||
| 125 | ge::DataType input0DType = input0Desc->GetDataType(); | 99 | ge::DataType input0DType = input0Desc->GetDataType(); |
| 126 | - if (input0DType == ge::DT_FLOAT16) { | 100 | + uint32_t dtSize = 0; |
| 127 | - BroadcastBaseTiling<LambNextMVOp::LambNextMVCompute<half, float>::OpDag> brcBaseTiling( | 101 | + if (input0DType == ge::DT_FLOAT) { |
| 128 | - context_, static_cast<uint32_t>(BROADCAST_KERNEL_TYPE::KERNEL_TYPE_NDDMA)); | 102 | + tilingKey = TILING_KEY_FP32; |
| 129 | - OP_CHECK_IF(brcBaseTiling.DoTiling() == ge::GRAPH_FAILED, | 103 | + dtSize = sizeof(float); |
| 130 | - OP_LOGE(context_->GetNodeName(), "Do tiling failed. Please check the detailed log."), | 104 | + } else if (input0DType == ge::DT_FLOAT16) { |
| 131 | - return ge::GRAPH_FAILED); | 105 | + tilingKey = TILING_KEY_FP16; |
| 132 | - tilingKey = GET_TPL_TILING_KEY(brcBaseTiling.GetSchMode()); | 106 | + dtSize = sizeof(uint16_t); |
| 133 | - } else if (input0DType == ge::DT_FLOAT) { | ||
| 134 | - BroadcastBaseTiling<LambNextMVOp::LambNextMVCompute<float, float>::OpDag> brcBaseTiling( | ||
| 135 | - context_, static_cast<uint32_t>(BROADCAST_KERNEL_TYPE::KERNEL_TYPE_NDDMA)); | ||
| 136 | - OP_CHECK_IF(brcBaseTiling.DoTiling() == ge::GRAPH_FAILED, | ||
| 137 | - OP_LOGE(context_->GetNodeName(), "Do tiling failed. Please check the detailed log."), | ||
| 138 | - return ge::GRAPH_FAILED); | ||
| 139 | - tilingKey = GET_TPL_TILING_KEY(brcBaseTiling.GetSchMode()); | ||
| 140 | } else { | 107 | } else { |
| 141 | OP_LOGE_FOR_INVALID_DTYPE(context_->GetNodeName(), "input_mul3", Ops::Base::ToString(input0DType).c_str(), | 108 | OP_LOGE_FOR_INVALID_DTYPE(context_->GetNodeName(), "input_mul3", Ops::Base::ToString(input0DType).c_str(), |
| 142 | "fp16 or fp32"); | 109 | "fp16 or fp32"); |
| 143 | return ge::GRAPH_FAILED; | 110 | return ge::GRAPH_FAILED; |
| 144 | } | 111 | } |
| 112 | + | ||
| 113 | + using PlanTiling = LambBrcTilingData<13, 4>; | ||
| 114 | + td_ = PlanTiling{}; | ||
| 115 | + // 空进空出: 输出 0 元素时 tiling 全 0, kernel 按 usedCoreNum=0 直接退出。 | ||
| 116 | + auto outShape0 = context_->GetOutputShape(0); | ||
| 117 | + if (outShape0 == nullptr || outShape0->GetStorageShape().GetShapeSize() != 0) { | ||
| 118 | + // 先取返回值再判: 模板实参里的逗号会被预处理器当成 OP_CHECK_IF 的参数分隔符。 | ||
| 119 | + ge::graphStatus planRet = BuildLambBrcPlan<13, 4>(context_, coreNum_, ubSize_, dtSize, td_); | ||
| 120 | + OP_CHECK_IF(planRet != ge::GRAPH_SUCCESS, OP_LOGE(context_->GetNodeName(), "build broadcast plan failed"), | ||
| 121 | + return ge::GRAPH_FAILED); | ||
| 122 | + } | ||
| 123 | + | ||
| 124 | + auto ret = memcpy_s(rawTilingData->GetData(), rawTilingData->GetCapacity(), &td_, sizeof(td_)); | ||
| 125 | + OP_CHECK_IF(ret != EOK, OP_LOGE(context_->GetNodeName(), "copy tiling data failed, ret %d", ret), | ||
| 126 | + return ge::GRAPH_FAILED); | ||
| 127 | + rawTilingData->SetDataSize(sizeof(td_)); | ||
| 128 | + context_->SetBlockDim(std::max<uint32_t>(td_.usedCoreNum, 1)); | ||
| 129 | + size_t* ws = context_->GetWorkspaceSizes(1); | ||
| 130 | + OP_CHECK_NULL_WITH_CONTEXT(context_, ws); | ||
| 131 | + ws[0] = 0U; | ||
| 145 | return ge::GRAPH_SUCCESS; | 132 | return ge::GRAPH_SUCCESS; |
| 146 | } | 133 | } |
| 147 | 134 | ||
| @@ -153,7 +140,17 @@ ge::graphStatus LambNextMVTiling::GetWorkspaceSize() { return ge::GRAPH_SUCCESS; | |||
| 153 | 140 | ||
| 154 | ge::graphStatus LambNextMVTiling::PostTiling() { return ge::GRAPH_SUCCESS; } | 141 | ge::graphStatus LambNextMVTiling::PostTiling() { return ge::GRAPH_SUCCESS; } |
| 155 | 142 | ||
| 156 | -ge::graphStatus LambNextMVTiling::GetPlatformInfo() { return ge::GRAPH_SUCCESS; } | 143 | +ge::graphStatus LambNextMVTiling::GetPlatformInfo() |
| 144 | +{ | ||
| 145 | + auto compileInfo = static_cast<const LambNextMVCompileInfo*>(context_->GetCompileInfo()); | ||
| 146 | + OP_CHECK_NULL_WITH_CONTEXT(context_, compileInfo); | ||
| 147 | + coreNum_ = compileInfo->coreNum; | ||
| 148 | + ubSize_ = compileInfo->ubSize; | ||
| 149 | + OP_CHECK_IF(coreNum_ == 0 || ubSize_ == 0, | ||
| 150 | + OP_LOGE(context_->GetNodeName(), "invalid platform info: coreNum %lu ubSize %lu", coreNum_, ubSize_), | ||
| 151 | + return ge::GRAPH_FAILED); | ||
| 152 | + return ge::GRAPH_SUCCESS; | ||
| 153 | +} | ||
| 157 | 154 | ||
| 158 | static ge::graphStatus TilingForLambNextMV(gert::TilingContext* context) | 155 | static ge::graphStatus TilingForLambNextMV(gert::TilingContext* context) |
| 159 | { | 156 | { |
| @@ -13,6 +13,7 @@ | |||
| 13 | * \brief | 13 | * \brief |
| 14 | */ | 14 | */ |
| 15 | 15 | ||
| 16 | + | ||
| 16 | 17 | ||
| 17 | 18 | ||
| 18 | 19 | ||
| @@ -20,28 +21,49 @@ | |||
| 20 | using namespace Ops::Base; | 21 | using namespace Ops::Base; |
| 21 | using namespace ge; | 22 | using namespace ge; |
| 22 | namespace ops { | 23 | namespace ops { |
| 23 | -// full tensors: input_mul3(0,g^2), input_mul0(4,m). All four outputs share their broadcast shape. | 24 | +// A2 语义: 本族算子的所有输入都是可广播的 ND Tensor(见 canndev |
| 24 | -constexpr size_t IN_MUL3 = 0; | 25 | +// ops/built-in/tbe/impl/lamb_*.py, 每一步 mul/sub/div 都先 shape_util.broadcast_shapes |
| 25 | -constexpr size_t IN_MUL0 = 4; | 26 | +// 再 tbe.broadcast), 输出形状为全部输入广播的结果。A2 的 op_proto 只声明了其中两个输入, |
| 27 | +// 属声明宽松, 不作为支持面依据。 | ||
| 28 | +constexpr size_t IN_NUM = 13; | ||
| 26 | constexpr size_t OUT_NUM = 4; | 29 | constexpr size_t OUT_NUM = 4; |
| 27 | 30 | ||
| 28 | static ge::graphStatus InferShape4LambNextMV(gert::InferShapeContext* context) | 31 | static ge::graphStatus InferShape4LambNextMV(gert::InferShapeContext* context) |
| 29 | { | 32 | { |
| 30 | - auto g2 = context->GetInputShape(IN_MUL3); | 33 | + std::vector<const gert::Shape*> inShapes; |
| 31 | - OP_CHECK_NULL_WITH_CONTEXT(context, g2); | 34 | + inShapes.reserve(IN_NUM); |
| 32 | - auto m = context->GetInputShape(IN_MUL0); | 35 | + for (size_t i = 0; i < IN_NUM; i++) { |
| 33 | - OP_CHECK_NULL_WITH_CONTEXT(context, m); | 36 | + auto in = context->GetInputShape(i); |
| 37 | + OP_CHECK_NULL_WITH_CONTEXT(context, in); | ||
| 38 | + inShapes.push_back(in); | ||
| 39 | + } | ||
| 40 | + gert::Shape bcShape; | ||
| 41 | + // 逐对折叠广播: 只用两参数重载。vector 重载虽在 op_common/op_host/infershape_broadcast_util.h | ||
| 42 | + // 以 Ops::Base 声明, 但 libops_base.so 只导出两参数版, vector 版的实现在 libop_common.so 的 | ||
| 43 | + // 小写 ops 命名空间下 —— 声明与实现命名空间不一致, 用它会编译期通过、加载期 | ||
| 44 | + // undefined symbol 而装不上包。 | ||
| 45 | + bcShape = *inShapes[0]; | ||
| 46 | + for (size_t i = 1; i < inShapes.size(); i++) { | ||
| 47 | + gert::Shape tmp; | ||
| 48 | + OP_CHECK_IF(!BroadcastShape(&bcShape, inShapes[i], &tmp), | ||
| 49 | + OP_LOGE(context->GetNodeName(), "input shapes cannot broadcast together"), return ge::GRAPH_FAILED); | ||
| 50 | + bcShape = tmp; | ||
| 51 | + } | ||
| 52 | + // 标量归一: 全标量输入 broadcast 得 0 维空 shape (), 与 A2 的 shape_util.scalar2tensor_one | ||
| 53 | + // 对齐, 归一为 (1,)。否则动态 shape 编译期 DFX 生成会对空 shape 做 reduce 连乘(无初值)而报 | ||
| 54 | + // TypeError 编译失败。 | ||
| 55 | + if (bcShape.GetDimNum() == 0) { | ||
| 56 | + bcShape.SetDimNum(1); | ||
| 57 | + bcShape.SetDim(0, 1); | ||
| 58 | + } | ||
| 34 | for (size_t i = 0; i < OUT_NUM; i++) { | 59 | for (size_t i = 0; i < OUT_NUM; i++) { |
| 35 | auto out = context->GetOutputShape(i); | 60 | auto out = context->GetOutputShape(i); |
| 36 | OP_CHECK_NULL_WITH_CONTEXT(context, out); | 61 | OP_CHECK_NULL_WITH_CONTEXT(context, out); |
| 37 | - OP_CHECK_IF(!BroadcastShape(g2, m, out), | 62 | + *out = bcShape; |
| 38 | - OP_LOGE_FOR_INVALID_SHAPES_WITH_REASON(context->GetNodeName(), "input_mul3 and input_mul0", | ||
| 39 | - (ToString(*g2) + " and " + ToString(*m)).c_str(), | ||
| 40 | - "shapes cannot broadcast"), | ||
| 41 | - return ge::GRAPH_FAILED); | ||
| 42 | } | 63 | } |
| 43 | return GRAPH_SUCCESS; | 64 | return GRAPH_SUCCESS; |
| 44 | } | 65 | } |
| 66 | + | ||
| 45 | static ge::graphStatus InferDataType4LambNextMV(gert::InferDataTypeContext* context) | 67 | static ge::graphStatus InferDataType4LambNextMV(gert::InferDataTypeContext* context) |
| 46 | { | 68 | { |
| 47 | if (context == nullptr) { | 69 | if (context == nullptr) { |
| @@ -0,0 +1,51 @@ | |||
| 1 | +/** | ||
| 2 | + * Copyright (c) 2026 Huawei Technologies Co., Ltd. | ||
| 3 | + * This program is free software, you can redistribute it and/or modify it under the terms and conditions of | ||
| 4 | + * CANN Open Software License Agreement Version 2.0 (the "License"). | ||
| 5 | + * Please refer to the License for details. You may not use this file except in compliance with the License. | ||
| 6 | + * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, | ||
| 7 | + * INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE. | ||
| 8 | + * See LICENSE in the root of the software repository for the full text of the License. | ||
| 9 | + */ | ||
| 10 | + | ||
| 11 | +/*! | ||
| 12 | + * \file lamb_next_m_v_tiling_def.h | ||
| 13 | + * \brief LambNextMV tiling data definition (arch35 手写内核) | ||
| 14 | + * | ||
| 15 | + * 只用于给 framework 定 raw tiling buffer 的容量; 实际下发由 host 侧 | ||
| 16 | + * memcpy 整个 LambBrcTilingData<13, 4> POD 完成, 故字段与 POD 严格一一对应。 | ||
| 17 | + */ | ||
| 18 | + | ||
| 19 | + | ||
| 20 | + | ||
| 21 | + | ||
| 22 | + | ||
| 23 | + | ||
| 24 | + | ||
| 25 | +namespace optiling { | ||
| 26 | +constexpr int32_t LAMB_NEXT_M_V_IN_NUM = 13; | ||
| 27 | +constexpr int32_t LAMB_NEXT_M_V_MAX_DIM = 8; | ||
| 28 | +constexpr int32_t LAMB_NEXT_M_V_STRIDE_NUM = LAMB_NEXT_M_V_IN_NUM * LAMB_NEXT_M_V_MAX_DIM; | ||
| 29 | + | ||
| 30 | +BEGIN_TILING_DATA_DEF(LambNextMVTilingData) | ||
| 31 | +TILING_DATA_FIELD_DEF(uint64_t, totalNum); | ||
| 32 | +TILING_DATA_FIELD_DEF(uint64_t, totalRows); | ||
| 33 | +TILING_DATA_FIELD_DEF_ARR(uint64_t, LAMB_NEXT_M_V_STRIDE_NUM, effStride); | ||
| 34 | +TILING_DATA_FIELD_DEF_ARR(uint64_t, LAMB_NEXT_M_V_IN_NUM, srcBlockLen); | ||
| 35 | +TILING_DATA_FIELD_DEF(uint64_t, perCoreElems); | ||
| 36 | +TILING_DATA_FIELD_DEF(uint64_t, rowsPerCore); | ||
| 37 | +TILING_DATA_FIELD_DEF(uint32_t, tileLen); | ||
| 38 | +TILING_DATA_FIELD_DEF(uint32_t, blockLen); | ||
| 39 | +TILING_DATA_FIELD_DEF(uint32_t, rowsPerTile); | ||
| 40 | +TILING_DATA_FIELD_DEF(uint32_t, usedCoreNum); | ||
| 41 | +TILING_DATA_FIELD_DEF(uint32_t, splitAxis); | ||
| 42 | +TILING_DATA_FIELD_DEF(uint32_t, collapsedRank); | ||
| 43 | +TILING_DATA_FIELD_DEF(uint32_t, tilingKey); | ||
| 44 | +TILING_DATA_FIELD_DEF_ARR(uint32_t, LAMB_NEXT_M_V_IN_NUM, inKind); | ||
| 45 | +TILING_DATA_FIELD_DEF_ARR(uint32_t, LAMB_NEXT_M_V_MAX_DIM, outShape); | ||
| 46 | +TILING_DATA_FIELD_DEF_ARR(uint32_t, LAMB_NEXT_M_V_STRIDE_NUM, inShape); | ||
| 47 | +END_TILING_DATA_DEF; | ||
| 48 | + | ||
| 49 | +REGISTER_TILING_DATA_CLASS(LambNextMV, LambNextMVTilingData) | ||
| 50 | +} // namespace optiling | ||
| 51 | + | ||
| @@ -1,104 +0,0 @@ | |||
| 1 | -/** | ||
| 2 | - * Copyright (c) 2026 Huawei Technologies Co., Ltd. | ||
| 3 | - * This program is free software, you can redistribute it and/or modify it under the terms and conditions of | ||
| 4 | - * CANN Open Software License Agreement Version 2.0 (the "License"). | ||
| 5 | - * Please refer to the License for details. You may not use this file except in compliance with the License. | ||
| 6 | - * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, | ||
| 7 | - * INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE. | ||
| 8 | - * See LICENSE in the root of the software repository for the full text of the License. | ||
| 9 | - */ | ||
| 10 | - | ||
| 11 | -/*! | ||
| 12 | - * \file lamb_next_m_v_dag.h | ||
| 13 | - * \brief lamb_next_m_v_dag head file | ||
| 14 | - */ | ||
| 15 | - | ||
| 16 | - | ||
| 17 | - | ||
| 18 | - | ||
| 19 | - | ||
| 20 | - | ||
| 21 | - | ||
| 22 | -// The atvoss Placeholder ships In0..In11 (12 inputs). LambNextMV has 13 inputs; extend with In12 | ||
| 23 | -// following the framework's exact pattern (additive: the struct + its IsInHolder trait, same namespace). | ||
| 24 | -namespace Ops { | ||
| 25 | -namespace Base { | ||
| 26 | -namespace Placeholder { | ||
| 27 | -template <class T, class Attr = InAttr<>> | ||
| 28 | -struct In12 : public Holder<T, Attr, 12> {}; | ||
| 29 | -template <class U, class Attr> | ||
| 30 | -struct IsInHolder<In12<U, Attr>> { | ||
| 31 | - constexpr static bool Value = true; | ||
| 32 | -}; | ||
| 33 | -} // namespace Placeholder | ||
| 34 | -} // namespace Base | ||
| 35 | -} // namespace Ops | ||
| 36 | - | ||
| 37 | -namespace LambNextMVOp { | ||
| 38 | -using namespace AscendC; | ||
| 39 | -using namespace Ops::Base; | ||
| 40 | -// In : 0 input_mul3(g^2) 1 input_mul2(v) 2 input_realdiv1(1-b2^t) 3 input_mul1(g) 4 input_mul0(m) | ||
| 41 | -// 5 input_realdiv0(1-b1^t) 6 input_mul4(param) 7 mul0_x(b1) 8 mul1_sub(1-b1) 9 mul2_x(b2) | ||
| 42 | -// 10 mul3_sub1(1-b2) 11 mul4_x(wd) 12 add2_y(eps) [0,1,3,4,6 full; rest scalar] | ||
| 43 | -// Out: 0 y1(update) 1 y2(next_m) 2 y3(next_v) 3 y4(m_unbiased/(sqrt(v_unbiased)+eps)) | ||
| 44 | -// Compute in U (=float): half casts to float (div/sqrt need float precision). rsqrt -> Div(_,Sqrt). | ||
| 45 | -template <typename T, typename U> | ||
| 46 | -struct LambNextMVCompute { | ||
| 47 | - // full tensors | ||
| 48 | - using InG2 = Bind<Vec::CopyInBrc<T>, Placeholder::In0<T>>; | ||
| 49 | - using InV = Bind<Vec::CopyInBrc<T>, Placeholder::In1<T>>; | ||
| 50 | - using InG = Bind<Vec::CopyInBrc<T>, Placeholder::In3<T>>; | ||
| 51 | - using InM = Bind<Vec::CopyInBrc<T>, Placeholder::In4<T>>; | ||
| 52 | - using InParam = Bind<Vec::CopyInBrc<T>, Placeholder::In6<T>>; | ||
| 53 | - // scalar coefficients | ||
| 54 | - using InRd1 = Bind<Vec::Duplicate<T>, Placeholder::In2<T, Placeholder::ScalarAttr<true>>>; | ||
| 55 | - using InRd0 = Bind<Vec::Duplicate<T>, Placeholder::In5<T, Placeholder::ScalarAttr<true>>>; | ||
| 56 | - using InB1 = Bind<Vec::Duplicate<T>, Placeholder::In7<T, Placeholder::ScalarAttr<true>>>; | ||
| 57 | - using InOmB1 = Bind<Vec::Duplicate<T>, Placeholder::In8<T, Placeholder::ScalarAttr<true>>>; | ||
| 58 | - using InB2 = Bind<Vec::Duplicate<T>, Placeholder::In9<T, Placeholder::ScalarAttr<true>>>; | ||
| 59 | - using InOmB2 = Bind<Vec::Duplicate<T>, Placeholder::In10<T, Placeholder::ScalarAttr<true>>>; | ||
| 60 | - using InWd = Bind<Vec::Duplicate<T>, Placeholder::In11<T, Placeholder::ScalarAttr<true>>>; | ||
| 61 | - using InEps = Bind<Vec::Duplicate<T>, Placeholder::In12<T, Placeholder::ScalarAttr<true>>>; | ||
| 62 | - | ||
| 63 | - using G2 = Bind<Vec::Cast<U, T, 0>, InG2>; | ||
| 64 | - using V = Bind<Vec::Cast<U, T, 0>, InV>; | ||
| 65 | - using G = Bind<Vec::Cast<U, T, 0>, InG>; | ||
| 66 | - using M = Bind<Vec::Cast<U, T, 0>, InM>; | ||
| 67 | - using Param = Bind<Vec::Cast<U, T, 0>, InParam>; | ||
| 68 | - using Rd1 = Bind<Vec::Cast<U, T, 0>, InRd1>; | ||
| 69 | - using Rd0 = Bind<Vec::Cast<U, T, 0>, InRd0>; | ||
| 70 | - using B1 = Bind<Vec::Cast<U, T, 0>, InB1>; | ||
| 71 | - using OmB1 = Bind<Vec::Cast<U, T, 0>, InOmB1>; | ||
| 72 | - using B2 = Bind<Vec::Cast<U, T, 0>, InB2>; | ||
| 73 | - using OmB2 = Bind<Vec::Cast<U, T, 0>, InOmB2>; | ||
| 74 | - using Wd = Bind<Vec::Cast<U, T, 0>, InWd>; | ||
| 75 | - using Eps = Bind<Vec::Cast<U, T, 0>, InEps>; | ||
| 76 | - | ||
| 77 | - // next_v = v*b2 + g^2*(1-b2) | ||
| 78 | - using NextV = Bind<Vec::Add<U>, Bind<Vec::Mul<U>, V, B2>, Bind<Vec::Mul<U>, G2, OmB2>>; | ||
| 79 | - using VUnbias = Bind<Vec::Div<U>, NextV, Rd1>; | ||
| 80 | - using SqrtVeps = Bind<Vec::Sqrt<U>, Bind<Vec::Add<U>, VUnbias, Eps>>; // sqrt(v_unbiased+eps) | ||
| 81 | - using SqrtVAddEps = Bind<Vec::Add<U>, Bind<Vec::Sqrt<U>, VUnbias>, Eps>; // sqrt(v_unbiased)+eps | ||
| 82 | - // next_m = m*b1 + g*(1-b1) | ||
| 83 | - using NextM = Bind<Vec::Add<U>, Bind<Vec::Mul<U>, M, B1>, Bind<Vec::Mul<U>, G, OmB1>>; | ||
| 84 | - using MUnbias = Bind<Vec::Div<U>, NextM, Rd0>; | ||
| 85 | - // y1 = param*wd + m_unbiased/sqrt(v_unbiased+eps) | ||
| 86 | - using Y1 = Bind<Vec::Add<U>, Bind<Vec::Mul<U>, Param, Wd>, Bind<Vec::Div<U>, MUnbias, SqrtVeps>>; | ||
| 87 | - using Y4 = Bind<Vec::Div<U>, MUnbias, SqrtVAddEps>; | ||
| 88 | - | ||
| 89 | - using Y1Cast = Bind<Vec::Cast<T, U, 1>, Y1>; | ||
| 90 | - using Y2Cast = Bind<Vec::Cast<T, U, 1>, NextM>; | ||
| 91 | - using Y3Cast = Bind<Vec::Cast<T, U, 1>, NextV>; | ||
| 92 | - using Y4Cast = Bind<Vec::Cast<T, U, 1>, Y4>; | ||
| 93 | - | ||
| 94 | - using OpOut0 = Bind<Vec::CopyOut<T>, Placeholder::Out0<T>, Y1Cast>; | ||
| 95 | - using OpOut1 = Bind<Vec::CopyOut<T>, Placeholder::Out1<T>, Y2Cast>; | ||
| 96 | - using OpOut2 = Bind<Vec::CopyOut<T>, Placeholder::Out2<T>, Y3Cast>; | ||
| 97 | - using OpOut3 = Bind<Vec::CopyOut<T>, Placeholder::Out3<T>, Y4Cast>; | ||
| 98 | - | ||
| 99 | - using Outputs = Elems<OpOut0, OpOut1, OpOut2, OpOut3>; | ||
| 100 | - using MemCfg = MemOptCfg<MemLevel::LEVEL_1>; | ||
| 101 | - using OpDag = DAGSch<Outputs, void, MemCfg>; | ||
| 102 | -}; | ||
| 103 | -} // namespace LambNextMVOp | ||
| 104 | - | ||
| @@ -1,27 +0,0 @@ | |||
| 1 | -/** | ||
| 2 | - * Copyright (c) 2026 Huawei Technologies Co., Ltd. | ||
| 3 | - * This program is free software, you can redistribute it and/or modify it under the terms and conditions of | ||
| 4 | - * CANN Open Software License Agreement Version 2.0 (the "License"). | ||
| 5 | - * Please refer to the License for details. You may not use this file except in compliance with the License. | ||
| 6 | - * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, | ||
| 7 | - * INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE. | ||
| 8 | - * See LICENSE in the root of the software repository for the full text of the License. | ||
| 9 | - */ | ||
| 10 | - | ||
| 11 | -/*! | ||
| 12 | - * \file lamb_next_m_v_tiling_key.h | ||
| 13 | - * \brief lamb_next_m_v_tiling_key head file | ||
| 14 | - */ | ||
| 15 | - | ||
| 16 | - | ||
| 17 | - | ||
| 18 | - | ||
| 19 | - | ||
| 20 | - | ||
| 21 | -using namespace Ops::Base; | ||
| 22 | -// 算子自定义的tiling key字段 | ||
| 23 | -ASCENDC_TPL_ARGS_DECL(LambNextMV, BRC_NDDMA_SCH_MODE_KEY_DECL(schMode)); | ||
| 24 | - | ||
| 25 | -ASCENDC_TPL_SEL(ASCENDC_TPL_ARGS_SEL(BRC_NDDMA_SCH_MODE_KEY_SEL(schMode))); | ||
| 26 | - | ||
| 27 | - | ||
Roptim/lamb_next_m_v_with_decay/op_kernel/lamb_next_m_v_with_decay.cpp→optim/lamb_next_m_v/op_kernel/arch35/lamb_next_mv.cpp+25-19文件内容审核中,请稍后刷新重试
| @@ -1,43 +0,0 @@ | |||
| 1 | -/** | ||
| 2 | - * Copyright (c) 2026 Huawei Technologies Co., Ltd. | ||
| 3 | - * This program is free software, you can redistribute it and/or modify it under the terms and conditions of | ||
| 4 | - * CANN Open Software License Agreement Version 2.0 (the "License"). | ||
| 5 | - * Please refer to the License for details. You may not use this file except in compliance with the License. | ||
| 6 | - * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, | ||
| 7 | - * INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE. | ||
| 8 | - * See LICENSE in the root of the software repository for the full text of the License. | ||
| 9 | - */ | ||
| 10 | - | ||
| 11 | -/*! | ||
| 12 | - * \file lamb_next_m_v.cpp | ||
| 13 | - * \brief lamb_next_m_v.cpp | ||
| 14 | - */ | ||
| 15 | - | ||
| 16 | - | ||
| 17 | - | ||
| 18 | - | ||
| 19 | - | ||
| 20 | - | ||
| 21 | -using namespace AscendC; | ||
| 22 | -using namespace Ops::Base; | ||
| 23 | - | ||
| 24 | -template <uint64_t schMode> | ||
| 25 | -__global__ __aicore__ void lamb_next_mv(GM_ADDR input_mul3, GM_ADDR input_mul2, GM_ADDR input_realdiv1, | ||
| 26 | - GM_ADDR input_mul1, GM_ADDR input_mul0, GM_ADDR input_realdiv0, | ||
| 27 | - GM_ADDR input_mul4, GM_ADDR mul0_x, GM_ADDR mul1_sub, GM_ADDR mul2_x, | ||
| 28 | - GM_ADDR mul3_sub1, GM_ADDR mul4_x, GM_ADDR add2_y, GM_ADDR y1, GM_ADDR y2, | ||
| 29 | - GM_ADDR y3, GM_ADDR y4, GM_ADDR workspace, GM_ADDR tiling) | ||
| 30 | -{ | ||
| 31 | - KERNEL_TASK_TYPE_DEFAULT(KERNEL_TYPE_AIV_ONLY); | ||
| 32 | - if constexpr (std::is_same<DTYPE_INPUT_MUL3, half>::value) { | ||
| 33 | - using OpDag = LambNextMVOp::LambNextMVCompute<half, float>::OpDag; | ||
| 34 | - BroadcastSch<schMode, OpDag> sch(tiling); | ||
| 35 | - sch.Process(input_mul3, input_mul2, input_realdiv1, input_mul1, input_mul0, input_realdiv0, input_mul4, mul0_x, | ||
| 36 | - mul1_sub, mul2_x, mul3_sub1, mul4_x, add2_y, y1, y2, y3, y4); | ||
| 37 | - } else { | ||
| 38 | - using OpDag = LambNextMVOp::LambNextMVCompute<float, float>::OpDag; | ||
| 39 | - BroadcastSch<schMode, OpDag> sch(tiling); | ||
| 40 | - sch.Process(input_mul3, input_mul2, input_realdiv1, input_mul1, input_mul0, input_realdiv0, input_mul4, mul0_x, | ||
| 41 | - mul1_sub, mul2_x, mul3_sub1, mul4_x, add2_y, y1, y2, y3, y4); | ||
| 42 | - } | ||
| 43 | -} | ||
| @@ -19,14 +19,18 @@ __golden__ = {"kernel": {"lamb_next_mv": "lamb_next_mv_golden"}} | |||
| 19 | 19 | ||
| 20 | def _scalars(*xs): | 20 | def _scalars(*xs): |
| 21 | """取标量并落到 float32 torch 标量张量上。不能返回 Python float——那是 fp64,标量 | 21 | """取标量并落到 float32 torch 标量张量上。不能返回 Python float——那是 fp64,标量 |
| 22 | - 运算会被抬到双精度,而算子在 fp32 上算(A2 的 TBE compute 里 dtype='float32', | 22 | + 运算会被抬到双精度,而 arch35 内核的计算类型 U = float(fp16 输入 unpack 成 fp32 再算)。 |
| 23 | + 注:A2 并**不**升精度 —— canndev 的 tbe impl 是 tvm.placeholder(dtype=input_dtype),全程无 cast_to, | ||
| 24 | + fp16 输入就在 fp16 上做 vmul/vdiv/vsqrt。此处跟随 arch35 的计算类型,不跟随 A2( | ||
| 23 | arch35 DAG 的计算类型 U = float)。numpy 只用于取值与 dtype 转换。""" | 25 | arch35 DAG 的计算类型 U = float)。numpy 只用于取值与 dtype 转换。""" |
| 24 | # 标量落成 **0 维 float64** 张量: torch 的类型提升里 0 维张量不会把 dim>0 的张量抬档, | 26 | # 标量落成 **0 维 float64** 张量: torch 的类型提升里 0 维张量不会把 dim>0 的张量抬档, |
| 25 | # 所以数据是 fp32 时结果仍是 fp32(与改动前一致),数据被 Promote 成 fp64 时标量自动 | 27 | # 所以数据是 fp32 时结果仍是 fp32(与改动前一致),数据被 Promote 成 fp64 时标量自动 |
| 26 | # 跟到 fp64,不会用一个先降到 fp32 的标量去污染高精度真值。 | 28 | # 跟到 fp64,不会用一个先降到 fp32 的标量去污染高精度真值。 |
| 27 | - return tuple( | 29 | + # A2 语义: 这些"系数"输入是**可广播的 ND Tensor**(canndev ops/built-in/tbe/impl/lamb_*.py |
| 28 | - torch.from_numpy(np.asarray(x, "float64").reshape(-1)[:1])[0] for x in xs | 30 | + # 每步 mul/sub/div 都先 shape_util.broadcast_shapes 再 tbe.broadcast)。原先 reshape(-1)[:1] |
| 29 | - ) | 31 | + # 只取首元素, 传多元素张量时静默按首元素计算 —— 与内核的广播实现不一致, 广播档必然假红。 |
| 32 | + # 改为返回完整张量交给 torch 自然广播: 形状 (1,) 的行为与原标量完全一致, 故常规档不变。 | ||
| 33 | + return tuple(_t(x) for x in xs) | ||
| 30 | 34 | ||
| 31 | 35 | ||
| 32 | def _t(x): | 36 | def _t(x): |
| @@ -140,31 +144,24 @@ def _tp_t(x): | |||
| 140 | 144 | ||
| 141 | 145 | ||
| 142 | def _tp_widen(t): | 146 | def _tp_widen(t): |
| 143 | - """把三方入参加宽到**内核的计算类型**, 复刻 op_kernel/arch35/lamb_next_m_v_dag.h 的 | 147 | + """按 NPU 的加宽行为加宽三方入参。 |
| 144 | - `Cast<U, T, 0>`(见该文件注释: "Compute in U (=float): half casts to float")。 | ||
| 145 | 148 | ||
| 146 | - 这不是"给竞品放水抬精度", 而是**同算法转写**——本算子是融合 DAG, 十几步中间量全程留在 | 149 | + 三方腿拿到的是**原始 dtype T**(TTK 的 Promote 只作用于 golden)。内核对 fp16 输入 unpack |
| 147 | - fp32、一次都不落回 T; 而 torch 只在**单个算子内部**用 opmath=float, 算子之间每一步都把 | 150 | + 成 fp32 全程不落回(dag.h 的 `Cast<U, T, 0>`, U = float), 而 torch 只在单个算子内部用 |
| 148 | - 结果落回 fp16(A100 实测: fp16 的 300*300 直接得 inf, 而 (a*a)/a 真值 300 明明存得下)。 | 151 | + opmath=float、算子之间每步落回 T —— 不加宽就等于拿"逐步截断的实现"当竞品。 |
| 149 | - 不加宽就等于拿"逐步截断的实现"当竞品, 与被测内核不是同一个算法。 | 152 | + T = fp32 时内核计算类型即 fp32, 不加宽; 整型不动(走 fp32 会抹掉 >2^24 的低位)。 |
| 150 | - | ||
| 151 | - 整型不动: 内核对 int 也是原生/int32 累加, 走 fp32 会把 >2^24 抹掉低位。 | ||
| 152 | """ | 153 | """ |
| 153 | return t.float() if t.dtype in (torch.float16, torch.bfloat16) else t | 154 | return t.float() if t.dtype in (torch.float16, torch.bfloat16) else t |
| 154 | 155 | ||
| 155 | 156 | ||
| 156 | def _tp_narrow(outs, dt): | 157 | def _tp_narrow(outs, dt): |
| 157 | - """出口复刻内核的 `Cast<T, U, 1>`: 算完窄回算子输出 dtype。 | 158 | + """出口复刻内核的 `Cast<T, U, 1>`: 窄回算子声明的 dtype T。""" |
| 158 | - | ||
| 159 | - 这个 cast **必须与 _tp_widen 成对出现**: 少了它, 三方停在 fp32, 会与走 TTK Promote | ||
| 160 | - (fp16->fp32) 的 golden 逐位相等 —— 双标杆塌成单标杆, 三比值分母被夹到 §4.5.1 的 err, | ||
| 161 | - 有量纲的 RMSE 比值随输出量级线性放大而假红。 | ||
| 162 | - """ | ||
| 163 | return [o.to(dt) if o.is_floating_point() else o for o in outs] | 159 | return [o.to(dt) if o.is_floating_point() else o for o in outs] |
| 164 | 160 | ||
| 165 | 161 | ||
| 166 | def _tp_s(x): | 162 | def _tp_s(x): |
| 167 | - return _tp_t(x).reshape(-1)[0] | 163 | + # 同 _scalars: 三方腿也必须广播, 不能只取首元素 |
| 164 | + return _tp_t(x) | ||
| 168 | 165 | ||
| 169 | 166 | ||
| 170 | class _LambNextMVCompose: | 167 | class _LambNextMVCompose: |
| @@ -185,7 +182,7 @@ class _LambNextMVCompose: | |||
| 185 | add2_y, | 182 | add2_y, |
| 186 | **kwargs, | 183 | **kwargs, |
| 187 | ): | 184 | ): |
| 188 | - _dt = _tp_t(input_mul3).dtype # 算子输出 dtype | 185 | + _dt = _tp_t(input_mul3).dtype # 算子声明的 dtype T(三方腿入参不经 Promote) |
| 189 | g2, v, g, m, param = ( | 186 | g2, v, g, m, param = ( |
| 190 | _tp_widen(_tp_t(t)) | 187 | _tp_widen(_tp_t(t)) |
| 191 | for t in (input_mul3, input_mul2, input_mul1, input_mul0, input_mul4) | 188 | for t in (input_mul3, input_mul2, input_mul1, input_mul0, input_mul4) |
| @@ -1,9 +1,9 @@ | |||
| 1 | # ----------------------------------------------------------------------------------------------------------- | 1 | # ----------------------------------------------------------------------------------------------------------- |
| 2 | # Copyright (c) 2026 Huawei Technologies Co., Ltd. | 2 | # Copyright (c) 2026 Huawei Technologies Co., Ltd. |
| 3 | -# This program is free software, you can redistribute it and/or modify it under the terms and conditions of | 3 | +# This program is free software, you can redistribute it and/or modify it under the terms and conditions of |
| 4 | # CANN Open Software License Agreement Version 2.0 (the "License"). | 4 | # CANN Open Software License Agreement Version 2.0 (the "License"). |
| 5 | # Please refer to the License for details. You may not use this file except in compliance with the License. | 5 | # Please refer to the License for details. You may not use this file except in compliance with the License. |
| 6 | -# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, | 6 | +# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, |
| 7 | # INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE. | 7 | # INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE. |
| 8 | # See LICENSE in the root of the software repository for the full text of the License. | 8 | # See LICENSE in the root of the software repository for the full text of the License. |
| 9 | # ----------------------------------------------------------------------------------------------------------- | 9 | # ----------------------------------------------------------------------------------------------------------- |
| @@ -13,4 +13,4 @@ set(SUPPORT_COMPUTE_UNIT "ascend950") | |||
| 13 | # 设置每种芯片类型对应的tiling文件目录,即采用op_host目录下哪个文件夹下的tiling文件编译 | 13 | # 设置每种芯片类型对应的tiling文件目录,即采用op_host目录下哪个文件夹下的tiling文件编译 |
| 14 | set(SUPPORT_TILING_DIR "arch35") | 14 | set(SUPPORT_TILING_DIR "arch35") |
| 15 | 15 | ||
| 16 | -add_modules_sources(HOSTNAME ${OPHOST_NAME} MODE PRIVATE DIR ${CMAKE_CURRENT_SOURCE_DIR} OPTYPE lamb_next_m_v_with_decay ACLNNTYPE aclnn_exclude COMPUTE_UNIT ${SUPPORT_COMPUTE_UNIT} TILING_DIR ${SUPPORT_TILING_DIR} DISABLE_IN_OPP TRUE) | 16 | +add_modules_sources(HOSTNAME ${OPHOST_NAME} MODE PRIVATE DIR ${CMAKE_CURRENT_SOURCE_DIR} OPTYPE lamb_next_m_v_with_decay ACLNNTYPE aclnn_exclude COMPUTE_UNIT ${SUPPORT_COMPUTE_UNIT} TILING_DIR ${SUPPORT_TILING_DIR} DEPENDENCIES lamb_apply_common DISABLE_IN_OPP TRUE) |
| @@ -48,7 +48,7 @@ | |||
| 48 | <tr> | 48 | <tr> |
| 49 | <td>input_mul3</td> | 49 | <td>input_mul3</td> |
| 50 | <td>输入</td> | 50 | <td>输入</td> |
| 51 | - <td>支持空Tensor。公式中的input_mul3(g^2),主张量,shape需与input_mul0满足broadcast关系。</td> | 51 | + <td>支持空Tensor。公式中的input_mul3(g^2),主张量,shape需与其他输入满足broadcast关系。</td> |
| 52 | <td>FLOAT16、FLOAT</td> | 52 | <td>FLOAT16、FLOAT</td> |
| 53 | <td>ND</td> | 53 | <td>ND</td> |
| 54 | </tr> | 54 | </tr> |
| @@ -76,7 +76,7 @@ | |||
| 76 | <tr> | 76 | <tr> |
| 77 | <td>input_mul0</td> | 77 | <td>input_mul0</td> |
| 78 | <td>输入</td> | 78 | <td>输入</td> |
| 79 | - <td>支持空Tensor。公式中的input_mul0(一阶矩m),主张量,shape需与input_mul3满足broadcast关系,其broadcast结果决定各输出的shape。</td> | 79 | + <td>支持空Tensor。公式中的input_mul0(一阶矩m),主张量,shape需与其他输入满足broadcast关系,全部输入的broadcast结果决定各输出的shape。</td> |
| 80 | <td>FLOAT16、FLOAT</td> | 80 | <td>FLOAT16、FLOAT</td> |
| 81 | <td>ND</td> | 81 | <td>ND</td> |
| 82 | </tr> | 82 | </tr> |
| @@ -97,70 +97,70 @@ | |||
| 97 | <tr> | 97 | <tr> |
| 98 | <td>mul0_x</td> | 98 | <td>mul0_x</td> |
| 99 | <td>输入</td> | 99 | <td>输入</td> |
| 100 | - <td>不支持空Tensor。公式中的mul0_x(beta1),标量。</td> | 100 | + <td>支持空Tensor。公式中的mul0_x(beta1),shape支持1-8维,需与其他输入满足broadcast规则(右对齐,对应维相等或为1)。</td> |
| 101 | <td>FLOAT16、FLOAT</td> | 101 | <td>FLOAT16、FLOAT</td> |
| 102 | <td>ND</td> | 102 | <td>ND</td> |
| 103 | </tr> | 103 | </tr> |
| 104 | <tr> | 104 | <tr> |
| 105 | <td>mul1_sub</td> | 105 | <td>mul1_sub</td> |
| 106 | <td>输入</td> | 106 | <td>输入</td> |
| 107 | - <td>不支持空Tensor。公式中的mul1_sub(1-beta1),标量。</td> | 107 | + <td>支持空Tensor。公式中的mul1_sub(1-beta1),shape支持1-8维,需与其他输入满足broadcast规则(右对齐,对应维相等或为1)。</td> |
| 108 | <td>FLOAT16、FLOAT</td> | 108 | <td>FLOAT16、FLOAT</td> |
| 109 | <td>ND</td> | 109 | <td>ND</td> |
| 110 | </tr> | 110 | </tr> |
| 111 | <tr> | 111 | <tr> |
| 112 | <td>mul2_x</td> | 112 | <td>mul2_x</td> |
| 113 | <td>输入</td> | 113 | <td>输入</td> |
| 114 | - <td>不支持空Tensor。公式中的mul2_x(beta2),标量。</td> | 114 | + <td>支持空Tensor。公式中的mul2_x(beta2),shape支持1-8维,需与其他输入满足broadcast规则(右对齐,对应维相等或为1)。</td> |
| 115 | <td>FLOAT16、FLOAT</td> | 115 | <td>FLOAT16、FLOAT</td> |
| 116 | <td>ND</td> | 116 | <td>ND</td> |
| 117 | </tr> | 117 | </tr> |
| 118 | <tr> | 118 | <tr> |
| 119 | <td>mul3_sub1</td> | 119 | <td>mul3_sub1</td> |
| 120 | <td>输入</td> | 120 | <td>输入</td> |
| 121 | - <td>不支持空Tensor。公式中的mul3_sub1(1-beta2),标量。</td> | 121 | + <td>支持空Tensor。公式中的mul3_sub1(1-beta2),shape支持1-8维,需与其他输入满足broadcast规则(右对齐,对应维相等或为1)。</td> |
| 122 | <td>FLOAT16、FLOAT</td> | 122 | <td>FLOAT16、FLOAT</td> |
| 123 | <td>ND</td> | 123 | <td>ND</td> |
| 124 | </tr> | 124 | </tr> |
| 125 | <tr> | 125 | <tr> |
| 126 | <td>mul4_x</td> | 126 | <td>mul4_x</td> |
| 127 | <td>输入</td> | 127 | <td>输入</td> |
| 128 | - <td>不支持空Tensor。公式中的mul4_x(权重衰减系数),标量。</td> | 128 | + <td>支持空Tensor。公式中的mul4_x(权重衰减系数),shape支持1-8维,需与其他输入满足broadcast规则(右对齐,对应维相等或为1)。</td> |
| 129 | <td>FLOAT16、FLOAT</td> | 129 | <td>FLOAT16、FLOAT</td> |
| 130 | <td>ND</td> | 130 | <td>ND</td> |
| 131 | </tr> | 131 | </tr> |
| 132 | <tr> | 132 | <tr> |
| 133 | <td>add2_y</td> | 133 | <td>add2_y</td> |
| 134 | <td>输入</td> | 134 | <td>输入</td> |
| 135 | - <td>不支持空Tensor。公式中的add2_y(epsilon),标量。</td> | 135 | + <td>支持空Tensor。公式中的add2_y(epsilon),shape支持1-8维,需与其他输入满足broadcast规则(右对齐,对应维相等或为1)。</td> |
| 136 | <td>FLOAT16、FLOAT</td> | 136 | <td>FLOAT16、FLOAT</td> |
| 137 | <td>ND</td> | 137 | <td>ND</td> |
| 138 | </tr> | 138 | </tr> |
| 139 | <tr> | 139 | <tr> |
| 140 | <td>y1</td> | 140 | <td>y1</td> |
| 141 | <td>输出</td> | 141 | <td>输出</td> |
| 142 | - <td>支持空Tensor。公式中的y1(update),shape取input_mul3与input_mul0的broadcast结果。</td> | 142 | + <td>支持空Tensor。公式中的y1(update),shape取全部输入的broadcast结果。</td> |
| 143 | <td>FLOAT16、FLOAT</td> | 143 | <td>FLOAT16、FLOAT</td> |
| 144 | <td>ND</td> | 144 | <td>ND</td> |
| 145 | </tr> | 145 | </tr> |
| 146 | <tr> | 146 | <tr> |
| 147 | <td>y2</td> | 147 | <td>y2</td> |
| 148 | <td>输出</td> | 148 | <td>输出</td> |
| 149 | - <td>支持空Tensor。公式中的y2(next_m),shape取input_mul3与input_mul0的broadcast结果。</td> | 149 | + <td>支持空Tensor。公式中的y2(next_m),shape取全部输入的broadcast结果。</td> |
| 150 | <td>FLOAT16、FLOAT</td> | 150 | <td>FLOAT16、FLOAT</td> |
| 151 | <td>ND</td> | 151 | <td>ND</td> |
| 152 | </tr> | 152 | </tr> |
| 153 | <tr> | 153 | <tr> |
| 154 | <td>y3</td> | 154 | <td>y3</td> |
| 155 | <td>输出</td> | 155 | <td>输出</td> |
| 156 | - <td>支持空Tensor。公式中的y3(next_v),shape取input_mul3与input_mul0的broadcast结果。</td> | 156 | + <td>支持空Tensor。公式中的y3(next_v),shape取全部输入的broadcast结果。</td> |
| 157 | <td>FLOAT16、FLOAT</td> | 157 | <td>FLOAT16、FLOAT</td> |
| 158 | <td>ND</td> | 158 | <td>ND</td> |
| 159 | </tr> | 159 | </tr> |
| 160 | <tr> | 160 | <tr> |
| 161 | <td>y4</td> | 161 | <td>y4</td> |
| 162 | <td>输出</td> | 162 | <td>输出</td> |
| 163 | - <td>支持空Tensor。公式中的y4,shape取input_mul3与input_mul0的broadcast结果。</td> | 163 | + <td>支持空Tensor。公式中的y4,shape取全部输入的broadcast结果。</td> |
| 164 | <td>FLOAT16、FLOAT</td> | 164 | <td>FLOAT16、FLOAT</td> |
| 165 | <td>ND</td> | 165 | <td>ND</td> |
| 166 | </tr> | 166 | </tr> |
| @@ -168,8 +168,10 @@ | |||
| 168 | 168 | ||
| 169 | ## 约束说明 | 169 | ## 约束说明 |
| 170 | 170 | ||
| 171 | +- 所有输入的shape需两两满足broadcast规则(右对齐,对应维相等或为1),输出shape为全部输入的broadcast结果。 | ||
| 172 | +- 所有输入及输出的维度数不超过8。 | ||
| 171 | - 所有输入的数据类型必须一致,同为FLOAT16或同为FLOAT。 | 173 | - 所有输入的数据类型必须一致,同为FLOAT16或同为FLOAT。 |
| 172 | -- input_mul0/input_mul1/input_mul2/input_mul3/input_mul4 为主张量,其shape需保持一致(或可相互广播到同一shape);各输出y1/y2/y3/y4的shape均取该广播结果(实现以input_mul3与input_mul0的broadcast结果为准)。 | 174 | +- 各输出y1/y2/y3/y4的shape均取全部输入的broadcast结果。 |
| 173 | 175 | ||
| 174 | ## 调用说明 | 176 | ## 调用说明 |
| 175 | 177 | ||
| @@ -15,20 +15,21 @@ | |||
| 15 | 15 | ||
| 16 | 16 | ||
| 17 | 17 | ||
| 18 | -#include "../../op_kernel/arch35/lamb_next_m_v_with_decay_dag.h" | 18 | +#include <securec.h> |
| 19 | -#include "atvoss/broadcast/broadcast_tiling.h" | 19 | +#include <algorithm> |
| 20 | 20 | ||
| 21 | 21 | ||
| 22 | 22 | ||
| 23 | 23 | ||
| 24 | 24 | ||
| 25 | 25 | ||
| 26 | -using namespace AscendC; | ||
| 27 | using namespace ge; | 26 | using namespace ge; |
| 28 | 27 | ||
| 29 | namespace optiling { | 28 | namespace optiling { |
| 30 | 29 | ||
| 31 | constexpr static uint64_t LAMB_NEXT_M_V_TILING_PRIORITY = 0; | 30 | constexpr static uint64_t LAMB_NEXT_M_V_TILING_PRIORITY = 0; |
| 31 | +constexpr static uint64_t TILING_KEY_FP32 = 100; | ||
| 32 | +constexpr static uint64_t TILING_KEY_FP16 = 200; | ||
| 32 | constexpr static int32_t INPUT_NUM = 13; | 33 | constexpr static int32_t INPUT_NUM = 13; |
| 33 | constexpr static int32_t OUTPUT_NUM = 4; | 34 | constexpr static int32_t OUTPUT_NUM = 4; |
| 34 | 35 | ||
| @@ -80,18 +81,6 @@ ge::graphStatus LambNextMVWithDecayTiling::GetShapeAttrsInfo() | |||
| 80 | return ge::GRAPH_FAILED; | 81 | return ge::GRAPH_FAILED; |
| 81 | } | 82 | } |
| 82 | } | 83 | } |
| 83 | - // 标量类输入为每元素计算所必需的系数, 空Tensor 视为缺失必选值(畸形输入), 不支持。 | ||
| 84 | - static const int32_t kScalarInputIdx[] = {7, 8, 9, 10, 11, 12}; | ||
| 85 | - for (int32_t scalarIdx : kScalarInputIdx) { | ||
| 86 | - auto scalarShape = context_->GetInputShape(scalarIdx); | ||
| 87 | - OP_CHECK_NULL_WITH_CONTEXT(context_, scalarShape); | ||
| 88 | - if (scalarShape->GetStorageShape().GetShapeSize() == 0) { | ||
| 89 | - OP_LOGE_FOR_INVALID_SHAPE_WITH_REASON(context_->GetNodeName(), kInputNames[scalarIdx], | ||
| 90 | - Ops::Base::ToString(scalarShape->GetStorageShape()).c_str(), | ||
| 91 | - "scalar input does not support empty tensor"); | ||
| 92 | - return ge::GRAPH_FAILED; | ||
| 93 | - } | ||
| 94 | - } | ||
| 95 | return ge::GRAPH_SUCCESS; | 84 | return ge::GRAPH_SUCCESS; |
| 96 | } | 85 | } |
| 97 | 86 | ||
| @@ -99,49 +88,44 @@ bool LambNextMVWithDecayTiling::IsCapable() { return true; } | |||
| 99 | 88 | ||
| 100 | ge::graphStatus LambNextMVWithDecayTiling::DoOpTiling() | 89 | ge::graphStatus LambNextMVWithDecayTiling::DoOpTiling() |
| 101 | { | 90 | { |
| 102 | - // 空 tensor 应对(空进空出): 输出为空(0元素)时设 1 核(空转), 配合全0 tiling 数据(blockFormer=0)使 kernel 空转退出, | 91 | + auto rawTilingData = context_->GetRawTilingData(); |
| 103 | - // 直接成功。 | 92 | + OP_CHECK_NULL_WITH_CONTEXT(context_, rawTilingData); |
| 104 | - auto emptyTensorOutShape0 = context_->GetOutputShape(0); | ||
| 105 | - if (emptyTensorOutShape0 != nullptr && emptyTensorOutShape0->GetStorageShape().GetShapeSize() == 0) { | ||
| 106 | - auto emptyRawTiling = context_->GetRawTilingData(); | ||
| 107 | - if (emptyRawTiling != nullptr && emptyRawTiling->GetData() != nullptr) { | ||
| 108 | - size_t emptyCap = emptyRawTiling->GetCapacity(); | ||
| 109 | - uint8_t* emptyPtr = static_cast<uint8_t*>(emptyRawTiling->GetData()); | ||
| 110 | - for (size_t emptyIdx = 0; emptyIdx < emptyCap; ++emptyIdx) { | ||
| 111 | - emptyPtr[emptyIdx] = 0; | ||
| 112 | - } | ||
| 113 | - emptyRawTiling->SetDataSize(emptyCap); | ||
| 114 | - } | ||
| 115 | - size_t* emptyWs = context_->GetWorkspaceSizes(1); | ||
| 116 | - if (emptyWs != nullptr) { | ||
| 117 | - emptyWs[0] = 0; | ||
| 118 | - } | ||
| 119 | - context_->SetBlockDim(1); | ||
| 120 | - tilingKey = GET_TPL_TILING_KEY(1); // schMode=1(已编译), 配合全0 tiling(blockFormer=0)空转 | ||
| 121 | - return ge::GRAPH_SUCCESS; | ||
| 122 | - } | ||
| 123 | auto input0Desc = context_->GetInputDesc(0); | 93 | auto input0Desc = context_->GetInputDesc(0); |
| 124 | OP_CHECK_NULL_WITH_CONTEXT(context_, input0Desc); | 94 | OP_CHECK_NULL_WITH_CONTEXT(context_, input0Desc); |
| 95 | + | ||
| 125 | ge::DataType input0DType = input0Desc->GetDataType(); | 96 | ge::DataType input0DType = input0Desc->GetDataType(); |
| 126 | - if (input0DType == ge::DT_FLOAT16) { | 97 | + uint32_t dtSize = 0; |
| 127 | - BroadcastBaseTiling<LambNextMVWithDecayOp::LambNextMVWithDecayCompute<half, float>::OpDag> brcBaseTiling( | 98 | + if (input0DType == ge::DT_FLOAT) { |
| 128 | - context_, static_cast<uint32_t>(BROADCAST_KERNEL_TYPE::KERNEL_TYPE_NDDMA)); | 99 | + tilingKey = TILING_KEY_FP32; |
| 129 | - OP_CHECK_IF(brcBaseTiling.DoTiling() == ge::GRAPH_FAILED, | 100 | + dtSize = sizeof(float); |
| 130 | - OP_LOGE(context_->GetNodeName(), "Do tiling failed. Please check the detailed log."), | 101 | + } else if (input0DType == ge::DT_FLOAT16) { |
| 131 | - return ge::GRAPH_FAILED); | 102 | + tilingKey = TILING_KEY_FP16; |
| 132 | - tilingKey = GET_TPL_TILING_KEY(brcBaseTiling.GetSchMode()); | 103 | + dtSize = sizeof(uint16_t); |
| 133 | - } else if (input0DType == ge::DT_FLOAT) { | ||
| 134 | - BroadcastBaseTiling<LambNextMVWithDecayOp::LambNextMVWithDecayCompute<float, float>::OpDag> brcBaseTiling( | ||
| 135 | - context_, static_cast<uint32_t>(BROADCAST_KERNEL_TYPE::KERNEL_TYPE_NDDMA)); | ||
| 136 | - OP_CHECK_IF(brcBaseTiling.DoTiling() == ge::GRAPH_FAILED, | ||
| 137 | - OP_LOGE(context_->GetNodeName(), "Do tiling failed. Please check the detailed log."), | ||
| 138 | - return ge::GRAPH_FAILED); | ||
| 139 | - tilingKey = GET_TPL_TILING_KEY(brcBaseTiling.GetSchMode()); | ||
| 140 | } else { | 104 | } else { |
| 141 | OP_LOGE_FOR_INVALID_DTYPE(context_->GetNodeName(), "input_mul3", Ops::Base::ToString(input0DType).c_str(), | 105 | OP_LOGE_FOR_INVALID_DTYPE(context_->GetNodeName(), "input_mul3", Ops::Base::ToString(input0DType).c_str(), |
| 142 | "fp16 or fp32"); | 106 | "fp16 or fp32"); |
| 143 | return ge::GRAPH_FAILED; | 107 | return ge::GRAPH_FAILED; |
| 144 | } | 108 | } |
| 109 | + | ||
| 110 | + using PlanTiling = LambBrcTilingData<13, 4>; | ||
| 111 | + td_ = PlanTiling{}; | ||
| 112 | + // 空进空出: 输出 0 元素时 tiling 全 0, kernel 按 usedCoreNum=0 直接退出。 | ||
| 113 | + auto outShape0 = context_->GetOutputShape(0); | ||
| 114 | + if (outShape0 == nullptr || outShape0->GetStorageShape().GetShapeSize() != 0) { | ||
| 115 | + // 先取返回值再判: 模板实参里的逗号会被预处理器当成 OP_CHECK_IF 的参数分隔符。 | ||
| 116 | + ge::graphStatus planRet = BuildLambBrcPlan<13, 4>(context_, coreNum_, ubSize_, dtSize, td_); | ||
| 117 | + OP_CHECK_IF(planRet != ge::GRAPH_SUCCESS, OP_LOGE(context_->GetNodeName(), "build broadcast plan failed"), | ||
| 118 | + return ge::GRAPH_FAILED); | ||
| 119 | + } | ||
| 120 | + | ||
| 121 | + auto ret = memcpy_s(rawTilingData->GetData(), rawTilingData->GetCapacity(), &td_, sizeof(td_)); | ||
| 122 | + OP_CHECK_IF(ret != EOK, OP_LOGE(context_->GetNodeName(), "copy tiling data failed, ret %d", ret), | ||
| 123 | + return ge::GRAPH_FAILED); | ||
| 124 | + rawTilingData->SetDataSize(sizeof(td_)); | ||
| 125 | + context_->SetBlockDim(std::max<uint32_t>(td_.usedCoreNum, 1)); | ||
| 126 | + size_t* ws = context_->GetWorkspaceSizes(1); | ||
| 127 | + OP_CHECK_NULL_WITH_CONTEXT(context_, ws); | ||
| 128 | + ws[0] = 0U; | ||
| 145 | return ge::GRAPH_SUCCESS; | 129 | return ge::GRAPH_SUCCESS; |
| 146 | } | 130 | } |
| 147 | 131 | ||
| @@ -153,7 +137,17 @@ ge::graphStatus LambNextMVWithDecayTiling::GetWorkspaceSize() { return ge::GRAPH | |||
| 153 | 137 | ||
| 154 | ge::graphStatus LambNextMVWithDecayTiling::PostTiling() { return ge::GRAPH_SUCCESS; } | 138 | ge::graphStatus LambNextMVWithDecayTiling::PostTiling() { return ge::GRAPH_SUCCESS; } |
| 155 | 139 | ||
| 156 | -ge::graphStatus LambNextMVWithDecayTiling::GetPlatformInfo() { return ge::GRAPH_SUCCESS; } | 140 | +ge::graphStatus LambNextMVWithDecayTiling::GetPlatformInfo() |
| 141 | +{ | ||
| 142 | + auto compileInfo = static_cast<const LambNextMVWithDecayCompileInfo*>(context_->GetCompileInfo()); | ||
| 143 | + OP_CHECK_NULL_WITH_CONTEXT(context_, compileInfo); | ||
| 144 | + coreNum_ = compileInfo->coreNum; | ||
| 145 | + ubSize_ = compileInfo->ubSize; | ||
| 146 | + OP_CHECK_IF(coreNum_ == 0 || ubSize_ == 0, | ||
| 147 | + OP_LOGE(context_->GetNodeName(), "invalid platform info: coreNum %lu ubSize %lu", coreNum_, ubSize_), | ||
| 148 | + return ge::GRAPH_FAILED); | ||
| 149 | + return ge::GRAPH_SUCCESS; | ||
| 150 | +} | ||
| 157 | 151 | ||
| 158 | static ge::graphStatus TilingForLambNextMVWithDecay(gert::TilingContext* context) | 152 | static ge::graphStatus TilingForLambNextMVWithDecay(gert::TilingContext* context) |
| 159 | { | 153 | { |
| @@ -13,6 +13,7 @@ | |||
| 13 | * \brief | 13 | * \brief |
| 14 | */ | 14 | */ |
| 15 | 15 | ||
| 16 | + | ||
| 16 | 17 | ||
| 17 | 18 | ||
| 18 | 19 | ||
| @@ -20,28 +21,49 @@ | |||
| 20 | using namespace Ops::Base; | 21 | using namespace Ops::Base; |
| 21 | using namespace ge; | 22 | using namespace ge; |
| 22 | namespace ops { | 23 | namespace ops { |
| 23 | -// full tensors: input_mul3(0,g^2), input_mul0(4,m). All four outputs share their broadcast shape. | 24 | +// A2 语义: 本族算子的所有输入都是可广播的 ND Tensor(见 canndev |
| 24 | -constexpr size_t IN_MUL3 = 0; | 25 | +// ops/built-in/tbe/impl/lamb_*.py, 每一步 mul/sub/div 都先 shape_util.broadcast_shapes |
| 25 | -constexpr size_t IN_MUL0 = 4; | 26 | +// 再 tbe.broadcast), 输出形状为全部输入广播的结果。A2 的 op_proto 只声明了其中两个输入, |
| 27 | +// 属声明宽松, 不作为支持面依据。 | ||
| 28 | +constexpr size_t IN_NUM = 13; | ||
| 26 | constexpr size_t OUT_NUM = 4; | 29 | constexpr size_t OUT_NUM = 4; |
| 27 | 30 | ||
| 28 | static ge::graphStatus InferShape4LambNextMVWithDecay(gert::InferShapeContext* context) | 31 | static ge::graphStatus InferShape4LambNextMVWithDecay(gert::InferShapeContext* context) |
| 29 | { | 32 | { |
| 30 | - auto g2 = context->GetInputShape(IN_MUL3); | 33 | + std::vector<const gert::Shape*> inShapes; |
| 31 | - OP_CHECK_NULL_WITH_CONTEXT(context, g2); | 34 | + inShapes.reserve(IN_NUM); |
| 32 | - auto m = context->GetInputShape(IN_MUL0); | 35 | + for (size_t i = 0; i < IN_NUM; i++) { |
| 33 | - OP_CHECK_NULL_WITH_CONTEXT(context, m); | 36 | + auto in = context->GetInputShape(i); |
| 37 | + OP_CHECK_NULL_WITH_CONTEXT(context, in); | ||
| 38 | + inShapes.push_back(in); | ||
| 39 | + } | ||
| 40 | + gert::Shape bcShape; | ||
| 41 | + // 逐对折叠广播: 只用两参数重载。vector 重载虽在 op_common/op_host/infershape_broadcast_util.h | ||
| 42 | + // 以 Ops::Base 声明, 但 libops_base.so 只导出两参数版, vector 版的实现在 libop_common.so 的 | ||
| 43 | + // 小写 ops 命名空间下 —— 声明与实现命名空间不一致, 用它会编译期通过、加载期 | ||
| 44 | + // undefined symbol 而装不上包。 | ||
| 45 | + bcShape = *inShapes[0]; | ||
| 46 | + for (size_t i = 1; i < inShapes.size(); i++) { | ||
| 47 | + gert::Shape tmp; | ||
| 48 | + OP_CHECK_IF(!BroadcastShape(&bcShape, inShapes[i], &tmp), | ||
| 49 | + OP_LOGE(context->GetNodeName(), "input shapes cannot broadcast together"), return ge::GRAPH_FAILED); | ||
| 50 | + bcShape = tmp; | ||
| 51 | + } | ||
| 52 | + // 标量归一: 全标量输入 broadcast 得 0 维空 shape (), 与 A2 的 shape_util.scalar2tensor_one | ||
| 53 | + // 对齐, 归一为 (1,)。否则动态 shape 编译期 DFX 生成会对空 shape 做 reduce 连乘(无初值)而报 | ||
| 54 | + // TypeError 编译失败。 | ||
| 55 | + if (bcShape.GetDimNum() == 0) { | ||
| 56 | + bcShape.SetDimNum(1); | ||
| 57 | + bcShape.SetDim(0, 1); | ||
| 58 | + } | ||
| 34 | for (size_t i = 0; i < OUT_NUM; i++) { | 59 | for (size_t i = 0; i < OUT_NUM; i++) { |
| 35 | auto out = context->GetOutputShape(i); | 60 | auto out = context->GetOutputShape(i); |
| 36 | OP_CHECK_NULL_WITH_CONTEXT(context, out); | 61 | OP_CHECK_NULL_WITH_CONTEXT(context, out); |
| 37 | - OP_CHECK_IF(!BroadcastShape(g2, m, out), | 62 | + *out = bcShape; |
| 38 | - OP_LOGE_FOR_INVALID_SHAPES_WITH_REASON(context->GetNodeName(), "input_mul3 and input_mul0", | ||
| 39 | - (ToString(*g2) + " and " + ToString(*m)).c_str(), | ||
| 40 | - "shapes cannot broadcast"), | ||
| 41 | - return ge::GRAPH_FAILED); | ||
| 42 | } | 63 | } |
| 43 | return GRAPH_SUCCESS; | 64 | return GRAPH_SUCCESS; |
| 44 | } | 65 | } |
| 66 | + | ||
| 45 | static ge::graphStatus InferDataType4LambNextMVWithDecay(gert::InferDataTypeContext* context) | 67 | static ge::graphStatus InferDataType4LambNextMVWithDecay(gert::InferDataTypeContext* context) |
| 46 | { | 68 | { |
| 47 | if (context == nullptr) { | 69 | if (context == nullptr) { |
| @@ -1,105 +0,0 @@ | |||
| 1 | -/** | ||
| 2 | - * Copyright (c) 2026 Huawei Technologies Co., Ltd. | ||
| 3 | - * This program is free software, you can redistribute it and/or modify it under the terms and conditions of | ||
| 4 | - * CANN Open Software License Agreement Version 2.0 (the "License"). | ||
| 5 | - * Please refer to the License for details. You may not use this file except in compliance with the License. | ||
| 6 | - * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, | ||
| 7 | - * INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE. | ||
| 8 | - * See LICENSE in the root of the software repository for the full text of the License. | ||
| 9 | - */ | ||
| 10 | - | ||
| 11 | -/*! | ||
| 12 | - * \file lamb_next_m_v_with_decay_dag.h | ||
| 13 | - * \brief lamb_next_m_v_with_decay_dag head file | ||
| 14 | - */ | ||
| 15 | - | ||
| 16 | - | ||
| 17 | - | ||
| 18 | - | ||
| 19 | - | ||
| 20 | - | ||
| 21 | - | ||
| 22 | -// The atvoss Placeholder ships In0..In11 (12 inputs). LambNextMVWithDecay has 13 inputs; extend with In12 | ||
| 23 | -// following the framework's exact pattern (additive: the struct + its IsInHolder trait, same namespace). | ||
| 24 | -namespace Ops { | ||
| 25 | -namespace Base { | ||
| 26 | -namespace Placeholder { | ||
| 27 | -template <class T, class Attr = InAttr<>> | ||
| 28 | -struct In12 : public Holder<T, Attr, 12> {}; | ||
| 29 | -template <class U, class Attr> | ||
| 30 | -struct IsInHolder<In12<U, Attr>> { | ||
| 31 | - constexpr static bool Value = true; | ||
| 32 | -}; | ||
| 33 | -} // namespace Placeholder | ||
| 34 | -} // namespace Base | ||
| 35 | -} // namespace Ops | ||
| 36 | - | ||
| 37 | -namespace LambNextMVWithDecayOp { | ||
| 38 | -using namespace AscendC; | ||
| 39 | -using namespace Ops::Base; | ||
| 40 | -// In : 0 input_mul3(g^2) 1 input_mul2(v) 2 input_realdiv1(1-b2^t) 3 input_mul1(g) 4 input_mul0(m) | ||
| 41 | -// 5 input_realdiv0(1-b1^t) 6 input_mul4(param) 7 mul0_x(b1) 8 mul1_sub(1-b1) 9 mul2_x(b2) | ||
| 42 | -// 10 mul3_sub1(1-b2) 11 mul4_x(wd) 12 add2_y(eps) [0,1,3,4,6 full; rest scalar] | ||
| 43 | -// Out: 0 y1(update) 1 y2(next_m) 2 y3(next_v) 3 y4(m_unbiased/(sqrt(v_unbiased)+eps)) | ||
| 44 | -// Compute in U (=float): half casts to float (div/sqrt need float precision). rsqrt -> Div(_,Sqrt). | ||
| 45 | -template <typename T, typename U> | ||
| 46 | -struct LambNextMVWithDecayCompute { | ||
| 47 | - // full tensors | ||
| 48 | - using InG2 = Bind<Vec::CopyInBrc<T>, Placeholder::In0<T>>; | ||
| 49 | - using InV = Bind<Vec::CopyInBrc<T>, Placeholder::In1<T>>; | ||
| 50 | - using InG = Bind<Vec::CopyInBrc<T>, Placeholder::In3<T>>; | ||
| 51 | - using InM = Bind<Vec::CopyInBrc<T>, Placeholder::In4<T>>; | ||
| 52 | - using InParam = Bind<Vec::CopyInBrc<T>, Placeholder::In6<T>>; | ||
| 53 | - // scalar coefficients | ||
| 54 | - using InRd1 = Bind<Vec::Duplicate<T>, Placeholder::In2<T, Placeholder::ScalarAttr<true>>>; | ||
| 55 | - using InRd0 = Bind<Vec::Duplicate<T>, Placeholder::In5<T, Placeholder::ScalarAttr<true>>>; | ||
| 56 | - using InB1 = Bind<Vec::Duplicate<T>, Placeholder::In7<T, Placeholder::ScalarAttr<true>>>; | ||
| 57 | - using InOmB1 = Bind<Vec::Duplicate<T>, Placeholder::In8<T, Placeholder::ScalarAttr<true>>>; | ||
| 58 | - using InB2 = Bind<Vec::Duplicate<T>, Placeholder::In9<T, Placeholder::ScalarAttr<true>>>; | ||
| 59 | - using InOmB2 = Bind<Vec::Duplicate<T>, Placeholder::In10<T, Placeholder::ScalarAttr<true>>>; | ||
| 60 | - using InWd = Bind<Vec::Duplicate<T>, Placeholder::In11<T, Placeholder::ScalarAttr<true>>>; | ||
| 61 | - using InEps = Bind<Vec::Duplicate<T>, Placeholder::In12<T, Placeholder::ScalarAttr<true>>>; | ||
| 62 | - | ||
| 63 | - using G2 = Bind<Vec::Cast<U, T, 0>, InG2>; | ||
| 64 | - using V = Bind<Vec::Cast<U, T, 0>, InV>; | ||
| 65 | - using G = Bind<Vec::Cast<U, T, 0>, InG>; | ||
| 66 | - using M = Bind<Vec::Cast<U, T, 0>, InM>; | ||
| 67 | - using Param = Bind<Vec::Cast<U, T, 0>, InParam>; | ||
| 68 | - using Rd1 = Bind<Vec::Cast<U, T, 0>, InRd1>; | ||
| 69 | - using Rd0 = Bind<Vec::Cast<U, T, 0>, InRd0>; | ||
| 70 | - using B1 = Bind<Vec::Cast<U, T, 0>, InB1>; | ||
| 71 | - using OmB1 = Bind<Vec::Cast<U, T, 0>, InOmB1>; | ||
| 72 | - using B2 = Bind<Vec::Cast<U, T, 0>, InB2>; | ||
| 73 | - using OmB2 = Bind<Vec::Cast<U, T, 0>, InOmB2>; | ||
| 74 | - using Wd = Bind<Vec::Cast<U, T, 0>, InWd>; | ||
| 75 | - using Eps = Bind<Vec::Cast<U, T, 0>, InEps>; | ||
| 76 | - | ||
| 77 | - // next_v = v*b2 + g^2*(1-b2) | ||
| 78 | - using NextV = Bind<Vec::Add<U>, Bind<Vec::Mul<U>, V, B2>, Bind<Vec::Mul<U>, G2, OmB2>>; | ||
| 79 | - using VUnbias = Bind<Vec::Div<U>, NextV, Rd1>; | ||
| 80 | - using SqrtVeps = Bind<Vec::Sqrt<U>, Bind<Vec::Add<U>, VUnbias, Eps>>; // sqrt(v_unbiased+eps) | ||
| 81 | - using SqrtVAddEps = Bind<Vec::Add<U>, Bind<Vec::Sqrt<U>, VUnbias>, Eps>; // sqrt(v_unbiased)+eps | ||
| 82 | - // next_m = m*b1 + g*(1-b1) | ||
| 83 | - using NextM = Bind<Vec::Add<U>, Bind<Vec::Mul<U>, M, B1>, Bind<Vec::Mul<U>, G, OmB1>>; | ||
| 84 | - using MUnbias = Bind<Vec::Div<U>, NextM, Rd0>; | ||
| 85 | - // y1 = param*wd + m_unbiased/sqrt(v_unbiased+eps) | ||
| 86 | - using Y1 = Bind<Vec::Add<U>, Bind<Vec::Mul<U>, Param, Wd>, Bind<Vec::Div<U>, MUnbias, SqrtVeps>>; | ||
| 87 | - // with_decay: y4 = param*wd + m_unbiased/(sqrt(v_unbiased)+eps) (NextMV has no param*wd term in y4) | ||
| 88 | - using Y4 = Bind<Vec::Add<U>, Bind<Vec::Mul<U>, Param, Wd>, Bind<Vec::Div<U>, MUnbias, SqrtVAddEps>>; | ||
| 89 | - | ||
| 90 | - using Y1Cast = Bind<Vec::Cast<T, U, 1>, Y1>; | ||
| 91 | - using Y2Cast = Bind<Vec::Cast<T, U, 1>, NextM>; | ||
| 92 | - using Y3Cast = Bind<Vec::Cast<T, U, 1>, NextV>; | ||
| 93 | - using Y4Cast = Bind<Vec::Cast<T, U, 1>, Y4>; | ||
| 94 | - | ||
| 95 | - using OpOut0 = Bind<Vec::CopyOut<T>, Placeholder::Out0<T>, Y1Cast>; | ||
| 96 | - using OpOut1 = Bind<Vec::CopyOut<T>, Placeholder::Out1<T>, Y2Cast>; | ||
| 97 | - using OpOut2 = Bind<Vec::CopyOut<T>, Placeholder::Out2<T>, Y3Cast>; | ||
| 98 | - using OpOut3 = Bind<Vec::CopyOut<T>, Placeholder::Out3<T>, Y4Cast>; | ||
| 99 | - | ||
| 100 | - using Outputs = Elems<OpOut0, OpOut1, OpOut2, OpOut3>; | ||
| 101 | - using MemCfg = MemOptCfg<MemLevel::LEVEL_1>; | ||
| 102 | - using OpDag = DAGSch<Outputs, void, MemCfg>; | ||
| 103 | -}; | ||
| 104 | -} // namespace LambNextMVWithDecayOp | ||
| 105 | - | ||
| @@ -1,27 +0,0 @@ | |||
| 1 | -/** | ||
| 2 | - * Copyright (c) 2026 Huawei Technologies Co., Ltd. | ||
| 3 | - * This program is free software, you can redistribute it and/or modify it under the terms and conditions of | ||
| 4 | - * CANN Open Software License Agreement Version 2.0 (the "License"). | ||
| 5 | - * Please refer to the License for details. You may not use this file except in compliance with the License. | ||
| 6 | - * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, | ||
| 7 | - * INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY, OR FITNESS FOR A PARTICULAR PURPOSE. | ||
| 8 | - * See LICENSE in the root of the software repository for the full text of the License. | ||
| 9 | - */ | ||
| 10 | - | ||
| 11 | -/*! | ||
| 12 | - * \file lamb_next_m_v_with_decay_tiling_key.h | ||
| 13 | - * \brief lamb_next_m_v_with_decay_tiling_key head file | ||
| 14 | - */ | ||
| 15 | - | ||
| 16 | - | ||
| 17 | - | ||
| 18 | - | ||
| 19 | - | ||
| 20 | - | ||
| 21 | -using namespace Ops::Base; | ||
| 22 | -// 算子自定义的tiling key字段 | ||
| 23 | -ASCENDC_TPL_ARGS_DECL(LambNextMVWithDecay, BRC_NDDMA_SCH_MODE_KEY_DECL(schMode)); | ||
| 24 | - | ||
| 25 | -ASCENDC_TPL_SEL(ASCENDC_TPL_ARGS_SEL(BRC_NDDMA_SCH_MODE_KEY_SEL(schMode))); | ||
| 26 | - | ||
| 27 | - | ||