骁龙神经处理引擎SDK参考指南(23)

281 篇文章 30 订阅
17 篇文章 8 订阅

骁龙神经处理引擎SDK参考指南(23)


6.5.3 C API 指南

C API 使用基于句柄的方法来访问 SNPE API 类。

处理创建
用户可以使用Create函数调用为特定类生成句柄。Create 调用类似于通过将参数传递给构造函数在 C++ 中创建对象。在下面的代码摘录中,Snpe_SNPEBuilder_Create 函数调用创建了一个 snpe 构建器对象并返回一个句柄,然后用户可以使用该句柄来调用构建器对象。

Snpe_SNPEBuilder_Handle_t snpeBuilderHandle = Snpe_SNPEBuilder_Create(containerHandle);

处理删除
创建并返回给用户的句柄的生命周期需要由用户管理。即用户创建的/按值返回给用户的每个句柄都需要由用户明确删除。以下代码摘录显示了用户如何调用Delete调用

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

把手的种类
C API 中有三种类型的句柄:

  • **用户创建的句柄**:用户可以调用Create方法生成句柄来访问特定类的对象。用户需要通过调用相应的删除API 调用来显式删除使用创建调用生成的句柄。
    
Snpe_RuntimeList_Handle_t runtimeListHandle = Snpe_RuntimeList_Create();
Snpe_RuntimeList_Delete(runtimeListHandle);
  • Value 返回的句柄:当 API 返回不同类型的句柄时,句柄的生命周期由用户管理。以下摘录演示了价值回报方案。
Snpe_StringList_Handle_t sl = Snpe_UserBufferMap_GetUserBufferNames(ubMapHandle);
Snpe_StringList_Delete(sl);
  • 引用返回的句柄:当 API 返回句柄作为引用时。当父句柄被删除时,句柄被隐式删除。因此,在这种情况下,用户不必管理生命周期。通过 ref 返回句柄的 API 由_Ref表示。在下面的代码摘录中,当对 ubMapHandle 调用 Delete API 时,ubHandle 被删除。
Snpe_IUserBuffer_Handle_t ubHandle = Snpe_UserBufferMap_GetUserBuffer_Ref(ubMapHandle, name);
Snpe_UserBufferMap_Delete(ubMapHandle);

将 C++ API 移植到 C
C API 函数遵循模式“Snpe__(, …)”。由于 C API 使用基于句柄的方法,在转换 C++ API 时,访问对象的句柄需要作为参数传递。以下代码片段演示了 C++ SNPE API 到 C API 的转换。

调用 C++ API 来创建用户缓冲区映射:

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 SNPE 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 来创建用户缓冲区映射:

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 SNPE 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);

6.5.4 安卓教程

先决条件
SNPE SDK 已按照SNPE 设置章节进行设置。
教程设置已完成。
介绍
本教程介绍了在 Android 应用程序中集成 SNPE 和snpe-platform-validator Java API的过程。

SNPE 和平台验证器 Java API 作为 Android 存档 (AAR) 文件提供,应用程序开发人员将其作为其应用程序的依赖项包含在内。

Gradle项目依赖

allprojects {
    repositories {
        ...
        flatDir {
            // Marks the directory as a repository for
            // dependencies. Place the snpe-release.aar
            // in the directory below.
            dirs 'libs'
        }
    }
}
...
dependencies {
    ...
    // This adds the SNPE SDK as a project dependency
    compile(name: 'snpe-release', ext:'aar')
    // This adds the Platform Validator tool (optional) as a project dependency
    compile(name: 'platformvalidator-release', ext:'aar')
}

如果项目中需要两个档案,则需要在 gradle 中使用“pickFirst”以避免库冲突。

android {
    ...
    packagingOptions {
        pickFirst 'lib/arm64-v8a/libc++_shared.so'
        pickFirst 'lib/armeabi-v7a/libSNPE.so'
        pickFirst 'lib/arm64-v8a/libsnpe_dsp_domains_v2.so'
        pickFirst 'lib/arm64-v8a/libsnpe_dsp_v65_domains_v2_skel.so'
        pickFirst 'lib/armeabi-v7a/libsnpe_dsp_v65_domains_v2_skel.so'
        pickFirst 'lib/arm64-v8a/libsnpe_dsp_v66_domains_v2_skel.so'
        pickFirst 'lib/armeabi-v7a/libsnpe_dsp_v66_domains_v2_skel.so'
        pickFirst 'lib/armeabi-v7a/libc++_shared.so'
        pickFirst 'lib/armeabi-v7a/libsnpe_dsp_domains_v2.so'
        pickFirst 'lib/arm64-v8a/libSNPE.so'
        pickFirst 'lib/armeabi-v7a/libsymphony-cpu.so'
        pickFirst 'lib/arm64-v8a/libsymphony-cpu.so'
    }

平台验证器 Java API 概述
一旦添加了 platformform validator 的可选依赖项,com.qualcomm.qti.platformvalidator包下的 Platform Validator 类将在应用程序类路径中可用。

所有应用程序将首先创建一个具有所需运行时的平台验证器对象,然后使用该对象调用验证 API,如下所述。

使用平台验证器

//Platform validator class for object creation
import com.qualcomm.qti.platformvalidator.PlatformValidator;
//available runtimes are defined in this class
import com.qualcomm.qti.platformvalidator.PlatformValidatorUtil;
...
//This create platform validator object for GPU runtime class
PlatformValidator pv = new PlatformValidator(PlatformValidatorUtil.Runtime.GPU);
// To check in general runtime is working use isRuntimeAvailable
boolean check = pv.isRuntimeAvailable(getApplication());
// To check SNPE runtime is working use runtimeCheck
boolean check = pv.runtimeCheck(getApplication());
//To get core version use libVersion api
String str = pv.coreVersion(getApplication());
//To get core version use coreVersion api
String str = pv.coreVersion(getApplication());
//List of available runtimes
PlatformValidatorUtil.Runtime.CPU
PlatformValidatorUtil.Runtime.GPU
PlatformValidatorUtil.Runtime.DSP
PlatformValidatorUtil.Runtime.GPU_FLOAT16
PlatformValidatorUtil.Runtime.AIP

SNPE Java API 概述
添加依赖项后,com.qualcomm.qti.snpe包下的 SNPE 类将在应用程序类路径中可用。

大多数应用程序在使用神经网络时将遵循以下模式:

  1. 选择神经网络模型和运行时目标
  2. 创建一个或多个输入张量
  3. 用网络输入填充一个或多个输入张量
  4. 通过网络前向传播输入张量
  5. 处理网络输出张量

以下部分描述了如何实施上述每个步骤。

配置神经网络

下面的代码摘录说明了如何使用 JAVA API 配置和构建神经网络。

final SNPE.NeuralNetworkBuilder builder = new SNPE.NeuralNetworkBuilder(application)
    // Allows selecting a runtime order for the network.
    // In the example below use DSP and fall back, in order, to GPU then CPU
    // depending on whether any of the runtimes is available.
    .setRuntimeOrder(DSP, GPU, CPU)
    // Loads a model from DLC file
    .setModel(new File("<model-path>"));
final NeuralNetwork network = builder.build();
...
// Calling release() once the application no longer needs the network instance
// is highly recommended as it releases native resources. Alternatively the
// resources will be released when the instance is garbage collected.
network.release();

加载模型的多种方式

SDK 目前支持从Android 设备中的java.io.File或从java.io.FileInputStream加载模型。

创建输入张量
下面的代码摘录说明了如何创建输入张量并用输入数据填充它。

final FloatTensor tensor = network.createFloatTensor(height, width, depth);
float[] input = // input data from application...
// Fills the tensor fully
tensor.write(input, 0, input.length);
// Fills the tensor at a specific position
tensor.write(input[0], y, x, z);
// Fills the input tensors map which will be passed to execute()
final Map<String, FloatTensor> inputsMap;
inputsMap.put(/*network input name*/, tensor);

关于张量的注释

  • 输入张量的重复使用
    鼓励开发人员在对 NeuralNetwork.execute(…) 的多次调用中重复使用相同的输入张量实例。张量是内存绑定类型,为每个执行调用创建新实例的效果可能会影响应用程序的响应能力。
  • 批量写入张量
    张量由本机内存支持,一次写入多个值(如果可能)将减少跨越 Java 和本机边界的开销。
    通过网络传播输入张量
    下面的代码摘录显示了如何通过神经网络传播输入张量。
final Map<String, FloatTensor> outputsMap = network.execute(inputsMap);
for (Map.Entry<String, FloatTensor> output : outputsMap.entrySet()) {
    // An output tensor for each output layer will be returned.
}

处理神经网络输出
下面的代码摘录显示了如何读取输出层的输出张量。

final Map<String, FloatTensor> outputsMap = network.execute(inputsMap);
for (Map.Entry<String, FloatTensor> output : outputsMap.entrySet()) {
    final FloatTensor tensor = output.getValue();
    final float[] values = new float[tensor.getSize()];
    tensor.read(values, 0, values.length);
    // Process the output ...
}

释放输入和输出张量
鼓励重用张量以减少应用程序开销。但是,一旦应用程序不再需要输入和/或输出张量,强烈建议对它们调用 release() 以释放本机资源。这对于多线程应用程序尤为重要。

// Release input tensors
for (FloatTensor tensor: inputsMap) {
    tensor.release();
}
// Release output tensors
for (FloatTensor tensor: outputsMap) {
    tensor.release();
}

Android 示例应用程序
SNPE Android SDK 包含一个展示 SDK 功能的示例应用程序。应用程序源代码位于:

  • $SNPE_ROOT/示例/android/图像分类器

这是示例的屏幕截图:

请添加图片描述

请注意,SNPE 提供了以下包含必要二进制文件的 AAR 文件:

  • snpe-release.aar:使用 libc++ STL 用 clang 编译的本机二进制文件

请为此 AAR 文件设置环境变量 SNPE_AAR。

要构建此示例,请包括如上所述的 SNPE SDK AAR 并使用以下命令构建。

export SNPE_AAR=snpe-release.aar
cd $SNPE_ROOT/examples/android/image-classifiers
bash ./setup_alexnet.sh
bash ./setup_inceptionv3.sh
cp ../../../android/$SNPE_AAR app/libs/snpe-release.aar
./gradlew assembleDebug

笔记:

  • 要构建示例,请通过调用setup_models.sh脚本导入网络模型和示例图像,如上所述。
  • 如果构建产生错误gradle build failure due to “SDK location not found”,请将环境变量 ANDROID_HOME 设置为指- 向您的 sdk 位置。
  • 使用 gradle 构建示例代码需要 java 8。
  • 应用程序中的未签名 PD 开关只能在加载模型时设置/取消设置一次。返回菜单屏幕并更改选项可能会导致意外行为。
  • 在特定运行时使用 UDO 运行网络需要将相应运行时的 UDO 包推送到设备上(运行 setup_inceptionv3.sh)。在没有 UDO 包的情况下运行 UDO 网络可能会导致意外行为

构建成功完成后,可以在应用程序构建文件夹中找到输出的 APK:

  • $SNPE_ROOT/examples/android/image-classifiers/app/build/outputs/apk
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值