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();
}