Pule pamięci

Na tej stronie opisujemy struktury danych i metody używane do wydajnej komunikacji buforów operandów między sterownikiem a platformą.

Podczas kompilacji modelu platforma udostępnia sterownikowi wartości operandów stałych. W zależności od czasu życia operandu stałego jego wartości znajdują się w wektorze HIDL lub w puli pamięci współdzielonej.

  • Jeśli czas życia to CONSTANT_COPY, wartości znajdują się w polu operandValues struktury modelu. Ponieważ wartości w wektorze HIDL są kopiowane podczas komunikacji międzyprocesowej (IPC), jest on zwykle używany tylko do przechowywania niewielkiej ilości danych, takich jak operandy skalarne (np. skalar aktywacji w ADD) i małe parametry tensorowe (np. tensor kształtu w RESHAPE).
  • Jeśli czas życia to CONSTANT_REFERENCE, wartości znajdują się w polu pools struktury modelu. Podczas IPC duplikowane są tylko uchwyty puli pamięci współdzielonej, a nie surowe wartości. Dlatego bardziej wydajne jest przechowywanie dużej ilości danych (np. parametrów wagi w konwolucjach) w pulach pamięci współdzielonej niż w wektorach HIDL.

Podczas wykonywania modelu platforma udostępnia sterownikowi bufory operandów wejściowych i wyjściowych. W przeciwieństwie do stałych czasu kompilacji, które mogą być wysyłane w wektorze HIDL, dane wejściowe i wyjściowe wykonania są zawsze przekazywane za pomocą kolekcji pul pamięci.

Typ danych HIDL hidl_memory jest używany zarówno podczas kompilacji, jak i wykonywania do reprezentowania niezmapowanej puli pamięci współdzielonej. Sterownik powinien odpowiednio zmapować pamięć, aby można było jej używać na podstawie nazwy typu danych hidl_memory. Obsługiwane nazwy pamięci:

  • ashmem: pamięć współdzielona Androida. Więcej informacji znajdziesz w sekcji dotyczącej pamięci.
  • mmap_fd: pamięć współdzielona obsługiwana przez deskryptor pliku za pomocą mmap.
  • hardware_buffer_blob: pamięć współdzielona obsługiwana przez AHardwareBuffer w formacie AHARDWARE_BUFFER_FORMAT_BLOB. Dostępne w HAL sieci neuronowych (NN) 1.2. Więcej informacji znajdziesz w sekcji AHardwareBuffer.
  • hardware_buffer: pamięć współdzielona obsługiwana przez ogólny AHardwareBuffer, który nie używa formatu AHARDWARE_BUFFER_FORMAT_BLOB. Bufor sprzętowy w trybie innym niż BLOB jest obsługiwany tylko podczas wykonywania modelu.Dostępne w NN HAL 1.2. Więcej informacji znajdziesz w sekcji AHardwareBuffer.

Od NN HAL 1.3 NNAPI obsługuje domeny pamięci, które udostępniają interfejsy alokatora dla buforów zarządzanych przez sterownik. Bufory zarządzane przez sterownik mogą być też używane jako dane wejściowe lub wyjściowe wykonania. Więcej informacji znajdziesz w sekcji Domeny pamięci.

Sterowniki NNAPI muszą obsługiwać mapowanie nazw pamięci ashmem i mmap_fd. Od NN HAL 1.3 sterowniki muszą też obsługiwać mapowanie hardware_buffer_blob. Obsługa ogólnego hardware_buffer w trybie innym niż BLOB i domen pamięci jest opcjonalna.

AHardwareBuffer

AHardwareBuffer to typ pamięci współdzielonej, która opakowuje bufor Gralloc. W Androidzie 10 interfejs Neural Networks API (NNAPI) obsługuje używanie AHardwareBuffer, co pozwala sterownikowi wykonywać operacje bez kopiowania danych, co zwiększa wydajność i zmniejsza zużycie energii w aplikacjach. Na przykład stos HAL aparatu może przekazywać obiekty AHardwareBuffer do NNAPI na potrzeby zadań uczenia maszynowego za pomocą uchwytów AHardwareBuffer generowanych przez interfejsy NDK aparatu i NDK multimediów. Więcej informacji znajdziesz w sekcji ANeuralNetworksMemory_createFromAHardwareBuffer.

Obiekty AHardwareBuffer używane w NNAPI są przekazywane do sterownika za pomocą hidl_memory struktury o nazwie hardware_buffer lub hardware_buffer_blob. Struktura hidl_memory hardware_buffer_blob reprezentuje tylko obiekty AHardwareBuffer w formacie AHARDWAREBUFFER_FORMAT_BLOB.

Informacje wymagane przez platformę są zakodowane w polu hidl_handle struktury hidl_memory. Pole hidl_handle opakowuje native_handle, które koduje wszystkie wymagane metadane dotyczące AHardwareBuffer lub bufora Gralloc.

Sterownik musi prawidłowo zdekodować podane pole hidl_handle i uzyskać dostęp do pamięci opisanej przez hidl_handle. Gdy wywoływana jest metoda getSupportedOperations_1_2, getSupportedOperations_1_1, lub getSupportedOperations, sterownik powinien wykryć, czy może zdekodować podany hidl_handle i uzyskać dostęp do pamięci opisanej przez hidl_handle. Przygotowanie modelu musi się nie udać, jeśli pole hidl_handle używane w przypadku operandu stałego nie jest obsługiwane. Wykonanie musi się nie udać, jeśli pole hidl_handle używane w przypadku operandu wejściowego lub wyjściowego wykonania nie jest obsługiwane. Jeśli przygotowanie lub wykonanie modelu się nie powiedzie, sterownik powinien zwrócić kod błędu GENERAL_FAILURE.

Domeny pamięci

W przypadku urządzeń z Androidem 11 lub nowszym NNAPI obsługuje domeny pamięci, które udostępniają interfejsy alokatora dla buforów zarządzanych przez sterownik. Umożliwia to przekazywanie natywnych pamięci urządzenia między wykonaniami, co eliminuje niepotrzebne kopiowanie i przekształcanie danych między kolejnymi wykonaniami na tym samym sterowniku. Ten proces jest przedstawiony na ilustracji 1.

Buforowanie przepływu danych z domenami pamięci i bez nich

Ilustracja 1. Przepływ danych bufora za pomocą domen pamięci

Funkcja domen pamięci jest przeznaczona dla tensorów, które są głównie wewnętrzne dla sterownika i nie wymagają częstego dostępu po stronie klienta. Przykładami takich tensorów są tensory stanu w modelach sekwencji. W przypadku tensorów, które wymagają częstego dostępu do procesora po stronie klienta, lepiej jest używać pul pamięci współdzielonej.

Aby obsługiwać funkcję domen pamięci, zaimplementuj IDevice::allocate aby umożliwić platformie żądanie alokacji bufora zarządzanego przez sterownik. Podczas alokacji platforma udostępnia te właściwości i wzorce użycia bufora:

  • BufferDesc opisuje wymagane właściwości bufora.
  • BufferRole opisuje potencjalny wzorzec użycia bufora jako danych wejściowych lub wyjściowych przygotowanego modelu. Podczas alokacji bufora można określić wiele ról, a przydzielony bufor może być używany tylko w tych rolach.

Przydzielony bufor jest wewnętrzny dla sterownika. Sterownik może wybrać dowolną lokalizację bufora lub układ danych. Gdy bufor zostanie pomyślnie przydzielony, klient sterownika może odwoływać się do bufora lub wchodzić z nim w interakcję za pomocą zwróconego tokena lub obiektu IBuffer.

Token z IDevice::allocate jest udostępniany podczas odwoływania się do bufora jako jednego z MemoryPool obiektów w strukturze Request wykonania. Aby uniemożliwić procesowi próbę uzyskania dostępu do bufora przydzielonego w innym procesie, sterownik musi stosować odpowiednią weryfikację przy każdym użyciu bufora. Sterownik musi sprawdzić, czy użycie bufora jest jedną z ról BufferRole podanych podczas alokacji, i natychmiast przerwać wykonanie, jeśli użycie jest nieprawidłowe.

Obiekt IBuffer służy do jawnego kopiowania pamięci. W niektórych sytuacjach klient sterownika musi zainicjować bufor zarządzany przez sterownik z puli pamięci współdzielonej lub skopiować bufor do puli pamięci współdzielonej. Przykłady przypadków użycia:

  • Inicjowanie tensora stanu
  • Buforowanie wyników pośrednich
  • Wykonanie rezerwowe na procesorze

Aby obsługiwać te przypadki użycia, sterownik musi zaimplementować IBuffer::copyTo i IBuffer::copyFrom z ashmem, mmap_fd i hardware_buffer_blob, jeśli obsługuje alokację domen pamięci. Obsługa hardware_buffer w trybie innym niż BLOB jest opcjonalna.

Podczas alokacji bufora wymiary bufora można wywnioskować z odpowiednich operandów modelu wszystkich ról określonych przez BufferRole oraz wymiarów podanych w BufferDesc. Po połączeniu wszystkich informacji o wymiarach bufor może mieć nieznane wymiary lub rangę. W takim przypadku bufor jest w stanie elastycznym, w którym wymiary są stałe, gdy jest używany jako dane wejściowe modelu, oraz w stanie dynamicznym, gdy jest używany jako dane wyjściowe modelu. Ten sam bufor może być używany z różnymi kształtami danych wyjściowych w różnych wykonaniach, a sterownik musi prawidłowo obsługiwać zmianę rozmiaru bufora.

Domena pamięci jest funkcją opcjonalną. Sterownik może stwierdzić, że nie może obsługiwać danego żądania alokacji z kilku powodów. Na przykład:

  • Żądany bufor ma dynamiczny rozmiar.
  • Sterownik ma ograniczenia pamięci, które uniemożliwiają mu obsługę dużych buforów.

Kilka różnych wątków może jednocześnie odczytywać dane z bufora zarządzanego przez sterownik. Jednoczesny dostęp do bufora w celu zapisu lub odczytu/zapisu jest niezdefiniowany, ale nie może powodować awarii usługi sterownika ani blokować wywołującego na czas nieokreślony. Sterownik może zwrócić błąd lub pozostawić zawartość bufora w stanie nieokreślonym.