CANN-opbase-Tiling框架详解-昇腾NPU算子分块参数怎么算才对Tiling 是 Ascend C 算子开发里最不直觉的部分。你要在 CPU 上算好分块参数传给 NPU 上的 kernel 用——但 CPU 上算的参数对不对NPU 上不会有报错只会输出错误结果。这篇把 Tiling 框架的完整逻辑讲清楚。Tiling 解决什么问题昇腾NPU的 AI Core 片上缓存有限L1 约 1MBLocal Buffer 约 256KB。一个 [4096, 4096] 的 fp16 矩阵占 32MB远超片上缓存。必须把大矩阵切成小块每次只搬一小块进 AI Core 计算。Tiling 的任务根据输入 shape 和 AI Core 的硬件限制计算最优的分块参数。Tiling 函数的输入输出ge::graphStatusMyOpTiling(constge::Operatorop,TilingContext*ctx){// 输入从 ctx 获取输入 tensor 的 shapeautox_shapectx-GetInputShape(0);int64_tMx_shape.GetDim(0);int64_tNx_shape.GetDim(1);int64_tKctx-GetInputShape(1).GetDim(1);// 输出写入 Tiling 数据ctx-SetTilingKey(1);// 策略编号可以有多套 Tiling 策略ctx-SetTilingData(tile_m,tile_m);ctx-SetTilingData(tile_n,tile_n);ctx-SetTilingData(tile_k,tile_k);ctx-SetTilingData(total_m,M);ctx-SetTilingData(total_n,N);returnge::GRAPH_SUCCESS;}Tiling 函数在 CPU 上执行每次算子被调用时运行一次。如果输入 shape 不变CANN 会缓存 Tiling 结果。分块参数的计算规则核心约束分块后每块的数据量不能超过 L1 容量。GEMM 的分块 需要同时存在 L1 的数据 - A 的分块tile_m × tile_k × dtype_size - B 的分块tile_k × tile_n × dtype_size - C 的分块tile_m × tile_n × dtype_size 总 L1 占用 tile_m × tile_k tile_k × tile_n tile_m × tile_n单位元素数 必须 L1 容量 / dtype_size以 fp16 GEMM 为例L1 1MB 512K fp16 元素tile_m tile_n tile_k 128 占用 128×128 128×128 128×128 49152 元素 96KB 远小于 1MB安全但 Cube 利用率不高 tile_m tile_n tile_k 256 占用 256×256 × 3 196608 元素 384KB 接近 1MB 的一半留空间给 double buffer多套 Tiling 策略不同输入 shape 可能需要不同的分块策略。比如小矩阵不需要分块大矩阵需要多层分块ge::graphStatusMyOpTiling(constge::Operatorop,TilingContext*ctx){int64_tMctx-GetInputShape(0).GetDim(0);int64_tNctx-GetInputShape(0).GetDim(1);if(M*N4096){// 小矩阵不分块一次算完ctx-SetTilingKey(0);ctx-SetTilingData(tile_m,M);ctx-SetTilingData(tile_n,N);}elseif(M*N1048576){// 中等矩阵单层分块ctx-SetTilingKey(1);ctx-SetTilingData(tile_m,128);ctx-SetTilingData(tile_n,128);}else{// 大矩阵双层分块L1 分块 L2 分块ctx-SetTilingKey(2);ctx-SetTilingData(tile_m,64);ctx-SetTilingData(tile_n,64);ctx-SetTilingData(l2_tile_m,512);ctx-SetTilingData(l2_tile_n,512);}returnge::GRAPH_SUCCESS;}kernel 端根据 TilingKey 选择不同的执行路径__aicore__inlinevoidProcess(){if(tiling_data_.tiling_key0){ProcessSmall();}elseif(tiling_data_.tiling_key1){ProcessMedium();}else{ProcessLarge();}}对齐要求昇腾NPU的 Cube 单元要求分块大小是 16 的倍数fp16 下。Vector 单元要求是 32 bytes 的倍数即 16 个 fp16 元素。// ❌ 非对齐分块int64_ttile_mM/3;// 可能不是 16 的倍数// ✅ 对齐到 16 的倍数int64_ttile_m((M15)/16)*16;tile_mstd::min(tile_m,M);// 不超过总长度不对齐的分块不会报错——但 Cube 单元会做 padding多算了一些无用数据浪费算力。严重时性能差 20-30%。AOE 自动调优手动计算 Tiling 参数很难找到全局最优解。AOE 通过搜索自动找最优参数aoe--job_type2--model_pathmodel.onnx--configconfig.jsonAOE 的搜索空间包括 tile_m/tile_n/tile_k、double buffer 数量、L1/L2 分配策略等。一次搜索约 2-4 小时但结果可以固化到 op_tiling 目录永久生效。Tiling 是 Ascend C 算子开发里最容易出错的环节。三个原则对齐、不超 L1 容量、大矩阵用多套策略。如果不确定参数对不对先用小矩阵测试确认无误再放大。仓库在这里https://atomgit.com/cann/opbase
CANN-opbase-Tiling框架详解-昇腾NPU算子分块参数怎么算才对
发布时间:2026/5/21 13:06:18
CANN-opbase-Tiling框架详解-昇腾NPU算子分块参数怎么算才对Tiling 是 Ascend C 算子开发里最不直觉的部分。你要在 CPU 上算好分块参数传给 NPU 上的 kernel 用——但 CPU 上算的参数对不对NPU 上不会有报错只会输出错误结果。这篇把 Tiling 框架的完整逻辑讲清楚。Tiling 解决什么问题昇腾NPU的 AI Core 片上缓存有限L1 约 1MBLocal Buffer 约 256KB。一个 [4096, 4096] 的 fp16 矩阵占 32MB远超片上缓存。必须把大矩阵切成小块每次只搬一小块进 AI Core 计算。Tiling 的任务根据输入 shape 和 AI Core 的硬件限制计算最优的分块参数。Tiling 函数的输入输出ge::graphStatusMyOpTiling(constge::Operatorop,TilingContext*ctx){// 输入从 ctx 获取输入 tensor 的 shapeautox_shapectx-GetInputShape(0);int64_tMx_shape.GetDim(0);int64_tNx_shape.GetDim(1);int64_tKctx-GetInputShape(1).GetDim(1);// 输出写入 Tiling 数据ctx-SetTilingKey(1);// 策略编号可以有多套 Tiling 策略ctx-SetTilingData(tile_m,tile_m);ctx-SetTilingData(tile_n,tile_n);ctx-SetTilingData(tile_k,tile_k);ctx-SetTilingData(total_m,M);ctx-SetTilingData(total_n,N);returnge::GRAPH_SUCCESS;}Tiling 函数在 CPU 上执行每次算子被调用时运行一次。如果输入 shape 不变CANN 会缓存 Tiling 结果。分块参数的计算规则核心约束分块后每块的数据量不能超过 L1 容量。GEMM 的分块 需要同时存在 L1 的数据 - A 的分块tile_m × tile_k × dtype_size - B 的分块tile_k × tile_n × dtype_size - C 的分块tile_m × tile_n × dtype_size 总 L1 占用 tile_m × tile_k tile_k × tile_n tile_m × tile_n单位元素数 必须 L1 容量 / dtype_size以 fp16 GEMM 为例L1 1MB 512K fp16 元素tile_m tile_n tile_k 128 占用 128×128 128×128 128×128 49152 元素 96KB 远小于 1MB安全但 Cube 利用率不高 tile_m tile_n tile_k 256 占用 256×256 × 3 196608 元素 384KB 接近 1MB 的一半留空间给 double buffer多套 Tiling 策略不同输入 shape 可能需要不同的分块策略。比如小矩阵不需要分块大矩阵需要多层分块ge::graphStatusMyOpTiling(constge::Operatorop,TilingContext*ctx){int64_tMctx-GetInputShape(0).GetDim(0);int64_tNctx-GetInputShape(0).GetDim(1);if(M*N4096){// 小矩阵不分块一次算完ctx-SetTilingKey(0);ctx-SetTilingData(tile_m,M);ctx-SetTilingData(tile_n,N);}elseif(M*N1048576){// 中等矩阵单层分块ctx-SetTilingKey(1);ctx-SetTilingData(tile_m,128);ctx-SetTilingData(tile_n,128);}else{// 大矩阵双层分块L1 分块 L2 分块ctx-SetTilingKey(2);ctx-SetTilingData(tile_m,64);ctx-SetTilingData(tile_n,64);ctx-SetTilingData(l2_tile_m,512);ctx-SetTilingData(l2_tile_n,512);}returnge::GRAPH_SUCCESS;}kernel 端根据 TilingKey 选择不同的执行路径__aicore__inlinevoidProcess(){if(tiling_data_.tiling_key0){ProcessSmall();}elseif(tiling_data_.tiling_key1){ProcessMedium();}else{ProcessLarge();}}对齐要求昇腾NPU的 Cube 单元要求分块大小是 16 的倍数fp16 下。Vector 单元要求是 32 bytes 的倍数即 16 个 fp16 元素。// ❌ 非对齐分块int64_ttile_mM/3;// 可能不是 16 的倍数// ✅ 对齐到 16 的倍数int64_ttile_m((M15)/16)*16;tile_mstd::min(tile_m,M);// 不超过总长度不对齐的分块不会报错——但 Cube 单元会做 padding多算了一些无用数据浪费算力。严重时性能差 20-30%。AOE 自动调优手动计算 Tiling 参数很难找到全局最优解。AOE 通过搜索自动找最优参数aoe--job_type2--model_pathmodel.onnx--configconfig.jsonAOE 的搜索空间包括 tile_m/tile_n/tile_k、double buffer 数量、L1/L2 分配策略等。一次搜索约 2-4 小时但结果可以固化到 op_tiling 目录永久生效。Tiling 是 Ascend C 算子开发里最容易出错的环节。三个原则对齐、不超 L1 容量、大矩阵用多套策略。如果不确定参数对不对先用小矩阵测试确认无误再放大。仓库在这里https://atomgit.com/cann/opbase