Single-operator API call refers to directly calling the single-operator API and executing the operator based on C language API. After an operator project is created, based on the project code framework, define the operator prototype, implement the operator on the kernel and tiling on the host, compile and deploy the operator through the project build script, and then call single-operator APIs.
After the custom operator is compiled and deployed, the single-operator API is automatically generated and can be directly called in an application.
Generally, the single-operator API is defined as a two-phase API. See the following example:
1 2 |
aclnnStatus aclnnXxxGetWorkspaceSize(const aclTensor *src, ..., aclTensor *out, uint64_t *workspaceSize, aclOpExecutor **executor); aclnnStatus aclnnXxx(void *workspace, uint64_t workspaceSize, aclOpExecutor *executor, aclrtStream stream); |
aclnnXxxGetWorkspaceSize/aclnnXxxTensorGetWorkspaceSize is the first-phase API, which is used to compute the workspace size required for the API calling. After obtaining the workspace size, allocate the NPU memory based on the workspace size, and then call the second-phase API aclnnXxx to perform computation. Xxx indicates the operator type passed during operator prototype registration.
The rules for generating input and output parameters of the aclnnXxxGetWorkspaceSize API are as follows:
1
|
aclnnStatus aclnnXxxGetWorkspaceSize(const aclTensor *xOptional, ..., aclTensor *out, uint64_t *workspaceSize, aclOpExecutor **executor); |
1
|
aclnnStatus aclnnXxxGetWorkspaceSize(aclTensor *xRef, ..., uint64_t *workspaceSize, aclOpExecutor **executor); |
1 2 3 4 |
// There is only one output. aclnnStatus aclnnXxxGetWorkspaceSize(const aclTensor *src, ..., aclTensor *out, uint64_t *workspaceSize, aclOpExecutor **executor); // There are multiple outputs. aclnnStatus aclnnXxxGetWorkspaceSize(const aclTensor *src, ..., aclTensor *yOut, aclTensor *y1Out, ..., uint64_t *workspaceSize, aclOpExecutor **executor); |
aclnnStatus aclnnXxxGetWorkspaceSize(const aclTensor *x, double negativeSlope, aclTensor *out, uint64_t *workspaceSize, aclOpExecutor **executor);
When the ValueDepend API is used to mark an input as data-dependent during operator prototype registration, an additional API is generated. This API supports one-phase computation in value-dependency scenarios where the input data is empty.
1
|
aclnnStatus aclnnXxxTensorGetWorkspaceSize(const aclTensor *src, ..., aclTensor *out, uint64_t *workspaceSize, aclOpExecutor **executor); |
1 2 |
aclnnStatus aclnnXxxGetWorkspaceSize(const aclIntArray *x0, const aclBoolArray *x1, const aclFloatArray *x2, aclTensor *out, uint64_t *workspaceSize, aclOpExecutor **executor); aclnnStatus aclnnXxxTensorGetWorkspaceSize(const aclTensor *x0, const aclTensor *x1, const aclTensor *x2, aclTensor *out, uint64_t *workspaceSize, aclOpExecutor **executor); |
"ENABLE_BINARY_PACKAGE": {
"type": "BOOL",
"value": "True"
},
After the operator is compiled and deployed, the header file aclnn_xx.h and dynamic library libcust_opapi.so for calling a single-operator are generated in the op_api directory under the operator package installation directory.
├── opp // Operator library directory │ ├── vendors // Directory of custom operators │ ├── config.ini │ └── vendor_name1 // Custom operator deployed by the storage vendor. vendor_name is configured during the build of the custom operator installation package. If vendor_name is not configured, the default value customize is used. │ ├── op_api │ │ ├── include │ │ │ └── aclnn_xx.h │ │ └── lib │ │ └── libcust_opapi.so ...
├── CMakeLists.txt // Build script ├── main.cpp // Main process implementation file for single-operator call
The calling process of the single-operator API is as follows.

This section uses the AddCustom operator as an example to describe how to compile the code logic for single-operator call. Other operators are called in a similar way to the logic AND Add operator. Modify the code based on your actual need.
The following is a code snippet of key steps only, which is not ready to be built or run. After APIs are called, you need to add exception handling branches and record error logs and info logs.
In single-operator API execution mode, .cpp and .h files are automatically generated in the build_out/autogen directory of the build project. When compiling the calling code of the single-operator, ensure that the automatically generated header file for single-operator API execution is included. The following is an example:
1
|
#include "aclnn_add_custom.h" |
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 |
// 1. Perform initialization. CHECK_ACL(aclnnInit(nullptr)); // 2. Allocate runtime resources. const int32_t deviceId = 0; CHECK_ACL(aclrtSetDevice(deviceId)); // 3. Allocate memory to store the input and output of the operator. // ...... // 4. Transmit data. CHECK_ACL(aclrtMemcpy(input0DeviceMem, bufferSize, input0HostData.data(), bufferSize, ACL_MEMCPY_HOST_TO_DEVICE)); CHECK_ACL(aclrtMemcpy(input1DeviceMem, bufferSize, input1HostData.data(), bufferSize, ACL_MEMCPY_HOST_TO_DEVICE)); // 5. Compute the workspace size and allocate memory. uint64_t workspaceSize = 0; aclOpExecutor* executor = nullptr; CHECK_ACL(aclnnAddCustomGetWorkspaceSize(input0, input1, output0, &workspaceSize, &executor)); void* workspaceDeviceMem = nullptr; if (workspaceSize > 0) { CHECK_ACL(aclrtMalloc(&workspaceDeviceMem, workspaceSize, ACL_MEM_MALLOC_HUGE_FIRST)); } // 6. Execute the single-operator. CHECK_ACL(aclnnAddCustom(workspaceDeviceMem, workspaceSize, executor, stream)); // 7. Synchronization. CHECK_ACL(aclrtSynchronizeStream(stream)); // 8. Process the output data after the operator is executed, for example, printing data to the screen or writing the data to a file. You can implement this function as required. // ...... // 9. Destroy runtime allocations. CHECK_ACL(aclrtResetDevice(deviceId)); // .... // 10. Perform deinitialization. CHECK_ACL(aclnnFinalize()); |
After the operator is compiled, the aclnn_xx.h file and dynamic library libcust_opapi.so for single-operator calling are generated. For details about the path, see Pre-operations.
When compiling the operator calling program, add the header file directory for single-operator calling to the search path include_directories of the header file so that the header file can be found. In addition, you need to link the cust_opapi dynamic library and add the directory where libcust_opapi.so is located to the search path link_directories of the library file.
target_include_directories(execute_add_op PRIVATE
$ENV{ASCEND_HOME_PATH}/include
$ENV{ASCEND_OPP_PATH}/vendors/customize/op_api/include
)target_link_libraries(execute_add_op PRIVATE
cust_opapi
nnopbase
acl_rt
)target_link_directories(execute_add_op PRIVATE
$ENV{ASCEND_HOME_PATH}/lib64
$ENV{ASCEND_OPP_PATH}/vendors/customize/op_api/lib
)export DDK_PATH=${INSTALL_DIR} export NPU_HOST_LIB=${INSTALL_DIR}/{arch-os}/devlib
mkdir -p build
Command example:
cd build cmake ../src -DCMAKE_SKIP_RPATH=TRUE
make
The executable file execute_add_op is generated in the output directory of the project.
Note: If your development environment is the operating environment, skip this step.
chmod +x execute_add_op ./execute_add_op
If "test pass" is displayed, the execution is successful.