CANN学习中心:as_strided算子实战

发布时间:2026/5/21 9:40:02

CANN学习中心:as_strided算子实战 使用 CANNBot 在 CANNJudge 上开发 Ascend C 算子as_strided 实战练习【免费下载链接】cann-learning-hubCANN 学习中心仓支持在线互动运行、边学边练提供教程、示例与优化方案一站式助力昇腾开发者快速上手。项目地址: https://gitcode.com/cann/cann-learning-hubCANNJudge 开放题库练习记录原 CANN 算子挑战赛 S2 赛季真题一、缘起CANNJudge 开放题库提供了历届 CANN 算子挑战赛的真题供练习我选择了一个看似简单的题目——as_strided步幅视图。数学定义$$\text{output}[i_0, i_1, \ldots, i_{n-1}] \text{input}[\text{storage_offset} \sum_{j} i_j \times \text{stride}_j]$$支持 float16/float32/int32 三种数据类型stride 可为正/负/零1-4 维任意形状。不就是按索引 gather 嘛我心想。然而接下来的开发经历告诉我在 Ascend C 的世界里一个索引操作背后有 UB 容量、Gather 偏移、Host 预计算、增量索引、命名空间兼容等多重陷阱。但这一次CANNBot 的设计串讲机制帮我在写代码前就发现了致命错误最终功能全部打通5/5 测试用例 precision1.0。二、工具与流程CANNBot 云开发2.1 打开云开发环境访问 cann-learning-hub登录 GitCode点击「云开发」按钮进入在线开发环境在终端中执行git clone https://gitcode.com/cann/cann-learning-hub.git cd cann-learning-hub opencode2.2 全自动安全登录RSA 加密 3 步设置CANNBot 可以自动登录 CANNJudge 完成下载和提交但禁止在对话中直接输入明文密码。采用 RSA 非对称加密仅需 3 步原理服务器生成 RSA 密钥对私钥留在服务器公钥下载到个人 PC。在 PC 上用公钥加密密码生成密文将密文告诉 CANNBotCANNBot 用私钥解密后调用登录 API。全程密码不出现在对话、日志或文件中仅在内存中短暂存在用于 API 调用。步骤 1在云开发终端生成 RSA 密钥对cd skills/cannjudge-submit python3 generate_key.py生成两个文件private.pem— 私钥留在云开发服务器绝不外传public.pem— 公钥需要下载到个人 PC步骤 2将公钥和加密脚本下载到个人 PC在云开发左侧文件浏览器中分别找到以下两个文件右键点击「下载」保存到个人电脑skills/cannjudge-submit/public.pem— 公钥skills/cannjudge-submit/encrypt_password.py— 加密脚本步骤 3在个人 PC 上加密密码pip install pycryptodome python3 encrypt_password.py --public-key public.pem输入 CANNJudge 密码后脚本输出 RSA 密文。将密文告诉 CANNBot 即可。密文可以复用——只要私钥不变同一密文可以反复使用。2.3 一句话启动算子开发在 opencode 对话界面中输入帮我开发 as_strided 算子 https://cannjudge.cn/public/s2/asstridedCANNBot 识别出 CANNJudge 题目链接自动完成全流程询问登录信息CANNBot 提示输入邮箱和 RSA 密文自动下载工程从 CANNJudge 下载 as_strided 题目的工程模板环境检查验证 CANN 安装、编译器、NPU 设备架构设计Architect Agent 分析需求、验证 API、输出 DESIGN.md PLAN.md设计串讲Developer Agent 从实现角度批判性审查设计发现 9 个问题代码开发Developer Agent 实现完整代码代码审查Reviewer Agent 独立审查100 分制评分修复循环审查发现的问题自动修复性能验收NPU profiling 采集性能数据CANNJudge 提交提交 4 个文件轮询等待判题结果三、设计阶段双路径策略3.1 算子本质分析as_strided 的本质是非连续索引读取——每个输出元素从输入 tensor 的任意位置 gather 而来。关键挑战stride 可为负/零负 stride 导致源索引递减零 stride 导致多元素映射同一源NPU 不支持动态索引计算__aicore__内禁止 float↔int 转换除法/取模是昂贵操作UB 容量有限ascend910b 仅 192KB输入过大时无法全量搬运3.2 双路径策略路径条件方案性能Path A输入可全量放入 UB全量 DataCopyPad → Gather API → DataCopyPad高效1次大DMA N次GatherPath B输入过大逐元素 DataCopyPad 从 GM 读取较慢回退方案路径判断公式Path A: inputBytesAligned offsetTileBytes outputTileBytes × 2 ≤ UBBudget (192KB - 8KB) Path B: 否则3.3 Host 预计算偏移表关键决策问题NPU 的__aicore__内除法/取模是软件模拟耗时数百周期。如果 Kernel 内逐元素计算多维索引反推性能极差。方案Host 侧一次性预计算所有输出元素对应的源索引通过GetRawTilingData()-Append()追加到 tiling data 二进制码流Kernel 侧用 DataCopyPad 搬运到 UB 后直接使用。// Host 侧预计算一次性 for (uint32_t f 0; f totalOutputElements; f) { int32_t srcIdx storageOffset; uint32_t remaining f; for (uint32_t d 0; d ndim; d) { uint32_t i_d remaining / dimStrideArr[d]; remaining remaining % dimStrideArr[d]; srcIdx (int32_t)i_d * strideArr[d]; } // 防御性 clamp防止负 stride 导致越界 if (srcIdx 0 || (uint32_t)srcIdx inputTotalElements) { srcIdx 0; } offsetTable[f] (uint32_t)srcIdx * dtype_size; // byte offset for Gather } // 追加到 tiling data rawTiling-Append(offsetTable.data(), totalOutputElements);这彻底消除了 Kernel 内的 SetValue 标量操作和除法/取模运算。四、设计串讲开发前的致命问题拦截这是本次开发最关键的环节。CANNBot 的 Developer Agent 从实现角度批判性审查了 Architect 的设计发现了 9 个问题其中 1 个是阻塞级。 阻塞级问题UB 容量假设错误Architect 的设计constexpr uint32_t UB_SIZE 253952; // 248KBDeveloper 的发现查阅 CANN 9.0.0 源码kernel_utils_constants.h确认ascend910b (DAV_2201) 实际 UB 为192KB (196608 bytes)不是 248KB// __NPU_ARCH__ 2201 (Ascend910B 系列) const uint32_t TOTAL_UB_SIZE 192 * 1024; // 196608 bytes如果按 248KB 规划 BufferInitBuffer分配超出 UB 容量 →运行时 buffer 溢出崩溃。Path A 的输入全量搬运假设有 ~245KB 可用实际只有 ~188KB能容纳的输入量大幅缩小。修正UB_SIZE从 253952 修正为 196608ubBudget从 245760 修正为 188416。教训不同 NPU 架构 UB 容量不同。A5 系列 248KBA3 系列 192KB。GetUBSizeInBytes()不支持 A3 系列必须查阅kernel_utils_constants.h确认目标架构的 UB 容量不能凭经验。 其他重要问题#问题Architect 回应2SetValue 逐元素构建偏移表性能差✅ 改为 Host 预计算 DataCopyPad 搬运3负 stride 可能导致 Gather 偏移越界✅ 增加防御性 clamp4TilingData 含每核变量设计错误✅ 改为 Kernel 内 GetBlockIdx() 动态计算5缺少 Double Buffer 策略⚠️ Path A 开启 Double Buffer6Path B GetValueSetValue 为已知反模式⚠️ 文档化回退串讲的价值如果不做串讲UB 容量错误会在运行时才暴露buffer 溢出崩溃排查可能需要数小时。串讲将问题前移到设计阶段零成本修复。五、代码审查从 FAIL 到 PASS首审结果FAIL (75/100)维度满分得分关键问题编译验证1010✅架构合规1515✅编码规范1510⚠️ SetValue 循环违规性能优化2013⚠️ 无 Double Buffer有 div/mod测试覆盖1515✅精度验证1010✅文档152❌ README.md 内容缺失必须修复项Path A 使用 SetValue 循环构建 Gather 偏移表违反编码规范。修复后复审PASS (98/100)修复措施修复项措施效果MF-1Host 预计算偏移表 → Append 到 tiling data → DataCopyPad 搬运✅ 主路径完全消除 SetValue 和 div/modSF-1Path A outQueueDstA 改为 BUFFER_NUM2✅ Double Buffer 生效SF-2补充 README.md✅SF-3FP32 精度标准从 1e-4 提升到 1e-5✅SF-4实现 Host 预计算方案与 DESIGN.md 对齐✅修复后编码规范详细分析代码路径SetValue/GetValuediv/mod评价Path A 主路径无无✅ 完全向量化Path A 回退SetValue仅 tiling 溢出时无⚠️ 极端场景Path BGetValueSetValue无⚠️ 文档化回退六、CANNJudge 提交从 Compile Error 到功能全通6.1 首次提交Compile Error提交后遭遇Compile Error。原因CANNJudge 使用 CANN 8.5Ascend C 类型在AscendC::命名空间下而本地 CANN 9.0 默认using namespace AscendC。修复在as_strided.cpp头部添加using namespace AscendC;。踩坑本地 CANN 9.0 编译通过 ≠ CANNJudge CANN 8.5 编译通过。必须在 Kernel .cpp 文件头部显式添加using namespace AscendC;确保 CANN 8.5/9.0 兼容。6.2 二次提交5/5 全部通过用例状态precision耗时best_time比值Case 1✅ Pass1.04.64ms3.86ms1.20xCase 2✅ Pass1.07.08ms7.08ms1.00x (最优!)Case 3✅ Pass1.091.42ms83.70ms1.09xCase 4✅ Pass1.05.30ms5.08ms1.04xCase 5✅ Pass1.051.30ms51.30ms1.00x (最优!)5/5 全部 Passprecision1.02 个用例达到全局最优时间功能全部打通。6.3 性能现状与优化空间当前功能已全通但部分用例与最优时间仍有差距用例耗时best_time比值分析Case 14.64ms3.86ms1.20x小数据量启动开销占比大Case 27.08ms7.08ms1.00x已达最优Case 391.42ms83.70ms1.09x可能走了 Path B 回退路径优化空间最大Case 45.30ms5.08ms1.04x接近最优Case 551.30ms51.30ms1.00x已达最优需要截图CANNJudge as_strided 提交结果页面https://cannjudge.cn/public/s2/asstrided展示 5/5 Pass 和各用例耗时七、性能验收NPU Profiling 分析7.1 核心指标指标值判定Task Duration (avg)12.92 us✅Block Dim40✅ 满核利用主导流水MTE2 (60.8%)✅ 符合 Gather 类算子特征aiv_vec_ratio2.8%✅ Gather 类预期低 VECaiv_scalar_ratio31.5%⚠️ 地址计算开销icache_miss_rate0.0%✅ 优秀7.2 扩展性配置输入输出延迟Small1K10043.37 usMedium10K1K42.97 usLarge50K10K43.01 usXL100K20K43.28 usXXL500K100K78.36 usSmall~XL 延迟恒定 (~43 us)说明受启动开销主导。XXL 才显示数据依赖的扩展性。需要截图msprof 界面中 as_strided 算子的 Task Duration 和流水线利用率八、踩坑全景图#坑阶段一句话严重程度1必须从 CANNJudge 下载工程模板环境搭建自行创建工程结构与评测环境不一致编译失败2CANN 8.5/9.0 命名空间差异首次提交本地通过 ≠ CANNJudge 通过必须显式using namespace AscendC;3UB 容量因芯片架构不同设计ascend910b 是 192KB 不是 248KBGetUBSizeInBytes()不支持 A34GetValue/SetValue 是性能黑洞代码审查标量操作吞吐极低优先用向量 API5aicore内除法/取模昂贵设计NPU 无硬件除法器索引计算移到 Host 侧6TilingData 不能存每核变量设计串讲所有核共享同一份 TilingData每核变量用 GetBlockIdx() 动态算7TilingFunc 无法写入 workspace设计CANN 9.0 不提供 GetWorkspaceData用 Append 到 tiling data8DataCopyPad 是非对齐统一方案开发避免对齐判断分支统一用 DataCopyPad9Gather 偏移是字节偏移开发Host 预计算时乘以 sizeof(T)10负 stride 需防御性 clamp设计串讲srcIdx 为负时 Gather 偏移越界触发硬件异常11Double Buffer 需独立 TQue代码审查不同路径用独立 TQue按需配置 BUFFER_NUM12设计串讲前移问题发现流程串讲发现 9 个问题含 1 阻塞零成本修复✅关键对比erf 算子踩坑 6.5 小时开发后才发现问题as_strided 算子踩坑约 2 小时设计串讲前移了致命问题。设计串讲节省了至少 4 小时的返工时间。九、核心架构全景9.1 数据流Path A主路径Host 预计算: 对每个输出元素计算 srcIdxclamp 到 [0, N-1] 生成 byte offset 表Append 到 tiling data 输入 input_x (Global Tensor) ↓ DataCopyPad (全量搬运, 仅一次) 输入 input_x (Local Tensor, VECIN) ↓ [per tile loop:] 偏移表 tilingGM[offsetStart..] (Global Tensor) ↓ DataCopyPad (搬运当前 tile 的偏移表) 偏移表 offsetLocal (Local Tensor, VECCALC) ↓ Gather (按 offset 表从 inputLocal 收集) 输出 output (Local Tensor, VECOUT, Double Buffer) ↓ DataCopyPad (非对齐写回) 输出 output (Global Tensor)9.2 Buffer 规划Path ABuffer用途TPositionDouble BufferinQueueSrc输入全量VECIN否tmpQueueOffsetGather 偏移表VECCALC否outQueueDstA输出 tileVECOUT是9.3 TilingData 结构struct AsStridedTilingData { uint32_t totalOutputElements; // 输出总元素数 uint32_t tileSize; // 每 tile 处理的元素数 uint32_t inputTotalElements; // 输入总元素数 uint32_t ndim; // 维度数 (1-4) uint32_t size[4]; // 输出各维度大小 int32_t stride[4]; // 输出各维度步长可为负 int32_t storageOffset; // 存储偏移量 uint32_t dimStride[4]; // 各维度累积步长 uint32_t pathFlag; // 0PathA, 1PathB uint32_t blockDim; // 使用的核数 uint32_t offsetTableInTiling; // 1偏移表追加在tiling data中 }; // 后跟 Append 的偏移表数据uint32_t 数组9.4 增量索引计算回退方案当 tiling data 容量不足时Kernel 内使用混合进制计数器递增多维索引仅用加法/减法/比较运算__aicore__ inline void advanceIncrementalState() { int32_t d (int32_t)ndim - 1; multiIdx[d]; curSrcIdx strideArr[d]; while (d 0 multiIdx[d] sizeArr[d]) { curSrcIdx - (int32_t)sizeArr[d] * strideArr[d]; multiIdx[d] 0; d--; multiIdx[d]; curSrcIdx strideArr[d]; } // Defensive clamp if (curSrcIdx 0 || (uint32_t)curSrcIdx inputTotalElements) { curSrcIdx 0; } }十、CANNBot 开发流程复盘10.1 7 步流水线Step 1: 环境检查 → ✅ 全部通过 Step 2: 设计(Architect) → DESIGN.md PLAN.md Step 2.5: 设计串讲(Developer↔Architect) → WALKTHROUGH.md发现 9 个问题 Step 3: 开发(Developer) → 编译通过 测试通过 Step 4: 审查(Reviewer) → FAIL (75/100) Step 5: 修复循环(1轮) → PASS (98/100) Step 6: 性能验收 → ✅ 达标 Step 7: CANNJudge 提交 → 5/5 Pass功能全通10.2 三 Agent 协作Agent职责本次贡献Architect需求分析、API 验证、架构设计输出双路径策略 Host 预计算方案串讲中接受 6 项修改Developer代码开发、编译测试串讲发现 9 个问题含 1 阻塞实现完整代码 修复Reviewer独立审查、100 分制评分首审 FAIL (75/100)复审 PASS (98/100)10.3 流程价值量化指标无串讲预估有串讲实际节省UB 容量错误发现时间运行时崩溃后排查 (~3h)设计阶段 (~0h)3hSetValue 反模式发现时间代码审查后修复 (~1h)设计阶段 (~0h)1hTilingData 设计错误发现时间多核输出错误后排查 (~2h)设计阶段 (~0h)2h总节省--~6h十一、给后来者的建议开发前必做必须从 CANNJudge 下载工程模板自行创建工程结构与评测环境不一致查阅 UB 容量不同架构容量不同GetUBSizeInBytes()可能不支持你的芯片设计串讲不可跳过从开发者视角审查设计能发现 Architect 容易忽略的实现细节开发中必做Host 预计算消除 Kernel 内昂贵操作除法/取模/SetValue 移到 Host 侧Gather 偏移是字节偏移Host 预计算时乘以 sizeof(T)防御性 clamp 防硬件异常负 stride/越界索引 clamp 到合法范围TilingData 不存每核变量用 GetBlockIdx() 动态计算Double Buffer 独立 TQue不同路径按需配置 BUFFER_NUM提交前必做显式using namespace AscendC;CANN 8.5/9.0 兼容首个用例最关键CANNJudge 第一个用例失败后后续全 Skip工具使用CANNBot 是好帮手一句话启动自动完成设计→串讲→开发→审查→提交全流程云开发零门槛GitCode 云开发环境预装 CANN 工具链clone 即用RSA 加密保安全密文登录 CANNJudge明文密码绝不暴露在对话中十二、与 erf 算子开发的对比维度erf初赛as_stridedS2踩坑时间~6.5 小时~2 小时提交次数4 次2 次1次 Compile Error 1次 Pass首次提交结果Wrong Answer (precision0.1875)Compile Error最终结果15/15 PASS5/5 PASS功能全通关键差异无设计串讲开发后才发现问题有设计串讲开发前拦截致命错误核心教训精度/对齐/同步之坑UB 容量/Host 预计算/命名空间之坑核心结论设计串讲是性价比最高的质量关卡。6 小时的踩坑 vs 2 小时的踩坑差异不在工具而在流程。十三、下一步从功能全通到性能调优当前 as_strided 已功能全通5/5 Passprecision1.0但性能仍有优化空间。Case 3 (91.42ms vs best 83.70ms) 是最大瓶颈可能走了 Path B 回退路径。性能优化需要更深入的算子开发知识。CANNBot 能帮你快速实现功能正确的算子但要指导它生成高性能算子你自己必须理解Tiling 切分如何影响多核利用率和 UB 访存效率Cube 与 Vector 流水线如何协同双缓冲/三缓冲的预取策略如何设计L0A/L0B/L0C 等 Buffer 的容量约束与分配策略推荐学习 cann-learning-hub 中的 Ascend C 算子开发教程至少完成前 5 章边学边练指导 CANNBot 做性能优化章节内容对性能优化的作用第1章Ascend C 编程模型与硬件架构理解 AI Core 的 Cube/Vector/Scalar 流水线第2章数据搬运与 Buffer 管理掌握 UB/L1/L0 的容量约束和对齐要求第3章向量计算 API 深度使用理解 API 的流水线行为和性能特征第4章Tiling 设计方法论掌握多核切分、UB 切分、分支覆盖策略第5章流水线并行与双缓冲掌握 MTE2/VEC/MTE3 重叠和预取策略学完前 5 章后你就能理解当前 as_strided 算子的性能瓶颈所在并指导 CANNBot 生成更高性能的实现。具体可优化的方向Case 3 Path B 回退优化输入过大无法全量入 UB可探索分批搬运输入的方案scalar_ratio 偏高 (31.5%)地址计算开销可尝试增大 Tiling 切分粒度连续 stride 优化对于 stride 规则的场景可优化为更大块的顺序读取十四、写在最后as_strided 的开发经历让我深刻体会到设计串讲是 Ascend C 算子开发中性价比最高的环节。它在写代码之前就拦截了 UB 容量错误运行时崩溃、TilingData 设计错误多核输出错误、SetValue 反模式性能黑洞等问题节省了至少 6 小时的返工时间。CANNBot 的三 Agent 协作机制——Architect 设计、Developer 串讲开发、Reviewer 审查——形成了一个有效的质量闭环。特别是串讲环节Developer 从实现角度质疑设计Architect 基于官方文档回应CANNBot 仲裁分歧整个过程严格 1 轮、不做多轮往返既保证了质量又控制了时间。与 erf 算子踩坑→修复→再踩坑的试错模式相比as_strided 的设计→串讲→开发→审查流程更加高效。建议所有 Ascend C 算子开发者永远不要跳过设计串讲。当前 as_strided 已功能全通但性能仍有优化空间。建议先跟着 CANNBot 快速出成绩、建立信心再系统学习 cann-learning-hub 前 5 章夯实基础最后指导 CANNBot 做性能优化——这才是 CANNBot 的正确打开方式。完整的技术设计文档、串讲记录、审查报告和源码详见 as_strided TUTORIAL.md 和 as_strided 算子说明。本文基于 CANNJudge 开放题库 as_strided 算子练习真实经历撰写原 CANN 算子挑战赛 S2 赛季真题。完整教程和源码详见 cann-learning-hub 中skills/examples/as_strided/。【免费下载链接】cann-learning-hubCANN 学习中心仓支持在线互动运行、边学边练提供教程、示例与优化方案一站式助力昇腾开发者快速上手。项目地址: https://gitcode.com/cann/cann-learning-hub创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

相关新闻