Asynchronous Model Inference

This section describes how to use the asynchronous inference API together with the callback function to deliver a callback task at a specified interval and obtain the asynchronous inference result of the previous period.

API Call Sequence

If synchronization in the asynchronous scenario is involved, ensure that your app contains the related code logic. For details about the API call sequence, see the following figure.

Figure 1 Synchronization flowchart in the callback scenario

The key APIs are described as follows:

  1. Create a callback function in advance to obtain and process the model inference or operator execution result.
  2. The user needs to create a thread in advance and customize a thread function. Call aclrtProcessReport in the thread function, set the timeout interval, and wait for the callback task delivered by aclrtLaunchCallback to be executed.
  3. Call aclrtSubscribeReport to specify the thread for processing the callback function in the stream. The thread must be the same as that created in 2.
  4. Call aclmdlExecuteAsync for asynchronous inference.

    For asynchronous APIs, call aclrtSynchronizeStream to block application running until all tasks in the specified stream are complete.

    You can obtain the asynchronous inference results of all images at a time after calling aclrtSynchronizeStream. However, if the image data volume is large, you need to wait for a long time. In this case, you can use the callback function to deliver a callback task at a specified interval, obtains the asynchronous inference result in the previous period.

  5. Call the aclrtLaunchCallback API to deliver a callback task in the task queue of the stream. When the callback task is executed in the system, the callback function is executed in the thread registered with the stream (the thread registered through the aclrtSubscribeReport API). The callback function must be the same as that in 1.

    Each time the aclrtLaunchCallback API is called, the callback function is triggered.

  6. Call aclrtUnSubscribeReport to unsubscribe from a thread. (The callback function in the stream is no longer processed by the specified thread.)

Sample Code

The following is a code example of key steps of asynchronous model inference. It is for reference only and cannot be directly copied, compiled, and run. After APIs are called, you need to add exception handling branches and record error logs and info logs.

You can click resnet50_async_imagenet_classification to obtain the sample.

  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
#include "acl/acl.h"
......

// Obtain the run mode of the AI software stack. The API calls for memory allocation and memory copy are different in different run modes.
aclrtRunMode runMode;
extern bool g_isDevice;
aclrtGetRunMode(&runMode);
g_isDevice = (runMode == ACL_DEVICE);

// 1. Apply for model inference resources.

// The two dots (..) indicate a path relative to the directory of the executable file.
// For example, if the executable file is stored in the out directory, the two dots (..) point to the parent directory of the out directory.
const char* omModelPath = "../model/resnet50.om";

// 1.1 Load the model.
// Obtain the weight memory size and workspace size required for model execution, and allocate memory as required.
aclmdlQuerySize(omModelPath, &modelMemSize_, &modelWeightSize_);
aclrtMalloc(&modelMemPtr_, modelMemSize_, ACL_MEM_MALLOC_HUGE_FIRST);
aclrtMalloc(&modelWeightPtr_, modelWeightSize_, ACL_MEM_MALLOC_HUGE_FIRST);

// Load the offline model file. After the model is successfully loaded, the model ID is returned.
aclmdlLoadFromFileWithMem(modelPath, &modelId_, modelMemPtr_,
        modelMemSize_, modelWeightPtr_, modelWeightSize_);

// 1.2 Obtain the model description based on the model ID.
modelDesc_ = aclmdlCreateDesc();
aclmdlGetDesc(modelDesc_, modelId_);

// 1.3 Use the custom function InitMemPool to initialize the memory pool and store the input and output data for model inference.
// -----Key implementation of the InitMemPool function.-----
string testFile[] = {
        "../data/dog1_1024_683.bin",
        "../data/dog2_1024_683.bin"
    };
size_t fileNum = sizeof(testFile) / sizeof(testFile[0]);
// g_memoryPoolSize indicates the number of memory blocks in the memory pool. The default value is 100.
for (size_t i = 0; i < g_memoryPoolSize; ++i) {
        size_t index = i % (sizeof(testFile) / sizeof(testFile[0]));
        // model process
        uint32_t devBufferSize;
        // Customize the GetDeviceBufferOfFile function to implement the following functions:
        // Obtain the buffer for storing the input image data together with the buffer size, and transfer the image data to the device.
        void *picDevBuffer = Utils::GetDeviceBufferOfFile(testFile[index], devBufferSize);
        aclmdlDataset *input = nullptr;
// Customize the CreateInput function to create data input of type aclmdlDataset for storing the input data of model inference.
        Result ret = CreateInput(picDevBuffer, devBufferSize, input);
        aclmdlDataset *output = nullptr;
        // Customize the CreateOutput function to create data output of type aclmdlDataset for storing the output data of model inference. modelDesc indicates the model description.
        CreateOutput(output, modelDesc);
        {
            std::lock_guard<std::recursive_mutex> lk(freePoolMutex_);
            freeMemoryPool_[input] = output;
        }
}
// -----Key implementation of the InitMemPool function.-----

// 2 Model inference
// 2.1 Create a thread tid and specify the thread with the tid as the thread for processing the callback function in the stream.
// ProcessCallback is a thread function. Call aclrtProcessReport in the thread function, and the callback function is triggered after a specified period of time.
pthread_t tid;
(void)pthread_create(&tid, nullptr, ProcessCallback, &s_isExit);

// 2.2 Specify the thread for processing the callback function in the stream.
aclrtSubscribeReport(tid, stream_);

// 2.3 Create a callback function for processing the model inference result. The callback function is user-defined.
void ModelProcess::CallBackFunc(void *arg)
{
    std::map<aclmdlDataset *, aclmdlDataset *> *dataMap =
        (std::map<aclmdlDataset *, aclmdlDataset *> *)arg;

    aclmdlDataset *input = nullptr;
    aclmdlDataset *output = nullptr;
    MemoryPool *memPool = MemoryPool::Instance();

    for (auto& data : *dataMap) {
        ModelProcess::OutputModelResult(data.second);
        memPool->FreeMemory(data.first, data.second);
    }

    delete dataMap;
}

// 2.4 Customize the ExecuteAsync function to perform model inference.
//-----Start of the key implementation of the user-defined ExecuteAsync function.-----
// g_callbackInterval indicates the callback interval. The default value is 1, indicating that a callback task is delivered every asynchronous inference.
bool isCallback = (g_callbackInterval != 0);
size_t callbackCnt = 0;
std::map<aclmdlDataset *, aclmdlDataset *> *dataMap = nullptr;
aclmdlDataset *input = nullptr;
aclmdlDataset *output = nullptr;
MemoryPool *memPool = MemoryPool::Instance();
    // g_executeTimes indicates the number asynchronous model inferences to run. The default value is 100.
for (uint32_t cnt = 0; cnt < g_executeTimes; ++cnt) {
    if (memPool->mallocMemory(input, output) != SUCCESS) {
        ERROR_LOG("get free memory failed");
        return FAILED;
    }
        // Perform asynchronous inference.
    aclError ret = aclmdlExecuteAsync(modelId_, input, output, stream_);

    if (isCallback) {
        if (dataMap == nullptr) {
            dataMap = new std::map<aclmdlDataset *, aclmdlDataset *>;
            if (dataMap == nullptr) {
                ERROR_LOG("malloc list failed, modelId is %u", modelId_);
                memPool->FreeMemory(input, output);
                return FAILED;
            }
        }
        (*dataMap)[input] = output;
        callbackCnt++;
        if ((callbackCnt % g_callbackInterval) == 0) {
                // Add a callback function to be executed to the stream.
            ret = aclrtLaunchCallback(CallBackFunc, (void *)dataMap, ACL_CALLBACK_BLOCK, stream_);
            if (ret != ACL_SUCCESS) {
                ERROR_LOG("launch callback failed, index=%zu", callbackCnt);
                memPool->FreeMemory(input, output);
                delete dataMap;
                return FAILED;
            }
            dataMap = nullptr;
        }

    }
}
//-----End of the key implementation of the user-defined ExecuteAsync function.-----

// 2.5 For asynchronous inference, the application needs to be blocked until all tasks in the specified stream are complete.
aclrtSynchronizeStream(stream_);

// 2.6 Unregister a thread so that the callback function in the stream is no longer processed by the specified thread.
aclrtUnSubscribeReport(static_cast<uint64_t>(tid), stream_);
s_isExit = true;
(void)pthread_join(tid, nullptr);

......