从第一次算子提交到跑通全部用例:CANNJudge 昇腾算子竞赛三幕实战
【免费下载链接】cann-learning-hubCANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。项目地址: https://gitcode.com/cann/cann-learning-hub
整场比赛其实就三件事:提交、过用例、刷性能。CANNJudge 是昇腾算子竞赛的官方在线评测平台,题目、工程模板、隐藏用例、排行榜都挂在上面,算子提交的闭环是:下载模板 → 本地实现 → 上传等判决。本文带你走三幕:首次提交、扛住全部用例、失败之后怎么查。
🔐 第一幕:一条命令串跑完首次算子提交
结论先行:赛前准备就是"Python + 两个依赖 + 一个账号 + 一段密文",凑齐之后 5 分钟能发出第一次算子提交。
备赛:出发前 5 分钟能备完的清单
- Python 3 环境,装好
requests - 装好
pycryptodome(只有 RSA 密文登录需要,终端隐藏输入登录可以省掉) - CANNJudge 账号已注册
- RSA 密钥对已生成、密码密文已拿到(见下一节)
- 选定一道题目,拿到完整题目链接或题目 ID
生成 RSA 密文并登录
为什么要用 RSA(非对称加密:公钥加密出去的密码,只有私钥能解回来)?因为明文密码不该出现在聊天框、命令历史或环境变量里。流程三步:
- 在服务器跑一次
python3 generate_key.py,生成 RSA-2048 密钥对:private.pem留在服务器、绝不外传,public.pem拷到个人电脑; - 个人电脑上运行
python3 encrypt_password.py --public-key public.pem,按提示隐藏输入密码,得到一段密文; - 回到服务器执行
python3 cannjudge_cli.py login --email "your@email.com" --ciphertext "密文" --private-key private.pem,完成登录。密钥与密文的注意事项见 RSA 登录说明。
别急,还有条更省事的路:手边有交互终端的话,直接python3 cannjudge_cli.py login就能隐藏输入密码登录,根本不用密文。登录成功后会话写入~/.cannjudge/session.json(只存用户 ID 和 Cookie,不存密码),最长可复用 7 天,之后下载、提交、查询全部自动续用。注意会话不跨系统搬运——在 Windows 上登录的会话没法拷到 Linux 提交机上,得在实际提交的主机上重新登录一次。
想把流程嵌进自己的脚本?同一个工具暴露了CANNJudgeClient类,按"加载会话 → 解析题目 → 下载模板 → 打包文件 → 提交 → 等待结果"的顺序调用即可;喜欢被一步步引导的,python3 example.py有交互式提示。入口在 CLI 工具源码。
跑最短路径:从登录到查询
备齐之后,一次完整的首次算子提交只需要下面这串命令(题目可以用完整链接--problem-url,也可以--problem-id;完整链接自带小组和比赛上下文,同名题不会选串):
# 1) 首次登录:终端隐藏输入,会话存入 ~/.cannjudge/session.json python3 cannjudge_cli.py login # 2) 读题目、下载工程模板(info/download 访问公开资源,未登录也能用) python3 cannjudge_cli.py info --problem-url "https://cannjudge.cn/hc_cann_codelabs/cann/add" python3 cannjudge_cli.py download --problem-url "https://cannjudge.cn/hc_cann_codelabs/cann/add" --output ./add-work # 3) 完成实现后先预检:打印将提交的文件列表与 SHA-256,不真正发送代码 python3 cannjudge_cli.py submit --problem-id <题目ID> --project-dir ./add-work/project --dry-run # 4) 正式提交(--no-wait 立即返回提交 ID),再查结果、看排行榜 python3 cannjudge_cli.py submit --problem-id <题目ID> --project-dir ./add-work/project --no-wait python3 cannjudge_cli.py query --submission-id <提交ID> python3 cannjudge_cli.py rank --problem-id <题目ID>两个细节:--dry-run是提交前的"空跑",它会核对只读模板是否被改、路径是否越界、工程元数据绑定的题目是否对得上,任何一项不过就直接停手,让你提前发现问题;正式提交后默认等 120 秒拿结果,--max-wait可调,--no-wait之后拿提交 ID 分次查也一样。
第二幕:写出扛得住全部用例的算子
平台不开放隐藏用例的 API。这意味着你写的算子必须对任何可能出现的 shape、dtype 和属性取值"免疫"。整幕分三步推进:读题、看懂模板、写泛化代码。
读懂题目:参考算子与默认值语义
题目通常是"参考算子 + 约束条件"的组合:参考torch.histc,支持 float16/float32,属性有 bins、axis、keepdims 之类。要读透三处:
- 算子原型:输入、输出、属性分别是什么,各自支持哪些数据类型。它决定你代码的骨架——不能增删输入输出,不能改数据类型,不能调换参数顺序;
- 默认值的语义:坑在这里。不少"默认值"其实是标志位而不是有效取值。
torch.histc(min=0, max=0)里的 0/0 不是范围 [0,0],而是"自动计算数据自身范围";axis=-1指最后一维,而不是负数维; - 公开数据:下载包里带的公开用例可以拿去本地验证,隐藏用例则不要试图碰。
动手写代码之前,先用 Python 把参考算子跑一遍,验证自己对这些语义的理解,尤其是"0 到底是不是自动标志"这一步别省。
看懂模板:哪些文件能改、哪些不能
下载的模板分两种类型,工具按网站返回的code_template字段自动分流,别靠目录名猜:
- 核函数工程(
npu_kernel_dev):核心是根目录的kernel.asc,它会被平台主程序包含,所以不要自己加main(),也别加头文件保护。main.asc、data_utils.h、run.sh、scripts/都是只读的本地调用与验证程序,一个都别动;提交时把所有可编辑文件以"相对路径 + 内容"的形式打包发出; - 传统算子工程:结构固定。
code/op_host/{op_name}.cpp写 Host 侧的 TilingFunc、InferShape、InferDataType;code/op_kernel/{op_name}_tiling.h定义 Tiling 数据结构(Tiling 是"切分参数表",告诉 kernel 数据怎么分、按什么类型算);tiling_key_{op_name}.h负责区分编译期分支;op_kernel/{op_name}.cpp才是真刀真枪的计算逻辑。Tiling 配置是 Host 与 Kernel 之间的契约——kernel 需要知道的每一个量,都必须从这里拿到。
铁律只有一条:只读模板文件一旦被改动,CLI 会直接停手;工程元数据绑定的题目对不上、路径越界、符号链接,也都会触发拦截。如果模板里缺CMakePresets.json或build.sh,从已有的 Ascend C 工程复制过来改改就行。
写泛化代码:算子泛化怎么写
泛化不是"多写几个测试",而是把代码写成不依赖先验的样子。落到实操上,三个习惯就能覆盖 shape、dtype、属性、对齐、边界这五个维度:
维度数要推导,不要写死。隐藏用例未必是 4D,别上来就GetDim(0..3)抓 NCHW,先用GetDimNum()拿到实际维度数,再循环乘出总元素数。dtype 要实例化,不要假设。Kernel 侧不支持运行时多态,用template<typename T>写类、入口函数按 Tiling 里记录的 dtype 分发,题目支持几种类型就实例化几个。属性和边界当"特殊值"处理。axis 可能是 -1,bins 可能是 0,输入可能是空张量或单元素,尾部不足 32 字节对齐的数据交给 DataCopyPad 兜底。两种写法的差距:
// ❌ 写死 4D 维度和 float:本地样例能过,隐藏用例必挂 int32_t N = shape.GetDim(0), C = shape.GetDim(1); int32_t H = shape.GetDim(2), W = shape.GetDim(3); AscendC::GlobalTensor<float> xGm; xGm.SetGlobalBuffer((__gm__ float *)x); // ✅ 泛化写法:维度和 dtype 都从实际输入"算"出来 int32_t rank = shape.GetDimNum(); uint32_t totalLength = 1; for (int32_t i = 0; i < rank; i++) { totalLength *= shape.GetDim(i); } template <typename T> class KernelOp { public: __aicore__ inline void Init(GM_ADDR x, GM_ADDR y, uint32_t len) { xGm.SetGlobalBuffer((__gm__ T *)x, len); yGm.SetGlobalBuffer((__gm__ T *)y, len); } private: AscendC::GlobalTensor<T> xGm, yGm; }; // 入口函数里按 tilingData.dtype 分发:float、half……各实例化一次提交节奏建议分阶段:先交一版基础功能确认链路通,再扩数据类型、补边界处理,最后才做性能调优;提交前用下载包的公开数据在本地过一遍,空输入、单元素、非对齐尾部都喂一次。
📊 第三幕:提交之后才是开始
提交先返回Running,再落到某个终态。别把 Running 当完成,也别只看整体状态——把每个测试点逐个展开看。
分析提交结果:状态码 → 动作
当前平台的成功终态是Pass(旧数据里写作Accepted),都表示全部通过。失败终态逐个对:
Compile Error:没编过。看平台编译信息修语法和依赖,别
【免费下载链接】cann-learning-hubCANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。项目地址: https://gitcode.com/cann/cann-learning-hub
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考