Rozszerzenia dotyczące dostawców

Rozszerzenia dostawcy interfejsu Neural Networks API (NNAPI), wprowadzone w Androidzie 10, to zbiory operacji i typów danych zdefiniowanych przez dostawcę. Na urządzeniach z NN HAL w wersji 1.2 lub nowszej sterowniki mogą udostępniać niestandardowe operacje akcelerowane sprzętowo, obsługując odpowiednie rozszerzenia dostawcy. Rozszerzenia dostawców nie modyfikują działania istniejących operacji.

Rozszerzenia dostawcy stanowią bardziej uporządkowaną alternatywę dla operacji i typów danych OEM, które zostały wycofane w Androidzie 10. Więcej informacji znajdziesz w artykule Działanie OEM i typy danych.

Lista dozwolonych rozszerzeń

Rozszerzenia dostawcy mogą być używane tylko przez wyraźnie określone aplikacje na Androida i natywne pliki binarne w partycjach /product, /vendor, /odm/data. Aplikacje i natywne pliki binarne znajdujące się na partycji /system nie mogą korzystać z rozszerzeń dostawcy.

Lista aplikacji i plików binarnych na Androida, które mogą korzystać z rozszerzeń dostawcy NNAPI, jest przechowywana w /vendor/etc/nnapi_extensions_app_allowlist. Każdy wiersz pliku zawiera nowy wpis. Wpis może być ścieżką do natywnego pliku binarnego, która zaczyna się od ukośnika (/), np. /data/foo, lub nazwą pakietu aplikacji na Androida, np. com.foo.bar.

Lista dozwolonych jest egzekwowana przez bibliotekę współdzieloną środowiska wykonawczego NNAPI. Ta biblioteka chroni przed przypadkowym użyciem, ale nie przed celowym obejściem przez aplikację, która bezpośrednio korzysta z interfejsu HAL sterownika NNAPI.

Definicja rozszerzenia dostawcy

Dostawca tworzy i utrzymuje plik nagłówkowy z definicją rozszerzenia. Pełny przykład definicji rozszerzenia znajdziesz w example/fibonacci/FibonacciExtension.h.

Każde rozszerzenie musi mieć unikalną nazwę, która zaczyna się od odwróconej nazwy domeny dostawcy.

const char EXAMPLE_EXTENSION_NAME[] = "com.example.my_extension";

Nazwa działa jako przestrzeń nazw dla operacji i typów danych. NNAPI używa tej nazwy do rozróżniania rozszerzeń dostawców.

Operacje i typy danych są deklarowane w sposób podobny do tych w runtime/include/NeuralNetworks.h.

enum {
    /**
     * A custom scalar type.
     */
    EXAMPLE_SCALAR = 0,

    /**
     * A custom tensor type.
     *
     * Attached to this tensor is {@link ExampleTensorParams}.
     */
    EXAMPLE_TENSOR = 1,
};

enum {
    /**
     * Computes example function.
     *
     * Inputs:
     * * 0: A scalar of {@link EXAMPLE_SCALAR}.
     *
     * Outputs:
     * * 0: A tensor of {@link EXAMPLE_TENSOR}.
     */
    EXAMPLE_FUNCTION = 0,
};

Operacja rozszerzenia może używać dowolnego typu operandu, w tym typów operandów innych niż rozszerzenia i typów operandów z innych rozszerzeń. Jeśli używasz typu operandu z innego rozszerzenia, sterownik musi obsługiwać to rozszerzenie.

Rozszerzenia mogą też deklarować niestandardowe struktury, które będą towarzyszyć operandowi rozszerzenia.

/**
 * Quantization parameters for {@link EXAMPLE_TENSOR}.
 */
typedef struct ExampleTensorParams {
    double scale;
    int64_t zeroPoint;
} ExampleTensorParams;

Korzystanie z rozszerzeń w klientach NNAPI

Plik runtime/include/NeuralNetworksExtensions.h (C API) zapewnia obsługę rozszerzeń w czasie działania. Ta sekcja zawiera omówienie interfejsu C API.

Aby sprawdzić, czy urządzenie obsługuje rozszerzenie, użyj funkcji ANeuralNetworksDevice_getExtensionSupport.

bool isExtensionSupported;
CHECK_EQ(ANeuralNetworksDevice_getExtensionSupport(device, EXAMPLE_EXTENSION_NAME,
                                                   &isExtensionSupported),
         ANEURALNETWORKS_NO_ERROR);
if (isExtensionSupported) {
    // The device supports the extension.
    ...
}

Aby utworzyć model z operandem rozszerzenia, użyj funkcji ANeuralNetworksModel_getExtensionOperandType, aby uzyskać typ operanda, i wywołaj funkcję ANeuralNetworksModel_addOperand.

int32_t type;
CHECK_EQ(ANeuralNetworksModel_getExtensionOperandType(model, EXAMPLE_EXTENSION_NAME, EXAMPLE_TENSOR, &type),
         ANEURALNETWORKS_NO_ERROR);
ANeuralNetworksOperandType operandType{
        .type = type,
        .dimensionCount = dimensionCount,
        .dimensions = dimensions,
};
CHECK_EQ(ANeuralNetworksModel_addOperand(model, &operandType), ANEURALNETWORKS_NO_ERROR);

Opcjonalnie możesz użyć ANeuralNetworksModel_setOperandExtensionData do powiązania dodatkowych danych z operandem rozszerzenia.

ExampleTensorParams params{
        .scale = 0.5,
        .zeroPoint = 128,
};
CHECK_EQ(ANeuralNetworksModel_setOperandExtensionData(model, operandIndex, &params, sizeof(params)),
         ANEURALNETWORKS_NO_ERROR);

Aby utworzyć model z operacją rozszerzenia, użyj ANeuralNetworksModel_getExtensionOperationType do uzyskania typu operacji i wywołaj ANeuralNetworksModel_addOperation.

ANeuralNetworksOperationType type;
CHECK_EQ(ANeuralNetworksModel_getExtensionOperationType(model, EXAMPLE_EXTENSION_NAME, EXAMPLE_FUNCTION,
                                                        &type),
         ANEURALNETWORKS_NO_ERROR);
CHECK_EQ(ANeuralNetworksModel_addOperation(model, type, inputCount, inputs, outputCount, outputs),
         ANEURALNETWORKS_NO_ERROR);

Dodawanie obsługi rozszerzeń do sterownika NNAPI

Sterowniki zgłaszają obsługiwane rozszerzenia za pomocą metody IDevice::getSupportedExtensions. Zwrócona lista musi zawierać wpis opisujący każde obsługiwane rozszerzenie.

Extension {
    .name = EXAMPLE_EXTENSION_NAME,
    .operandTypes = {
        {
            .type = EXAMPLE_SCALAR,
            .isTensor = false,
            .byteSize = 8,
        },
        {
            .type = EXAMPLE_TENSOR,
            .isTensor = true,
            .byteSize = 8,
        },
    },
}

Z 32 bitów używanych do identyfikowania typów i operacji Model::ExtensionTypeEncoding::HIGH_BITS_PREFIX bity o wysokiej wartości to prefiks rozszerzenia, a Model::ExtensionTypeEncoding::LOW_BITS_TYPE bity o niskiej wartości reprezentują typ lub operację rozszerzenia.

Podczas obsługi operacji lub typu operandu sterownik musi sprawdzić prefiks rozszerzenia. Jeśli prefiks rozszerzenia ma wartość różną od zera, typ operacji lub operandu jest typem rozszerzenia. Jeśli wartość to 0, typ operacji lub operandu nie jest typem rozszerzenia.

Aby przypisać prefiks do nazwy rozszerzenia, wyszukaj go w model.extensionNameToPrefix. Mapowanie prefiksu na nazwę rozszerzenia jest w przypadku danego modelu relacją 1:1 (bijekcją). Różne wartości prefiksów mogą odpowiadać tej samej nazwie rozszerzenia w różnych modelach.

Sterownik musi weryfikować operacje rozszerzenia i typy danych, ponieważ środowisko wykonawcze NNAPI nie może weryfikować konkretnych operacji rozszerzenia i typów danych.

Operandy rozszerzenia mogą mieć powiązane dane w operand.extraParams.extension, które środowisko wykonawcze traktuje jako nieprzetworzony blok danych o dowolnym rozmiarze.

Operacje OEM i typy danych

NNAPI ma operację OEM i typy danych OEM, które umożliwiają producentom urządzeń udostępnianie niestandardowych funkcji specyficznych dla sterowników. Te typy operacji i danych są używane tylko przez aplikacje OEM. Semantyka działania OEM i typy danych są specyficzne dla OEM i mogą się w każdej chwili zmienić. Operacje i typy danych OEM są kodowane za pomocą znaków OperationType::OEM_OPERATION, OperandType::OEMOperandType::TENSOR_OEM_BYTE.