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:

  1. Before a stream enters the capture state, tasks in the stream are still executed immediately.
  2. 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.
  3. 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.

  4. 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.
  5. 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.
  6. During the capture, operations on the default stream are also invalid.
  7. 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();
}