Android Neural Networks API (NNAPI) to interfejs API w Androidzie C zaprojektowany do uruchamiania. w przypadku systemów uczących się na urządzeniach z Androidem o dużej mocy obliczeniowej. Interfejs NNAPI został zaprojektowany z myślą o zapewnieniu podstawowej warstwy funkcji dla wyższego poziomu systemów uczących się, takich jak TensorFlow Lite (w języku angielskim) i Caffe2, które budują i trenują sieci neuronowe. Interfejs API jest dostępny na wszystkich urządzeniach z Androidem 8.1 (poziom interfejsu API 27) lub nowszym.
NNAPI obsługuje wnioskowanie przez stosowanie danych z urządzeń z Androidem do poprzedniego wytrenowanych, zdefiniowanych przez programistę modeli. Przykłady wnioskowania to klasyfikowanie obrazów, przewidywania zachowań użytkowników i wybierania odpowiednich reakcji wyszukiwanego hasła.
Określanie lokalizacji na urządzeniu przynosi wiele korzyści:
- Opóźnienie: nie musisz wysyłać żądania przez połączenie sieciowe. oczekiwanie na odpowiedź. Może to być krytyczne np. w przypadku aplikacji wideo, przetwarzający kolejne klatki z kamery.
- Dostępność: aplikacja działa nawet poza zasięgiem sieci.
- Szybkość: nowy sprzęt dostosowany do przetwarzania sieci neuronowych. zapewnia znacznie szybsze obliczenia niż pojedynczy procesor do zwykłych obciążeń.
- Prywatność: dane nie opuszczają urządzenia z Androidem.
- Koszt: gdy wszystkie obliczenia są wykonywane na serwerze, nie jest potrzebna farma serwerów na urządzeniu z Androidem.
Są też inne kompromisy, o których deweloper powinien pamiętać:
- Wykorzystanie systemu: ocena sieci neuronowych wymaga może zużywać więcej energii. Zastanów się, monitorowania stanu baterii, jeśli stanowi to problem dla aplikacji, przy długotrwałych obliczeniach.
- Rozmiar aplikacji: zwróć uwagę na rozmiar modeli. Modele mogą zajmujące wiele megabajtów miejsca. Jeśli łączysz duże modele w pakiecie APK niekorzystnie na użytkowników, rozważ pobranie po zainstalowaniu aplikacji, przy użyciu mniejszych modeli lub do obliczeń w chmurze. NNAPI nie udostępnia funkcji uruchamiania i modele w chmurze.
Zobacz Przykład interfejsu Android Neural Networks API aby zobaczyć przykład użycia NNAPI.
Omówienie środowiska wykonawczego interfejsu Neural Networks API
NNAPI ma być wywoływane przez biblioteki, platformy i narzędzia systemów uczących się które pozwalają programistom trenować modele poza urządzeniem i wdrażać je na urządzeniach z Androidem urządzenia. Aplikacje zwykle nie korzystają bezpośrednio z NNAPI, ale zamiast tego wykorzystują systemy uczące się wyższego poziomu. Z kolei platformy te mogłyby wykorzystać NNAPI do wykonywania operacji wnioskowania z akceleracją sprzętową na obsługiwanych urządzeniach.
Na podstawie wymagań aplikacji i możliwości sprzętowych urządzenia z Androidem urządzenia, środowisko wykonawcze sieci neuronowej Androida może skutecznie rozpowszechniać i obsługuje zadania obliczeniowe na dostępnych procesorach na urządzeniu, w tym sprzęt do sieci neuronowej, procesory graficzne (GPU) i sygnał cyfrowy procesory (DSP).
W przypadku urządzeń z Androidem, które nie mają specjalistycznego sterownika dostawcy, środowisko wykonawcze NNAPI uruchamia żądania na CPU.
Rysunek 1 przedstawia ogólną architekturę systemu NNAPI.
Model programowania interfejsu Neural Networks API
Aby wykonywać obliczenia przy użyciu NNAPI, musisz najpierw utworzyć , który określa obliczenia do wykonania. Ten wykres obliczeniowy, połączony z danymi wejściowymi (na przykład wagi i uprzedzeń przekazane z interfejsu systemów uczących się) stanowi model oceny środowiska wykonawczego NNAPI.
NNAPI wykorzystuje 4 główne abstrakcje:
- Model: graf obliczeniowy działań matematycznych i stałej wartości.
wartości nabytych w procesie trenowania. Te operacje dotyczą
sieci neuronowych. Są to obiekty dwuwymiarowe (2D)
convolution,
logistyka
(sigmoid).
aktywacji,
liniowy wyprostowany
aktywacji (ReLU). Tworzenie modelu jest operacją synchroniczną.
Po utworzeniu można go używać ponownie w wątkach i kompilacjach.
W NNAPI model jest przedstawiany jako
ANeuralNetworksModel
instancji. - Kompilacja: reprezentuje konfigurację skompilowania modelu NNAPI w
kodu niższego poziomu. Tworzenie kompilacji jest operacją synchroniczną. Jednorazowo
został utworzony, można go używać ponownie w wątkach i wykonaniach. W
NNAPI, każda kompilacja jest przedstawiana jako
ANeuralNetworksCompilation
instancji. - Pamięć: reprezentuje pamięć współdzieloną, pliki mapowane w pamięci i podobną pamięć.
bufory. Dzięki buforowi pamięci środowisko wykonawcze NNAPI może przesyłać dane do sterowników
efektywniejsza. Aplikacja zwykle tworzy jeden bufor współdzielonej pamięci, który
zawiera wszystkie tensory potrzebne do zdefiniowania modelu. Możesz też używać pamięci
bufory do przechowywania danych wejściowych i wyjściowych instancji wykonawczej. W NNAPI
każdy bufor pamięci jest przedstawiany jako
ANeuralNetworksMemory
instancji. Wykonywanie: interfejs do stosowania modelu NNAPI do zbioru danych wejściowych oraz aby zebrać wyniki. Wykonywanie może być wykonywane synchronicznie lub asynchronicznie.
W przypadku wykonywania asynchronicznego wiele wątków mogą czekać na to samo wykonanie. Po zakończeniu tego wykonania wszystkie wątki zostaną został zwolniony.
W NNAPI każde wykonanie jest przedstawiane jako
ANeuralNetworksExecution
instancji.
Rysunek 2 przedstawia podstawowy proces programowania.
W pozostałej części tej sekcji znajdziesz opis czynności, które musisz wykonać, aby skonfigurować model NNAPI, wykonywać obliczenia, skompilować model i uruchomić go.
Zapewnianie dostępu do danych treningowych
Wytrenowane wagi i dane dotyczące uprzedzeń są prawdopodobnie przechowywane w pliku. Aby udostępnić
Środowisko wykonawcze NNAPI zapewniające skuteczny dostęp do tych danych, utwórz
ANeuralNetworksMemory
przez wywołanie funkcji
ANeuralNetworksMemory_createFromFd()
i przekazywać deskryptor otwartego pliku danych. Dodatkowo
określ flagi ochrony pamięci i przesunięcie, w którym region pamięci współdzielonej
zaczyna się w pliku.
// Create a memory buffer from the file that contains the trained data
ANeuralNetworksMemory* mem1 = NULL;
int fd = open("training_data", O_RDONLY);
ANeuralNetworksMemory_createFromFd(file_size, PROT_READ, fd, 0, &mem1);
Chociaż w tym przykładzie korzystamy tylko z jednej
ANeuralNetworksMemory
dla wszystkich wag, można użyć więcej niż jednej
ANeuralNetworksMemory
wystąpienie dla wielu plików.
Używaj natywnych buforów sprzętowych
Możesz używać natywnych buforów sprzętowych.
dla danych wejściowych i wyjściowych modelu oraz stałych wartości argumentów. W pewnych przypadkach
Akcelerator NNAPI ma dostęp
AHardwareBuffer
obiektów bez konieczności kopiowania danych przez sterownika. AHardwareBuffer
ma wiele
różne konfiguracje i nie każdy akcelerator NNAPI może obsługiwać wszystkie
dla każdej z nich. Z tego powodu zapoznaj się z ograniczeniami
znajduje się w
Dokumentacja referencyjna ANeuralNetworksMemory_createFromAHardwareBuffer
i testować je z wyprzedzeniem na urządzeniach docelowych, aby zapewnić kompilacje i wykonania.
w których wartości AHardwareBuffer
działają zgodnie z oczekiwaniami, przy użyciu
przypisanie urządzenia, aby określić akcelerator.
Aby zezwolić środowisku wykonawczym NNAPI na dostęp do obiektu AHardwareBuffer
, utwórz
ANeuralNetworksMemory
.
przez wywołanie funkcji
ANeuralNetworksMemory_createFromAHardwareBuffer
i przekazywanie w ramach funkcji
AHardwareBuffer
– tak jak w tym przykładowym kodzie:
// Configure and create AHardwareBuffer object AHardwareBuffer_Desc desc = ... AHardwareBuffer* ahwb = nullptr; AHardwareBuffer_allocate(&desc, &ahwb); // Create ANeuralNetworksMemory from AHardwareBuffer ANeuralNetworksMemory* mem2 = NULL; ANeuralNetworksMemory_createFromAHardwareBuffer(ahwb, &mem2);
Gdy NNAPI nie będzie już potrzebować dostępu do obiektu AHardwareBuffer
, zwolnij
odpowiednie wystąpienie ANeuralNetworksMemory
:
ANeuralNetworksMemory_free(mem2);
Uwaga:
- Za pomocą
AHardwareBuffer
tylko dla całego bufora; nie można użyć z parametrARect
. - Środowisko wykonawcze NNAPI nie zostanie usunięte bufora. Musisz się upewnić, że bufory wejściowe i wyjściowe są dostępne przed zaplanowaniem wykonania.
- Brak wsparcia dla synchronizacji deskryptorów plików fence.
- Dla:
AHardwareBuffer
z w zależności od dostawcy, zasady użytkowania i formaty aby ustalić, czy to klient czy kierowca odpowiada za opróżnienie pamięci podręcznej.
Model
Model jest podstawową jednostką obliczeniową w NNAPI. Każdy model jest zdefiniowany za pomocą co najmniej jednego operandu lub operacji.
Operatory
Argumenty to obiekty danych używane do definiowania wykresu. Są to m.in. dane wejściowe i danymi wyjściowymi modelu, czyli węzłami pośrednimi zawierającymi dane, przepływów między operacjami i stałe przekazywane do tych operacji.
Istnieją 2 typy operandów, które można dodać do modeli NNAPI: skalary i tendencje.
Argument skalarny reprezentuje jedną wartość. NNAPI obsługuje wartości skalarne w wartościach logicznych, 16-bitowa, 32-bitowa zmiennoprzecinkowa, 32-bitowa liczba całkowita i bez znaku 32-bitowe formaty liczb całkowitych.
Większość operacji w NNAPI obejmuje tensory. Tensory to tablice n-wymiarowe. NNAPI obsługuje tensory z 16-bitową zmiennoprzecinkową, 32-bitową zmiennoprzecinkową i 8-bitową kwantyzowana, 16-bitowa kwantyzowana, 32-bitowa liczba całkowita i 8-bitowa wartości logiczne.
Na przykład na ilustracji 3 przedstawiono model z dwoma operacjami: dodawanie po czym następuje mnożenie. Model pobiera tensor wejściowy i generuje jeden tensor wyjściowy.
Powyższy model ma 7 operandów. Te operandy są identyfikowane pośrednio przez indeks kolejności ich dodania do modelu. Pierwszy operand ma indeks równy 0, drugi indeks równy 1 itd. Argumenty 1, 2, 3, a 5 to operandy stałe.
Kolejność dodawania operandów nie ma znaczenia. Na przykład model operand wyjściowy może być pierwszym dodanym operandem. Ważne jest, aby używać funkcji prawidłową wartość indeksu w odniesieniu do operandu.
Operatory mają typy. Są one określane podczas dodawania do modelu.
operandu nie można używać jako danych wejściowych i wyjściowych modelu.
Każdy operand musi być danymi wejściowymi modelu, stałą lub wyjściowym operandem dokładnie 1 operację.
Więcej informacji o używaniu operandów znajdziesz w artykule Więcej informacji o operandach
Zarządzanie
Operacja określa obliczenia do wykonania. Każda operacja składa się tych elementów:
- typ operacji (np. dodawanie, mnożenie, splot),
- listę indeksów operandów używanych przez operację jako dane wejściowe;
- lista indeksów operandów używanych przez operację na potrzeby danych wyjściowych.
Kolejność na tych listach ma znaczenie. zobacz Dokumentacja interfejsu NNAPI API dla oczekiwanych danych wejściowych. i dane wyjściowe poszczególnych typów operacji.
Musisz dodać do modelu operandy, które wykorzystuje lub generuje operacja przed dodaniem operacji.
Kolejność dodawania operacji nie ma znaczenia. NNAPI opiera się na zależności określone przez graf obliczeniowy operandów i operacji na określają kolejność wykonywania operacji.
Operacje obsługiwane przez NNAPI zostały opisane w tabeli poniżej:
Znany problem na poziomie interfejsu API 28: Podczas uzyskiwania zaliczenia
ANEURALNETWORKS_TENSOR_QUANT8_ASYMM
tensorów do potęgi
ANEURALNETWORKS_PAD
jest dostępna w Androidzie 9 (poziom interfejsu API 28) i nowszych,
dane wyjściowe z NNAPI mogą nie pasować do danych wyjściowych z systemów uczących się wyższego poziomu
platform, takich jak
TensorFlow Lite – Ty
powinna zamiast tego przekazywać tylko
ANEURALNETWORKS_TENSOR_FLOAT32
Problem został rozwiązany w Androidzie 10 (poziom interfejsu API 29) i nowszych.
Tworzenie modeli
W poniższym przykładzie tworzymy model dwuoperacyjny znaleziony w rys. 3.
Aby utworzyć model, wykonaj te czynności:
Wywołaj funkcję
ANeuralNetworksModel_create()
do zdefiniowania pustego modelu.ANeuralNetworksModel* model = NULL; ANeuralNetworksModel_create(&model);
Dodaj operandy do modelu, wywołując
ANeuralNetworks_addOperand()
Typy ich danych definiuje się za pomocąANeuralNetworksOperandType
do struktury danych.// In our example, all our tensors are matrices of dimension [3][4] ANeuralNetworksOperandType tensor3x4Type; tensor3x4Type.type = ANEURALNETWORKS_TENSOR_FLOAT32; tensor3x4Type.scale = 0.f; // These fields are used for quantized tensors tensor3x4Type.zeroPoint = 0; // These fields are used for quantized tensors tensor3x4Type.dimensionCount = 2; uint32_t dims[2] = {3, 4}; tensor3x4Type.dimensions = dims;
// We also specify operands that are activation function specifiers ANeuralNetworksOperandType activationType; activationType.type = ANEURALNETWORKS_INT32; activationType.scale = 0.f; activationType.zeroPoint = 0; activationType.dimensionCount = 0; activationType.dimensions = NULL;
// Now we add the seven operands, in the same order defined in the diagram ANeuralNetworksModel_addOperand(model, &tensor3x4Type); // operand 0 ANeuralNetworksModel_addOperand(model, &tensor3x4Type); // operand 1 ANeuralNetworksModel_addOperand(model, &activationType); // operand 2 ANeuralNetworksModel_addOperand(model, &tensor3x4Type); // operand 3 ANeuralNetworksModel_addOperand(model, &tensor3x4Type); // operand 4 ANeuralNetworksModel_addOperand(model, &activationType); // operand 5 ANeuralNetworksModel_addOperand(model, &tensor3x4Type); // operand 6W przypadku operandów o wartościach stałych, takich jak wagi i uprzedzenia, które uzyskane w trakcie procesu trenowania, należy użyć funkcji
ANeuralNetworksModel_setOperandValue()
orazANeuralNetworksModel_setOperandValueFromMemory()
funkcji.W tym przykładzie ustawiliśmy stałe wartości z pliku danych treningowych odpowiadający buforowi pamięci, który utworzyliśmy w sekcji Zapewnij dostęp do danych treningowych.
// In our example, operands 1 and 3 are constant tensors whose values were // established during the training process const int sizeOfTensor = 3 * 4 * 4; // The formula for size calculation is dim0 * dim1 * elementSize ANeuralNetworksModel_setOperandValueFromMemory(model, 1, mem1, 0, sizeOfTensor); ANeuralNetworksModel_setOperandValueFromMemory(model, 3, mem1, sizeOfTensor, sizeOfTensor);
// We set the values of the activation operands, in our example operands 2 and 5 int32_t noneValue = ANEURALNETWORKS_FUSED_NONE; ANeuralNetworksModel_setOperandValue(model, 2, &noneValue, sizeof(noneValue)); ANeuralNetworksModel_setOperandValue(model, 5, &noneValue, sizeof(noneValue));W przypadku każdej operacji na ukierunkowanym wykresie, który chcesz obliczyć, dodaj do modelu przez wywołanie funkcji
ANeuralNetworksModel_addOperation()
.Jako parametry tego wywołania aplikacja musi zawierać:
- typ operacji,
- liczba wartości wejściowych
- tablica indeksów dla operandów wejściowych
- liczba wartości wyjściowych
- tablica indeksów operandów wyjściowych
Pamiętaj, że operandu nie można używać jednocześnie dla danych wejściowych i wyjściowych tego samego argumentu .
// We have two operations in our example // The first consumes operands 1, 0, 2, and produces operand 4 uint32_t addInputIndexes[3] = {1, 0, 2}; uint32_t addOutputIndexes[1] = {4}; ANeuralNetworksModel_addOperation(model, ANEURALNETWORKS_ADD, 3, addInputIndexes, 1, addOutputIndexes);
// The second consumes operands 3, 4, 5, and produces operand 6 uint32_t multInputIndexes[3] = {3, 4, 5}; uint32_t multOutputIndexes[1] = {6}; ANeuralNetworksModel_addOperation(model, ANEURALNETWORKS_MUL, 3, multInputIndexes, 1, multOutputIndexes);Określ, które operandy powinien być traktowany przez model jako dane wejściowe i wyjściowe, Wywołując funkcję
ANeuralNetworksModel_identifyInputsAndOutputs()
.// Our model has one input (0) and one output (6) uint32_t modelInputIndexes[1] = {0}; uint32_t modelOutputIndexes[1] = {6}; ANeuralNetworksModel_identifyInputsAndOutputs(model, 1, modelInputIndexes, 1 modelOutputIndexes);
Opcjonalnie określ, czy
ANEURALNETWORKS_TENSOR_FLOAT32
można obliczyć z dokładnością lub do wartości 16-bitowy format zmiennoprzecinkowy IEEE 754 przez wywołanieANeuralNetworksModel_relaxComputationFloat32toFloat16()
Zadzwoń pod numer
ANeuralNetworksModel_finish()
aby dokończyć definicję modelu. Jeśli nie wystąpią błędy, zwraca kod wynikuANEURALNETWORKS_NO_ERROR
ANeuralNetworksModel_finish(model);
Utworzony model możesz skompilować dowolną liczbę razy i wykonywać każdy z nich dowolną kompilację.
Sterowanie przepływem pracy
Aby włączyć przepływ sterowania w modelu NNAPI, wykonaj te czynności:
Utwórz odpowiednie podgrafy wykonania (podgrafy
then
ielse
). dla instrukcjiIF
, podgrafówcondition
ibody
dla pętliWHILE
) jako samodzielne modeleANeuralNetworksModel*
:ANeuralNetworksModel* thenModel = makeThenModel(); ANeuralNetworksModel* elseModel = makeElseModel();
Utwórz operandy odwołujące się do tych modeli w modelu zawierającym sterowanie przepływem:
ANeuralNetworksOperandType modelType = { .type = ANEURALNETWORKS_MODEL, }; ANeuralNetworksModel_addOperand(model, &modelType); // kThenOperandIndex ANeuralNetworksModel_addOperand(model, &modelType); // kElseOperandIndex ANeuralNetworksModel_setOperandValueFromModel(model, kThenOperandIndex, &thenModel); ANeuralNetworksModel_setOperandValueFromModel(model, kElseOperandIndex, &elseModel);
Dodaj operację przepływu sterowania:
uint32_t inputs[] = {kConditionOperandIndex, kThenOperandIndex, kElseOperandIndex, kInput1, kInput2, kInput3}; uint32_t outputs[] = {kOutput1, kOutput2}; ANeuralNetworksModel_addOperation(model, ANEURALNETWORKS_IF, std::size(inputs), inputs, std::size(output), outputs);
Kompilacja
Etap kompilacji określa, które procesory zostaną wykonane i prosi odpowiednich kierowców o przygotowanie się do jej realizacji. Może to spowodować obejmuje generowanie kodu maszynowego konkretnego procesora będzie działać.
Aby skompilować model, wykonaj te czynności:
Wywołaj funkcję
ANeuralNetworksCompilation_create()
w celu utworzenia nowej instancji kompilacji.// Compile the model ANeuralNetworksCompilation* compilation; ANeuralNetworksCompilation_create(model, &compilation);
Opcjonalnie możesz użyć przypisania urządzenia, aby wybrać urządzenia, na których mają zostać uruchomione aplikacje.
Możesz też opcjonalnie wpływać na zamianę czasu działania na zasilanie z baterii. i wydajnością pracy. Aby to zrobić, zadzwoń pod numer
ANeuralNetworksCompilation_setPreference()
// Ask to optimize for low power consumption ANeuralNetworksCompilation_setPreference(compilation, ANEURALNETWORKS_PREFER_LOW_POWER);
Możesz określić następujące ustawienia:
ANEURALNETWORKS_PREFER_LOW_POWER
: Wolę wykonywać czynności w sposób, który minimalizuje obciążenie baterii. To pożądane w przypadku często wykonywanych kompilacji.ANEURALNETWORKS_PREFER_FAST_SINGLE_ANSWER
: Wolę jak najszybciej zwracać jedną odpowiedź, nawet jeśli powoduje to taką sytuację większe zużycie energii. Jest to ustawienie domyślne.ANEURALNETWORKS_PREFER_SUSTAINED_SPEED
: Preferuj maksymalizację przepustowości kolejnych klatek, na przykład kolejne klatki przesyłane z kamery.
Opcjonalnie możesz skonfigurować buforowanie kompilacji, wywołując
ANeuralNetworksCompilation_setCaching
// Set up compilation caching ANeuralNetworksCompilation_setCaching(compilation, cacheDir, token);
Użyj formatu
getCodeCacheDir()
zacacheDir
. Podana wartośćtoken
musi być unikalna dla każdego modelu w obrębie aplikacji.Dokończ definicję kompilacji, wywołując
ANeuralNetworksCompilation_finish()
Jeśli nie ma błędów, ta funkcja zwraca kod wynikuANEURALNETWORKS_NO_ERROR
ANeuralNetworksCompilation_finish(compilation);
Wykrywanie i przypisywanie urządzeń
Na urządzeniach z Androidem 10 (poziom interfejsu API 29) lub nowszym NNAPI zapewnia które pozwalają bibliotekom i aplikacjom platformy do uczenia maszynowego pobierać informacje o dostępnych urządzeniach i określenie urządzeń, które mają być używane do Podanie informacji o dostępnych urządzeniach umożliwia aplikacjom dokładną wersję sterowników urządzenia, dzięki której można uniknąć znanych kompatybilności. Umożliwiając aplikacjom określenie, które urządzenia mają uruchamiają różne sekcje modelu, aplikacje mogą być zoptymalizowane pod kątem Androida, na których zostały wdrożone.
Wykrywanie urządzeń
Używaj
ANeuralNetworks_getDeviceCount
, aby sprawdzić liczbę dostępnych urządzeń. W przypadku każdego urządzenia użyj
ANeuralNetworks_getDevice
aby ustawić odwołanie do tego urządzenia w instancji ANeuralNetworksDevice
.
Gdy masz już numer referencyjny urządzenia, możesz dowiedzieć się więcej za pomocą tych funkcji:
ANeuralNetworksDevice_getFeatureLevel
ANeuralNetworksDevice_getName
ANeuralNetworksDevice_getType
ANeuralNetworksDevice_getVersion
Przypisanie urządzenia
Używaj
ANeuralNetworksModel_getSupportedOperationsForDevices
które operacje modelu można uruchamiać na określonych urządzeniach.
Aby określić, które akceleratory mają być używane do wykonania, wywołaj
ANeuralNetworksCompilation_createForDevices
w miejsce ANeuralNetworksCompilation_create
.
Użyj powstałego w ten sposób obiektu ANeuralNetworksCompilation
w zwykły sposób.
Funkcja zwraca błąd, jeśli podany model zawiera operacje, które są
nie jest obsługiwane przez wybrane urządzenia.
Jeśli podasz wiele urządzeń, środowisko wykonawcze odpowiada za dystrybucję do pracy na różnych urządzeniach.
Tak jak w przypadku innych urządzeń, implementacja CPU NNAPI jest reprezentowana przez
Pole ANeuralNetworksDevice
o nazwie nnapi-reference
i typie
ANEURALNETWORKS_DEVICE_TYPE_CPU
Podczas rozmowy
ANeuralNetworksCompilation_createForDevices
, implementacja procesora nie jest
używana do obsługi przypadków błędów przy kompilowaniu i wykonywaniu modeli.
Obowiązkiem aplikacji jest podział modelu na modele podrzędne,
może działać na określonych urządzeniach. Aplikacje, które nie wymagają ręcznej obsługi
partycjonowanie powinno nadal wywoływać prostsze
ANeuralNetworksCompilation_create
na wszystkich dostępnych urządzeniach (w tym procesorach), aby przyspieszyć
model atrybucji. Jeśli określony model nie będzie w pełni obsługiwany przez określone urządzenia
za pomocą ANeuralNetworksCompilation_createForDevices
,
ANEURALNETWORKS_BAD_DATA
.
.
Partycjonowanie modelu
Jeśli dla modelu dostępnych jest wiele urządzeń, środowisko wykonawcze NNAPI
rozdziela pracę między urządzenia. Jeśli na przykład więcej niż jedno urządzenie zostało
przekazano do ANeuralNetworksCompilation_createForDevices
, wszystkie określone
które są brane pod uwagę przy przydzielaniu utworu. Pamiętaj, że jeśli procesor
nie ma na liście, wykonywanie procesora będzie wyłączone. Gdy używasz ANeuralNetworksCompilation_create
pod uwagę brane będą wszystkie dostępne urządzenia, w tym procesor.
Dystrybucja odbywa się przez wybranie z listy dostępnych urządzeń dla każdego
działania w ramach modelu, a także na urządzenie obsługujące
deklaracja o najwyższej wydajności, tj. najkrótszym czasie wykonywania lub
najmniejsze zużycie energii w zależności od ustawienia wybranego przez
do klienta. Ten algorytm partycjonowania nie uwzględnia możliwych
i zmniejsza wydajność powodowaną przez zamówienie reklamowe między różnymi podmiotami przetwarzającymi.
określenie wielu procesorów (jawnie w przypadku funkcji
ANeuralNetworksCompilation_createForDevices
lub domyślnie za pomocą funkcji
ANeuralNetworksCompilation_create
), ważne jest profilowanie wyniku
aplikacji.
Aby dowiedzieć się, jak Twój model został podzielony przez NNAPI, sprawdź
Dzienniki Androida dotyczące wiadomości (na poziomie INFO z tagiem ExecutionPlan
):
ModelBuilder::findBestDeviceForEachOperation(op-name): device-index
op-name
to opisowa nazwa operacji na wykresie oraz
device-index
to indeks urządzenia kandydata na liście urządzeń.
Ta lista zawiera dane wejściowe dla funkcji ANeuralNetworksCompilation_createForDevices
lub, jeśli używasz ANeuralNetworksCompilation_createForDevices
, listę urządzeń
zwracany w przypadku iteracji na wszystkich urządzeniach z użyciem: ANeuralNetworks_getDeviceCount
i
ANeuralNetworks_getDevice
Wiadomość (na poziomie INFO z tagiem ExecutionPlan
):
ModelBuilder::partitionTheWork: only one best device: device-name
Ten komunikat oznacza, że cały wykres został przyspieszony na urządzeniu
device-name
Realizacja
Etap wykonywania stosuje model do zbioru danych wejściowych i przechowuje dane wyjściowe w co najmniej jednym buforze użytkownika lub do przestrzeni pamięci, w której aplikacja i przydzielonych.
Aby wykonać skompilowany model, wykonaj te czynności:
Wywołaj funkcję
ANeuralNetworksExecution_create()
w celu utworzenia nowej instancji wykonania.// Run the compiled model against a set of inputs ANeuralNetworksExecution* run1 = NULL; ANeuralNetworksExecution_create(compilation, &run1);
Określ, gdzie aplikacja odczytuje wartości wejściowe obliczeń. Twoja aplikacja może odczytywać wartości wejściowe z bufora użytkownika lub przydzielonego miejsca w pamięci Połączenie
ANeuralNetworksExecution_setInput()
lubANeuralNetworksExecution_setInputFromMemory()
.// Set the single input to our sample model. Since it is small, we won't use a memory buffer float32 myInput[3][4] = { ...the data... }; ANeuralNetworksExecution_setInput(run1, 0, NULL, myInput, sizeof(myInput));
Określ, gdzie aplikacja zapisuje wartości wyjściowe. Aplikacja może zapisywać wartości wyjściowe w bufor użytkownika lub przydzielone miejsce w pamięci, wywołując
ANeuralNetworksExecution_setOutput()
lubANeuralNetworksExecution_setOutputFromMemory()
.// Set the output float32 myOutput[3][4]; ANeuralNetworksExecution_setOutput(run1, 0, NULL, myOutput, sizeof(myOutput));
Zaplanuj rozpoczęcie wykonania, wywołując metodę
ANeuralNetworksExecution_startCompute()
. Jeśli nie ma błędów, ta funkcja zwraca kod wynikuANEURALNETWORKS_NO_ERROR
// Starts the work. The work proceeds asynchronously ANeuralNetworksEvent* run1_end = NULL; ANeuralNetworksExecution_startCompute(run1, &run1_end);
Zadzwoń pod numer
ANeuralNetworksEvent_wait()
i czeka na zakończenie wykonywania. Jeśli wykonanie zostało powodzenie, funkcja zwraca kod wynikuANEURALNETWORKS_NO_ERROR
Oczekiwanie można wykonać w innym wątku niż ten, w którym rozpoczyna się wykonanie.// For our example, we have no other work to do and will just wait for the completion ANeuralNetworksEvent_wait(run1_end); ANeuralNetworksEvent_free(run1_end); ANeuralNetworksExecution_free(run1);
Opcjonalnie do skompilowanego modelu możesz zastosować inny zestaw danych wejściowych, wykonując za pomocą tej samej instancji kompilacji do utworzenia nowej
ANeuralNetworksExecution
instancji.// Apply the compiled model to a different set of inputs ANeuralNetworksExecution* run2; ANeuralNetworksExecution_create(compilation, &run2); ANeuralNetworksExecution_setInput(run2, ...); ANeuralNetworksExecution_setOutput(run2, ...); ANeuralNetworksEvent* run2_end = NULL; ANeuralNetworksExecution_startCompute(run2, &run2_end); ANeuralNetworksEvent_wait(run2_end); ANeuralNetworksEvent_free(run2_end); ANeuralNetworksExecution_free(run2);
Wykonanie synchroniczne
Wykonywanie asynchroniczne wymaga czasu na powstawanie i synchronizowanie wątków. Poza tym opóźnienie może być bardzo zmienne, opóźnienia do 500 mikrosekund między powiadomieniem wątku lub i czas jego ostatecznego powiązania z rdzeniem procesora.
Aby skrócić czas oczekiwania, możesz zamiast tego skierować aplikację tak, aby synchronizowała
przez wywołanie wnioskowania dla środowiska wykonawczego. Wywołanie to zostanie zwrócone tylko wtedy, gdy wnioskowanie ma już
została ukończona, a nie zwracana po rozpoczęciu wnioskowania. Zamiast tego
połączeń
ANeuralNetworksExecution_startCompute
dla asynchronicznego wywołania wnioskowania do środowiska wykonawczego, aplikacja wywołuje
ANeuralNetworksExecution_compute
, aby synchronicznie wywoływać środowisko wykonawcze. Wywołanie:
ANeuralNetworksExecution_compute
nie jedzie: ANeuralNetworksEvent
i
– urządzenie nie jest sparowane z połączeniem z numerem ANeuralNetworksEvent_wait
.
Uruchomienia serii
Na urządzeniach z Androidem 10 (poziom interfejsu API 29) lub nowszym NNAPI obsługuje tryb burst
wykonań za pomocą metody
ANeuralNetworksBurst
obiektu. Wykonania serii to sekwencja wykonań tej samej kompilacji
następują w krótkich odstępach czasu, np. w klatkach aparatu.
przechwytywanie lub kolejne próbki audio. Korzystanie z obiektów ANeuralNetworksBurst
może
przyspieszają wykonywanie, ponieważ wskazują akceleratory, że zasoby mogą
mogą być wielokrotnie wykorzystywane w różnych wykonaniach, a akceleratory powinny pozostać w
stanu wysokiej wydajności przez cały czas trwania serii.
ANeuralNetworksBurst
wprowadza tylko niewielką zmianę w normalnym działaniu
ścieżki konwersji. Obiekt z serią tworzy się za pomocą
ANeuralNetworksBurst_create
,
Jak widać w tym fragmencie kodu:
// Create burst object to be reused across a sequence of executions ANeuralNetworksBurst* burst = NULL; ANeuralNetworksBurst_create(compilation, &burst);
Wykonania serii są synchroniczne. Jednak zamiast używania
ANeuralNetworksExecution_compute
dla każdego zależności, łącz różne wartości
ANeuralNetworksExecution
obiekty z takim samym elementem ANeuralNetworksBurst
w wywołaniach funkcji
ANeuralNetworksExecution_burstCompute
.
// Create and configure first execution object // ... // Execute using the burst object ANeuralNetworksExecution_burstCompute(execution1, burst); // Use results of first execution and free the execution object // ... // Create and configure second execution object // ... // Execute using the same burst object ANeuralNetworksExecution_burstCompute(execution2, burst); // Use results of second execution and free the execution object // ...
Zwolnij obiekt ANeuralNetworksBurst
za pomocą
ANeuralNetworksBurst_free
.
gdy nie są już potrzebne.
// Cleanup ANeuralNetworksBurst_free(burst);
Asynchroniczne kolejki poleceń i zabezpieczone wykonywanie
W Androidzie 11 i nowszych NNAPI obsługuje dodatkowy sposób planowania
wykonanie asynchroniczne za pomocą metody
ANeuralNetworksExecution_startComputeWithDependencies()
. Gdy użyjesz tej metody, wykonanie czeka na wszystkie
zdarzenia, które mają być sygnalizowane przed rozpoczęciem oceny. Gdy wykonanie
i dane wyjściowe są gotowe do wykorzystania, zwrócone zdarzenie to
jest sygnalizowany.
W zależności od tego, które urządzenia obsługują wykonywanie, zdarzenie może być wspierane przez
synchronizacji ogrodzenia. Ty
musi wywołać
ANeuralNetworksEvent_wait()
aby poczekać na zdarzenie i odzyskać zasoby użyte przez wykonanie. Ty
można zaimportować ogrodzenia synchronizacji do obiektu zdarzenia za pomocą
ANeuralNetworksEvent_createFromSyncFenceFd()
,
i wyeksportować ogrodzenia z obiektu zdarzenia za pomocą
ANeuralNetworksEvent_getSyncFenceFd()
Dynamiczne wymiary wyjściowe
Aby obsługiwać modele, w których rozmiar danych wyjściowych zależy od danych wejściowych
danych – czyli gdy nie można określić rozmiaru podczas wykonywania modelu
czas – użyj
ANeuralNetworksExecution_getOutputOperandRank
oraz
ANeuralNetworksExecution_getOutputOperandDimensions
Jak to zrobić:
// Get the rank of the output uint32_t myOutputRank = 0; ANeuralNetworksExecution_getOutputOperandRank(run1, 0, &myOutputRank); // Get the dimensions of the output std::vector<uint32_t> myOutputDimensions(myOutputRank); ANeuralNetworksExecution_getOutputOperandDimensions(run1, 0, myOutputDimensions.data());
Uporządkuj
Etap czyszczenia służy do zwalniania wewnętrznych zasobów używanych obliczeń.
// Cleanup ANeuralNetworksCompilation_free(compilation); ANeuralNetworksModel_free(model); ANeuralNetworksMemory_free(mem1);
Zarządzanie błędami i korzystanie z procesora awaryjnego
Jeśli podczas partycjonowania wystąpi błąd, a kierowca nie skompiluje (fragment modelu a) lub jeśli kierowca nie uruchomi skompilowanego modelu A, NNAPI może wrócić do własnej implementacji procesora operacji.
Jeśli klient NNAPI zawiera zoptymalizowane wersje operacji (jako np. TFLite) warto wyłączyć opcję zastępczego procesora, obsługi błędów przy użyciu optymalizowanej implementacji operacji klienta.
Jeśli w Androidzie 10 kompilacja jest wykonywana za pomocą parametru
ANeuralNetworksCompilation_createForDevices
, funkcja zastępcza procesora zostanie wyłączona.
W Androidzie P wykonanie NNAPI jest wracane do procesora, jeśli wykonanie nie powiedzie się na sterowniku.
Dzieje się tak również na Androidzie 10, gdy ANeuralNetworksCompilation_create
niż ANeuralNetworksCompilation_createForDevices
.
Pierwsze uruchomienie jest wykonywane dla tej pojedynczej partycji, a jeśli wciąż spowoduje ponowienie próby na procesorze całego modelu.
Jeśli partycjonowanie lub kompilacja się nie uda, na CPU zostanie wypróbowany cały model.
W pewnych przypadkach niektóre operacje nie są obsługiwane na CPU. kompilacja lub wykonanie w sytuacjach kończy się niepowodzeniem i nie cofają się.
Nawet po wyłączeniu przełączania awaryjnego procesora nadal mogą być wykonywane operacje w modelu
które są zaplanowane na procesor. Jeśli procesor znajduje się na liście podanych procesorów
do ANeuralNetworksCompilation_createForDevices
i jest jedyną
obsługujący te operacje lub procesor, który deklaruje najlepsze
dla tych operacji, zostanie ona wybrana jako główna (bez zastępcza)
wykonawcy.
Aby mieć pewność, że procesor nie zostanie uruchomiony, użyj funkcji ANeuralNetworksCompilation_createForDevices
przy wykluczeniu urządzenia nnapi-reference
z listy urządzeń.
Począwszy od Androida P, można wyłączyć reklamy zastępcze w czasie wykonywania
DEBUGUJ kompilacje, ustawiając właściwość debug.nn.partition
na 2.
Domeny pamięci
W Androidzie 11 i nowszych NNAPI obsługuje domeny pamięci, które udostępniają funkcję przydzielającą dla nieprzezroczystych wspomnień. Pozwala to aplikacjom na przekazywanie pamięci w różnych uruchomieniach, dzięki czemu NNAPI nie kopiuje ani nie przekształca danych niepotrzebnie w przypadku wykonywania kolejnych uruchomień na tym samym sterowniku.
Funkcja domeny pamięci jest przeznaczona dla tensorów, których głównym przeznaczeniem jest które nie wymagają częstego dostępu po stronie klienta. Przykłady obejmują tensory stanów w modelach sekwencji. Do tensorów, które muszą częsty dostęp do procesora po stronie klienta, używaj pul pamięci współdzielonej.
Aby przydzielić nieprzejrzystą pamięć, wykonaj te czynności:
Wywołaj funkcję
ANeuralNetworksMemoryDesc_create()
do utworzenia nowego deskryptora pamięci:// Create a memory descriptor ANeuralNetworksMemoryDesc* desc; ANeuralNetworksMemoryDesc_create(&desc);
Określ wszystkie zamierzone role wejściowe i wyjściowe, wywołując
ANeuralNetworksMemoryDesc_addInputRole()
orazANeuralNetworksMemoryDesc_addOutputRole()
// Specify that the memory may be used as the first input and the first output // of the compilation ANeuralNetworksMemoryDesc_addInputRole(desc, compilation, 0, 1.0f); ANeuralNetworksMemoryDesc_addOutputRole(desc, compilation, 0, 1.0f);
Opcjonalnie określ wymiary pamięci, wywołując
ANeuralNetworksMemoryDesc_setDimensions()
// Specify the memory dimensions uint32_t dims[] = {3, 4}; ANeuralNetworksMemoryDesc_setDimensions(desc, 2, dims);
Finalizuj definicję deskryptora, wywołując
ANeuralNetworksMemoryDesc_finish()
ANeuralNetworksMemoryDesc_finish(desc);
Przydziel tyle wspomnień, ile potrzebujesz, przekazując deskryptor do
ANeuralNetworksMemory_createFromDesc()
// Allocate two opaque memories with the descriptor ANeuralNetworksMemory* opaqueMem; ANeuralNetworksMemory_createFromDesc(desc, &opaqueMem);
Zwolnij deskryptor pamięci, gdy go już nie potrzebujesz.
ANeuralNetworksMemoryDesc_free(desc);
Klient może używać wyłącznie utworzonego obiektu ANeuralNetworksMemory
z
ANeuralNetworksExecution_setInputFromMemory()
lub
ANeuralNetworksExecution_setOutputFromMemory()
według ról
określona w obiekcie ANeuralNetworksMemoryDesc
. Przesunięcie i długość.
musi mieć wartość 0, co oznacza, że używana jest cała pamięć. Klient
może też jawnie ustawiać lub wyodrębniać zawartość pamięci za pomocą funkcji
ANeuralNetworksMemory_copy()
Możesz tworzyć nieprzezroczyste wspomnienia z rolami o nieokreślonych wymiarach lub pozycji.
W takim przypadku utworzenie pamięci może się nie powieść przy użyciu
stan ANEURALNETWORKS_OP_FAILED
, jeśli nie jest on obsługiwany przez źródło
sterownika. Zachęcamy klienta do wdrożenia logiki awaryjnej przez przydzielenie
wystarczająco duży bufor w trybie Ashmem lub BLOB AHardwareBuffer
.
Gdy NNAPI nie musi już mieć dostępu do nieprzejrzystego obiektu pamięci, zwolnij
odpowiadająca mu instancja ANeuralNetworksMemory
:
ANeuralNetworksMemory_free(opaqueMem);
Pomiar wyników
Możesz ocenić wydajność aplikacji, mierząc czas wykonywania lub wykonując profilowanie.
Czas wykonania
Jeśli chcesz określić łączny czas wykonywania w środowisku wykonawczym, możesz użyć opcji
interfejsu API do asynchronicznego wykonywania i mierzyć czas potrzebny na wykonanie tego wywołania. Gdy
chcesz określić całkowity czas wykonywania za pomocą niższego poziomu oprogramowania
grupowania, możesz użyć
ANeuralNetworksExecution_setMeasureTiming
oraz
ANeuralNetworksExecution_getDuration
aby uzyskać:
- czas wykonywania w akceleratorze (nie w sterowniku, który działa na hoście procesor).
- czas wykonywania w sterowniku, w tym czas na akceleratorze.
Czas wykonywania w sterowniku nie uwzględnia narzutów, takich jak środowisko wykonawcze i IPC niezbędne do komunikacji środowiska wykonawczego ze sterownikiem.
Te interfejsy API mierzą czas między przesłaniem zadania a jego ukończeniem zdarzeń, a nie czasu, który kierowca lub akcelerator poświęca na wykonanie wnioskowania, które może zostać przerwane przez przełączanie kontekstu.
Jeśli np. rozpoczyna się wnioskowanie 1, kierowca przerywa pracę, aby wykonać zakładania 2, następnie wznawia działanie i finalizuje wnioskowanie 1, czyli czas wykonania wnioskowanie 1 będzie uwzględniać czas zatrzymania pracy, aby uzyskać wnioskowanie 2.
Te informacje o czasie mogą być przydatne podczas produkcyjnego wdrożenia Aplikacja do zbierania danych telemetrycznych do użytku w trybie offline. Danych o czasie możesz używać do: modyfikować aplikację w celu zwiększenia jej wydajności.
Podczas korzystania z tej funkcji pamiętaj o tych kwestiach:
- Zbieranie informacji o czasie może mieć wpływ na wydajność.
- Tylko kierowca jest w stanie obliczyć czas spędzony samodzielnie lub akceleratora, z wyłączeniem czasu w środowisku wykonawczym NNAPI i w IPC.
- Możesz używać tych interfejsów API tylko za pomocą interfejsu
ANeuralNetworksExecution
, który został utworzono za pomocą:ANeuralNetworksCompilation_createForDevices
za pomocą dodatkunumDevices = 1
. - Aby przesłać informacje o czasie, nie jest wymagany żaden kierowca.
Profilowanie aplikacji przy użyciu systemu Android Systrace
Począwszy od Androida 10, NNAPI generuje automatycznie zdarzenia systrace, które których możesz użyć do profilowania swojej aplikacji.
Źródło NNAPI zawiera narzędzie parse_systrace
do przetwarzania
zdarzenia systrace wygenerowane przez aplikację i wygenerują widok tabeli przedstawiający
czas na różnych fazach cyklu życia modelu (wystąpienie,
„przygotowanie, wykonanie kompilacji i jego zakończenie”) oraz różne warstwy
aplikacji. Aplikacja jest podzielona na następujące warstwy:
Application
: kod głównej aplikacji.Runtime
: środowisko wykonawcze NNAPIIPC
: komunikacja między procesami między środowiskiem wykonawczym NNAPI a sterownikiem kodDriver
: proces sterownika akceleratora.
Generowanie danych analiz profilowania
Załóżmy, że sprawdzisz drzewo źródłowe AOSP pod adresem $ANDROID_BUILD_TOP i za pomocą przykładu klasyfikacji obrazów TFLite. jako aplikację docelową, możesz wygenerować dane profilowania NNAPI za pomocą następujące kroki:
- Uruchom Android systrace za pomocą tego polecenia:
$ANDROID_BUILD_TOP/external/chromium-trace/systrace.py -o trace.html -a org.tensorflow.lite.examples.classification nnapi hal freq sched idle load binder_driver
Parametr -o trace.html
wskazuje, że ślady będą
napisany w dokumencie trace.html
. Podczas profilowania własnej aplikacji należy:
zastąp org.tensorflow.lite.examples.classification
nazwą procesu
określone w manifeście aplikacji.
Dzięki temu jedna z konsoli powłoki będzie zajęta. Nie uruchamiaj tego polecenia w
w tle, ponieważ interaktywnie czeka na zakończenie działania funkcji enter
.
- Po uruchomieniu kolektora systrace uruchom aplikację i uruchom test porównawczy.
W naszym przypadku możesz uruchomić aplikację Image Classification w Android Studio. lub bezpośrednio z testowego interfejsu telefonu, jeśli aplikacja jest już zainstalowana. Aby wygenerować niektóre dane NNAPI, musisz skonfigurować aplikację do korzystania z NNAPI wybierając NNAPI jako urządzenie docelowe w oknie konfiguracji aplikacji.
Gdy test się zakończy, przerwij systrace, naciskając
enter
na terminal w konsoli jest aktywny od kroku 1.Uruchom narzędzie
systrace_parser
, aby wygenerować łączne statystyki:
$ANDROID_BUILD_TOP/frameworks/ml/nn/tools/systrace_parser/parse_systrace.py --total-times trace.html
Parser akceptuje te parametry:
– --total-times
: pokazuje całkowity czas spędzony w warstwie, w tym czas.
oczekiwanie na wykonanie na wywołaniem warstwy bazowej
– --print-detail
: drukuje wszystkie zdarzenia zebrane z systrace
- --per-execution
: drukuje tylko wykonanie i jego podfazy
(jako czas realizacji) zamiast statystyk dla wszystkich faz
– --json
: tworzy dane wyjściowe w formacie JSON
Oto przykładowe dane wyjściowe:
===========================================================================================================================================
NNAPI timing summary (total time, ms wall-clock) Execution
----------------------------------------------------
Initialization Preparation Compilation I/O Compute Results Ex. total Termination Total
-------------- ----------- ----------- ----------- ------------ ----------- ----------- ----------- ----------
Application n/a 19.06 1789.25 n/a n/a 6.70 21.37 n/a 1831.17*
Runtime - 18.60 1787.48 2.93 11.37 0.12 14.42 1.32 1821.81
IPC 1.77 - 1781.36 0.02 8.86 - 8.88 - 1792.01
Driver 1.04 - 1779.21 n/a n/a n/a 7.70 - 1787.95
Total 1.77* 19.06* 1789.25* 2.93* 11.74* 6.70* 21.37* 1.32* 1831.17*
===========================================================================================================================================
* This total ignores missing (n/a) values and thus is not necessarily consistent with the rest of the numbers
Parser może ulec awarii, jeśli zebrane zdarzenia nie odzwierciedlają pełnego logu czasu aplikacji. Może się to zdarzyć szczególnie w przypadku generowania zdarzeń systrace , aby zaznaczyć koniec sekcji, występują w logu czasu bez powiązanego zdarzenia rozpoczęcia sekcji. Zwykle dzieje się tak, gdy niektóre wydarzenia z poprzedniego Sesja profilowania jest generowana po uruchomieniu kolektora systrace. W takim przypadku musisz ponownie przeprowadzić profilowanie.
Dodaj statystyki kodu aplikacji do danych wyjściowych systrace_parser
Aplikacja parse_systrace jest oparta na wbudowanej bazie systemu Android funkcji. Ślady konkretnych operacji w aplikacji możesz dodać za pomocą Interfejs API systrace (w języku Java , na potrzeby aplikacji natywnych ) z niestandardowymi nazwami zdarzeń.
Aby powiązać zdarzenia niestandardowe z etapami cyklu życia aplikacji, dołącz do nazwy wydarzenia jeden z następujących ciągów:
[NN_LA_PI]
: zdarzenie na poziomie aplikacji podczas inicjowania[NN_LA_PP]
: zdarzenie na poziomie aplikacji w ramach przygotowań[NN_LA_PC]
: zdarzenie na poziomie aplikacji na potrzeby kompilacji[NN_LA_PE]
: zdarzenie na poziomie aplikacji do wykonania
Oto przykład, jak możesz zmienić przykład klasyfikacji obrazów TFLite
przez dodanie sekcji runInferenceModel
dla etapu Execution
oraz
Application
warstwa zawierająca inne sekcje preprocessBitmap
, które
nie będą uwzględniane w logach czasu NNAPI. Sekcja runInferenceModel
będzie
części zdarzeń systrace przetworzonych przez parser nnapi systrace:
Kotlin
/** Runs inference and returns the classification results. */ fun recognizeImage(bitmap: Bitmap): List{ // This section won’t appear in the NNAPI systrace analysis Trace.beginSection("preprocessBitmap") convertBitmapToByteBuffer(bitmap) Trace.endSection() // Run the inference call. // Add this method in to NNAPI systrace analysis. Trace.beginSection("[NN_LA_PE]runInferenceModel") long startTime = SystemClock.uptimeMillis() runInference() long endTime = SystemClock.uptimeMillis() Trace.endSection() ... return recognitions }
Java
/** Runs inference and returns the classification results. */ public ListrecognizeImage(final Bitmap bitmap) { // This section won’t appear in the NNAPI systrace analysis Trace.beginSection("preprocessBitmap"); convertBitmapToByteBuffer(bitmap); Trace.endSection(); // Run the inference call. // Add this method in to NNAPI systrace analysis. Trace.beginSection("[NN_LA_PE]runInferenceModel"); long startTime = SystemClock.uptimeMillis(); runInference(); long endTime = SystemClock.uptimeMillis(); Trace.endSection(); ... Trace.endSection(); return recognitions; }
Jakość usługi
W Androidzie 11 i nowszych NNAPI zapewnia lepszą jakość usług (QoS), dzięki co pozwala aplikacji określać względne priorytety jej modeli, maksymalny czas przygotowania danego modelu oraz maksymalny czas oczekiwanego czasu ukończenia danego obliczenia. Android 11: dodatkowe kody wyników NNAPI które pozwalają aplikacjom analizować błędy, takie jak niewykonane pod kątem terminów.
Ustawianie priorytetu zadania
Aby ustawić priorytet zadania NNAPI, wywołaj
ANeuralNetworksCompilation_setPriority()
przed nawiązaniem połączenia z numerem ANeuralNetworksCompilation_finish()
.
Wyznaczanie terminów
Aplikacje mogą określać terminy zarówno kompilacji modeli, jak i wnioskowania.
- Aby ustawić czas oczekiwania na kompilację, wywołaj
ANeuralNetworksCompilation_setTimeout()
przed nawiązaniem połączenia z numeremANeuralNetworksCompilation_finish()
. - Aby ustawić czas oczekiwania wnioskowania, wywołaj
ANeuralNetworksExecution_setTimeout()
przed rozpoczęciem kompilacji.
Więcej informacji o operandach
W dalszej części omówiono zaawansowane zagadnienia dotyczące używania operandów.
Tensory poddane kwantyzacji
Tensor kwantowy to zwarty sposób przedstawiania n-wymiarowej tablicy wartości zmiennoprzecinkowych.
NNAPI obsługuje 8-bitowe tensory asymetryczne kwantyzowane. W przypadku tych tensorów funkcja wartość każdej komórki jest reprezentowana przez 8-bitową liczbę całkowitą. Powiązana z tensor to skala i wartość punktowa zero. Służą one do konwertowania 8-bitowego kodu liczb całkowitych do reprezentowanych wartości zmiennoprzecinkowych.
Zastosowana formuła to:
(cellValue - zeroPoint) * scale
gdzie wartość zeroPoint to 32-bitowa liczba całkowita, a skala to 32-bitowa liczba zmiennoprzecinkowa liczbę punktów.
W porównaniu z tensorami 32-bitowych wartości zmiennoprzecinkowych 8-bitowych tensorów kwantyzowanych mają dwie zalety:
- Twoja aplikacja jest mniejsza, ponieważ wytrenowane waga zajmuje jedną czwartą rozmiaru tensorów 32-bitowych.
- Obliczenia często są często wykonywane szybciej. To ze względu na mniejszą kwotę danych, które trzeba pobrać z pamięci, oraz wydajność procesorów np. platformy DSP do liczenia liczb całkowitych.
Chociaż można przekonwertować model zmiennoprzecinkowy na model kwantowy, Doświadczenie pokazuje, że lepsze wyniki osiąga się przez trenowanie skwantyzowanego modelu model bezpośrednio. W efekcie sieć neuronowa uczy się kompensować większa szczegółowość każdej wartości. Dla każdego tensora kwantowego skala i Wartości zeroPoint są określane podczas procesu trenowania.
W NNAPI definiujesz typy tensorów kwantowych, ustawiając pole typu
ANeuralNetworksOperandType
do struktury danych
ANEURALNETWORKS_TENSOR_QUANT8_ASYMM
Określasz również skalę i wartość zeroPoint tensora w tych danych
do jego struktury.
Oprócz 8-bitowych tensorów asymetrycznych kwantyzowanych NNAPI obsługuje następujące funkcje:
ANEURALNETWORKS_TENSOR_QUANT8_SYMM_PER_CHANNEL
który może służyć do przedstawiania wag Operacje:CONV/DEPTHWISE_CONV/TRANSPOSED_CONV
.ANEURALNETWORKS_TENSOR_QUANT16_ASYMM
które mogą być używane jako wewnętrzny stanQUANTIZED_16BIT_LSTM
ANEURALNETWORKS_TENSOR_QUANT8_SYMM
które mogą być danymi wejściowymi dlaANEURALNETWORKS_DEQUANTIZE
Opcjonalne operandy
Kilka operacji, takich jak
ANEURALNETWORKS_LSH_PROJECTION
,
opcjonalnych operandów. Aby wskazać w modelu, że opcjonalny operand to
został pominięty, wywołaj funkcję
ANeuralNetworksModel_setOperandValue()
, podając wartość NULL
jako bufor i 0 jako długość.
Jeśli decyzja o tym, czy operand jest obecny, czy nie, różni się
możesz wskazać, że operand jest pominięty,
ANeuralNetworksExecution_setInput()
lub
ANeuralNetworksExecution_setOutput()
, podając wartość NULL
jako bufor i 0 jako długość.
Tensory nieznanego rangi
W Androidzie 9 (poziom interfejsu API 28) wprowadzono operandy modelu o nieznanych wymiarach, ale znana pozycja (liczba wymiarów). Wprowadzenie Androida 10 (poziom interfejsu API 29) tensory nieznanego rangi, jak w argumencie ANeuralNetworksOperandType.
Test porównawczy NNAPI
Test porównawczy NNAPI jest dostępny na stronie AOSP w tych krajach: platform/test/mlts/benchmark
(aplikacja testu porównawczego) i platform/test/mlts/models
(modele i zbiory danych).
Test porównawczy określa czas oczekiwania i dokładność, a następnie porównuje czynniki za pomocą procesora Tensorflow Lite uruchomionego na procesorach, dla tych samych modeli w wielu zbiorach danych.
Aby użyć testu porównawczego:
Podłącz docelowe urządzenie z Androidem do komputera, otwórz okno terminala i upewnij się, że urządzenie jest osiągalne za pomocą narzędzia adb.
Jeśli podłączone jest więcej niż 1 urządzenie z Androidem, wyeksportuj urządzenie docelowe Zmienna środowiskowa
ANDROID_SERIAL
.Przejdź do katalogu źródłowego najwyższego poziomu Androida.
Uruchom te polecenia:
lunch aosp_arm-userdebug # Or aosp_arm64-userdebug if available ./test/mlts/benchmark/build_and_run_benchmark.sh
Na koniec testu porównawczego jego wyniki zostaną wyświetlone w postaci strony HTML przekazano do
xdg-open
.
Logi NNAPI
NNAPI generuje przydatne informacje diagnostyczne w dziennikach systemowych. Do analizy dzienników służy polecenie logcat. za media.
Włącz szczegółowe logowanie NNAPI dla określonych faz lub komponentów, ustawiając
właściwości debug.nn.vlog
(przy użyciu adb shell
) do poniższej listy wartości,
oddzielając je spacją, dwukropkiem lub przecinkiem:
model
: budowanie modelicompilation
: generowanie planu wykonania modelu i kompilacjiexecution
: wykonanie modelucpuexe
: wykonywanie operacji przy użyciu implementacji procesora NNAPImanager
: rozszerzenia NNAPI, dostępne interfejsy i informacje związane z funkcjamiall
lub1
: wszystkie elementy powyżej
Aby na przykład włączyć pełne logowanie szczegółowe, użyj polecenia
adb shell setprop debug.nn.vlog all
Aby wyłączyć logowanie szczegółowe, użyj polecenia
adb shell setprop debug.nn.vlog '""'
Po włączeniu logowanie szczegółowe generuje wpisy logu na poziomie INFO z tagiem ustawionym na nazwę etapu lub komponentu.
Oprócz wiadomości kontrolowanych debug.nn.vlog
komponenty interfejsu NNAPI API zapewniają
inne wpisy logu na różnych poziomach, z których każdy ma określony tag dziennika.
Aby uzyskać listę komponentów, przeszukaj drzewo źródłowe za pomocą następujące wyrażenie:
grep -R 'define LOG_TAG' | awk -F '"' '{print $2}' | sort -u | egrep -v "Sample|FileTag|test"
To wyrażenie zwraca obecnie następujące tagi:
- Kreator rozbłysku
- Wywołania zwrotne
- Kompilator kompilacji
- Wykonawca procesora graficznego
- Kreator wykonywania
- Sterowanie kręceniem ujęć
- Serwer ExecutionBurst
- Plan wykonania
- FibonacciDriver
- Zrzut wykresu
- Element IndexedStatusWrapper
- IonWatcher
- Menedżer
- Pamięć
- Memoryutils
- Metamodel
- Informacje o argumentach modelu
- Kreator modeli
- NeuralNetworks
- Program do rozpoznawania operacji
- Zarządzanie
- Narzędzia operacyjne
- Informacje o pakiecie
- Haszer tokenów
- Menedżer typów
- Narzędzia
- ValidateHal
- Wersjonowane interfejsy
Aby kontrolować poziom komunikatów logu wyświetlanych przez funkcję logcat
, użyj
zmienną środowiskową ANDROID_LOG_TAGS
.
Aby wyświetlić pełny zestaw komunikatów dziennika NNAPI i wyłączyć wszystkie inne, ustaw ANDROID_LOG_TAGS
na
następujące:
BurstBuilder:V Callbacks:V CompilationBuilder:V CpuExecutor:V ExecutionBuilder:V ExecutionBurstController:V ExecutionBurstServer:V ExecutionPlan:V FibonacciDriver:V GraphDump:V IndexedShapeWrapper:V IonWatcher:V Manager:V MemoryUtils:V Memory:V MetaModel:V ModelArgumentInfo:V ModelBuilder:V NeuralNetworks:V OperationResolver:V OperationsUtils:V Operations:V PackageInfo:V TokenHasher:V TypeManager:V Utils:V ValidateHal:V VersionedInterfaces:V *:S.
Aby ustawić ANDROID_LOG_TAGS
, użyj tego polecenia:
export ANDROID_LOG_TAGS=$(grep -R 'define LOG_TAG' | awk -F '"' '{ print $2 ":V" }' | sort -u | egrep -v "Sample|FileTag|test" | xargs echo -n; echo ' *:S')
Pamiętaj, że ten filtr ma zastosowanie tylko do logcat
. Nadal musisz
ustaw właściwość debug.nn.vlog
na all
, aby wygenerować szczegółowe informacje logu.