For Beginners
Sample Function
This sample shows how to use the CANN Runtime API and the Add operator in the operator library to implement vector addition: out = self + alpha × other.
Input vectors: self: [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0] other: [0.5, 1.0, 1.5, 2.0, 2.5, 3.0, 3.5, 4.0] alpha: 1.0 Vector addition result: out: [1.5, 3.0, 4.5, 6.0, 7.5, 9.0, 10.5, 12.0]
App Build and Run
You can click here to obtain the complete sample code. The README.md file of the sample also provides building and running guidance.
Understanding the Key Code Logic
This section describes the logic of the sample code based on the app development sequence: Resource initialization → Stream creation → Input preparation → Add operator call → Result output → Resource release → Resource deinitialization. This section also helps you understand the functions of key APIs in CANN app development. These key APIs are prefixed with acl, which are referred to as acl APIs for short.
- Initialize resources.
1 2 3 4 5 6
// Initialize the system. nullptr indicates that the system is initialized using the default configuration. Before calling other acl APIs, you must initialize the system. Otherwise, errors may occur during the initialization of internal system resources, causing service exceptions. aclInit(nullptr); // Specify the compute device. int32_t deviceId = 0; aclrtSetDevice(deviceId);
- Create a stream.
1 2 3
// A stream is equivalent to a task queue. Tasks are executed in the order they enter the queue. aclrtStream stream = nullptr; aclrtCreateStream(&stream);
- Prepare the input.In the calculation formula of the Add operator (out = self + alpha × other), self and other are the two input vectors to be added, alpha is the coefficient, and out is the result vector after addition. Therefore, when preparing the input, you need to create two input tensors, a scalar representing the coefficient, and an output tensor. When creating input and output tensors, you need to allocate device memory to store the input or output data.
- Define a common API for creating tensors.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
template <typename T> int CreateAclTensor(const std::vector<T>& hostData, const std::vector<int64_t>& shape, void** deviceAddr, aclDataType dataType, aclTensor** tensor) { auto size = GetShapeSize(shape) * sizeof(T); // Allocate the device memory. aclrtMalloc(deviceAddr, size, ACL_MEM_MALLOC_HUGE_FIRST); // Synchronously copy the input data from the host to the device. aclrtMemcpy(*deviceAddr, size, hostData.data(), size, ACL_MEMCPY_HOST_TO_DEVICE); // Compute strides. std::vector<int64_t> strides(shape.size(), 1); for (int64_t i = shape.size() - 2; i >= 0; i--) { strides[i] = shape[i + 1] * strides[i + 1]; } // Create a tensor. *tensor = aclCreateTensor(shape.data(), shape.size(), dataType, strides.data(), 0, aclFormat::ACL_FORMAT_ND, shape.data(), shape.size(), *deviceAddr); return 0; }
- Create the input and output tensors and a coefficient scalar.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
std::vector<int64_t> shape{8}; // The vector length is 8. void* selfDeviceAddr = nullptr; void* otherDeviceAddr = nullptr; void* outDeviceAddr = nullptr; aclDataBuffer* outDataBuffer = nullptr; aclTensor* self = nullptr; aclTensor* other = nullptr; aclTensor* out = nullptr; aclScalar* alpha = nullptr; std::vector<float> selfHostData = {1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f, 8.0f}; // Input data of the self vector. std::vector<float> otherHostData = {0.5f, 1.0f, 1.5f, 2.0f, 2.5f, 3.0f, 3.5f, 4.0f}; // Input data of the other vector. std::vector<float> outHostData(8, 0.0f); float alphaValue = 1.0f; // Coefficient value // Create input tensors. CreateAclTensor(selfHostData, shape, &selfDeviceAddr, aclDataType::ACL_FLOAT, &self); CreateAclTensor(otherHostData, shape, &otherDeviceAddr, aclDataType::ACL_FLOAT, &other); // Create the alpha scalar. alpha = aclCreateScalar(&alphaValue, aclDataType::ACL_FLOAT); // Create the output tensor. CreateAclTensor(outHostData, shape, &outDeviceAddr, aclDataType::ACL_FLOAT, &out);
- Define a common API for creating tensors.
- Call the Add operator.
// Call the built-in operator of CANN. Generally, the two-phase APIs are called. // The first-phase API aclnnAddGetWorkspaceSize verifies input parameters, infers the output shape in dynamic shape scenarios, tiles data, and computes the workspace memory size required for operator execution. uint64_t workspaceSize = 0; aclOpExecutor* executor = nullptr; ret = aclnnAddGetWorkspaceSize(self, other, alpha, out, &workspaceSize, &executor); // The second-phase API aclnnAdd performs operator computation. This API involves DFX (such as dump and overflow/underflow detection) and calls the LaunchKernel API provided by Runtime. // workspaceAddr indicates address of the workspace to be allocated on the device. ret = aclnnAdd(workspaceAddr, workspaceSize, executor, stream); // Wait until the task is complete. aclrtSynchronizeStream(stream);
- Output the result.Send the result data on the device to the host.
1 2 3
// outBufferAddr indicates the address of the memory for storing the result data on the device. // resultData.data() indicates the address of the memory for storing data on the host. aclrtMemcpy(resultData.data(), resultData.size() * sizeof(float), outBufferAddr, size * sizeof(float), ACL_MEMCPY_DEVICE_TO_HOST);
- Release resources.
// Destroy the tensors and scalar. aclDestroyTensor(self); aclDestroyTensor(other); aclDestroyScalar(alpha); aclDestroyTensor(out); // Free the device memory. aclrtFree(selfDeviceAddr); aclrtFree(otherDeviceAddr); aclrtFree(outDeviceAddr); aclrtFree(workspaceAddr); // Destroy the stream. aclrtDestroyStream(stream);
- Deinitialize resources.
1 2 3 4
// Reset the device. aclrtResetDeviceForce(deviceId); // Perform deinitialization. aclFinalize();
Parent topic: Quick Start