C API Guidelines

The C API uses a handle based approach for accessing the Qualcomm® Neural Processing SDK API classes.

Handle Creation

The user can generate a handle for a specific class using the Create function call. The Create call is similar to creating an object in C++ by passing arguments to a constructor. In the below code excerpt, the Snpe_SNPEBuilder_Create function call creates a snpe builder object and returns back a handle, which can then be used by the user to invoke the builder object.

Snpe_SNPEBuilder_Handle_t snpeBuilderHandle = Snpe_SNPEBuilder_Create(containerHandle);

Handle Deletion

The lifecycle of handles created and returned to the user needs to be managed by the user. i.e Every handle created by the user or returned to the user by value needs to be deleted explicitly by the user. The following code excerpt, shows how the Delete call can be invoked by the user

Snpe_IUserBuffer_Handle_t userBufferEncodingFloatHandle = Snpe_UserBufferEncodingFloat_Create();
Snpe_UserBufferEncodingFloat_Delete(userBufferEncodingFloatHandle);

Types of handles

There are three types of handles in the C API:

  1. Handle Created by User: The User can invoke the Create method to generate a handle to access the object for a specific class. The handles generated using the create call needs to be deleted explicitly by the user by calling the corresponding Delete API call.

    Snpe_RuntimeList_Handle_t runtimeListHandle = Snpe_RuntimeList_Create();
    Snpe_RuntimeList_Delete(runtimeListHandle);
    
  2. Handle returned by Value: When an API returns back a handle of a different type, the lifecycle of the handle is to be managed by the user. The following excerpt demonstrates the return by value scenario.

    Snpe_StringList_Handle_t sl = Snpe_UserBufferMap_GetUserBufferNames(ubMapHandle);
    Snpe_StringList_Delete(sl);
    
  3. Handle returned by Reference: When a handle is returned by an API as reference, the handle is implicitly deleted when the parent handle is deleted. So the user does not have to manage the lifecycle in this case. The API returning a handle by ref is denoted by _Ref. In the following code excerpt the ubHandle is deleted when the Delete API is called on the ubMapHandle.

    Snpe_IUserBuffer_Handle_t ubHandle = Snpe_UserBufferMap_GetUserBuffer_Ref(ubMapHandle, name);
    Snpe_UserBufferMap_Delete(ubMapHandle);
    

Porting C++ APIs to C

The C API functions follow the pattern “Snpe_<ClassName>_<function>(<objectHandle>, …)”. As the C API uses a handle based approach, while translating the C++ APIs, the handle to access the object needs to be passed as an argument. The following code snippets demonstrates the translation of Qualcomm® Neural Processing SDK C++ API to C API.

C++ API calls to create a userbuffer Map:

void createUserBuffer(zdl::DlSystem::UserBufferMap& userBufferMap,
                      std::unordered_map<std::string, std::vector<uint8_t>>& applicationBuffers,
                      std::vector<std::unique_ptr<zdl::DlSystem::IUserBuffer>>& snpeUserBackedBuffers,
                      std::unique_ptr<zdl::SNPE::SNPE>& snpe,
                      const char * name)
{
   // get attributes of buffer by name
   auto bufferAttributesOpt = snpe->getInputOutputBufferAttributes(name);
   if (!bufferAttributesOpt) throw std::runtime_error(std::string("Error obtaining attributes for input tensor ") + name);
   // calculate the size of buffer required by the input tensor
   const zdl::DlSystem::TensorShape& bufferShape = (*bufferAttributesOpt)->getDims();
   // Calculate the stride based on buffer strides, assuming tightly packed.
   // Note: Strides = Number of bytes to advance to the next element in each dimension.
   // For example, if a float tensor of dimension 2x4x3 is tightly packed in a buffer of 96 bytes, then the strides would be (48,12,4)
   // Note: Buffer stride is usually known and does not need to be calculated.
   std::vector<size_t> strides(bufferShape.rank());
   strides[strides.size() - 1] = sizeof(float);
   size_t stride = strides[strides.size() - 1];
   for (size_t i = bufferShape.rank() - 1; i > 0; i--)
   {
      stride *= bufferShape[i];
      strides[i-1] = stride;
   }
   const size_t bufferElementSize = (*bufferAttributesOpt)->getElementSize();
   size_t bufSize = calcSizeFromDims(bufferShape.getDimensions(), bufferShape.rank(), bufferElementSize);
   // set the buffer encoding type
   zdl::DlSystem::UserBufferEncodingFloat userBufferEncodingFloat;
   // create user-backed storage to load input data onto it
   applicationBuffers.emplace(name, std::vector<uint8_t>(bufSize));
   // create Qualcomm (R) Neural Processing SDK user buffer from the user-backed buffer
   zdl::DlSystem::IUserBufferFactory& ubFactory = zdl::SNPE::SNPEFactory::getUserBufferFactory();
   snpeUserBackedBuffers.push_back(ubFactory.createUserBuffer(applicationBuffers.at(name).data(),
                                                              bufSize,
                                                              strides,
                                                              &userBufferEncodingFloat));
   // add the user-backed buffer to the inputMap, which is later on fed to the network for execution
   userBufferMap.add(name, snpeUserBackedBuffers.back().get());

C API calls to create a userbuffer Map:

void createUserBuffer(Snpe_UserBufferMap_Handle_t userBufferMapHandle,
                      std::unordered_map<std::string, std::vector<uint8_t>>& applicationBuffers,
                      std::vector<Snpe_IUserBuffer_Handle_t> snpeUserBackedBuffersHandle,
                      Snpe_SNPE_Handle_t snpeHandle,
                      const char * name)
{
   // get attributes of buffer by name
   Snpe_IBufferAttributes_Handle_t bufferAttributesOptHandle = Snpe_SNPE_GetInputOutputBufferAttributes(snpeHandle, name);
   if (bufferAttributesOptHandle == nullptr) throw std::runtime_error(std::string("Error obtaining attributes for input tensor ") + name);
   // calculate the size of buffer required by the input tensor
   Snpe_TensorShape_Handle_t bufferShapeHandle = Snpe_IBufferAttributes_GetDims(bufferAttributesOptHandle);
   // Calculate the stride based on buffer strides, assuming tightly packed.
   // Note: Strides = Number of bytes to advance to the next element in each dimension.
   // For example, if a float tensor of dimension 2x4x3 is tightly packed in a buffer of 96 bytes, then the strides would be (48,12,4)
   // Note: Buffer stride is usually known and does not need to be calculated.
   std::vector<size_t> strides(Snpe_TensorShape_Rank(bufferShapeHandle));
   strides[strides.size() - 1] = sizeof(float);
   size_t stride = strides[strides.size() - 1];
   for (size_t i = Snpe_TensorShape_Rank(bufferShapeHandle) - 1; i > 0; i--)
   {
      stride *= Snpe_TensorShape_At(bufferShapeHandle, i);
      strides[i-1] = stride;
   }
   Snpe_TensorShape_Handle_t stridesHandle = Snpe_TensorShape_CreateDimsSize(strides.data(), Snpe_TensorShape_Rank(bufferShapeHandle));
   size_t bufferElementSize = Snpe_IBufferAttributes_GetElementSize(bufferAttributesOptHandle);
   size_t bufSize = calcSizeFromDims(Snpe_TensorShape_GetDimensions(bufferShapeHandle), Snpe_TensorShape_Rank(bufferShapeHandle), bufferElementSize);
   // set the buffer encoding type
   Snpe_UserBufferEncoding_Handle_t userBufferEncodingFloatHandle = Snpe_UserBufferEncodingFloat_Create();
   // create user-backed storage to load input data onto it
   applicationBuffers.emplace(name, std::vector<uint8_t>(bufSize));
   // create Qualcomm (R) Neural Processing SDK user buffer from the user-backed buffer
   ubsHandle.push_back(Snpe_Util_CreateUserBuffer(applicationBuffers.at(name).data(),
                                                  bufSize,
                                                  stridesHandle,
                                                  userBufferEncodingFloatHandle));
   // add the user-backed buffer to the inputMap, which is later on fed to the network for execution
   Snpe_UserBufferMap_Add(userBufferMapHandle, name, snpeUserBackedBuffersHandle.back());
   // clean up created handles
   Snpe_IBufferAttributes_Delete(bufferAttributesOptHandle);
   Snpe_UserBufferEncodingFloat_Delete(userBufferEncodingFloatHandle);
   Snpe_TensorShape_Delete(bufferShapeHandle);