Pule pamięci

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

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

  • Jeśli okres ważności wynosi 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 okres ważności wynosi CONSTANT_REFERENCE, wartości znajdują się w polu pools struktury modelu. Podczas komunikacji międzyprocesowej duplikowane są tylko uchwyty pul pamięci współdzielonej, a nie kopiowane są wartości surowe. Dlatego przechowywanie dużej ilości danych (np. parametrów wagi w konwolucjach) w puli pamięci współdzielonej jest bardziej wydajne niż w wektorach HIDL.

W momencie 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 nieprzypisanej 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 to:

  • ashmem: wspomnienie udostępnione na Androidzie. Więcej informacji znajdziesz w artykule pamięć.
  • 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 od wersji 1.2 interfejsu HAL sieci neuronowych. Więcej informacji znajdziesz w artykule AHardwareBuffer.
  • hardware_buffer: pamięć współdzielona oparta na ogólnym buforze 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.Jest dostępny od wersji 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 alokatora dla buforów zarządzanych przez sterownik. Bufory zarządzane przez sterownik mogą też być używane jako dane wejściowe lub wyjściowe wykonywania. Więcej informacji znajdziesz w artykule Domeny pamięci.

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

AHardwareBuffer

AHardwareBuffer to rodzaj pamięci współdzielonej, która otacza bufor Gralloc. W Androidzie 10 interfejs Neural Networks API (NNAPI) obsługuje używanie AHardwareBuffer, co umożliwia sterownikowi wykonywanie operacji bez kopiowania danych, co z kolei zwiększa wydajność i zmniejsza zużycie energii przez aplikacje. 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 API 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ą 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ą kodowane w polu hidl_handle struktury hidl_memory. Pole hidl_handle zawiera native_handle, które koduje wszystkie wymagane metadane dotyczące bufora AHardwareBuffer lub Gralloc.

Sterownik musi prawidłowo dekodować podane pole hidl_handle i uzyskiwać 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 element hidl_handle i uzyskać dostęp do pamięci opisanej przez hidl_handle. Przygotowanie modelu musi się nie powieść, jeśli pole hidl_handle użyte w przypadku stałego operandu nie jest obsługiwane. Wykonanie musi się nie powieść, jeśli pole hidl_handle użyte jako operand wejściowy lub wyjściowy wykonania nie jest obsługiwane. Zalecamy, aby sterownik zwracał kod błędu GENERAL_FAILURE, jeśli przygotowanie lub wykonanie modelu nie powiedzie się.

Domeny pamięci

W przypadku urządzeń z Androidem 11 lub nowszym interfejs 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, tłumiąc niepotrzebne kopiowanie i przekształcanie danych między kolejnymi wykonaniami na tym samym sterowniku. Ten proces ilustruje rysunek 1.

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

Rysunek 1. Buforowanie przepływu danych za pomocą 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 stanu w modelach sekwencyjnych. 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ę domeny pamięci, zaimplementuj IDevice::allocate , aby umożliwić platformie żądanie przydzielania bufora zarządzanego przez sterownik. Podczas przydzielania bufora platforma udostępnia te właściwości i wzorce użycia:

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

Przydzielony bufor jest wewnętrzny dla sterownika. Sterownik może wybrać dowolną lokalizację bufora lub układ danych. Gdy bufor zostanie 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 podawany, gdy bufor jest przywoływany jako jeden z obiektów MemoryPool w strukturze Request wykonania. Aby zapobiec próbom uzyskania dostępu do bufora przydzielonego w innym procesie, sterownik musi przeprowadzać odpowiednią weryfikację przy każdym użyciu bufora. Sterownik musi sprawdzić, czy użycie bufora jest jedną z ról BufferRole podanych podczas przydzielania, i natychmiast przerwać wykonywanie, jeśli użycie jest nieprawidłowe.

Obiekt IBuffer jest używany 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ładowe przypadki użycia:

  • Inicjowanie tensora stanu
  • Buforowanie wyników pośrednich
  • Wykonywanie awaryjne na procesorze

Aby obsługiwać te przypadki użycia, sterownik musi implementować funkcje IBuffer::copyToIBuffer::copyFrom z parametrami ashmem, mmap_fdhardware_buffer_blob, jeśli obsługuje przydzielanie domen pamięci. Obsługa trybu innego niż BLOB jest opcjonalna dla sterownika hardware_buffer.

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 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, i dynamiczne, 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łużyć danego żądania przydziału z różnych 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 nieokreślony, ale nie może powodować awarii usługi sterownika ani blokować wywołującego w nieskończoność. Sterownik może zwrócić błąd lub pozostawić zawartość bufora w nieokreślonym stanie.