Rozszerzenia 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 kodem NN HAL 1.2 lub nowszym sterowniki mogą umożliwiać niestandardowe, przyspieszane sprzętowo operacje, które obsługują odpowiednie rozszerzenia dostawców. Rozszerzenia dostawców nie modyfikują zachowania dotychczasowych operacji.

Rozszerzenia dostawców zapewniają 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 na temat działań i typów danych OEM.

Lista dozwolonych na temat użycia rozszerzeń

Rozszerzenia dostawców mogą być używane tylko przez jawnie określone aplikacje na Androida i natywne pliki binarne na partycjach /product, /vendor, /odm i /data. Aplikacje ani natywne pliki binarne znajdujące się na partycji /system nie mogą używać rozszerzeń dostawców.

Lista aplikacji na Androida i plików binarnych, które mogą używać rozszerzeń dostawcy NNAPI, jest przechowywana w /vendor/etc/nnapi_extensions_app_allowlist. Każdy wiersz w pliku zawiera nowy wpis. Wpis może być natywną ścieżką binarną poprzedzoną ukośnikiem (/), np. /data/foo, albo nazwą pakietu aplikacji na Androida, np. com.foo.bar.

Lista dozwolonych jest egzekwowana z zasobów wspólnych środowiska wykonawczego NNAPI. Ta biblioteka chroni przed przypadkowym użyciem, ale nie przed celowym obchodzeniem zabezpieczeń przez aplikację bezpośrednio za pomocą interfejsu HAL sterownika NNAPI.

Definicja rozszerzenia dostawcy

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

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

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

Jest to przestrzeń nazw 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 operandów, w tym typów operandów bez rozszerzeń oraz typów operandów z innych rozszerzeń. W przypadku użycia typu operandu z innego rozszerzenia sterownik musi je obsługiwać.

Rozszerzenia mogą też deklarować niestandardowe struktury, aby towarzyszyć operandom rozszerzenia.

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

Używanie rozszerzeń w klientach NNAPI

Plik runtime/include/NeuralNetworksExtensions.h (C API) zapewnia obsługę rozszerzenia środowiska wykonawczego. W tej sekcji znajdziesz omówienie interfejsu C API.

Aby sprawdzić, czy urządzenie obsługuje rozszerzenie, użyj narzędzia 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 argumentu ANeuralNetworksModel_getExtensionOperandType w celu uzyskania typu argumentu i wywołania 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ć metody ANeuralNetworksModel_setOperandExtensionData, aby powiązać dodatkowe dane 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 polecenia ANeuralNetworksModel_getExtensionOperationType, aby uzyskać typ 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 rozszerzenia 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,
        },
    },
}

Spośród 32 bitów używanych do identyfikowania typów i operacji górne bity Model::ExtensionTypeEncoding::HIGH_BITS_PREFIX to prefiks rozszerzenia, a niskie bity Model::ExtensionTypeEncoding::LOW_BITS_TYPE reprezentują typ lub działanie rozszerzenia.

Podczas obsługi operacji lub typu operandu sterownik musi sprawdzić prefiks rozszerzenia. Jeśli prefiks rozszerzenia ma wartość inną niż zero, operacja lub typ argumentu jest typem rozszerzenia. Jeśli wartość to 0, typ operacji lub argumentu nie jest typem rozszerzenia.

Aby zmapować prefiks na nazwę rozszerzenia, wyszukaj go tutaj: model.extensionNameToPrefix. Mapowanie z prefiksu na nazwę rozszerzenia to korespondencja 1:1 (bijektor) dla danego modelu. W różnych modelach różne wartości prefiksów mogą odpowiadać tej samej nazwie rozszerzenia.

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

operandy rozszerzenia mogą mieć powiązane dane w elemencie operand.extraParams.extension, który środowisko wykonawcze traktuje jako nieprzetworzony obiekt blob danych o dowolnym rozmiarze.

Działania i typy danych OEM

NNAPI ma placówkę OEM i typ danych OEM, dzięki czemu producenci urządzeń mogą udostępniać niestandardowe funkcje dostosowane do kierowców. Te operacje i typy danych są używane tylko w aplikacjach OEM. Semantyka działania OEM i typy danych są typowe dla OEM i mogą się zmienić w każdej chwili. Operacje i typy danych OEM są kodowane za pomocą znaczników OperationType::OEM_OPERATION, OperandType::OEM i OperandType::TENSOR_OEM_BYTE.