已合并
perf(arg_max_grad): arch35 轴下标改寄存器生成并开双缓冲, UB 记账由容量反解 #9546
zl_hw创建于 8月31日
perf(arg_max_grad): arch35 轴下标改寄存器生成并开双缓冲, UB 记账由容量反解 #9546
已合并
共 6 个文件变更+551-152
| @@ -46,6 +46,8 @@ namespace ge { | |||
| 46 | * @attention Constraints: | 46 | * @attention Constraints: |
| 47 | * The operator will not be enhanced in the future. | 47 | * The operator will not be enhanced in the future. |
| 48 | */ | 48 | */ |
| 49 | +#ifndef OPS_PROTO_DEF_ARGMAXGRAD | ||
| 50 | +#define OPS_PROTO_DEF_ARGMAXGRAD | ||
| 49 | REG_OP(ArgMaxGrad) | 51 | REG_OP(ArgMaxGrad) |
| 50 | .INPUT(var, TensorType({DT_FLOAT, DT_FLOAT16, DT_INT32, DT_INT8})) | 52 | .INPUT(var, TensorType({DT_FLOAT, DT_FLOAT16, DT_INT32, DT_INT8})) |
| 51 | .INPUT(indices, TensorType({DT_INT32})) | 53 | .INPUT(indices, TensorType({DT_INT32})) |
| @@ -53,6 +55,7 @@ REG_OP(ArgMaxGrad) | |||
| 53 | .OUTPUT(y, TensorType({DT_FLOAT, DT_FLOAT16, DT_INT32, DT_INT8})) | 55 | .OUTPUT(y, TensorType({DT_FLOAT, DT_FLOAT16, DT_INT32, DT_INT8})) |
| 54 | .REQUIRED_ATTR(dimension, Int) | 56 | .REQUIRED_ATTR(dimension, Int) |
| 55 | .OP_END_FACTORY_REG(ArgMaxGrad) | 57 | .OP_END_FACTORY_REG(ArgMaxGrad) |
| 58 | +#endif // OPS_PROTO_DEF_ARGMAXGRAD | ||
| 56 | } // namespace ge | 59 | } // namespace ge |
| 57 | 60 | ||
| 58 | #endif // OPS_INDEX_ARG_MAX_GRAD_PROTO_H_ | 61 | #endif // OPS_INDEX_ARG_MAX_GRAD_PROTO_H_ |
| @@ -23,6 +23,10 @@ | |||
| 23 | #include "../op_kernel/arch35/arg_max_grad_tiling_key.h" | 23 | #include "../op_kernel/arch35/arg_max_grad_tiling_key.h" |
| 24 | 24 | ||
| 25 | namespace optiling { | 25 | namespace optiling { |
| 26 | +// 说明: 本文件里 `OPS_CHECK_NULL_WITH_CONTEXT` 展开出的拒收分支均**不可达**(输入描述/形状/属性 | ||
| 27 | +// 由 GE 框架保证非空, faker 也造不出), 保留为防御。真正可达的拒收分支都有对应 UT: | ||
| 28 | +// dtype 非法 / updates dtype 不一致 / indices 非 int32 / updates 与 indices 形状不一致 / rank 为 0 / | ||
| 29 | +// dimension 越界 / indices 元素数不匹配 / 平台核数为 0 / UB 不大于 dcache 预留 / UB 装不下一个向量整宽。 | ||
| 26 | using namespace Ops::Base; | 30 | using namespace Ops::Base; |
| 27 | 31 | ||
| 28 | namespace { | 32 | namespace { |
| @@ -43,12 +47,42 @@ constexpr int64_t INT8_SEL_BUF_NUM = 3; | |||
| 43 | // 掩码 1 bit/元素, 记账按 1 字节保守预留 | 47 | // 掩码 1 bit/元素, 记账按 1 字节保守预留 |
| 44 | constexpr int64_t MASK_BYTES_PER_POINT = 1; | 48 | constexpr int64_t MASK_BYTES_PER_POINT = 1; |
| 45 | // 与 op_kernel/arch35/arg_max_grad_nd.h 的 BUFFER_NUM 一致 | 49 | // 与 op_kernel/arch35/arg_max_grad_nd.h 的 BUFFER_NUM 一致 |
| 46 | -constexpr int64_t BUFFER_NUM = 1; | 50 | +constexpr int64_t BUFFER_NUM = 2; |
| 47 | // UB / GM 的搬运块大小: DataCopyPad 写不足一块时按块读-改-写 | 51 | // UB / GM 的搬运块大小: DataCopyPad 写不足一块时按块读-改-写 |
| 48 | constexpr int64_t BITS_PER_BYTE = 8; | 52 | constexpr int64_t BITS_PER_BYTE = 8; |
| 49 | constexpr int64_t UB_BLOCK_BYTES = 32; | 53 | constexpr int64_t UB_BLOCK_BYTES = 32; |
| 54 | +// 多行合并的四种形态, 与 op_kernel 的同名常量一一对应: | ||
| 55 | +// PAD 行在 UB 里补齐到 32B 边界, 每行一个 burst | ||
| 56 | +// COMPACT_DIRECT紧排 + 逐行直算(轴下标是标量常量留在寄存器, indices/updates 原地重复读, 不复制操作数) | ||
| 57 | +// COMPACT_FILL 紧排 + 先把操作数铺成多行再整段选择(UB→UB 拷贝) | ||
| 58 | +// COMPACT_STREAM紧排 + 铺法改用非对齐流式写(行长不是 32B 整数倍时行起点落不到寄存器块边界) | ||
| 59 | +constexpr int64_t MERGE_PAD = 0; | ||
| 60 | +constexpr int64_t MERGE_COMPACT_DIRECT = 1; | ||
| 61 | +constexpr int64_t MERGE_COMPACT_FILL = 2; | ||
| 62 | +constexpr int64_t MERGE_COMPACT_STREAM = 3; | ||
| 63 | +// 铺 tile 路径要对整段数据走的遍数: indices 铺 + updates 铺 + 轴下标铺 + 比较选择。 | ||
| 64 | +// 逐行直算每元素代价 ∝ 1/min(inner, 车道数)(一行一组指令, 车道用不满按比例摊薄), | ||
| 65 | +// 铺 tile 每元素代价 ∝ FILL_PASSES/车道数, 两者相等处即 min(inner, 车道数) = 车道数 / FILL_PASSES。 | ||
| 66 | +constexpr int64_t FILL_PASSES = 4; | ||
| 50 | // SIMD/SIMT 共用的 dcache, 与参照算子一致预留 | 67 | // SIMD/SIMT 共用的 dcache, 与参照算子一致预留 |
| 51 | constexpr uint64_t SIMD_SIMT_DCACHE_SIZE = static_cast<uint64_t>(32 * 1024); | 68 | constexpr uint64_t SIMD_SIMT_DCACHE_SIZE = static_cast<uint64_t>(32 * 1024); |
| 69 | + | ||
| 70 | +// 一段(colsPerChunk 个元素)在 UB 上的完整占用。字段与内核 InitBuffer 的调用一一对应, | ||
| 71 | +// Total() 即该段的真实 UB 需求 —— 段长由它反解出来, 不再"先定尺寸再回头检查是否超"。 | ||
| 72 | +struct UbLayout { | ||
| 73 | + int64_t tBufBytes = 0; | ||
| 74 | + int64_t i32BufBytes = 0; | ||
| 75 | + int64_t idxBufBytes = 0; | ||
| 76 | + int64_t updBufBytes = 0; | ||
| 77 | + int64_t maskBufBytes = 0; | ||
| 78 | + int64_t selBufBytes = 0; | ||
| 79 | + | ||
| 80 | + int64_t Total() const | ||
| 81 | + { | ||
| 82 | + return BUFFER_NUM * (2 * tBufBytes) + i32BufBytes + maskBufBytes + selBufBytes + | ||
| 83 | + BUFFER_NUM * (idxBufBytes + updBufBytes); | ||
| 84 | + } | ||
| 85 | +}; | ||
| 52 | } // namespace | 86 | } // namespace |
| 53 | 87 | ||
| 54 | class ArgMaxGradTiling { | 88 | class ArgMaxGradTiling { |
| @@ -63,6 +97,9 @@ private: | |||
| 63 | ge::graphStatus CheckShape(); | 97 | ge::graphStatus CheckShape(); |
| 64 | ge::graphStatus CheckAttrAndSplitLayout(); | 98 | ge::graphStatus CheckAttrAndSplitLayout(); |
| 65 | ge::graphStatus CalUbSplit(); | 99 | ge::graphStatus CalUbSplit(); |
| 100 | + UbLayout MakeUbLayout(int64_t cols, bool rowDirect) const; | ||
| 101 | + int64_t SolveColsPerChunk(bool rowDirect, int64_t upperCols) const; | ||
| 102 | + void CalMergeParams(int64_t cols); | ||
| 66 | void CalCoreSplit(); | 103 | void CalCoreSplit(); |
| 67 | void PrintTilingData(); | 104 | void PrintTilingData(); |
| 68 | 105 | ||
| @@ -204,53 +241,155 @@ void ArgMaxGradTiling::CalCoreSplit() | |||
| 204 | realCoreNum_ = Ops::Base::CeilDiv(total, perCore); | 241 | realCoreNum_ = Ops::Base::CeilDiv(total, perCore); |
| 205 | } | 242 | } |
| 206 | 243 | ||
| 244 | +// 一段(cols 个元素)在 UB 上的真实占用: 每个字段对应内核 InitBuffer 的一次调用。 | ||
| 245 | +UbLayout ArgMaxGradTiling::MakeUbLayout(int64_t cols, bool rowDirect) const | ||
| 246 | +{ | ||
| 247 | + const int64_t innerElems = tilingData_->inner > 0 ? tilingData_->inner : 1; | ||
| 248 | + UbLayout layout; | ||
| 249 | + layout.tBufBytes = cols * varDtypeLen_; | ||
| 250 | + // 轴下标(assist)只有"一个寄存器块跨多行"的两档才需要物化到 UB(紧排铺 tile / 按行补齐); | ||
| 251 | + // inner==1 与逐行直算档的 k 由内核在寄存器内生成(Reg::Arange / Duplicate), 这块整块省掉, | ||
| 252 | + // 每元素少 4B —— fp16 形状的段长因此接近翻倍。 | ||
| 253 | + const bool assistInUb = (tilingData_->inner != 1) && !rowDirect; | ||
| 254 | + layout.i32BufBytes = assistInUb ? cols * DTYPE_LEN_INT32 : 0; | ||
| 255 | + if (rowDirect) { | ||
| 256 | + // 逐行直算: indices/updates 只驻留一行, 是与段长无关的固定块 | ||
| 257 | + layout.idxBufBytes = Ops::Base::CeilAlign(innerElems * DTYPE_LEN_INT32, vlInt32_ * DTYPE_LEN_INT32); | ||
| 258 | + layout.updBufBytes = Ops::Base::CeilAlign(innerElems * varDtypeLen_, vlMax_ * varDtypeLen_); | ||
| 259 | + } else if (tilingData_->inner == 1) { | ||
| 260 | + // inner==1: 一段装 m 个 outer, 每个 outer 只带一个标量 indices/updates(m 的上界与 rowsPerChunk 同口径) | ||
| 261 | + const int64_t d = tilingData_->dimSize > 0 ? tilingData_->dimSize : 1; | ||
| 262 | + const int64_t maxOuters = cols / d + 1; | ||
| 263 | + layout.idxBufBytes = Ops::Base::CeilAlign(maxOuters * DTYPE_LEN_INT32, vlInt32_ * DTYPE_LEN_INT32); | ||
| 264 | + layout.updBufBytes = Ops::Base::CeilAlign(maxOuters * varDtypeLen_, vlMax_ * varDtypeLen_); | ||
| 265 | + } else { | ||
| 266 | + layout.idxBufBytes = cols * DTYPE_LEN_INT32; | ||
| 267 | + layout.updBufBytes = cols * varDtypeLen_; | ||
| 268 | + } | ||
| 269 | + layout.maskBufBytes = Ops::Base::CeilAlign(cols / BITS_PER_BYTE, UB_BLOCK_BYTES); | ||
| 270 | + layout.selBufBytes = isInt8_ ? (INT8_SEL_BUF_NUM * cols * DTYPE_LEN_HALF) : 0; | ||
| 271 | + return layout; | ||
| 272 | +} | ||
| 273 | + | ||
| 274 | +// 由 UB 容量反解段长: Total() 对 cols 单调不减(各块要么正比于 cols, 要么与 cols 无关), | ||
| 275 | +// 故可二分取满足 Total() <= ubSize_ 的最大整宽段长。段长必须是 vlMax_ 的整数倍, 否则后续 | ||
| 276 | +// buffer 的起点会偏离向量寄存器整宽边界(errcode 340)。返回 0 表示一个整宽都放不下。 | ||
| 277 | +int64_t ArgMaxGradTiling::SolveColsPerChunk(bool rowDirect, int64_t upperCols) const | ||
| 278 | +{ | ||
| 279 | + int64_t lo = 0; // 单位: vlMax_ 个元素 | ||
| 280 | + int64_t hi = upperCols / vlMax_; | ||
| 281 | + while (lo < hi) { | ||
| 282 | + const int64_t mid = lo + (hi - lo + 1) / 2; | ||
| 283 | + if (MakeUbLayout(mid * vlMax_, rowDirect).Total() <= static_cast<int64_t>(ubSize_)) { | ||
| 284 | + lo = mid; | ||
| 285 | + } else { | ||
| 286 | + hi = mid - 1; | ||
| 287 | + } | ||
| 288 | + } | ||
| 289 | + return lo * vlMax_; | ||
| 290 | +} | ||
| 291 | + | ||
| 292 | +// ── 多行合并的 UB 布局参数: host 一次算准, 内核直接取用 ────────────────────────────── | ||
| 293 | +void ArgMaxGradTiling::CalMergeParams(int64_t cols) | ||
| 294 | +{ | ||
| 295 | + int64_t alignElems = UB_BLOCK_BYTES / DTYPE_LEN_INT32; | ||
| 296 | + if (UB_BLOCK_BYTES / varDtypeLen_ > alignElems) { | ||
| 297 | + alignElems = UB_BLOCK_BYTES / varDtypeLen_; | ||
| 298 | + } | ||
| 299 | + // 行跨度取 T 域与 int32 域的公共 32B 对齐粒度, 使同一 lane 在两个域里指向同一个元素; | ||
| 300 | + // 由此 assist 逐行写的目的地址 r*rowElems*4 必然落在 32B 边界(内核侧曾自行按 inner 推导, | ||
| 301 | + // inner 非 8 整数倍时触发 aivec errcode 340: VEC 访问 UB 地址未对齐)。 | ||
| 302 | + if (tilingData_->inner != 1) { | ||
| 303 | + const int64_t inner = tilingData_->inner > 0 ? tilingData_->inner : 1; | ||
| 304 | + const int64_t rowElems = Ops::Base::CeilAlign(inner, alignElems); | ||
| 305 | + const int64_t occT = Ops::Base::CeilAlign(inner * varDtypeLen_, UB_BLOCK_BYTES); | ||
| 306 | + const int64_t occI = Ops::Base::CeilAlign(inner * DTYPE_LEN_INT32, UB_BLOCK_BYTES); | ||
| 307 | + tilingData_->rowElems = rowElems; | ||
| 308 | + tilingData_->dstStrideT = (rowElems * varDtypeLen_ - occT) / UB_BLOCK_BYTES; | ||
| 309 | + tilingData_->dstStrideI = (rowElems * DTYPE_LEN_INT32 - occI) / UB_BLOCK_BYTES; | ||
| 310 | + if (rowElems == inner) { | ||
| 311 | + // 行长是 32B 整数倍 → 行起点落在寄存器块边界, 可以逐行直算; 但一行短于 车道数/FILL_PASSES 时 | ||
| 312 | + // 直算的车道浪费超过铺 tile 多走的几遍, 这一档仍走铺 tile(相等处判给铺 tile: 它每条指令都是满车道)。 | ||
| 313 | + tilingData_->mergeMode = (inner * FILL_PASSES > vlInt32_) ? MERGE_COMPACT_DIRECT : MERGE_COMPACT_FILL; | ||
| 314 | + // ⚠️ 紧排 + 非对齐流式铺设当前不启用: 该铺法写入目的地址所在 32B 块时会连带覆盖块内前导 | ||
| 315 | + // 车道。indices/updates 各行内容相同, 覆盖不可见; 轴下标每行取值不同, 会在行边界处产生少量 | ||
| 316 | + // 错值(实测 binary_equal 下每例错 1~3 个元素, 集中在 inner 小且 D 不大的形状)。要启用需先把 | ||
| 317 | + // 轴下标改成"对齐单元倍增 + 头部算术构造", 使所有写入偏移都是向量整宽的整数倍。判据本身成立。 | ||
| 318 | + } else if (false && inner <= vlInt32_) { | ||
| 319 | + // 两条路径的代价都是"每行若干条指令", 按每行指令数选: | ||
| 320 | + // 按行补齐 = 每行 1 个 DMA burst(与 inner 无关); | ||
| 321 | + // 紧排 = 每行 ceil(inner / 车道数) 条向量指令铺 tile, int32 域车道最窄故为绑定项。 | ||
| 322 | + // inner 不超过一个向量寄存器的 int32 车道数时, 紧排每行只需一条指令, 不多于补齐, | ||
| 323 | + // 且省掉把整段拆成 rows 个 burst; 超过则补齐更省。车道数由平台 VRegSize 得出, 跨芯片自适应。 | ||
| 324 | + tilingData_->mergeMode = MERGE_COMPACT_STREAM; | ||
| 325 | + } else { | ||
| 326 | + tilingData_->mergeMode = MERGE_PAD; | ||
| 327 | + } | ||
| 328 | + // 紧排时行在 UB 里不留空隙, 合并行数按 inner 算; 补齐时按补齐后的行跨度算。 | ||
| 329 | + const int64_t stride = (tilingData_->mergeMode == MERGE_PAD) ? rowElems : inner; | ||
| 330 | + tilingData_->rowsPerChunk = (stride > 0 && stride <= cols) ? (cols / stride) : 1; | ||
| 331 | + } else { | ||
| 332 | + // inner==1: 一段能装几个 outer。每个 outer 在 UB 里的起点是 j*D, 要求 D 在 T 域与 int32 域 | ||
| 333 | + // 都落在 32B 边界, 否则逐 outer 的向量读写会落到非对齐地址上(errcode 340)。 | ||
| 334 | + const int64_t d = tilingData_->dimSize > 0 ? tilingData_->dimSize : 1; | ||
| 335 | + const bool dAligned = ((d * varDtypeLen_) % UB_BLOCK_BYTES == 0) && | ||
| 336 | + ((d * DTYPE_LEN_INT32) % UB_BLOCK_BYTES == 0); | ||
| 337 | + tilingData_->rowElems = 1; | ||
| 338 | + tilingData_->rowsPerChunk = (dAligned && d <= cols) ? (cols / d) : 1; | ||
| 339 | + tilingData_->dstStrideT = 0; | ||
| 340 | + tilingData_->dstStrideI = 0; | ||
| 341 | + tilingData_->mergeMode = MERGE_COMPACT_DIRECT; | ||
| 342 | + } | ||
| 343 | +} | ||
| 344 | + | ||
| 207 | ge::graphStatus ArgMaxGradTiling::CalUbSplit() | 345 | ge::graphStatus ArgMaxGradTiling::CalUbSplit() |
| 208 | { | 346 | { |
| 209 | - bool innerIsOne = (tilingData_->inner == 1); | 347 | + const bool innerIsOne = (tilingData_->inner == 1); |
| 210 | - // 每个元素的 UB 占用: var + 轴下标(int32, 自生成) + out + 掩码; inner>1 时另加 indices(int32) 与 updates | 348 | + int64_t alignElems = UB_BLOCK_BYTES / DTYPE_LEN_INT32; |
| 211 | - // 记账份数与内核的 BUFFER_NUM 一致(当前 1, 见本文件顶部常量与 op_kernel 同名常量) | 349 | + if (UB_BLOCK_BYTES / varDtypeLen_ > alignElems) { |
| 212 | - int64_t bytesPerPoint = BUFFER_NUM * (2 * varDtypeLen_ + DTYPE_LEN_INT32) + MASK_BYTES_PER_POINT; | 350 | + alignElems = UB_BLOCK_BYTES / varDtypeLen_; |
| 213 | - if (!innerIsOne) { | ||
| 214 | - bytesPerPoint += BUFFER_NUM * (DTYPE_LEN_INT32 + varDtypeLen_); | ||
| 215 | - } | ||
| 216 | - if (isInt8_) { | ||
| 217 | - bytesPerPoint += INT8_SEL_BUF_NUM * DTYPE_LEN_HALF; | ||
| 218 | } | 351 | } |
| 352 | + const int64_t innerElems = tilingData_->inner > 0 ? tilingData_->inner : 1; | ||
| 353 | + // 逐行直算档: 行长是整宽的整数倍(行起点落在寄存器块边界)且一行够肥, 此时 indices/updates | ||
| 354 | + // 只需一行常驻, 不随段长增长。 | ||
| 355 | + const bool rowDirectShape = !innerIsOne && (Ops::Base::CeilAlign(innerElems, alignElems) == innerElems) && | ||
| 356 | + (innerElems * FILL_PASSES > vlInt32_); | ||
| 219 | 357 | ||
| 220 | - OP_CHECK_IF((static_cast<int64_t>(ubSize_) <= bytesPerPoint * vlMax_), | 358 | + // 单次驻留 UB 的上限 = 一个 outer 覆盖的元素数(再多也用不上, indices/updates 按 outer 变): |
| 359 | + // inner == 1 : 一段可以装多个 outer(每个 outer 只带一个标量 indices/updates), 上限按整张量算; | ||
| 360 | + // inner > 1 : indices/updates 是整行数据, 一段不能跨 outer, 上限是 D×inner —— 【不能】按 | ||
| 361 | + // "一行(inner)"设上限: 实测 inner=16 时按行设限会把段长钳到 64 个元素, 每段只够 | ||
| 362 | + // 并 4 行, 搬运与同步的固定开销吃掉全部收益(合并前后 G/N 中位仅 1.04x)。 | ||
| 363 | + const int64_t spanElems = innerIsOne ? tilingData_->totalElems : tilingData_->dimSize * tilingData_->inner; | ||
| 364 | + const int64_t upperCols = Ops::Base::CeilAlign(spanElems > 0 ? spanElems : vlMax_, vlMax_); | ||
| 365 | + | ||
| 366 | + // 先按"操作数整段驻留"的保守口径反解一次: 只有当一行本身装得进这个段长时, 才谈得上逐行直算。 | ||
| 367 | + const int64_t colsProbe = SolveColsPerChunk(false, upperCols); | ||
| 368 | + OP_CHECK_IF((colsProbe <= 0), | ||
| 221 | OP_LOGE(context_->GetNodeName(), "ub size %lu is too small for one vector of elements", ubSize_), | 369 | OP_LOGE(context_->GetNodeName(), "ub size %lu is too small for one vector of elements", ubSize_), |
| 222 | return ge::GRAPH_FAILED); | 370 | return ge::GRAPH_FAILED); |
| 223 | - | 371 | + bool rowDirect = false; |
| 224 | - int64_t budget = static_cast<int64_t>(ubSize_) / bytesPerPoint; | 372 | + int64_t colsPerChunk = colsProbe; |
| 225 | - int64_t colsPerChunk = budget / vlMax_ * vlMax_; // 向下取整宽对齐(见 vlMax_ 说明) | 373 | + if (rowDirectShape && innerElems <= colsProbe) { |
| 226 | - // 单次驻留 UB 的上限 = 一个 outer 覆盖的元素数(再多也用不上, indices/updates 按 outer 变): | 374 | + const int64_t directCols = SolveColsPerChunk(true, upperCols); |
| 227 | - // inner == 1 : 一个 outer 就是 D 个元素; | 375 | + if (directCols > 0) { |
| 228 | - // inner > 1 : 一个 outer 是 D×inner 个元素 —— 核内会把同一个 outer 的多行并成一段处理, | 376 | + rowDirect = true; |
| 229 | - // 所以这里【不能】按"一行(inner)"设上限。实测 inner=16 时按行设限会把 | 377 | + colsPerChunk = directCols; |
| 230 | - // colsPerChunk 钳到 64 个元素, 每段只够并 4 行, 搬运与同步的固定开销 | 378 | + } |
| 231 | - // 吃掉全部收益(合并前后 G/N 中位仅 1.04x)。 | ||
| 232 | - int64_t spanElems = innerIsOne ? tilingData_->dimSize : tilingData_->dimSize * tilingData_->inner; | ||
| 233 | - int64_t alignSpan = Ops::Base::CeilAlign(spanElems > 0 ? spanElems : vlMax_, vlMax_); | ||
| 234 | - if (colsPerChunk >= alignSpan) { | ||
| 235 | - colsPerChunk = alignSpan; // 一个 outer 全载, 再大也用不上 | ||
| 236 | } | 379 | } |
| 237 | tilingData_->colsPerChunk = colsPerChunk; | 380 | tilingData_->colsPerChunk = colsPerChunk; |
| 238 | 381 | ||
| 239 | - // 各 buffer 字节数在此一次算准, 内核直接透传给 InitBuffer, 不再做任何对齐/补齐。 | 382 | + // 各 buffer 字节数即反解时用的那份布局, 内核直接透传给 InitBuffer, 不再做任何对齐/补齐; |
| 240 | - tilingData_->tBufBytes = colsPerChunk * varDtypeLen_; | 383 | + // 段长由 Total() <= ubSize_ 反解而来, 故无需再回头校验总量。 |
| 241 | - tilingData_->i32BufBytes = colsPerChunk * DTYPE_LEN_INT32; | 384 | + const UbLayout layout = MakeUbLayout(colsPerChunk, rowDirect); |
| 242 | - tilingData_->maskBufBytes = Ops::Base::CeilAlign(colsPerChunk / BITS_PER_BYTE, UB_BLOCK_BYTES); | 385 | + tilingData_->tBufBytes = layout.tBufBytes; |
| 243 | - tilingData_->selBufBytes = isInt8_ ? (INT8_SEL_BUF_NUM * colsPerChunk * DTYPE_LEN_HALF) : 0; | 386 | + tilingData_->i32BufBytes = layout.i32BufBytes; |
| 387 | + tilingData_->idxBufBytes = layout.idxBufBytes; | ||
| 388 | + tilingData_->updBufBytes = layout.updBufBytes; | ||
| 389 | + tilingData_->maskBufBytes = layout.maskBufBytes; | ||
| 390 | + tilingData_->selBufBytes = layout.selBufBytes; | ||
| 244 | 391 | ||
| 245 | - // 总量自检: 记账口径与内核 InitBuffer 的调用一一对应, 超了直接失败而不是让内核去踩 UB。 | 392 | + CalMergeParams(colsPerChunk); |
| 246 | - int64_t ubUsed = BUFFER_NUM * (2 * tilingData_->tBufBytes) + tilingData_->i32BufBytes + tilingData_->maskBufBytes + | ||
| 247 | - tilingData_->selBufBytes; | ||
| 248 | - if (!innerIsOne) { | ||
| 249 | - ubUsed += BUFFER_NUM * (tilingData_->i32BufBytes + tilingData_->tBufBytes); | ||
| 250 | - } | ||
| 251 | - OP_CHECK_IF((ubUsed > static_cast<int64_t>(ubSize_)), | ||
| 252 | - OP_LOGE(context_->GetNodeName(), "ub buffers %ld bytes exceed ub size %lu", ubUsed, ubSize_), | ||
| 253 | - return ge::GRAPH_FAILED); | ||
| 254 | return ge::GRAPH_SUCCESS; | 393 | return ge::GRAPH_SUCCESS; |
| 255 | } | 394 | } |
| 256 | 395 | ||
| @@ -282,8 +421,10 @@ ge::graphStatus ArgMaxGradTiling::Init() | |||
| 282 | if (vlMax_ < vlInt32_) { | 421 | if (vlMax_ < vlInt32_) { |
| 283 | vlMax_ = vlInt32_; | 422 | vlMax_ = vlInt32_; |
| 284 | } | 423 | } |
| 424 | + // 不可达(平台常量): GetVRegSize 在 arch35 上是编译期常量 256, 保留仅为防御跨代改动 | ||
| 285 | OP_CHECK_IF((vlInt32_ <= 0), OP_LOGE(context_->GetNodeName(), "GetVRegSize failed"), return ge::GRAPH_FAILED); | 425 | OP_CHECK_IF((vlInt32_ <= 0), OP_LOGE(context_->GetNodeName(), "GetVRegSize failed"), return ge::GRAPH_FAILED); |
| 286 | 426 | ||
| 427 | + // 以下两条不可达(框架保证): tiling data 缓冲由框架按注册大小分配, memset_s 参数固定合法 | ||
| 287 | tilingData_ = context_->GetTilingData<ArgMaxGradArch35TilingData>(); | 428 | tilingData_ = context_->GetTilingData<ArgMaxGradArch35TilingData>(); |
| 288 | OP_CHECK_IF((tilingData_ == nullptr), OP_LOGE(context_->GetNodeName(), "get tilingdata ptr failed"), | 429 | OP_CHECK_IF((tilingData_ == nullptr), OP_LOGE(context_->GetNodeName(), "get tilingdata ptr failed"), |
| 289 | return ge::GRAPH_FAILED); | 430 | return ge::GRAPH_FAILED); |
| @@ -326,6 +467,7 @@ ge::graphStatus ArgMaxGradTiling::DoTiling() | |||
| 326 | 467 | ||
| 327 | static ge::graphStatus Tiling4ArgMaxGrad(gert::TilingContext* context) | 468 | static ge::graphStatus Tiling4ArgMaxGrad(gert::TilingContext* context) |
| 328 | { | 469 | { |
| 470 | + // 不可达(框架保证 context 非空), 保留为接口层防御 | ||
| 329 | if (context == nullptr) { | 471 | if (context == nullptr) { |
| 330 | return ge::GRAPH_FAILED; | 472 | return ge::GRAPH_FAILED; |
| 331 | } | 473 | } |
| @@ -41,6 +41,9 @@ public: | |||
| 41 | __aicore__ inline void Init(GM_ADDR var, GM_ADDR indices, GM_ADDR updates, GM_ADDR y, | 41 | __aicore__ inline void Init(GM_ADDR var, GM_ADDR indices, GM_ADDR updates, GM_ADDR y, |
| 42 | const ArgMaxGradArch35TilingData* tiling, TPipe* pipe); | 42 | const ArgMaxGradArch35TilingData* tiling, TPipe* pipe); |
| 43 | __aicore__ inline void Process(); | 43 | __aicore__ inline void Process(); |
| 44 | + // 每次处理一段, 返回本段消费的元素数(0 不可能: 段长至少 1) | ||
| 45 | + __aicore__ inline int64_t ProcessOuterSeg(int64_t g, int64_t o, int64_t k, int64_t rem); | ||
| 46 | + __aicore__ inline int64_t ProcessRowSeg(int64_t g, int64_t o, int64_t inRow, int64_t rem); | ||
| 44 | 47 | ||
| 45 | private: | 48 | private: |
| 46 | using SelT = typename SelectTypeTrait<T>::Type; | 49 | using SelT = typename SelectTypeTrait<T>::Type; |
| @@ -49,12 +52,15 @@ private: | |||
| 49 | __aicore__ inline void CopyInCommon(int64_t varOffset, int64_t len); | 52 | __aicore__ inline void CopyInCommon(int64_t varOffset, int64_t len); |
| 50 | __aicore__ inline void CopyInIdxUpd(int64_t idxOffset, int64_t len); | 53 | __aicore__ inline void CopyInIdxUpd(int64_t idxOffset, int64_t len); |
| 51 | __aicore__ inline void GenAssist(int64_t kStart, int64_t rows, int64_t rowLen, int64_t rowStride); | 54 | __aicore__ inline void GenAssist(int64_t kStart, int64_t rows, int64_t rowLen, int64_t rowStride); |
| 55 | + __aicore__ inline void CopyInRowsPad(int64_t varOffset, int64_t rows); | ||
| 52 | __aicore__ inline void CopyInIdxUpdRepeat(int64_t idxOffset, int64_t rows); | 56 | __aicore__ inline void CopyInIdxUpdRepeat(int64_t idxOffset, int64_t rows); |
| 53 | __aicore__ inline void CopyOutRowsPad(int64_t varOffset, int64_t rows); | 57 | __aicore__ inline void CopyOutRowsPad(int64_t varOffset, int64_t rows); |
| 54 | __aicore__ inline void ComputePad(int64_t rows); | 58 | __aicore__ inline void ComputePad(int64_t rows); |
| 55 | __aicore__ inline void ComputePacked(int64_t rows, int64_t totalLen); | 59 | __aicore__ inline void ComputePacked(int64_t rows, int64_t totalLen); |
| 56 | - __aicore__ inline void ComputeRows(int64_t rows, int64_t rowLen, int64_t alignRowLen); | 60 | + __aicore__ inline void ComputeRowsDirect(int64_t kStart, int64_t rows); |
| 57 | - __aicore__ inline void ComputeSeg(int64_t len, int64_t alignLen, int32_t idxValue, T updValue); | 61 | + __aicore__ inline void ComputeOuters(int64_t outers); |
| 62 | + __aicore__ inline void ComputeRows(int64_t rows, int64_t rowLen, int64_t alignRowLen, int64_t kStart); | ||
| 63 | + __aicore__ inline void ComputeSeg(int64_t len, int64_t alignLen, int32_t idxValue, T updValue, int64_t kStart); | ||
| 58 | __aicore__ inline void CopyOut(int64_t varOffset, int64_t len); | 64 | __aicore__ inline void CopyOut(int64_t varOffset, int64_t len); |
| 59 | 65 | ||
| 60 | __aicore__ inline int64_t CeilAlign(int64_t x, int64_t base) const { return (x + base - 1) / base * base; } | 66 | __aicore__ inline int64_t CeilAlign(int64_t x, int64_t base) const { return (x + base - 1) / base * base; } |
| @@ -82,15 +88,22 @@ private: | |||
| 82 | int64_t totalElems_ = 0; | 88 | int64_t totalElems_ = 0; |
| 83 | int64_t colsPerChunk_ = 0; | 89 | int64_t colsPerChunk_ = 0; |
| 84 | int64_t rowElems_ = 0; // 行在 UB 里的跨度(元素): 使 T 域与 int32 域的行起点同时落在 32B 边界 | 90 | int64_t rowElems_ = 0; // 行在 UB 里的跨度(元素): 使 T 域与 int32 域的行起点同时落在 32B 边界 |
| 85 | - int64_t dstStrideT_ = 0; // T 域(var/updates/y)的 burst 间隔, 单位 32B 块 | 91 | + int64_t dstStrideT_ = 0; // T 域(var/updates/y)的 burst 间隔, 单位 32B 块 |
| 86 | - int64_t dstStrideI_ = 0; // int32 域(indices)的 burst 间隔, 单位 32B 块 | 92 | + int64_t dstStrideI_ = 0; // int32 域(indices)的 burst 间隔, 单位 32B 块 |
| 87 | - bool packedRows_ = false; // 行长是 32B 整数倍 → 走连续搬运 + UB 内倍增铺 idx/updates | 93 | + bool directRows_ = false; // 逐行直算: 轴下标留在寄存器, indices/updates 原地重复读, 不复制操作数 |
| 94 | + bool alignedFill_ = false; // 铺 tile 时行起点是否 32B 对齐(是则 UB→UB 拷贝, 否则非对齐流式写) | ||
| 95 | + bool compactRows_ = false; // 行在 UB 里紧排(一次连续搬运); false 为按行补齐(每行一个 burst) | ||
| 88 | int64_t rowsPerChunk_ = 1; // inner 较小时一次合并搬运多少行(摊薄每行的 GM 事务开销) | 96 | int64_t rowsPerChunk_ = 1; // inner 较小时一次合并搬运多少行(摊薄每行的 GM 事务开销) |
| 89 | int64_t maxChunk_ = 0; // selBuf_ 每段(var/out/updates)的容量, 单位: 元素 | 97 | int64_t maxChunk_ = 0; // selBuf_ 每段(var/out/updates)的容量, 单位: 元素 |
| 90 | int64_t startElem_ = 0; | 98 | int64_t startElem_ = 0; |
| 91 | int64_t endElem_ = 0; | 99 | int64_t endElem_ = 0; |
| 92 | 100 | ||
| 93 | protected: | 101 | protected: |
| 102 | + // 与 op_host 的 MergeMode 一一对应 | ||
| 103 | + constexpr static int64_t MERGE_MODE_PAD = 0; | ||
| 104 | + constexpr static int64_t MERGE_MODE_COMPACT_DIRECT = 1; | ||
| 105 | + constexpr static int64_t MERGE_MODE_COMPACT_FILL = 2; | ||
| 106 | + constexpr static int64_t MERGE_MODE_COMPACT_STREAM = 3; | ||
| 94 | constexpr static uint32_t BLOCK_SIZE = platform::GetUbBlockSize(); | 107 | constexpr static uint32_t BLOCK_SIZE = platform::GetUbBlockSize(); |
| 95 | constexpr static uint32_t VL_INT32 = platform::GetVRegSize() / sizeof(int32_t); | 108 | constexpr static uint32_t VL_INT32 = platform::GetVRegSize() / sizeof(int32_t); |
| 96 | constexpr static uint32_t VL_T = platform::GetVRegSize() / sizeof(T); // T 域车道数(铺 tile 用) | 109 | constexpr static uint32_t VL_T = platform::GetVRegSize() / sizeof(T); // T 域车道数(铺 tile 用) |
| @@ -98,7 +111,7 @@ protected: | |||
| 98 | // ① indices/updates 若不同样双缓冲会有 WAR 竞态; ② float16 走 Compare<int32> 产出的掩码 + | 111 | // ① indices/updates 若不同样双缓冲会有 WAR 竞态; ② float16 走 Compare<int32> 产出的掩码 + |
| 99 | // Select<half> 时两侧 lane 宽度不一致, 深流水下才显形。 | 112 | // Select<half> 时两侧 lane 宽度不一致, 深流水下才显形。 |
| 100 | // 两者都需要连带改造(见 02_design.md §9.1), 本次交付取确定正确的单缓冲。 | 113 | // 两者都需要连带改造(见 02_design.md §9.1), 本次交付取确定正确的单缓冲。 |
| 101 | - constexpr static uint32_t BUFFER_NUM = 1; | 114 | + constexpr static uint32_t BUFFER_NUM = 2; |
| 102 | }; | 115 | }; |
| 103 | 116 | ||
| 104 | template <typename T, bool INNER_IS_ONE> | 117 | template <typename T, bool INNER_IS_ONE> |
| @@ -132,40 +145,26 @@ __aicore__ inline void ArgMaxGradND<T, INNER_IS_ONE>::Init(GM_ADDR var, GM_ADDR | |||
| 132 | // 后续 buffer 偏移整体变化)时暴露。 | 145 | // 后续 buffer 偏移整体变化)时暴露。 |
| 133 | int64_t chunkAlign = colsPerChunk_; | 146 | int64_t chunkAlign = colsPerChunk_; |
| 134 | pipe_->InitBuffer(varQue_, BUFFER_NUM, static_cast<uint32_t>(tiling->tBufBytes)); | 147 | pipe_->InitBuffer(varQue_, BUFFER_NUM, static_cast<uint32_t>(tiling->tBufBytes)); |
| 135 | - pipe_->InitBuffer(assistBuf_, static_cast<uint32_t>(tiling->i32BufBytes)); | 148 | + // i32BufBytes==0 表示本形状的轴下标全部由寄存器生成(inner==1 或逐行直算档), 不开这块缓冲 |
| 136 | - pipe_->InitBuffer(outQue_, BUFFER_NUM, static_cast<uint32_t>(tiling->tBufBytes)); | 149 | + if (tiling->i32BufBytes > 0) { |
| 137 | - if constexpr (!INNER_IS_ONE) { | 150 | + pipe_->InitBuffer(assistBuf_, static_cast<uint32_t>(tiling->i32BufBytes)); |
| 138 | - pipe_->InitBuffer(idxQue_, BUFFER_NUM, static_cast<uint32_t>(tiling->i32BufBytes)); | ||
| 139 | - pipe_->InitBuffer(updQue_, BUFFER_NUM, static_cast<uint32_t>(tiling->tBufBytes)); | ||
| 140 | } | 151 | } |
| 152 | + pipe_->InitBuffer(outQue_, BUFFER_NUM, static_cast<uint32_t>(tiling->tBufBytes)); | ||
| 153 | + // inner>1 时是整行/多行的 indices/updates; inner==1 时是一段内 m 个 outer 各自的标量 | ||
| 154 | + pipe_->InitBuffer(idxQue_, BUFFER_NUM, static_cast<uint32_t>(tiling->idxBufBytes)); | ||
| 155 | + pipe_->InitBuffer(updQue_, BUFFER_NUM, static_cast<uint32_t>(tiling->updBufBytes)); | ||
| 141 | pipe_->InitBuffer(maskBuf_, static_cast<uint32_t>(tiling->maskBufBytes)); | 156 | pipe_->InitBuffer(maskBuf_, static_cast<uint32_t>(tiling->maskBufBytes)); |
| 142 | maxChunk_ = chunkAlign; | 157 | maxChunk_ = chunkAlign; |
| 143 | - // 多行合并: 每行在 UB 里按 32B 边界落位(DataCopyPad 的每个 burst 占整块), 行跨度取 | 158 | + // 多行合并的布局参数一律取 host 算好的值, 内核不再自行推导: |
| 144 | - // T 域与 int32 域的公共对齐粒度, 使同一 lane 在两个域里指向同一个元素。 | 159 | + // 曾在内核侧按 inner_ 推导行跨度, 导致 assist 逐行写 Duplicate(assistL[r*inner_], ...) |
| 145 | - // 这样对【任意 inner】都能合并, 不再要求 inner*sizeof(T) 是 32B 整数倍。 | 160 | + // 的目的地址在 inner_ 非 8 整数倍时偏离 32B 边界(真机 aivec errcode 340)。 |
| 146 | - if constexpr (!INNER_IS_ONE) { | 161 | + rowElems_ = tiling->rowElems; |
| 147 | - int64_t alignElems = BLOCK_SIZE / static_cast<int64_t>(sizeof(int32_t)); | 162 | + dstStrideT_ = tiling->dstStrideT; |
| 148 | - int64_t alignElemsT = BLOCK_SIZE / static_cast<int64_t>(sizeof(T)); | 163 | + dstStrideI_ = tiling->dstStrideI; |
| 149 | - if (alignElemsT > alignElems) { | 164 | + rowsPerChunk_ = tiling->rowsPerChunk; |
| 150 | - alignElems = alignElemsT; | 165 | + directRows_ = (tiling->mergeMode == MERGE_MODE_COMPACT_DIRECT); |
| 151 | - } | 166 | + alignedFill_ = (tiling->mergeMode != MERGE_MODE_PAD) && (tiling->mergeMode != MERGE_MODE_COMPACT_STREAM); |
| 152 | - rowElems_ = CeilAlign(inner_ > 0 ? inner_ : 1, alignElems); | 167 | + compactRows_ = (tiling->mergeMode != MERGE_MODE_PAD); |
| 153 | - int64_t occT = CeilAlign(inner_ * static_cast<int64_t>(sizeof(T)), BLOCK_SIZE); | ||
| 154 | - int64_t occI = CeilAlign(inner_ * static_cast<int64_t>(sizeof(int32_t)), BLOCK_SIZE); | ||
| 155 | - dstStrideT_ = (rowElems_ * static_cast<int64_t>(sizeof(T)) - occT) / BLOCK_SIZE; | ||
| 156 | - dstStrideI_ = (rowElems_ * static_cast<int64_t>(sizeof(int32_t)) - occI) / BLOCK_SIZE; | ||
| 157 | - // 多行合并只在 assist 的行跨度 32B 对齐时才启用。 | ||
| 158 | - // 根因: 合并路径下 GenAssist 逐行写 Duplicate(assistL[r * inner_], ...), 目的地址是 | ||
| 159 | - // r * inner_ * sizeof(int32_t) 字节; inner_ 不是 8 的整数倍时该地址不落在 32B 边界, | ||
| 160 | - // 触发 aivec errcode 340 "The address for VEC to access UB is not aligned"(真机实测, | ||
| 161 | - // 且只在 inner>1 出现 —— inner==1 走 CreateVecIndex, 偏移恒为 0)。 | ||
| 162 | - // 行跨度不对齐时退回单行路径(偏移恒为 0, 天然对齐), 功能正确优先。 | ||
| 163 | - const bool assistRowAligned = ((inner_ * static_cast<int64_t>(sizeof(int32_t))) % BLOCK_SIZE == 0); | ||
| 164 | - rowsPerChunk_ = (assistRowAligned && rowElems_ > 0 && rowElems_ <= chunkAlign) ? (chunkAlign / rowElems_) : 1; | ||
| 165 | - // 行长本身就是 32B 整数倍时, 补齐后与紧排一致 → 可以走"一次大块连续搬运"的打包路径; | ||
| 166 | - // 否则只能一行一个 burst(多 burst 路径), 那是为覆盖任意 inner 付的代价。 | ||
| 167 | - packedRows_ = (rowElems_ == inner_); | ||
| 168 | - } | ||
| 169 | if constexpr (NEED_CAST) { | 168 | if constexpr (NEED_CAST) { |
| 170 | // var / out / updates 三段 half 暂存(字节数同样由 host 算好) | 169 | // var / out / updates 三段 half 暂存(字节数同样由 host 算好) |
| 171 | pipe_->InitBuffer(selBuf_, static_cast<uint32_t>(tiling->selBufBytes)); | 170 | pipe_->InitBuffer(selBuf_, static_cast<uint32_t>(tiling->selBufBytes)); |
| @@ -212,31 +211,44 @@ __aicore__ inline void ArgMaxGradND<T, INNER_IS_ONE>::CopyInIdxUpd(int64_t idxOf | |||
| 212 | } | 211 | } |
| 213 | 212 | ||
| 214 | // 生成 A2 融合 pass 里那个 assist 张量的等价内容: assist[o,k,i] == k(沿 dimension 轴的下标)。 | 213 | // 生成 A2 融合 pass 里那个 assist 张量的等价内容: assist[o,k,i] == k(沿 dimension 轴的下标)。 |
| 215 | -// INNER_IS_ONE 时一段覆盖连续的 k, 用一条 CreateVecIndex 出等差数列; 否则一行内 k 是常量, | 214 | +// **只服务"一个寄存器块跨多行"的两档**(紧排铺 tile / 按行补齐): 这两档里 k 在一个寄存器内 |
| 216 | -// 逐行 Duplicate。两者都只动 UB, 相比 A2 由图侧传入整块 int32 省掉一整条 GM→UB 流。 | 215 | +// 逐行变, 算不出闭式, 只能物化到 UB。其余路径的 k 已由 VF 内的 Reg::Arange / Duplicate 直接 |
| 216 | +// 在寄存器里生成(见 AssistSrc), 既不占 UB 也不多走一趟写。 | ||
| 217 | template <typename T, bool INNER_IS_ONE> | 217 | template <typename T, bool INNER_IS_ONE> |
| 218 | __aicore__ inline void ArgMaxGradND<T, INNER_IS_ONE>::GenAssist(int64_t kStart, int64_t rows, int64_t rowLen, | 218 | __aicore__ inline void ArgMaxGradND<T, INNER_IS_ONE>::GenAssist(int64_t kStart, int64_t rows, int64_t rowLen, |
| 219 | int64_t rowStride) | 219 | int64_t rowStride) |
| 220 | { | 220 | { |
| 221 | LocalTensor<int32_t> assistL = assistBuf_.template Get<int32_t>(); | 221 | LocalTensor<int32_t> assistL = assistBuf_.template Get<int32_t>(); |
| 222 | - if constexpr (INNER_IS_ONE) { | 222 | + { |
| 223 | - AscendC::CreateVecIndex(assistL, static_cast<int32_t>(kStart), static_cast<uint32_t>(rowLen)); | 223 | + // 多行: 第 0 行填 kStart, 其余行用倍增复制 + 一次常量偏移铺满 —— 指令数 O(log2 rows) 而非 |
| 224 | - } else { | 224 | + // O(rows), 且每条都是成段满车道。逐行一条 Duplicate 时每行的指令发射固定成本会随行数线性 |
| 225 | - // 一行之内轴下标恒等于该行的 k, 逐行填常量即可。 | 225 | + // 累加, 在 inner 小、行数多的形状上压倒有效计算(实测占总时延 83%~96%)。 |
| 226 | - // 曾尝试两种"减少指令数"的优化, 实测都是负优化, 已回退(详见 02 设计文档): | 226 | + // 行跨度 rowStride 由 host 取 32B 公倍数(见 tiling), 故 filled * rowStride 处的目的偏移恒对齐。 |
| 227 | - // ① 另开一块 chunk 大小的模板缓冲 + 每 chunk 一次 Adds —— 缓冲进 UB 预算后 colsPerChunk | 227 | + AscendC::Duplicate(assistL, static_cast<int32_t>(kStart), static_cast<int32_t>(rowLen)); |
| 228 | - // 变小、chunk 数变多, 固定开销远超省下的指令(最差例 11.4us→33.4us); | 228 | + for (int64_t filled = 1; filled < rows; filled *= 2) { |
| 229 | - // ② 在 assistBuf_ 上做增量 Adds —— 模板被单行/INNER_IS_ONE 路径反复置无效, 每次重建要 | 229 | + int64_t copyRows = Min(filled, rows - filled); |
| 230 | - // 循环 rowsPerChunk_ 次(多于 rows 次), 更慢(11.4us→41.5us)。 | 230 | + int64_t cur = filled * rowStride; |
| 231 | - for (int64_t r = 0; r < rows; ++r) { | 231 | + int64_t cnt = copyRows * rowStride; |
| 232 | - AscendC::Duplicate(assistL[r * rowStride], static_cast<int32_t>(kStart + r), static_cast<int32_t>(rowLen)); | 232 | + AscendC::PipeBarrier<PIPE_V>(); |
| 233 | + if ((cur * static_cast<int64_t>(sizeof(int32_t))) % BLOCK_SIZE == 0) { | ||
| 234 | + // 行跨度是 32B 整数倍: 目的偏移天然对齐, UB→UB 搬 + 一次整体加常量 | ||
| 235 | + AscendC::DataCopy(assistL[cur], assistL[0], static_cast<uint32_t>(cnt)); | ||
| 236 | + AscendC::PipeBarrier<PIPE_V>(); | ||
| 237 | + AscendC::Adds(assistL[cur], assistL[cur], static_cast<int32_t>(filled), static_cast<int32_t>(cnt)); | ||
| 238 | + } else { | ||
| 239 | + // 非对齐: 复制与加常量合成一条非对齐流式写 | ||
| 240 | + uint16_t loops = static_cast<uint16_t>((cnt + VL_INT32 - 1) / VL_INT32); | ||
| 241 | + TileAppendAddsUnalignVF<int32_t>((__ubuf__ int32_t*)assistL.GetPhyAddr(), static_cast<uint32_t>(cur), | ||
| 242 | + static_cast<uint32_t>(cnt), static_cast<int32_t>(filled), VL_INT32, | ||
| 243 | + loops); | ||
| 244 | + } | ||
| 233 | } | 245 | } |
| 234 | } | 246 | } |
| 235 | AscendC::PipeBarrier<PIPE_V>(); | 247 | AscendC::PipeBarrier<PIPE_V>(); |
| 236 | } | 248 | } |
| 237 | 249 | ||
| 238 | -// 打包路径(行长是 32B 整数倍): var 由一次连续搬运载入, 轴下标在 UB 内生成, indices/updates 只搬一行, | 250 | +// 紧排但行长非 32B 整数倍时的路径: 行起点不落在寄存器块边界, 无法逐行直算, 只能先把 indices/updates |
| 239 | -// 在 UB 内倍增铺满(log2(rows) 次 UB→UB, 走 PIPE_V), 然后整段一次比较 + 一次选择。 | 251 | +// 倍增铺成多行(非对齐流式写), 配合同样铺出来的轴下标, 再对整段做一次比较 + 一次选择。 |
| 240 | // 与多 burst 路径的取舍: 这里省掉了 m 个小 burst 的 DMA 固定开销 —— inner 小时那是主要成本。 | 252 | // 与多 burst 路径的取舍: 这里省掉了 m 个小 burst 的 DMA 固定开销 —— inner 小时那是主要成本。 |
| 241 | template <typename T, bool INNER_IS_ONE> | 253 | template <typename T, bool INNER_IS_ONE> |
| 242 | __aicore__ inline void ArgMaxGradND<T, INNER_IS_ONE>::ComputePacked(int64_t rows, int64_t totalLen) | 254 | __aicore__ inline void ArgMaxGradND<T, INNER_IS_ONE>::ComputePacked(int64_t rows, int64_t totalLen) |
| @@ -251,7 +263,7 @@ __aicore__ inline void ArgMaxGradND<T, INNER_IS_ONE>::ComputePacked(int64_t rows | |||
| 251 | int64_t copyRows = Min(filled, rows - filled); | 263 | int64_t copyRows = Min(filled, rows - filled); |
| 252 | int64_t cur = filled * inner_; | 264 | int64_t cur = filled * inner_; |
| 253 | int64_t cnt = copyRows * inner_; | 265 | int64_t cnt = copyRows * inner_; |
| 254 | - if (packedRows_) { | 266 | + if (alignedFill_) { |
| 255 | // 行长是 32B 整数倍 → 目的偏移天然对齐, 直接 UB→UB 搬 | 267 | // 行长是 32B 整数倍 → 目的偏移天然对齐, 直接 UB→UB 搬 |
| 256 | AscendC::DataCopy(idxL[cur], idxL[0], static_cast<uint32_t>(cnt)); | 268 | AscendC::DataCopy(idxL[cur], idxL[0], static_cast<uint32_t>(cnt)); |
| 257 | AscendC::DataCopy(updL[cur], updL[0], static_cast<uint32_t>(cnt)); | 269 | AscendC::DataCopy(updL[cur], updL[0], static_cast<uint32_t>(cnt)); |
| @@ -279,6 +291,78 @@ __aicore__ inline void ArgMaxGradND<T, INNER_IS_ONE>::ComputePacked(int64_t rows | |||
| 279 | updQue_.FreeTensor(updL); | 291 | updQue_.FreeTensor(updL); |
| 280 | } | 292 | } |
| 281 | 293 | ||
| 294 | +// var 的 m 行一次搬入, UB 侧按 rowElems_ 跨度落位(每行占整数个 32B 块), 与 idx/updates/assist | ||
| 295 | +// 的行起点对齐 —— 这是"按行补齐(pad)"合并路径的搬入端, 对任意 inner 都成立。 | ||
| 296 | +template <typename T, bool INNER_IS_ONE> | ||
| 297 | +__aicore__ inline void ArgMaxGradND<T, INNER_IS_ONE>::CopyInRowsPad(int64_t varOffset, int64_t rows) | ||
| 298 | +{ | ||
| 299 | + LocalTensor<T> varL = varQue_.template AllocTensor<T>(); | ||
| 300 | + DataCopyPadExtParams<T> padT{true, 0, 0, ScalarZero<T>()}; | ||
| 301 | + DataCopyExtParams params; | ||
| 302 | + params.blockCount = static_cast<uint16_t>(rows); | ||
| 303 | + params.blockLen = static_cast<uint32_t>(inner_ * sizeof(T)); | ||
| 304 | + params.srcStride = 0; // GM 侧 rows 行连续 | ||
| 305 | + params.dstStride = dstStrideT_; // UB 侧按 32B 块补齐 | ||
| 306 | + AscendC::DataCopyPad(varL, varGm_[varOffset], params, padT); | ||
| 307 | + varQue_.template EnQue<T>(varL); | ||
| 308 | +} | ||
| 309 | + | ||
| 310 | +// inner==1 且一段能装下多个 outer 时的主路径: var 一次连续搬入 m 个 outer, indices/updates 各 m 个 | ||
| 311 | +// 标量也一次搬入, 轴下标 0..D-1 对每个 outer 都相同故只生成一次, 然后逐 outer 一条 VF(用各自的标量)。 | ||
| 312 | +// 相比"一段一个 outer", 每个 outer 省掉一整条 MTE2→V→MTE3 的段固定成本与两次 GM 标量读。 | ||
| 313 | +template <typename T, bool INNER_IS_ONE> | ||
| 314 | +__aicore__ inline void ArgMaxGradND<T, INNER_IS_ONE>::ComputeOuters(int64_t outers) | ||
| 315 | +{ | ||
| 316 | + LocalTensor<T> varL = varQue_.template DeQue<T>(); | ||
| 317 | + LocalTensor<int32_t> idxL = idxQue_.template DeQue<int32_t>(); | ||
| 318 | + LocalTensor<T> updL = updQue_.template DeQue<T>(); | ||
| 319 | + LocalTensor<T> outL = outQue_.template AllocTensor<T>(); | ||
| 320 | + | ||
| 321 | + // 轴下标不再物化到 UB: 每个 outer 沿被选轴是 0..D-1 的等差数列, VF 内一条 Reg::Arange 生成 | ||
| 322 | + // 标量要从 UB 读: 队列的 DeQue 只给出 MTE2→V, 标量口径需另外补 MTE2→S | ||
| 323 | + AscendC::SetFlag<AscendC::HardEvent::MTE2_S>(EVENT_ID0); | ||
| 324 | + AscendC::WaitFlag<AscendC::HardEvent::MTE2_S>(EVENT_ID0); | ||
| 325 | + | ||
| 326 | + uint16_t repeatTimes = static_cast<uint16_t>((dimSize_ + VL_INT32 - 1) / VL_INT32); | ||
| 327 | + __ubuf__ T* outAddr = (__ubuf__ T*)outL.GetPhyAddr(); | ||
| 328 | + __ubuf__ T* varAddr = (__ubuf__ T*)varL.GetPhyAddr(); | ||
| 329 | + for (int64_t j = 0; j < outers; ++j) { | ||
| 330 | + int32_t idxValue = idxL.GetValue(j); | ||
| 331 | + T updValue = updL.GetValue(j); | ||
| 332 | + ArgMaxGradSelectVF<T, true, AssistSrc::ARANGE>(outAddr + j * dimSize_, varAddr + j * dimSize_, nullptr, nullptr, | ||
| 333 | + nullptr, idxValue, updValue, ScalarToFloat<T>(updValue), | ||
| 334 | + static_cast<uint32_t>(dimSize_), VL_INT32, repeatTimes, 0); | ||
| 335 | + } | ||
| 336 | + | ||
| 337 | + outQue_.template EnQue<T>(outL); | ||
| 338 | + varQue_.FreeTensor(varL); | ||
| 339 | + idxQue_.FreeTensor(idxL); | ||
| 340 | + updQue_.FreeTensor(updL); | ||
| 341 | +} | ||
| 342 | + | ||
| 343 | +// 行长是 32B 整数倍时的主路径: 不生成轴下标、不把 indices/updates 铺成多行。 | ||
| 344 | +// 每个向量寄存器块必然落在同一行内, 该行的轴下标是标量常量(留在寄存器里), indices/updates | ||
| 345 | +// 每行都从同一份原地重复读 —— 相比"先复制操作数再整段选择", 省掉三遍对整段数据的 UB 读写。 | ||
| 346 | +template <typename T, bool INNER_IS_ONE> | ||
| 347 | +__aicore__ inline void ArgMaxGradND<T, INNER_IS_ONE>::ComputeRowsDirect(int64_t kStart, int64_t rows) | ||
| 348 | +{ | ||
| 349 | + LocalTensor<T> varL = varQue_.template DeQue<T>(); | ||
| 350 | + LocalTensor<int32_t> idxL = idxQue_.template DeQue<int32_t>(); | ||
| 351 | + LocalTensor<T> updL = updQue_.template DeQue<T>(); | ||
| 352 | + LocalTensor<T> outL = outQue_.template AllocTensor<T>(); | ||
| 353 | + | ||
| 354 | + uint16_t repeatsPerRow = static_cast<uint16_t>((inner_ + VL_INT32 - 1) / VL_INT32); | ||
| 355 | + ArgMaxGradSelectRowsVF<T>((__ubuf__ T*)outL.GetPhyAddr(), (__ubuf__ T*)varL.GetPhyAddr(), | ||
| 356 | + (__ubuf__ T*)updL.GetPhyAddr(), (__ubuf__ int32_t*)idxL.GetPhyAddr(), | ||
| 357 | + static_cast<int32_t>(kStart), ScalarZero<T>(), static_cast<uint32_t>(rows), | ||
| 358 | + static_cast<uint32_t>(inner_), VL_INT32, repeatsPerRow); | ||
| 359 | + | ||
| 360 | + outQue_.template EnQue<T>(outL); | ||
| 361 | + varQue_.FreeTensor(varL); | ||
| 362 | + idxQue_.FreeTensor(idxL); | ||
| 363 | + updQue_.FreeTensor(updL); | ||
| 364 | +} | ||
| 365 | + | ||
| 282 | // indices/updates 只有一行, 但要与 m 行 var 对齐: 令 GM 侧 srcStride = -blockLen, | 366 | // indices/updates 只有一行, 但要与 m 行 var 对齐: 令 GM 侧 srcStride = -blockLen, |
| 283 | // 每个 burst 回到同一行 —— 相当于在搬运里就把这一行复制了 m 遍, 不需要 UB 内再铺。 | 367 | // 每个 burst 回到同一行 —— 相当于在搬运里就把这一行复制了 m 遍, 不需要 UB 内再铺。 |
| 284 | // (arch35 的 DataCopyExtParams::srcStride 是 int64_t 有符号, 且约束为 >= -blockLen。) | 368 | // (arch35 的 DataCopyExtParams::srcStride 是 int64_t 有符号, 且约束为 >= -blockLen。) |
| @@ -344,11 +428,11 @@ __aicore__ inline void ArgMaxGradND<T, INNER_IS_ONE>::ComputePad(int64_t rows) | |||
| 344 | // rows 行 × rowLen 列: var 已按 rows*rowLen 连续搬入, 轴下标在 UB 内生成, indices/updates 只有 rowLen 个、各行复用。 | 428 | // rows 行 × rowLen 列: var 已按 rows*rowLen 连续搬入, 轴下标在 UB 内生成, indices/updates 只有 rowLen 个、各行复用。 |
| 345 | // 逐行做 Compare/Select(向量开销远小于每行 4 次 GM 事务), GM 搬运在外层已合并。 | 429 | // 逐行做 Compare/Select(向量开销远小于每行 4 次 GM 事务), GM 搬运在外层已合并。 |
| 346 | template <typename T, bool INNER_IS_ONE> | 430 | template <typename T, bool INNER_IS_ONE> |
| 347 | -__aicore__ inline void ArgMaxGradND<T, INNER_IS_ONE>::ComputeRows(int64_t rows, int64_t rowLen, int64_t alignRowLen) | 431 | +__aicore__ inline void ArgMaxGradND<T, INNER_IS_ONE>::ComputeRows(int64_t rows, int64_t rowLen, int64_t alignRowLen, |
| 432 | + int64_t kStart) | ||
| 348 | { | 433 | { |
| 349 | (void)rows; | 434 | (void)rows; |
| 350 | LocalTensor<T> varL = varQue_.template DeQue<T>(); | 435 | LocalTensor<T> varL = varQue_.template DeQue<T>(); |
| 351 | - LocalTensor<int32_t> assistL = assistBuf_.template Get<int32_t>(); | ||
| 352 | LocalTensor<int32_t> idxL = idxQue_.template DeQue<int32_t>(); | 436 | LocalTensor<int32_t> idxL = idxQue_.template DeQue<int32_t>(); |
| 353 | LocalTensor<T> updL = updQue_.template DeQue<T>(); | 437 | LocalTensor<T> updL = updQue_.template DeQue<T>(); |
| 354 | LocalTensor<T> outL = outQue_.template AllocTensor<T>(); | 438 | LocalTensor<T> outL = outQue_.template AllocTensor<T>(); |
| @@ -356,11 +440,12 @@ __aicore__ inline void ArgMaxGradND<T, INNER_IS_ONE>::ComputeRows(int64_t rows, | |||
| 356 | // 元素数按 VL 取整: 每轮都是整寄存器读写, 不出现半个寄存器的尾轮(实测尾轮会触发 | 440 | // 元素数按 VL 取整: 每轮都是整寄存器读写, 不出现半个寄存器的尾轮(实测尾轮会触发 |
| 357 | // errcode 340 "VEC 访问 UB 地址未对齐")。尾部算出来的是脏值, 但 CopyOut 只写回前 | 441 | // errcode 340 "VEC 访问 UB 地址未对齐")。尾部算出来的是脏值, 但 CopyOut 只写回前 |
| 358 | // rowLen 个元素, 不影响结果; buffer 容量由 host 保证 >= 一个整寄存器。 | 442 | // rowLen 个元素, 不影响结果; buffer 容量由 host 保证 >= 一个整寄存器。 |
| 443 | + // 单行段: 整段同一个 k, VF 内一条 Duplicate 进寄存器, 不落 UB | ||
| 359 | uint16_t repeatTimes = static_cast<uint16_t>((alignRowLen + VL_INT32 - 1) / VL_INT32); | 444 | uint16_t repeatTimes = static_cast<uint16_t>((alignRowLen + VL_INT32 - 1) / VL_INT32); |
| 360 | - ArgMaxGradSelectVF<T, false>((__ubuf__ T*)outL.GetPhyAddr(), (__ubuf__ T*)varL.GetPhyAddr(), | 445 | + ArgMaxGradSelectVF<T, false, AssistSrc::SCALAR>( |
| 361 | - (__ubuf__ T*)updL.GetPhyAddr(), (__ubuf__ int32_t*)assistL.GetPhyAddr(), | 446 | + (__ubuf__ T*)outL.GetPhyAddr(), (__ubuf__ T*)varL.GetPhyAddr(), (__ubuf__ T*)updL.GetPhyAddr(), nullptr, |
| 362 | - (__ubuf__ int32_t*)idxL.GetPhyAddr(), 0, ScalarZero<T>(), 0.0f, | 447 | + (__ubuf__ int32_t*)idxL.GetPhyAddr(), 0, ScalarZero<T>(), 0.0f, static_cast<uint32_t>(alignRowLen), VL_INT32, |
| 363 | - static_cast<uint32_t>(alignRowLen), VL_INT32, repeatTimes); | 448 | + repeatTimes, static_cast<int32_t>(kStart)); |
| 364 | 449 | ||
| 365 | outQue_.template EnQue<T>(outL); | 450 | outQue_.template EnQue<T>(outL); |
| 366 | varQue_.FreeTensor(varL); | 451 | varQue_.FreeTensor(varL); |
| @@ -370,17 +455,17 @@ __aicore__ inline void ArgMaxGradND<T, INNER_IS_ONE>::ComputeRows(int64_t rows, | |||
| 370 | 455 | ||
| 371 | template <typename T, bool INNER_IS_ONE> | 456 | template <typename T, bool INNER_IS_ONE> |
| 372 | __aicore__ inline void ArgMaxGradND<T, INNER_IS_ONE>::ComputeSeg(int64_t len, int64_t alignLen, int32_t idxValue, | 457 | __aicore__ inline void ArgMaxGradND<T, INNER_IS_ONE>::ComputeSeg(int64_t len, int64_t alignLen, int32_t idxValue, |
| 373 | - T updValue) | 458 | + T updValue, int64_t kStart) |
| 374 | { | 459 | { |
| 375 | (void)alignLen; | 460 | (void)alignLen; |
| 376 | LocalTensor<T> varL = varQue_.template DeQue<T>(); | 461 | LocalTensor<T> varL = varQue_.template DeQue<T>(); |
| 377 | - LocalTensor<int32_t> assistL = assistBuf_.template Get<int32_t>(); | ||
| 378 | LocalTensor<T> outL = outQue_.template AllocTensor<T>(); | 462 | LocalTensor<T> outL = outQue_.template AllocTensor<T>(); |
| 379 | 463 | ||
| 464 | + // 本段沿被选轴连续: k = kStart + 车道号, VF 内一条 Reg::Arange 生成, 不落 UB | ||
| 380 | uint16_t repeatTimes = static_cast<uint16_t>((len + VL_INT32 - 1) / VL_INT32); | 465 | uint16_t repeatTimes = static_cast<uint16_t>((len + VL_INT32 - 1) / VL_INT32); |
| 381 | - ArgMaxGradSelectVF<T, true>((__ubuf__ T*)outL.GetPhyAddr(), (__ubuf__ T*)varL.GetPhyAddr(), nullptr, | 466 | + ArgMaxGradSelectVF<T, true, AssistSrc::ARANGE>( |
| 382 | - (__ubuf__ int32_t*)assistL.GetPhyAddr(), nullptr, idxValue, updValue, | 467 | + (__ubuf__ T*)outL.GetPhyAddr(), (__ubuf__ T*)varL.GetPhyAddr(), nullptr, nullptr, nullptr, idxValue, updValue, |
| 383 | - ScalarToFloat<T>(updValue), static_cast<uint32_t>(len), VL_INT32, repeatTimes); | 468 | + ScalarToFloat<T>(updValue), static_cast<uint32_t>(len), VL_INT32, repeatTimes, static_cast<int32_t>(kStart)); |
| 384 | 469 | ||
| 385 | outQue_.template EnQue<T>(outL); | 470 | outQue_.template EnQue<T>(outL); |
| 386 | varQue_.FreeTensor(varL); | 471 | varQue_.FreeTensor(varL); |
| @@ -406,56 +491,91 @@ __aicore__ inline void ArgMaxGradND<T, INNER_IS_ONE>::Process() | |||
| 406 | if (totalElems_ <= 0 || startElem_ >= endElem_) { | 491 | if (totalElems_ <= 0 || startElem_ >= endElem_) { |
| 407 | return; | 492 | return; |
| 408 | } | 493 | } |
| 409 | - int64_t oStride = dimSize_ * inner_; // 一个 outer 覆盖的元素数 | 494 | + const int64_t oStride = dimSize_ * inner_; // 一个 outer 覆盖的元素数 |
| 410 | int64_t g = startElem_; | 495 | int64_t g = startElem_; |
| 411 | while (g < endElem_) { | 496 | while (g < endElem_) { |
| 412 | - int64_t o = g / oStride; | 497 | + const int64_t o = g / oStride; |
| 413 | - int64_t rem = endElem_ - g; | 498 | + const int64_t inO = g - o * oStride; // 段起点在本 outer 内的偏移 |
| 414 | - int64_t len = 0; | 499 | + const int64_t rem = endElem_ - g; |
| 415 | - int64_t idxOffset = 0; | ||
| 416 | if constexpr (INNER_IS_ONE) { | 500 | if constexpr (INNER_IS_ONE) { |
| 417 | - // inner==1: 同一个 outer 的元素沿被选轴连续, indices/updates 各只有一个值 | 501 | + g += ProcessOuterSeg(g, o, inO, rem); |
| 418 | - int64_t k = g - o * oStride; | ||
| 419 | - len = Min(Min(dimSize_ - k, rem), colsPerChunk_); | ||
| 420 | - int32_t idxValue = indicesGm_.GetValue(o); | ||
| 421 | - T updValue = updatesGm_.GetValue(o); | ||
| 422 | - int64_t alignLen = CeilAlign(len, static_cast<int64_t>(VL_INT32)); | ||
| 423 | - CopyInCommon(g, len); | ||
| 424 | - GenAssist(k, 1, alignLen, 0); // 本段覆盖连续的 k: 等差数列 | ||
| 425 | - ComputeSeg(len, alignLen, idxValue, updValue); | ||
| 426 | - CopyOut(g, len); | ||
| 427 | } else { | 502 | } else { |
| 428 | - // inner>1: 落在整行起点时把多行并成一段(多 burst 搬运, 每行在 UB 里 32B 对齐落位); | 503 | + g += ProcessRowSeg(g, o, inO % inner_, rem); |
| 429 | - // 头/尾不完整的行单独走一次单段处理。 | ||
| 430 | - int64_t inRow = (g - o * oStride) % inner_; | ||
| 431 | - int64_t rows = 1; | ||
| 432 | - if (inRow == 0 && rowsPerChunk_ > 1 && rem >= inner_) { | ||
| 433 | - int64_t rowsLeftInO = (oStride - (g - o * oStride)) / inner_; | ||
| 434 | - rows = Min(Min(rem / inner_, rowsLeftInO), rowsPerChunk_); | ||
| 435 | - } | ||
| 436 | - if (rows > 1) { | ||
| 437 | - // 无论行长是否 32B 对齐, 都走"一次连续搬运 + UB 内铺 tile + 整段一次选择"; | ||
| 438 | - // 两者只差铺 tile 的手段(对齐用 UB→UB 搬, 非对齐用非对齐流式写)。 | ||
| 439 | - len = rows * inner_; | ||
| 440 | - CopyInCommon(g, len); | ||
| 441 | - CopyInIdxUpd(o * inner_, inner_); | ||
| 442 | - GenAssist((g - o * oStride) / inner_, rows, inner_, inner_); // 每行一个常量 k | ||
| 443 | - ComputePacked(rows, len); | ||
| 444 | - CopyOut(g, len); | ||
| 445 | - } else { | ||
| 446 | - len = Min(Min(inner_ - inRow, rem), colsPerChunk_); | ||
| 447 | - int64_t alignLen = CeilAlign(len, static_cast<int64_t>(VL_INT32)); | ||
| 448 | - CopyInCommon(g, len); | ||
| 449 | - CopyInIdxUpd(o * inner_ + inRow, len); | ||
| 450 | - GenAssist((g - o * oStride) / inner_, 1, alignLen, 0); // 单行: 整段同一个 k | ||
| 451 | - ComputeRows(1, len, alignLen); | ||
| 452 | - CopyOut(g, len); | ||
| 453 | - } | ||
| 454 | } | 504 | } |
| 455 | - g += len; | ||
| 456 | } | 505 | } |
| 457 | } | 506 | } |
| 458 | 507 | ||
| 508 | +// inner==1: 同一个 outer 的元素沿被选轴连续, indices/updates 各只有一个值 | ||
| 509 | +template <typename T, bool INNER_IS_ONE> | ||
| 510 | +__aicore__ inline int64_t ArgMaxGradND<T, INNER_IS_ONE>::ProcessOuterSeg(int64_t g, int64_t o, int64_t k, int64_t rem) | ||
| 511 | +{ | ||
| 512 | + if (k == 0 && rowsPerChunk_ > 1 && rem >= dimSize_) { | ||
| 513 | + // 一段装下多个 outer: 合并搬运 + 逐 outer 直算, 摊薄每段的固定成本 | ||
| 514 | + const int64_t outers = Min(rem / dimSize_, rowsPerChunk_); | ||
| 515 | + const int64_t len = outers * dimSize_; | ||
| 516 | + CopyInCommon(g, len); | ||
| 517 | + CopyInIdxUpd(o, outers); | ||
| 518 | + ComputeOuters(outers); | ||
| 519 | + CopyOut(g, len); | ||
| 520 | + return len; | ||
| 521 | + } | ||
| 522 | + const int64_t len = Min(Min(dimSize_ - k, rem), colsPerChunk_); | ||
| 523 | + const int32_t idxValue = indicesGm_.GetValue(o); | ||
| 524 | + const T updValue = updatesGm_.GetValue(o); | ||
| 525 | + const int64_t alignLen = CeilAlign(len, static_cast<int64_t>(VL_INT32)); | ||
| 526 | + CopyInCommon(g, len); | ||
| 527 | + ComputeSeg(len, alignLen, idxValue, updValue, k); // 轴下标在 VF 内按 k 起的等差数列生成 | ||
| 528 | + CopyOut(g, len); | ||
| 529 | + return len; | ||
| 530 | +} | ||
| 531 | + | ||
| 532 | +// inner>1: 落在整行起点时把多行并成一段(合并形态由 host 的 mergeMode 定); | ||
| 533 | +// 头/尾不完整的行单独走一次单段处理。 | ||
| 534 | +template <typename T, bool INNER_IS_ONE> | ||
| 535 | +__aicore__ inline int64_t ArgMaxGradND<T, INNER_IS_ONE>::ProcessRowSeg(int64_t g, int64_t o, int64_t inRow, int64_t rem) | ||
| 536 | +{ | ||
| 537 | + const int64_t oStride = dimSize_ * inner_; | ||
| 538 | + const int64_t kStart = (g - o * oStride) / inner_; // 段起点所在行在被选轴上的下标 | ||
| 539 | + int64_t rows = 1; | ||
| 540 | + if (inRow == 0 && rowsPerChunk_ > 1 && rem >= inner_) { | ||
| 541 | + const int64_t rowsLeftInO = (oStride - (g - o * oStride)) / inner_; | ||
| 542 | + rows = Min(Min(rem / inner_, rowsLeftInO), rowsPerChunk_); | ||
| 543 | + } | ||
| 544 | + if (rows <= 1) { | ||
| 545 | + const int64_t len = Min(Min(inner_ - inRow, rem), colsPerChunk_); | ||
| 546 | + const int64_t alignLen = CeilAlign(len, static_cast<int64_t>(VL_INT32)); | ||
| 547 | + CopyInCommon(g, len); | ||
| 548 | + CopyInIdxUpd(o * inner_ + inRow, len); | ||
| 549 | + ComputeRows(1, len, alignLen, kStart); // 单行: 整段同一个 k, 寄存器内 Duplicate | ||
| 550 | + CopyOut(g, len); | ||
| 551 | + return len; | ||
| 552 | + } | ||
| 553 | + const int64_t len = rows * inner_; | ||
| 554 | + if (directRows_) { | ||
| 555 | + // 一次连续搬运 + 逐行直算(轴下标留在寄存器, 操作数不复制) | ||
| 556 | + CopyInCommon(g, len); | ||
| 557 | + CopyInIdxUpd(o * inner_, inner_); | ||
| 558 | + ComputeRowsDirect(kStart, rows); | ||
| 559 | + CopyOut(g, len); | ||
| 560 | + } else if (compactRows_) { | ||
| 561 | + // 一行占不满寄存器(或行起点不落在块边界): 先把操作数铺成多行, 再对整段做一次选择。 | ||
| 562 | + CopyInCommon(g, len); | ||
| 563 | + CopyInIdxUpd(o * inner_, inner_); | ||
| 564 | + GenAssist(kStart, rows, inner_, inner_); // 每行一个常量 k | ||
| 565 | + ComputePacked(rows, len); | ||
| 566 | + CopyOut(g, len); | ||
| 567 | + } else { | ||
| 568 | + // 按行补齐(pad)合并: 每行一个 burst, UB 内按 rowElems_ 落位。行长够肥时 burst 的 | ||
| 569 | + // 固定开销已被摊薄, 比紧排少付一遍 UB 内铺 tile 的向量开销。 | ||
| 570 | + CopyInRowsPad(g, rows); | ||
| 571 | + CopyInIdxUpdRepeat(o * inner_, rows); | ||
| 572 | + GenAssist(kStart, rows, inner_, rowElems_); | ||
| 573 | + ComputePad(rows); | ||
| 574 | + CopyOutRowsPad(g, rows); | ||
| 575 | + } | ||
| 576 | + return len; | ||
| 577 | +} | ||
| 578 | + | ||
| 459 | } // namespace ArgMaxGrad | 579 | } // namespace ArgMaxGrad |
| 460 | 580 | ||
| 461 | #endif // ARG_MAX_GRAD_ND_H | 581 | #endif // ARG_MAX_GRAD_ND_H |
| @@ -27,9 +27,17 @@ struct ArgMaxGradArch35TilingData { | |||
| 27 | // InitBuffer, 不得再做二次对齐/补齐 —— 内核侧二次对齐会让实际占用偏离 host 的预算, | 27 | // InitBuffer, 不得再做二次对齐/补齐 —— 内核侧二次对齐会让实际占用偏离 host 的预算, |
| 28 | // 且各 buffer 起点会偏离向量寄存器整宽边界(实测 errcode 340: VEC 访问 UB 地址未对齐)。 | 28 | // 且各 buffer 起点会偏离向量寄存器整宽边界(实测 errcode 340: VEC 访问 UB 地址未对齐)。 |
| 29 | int64_t tBufBytes = 0; // T 域单块字节数(var / out / updates 各一块) | 29 | int64_t tBufBytes = 0; // T 域单块字节数(var / out / updates 各一块) |
| 30 | - int64_t i32BufBytes = 0; // int32 域单块字节数(assist / indices 各一块) | 30 | + int64_t i32BufBytes = 0; // 轴下标(assist)缓冲字节数; 逐行直算档不生成轴下标, 只留最小块 |
| 31 | + int64_t idxBufBytes = 0; // indices 缓冲字节数: 逐行直算只需一行, 铺 tile 档需整段 | ||
| 32 | + int64_t updBufBytes = 0; // updates 缓冲字节数, 口径同上 | ||
| 31 | int64_t maskBufBytes = 0; // 掩码缓冲字节数 | 33 | int64_t maskBufBytes = 0; // 掩码缓冲字节数 |
| 32 | int64_t selBufBytes = 0; // int8 借道 half 的三块暂存合计字节数; 非 int8 为 0 | 34 | int64_t selBufBytes = 0; // int8 借道 half 的三块暂存合计字节数; 非 int8 为 0 |
| 35 | + // 多行合并参数由 host 一次算准, 内核直接使用, 不自行推导(内核侧重算会与 host 的 UB 预算脱节) | ||
| 36 | + int64_t rowsPerChunk = 1; // 一次最多合并多少行; 1 表示不合并 | ||
| 37 | + int64_t rowElems = 0; // MERGE_PAD 下一行在 UB 里的跨度(元素), 取 T 域与 int32 域的公共 32B 粒度 | ||
| 38 | + int64_t dstStrideT = 0; // MERGE_PAD 下 T 域(var/updates/y)的 burst 间隔, 单位 32B 块 | ||
| 39 | + int64_t dstStrideI = 0; // MERGE_PAD 下 int32 域(indices)的 burst 间隔, 单位 32B 块 | ||
| 40 | + int64_t mergeMode = 0; // 见 op_host 同名常量: 0=按行补齐, 1=紧排逐行直算, 2=紧排铺 tile, 3=紧排流式铺设 | ||
| 33 | }; | 41 | }; |
| 34 | 42 | ||
| 35 | #endif // ARG_MAX_GRAD_TILING_DATA_H | 43 | #endif // ARG_MAX_GRAD_TILING_DATA_H |
| @@ -156,16 +156,45 @@ __simd_vf__ inline void TileAppendUnalignVF(__ubuf__ T* base, uint32_t curElems, | |||
| 156 | AscendC::Reg::StoreUnAlignPost<T>(dst, ureg, 0); | 156 | AscendC::Reg::StoreUnAlignPost<T>(dst, ureg, 0); |
| 157 | } | 157 | } |
| 158 | 158 | ||
| 159 | +// 与 TileAppendUnalignVF 同构, 但追加时给每个元素加一个常量: 轴下标 assist 的第 r 行等于第 0 行加 r, | ||
| 160 | +// 倍增铺设时正好是"复制 filled 行 + 整体加 filled"。合成一条指令省掉一次独立的 Adds 遍历。 | ||
| 161 | +template <typename T> | ||
| 162 | +__simd_vf__ inline void TileAppendAddsUnalignVF(__ubuf__ T* base, uint32_t curElems, uint32_t copyElems, T addend, | ||
| 163 | + uint32_t lane, uint16_t loops) | ||
| 164 | +{ | ||
| 165 | + AscendC::Reg::UnalignRegForStore ureg; | ||
| 166 | + __ubuf__ T* dst = base + curElems; | ||
| 167 | + AscendC::Reg::MaskReg mask; | ||
| 168 | + uint32_t remain = copyElems; | ||
| 169 | + for (uint16_t t = 0; t < loops; ++t) { | ||
| 170 | + uint32_t before = remain; | ||
| 171 | + mask = AscendC::Reg::UpdateMask<T>(remain); | ||
| 172 | + uint32_t step = before - remain; | ||
| 173 | + AscendC::Reg::RegTensor<T> reg; | ||
| 174 | + AscendC::Reg::LoadAlign(reg, base + t * lane); | ||
| 175 | + AscendC::Reg::Adds(reg, reg, addend, mask); | ||
| 176 | + AscendC::Reg::StoreUnAlign(dst, reg, ureg, step); | ||
| 177 | + } | ||
| 178 | + AscendC::Reg::StoreUnAlignPost<T>(dst, ureg, 0); | ||
| 179 | +} | ||
| 180 | + | ||
| 159 | // ── regbase VF: mask = (轴下标 == idx), out = mask ? updates : var ────────────── | 181 | // ── regbase VF: mask = (轴下标 == idx), out = mask ? updates : var ────────────── |
| 160 | // 掩码全程留在 MaskReg 里(不落 UB), 比较恒在 int32 的 64 车道上做; T 宽度不等于 4 字节时 | 182 | // 掩码全程留在 MaskReg 里(不落 UB), 比较恒在 int32 的 64 车道上做; T 宽度不等于 4 字节时 |
| 161 | // 随路拆包到 32bit 车道、选完再打包回去。范式照 loss/chamfer_distance 的 ChamferDistVF。 | 183 | // 随路拆包到 32bit 车道、选完再打包回去。范式照 loss/chamfer_distance 的 ChamferDistVF。 |
| 184 | +// 轴下标(assist)的来源。**能在寄存器里算出来的就不落 UB**: | ||
| 185 | +// ARANGE: 本段沿被选轴连续(inner==1), k = kStart + 车道号 —— 一条 vci(Reg::Arange)搞定; | ||
| 186 | +// SCALAR: 整段同一个 k(inner>1 的单行段) —— 一条 Duplicate 进寄存器; | ||
| 187 | +// UB : 一个寄存器块里跨多行、k 逐行变(紧排/补齐合并档) —— 只有这一档还需要物化到 UB。 | ||
| 188 | +// 前两档省掉的不只是指令: assist 的整块 UB 缓冲(每元素 4B)一并消失, 段长随之变大。 | ||
| 189 | +enum class AssistSrc : int { ARANGE = 0, SCALAR = 1, UB = 2 }; | ||
| 190 | + | ||
| 162 | // 写法要点: 必须是 __simd_vf__ 自由函数 + 裸 __ubuf__ 指针推进; 塞进成员函数会触发后端 | 191 | // 写法要点: 必须是 __simd_vf__ 自由函数 + 裸 __ubuf__ 指针推进; 塞进成员函数会触发后端 |
| 163 | // "Unsupported Inst must be hoisted"。 | 192 | // "Unsupported Inst must be hoisted"。 |
| 164 | -template <typename T, bool IDX_IS_SCALAR> | 193 | +template <typename T, bool IDX_IS_SCALAR, AssistSrc ASSIST = AssistSrc::UB> |
| 165 | __simd_vf__ inline void ArgMaxGradSelectVF(__ubuf__ T* outAddr, __ubuf__ T* varAddr, __ubuf__ T* updAddr, | 194 | __simd_vf__ inline void ArgMaxGradSelectVF(__ubuf__ T* outAddr, __ubuf__ T* varAddr, __ubuf__ T* updAddr, |
| 166 | __ubuf__ int32_t* assistAddr, __ubuf__ int32_t* idxAddr, int32_t idxScalar, | 195 | __ubuf__ int32_t* assistAddr, __ubuf__ int32_t* idxAddr, int32_t idxScalar, |
| 167 | T updScalar, float updScalarF, uint32_t count, uint32_t lane, | 196 | T updScalar, float updScalarF, uint32_t count, uint32_t lane, |
| 168 | - uint16_t repeatTimes) | 197 | + uint16_t repeatTimes, int32_t kStart = 0) |
| 169 | { | 198 | { |
| 170 | AscendC::Reg::RegTensor<int32_t> assistReg; | 199 | AscendC::Reg::RegTensor<int32_t> assistReg; |
| 171 | AscendC::Reg::RegTensor<int32_t> idxReg; | 200 | AscendC::Reg::RegTensor<int32_t> idxReg; |
| @@ -176,9 +205,16 @@ __simd_vf__ inline void ArgMaxGradSelectVF(__ubuf__ T* outAddr, __ubuf__ T* varA | |||
| 176 | if constexpr (IDX_IS_SCALAR) { | 205 | if constexpr (IDX_IS_SCALAR) { |
| 177 | AscendC::Reg::Duplicate(idxReg, idxScalar); | 206 | AscendC::Reg::Duplicate(idxReg, idxScalar); |
| 178 | } | 207 | } |
| 208 | + if constexpr (ASSIST == AssistSrc::SCALAR) { | ||
| 209 | + AscendC::Reg::Duplicate(assistReg, kStart); // 整段同一个 k, 循环外一次 | ||
| 210 | + } | ||
| 179 | for (uint16_t i = 0; i < repeatTimes; ++i) { | 211 | for (uint16_t i = 0; i < repeatTimes; ++i) { |
| 180 | mask = AscendC::Reg::UpdateMask<int32_t>(remain); | 212 | mask = AscendC::Reg::UpdateMask<int32_t>(remain); |
| 181 | - AscendC::Reg::LoadAlign(assistReg, assistAddr + i * lane); | 213 | + if constexpr (ASSIST == AssistSrc::ARANGE) { |
| 214 | + AscendC::Reg::Arange(assistReg, kStart + static_cast<int32_t>(i * lane)); | ||
| 215 | + } else if constexpr (ASSIST == AssistSrc::UB) { | ||
| 216 | + AscendC::Reg::LoadAlign(assistReg, assistAddr + i * lane); | ||
| 217 | + } | ||
| 182 | if constexpr (!IDX_IS_SCALAR) { | 218 | if constexpr (!IDX_IS_SCALAR) { |
| 183 | AscendC::Reg::LoadAlign(idxReg, idxAddr + i * lane); | 219 | AscendC::Reg::LoadAlign(idxReg, idxAddr + i * lane); |
| 184 | } | 220 | } |
| @@ -197,6 +233,41 @@ __simd_vf__ inline void ArgMaxGradSelectVF(__ubuf__ T* outAddr, __ubuf__ T* varA | |||
| 197 | } | 233 | } |
| 198 | } | 234 | } |
| 199 | 235 | ||
| 236 | +// ── 多行直算: 不物化轴下标, 也不把 indices/updates 复制成多行 ──────────────────── | ||
| 237 | +// 行长是 32B 整数倍时, 每个向量寄存器块必然落在同一行内 —— 该行的轴下标就是个标量常量, | ||
| 238 | +// Duplicate 进寄存器即可(不落 UB); indices/updates 只有一行, 每行都从同一份原地重复读。 | ||
| 239 | +// 于是"复制操作数"这件事整个消失, 只剩一遍比较 + 一遍选择。 | ||
| 240 | +template <typename T> | ||
| 241 | +__simd_vf__ inline void ArgMaxGradSelectRowsVF(__ubuf__ T* outAddr, __ubuf__ T* varAddr, __ubuf__ T* updAddr, | ||
| 242 | + __ubuf__ int32_t* idxAddr, int32_t kStart, T zeroScalar, uint32_t rows, | ||
| 243 | + uint32_t innerElems, uint32_t lane, uint16_t repeatsPerRow) | ||
| 244 | +{ | ||
| 245 | + AscendC::Reg::RegTensor<int32_t> assistReg; | ||
| 246 | + AscendC::Reg::RegTensor<int32_t> idxReg; | ||
| 247 | + AscendC::Reg::MaskReg mask; | ||
| 248 | + AscendC::Reg::MaskReg hit; | ||
| 249 | + for (uint32_t m = 0; m < rows; ++m) { | ||
| 250 | + AscendC::Reg::Duplicate(assistReg, static_cast<int32_t>(kStart) + static_cast<int32_t>(m)); | ||
| 251 | + uint32_t remain = innerElems; // UpdateMask 每轮自减, 尾块自动收窄 | ||
| 252 | + uint32_t rowBase = m * innerElems; | ||
| 253 | + for (uint16_t t = 0; t < repeatsPerRow; ++t) { | ||
| 254 | + mask = AscendC::Reg::UpdateMask<int32_t>(remain); | ||
| 255 | + AscendC::Reg::LoadAlign(idxReg, idxAddr + t * lane); | ||
| 256 | + AscendC::Reg::Compare<int32_t, AscendC::CMPMODE::EQ>(hit, assistReg, idxReg, mask); | ||
| 257 | + if constexpr (IsSameType<T, float>::value || IsSameType<T, int32_t>::value) { | ||
| 258 | + SelectLane32<T, false>(outAddr + rowBase + t * lane, varAddr + rowBase + t * lane, updAddr + t * lane, | ||
| 259 | + zeroScalar, hit, mask); | ||
| 260 | + } else if constexpr (sizeof(T) == B16_BYTES) { | ||
| 261 | + SelectLaneB16<T, false>(outAddr + rowBase + t * lane, varAddr + rowBase + t * lane, updAddr + t * lane, | ||
| 262 | + 0.0f, hit, mask); | ||
| 263 | + } else { | ||
| 264 | + SelectLaneB8<T, false>(outAddr + rowBase + t * lane, varAddr + rowBase + t * lane, updAddr + t * lane, | ||
| 265 | + zeroScalar, hit, mask); | ||
| 266 | + } | ||
| 267 | + } | ||
| 268 | + } | ||
| 269 | +} | ||
| 270 | + | ||
| 200 | } // namespace ArgMaxGrad | 271 | } // namespace ArgMaxGrad |
| 201 | 272 | ||
| 202 | #endif // ARG_MAX_GRAD_VF_H | 273 | #endif // ARG_MAX_GRAD_VF_H |
| @@ -35,10 +35,13 @@ protected: | |||
| 35 | }; | 35 | }; |
| 36 | 36 | ||
| 37 | static void InitPlatForm(fe::PlatFormInfos& platFormInfo, map<string, string>& socInfos, | 37 | static void InitPlatForm(fe::PlatFormInfos& platFormInfo, map<string, string>& socInfos, |
| 38 | - map<string, string>& aicoreSpec, map<string, string>& intrinsics) | 38 | + map<string, string>& aicoreSpec, map<string, string>& intrinsics, int64_t ubSize = 253952, |
| 39 | + int64_t coreNum = 64) | ||
| 39 | { | 40 | { |
| 41 | + // 平台参数可覆盖: 用于验证 tiling 对异常平台信息的拒收(见 test_tiling_platform_* 三例) | ||
| 40 | string hardwareInfo = R"({ | 42 | string hardwareInfo = R"({ |
| 41 | - "hardware_info": {"UB_SIZE": 253952, "CORE_NUM": 64} | 43 | + "hardware_info": {"UB_SIZE": )" + |
| 44 | + std::to_string(ubSize) + R"(, "CORE_NUM": )" + std::to_string(coreNum) + R"(} | ||
| 42 | })"; | 45 | })"; |
| 43 | GetPlatFormInfos(hardwareInfo.c_str(), socInfos, aicoreSpec, intrinsics); | 46 | GetPlatFormInfos(hardwareInfo.c_str(), socInfos, aicoreSpec, intrinsics); |
| 44 | platFormInfo.Init(); | 47 | platFormInfo.Init(); |
| @@ -162,13 +165,14 @@ static ge::graphStatus RunTiling(TilingFunc tilingFunc, HolderT& holder, map<str | |||
| 162 | } | 165 | } |
| 163 | 166 | ||
| 164 | static ge::graphStatus DoArgMaxGradTilingCase(const ArgMaxGradCase& c, uint64_t& tilingKey, uint32_t& blockDim, | 167 | static ge::graphStatus DoArgMaxGradTilingCase(const ArgMaxGradCase& c, uint64_t& tilingKey, uint32_t& blockDim, |
| 165 | - ArgMaxGradArch35TilingData* tdOut = nullptr) | 168 | + ArgMaxGradArch35TilingData* tdOut = nullptr, int64_t ubSize = 253952, |
| 169 | + int64_t coreNum = 64) | ||
| 166 | { | 170 | { |
| 167 | fe::PlatFormInfos platFormInfo; | 171 | fe::PlatFormInfos platFormInfo; |
| 168 | map<string, string> socInfos; | 172 | map<string, string> socInfos; |
| 169 | map<string, string> aicoreSpec; | 173 | map<string, string> aicoreSpec; |
| 170 | map<string, string> intrinsics; | 174 | map<string, string> intrinsics; |
| 171 | - InitPlatForm(platFormInfo, socInfos, aicoreSpec, intrinsics); | 175 | + InitPlatForm(platFormInfo, socInfos, aicoreSpec, intrinsics, ubSize, coreNum); |
| 172 | 176 | ||
| 173 | struct ArgMaxGradCompileInfo {}; | 177 | struct ArgMaxGradCompileInfo {}; |
| 174 | ArgMaxGradCompileInfo compileInfo; | 178 | ArgMaxGradCompileInfo compileInfo; |
| @@ -426,6 +430,57 @@ TEST_F(ArgMaxGradTiling, test_tiling_rank_full_range) | |||
| 426 | } | 430 | } |
| 427 | 431 | ||
| 428 | // 多核切分: elemsPerCore 必须 32B 对齐(跨核不共享搬运块), 且能覆盖全部元素 | 432 | // 多核切分: elemsPerCore 必须 32B 对齐(跨核不共享搬运块), 且能覆盖全部元素 |
| 433 | +// inner==1 且 D 很小、outer 很大: 每段能装 colsPerChunk/D 个 outer, indices/updates 各要 | ||
| 434 | +// 一个与之等长的标量缓冲, 但 bytesPerPoint 对 inner==1 把这两块记成 0 —— 段长按不含它们的 | ||
| 435 | +// 口径算满 UB 后再分配, 总量必然超。这类形状在支持面之内, 不该被 tiling 拒收。 | ||
| 436 | +TEST_F(ArgMaxGradTiling, test_tiling_inner_one_small_axis_large_outer) | ||
| 437 | +{ | ||
| 438 | + uint64_t tilingKey = 0; | ||
| 439 | + uint32_t blockDim = 0; | ||
| 440 | + ArgMaxGradArch35TilingData td{}; | ||
| 441 | + for (int64_t d : {2, 4, 8}) { | ||
| 442 | + EXPECT_EQ(DoArgMaxGradTilingCase({{1000000, d, 1}, 1, ge::DT_FLOAT}, tilingKey, blockDim, &td), | ||
| 443 | + ge::GRAPH_SUCCESS) | ||
| 444 | + << "d=" << d; | ||
| 445 | + } | ||
| 446 | +} | ||
| 447 | + | ||
| 448 | +// 平台信息异常时必须干净拒收(而不是拿非法值继续算)。这三条对应 tiling 里三处拒收分支: | ||
| 449 | +// 核数为 0 / UB 不大于 SIMD-SIMT dcache 预留 / UB 扣掉预留后装不下一个向量整宽的元素 | ||
| 450 | +// rank 为 0(标量 var)必须拒收: 被选轴不存在, 语义不成立 | ||
| 451 | +TEST_F(ArgMaxGradTiling, test_tiling_rank0_rejected) | ||
| 452 | +{ | ||
| 453 | + uint64_t tilingKey = 0; | ||
| 454 | + uint32_t blockDim = 0; | ||
| 455 | + EXPECT_EQ(DoArgMaxGradTilingCase({{}, 0, ge::DT_FLOAT}, tilingKey, blockDim), ge::GRAPH_FAILED); | ||
| 456 | +} | ||
| 457 | + | ||
| 458 | +TEST_F(ArgMaxGradTiling, test_tiling_platform_zero_core_rejected) | ||
| 459 | +{ | ||
| 460 | + uint64_t tilingKey = 0; | ||
| 461 | + uint32_t blockDim = 0; | ||
| 462 | + EXPECT_EQ(DoArgMaxGradTilingCase({{4, 16, 8}, 1, ge::DT_FLOAT}, tilingKey, blockDim, nullptr, 253952, 0), | ||
| 463 | + ge::GRAPH_FAILED); | ||
| 464 | +} | ||
| 465 | + | ||
| 466 | +TEST_F(ArgMaxGradTiling, test_tiling_platform_ub_not_above_dcache_rejected) | ||
| 467 | +{ | ||
| 468 | + uint64_t tilingKey = 0; | ||
| 469 | + uint32_t blockDim = 0; | ||
| 470 | + // 32 KB 恰好等于 SIMD/SIMT 共用 dcache 的预留量, 扣完一个字节都不剩 | ||
| 471 | + EXPECT_EQ(DoArgMaxGradTilingCase({{4, 16, 8}, 1, ge::DT_FLOAT}, tilingKey, blockDim, nullptr, 32 * 1024, 64), | ||
| 472 | + ge::GRAPH_FAILED); | ||
| 473 | +} | ||
| 474 | + | ||
| 475 | +TEST_F(ArgMaxGradTiling, test_tiling_platform_ub_too_small_for_one_vector_rejected) | ||
| 476 | +{ | ||
| 477 | + uint64_t tilingKey = 0; | ||
| 478 | + uint32_t blockDim = 0; | ||
| 479 | + // 比 dcache 多 512 B: 过得了 Init 的门槛, 但一个向量整宽(64 个 fp32 元素)的各路 buffer 放不下 | ||
| 480 | + EXPECT_EQ(DoArgMaxGradTilingCase({{4, 16, 8}, 1, ge::DT_FLOAT}, tilingKey, blockDim, nullptr, 32 * 1024 + 512, 64), | ||
| 481 | + ge::GRAPH_FAILED); | ||
| 482 | +} | ||
| 483 | + | ||
| 429 | TEST_F(ArgMaxGradTiling, test_tiling_core_split_is_block_aligned) | 484 | TEST_F(ArgMaxGradTiling, test_tiling_core_split_is_block_aligned) |
| 430 | { | 485 | { |
| 431 | uint64_t tilingKey = 0; | 486 | uint64_t tilingKey = 0; |