Running a Model

This section describes the API call sequence of network-wide model execution. This section describes the data to be prepared before model execution, model execution APIs, and resources to be deallocated after model execution cased on the API call sequence and sample code.

Principles

Before executing a model after it is loaded, prepare the input and output data structures, and upload the input data to the buffer corresponding to the model's input data structure.

After model execution is complete, free the buffer and destroy allocations (including the input data, aclmdlDesc type, aclmdlDataset type, and aclDataBuffer type) in a timely manner to avoid buffer exceptions. A model may have multiple inputs and outputs. The memory address and memory size of each input/output are described by data of the aclDataBuffer type. For each input/output, you need to call aclrtFree to release the data in the memory, call aclDestroyDataBuffer to destroy the corresponding aclDataBuffer type.

Model Execution

Figure 1 Typical model inference workflow

The key APIs are described as follows:

  1. Call aclmdlCreateDesc to create data types that describe the basic information of a model.
  2. Call the aclmdlGetDesc API to obtain basic model information based on the model ID returned by Loading a Model.
  3. Prepare the input and output data structures for model execution. For details, see Preparing Input/Output Data Structure for Model Execution.

    If the model input involves features such as dynamic batch size, dynamic image size, dynamic AIPP, and dynamic dimensions (ND format only), see Model Inference with Dynamic-Shape Inputs and Dynamic AIPP Model Inference.

  4. Run model inference.

    In static batch size (greater than 1) scenarios, the input data is fed to your model for inference only when the input data reaches the given batch size. Design the processing logic for the remainder data that is less than the batch size as needed.

    Currently, synchronous model inference and asynchronous model inference are supported.

    • Synchronous inference

      Call aclmdlExecute to perform synchronous inference.

    • Asynchronous inference

      Call aclmdlExecuteAsync to perform asynchronous inference.

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

      For details about asynchronous inference, see Asynchronous Model Inference.

  5. Obtain the results of model inference for subsequent use.
    • For synchronous inference, obtain the output data of model inference directly.
    • For asynchronous inference with callback, obtain the model inference result from the callback function for subsequent use.
  6. Free the buffer.

    aclrtFree: frees device memory.

  7. Destroy data of specific types.

    After model inference is complete, call aclDestroyDataBuffer and aclmdlDestroyDataset in sequence to release the data that describes the model input and output data types in a timely manner. If there are multiple inputs and outputs, aclDestroyDataBuffer needs to be called for multiple times.

Preparing Input/Output Data Structure for Model Execution

The following data types are provided to describe a model, model inputs and outputs, and data buffers, as the input parameters of the model execution call:

  • Use data of the aclmdlDesc type to describe the basic information of your model (such as the input/output count, and the name, data type, format, and shape of each input/output).

    After a model is successfully loaded, you can call the aclmdlGetDesc API to obtain the model description based on the model ID, and then obtain the model input/output count, memory size, dimensions, format, and data type from the model description, for details, see the operation APIs of the aclmdlDesc type.

  • Use data of the aclmdlDataset type to describe the input/output data of your model. Note that a model might have more than one input and more than one output.

    Call the operation APIs under aclmdlDataset to add data of type aclDataBuffer and obtain the number of aclDataBuffers.

  • Use data of the aclDataBuffer type to describe the buffer address and buffer size of each input/output.
    Call the operation APIs under aclDataBuffer to obtain the buffer address and buffer size of each input/output.
    Figure 2 Relationship between aclmdlDataset and aclDataBuffer

After learning related data types, you can use the operation APIs for these data types to prepare the input and output data structures of the model, as shown in the following figure.

Figure 3 Input and output data structure preparation workflow

The key points are described as follows:

  • If a model has multiple inputs and outputs, you can call the aclmdlGetNumInputs and aclmdlGetNumOutputs APIs to obtain the number of inputs and outputs.
  • Call aclmdlGetInputSizeByIndex and aclmdlGetOutputSizeByIndex to obtain the buffer size required by each input and output of the model.
  • If a model has multiple inputs and outputs, you can call aclmdlGetInputNameByIndex and aclmdlGetOutputNameByIndex to obtain the input and output names before adding aclDataBuffer to aclmdlDataset to avoid sequence errors, add indexes based on the sequence of the indexes corresponding to the input and output names.

Sample Code

This sample processes the outputs of the image classification model by printing the class indexes of the top 5 confidence values of each image. You can customize your own output processing logic.

The following is a code snippet of key steps only, which is not ready to be built or run. After APIs are called, you need to add exception handling branches and record error logs and info logs.

You can click resnet50_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
// 1. Obtain the model description based on the model ID.
// modelDesc_ is the aclmdlDesc type.
modelDesc_ = aclmdlCreateDesc();
aclError ret = aclmdlGetDesc(modelDesc_, modelId_);

// 2. Prepare the input data structure for model inference.
// (1) Allocate input buffer.
size_t modelInputSize;
void *modelInputBuffer = nullptr;
// The model in this sample code has only one input, which is naturally indexed 0. If the model has multiple inputs, call the aclmdlGetNumInputs API to obtain the actual input count.
modelInputSize = aclmdlGetInputSizeByIndex(modelDesc_, 0);
ret = aclrtMalloc(&modelInputBuffer, modelInputSize, ACL_MEM_MALLOC_HUGE_FIRST);

// (2) Prepare the input data structure of the model.
// Create data of the aclmdlDataset type to describe the inputs for model inference. input_ is of the aclmdlDataset type.
input_ = aclmdlCreateDataset();
aclDataBuffer *inputData = aclCreateDataBuffer(modelInputBuffer, modelInputSize);
ret = aclmdlAddDatasetBuffer(input_, inputData);

// 3. Prepare the output data structure for model inference.
// (1) Create data of the aclmdlDataset type to describe the outputs of model inference. output_ is of the aclmdlDataset type.
output_ = aclmdlCreateDataset();

// (2) Obtain the number of model outputs.
size_t outputSize = aclmdlGetNumOutputs(modelDesc_);

// (3) Allocate buffer for each output with a for loop and add each output to aclmdlDataset.
for (size_t i = 0; i < outputSize; ++i) {
    size_t buffer_size = aclmdlGetOutputSizeByIndex(modelDesc_, i);
    void *outputBuffer = nullptr;
    ret = aclrtMalloc(&outputBuffer, buffer_size, ACL_MEM_MALLOC_HUGE_FIRST);
    aclDataBuffer* outputData = aclCreateDataBuffer(outputBuffer, buffer_size);   
    ret = aclmdlAddDatasetBuffer(output_, outputData);
    }

// 4. Run the model.
string testFile[] = {
        "../data/dog1_1024_683.bin",
        "../data/dog2_1024_683.bin"
    };

for (size_t index = 0; index < sizeof(testFile) / sizeof(testFile[0]); ++index) {
    // 4.1 Define the ReadBinFile function, and call the functions in the C++ standard library std::ifstream to read the image files to obtain the buffer size (inputBuffSize) and buffer address (inputBuff) of the image files.
    void *inputBuff = nullptr;
    uint32_t inputBuffSize = 0;
    auto ret1 = Utils::ReadBinFile(fileName, inputBuff, inputBuffSize);
    
    // 4.2. Prepare the input data for model inference.
    //Call aclrtGetRunMode to obtain the run mode of the software stack before you allocate runtime resources.
    // If the run mode is ACL_DEVICE, the value of g_isDevice is True, which indicates that the software stack runs on the device. Therefore, image data transfer is not required. You need to transfer data on the device. Otherwise, the memory copy API needs to be called to transfer data to the device.
    if (!g_isDevice) {
        // if app is running in host, need copy data from host to device
        // modelInputBuffer and modelInputSize indicate the buffer address and buffer size of the input data for model inference, respectively. The buffer is allocated when the input/output data structure is prepared.
        ret = aclrtMemcpy(modelInputBuffer, modelInputSize, inputBuff, inputBuffSize, ACL_MEMCPY_HOST_TO_DEVICE);
        (void)aclrtFreeHost(inputBuff);
    } else { // app is running in device
        ret = aclrtMemcpy(modelInputBuffer, modelInputSize, inputBuff, inputBuffSize, ACL_MEMCPY_DEVICE_TO_DEVICE);
        (void)aclrtFree(inputBuff);
    }

    // 4.3 Run model inference.
    // modelId_ indicates the model ID. After the model is successfully loaded, the model ID is returned.
    // input_ and output_ indicate the input data and output data of model inference, respectively. They are defined when the input and output data structures of model inference are prepared.
    ret = aclmdlExecute(modelId_, input_, output_);
        

    // Process the model inference output and print the class indexes corresponding to the top 5 confidence values.
    // output_ indicates the output of model execution.
    for (size_t i = 0; i < aclmdlGetDatasetNumBuffers(output_); ++i) {
    // Obtain the buffer address and buffer size of each output.
        aclDataBuffer* dataBuffer = aclmdlGetDatasetBuffer(output_, i);
        void* data = aclGetDataBufferAddr(dataBuffer);

        size_t len = aclGetDataBufferSizeV2(dataBuffer);

        // Cast the buffered data to the float type.
        float *outData = NULL;
        outData = reinterpret_cast<float*>(data);
        
        // Print the class indexes of top 5 confidence values.
        map<float, int, greater<float> > resultMap;
        for (int j = 0; j < len / sizeof(float); ++j) {
            resultMap[*outData] = j;
            outData++;
        }
        int cnt = 0;
        for (auto it = resultMap.begin(); it != resultMap.end(); ++it) {
            // print top 5
            if (++cnt > 5) {
                break;
            }

            INFO_LOG("top %d: index[%d] value[%lf]", cnt, it->second, it->first);
        }
    }
}

// 5. Destroy the input and output allocations for model inference.
// Destroy the input data structures and free the buffer.
for (size_t i = 0; i < aclmdlGetDatasetNumBuffers(input_); ++i) {
        aclDataBuffer *dataBuffer = aclmdlGetDatasetBuffer(input_, i);
        (void)aclDestroyDataBuffer(dataBuffer);
}
(void)aclmdlDestroyDataset(input_);
input_ = nullptr;
aclrtFree(modelInputBuffer);

// Destroy the output data structures and free the buffer.
for (size_t i = 0; i < aclmdlGetDatasetNumBuffers(output_); ++i) {
    aclDataBuffer* dataBuffer = aclmdlGetDatasetBuffer(output_, i);
    void* data = aclGetDataBufferAddr(dataBuffer);
    (void)aclrtFree(data);
    (void)aclDestroyDataBuffer(dataBuffer);
}

(void)aclmdlDestroyDataset(output_);
output_ = nullptr;

When building a model, if batchSize is greater than or equal to 2 (specified by the input_shape parameter of ATC), you need to compile a code snippet before inference. The implementation logic is as follows: Allocate the buffer on the device to store the data as the input of model inference when the input data meets the batchSize requirement (for example, batchSize=8). The remaining data that is smaller than the batch size will be directly fed to the model for inference.

In this example, batchSize is set to 8.
 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
uint32_t batchSize = 8;
uint32_t deviceNum = 1;
uint32_t deviceId = 0;

//Obtain the size of the first input of the model.
uint32_t modelInputSize = aclmdlGetInputSizeByIndex(modelDesc, 0);
//Obtain the buffer size per batch.
uint32_t singleBuffSize = modelInputSize / batchSize;

//Define a variable to accumulate the input data to the batch size (8).
uint32_t cnt = 0;
//Define a variable to describe the offset for loading each file to memory.
uint32_t pos = 0;

void* p_batchDst = NULL;
std::vector<std::string>inferFile_vec;

for (int i = 0; i < files.size(); ++i) 
        {
           //Allocate buffer on the device every eight input files (batchSize = 8).
            if (cnt % batchSize == 0)
            {
                pos = 0;
                inferFile_vec.clear();
                //Allocate device buffer.
                aclrtMalloc(&p_batchDst, modelInputSize, ACL_MEM_MALLOC_HUGE_FIRST);
            }

            //TODO: Load a file from a directory and calculate the file size fileSize.
            
             //Allocate buffer to store file data based on the file size.
            aclrtMallocHost(&p_imgBuf, fileSize);

            //Transfer data to the device buffer.
            aclrtMemcpy((uint8_t *)p_batchDst + pos, fileSize, p_imgBuf, fileSize, ACL_MEMCPY_HOST_TO_DEVICE);
            pos += fileSize;
            //Free unused memory in a timely manner.
            aclrtFreeHost(p_imgBuf);

            //Save the ith file to the vector and increase the value of cnt by 1.
            inferFile_vec.push_back(files[i]);
            cnt++;

            // Send the input data (batchSize = 8) for model inference.
            if (cnt % batchSize == 0)
            {
               //TODO: Create data of type aclmdlDataset and aclDataBuffer to describe the input and output data of the model.
               //TODO: Call aclmdlExecute to start model inference.
                //TODO: Call aclrtFree to free device memory after the inference is complete.
            }
        }

//Feed the remainder data that is less than the batch size to the model for inference.
if (cnt % batchSize != 0)
    {
            //TODO: Create data of type aclmdlDataset and aclDataBuffer to describe the input and output data of the model.
            //TODO: Call aclmdlExecute to start model inference.
            //TODO: Call aclrtFree to free device memory after the inference is complete.
    }