Cross-Stream Capture
When a task in a stream is captured, the streams specified by aclmdlRICaptureBegin and aclmdlRICaptureEnd must be the same stream (main stream). To capture tasks across streams, you can call aclrtRecordEvent to deliver the Event Record task in the main stream and call aclrtStreamWaitEvent to deliver the Event Wait task in other streams to establish the association between the main stream and other streams. In this way, the tasks in the main stream and other streams can be captured to the same model. The event also enters the capture state. If there are other streams waiting for the event, the corresponding streams also enter the capture state. As shown in the following figure, stream 2 needs to wait for the completion of task 1 in the main stream, and stream 3 needs to wait for the completion of task 2 in stream 2. In this case, stream 2 directly depends on the main stream, and stream 3 indirectly depends on the main stream. Therefore, both stream 2 and stream 3 are in the capture state, and task 2 in stream 2 and task 3 in stream 3 are captured into the model.
Streams that are added to the capture state through events must be returned to the main stream through events directly or indirectly. Otherwise, an error is reported when the capture is ended. As shown in the following figure, you can call aclrtRecordEvent to deliver the Event Record task to stream 2 and stream 3, and call aclrtStreamWaitEvent to deliver the Event Wait task to the main stream, so that stream 2 and stream 3 can return to the main stream. In addition, for a stream (such as stream 3) that indirectly depends on the main stream, you can deliver the Event Record task to stream 3 and deliver the Event Wait task to stream 2 so that stream 3 returns to stream 2. Then deliver the Event Record task to stream 2 and the Event Wait task to the main stream so that the streams return to the main stream. After the streams return to the main stream and before the capture ends, do not deliver tasks (for example, task 5 in the following figure) to stream 2 and stream 3. Otherwise, an error is reported when the capture ends because of unassociated tasks.
The following figure shows the cross-stream task capture process.

The following example uses two streams to demonstrate cross-stream capture, where stream 1 is the main 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 146 147 148 149 150 151 152 153 154 |
#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 stream1, stream2; aclrtEvent event1, event2; aclrtCreateStream(&stream1); aclrtCreateStream(&stream2); aclrtCreateEvent(&event1); aclrtCreateEvent(&event2); // ====== ==Start the capture task.======== aclmdlRICaptureBegin(stream1, 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, stream1); // 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); // Add stream 2 to the capture state using event 1. aclrtRecordEvent(event1, stream1); aclrtStreamWaitEvent(stream2, event1); // Execute the aclnnAdd operator. aclnnAdd(workspaceAddr, workspaceSize, executor, stream2); // After the tasks in stream 2 are executed, use event 2 to return stream 2 to the main stream (stream 1). aclrtRecordEvent(event2, stream2); aclrtStreamWaitEvent(stream1, event2); // 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, stream1); // ====== ==End the capture task.======== aclmdlRICaptureEnd(stream1, &modelRI); // Execute the model multiple times. for (int i = 0; i < 8; i++) { aclmdlRIExecuteAsync(modelRI, stream1); aclrtSynchronizeStream(stream1); // 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(stream1); aclrtDestroyStream(stream2); aclrtDestroyEvent(event1); aclrtDestroyEvent(event2); 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(); } |