Single-Stream Capture
The following figure shows the basic process of capturing tasks to a model and then executing the model.

In the scenario where tasks are captured to a model before model execution, there are the following restrictions:
- Before a stream enters the capture state, tasks in the stream are still executed immediately.
- When tasks in the stream are captured, the tasks are offloaded to the device but not executed immediately. As a result, querying or synchronizing the stream or event is invalid. Similarly, the query or synchronization of the device or context is also invalid because the device and context contain the synchronization information of the stream. During the capture, the synchronization or query of streams, events, devices, and contexts is invalid in any capture mode.
-
During the capture, if ACL_MODEL_RI_CAPTURE_MODE_GLOBAL (all threads are not allowed to call non-secure functions) is used, the memory synchronization functions (such as aclrtMemset, aclrtMemcpy, and aclrtMemcpy2d) are invalid. If these functions are called, an error is reported and the capture fails. If the service side determines that the execution of these functions does not affect task capture, you can call aclmdlRICaptureThreadExchangeMode to switch the capture mode of the current thread to ACL_MODEL_RI_CAPTURE_MODE_RELAXED to remove the restriction.
- During the capture process, if configuration tasks such as profiling configuration, dump configuration, and overflow/underflow detection configuration are delivered, an error may be returned or the configuration may not take effect for the capture model.
- If the captured asynchronous memory copy task involves the host memory, only the acl API (for example, aclrtMallocHost) can be used to allocate the host page-locked memory. If the acl API is not used, an error will be reported during the capture.
- During the capture, operations on the default stream are also invalid.
- Note that after a task is captured, you need to ensure the validity of the resources used by the task in the model. The resources can be destroyed only after the model is destroyed.
The following is the sample code for capturing the computation of the add operator in a single stream:
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 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 |
#include <stdio.h> #include <vector> #include "acl/acl.h" #include "aclnnop/aclnn_add.h" #define ACL_LOG(fmt, args...) fprintf(stdout, "[INFO] " fmt "\n", ##args) int64_t GetShapeSize(const std::vector<int64_t> &shape) { int64_t shape_size = 1; for (auto i : shape) { shape_size *= i; } return shape_size; } int CreateAclTensor(const std::vector<int64_t> &shape, void **deviceAddr, aclDataType dataType, aclTensor **tensor) { auto size = GetShapeSize(shape) * sizeof(float); // Allocate the device memory. auto ret = aclrtMalloc(deviceAddr, size, ACL_MEM_MALLOC_HUGE_FIRST); // Compute the strides of the contiguous tensor. 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]; } // Call aclCreateTensor to create an aclTensor. *tensor = aclCreateTensor(shape.data(), shape.size(), dataType, strides.data(), 0, aclFormat::ACL_FORMAT_ND, shape.data(), shape.size(), *deviceAddr); return 0; } int main() { int devID = 0; void *self_d = nullptr; void *other_d = nullptr; void *out_d = nullptr; aclTensor *self = nullptr; aclTensor *other = nullptr; aclScalar *alpha = nullptr; aclTensor *out = nullptr; /* aclnnAdd: out = self + other * alpha */ float *self_h = nullptr; float *other_h = nullptr; std::vector<int64_t> shape = {4, 2}; float alphaValue = 1.1f; uint64_t workspaceSize = 0; aclOpExecutor *executor; auto size = GetShapeSize(shape); // Perform initialization. aclInit(NULL); // Specify the compute device. aclrtSetDevice(devID); // Prepare the input and output parameters of the aclnnAdd operator. CreateAclTensor(shape, &self_d, aclDataType::ACL_FLOAT, &self); CreateAclTensor(shape, &other_d, aclDataType::ACL_FLOAT, &other); alpha = aclCreateScalar(&alphaValue, aclDataType::ACL_FLOAT); CreateAclTensor(shape, &out_d, aclDataType::ACL_FLOAT, &out); // Obtain the workspace size required for operator computation and the executor that contains the operator computation process. aclnnAddGetWorkspaceSize(self, other, alpha, out, &workspaceSize, &executor); void *workspaceAddr = nullptr; if (workspaceSize > 0) { aclrtMalloc(&workspaceAddr, workspaceSize, ACL_MEM_MALLOC_HUGE_FIRST); } // Allocate page-locked memory by using aclrtMallocHost. aclrtMallocHost((void **)&self_h, size * sizeof(float)); aclrtMallocHost((void **)&other_h, size * sizeof(float)); for (int i = 0; i < 8; i++) { self_h[i] = static_cast<float>(0); other_h[i] = static_cast<float>(1); } aclmdlRI modelRI; aclrtStream stream; aclrtCreateStream(&stream); // ====== ==Start the capture task.======== aclmdlRICaptureBegin(stream, ACL_MODEL_RI_CAPTURE_MODE_GLOBAL); // Asynchronous copy, which is used to copy the input data of the operator's self parameter from the host to the device aclrtMemcpyAsync(self_d, size * sizeof(float), self_h, size * sizeof(float), ACL_MEMCPY_HOST_TO_DEVICE, stream); // Switch the capture mode to RELAXED to allow the calls to the aclrtMemcpy function. aclmdlRICaptureMode mode = ACL_MODEL_RI_CAPTURE_MODE_RELAXED; aclmdlRICaptureThreadExchangeMode(&mode); // Synchronous copy, which is used to copy the input data of the operator's other parameter from the host to the device. This operation is performed only once. aclrtMemcpy(other_d, size * sizeof(float), other_h, size * sizeof(float), ACL_MEMCPY_HOST_TO_DEVICE); // Switch the capture mode back to GLOBAL. aclmdlRICaptureThreadExchangeMode(&mode); // Execute the aclnnAdd operator. aclnnAdd(workspaceAddr, workspaceSize, executor, stream); // Asynchronous copy, which is used to copy the output data of the operator from the device to the host aclrtMemcpyAsync(self_h, size * sizeof(float), out_d, size * sizeof(float), ACL_MEMCPY_DEVICE_TO_HOST, stream); // ====== ==End the capture task.======== aclmdlRICaptureEnd(stream, &modelRI); // Print model information, which is used in the maintenance and debugging scenario. const char *jsonPath = "./modelRI.json"; aclmdlRIDebugJsonPrint(modelRI, jsonPath, 0); // Execute the model multiple times. for (int i = 0; i < 8; i++) { aclmdlRIExecuteAsync(modelRI, stream); aclrtSynchronizeStream(stream); // Print each output of the operator. ACL_LOG("%f %f %f %f %f %f %f %f\n", self_h[0], self_h[1], self_h[2], self_h[3], self_h[4], self_h[5], self_h[6], self_h[7]); } // Free resources. aclmdlRIDestroy(modelRI); aclrtDestroyStream(stream); aclDestroyTensor(self); aclDestroyTensor(other); aclDestroyTensor(out); aclDestroyScalar(alpha); aclrtFree(self_d); aclrtFree(other_d); aclrtFree(out_d); if (workspaceAddr != nullptr) { aclrtFree(workspaceAddr); } // Free the resources of the compute device. aclrtResetDevice(devID); // Perform deinitialization. aclFinalize(); } |