欢迎光临
我们一直在努力

详解 Android NNAPI 的中间层机制:它是如何调度不同厂商驱动实现硬件加速的

深入理解 Android NNAPI 的中间层调度机制

Android Neural Networks API (NNAPI) 是 Google 为 Android 设备提供的一套用于运行计算密集型机器学习模型的框架。它的核心价值在于提供了一个统一的抽象层,使得应用程序无需关注底层硬件(如 NPU、DSP 或 GPU)的具体实现细节,从而实现跨设备的高效硬件加速。

本文将深入探讨 NNAPI 如何通过其中间层机制,有效地调度和利用不同厂商提供的硬件驱动。

1. NNAPI 架构概述:HAL层的核心作用

NNAPI 架构可以大致分为三层:

  1. 应用层 (Application Layer): 开发者通过 TensorFlow Lite (TFLite) 或直接通过 NNAPI C API 调用模型执行。
  2. 框架层 (NNAPI Framework): 这是 Android 系统服务的一部分,负责模型的构建、编译、执行调度,以及最重要的——资源分配和设备选择
  3. 硬件抽象层 (Hardware Abstraction Layer, HAL): 这是 NNAPI 中间的关键层。它定义了标准的接口(android.hardware.neuralnetworks),所有硬件厂商必须遵循该接口实现他们的驱动。这个 HAL 层就是连接通用框架和专有硬件驱动的“中间人”。

2. 中间层(HAL)如何实现调度

当应用请求执行一个模型时,NNAPI 框架会经历以下关键调度步骤:

A. 设备发现与能力查询

当 NNAPI 服务启动时,它会查询所有注册到系统的 HAL 实现(即硬件厂商的驱动)。每个驱动会报告其支持的操作集和性能特征。

B. 模型编译与划分 (Partitioning)

模型(通常以图的形式表示)被提交给 NNAPI 框架。框架随后会尝试将其分配给最合适的加速器。

  1. 全图分配: 如果某个加速器(如 NPU)可以完整、高效地执行整个模型,NNAPI 倾向于将整个图分配给它。
  2. 子图划分: 如果模型包含某些加速器不支持的操作(例如,NPU可能不支持某些复杂的控制流操作),NNAPI 框架会进行图划分。它将图分解为多个子图,把支持的部分分配给硬件加速器(通过 HAL 驱动),不支持的部分退回到 CPU 上执行。

NNAPI HAL 接口中的 IDevice::getSupportedOperations 方法是实现这一划分的关键,它允许框架在编译阶段决定哪些操作可以被特定驱动加速。

C. 运行时执行

一旦模型被编译成 IPreparedModel 对象,在运行时,NNAPI 框架只需调用对应驱动的执行接口。数据流在 CPU 和加速器之间高效地传递,由 HAL 层负责所有的数据同步和命令提交。

3. TFLite 实践:如何启用 NNAPI 调度

对于大部分应用开发者而言,与 NNAPI 交互是通过 TensorFlow Lite (TFLite) Delegate 完成的。启用 TFLite 的 NNAPI Delegate,即是将模型的执行权交给了上文所述的 NNAPI 调度框架。

以下是在 Android (Kotlin/Java) 环境中启用 NNAPI Delegate 的实操代码示例:

import org.tensorflow.lite.Interpreter
import org.tensorflow.lite.Delegate
import org.tensorflow.lite.nnapi.NnApiDelegate

fun setupTFLiteInterpreter(modelBuffer: ByteBuffer): Interpreter {
    // 1. 创建 NNAPI Delegate 实例
    // 实例化时,NNAPI会尝试连接到系统的HAL服务
    val nnApiDelegate: Delegate? = try {
        // 尝试创建 NnApiDelegate,如果设备不支持或系统版本过低,则可能返回 null
        NnApiDelegate()
    } catch (e: Exception) {
        Log.w("NNAPI", "NNAPI Delegate creation failed: ", e)
        null
    }

    val options = Interpreter.Options()

    if (nnApiDelegate != null) {
        // 2. 将 Delegate 附加到 Interpreter 选项中
        // 这一步告诉 TFLite 运行时,首先尝试使用 NNAPI 进行推理
        options.addDelegate(nnApiDelegate)
        Log.i("NNAPI", "NNAPI Delegate successfully added.")
    } else {
        Log.w("NNAPI", "Falling back to CPU execution.")
    }

    // 3. 创建 Interpreter,触发模型编译和NNAPI的调度过程
    // 在 Interpreter 构造时,NNAPI框架会进行图划分和设备选择
    val interpreter = Interpreter(modelBuffer, options)

    // 别忘了在推理完成后关闭 Delegate 和 Interpreter
    // nnApiDelegate?.close()
    // interpreter.close()

    return interpreter
}

通过上述步骤,当 Interpreter 初始化时,TFLite 会将模型图提交给 NNAPI 框架。NNAPI 框架利用其 HAL 中间层机制,自动查询所有可用的硬件加速器(NPU, DSP, GPU 驱动),并根据操作支持和性能指标,选择最佳的设备进行编译和执行,从而实现透明化的硬件加速调度。

【本站文章皆为原创,未经允许不得转载】:汤不热吧 » 详解 Android NNAPI 的中间层机制:它是如何调度不同厂商驱动实现硬件加速的
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址