Pule pamięci

Na tej stronie opisujemy struktury danych i metody używane do efektywnego przesyłania buforów operandów między sterownikami a ramą.

Podczas kompilowania modelu platforma przekazuje wartości stałych operandów do sterownika. 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 okres jest określony jako CONSTANT_COPY, wartości znajdują się w polu operandValues w strukturze modelu. Ponieważ wartości w wektorze HIDL są kopiowane podczas komunikacji między procesami (IPC), służą one zwykle tylko do przechowywania niewielkiej ilości danych, takich jak operandy skalarne (np. skalar aktywacji w funkcji ADD) i małe parametry tensora (np. tensor kształtu w funkcji RESHAPE).
  • Jeśli okres jest określony jako CONSTANT_REFERENCE, wartości znajdują się w polu pools w strukturze modelu. Podczas komunikacji między procesami duplikowane są tylko uchwyty puli pamięci współdzielonej, a nie 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 momencie wykonywania modelu framework udostępnia sterownikowi bufory danych 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 wykonania, aby reprezentować niezamapowany wspólny obszar pamięci. Sterownik powinien mapować pamięć w odpowiednim miejscu, aby można było z niej korzystać na podstawie nazwy typu danych hidl_memory. Obsługiwane nazwy pamięci:

  • ashmem: współdzielone wspomnienie na Androidzie. Więcej informacji znajdziesz w sekcji pamięć.
  • mmap_fd: pamięć udostępniona wspierana przez deskryptor pliku przez usługę mmap.
  • hardware_buffer_blob: współdzielona pamięć obsługiwana przez AHardwareBuffer w formacie AHARDWARE_BUFFER_FORMAT_BLOB. Dostępne w ramach Neural Networks (NN) HAL 1.2. Więcej informacji znajdziesz na stronie AHardwareBuffer.
  • hardware_buffer: współdzielona pamięć obsługiwana przez ogólny AHardwareBuffer, która 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 od NN HAL 1.2. Więcej informacji znajdziesz w artykule AHardwareBuffer.

Od wersji NN HAL 1.3 interfejs NNAPI obsługuje domeny pamięci, które udostępniają interfejsy alokacji dla buforów zarządzanych przez sterownik. Bufory zarządzane przez sterownika mogą też służyć jako dane wejściowe lub wyjściowe. Więcej informacji znajdziesz w artykule o 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 hardware_buffer_blob. Obsługa ogólnego trybu nieBLOB hardware_buffer i domen pamięci jest opcjonalna.

AHardwareBuffer

AHardwareBuffer to typ współdzielonej pamięci, który otacza bufor Gralloc. W Androidzie 10 interfejs Neural Networks API (NNAPI) obsługuje AHardwareBuffer, co pozwala sterownikowi wykonywać instrukcje bez kopiowania danych. Dzięki temu poprawia się wydajność i zużycie energii aplikacji. Na przykład aparat HALstack może przekazywać obiekty AHardwareBuffer do NNAPI na potrzeby zadań związanych z systemem uczącym się, używając uchwytów AHardwareBuffer wygenerowanych przez interfejsy NDK aparatu 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 o formacie AHARDWAREBUFFER_FORMAT_BLOB.

Informacje wymagane przez framework 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. Przygotowanie modelu musi zakończyć się niepowodzeniem, jeśli pole hidl_handle używane do stałej wartości operanda nie jest obsługiwane. Jeśli pole hidl_handle używane do danych wejściowych lub wyjściowych nie jest obsługiwane, wykonanie nie powiedzie się. W przypadku niepowodzenia przygotowania lub wykonania modelu zalecamy zwrócenie przez sterownik kodu błędu GENERAL_FAILURE.

Domeny wspomnień

W przypadku urządzeń z Androidem 11 lub nowszym interfejs NNAPI obsługuje domeny pamięci, które udostępniają interfejsy alokacji dla buforów zarządzanych przez sterownik. Umożliwia to przekazywanie danych z pamięci natywnej urządzenia w trakcie kolejnych wywołań, co eliminuje niepotrzebne kopiowanie i przekształcanie danych między kolejnymi wywołaniami tego samego sterownika. Ten proces ilustruje rysunek 1.

buforowanie przepływu danych z domenami pamięci z pamięcią podręczną i bez niej;

Rysunek 1. buforowanie przepływu danych za pomocą domen pamięci;

Funkcja domeny 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, zalecamy używanie wspólnych puli 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 framework 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 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 zgodnie z określonymi rolami.

Przydzielone miejsce na bufor jest wewnętrzne dla sterownika. Sterownik może wybrać dowolną lokalizację bufora lub układ danych. Gdy bufor zostanie przydzielony, klient sterownika może się do niego odwoływać lub wchodzić z nim w 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 zapobiec próbom uzyskania dostępu do bufora przydzielonego w innym procesie, sterownik musi stosować 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 niektórych sytuacjach klient sterownika musi zainicjować zarządzany przez niego bufor z wspólnego puli pamięci lub skopiować bufor do wspólnej puli pamięci. Przykładowe przypadki użycia:

  • Inicjalizacja tensora stanu
  • Buforowanie wyników pośrednich
  • Fallbackowe wykonywanie na procesorze

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

Podczas przydzielania 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 zawierać nieznane wymiary lub nieznaną pozycję. 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. 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 zdecydować, że nie może obsługiwać danego żądania alokacji z różnych powodów. Na przykład:

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

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