This document guides you through the following tasks, so you can experience the basic process of Ascend C SIMT operator development.
Before development, you need to set up the environment. The Ascend C operator development process is illustrated in the following figure.

Before developing an operator, set up the development environment and operating environment. For details about the development environment and operating environment and how to install them, see CANN Software Installation.
After the CANN software is installed, log in to the environment as the CANN operating user and run the source ${INSTALL_DIR}/set_env.sh command to set environment variables. Replace ${INSTALL_DIR} with the CANN component directory. For example, if the installation is performed by the root user, the default file storage path is /usr/local/Ascend/cann.
Analyze the mathematical expression, number of inputs and outputs, shape range, and computation logic implementation of an operator, and specify the Ascend C SIMT APIs or operators to be called. The following uses the Add operator as an example to describe the analysis process.
The Add operator is used as an example.

The computation logic is as follows: Add inputs x and y at the corresponding positions in the global memory element-wise, and store the result in the global memory output z.
Name |
Shape |
Data Type |
Format |
|---|---|---|---|
x (input) |
48 × 256 |
float* |
ND |
y (input) |
48 × 256 |
float* |
ND |
z (output) |
48 × 256 |
float* |
ND |
total_length |
- |
uint64_t |
- |
1 | int32_t idx = blockIdx.x * blockDim.x + threadIdx.x; |
1 | z[idx] = x[idx] + y[idx]; |
The complete kernel function code is as follows:
1 2 3 4 5 6 7 8 9 10 | __global__ void add_custom(float* x, float* y, float* z, uint64_t total_length) { // Calculate global thread ID int32_t idx = blockIdx.x * blockDim.x + threadIdx.x; // Maps to the row index of output tensor if (idx >= total_length) { return; } z[idx] = x[idx] + y[idx]; } |
After the kernel function is developed on the kernel, you can compile the kernel function calling program on the host, implementing the function of calling operators from the application program on the host to execute the computation process.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | // Header file required for host calling #include <vector> #include "acl/acl.h" // Kernel function development __global__ void add_custom(float* x, float* y, float* z, uint64_t total_length) { ... } // Call the operator using the kernel launch symbol <<<...>>>. std::vector<float> add(std::vector<float>& x, std::vector<float>& y) { ... // Calc splite params uint32_t block_num = 48; uint32_t thread_num_per_block = 256; uint32_t dyn_ubuf_size = 0; // No need to alloc dynamic memory. // Call kernel function with <<<...>>> add_custom<<<block_num, thread_num_per_block, dyn_ubuf_size, stream>>>(x_device, y_device, z_device, x.size()); ... return output; } // Compare the computation result. uint32_t verify_result(std::vector<float>& output, std::vector<float>& golden) { if (std::equal(output.begin(), output.end(), golden.begin())) { std::cout << "[Success] Case accuracy is verification passed." << std::endl; return 0; } else { std::cout << "[Failed] Case accuracy is verification failed!" << std::endl; return 1; } return 0; } // Verify the operator main program. int32_t main(int32_t argc, char* argv[]) { constexpr uint32_t in_shape = 48 * 256; std::vector<float> x(in_shape); std::vector<float> y(in_shape); std::vector<float> golden(in_shape); ... std::vector<float> output = add(x, y); return verify_result(output, golden); } |

For details about how to use the acl APIs in the following example, see Runtime API Reference.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | std::vector<float> add(std::vector<float>& x, std::vector<float>& y) { size_t total_byte_size =x.size() * sizeof(float); int32_t device_id = 0; aclrtStream stream = nullptr; uint8_t* x_host = reinterpret_cast<uint8_t *>(x.data()); uint8_t* y_host = reinterpret_cast<uint8_t *>(y.data()); uint8_t* z_host = nullptr; float* x_device = nullptr; float* y_device = nullptr; float* z_device = nullptr; // Init aclInit(nullptr); aclrtSetDevice(device_id); aclrtCreateStream(&stream); // Malloc memory in host and device aclrtMallocHost((void **)(&z_host), total_byte_size); aclrtMalloc((void **)&x_device, total_byte_size, ACL_MEM_MALLOC_HUGE_FIRST); aclrtMalloc((void **)&y_device, total_byte_size, ACL_MEM_MALLOC_HUGE_FIRST); aclrtMalloc((void **)&z_device, total_byte_size, ACL_MEM_MALLOC_HUGE_FIRST); aclrtMemcpy(x_device, total_byte_size, x_host, total_byte_size, ACL_MEMCPY_HOST_TO_DEVICE); aclrtMemcpy(y_device, total_byte_size, y_host, total_byte_size, ACL_MEMCPY_HOST_TO_DEVICE); // Calc splite params uint32_t block_num = 48; uint32_t thread_num_per_block = 256; uint32_t dyn_ubuf_size = 0; // No need to alloc dynamic memory. // Call kernel function with <<<...>>> add_custom<<<block_num, thread_num_per_block, dyn_ubuf_size, stream>>>(x_device, y_device, z_device, x.size()); aclrtSynchronizeStream(stream); // Copy result from device to host aclrtMemcpy(z_host, total_byte_size, z_device, total_byte_size, ACL_MEMCPY_DEVICE_TO_HOST); std::vector<float> output((float *)z_host, (float *)(z_host + total_byte_size)); // Free memory aclrtFree(x_device); aclrtFree(y_device); aclrtFree(z_device); aclrtFreeHost(z_host); // DeInt aclrtDestroyStream(stream); aclrtResetDevice(device_id); aclFinalize(); return output; } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | cmake_minimum_required(VERSION 3.16) # find_package(ASC) is a command used in CMake to search for and configure the Ascend C compilation toolchain. find_package(ASC REQUIRED) # Specify that the project supports the ASC and CXX languages. ASC indicates that the Ascend C programming language can be compiled using the BiSheng compiler. project(kernel_samples LANGUAGES ASC CXX) add_executable(demo add.asc ) # Set the NPU architecture through the compilation option. target_compile_options(demo PRIVATE $<$<COMPILE_LANGUAGE:ASC>:--npu-arch=dav-3510 --enable-simt> ) |
1 2 3 | mkdir -p build && cd build; cmake ..;make -j; ./demo |
To learn more about SIMT programming concepts, you can refer to AI Core SIMT Programming to learn the basic concepts and then review this tutorial. If you already understand related concepts and can run the sample through, refer to SIMT Operator Implementation for more details about Ascend C SIMT programming.