内存池

本页介绍了用于在驱动程序和框架之间高效通信操作数缓冲区的数据结构和方法。

在模型编译时,框架会向驱动程序提供常量操作数的值。根据常量操作数的生命周期,其值可以位于 HIDL 向量或共享内存池中。

  • 如果生命周期为 CONSTANT_COPY,则这些值位于模型结构的 operandValues 字段中。由于 HIDL 向量中的值在进程间通信 (IPC) 期间会被复制,因此通常仅用于保存少量数据,例如标量操作数(例如,ADD 中的激活标量)和小张量参数(例如,RESHAPE 中的形状张量)。
  • 如果生命周期为 CONSTANT_REFERENCE,则这些值位于模型结构的 pools 字段中。IPC 期间仅复制共享内存池的句柄,而不是复制原始值。因此,使用共享内存池比 HIDL 向量更有效地保存大量数据(例如,卷积中的权重参数)。

在模型执行时,框架会向驱动程序提供输入和输出操作数的缓冲区。与可能在 HIDL 向量中发送的编译时常量不同,执行的输入和输出数据始终通过内存池集合进行通信。

HIDL 数据类型 hidl_memory 在编译和执行中都用于表示未映射的共享内存池。驱动程序应根据 hidl_memory 数据类型的名称相应地映射内存,使其可用。受支持的内存名称包括

  • ashmem:Android 共享内存。如需了解详情,请参阅内存
  • mmap_fd:由文件描述符通过 mmap 支持的共享内存。
  • hardware_buffer_blob:由格式为 AHARDWARE_BUFFER_FORMAT_BLOB 的 AHardwareBuffer 支持的共享内存。从 Neural Networks (NN) HAL 1.2 开始提供。如需了解详情,请参阅AHardwareBuffer
  • hardware_buffer:由不使用 AHARDWARE_BUFFER_FORMAT_BLOB 格式的通用 AHardwareBuffer 支持的共享内存。非 BLOB 模式硬件缓冲区仅在模型执行中受支持。从 NN HAL 1.2 开始提供。如需了解详情,请参阅AHardwareBuffer

从 NN HAL 1.3 开始,NNAPI 支持内存域,这些内存域为驱动程序管理的缓冲区提供分配器接口。驱动程序管理的缓冲区也可以用作执行输入或输出。如需了解详情,请参阅内存域

NNAPI 驱动程序必须支持 ashmemmmap_fd 内存名称的映射。从 NN HAL 1.3 开始,驱动程序还必须支持 hardware_buffer_blob 的映射。对通用非 BLOB 模式 hardware_buffer 和内存域的支持是可选的。

AHardwareBuffer

AHardwareBuffer 是一种包装 Gralloc 缓冲区的共享内存类型。在 Android 10 中,Neural Networks API (NNAPI) 支持使用 AHardwareBuffer,允许驱动程序执行操作而无需复制数据,从而提高应用的性能和降低功耗。例如,相机 HAL 堆栈可以将 AHardwareBuffer 对象传递到 NNAPI,以用于使用相机 NDK 和媒体 NDK API 生成的 AHardwareBuffer 句柄的机器学习工作负载。如需了解详情,请参阅ANeuralNetworksMemory_createFromAHardwareBuffer

NNAPI 中使用的 AHardwareBuffer 对象通过名为 hardware_bufferhardware_buffer_blobhidl_memory 结构体传递给驱动程序。hidl_memory 结构体 hardware_buffer_blob 仅表示格式为 AHARDWAREBUFFER_FORMAT_BLOB 的 AHardwareBuffer 对象。

框架所需的信息编码在 hidl_memory 结构体的 hidl_handle 字段中。hidl_handle 字段包装了 native_handle,后者编码了有关 AHardwareBuffer 或 Gralloc 缓冲区的所有必需元数据。

驱动程序必须正确解码提供的 hidl_handle 字段,并访问 hidl_handle 描述的内存。当调用 getSupportedOperations_1_2getSupportedOperations_1_1getSupportedOperations 方法时,驱动程序应检测它是否可以解码提供的 hidl_handle 并访问 hidl_handle 描述的内存。如果用于常量操作数的 hidl_handle 字段不受支持,则模型准备必须失败。如果用于执行的输入或输出操作数的 hidl_handle 字段不受支持,则执行必须失败。建议驱动程序在模型准备或执行失败时返回 GENERAL_FAILURE 错误代码。

内存域

对于运行 Android 11 或更高版本的设备,NNAPI 支持内存域,这些内存域为驱动程序管理的缓冲区提供分配器接口。这允许跨执行传递设备原生内存,从而抑制同一驱动程序上连续执行之间不必要的数据复制和转换。图 1 说明了此流程。

Buffer data flow with and without memory domains

图 1. 使用内存域的缓冲区数据流

内存域功能适用于主要在驱动程序内部且不需要客户端频繁访问的张量。此类张量的示例包括序列模型中的状态张量。对于需要在客户端频繁进行 CPU 访问的张量,最好使用共享内存池。

为了支持内存域功能,请实现 IDevice::allocate,以允许框架请求驱动程序管理的缓冲区分配。在分配期间,框架提供缓冲区的以下属性和使用模式

  • BufferDesc 描述了缓冲区的必需属性。
  • BufferRole 描述了缓冲区作为准备好的模型的输入或输出的潜在使用模式。可以在缓冲区分配期间指定多个角色,并且分配的缓冲区只能用作那些指定的角色。

分配的缓冲区在驱动程序内部。驱动程序可以选择任何缓冲区位置或数据布局。成功分配缓冲区后,驱动程序的客户端可以使用返回的令牌或 IBuffer 对象引用缓冲区或与其交互。

当在执行的 Request 结构中将缓冲区引用为 MemoryPool 对象之一时,会提供来自 IDevice::allocate 的令牌。为了防止进程尝试访问在另一个进程中分配的缓冲区,驱动程序必须在每次使用缓冲区时应用适当的验证。驱动程序必须验证缓冲区使用是否是分配期间提供的 BufferRole 角色之一,并且如果使用非法,则必须立即使执行失败。

IBuffer 对象用于显式内存复制。在某些情况下,驱动程序的客户端必须从共享内存池初始化驱动程序管理的缓冲区,或将缓冲区复制到共享内存池。用例示例包括

  • 状态张量的初始化
  • 缓存中间结果
  • 在 CPU 上回退执行

为了支持这些用例,如果驱动程序支持内存域分配,则必须使用 ashmemmmap_fdhardware_buffer_blob 实现 IBuffer::copyToIBuffer::copyFrom。驱动程序可以选择支持非 BLOB 模式 hardware_buffer

在缓冲区分配期间,缓冲区的维度可以从 BufferRole 指定的所有角色的相应模型操作数以及 BufferDesc 中提供的维度推断出来。结合所有维度信息,缓冲区可能具有未知的维度或秩。在这种情况下,缓冲区处于灵活状态,其中维度在用作模型输入时是固定的,而在用作模型输出时是动态的。同一缓冲区可以在不同的执行中使用不同形状的输出,并且驱动程序必须正确处理缓冲区大小调整。

内存域是一项可选功能。驱动程序可以出于多种原因确定它无法支持给定的分配请求。例如

  • 请求的缓冲区具有动态大小。
  • 驱动程序的内存约束阻止其处理大型缓冲区。

多个不同的线程可以同时从驱动程序管理的缓冲区读取数据。同时访问缓冲区进行写入或读/写是未定义的,但不得导致驱动程序服务崩溃或无限期阻止调用方。驱动程序可以返回错误或使缓冲区的内容处于不确定状态。