메모리 풀

이 페이지에서는 드라이버와 프레임워크 간에 피연산자 버퍼를 효율적으로 통신하는 데 사용되는 데이터 구조 및 방법에 대해 설명합니다.

모델 컴파일 시 프레임워크는 상수 피연산자의 값을 드라이버에 제공합니다. 상수 피연산자의 수명에 따라 해당 값은 HIDL 벡터 또는 공유 메모리 풀에 있습니다.

  • 수명이 CONSTANT_COPY 인 경우 값은 모델 구조의 operandValues ​​필드에 있습니다. HIDL 벡터의 값은 IPC(프로세스 간 통신) 중에 복사되기 때문에 일반적으로 스칼라 피연산자(예: ADD 의 활성화 스칼라) 및 작은 텐서 매개변수(예: RESHAPE 의 모양 텐서).
  • 수명이 CONSTANT_REFERENCE 인 경우 값은 모델 구조의 pools 필드에 있습니다. IPC 중에는 원시 값을 복사하는 대신 공유 메모리 풀의 핸들만 복제됩니다. 따라서 HIDL 벡터보다 공유 메모리 풀을 사용하여 많은 양의 데이터(예: 컨볼루션의 가중치 매개변수)를 보유하는 것이 더 효율적입니다.

모델 실행 시 프레임워크는 입력 및 출력 피연산자의 버퍼를 드라이버에 제공합니다. HIDL 벡터로 전송될 수 있는 컴파일 타임 상수와 달리 실행의 입력 및 출력 데이터는 항상 메모리 풀 모음을 통해 전달됩니다.

HIDL 데이터 유형 hidl_memory 는 매핑되지 않은 공유 메모리 풀을 나타내기 위해 컴파일과 실행 모두에 사용됩니다. 드라이버는 hidl_memory 데이터 유형의 이름을 기반으로 사용할 수 있도록 메모리를 적절하게 매핑해야 합니다. 지원되는 메모리 이름은 다음과 같습니다.

  • ashmem : 안드로이드 공유 메모리. 자세한 내용은 메모리 를 참조하십시오.
  • mmap_fd : mmap 을 통해 파일 기술자가 지원하는 공유 메모리.
  • hardware_buffer_blob : AHARDWARE_BUFFER_FORMAT_BLOB 형식의 AHARDWARE_BUFFER_FORMAT_BLOB 가 지원하는 공유 메모리. 신경망(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 및 메모리 도메인에 대한 지원은 선택 사항입니다.

A하드웨어버퍼

AHardwareBuffer는 Gralloc 버퍼 를 래핑하는 공유 메모리 유형입니다. Android 10에서 NNAPI(신경망 API)는 AHardwareBuffer 사용을 지원하므로 드라이버가 데이터를 복사하지 않고 실행을 수행할 수 있으므로 앱의 성능과 전력 소비가 향상됩니다. 예를 들어 카메라 HAL 스택은 카메라 NDK 및 미디어 NDK API에서 생성된 AHardwareBuffer 핸들을 사용하여 기계 학습 워크로드용 NNAPI에 AHardwareBuffer 객체를 전달할 수 있습니다. 자세한 내용은 ANeuralNetworksMemory_createFromAHardwareBuffer 를 참조하십시오.

NNAPI에서 사용되는 AHardwareBuffer 객체는 hardware_buffer 또는 hardware_buffer_blob 이라는 hidl_memory 구조체를 통해 드라이버에 전달됩니다. hidl_memory 구조체 hardware_buffer_blobAHARDWAREBUFFER_FORMAT_BLOB 형식의 AHardwareBuffer 개체만 나타냅니다.

프레임워크에 필요한 정보는 hidl_memory 구조체의 hidl_handle 필드에 인코딩됩니다. hidl_handle 필드는 AHardwareBuffer 또는 Gralloc 버퍼에 대한 모든 필수 메타데이터를 인코딩하는 native_handle 을 래핑합니다.

드라이버는 제공된 hidl_handle 필드를 올바르게 디코딩하고 hidl_handle 에서 설명하는 메모리에 액세스해야 합니다. getSupportedOperations_1_2 , getSupportedOperations_1_1 또는 getSupportedOperations 메서드가 호출되면 드라이버는 제공된 hidl_handle 을 디코딩하고 hidl_handle 에서 설명하는 메모리에 액세스할 수 있는지 여부를 감지해야 합니다. 상수 피연산자에 사용되는 hidl_handle 필드가 지원되지 않으면 모델 준비가 실패해야 합니다. 실행의 입력 또는 출력 피연산자에 사용되는 hidl_handle 필드가 지원되지 않으면 실행이 실패해야 합니다. 모델 준비 또는 실행이 실패하면 드라이버가 GENERAL_FAILURE 오류 코드를 반환하는 것이 좋습니다.

메모리 도메인

Android 11 이상을 실행하는 기기의 경우 NNAPI는 드라이버 관리 버퍼에 할당자 인터페이스를 제공하는 메모리 도메인을 지원합니다. 이를 통해 실행 간에 장치 기본 메모리를 전달할 수 있으므로 동일한 드라이버에서 연속 실행 간에 불필요한 데이터 복사 및 변환이 억제됩니다. 이 흐름은 그림 1에 나와 있습니다.

메모리 도메인이 있거나 없는 버퍼 데이터 흐름
그림 1. 메모리 도메인을 사용한 버퍼 데이터 흐름

메모리 도메인 기능은 대부분 드라이버 내부에 있고 클라이언트 측에서 자주 액세스할 필요가 없는 텐서를 위한 것입니다. 이러한 텐서의 예로는 시퀀스 모델의 상태 텐서가 있습니다. 클라이언트 측에서 빈번한 CPU 액세스가 필요한 텐서의 경우 공유 메모리 풀을 사용하는 것이 좋습니다.

메모리 도메인 기능을 지원하려면 프레임워크가 드라이버 관리 버퍼 할당을 요청할 수 있도록 IDevice::allocate 를 구현하십시오. 할당하는 동안 프레임워크는 버퍼에 대해 다음 속성과 사용 패턴을 제공합니다.

  • BufferDesc 는 버퍼의 필수 속성을 설명합니다.
  • BufferRole 은 버퍼의 잠재적인 사용 패턴을 준비된 모델의 입력 또는 출력으로 설명합니다. 버퍼 할당 시 여러 역할을 지정할 수 있으며 할당된 버퍼는 지정된 역할로만 사용할 수 있습니다.

할당된 버퍼는 드라이버 내부에 있습니다. 드라이버는 버퍼 위치나 데이터 레이아웃을 선택할 수 있습니다. 버퍼가 성공적으로 할당되면 드라이버의 클라이언트는 반환된 토큰 또는 IBuffer 개체를 사용하여 버퍼를 참조하거나 버퍼와 상호 작용할 수 있습니다.

IDevice::allocate 의 토큰은 버퍼를 실행의 Request 구조에서 MemoryPool 개체 중 하나로 참조할 때 제공됩니다. 프로세스가 다른 프로세스에 할당된 버퍼에 액세스하려는 시도를 방지하려면 드라이버는 버퍼를 사용할 때마다 적절한 유효성 검사를 적용해야 합니다. 드라이버는 버퍼 사용이 할당 중에 제공된 BufferRole 역할 중 하나인지 확인해야 하며 사용이 잘못된 경우 즉시 실행을 실패해야 합니다.

IBuffer 개체는 명시적 메모리 복사에 사용됩니다. 특정 상황에서 드라이버의 클라이언트는 공유 메모리 풀에서 드라이버 관리 버퍼를 초기화하거나 버퍼를 공유 메모리 풀로 복사해야 합니다. 사용 사례의 예는 다음과 같습니다.

  • 상태 텐서 초기화
  • 중간 결과 캐싱
  • CPU에서 대체 실행

이러한 사용 사례를 지원하려면 드라이버가 메모리 도메인 할당을 지원하는 경우 ashmem , mmap_fdhardware_buffer_blob 를 사용하여 IBuffer::copyToIBuffer::copyFrom 을 구현해야 합니다. 드라이버가 비BLOB 모드 hardware_buffer 를 지원하는 것은 선택 사항입니다.

버퍼 할당 중에 버퍼의 차원은 BufferRole 에 지정된 모든 역할의 해당 모델 피연산자와 BufferDesc 에서 제공되는 차원에서 추론할 수 있습니다. 모든 차원 정보가 결합되면 버퍼에 알 수 없는 차원이나 순위가 있을 수 있습니다. 이 경우 버퍼는 모델 입력으로 사용할 때 치수가 고정된 유연한 상태이고 모델 출력으로 사용할 때 동적 상태입니다. 동일한 버퍼가 다른 실행에서 다른 모양의 출력과 함께 사용될 수 있으며 드라이버는 버퍼 크기 조정을 적절하게 처리해야 합니다.

메모리 도메인은 선택적 기능입니다. 드라이버는 여러 가지 이유로 지정된 할당 요청을 지원할 수 없다고 결정할 수 있습니다. 예를 들어:

  • 요청된 버퍼의 크기가 동적입니다.
  • 드라이버에 큰 버퍼를 처리할 수 없도록 하는 메모리 제약이 있습니다.

여러 다른 스레드가 드라이버 관리 버퍼에서 동시에 읽을 수 있습니다. 쓰기 또는 읽기/쓰기를 위해 버퍼에 동시에 액세스하는 것은 정의되지 않지만 드라이버 서비스가 충돌하거나 호출자를 무기한 차단해서는 안 됩니다. 드라이버는 오류를 반환하거나 버퍼의 내용을 불확실한 상태로 둘 수 있습니다.