Interfejs API sieci neuronowych

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.

Rysunek 1. Architektura systemu dla interfejsu Android Neural Networks API
.
.

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.

Rysunek 2. Proces programowania interfejsu Android Neural Networks API
.
.

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 parametr ARect.
  • Ś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.

Rysunek 3. Przykład operandów modelu NNAPI
.
.

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:

Kategoria Zarządzanie
Działania matematyczne z użyciem elementów
Manipulacja Tensor
Operacje na obrazie
Operacje wyszukiwania
Operacje normalizacji
Operacje splotowe
Operacje łączenia
Operacje aktywacji
Inne operacje

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:

  1. Wywołaj funkcję ANeuralNetworksModel_create() do zdefiniowania pustego modelu.

    ANeuralNetworksModel* model = NULL;
    ANeuralNetworksModel_create(&model);
    
  2. 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 6
  3. W 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() oraz ANeuralNetworksModel_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));
  4. 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);
  5. 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);
    
  6. 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łanie ANeuralNetworksModel_relaxComputationFloat32toFloat16()

  7. Zadzwoń pod numer ANeuralNetworksModel_finish() aby dokończyć definicję modelu. Jeśli nie wystąpią błędy, zwraca kod wyniku ANEURALNETWORKS_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:

  1. Utwórz odpowiednie podgrafy wykonania (podgrafy then i else). dla instrukcji IF, podgrafów condition i body dla pętli WHILE) jako samodzielne modele ANeuralNetworksModel*:

    ANeuralNetworksModel* thenModel = makeThenModel();
    ANeuralNetworksModel* elseModel = makeElseModel();
    
  2. 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);
    
  3. 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:

  1. 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.

  2. 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:

  3. Opcjonalnie możesz skonfigurować buforowanie kompilacji, wywołując ANeuralNetworksCompilation_setCaching

    // Set up compilation caching
    ANeuralNetworksCompilation_setCaching(compilation, cacheDir, token);
    

    Użyj formatu getCodeCacheDir() za cacheDir. Podana wartość token musi być unikalna dla każdego modelu w obrębie aplikacji.

  4. Dokończ definicję kompilacji, wywołując ANeuralNetworksCompilation_finish() Jeśli nie ma błędów, ta funkcja zwraca kod wyniku ANEURALNETWORKS_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:

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:

  1. 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);
    
  2. 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() lub ANeuralNetworksExecution_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));
    
  3. 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() lub ANeuralNetworksExecution_setOutputFromMemory() .

    // Set the output
    float32 myOutput[3][4];
    ANeuralNetworksExecution_setOutput(run1, 0, NULL, myOutput, sizeof(myOutput));
    
  4. Zaplanuj rozpoczęcie wykonania, wywołując metodę ANeuralNetworksExecution_startCompute() . Jeśli nie ma błędów, ta funkcja zwraca kod wyniku ANEURALNETWORKS_NO_ERROR

    // Starts the work. The work proceeds asynchronously
    ANeuralNetworksEvent* run1_end = NULL;
    ANeuralNetworksExecution_startCompute(run1, &run1_end);
    
  5. Zadzwoń pod numer ANeuralNetworksEvent_wait() i czeka na zakończenie wykonywania. Jeśli wykonanie zostało powodzenie, funkcja zwraca kod wyniku ANEURALNETWORKS_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);
    
  6. 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:

  1. Wywołaj funkcję ANeuralNetworksMemoryDesc_create() do utworzenia nowego deskryptora pamięci:

    // Create a memory descriptor
    ANeuralNetworksMemoryDesc* desc;
    ANeuralNetworksMemoryDesc_create(&desc);
    
  2. Określ wszystkie zamierzone role wejściowe i wyjściowe, wywołując ANeuralNetworksMemoryDesc_addInputRole() oraz ANeuralNetworksMemoryDesc_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);
    
  3. 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);
    
  4. Finalizuj definicję deskryptora, wywołując ANeuralNetworksMemoryDesc_finish()

    ANeuralNetworksMemoryDesc_finish(desc);
    
  5. Przydziel tyle wspomnień, ile potrzebujesz, przekazując deskryptor do ANeuralNetworksMemory_createFromDesc()

    // Allocate two opaque memories with the descriptor
    ANeuralNetworksMemory* opaqueMem;
    ANeuralNetworksMemory_createFromDesc(desc, &opaqueMem);
    
  6. 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ą dodatku numDevices = 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 NNAPI
  • IPC: komunikacja między procesami między środowiskiem wykonawczym NNAPI a sterownikiem kod
  • Driver: 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:

  1. 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.

  1. 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.

  1. Gdy test się zakończy, przerwij systrace, naciskając enter na terminal w konsoli jest aktywny od kroku 1.

  2. 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 List recognizeImage(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.

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:

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:

  1. 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.

  2. Jeśli podłączone jest więcej niż 1 urządzenie z Androidem, wyeksportuj urządzenie docelowe Zmienna środowiskowa ANDROID_SERIAL.

  3. Przejdź do katalogu źródłowego najwyższego poziomu Androida.

  4. 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 modeli
  • compilation: generowanie planu wykonania modelu i kompilacji
  • execution: wykonanie modelu
  • cpuexe: wykonywanie operacji przy użyciu implementacji procesora NNAPI
  • manager: rozszerzenia NNAPI, dostępne interfejsy i informacje związane z funkcjami
  • all lub 1: 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.