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 poluoperandValues
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 wADD
) i małe parametry tensorowe (np. tensor kształtu wRESHAPE
). - Jeśli okres ważności wynosi
CONSTANT_REFERENCE
, wartości znajdują się w polupools
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 formacieAHARDWARE_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 formatuAHARDWARE_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 ashmem
i mmap_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.
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::copyTo
i IBuffer::copyFrom
z parametrami ashmem
, mmap_fd
i hardware_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.