Pule pamięci

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

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

  • Jeśli czas trwania wynosi CONSTANT_COPY, wartości znajdują się w polu operandValues struktury modelu. Wartości w wektorze HIDL są kopiowane podczas komunikacji międzyprocesowej (IPC), dlatego zwykle jest to używane tylko do przechowywania niewielkiej ilości danych, takich jak operandy skalarne (np. skalarny aktywacji w ADD) i parametry małych tensorów (np. tensor kształtu w RESHAPE).
  • Jeśli czas trwania wynosi CONSTANT_REFERENCE, wartości znajdują się w polu pools struktury modelu. W trakcie IPC duplikowane są tylko uchwyty współdzielonych pul pamięci, zamiast kopiować nieprzetworzone wartości. W związku z tym przechowywanie dużej ilości danych (np. parametrów wagi w splotach) przy użyciu pul pamięci współdzielonej jest skuteczniejsze niż wektory HIDL.

W czasie 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 przez zbiór 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: wspomnienie udostępnione z Androida. Więcej informacji znajdziesz w opisie memory.
  • mmap_fd: pamięć udostępniona wspierana przez deskryptor pliku przez usługę mmap.
  • hardware_buffer_blob: pamięć udostępniona wspierana przez AHardwareBuffer w formacie AHARDWARE_BUFFER_FORMAT_BLOB. Dostępne w NNN HAL 1.2. Więcej informacji znajdziesz na stronie AHardwareBuffer.
  • hardware_buffer: pamięć współdzielona przez ogólny bufor AHardwareBuffer, który nie korzysta z formatu AHARDWARE_BUFFER_FORMAT_BLOB. Bufor sprzętowy w trybie innym niż BLOB jest obsługiwany tylko podczas wykonywania modelu.Dostępne od NN HAL 1.2. Więcej informacji znajdziesz na stronie AHardwareBuffer.

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

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

AHardwareBuffer

AHardwareBuffer to typ pamięci współdzielonej, która pakuje bufor Gralloc. W Androidzie 10 interfejs Neural Networks API (NNAPI) obsługuje użycie interfejsu AHardwareBuffer, co umożliwia sterownikowi wykonywanie uruchomień bez kopiowania danych, co poprawia wydajność i zużycie energii przez aplikacje. Na przykład stos HAL kamery może przekazywać obiekty AHardwareBuffer do NNAPI na potrzeby zadań systemów uczących się przy użyciu uchwytów AHardwareBuffer wygenerowanych przez interfejsy API NDK kamery i media NDK. Więcej informacji: ANeuralNetworksMemory_createFromAHardwareBuffer.

Obiekty AHardwareBuffer używane w NNAPI są przekazywane do sterownika za pomocą struktury hidl_memory 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 umieszcza tag native_handle, który koduje wszystkie wymagane metadane dotyczące AHardwareBuffer lub Gralloc.

Sterownik musi prawidłowo zdekodować podane pole hidl_handle i uzyskać dostęp do pamięci opisanej przez hidl_handle. Przy wywołaniu metody getSupportedOperations_1_2, getSupportedOperations_1_1 lub getSupportedOperations sterownik powinien wykryć, czy jest w stanie zdekodować podane hidl_handle i uzyskać dostęp do pamięci opisanej przez hidl_handle. Jeśli pole hidl_handle używane na potrzeby stałego operandu nie jest obsługiwane, przygotowanie modelu musi się nie udać. Jeśli pole hidl_handle użyte jako argument wejściowy lub wyjściowy do wykonania nie jest obsługiwane, wykonanie musi się zakończyć. Zaleca się, aby sterownik zwracał kod błędu GENERAL_FAILURE w przypadku niepowodzenia przygotowania lub wykonania modelu.

Domeny pamięci

W przypadku urządzeń z Androidem 11 lub nowszym NNAPI obsługuje domeny pamięci, które udostępniają interfejsy alokacji dla buforów zarządzanych przez sterownika. Pozwala to na przekazywanie natywnych pamięci urządzenia między uruchomieniami, co eliminuje niepotrzebne kopiowanie i przekształcanie danych między kolejnymi wykonaniami na tym samym sterowniku. Proces ten został przedstawiony na rys. 1.

Przepływ danych buforów z domenami pamięci i bez nich

Rysunek 1. Przepływ danych buforów z użyciem domen pamięci

Funkcja domeny pamięci jest przeznaczona dla tensorów, które są w większości wewnętrzne dla sterownika i nie wymagają częstego dostępu po stronie klienta. Przykładami takich tensorów są tensory stanów w modelach sekwencji. W przypadku tensorów, które potrzebują częstego dostępu do CPU po stronie klienta, lepiej jest używać współużytkowanych pul pamięci.

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

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

Przydzielony bufor jest wewnętrzny dla sterownika. Kierowca może wybrać dowolną lokalizację bufora lub układ danych. Po przydzieleniu bufora klient sterownika może odwoływać się do bufora lub wykonywać w nim interakcję za pomocą zwróconego tokena lub obiektu IBuffer.

Token z IDevice::allocate jest podawany przy odwoływaniu się do bufora jako jednego z obiektów MemoryPool w strukturze wykonania Request. Aby proces nie próbował uzyskać dostępu do bufora przydzielonego w innym procesie, sterownik musi zastosować odpowiednią weryfikację przy każdym użyciu bufora. Sterownik musi sprawdzić, czy wykorzystanie bufora jest jedną z ról BufferRole udostępnionych podczas alokacji, i musi natychmiast zakończyć wykonanie działania, jeśli użycie jest nielegalne.

Obiekt IBuffer służy do jednoznacznego kopiowania pamięci. W pewnych sytuacjach klient sterownika musi zainicjować bufor zarządzany przez sterownika z puli pamięci współdzielonej lub skopiować go do puli pamięci współdzielonej. Przykładowe zastosowania:

  • Inicjalizacja tensora stanu
  • Wyniki pośrednie dotyczące buforowania
  • Wykonanie kreacji awaryjnej na CPU

Aby obsługiwać te przypadki użycia, sterownik musi wdrożyć IBuffer::copyTo i IBuffer::copyFrom z usługami ashmem, mmap_fd i hardware_buffer_blob, jeśli obsługuje alokację domeny pamięci. Obsługiwanie trybu innego niż BLOB przez kierowcę hardware_buffer jest opcjonalne.

Podczas przydzielania bufora wymiary bufora można wyjąć z odpowiadających im operandów modelu wszystkich ról określonych przez funkcję BufferRole oraz wymiarów podanych w zasadzie BufferDesc. Po połączeniu wszystkich informacji o wymiarach bufor może mieć nieznane wymiary lub pozycję w rankingu. W takim przypadku bufor jest w stanie elastycznym, w którym wymiary są stałe, gdy są używane jako dane wejściowe modelu, a w stanie dynamicznym, gdy są używane jako dane wyjściowe modelu. Tego samego bufora można używać z różnymi kształtami danych wyjściowych w różnych wykonaniach, a sterownik musi prawidłowo zmieniać rozmiar bufora.

Domena pamięci jest funkcją opcjonalną. Kierowca może z różnych powodów stwierdzić, że nie może obsłużyć danej prośby o przyznanie alokacji. Na przykład:

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

Z bufora zarządzanego przez sterownika może być jednocześnie odczytywane kilka różnych wątków. Jednoczesny dostęp do bufora w celu zapisu lub odczytu i zapisu jest niezdefiniowany, ale nie może powodować awarii usługi sterownika ani blokowania na zawsze wywołania wywołującego. Sterownik może zwrócić błąd lub pozostawić zawartość bufora w stanie nieokreślonym.