SuperAPI AI Directory

ascendc-operator-code-gen

Agents & models

View repository

Updated 10-15 19:57

About this skill


name: ascendc-operator-code-gen description: Generate the complete AscendC operator code implementation from the design document and complete framework adaptation. TRIGGER when: the design document is complete and you need to generate op_host/op_kernel code, register it with the PyTorch framework, and compile and test. Keywords: code generation, op_host, op_kernel, tiling, kernel, framework adaptation, operator registration.

AscendC Operator Code Generation and Framework Adaptation

Generate op_host + op_kernel code from the design document, register it with the PyTorch framework, and call ascendc-operator-compile-debug skill to complete compilation, installation, and accuracy testing.

Prerequisites: Design document ascend-kernel/csrc/ops/<op_name>/design.md is ready, and the directory structure has been created.

Workflow Overview

读取设计文档 → 加载 reference → 选择模板 → 生成 op_host + op_kernel
    → 框架适配 (ops.h + register.cpp + csrc/CMakeLists.txt)
    → 调用 ascendc-operator-compile-debug skill (编译 + 安装 + 测试)

Phase 1: Load Reference Documents

MANDATORY — READ BEFORE CODING: Read references/GUIDE.md, and load the corresponding reference file according to the operator type in the design document.Never skip this step.

Phase 2: Read the Design Document

From ascend-kernel/csrc/ops/<op_name>/design.md extract:

Extraction Item Design Document Section Purpose
Function signature + supported data types Prototype design op_host function prototype, kernel template parameters
Operator type Tiling split → Step 1 Select template (elementwise / row)
UB allocation table UB space allocation Derive bufferCoefficient, InitBuffer size
Computation pseudocode Kernel implementation Compute function logic

Phase 3: Select a Template and Generate Code

Select the corresponding template according to the operator type, copy it to the project directory, and then modify it.

Template Selection

Operator Type op_host Template op_kernel Template
Elementwise (ReLU, GELU, Add...) templates/elementwise_op_host.cpp templates/elementwise_op_kernel.cpp
Row processing (LayerNorm, Softmax...) templates/row_op_host.cpp templates/row_op_kernel.cpp
Index-PerRow (IndexSelect, shared 1D index) templates/index_op_host.cpp templates/index_op_kernel.cpp
Index-PerElem (Gather, Scatter, per-row index) templates/index_op_per_elem_host.cpp templates/index_op_per_elem_kernel.cpp
Sort (Sort, TopK...) templates/sort_op_host.cpp templates/sort_op_kernel.cpp
Pool (MaxPool, AvgPool...) templates/pool_ndhwc_op_host.cpp templates/pool_ndhwc_op_kernel.cpp

MANDATORY: Read the complete contents of the corresponding template file, copy it to the target path, and then modify it.

Generation Steps

  1. Read the complete contents of the selected op_host and op_kernel templates
  2. Write the contents to ascend-kernel/csrc/ops/<op_name>/op_host/<op_name>.cpp and op_kernel/<op_name>.cpp(overwriting the skeleton placeholder files)
  3. Replace all placeholders (<op_name>, <OpName> , etc.)
  4. Modify according to the design document:
    • op_host: function signature, input validation,bufferCoefficient, EXEC_KERNEL_CMD parameters
    • op_kernel: Init's GM parameters, InitBuffer size, Compute computation logic

FP16/FP32 template branch

The Kernel template uses template <typename T> generics + if constexpr branches to handle different data types:

if constexpr (sizeof(T) == sizeof(float)) {
    // float32 直接计算
} else {
    // fp16/bf16: Cast 升精度 → fp32 计算 → Cast 降精度
}

The AscendC compiler supports C++17 if constexpr, which can be safely used for template branches. If compatibility with older compilers is needed, you can also use sizeof(T) == 4 plain if (the compiler will optimize away dead branches).

Key Points for Template Modification

Hardware Parameter Retrieval (built into the template)

The template uses platform APIs to dynamically retrieve hardware parameters, no longer hardcoded:

#include "tiling/platform/platform_ascendc.h"

auto ascendc_platform = platform_ascendc::PlatformAscendCManager::GetInstance();
int64_t coreNum = static_cast<int64_t>(ascendc_platform->GetCoreNumAiv());
uint64_t ubSize;
ascendc_platform->GetCoreMemSize(platform_ascendc::CoreMemType::UB, ubSize);
// 需要 workspace 时:
// uint64_t sysWorkspaceSize = ascendc_platform->GetLibApiWorkSpaceSize();

bufferCoefficient Derivation

Derived from the UB allocation table in the design document—the total size of all Buffers = bufferCoefficient * tileLength:

  • Sum the "total size" column of all buffers in the UB allocation table
  • The result has the form tileLength * N, then bufferCoefficient = N
  • When different dtypes have different coefficients, use the if (dtypeSize == 2) {...} else {...} branch

FP16/BF16 precision promotion

Both FP16 and BF16 must be promoted to FP32 for computation and then converted back. The template already marks the insertion positions for the precision-promotion code; just uncomment and fill in the computation logic.

GM ↔ UB transfer (DataCopyPad)

Production code must use DataCopyPad(do not use DataCopy for GM↔UB transfers):

// CopyIn: GM → UB
AscendC::DataCopyExtParams copyInParams{1,
    static_cast<uint32_t>(curTileLength * sizeof(T)), 0, 0, 0};
AscendC::DataCopyPadExtParams<T> padParams{false, 0, 0, static_cast<T>(0)};
AscendC::DataCopyPad(xLocal, xGm[progress * tileLength], copyInParams, padParams);

// CopyOut: UB → GM
AscendC::DataCopyExtParams copyOutParams{1,
    static_cast<uint32_t>(curTileLength * sizeof(T)), 0, 0, 0};
AscendC::DataCopyPad(yGm[progress * tileLength], yLocal, copyOutParams);

Note: template files may use DataCopy to simplify examples; production code should replace it with DataCopyPad.

ReduceSum/ReduceMax precautions

Reduction operations may modify the source tensor, so a backup must be made first:

AscendC::Adds(backup, src, 0.0f, len);  // 备份
AscendC::ReduceSum<float, true>(result, backup, sharedTmp, dimLen);

Stage 4: Framework adaptation

4.1 Update csrc/ops.h

In namespace ascend_kernel , add the function declaration:

<返回类型> <op_name>(<参数列表>);

4.2 Update csrc/register.cpp

In TORCH_LIBRARY_FRAGMENT(npu, m) , add the schema:

m.def("<op_name>(<schema_参数>) -> <返回类型>");

In TORCH_LIBRARY_IMPL(npu, PrivateUse1, m) , add the implementation:

m.impl("<op_name>", TORCH_FN(ascend_kernel::<op_name>));

Schema syntax quick reference:

C++ type Schema type Example
const at::Tensor & Tensor Tensor self
at::IntArrayRef int[] int[] kernel_size
int64_t int int dim=-1
double float float eps=1e-5
bool bool bool flag=False
c10::optional<at::Tensor> Tensor? Tensor? weight=None
c10::optional<int64_t> int? int? divisor=None

4.3 Update csrc/CMakeLists.txt

In FILE(GLOB OP_SRCS ...) , add the host source file:

${PROJECT_OP_SRC_BASE}/ops/<op_name>/op_host/<op_name>.cpp

In ascendc_library(no_workspace_kernel STATIC ...) , add the kernel source file:

${PROJECT_OP_SRC_BASE}/ops/<op_name>/op_kernel/<op_name>.cpp

Stage 5: Build, install, and test

MANDATORY — execute according to the ascendc-operator-compile-debug skill process:

  1. Load the environment: source the CANN environment variables, activate the conda environment
  2. Ensure build.sh has execute permission:chmod +x build.sh
  3. Build the project:bash build.sh
  4. Verify the build succeeded:ls output/ascend_kernel*.whl
  5. Install the whl package:pip install output/ascend_kernel*.whl --force-reinstall --no-deps
  6. Generate the test file tests/test_<op_name>.py
  7. Run the functional test:python tests/test_<op_name>.py
  8. Run the precision test:pytest tests/test_<op_name>.py -v
  9. If build/test fails, enter the troubleshooting loop (at most 3 times)
  10. When the precision test fails: if allclose does not pass, the output deviates, or it is all zeros or NaN,MUST read and follow the ascendc-operator-precision-debug skill process to troubleshoot the root cause (error analysis → code review → experimental isolation → instrumentation localization → fix verification)

Practical experience: source the environment variables before every shell command, otherwise the build tools or Python packages will not be found.


Post-generation checklist

op_host

  • namespace ascend_kernel,include torch_kernel_helper.h + tiling/platform/platform_ascendc.h + aclrtlaunch_<op_name>.h
  • use platform APIs to obtain coreNum and ubSize (do not hardcode)
  • bufferCoefficient is consistent with the UB allocation table in the design document
  • all parameters of EXEC_KERNEL_CMD are lvalues
  • [row processing] padding/unpadding is correct

op_kernel

  • include kernel_operator.h,BUFFER_NUM = 2
  • Init whole-core/tail-core offsets are correct, and InitBuffer sizes are consistent with the UB allocation table
  • Process tail tile alignment (alignedTailLen)
  • AllocTensor/FreeTensor are paired, EnQue/DeQue are paired
  • FP16/BF16 must be promoted to FP32 for computation
  • Back up the source data before ReduceSum

Framework adaptation

  • ops.h declarations match the op_host function signatures
  • register.cpp schema parameter types/default values are correct
  • csrc/CMakeLists.txt has the host and kernel source files added

Anti-pattern list

  • NEVER To let FP16/BF16 directly participate in complex mathematical calculations (Mul/Div/Exp/Tanh, etc.), you must first Cast to FP32
  • NEVER Pass rvalues (temporary objects, literals, expression results) in EXEC_KERNEL_CMD
  • NEVER Use the bool parameter type in a kernel; use int64_t instead
  • NEVER For GM↔UB transfers, use DataCopy; you must use DataCopyPad
  • NEVER Directly reuse the source tensor after ReduceSum/ReduceMax (the reduction may modify the source data)
  • NEVER Use in a kernel std::min/max/abs/sqrt/exp and other standard library functions
  • NEVER Pass repeatTime > 255 to a high-dimensional slicing API (uint8_t will silently truncate to 0)
  • NEVER Modify cmake/ or csrc/utils/ files under
  • NEVER Omit default values in the schema in register.cpp (e.g., int dim should be written as int dim=-1)
  • NEVER Hardcode the number of cores or UB size; it must be obtained through the platform API
  • NEVER Make the dst of ReduceMax/Sum point to the same memory block as tmpBuffer