Neural Networks API

Android Neural Networks API (NNAPI) – это Android C API, предназначенный для выполнения ресурсоемких операций машинного обучения на устройствах Android. NNAPI предоставляет базовый уровень функциональности для фреймворков машинного обучения более высокого уровня, таких как TensorFlow Lite и Caffe2, которые создают и обучают нейронные сети. API доступен на всех устройствах Android с Android 8.1 (уровня API 27) или более поздней версии, но в Android 15 он был объявлен устаревшим.

NNAPI поддерживает логический вывод, применяя данные с устройств Android к ранее обученным моделям, определенным разработчиком. Примеры логического вывода включают классификацию изображений, прогнозирование поведения пользователей и выбор подходящих ответов на поисковый запрос.

У вывода на устройстве есть много преимуществ:

  • Задержка. Вам не нужно отправлять запрос по сетевому подключению и ждать ответа. Например, это может быть критически важно для видеоприложений, обрабатывающих последовательные кадры, поступающие с камеры.
  • Доступность. Приложение работает даже вне зоны покрытия сети.
  • Скорость. Новое аппаратное обеспечение, предназначенное для обработки нейронных сетей, обеспечивает значительно более быстрые вычисления, чем ЦП общего назначения.
  • Конфиденциальность. Данные не передаются за пределы устройства Android.
  • Стоимость. Если все вычисления выполняются на устройстве Android, серверная ферма не нужна.

Разработчикам также следует учитывать следующие особенности:

  • Использование ресурсов системы. Оценка нейросетей требует значительных вычислительных мощностей, что может привести к повышенному расходу заряда батареи. Если это важно для вашего приложения, особенно при длительных вычислениях, рекомендуем отслеживать состояние батареи.
  • Размер приложения. Обратите внимание на размер моделей. Модели могут занимать несколько мегабайт. Если добавление больших моделей в APK-файл может негативно повлиять на пользователей, вы можете скачивать модели после установки приложения, использовать модели меньшего размера или выполнять вычисления в облаке. NNAPI не позволяет запускать модели в облаке.

Пример использования NNAPI можно найти в примере Android Neural Networks API.

Как работает Neural Networks API

NNAPI предназначен для вызова библиотеками, фреймворками и инструментами машинного обучения, которые позволяют разработчикам обучать свои модели вне устройства и развертывать их на устройствах Android. Обычно приложения не используют NNAPI напрямую, а обращаются к фреймворкам машинного обучения более высокого уровня. Эти фреймворки, в свою очередь, могут использовать NNAPI для выполнения операций вывода с аппаратным ускорением на поддерживаемых устройствах.

В зависимости от требований приложения и аппаратных возможностей устройства Android среда выполнения нейронной сети Android может эффективно распределять вычислительную нагрузку между доступными процессорами на устройстве, включая специализированное оборудование нейронной сети, графические процессоры (GPU) и цифровые сигнальные процессоры (DSP).

Если на устройстве Android нет специализированного драйвера от поставщика, среда выполнения NNAPI выполняет запросы на ЦП.

На рисунке 1 показана общая архитектура системы для NNAPI.

Рисунок 1. Архитектура системы для Android Neural Networks API

Модель программирования Neural Networks API

Чтобы выполнить вычисления с помощью NNAPI, сначала нужно построить ориентированный граф, который определяет эти вычисления. Этот граф вычислений в сочетании с входными данными (например, весами и смещениями, переданными из фреймворка машинного обучения) формирует модель для оценки среды выполнения NNAPI.

NNAPI использует четыре основные абстракции:

  • Модель. Граф вычислений математических операций и постоянных значений, полученных в процессе обучения. Эти операции специфичны для нейронных сетей. К ним относятся двумерная свертка, логистическая (сигмоидальная) активация, выпрямленная линейная (ReLU) активация и т. д. Создание модели – это синхронная операция. После создания его можно использовать в разных цепочках и подборках. В NNAPI модель представлена как экземпляр ANeuralNetworksModel.
  • Компиляция. Представляет собой конфигурацию для компиляции модели NNAPI в код более низкого уровня. Создание компиляции – это синхронная операция. После успешного создания его можно повторно использовать в разных цепочках и выполнениях. В NNAPI каждая компиляция представлена экземпляром ANeuralNetworksCompilation.
  • Память. Общая память, файлы, сопоставленные с памятью, и аналогичные буферы памяти. Использование буфера памяти позволяет среде выполнения NNAPI более эффективно передавать данные драйверам. Обычно приложение создает один общий буфер памяти, содержащий все тензоры, необходимые для определения модели. Вы также можете использовать буферы памяти для хранения входных и выходных данных для экземпляра выполнения. В NNAPI каждый буфер памяти представлен как экземпляр ANeuralNetworksMemory.
  • Выполнение. Интерфейс для применения модели NNAPI к набору входных данных и сбора результатов. Выполнение может быть синхронным или асинхронным.

    При асинхронном выполнении несколько потоков могут ожидать одного и того же выполнения. Когда выполнение завершается, все потоки освобождаются.

    В NNAPI каждое выполнение представлено как экземпляр ANeuralNetworksExecution.

На рисунке 2 показан базовый процесс программирования.

Рисунок 2. Процесс программирования для Android Neural Networks API

В оставшейся части этого раздела описываются шаги по настройке модели NNAPI для выполнения вычислений, компиляции модели и выполнения скомпилированной модели.

Предоставление доступа к данным для обучения

Данные о весах и смещениях, полученные в результате обучения, скорее всего, хранятся в файле. Чтобы предоставить среде выполнения NNAPI эффективный доступ к этим данным, создайте экземпляр ANeuralNetworksMemory, вызвав функцию ANeuralNetworksMemory_createFromFd() и передав дескриптор открытого файла данных. Вы также указываете флаги защиты памяти и смещение, с которого начинается область общей памяти в файле.

// 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);

Хотя в этом примере мы используем только один экземпляр ANeuralNetworksMemory для всех весов, можно использовать несколько экземпляров ANeuralNetworksMemory для нескольких файлов.

Использовать нативные аппаратные буферы

Вы можете использовать нативные аппаратные буферы для входных и выходных данных модели, а также для значений постоянных операндов. В некоторых случаях ускоритель NNAPI может получать доступ к объектам AHardwareBuffer без необходимости копирования данных драйвером. AHardwareBuffer имеет множество различных конфигураций, и не каждый ускоритель NNAPI может поддерживать все эти конфигурации. Поэтому ознакомьтесь с ограничениями, перечисленными в справочной документации по ANeuralNetworksMemory_createFromAHardwareBuffer, и заранее протестируйте целевые устройства, чтобы убедиться, что компиляция и выполнение с использованием AHardwareBuffer работают правильно. Для указания ускорителя используйте назначение устройств.

Чтобы среда выполнения NNAPI могла получить доступ к объекту AHardwareBuffer, создайте экземпляр ANeuralNetworksMemory, вызвав функцию ANeuralNetworksMemory_createFromAHardwareBuffer и передав объект AHardwareBuffer, как показано в следующем примере кода:

// 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);

Когда NNAPI больше не нужен доступ к объекту AHardwareBuffer, освободите соответствующий экземпляр ANeuralNetworksMemory:

ANeuralNetworksMemory_free(mem2);

Примечание.

  • Метод AHardwareBuffer можно использовать только для всего буфера, но не с параметром ARect.
  • Среда выполнения NNAPI не будет очищать буфер. Перед планированием выполнения необходимо убедиться, что входной и выходной буферы доступны.
  • Поддержка дескрипторов файлов барьеров синхронизации отсутствует.
  • Для AHardwareBuffer с форматами и битами использования, зависящими от поставщика, ответственность за очистку кеша определяется реализацией поставщика.

Модель

Модель – это фундаментальная единица вычислений в NNAPI. Каждая модель определяется одним или несколькими операндами и операциями.

Операнды

Операнды – это объекты данных, используемые при определении графа. К ним относятся входные и выходные данные модели, промежуточные узлы, содержащие данные, которые передаются от одной операции к другой, и константы, передаваемые этим операциям.

В модели NNAPI можно добавлять операнды двух типов: скаляры и тензоры.

Скаляр представляет собой одно значение. NNAPI поддерживает скалярные значения в логическом формате, 16- и 32-битных форматах с плавающей запятой, а также 32-битных форматах целых чисел со знаком и без.

Большинство операций в NNAPI включают тензоры. Тензоры – это n-мерные массивы. NNAPI поддерживает тензоры с 16- и 32-битными значениями с плавающей запятой, 8- и 16-битными квантованными значениями, 32-битными целыми числами и 8-битными логическими значениями.

Например, на рисунке 3 показана модель с двумя операциями: сложением и умножением. Модель принимает входной тензор и создает один выходной тензор.

Рисунок 3. Пример операндов для модели NNAPI

В модели выше семь операндов. Эти операнды неявно идентифицируются по индексу порядка, в котором они добавляются в модель. Первый добавленный операнд имеет индекс 0, второй – 1 и т. д. Операнды 1, 2, 3 и 5 – постоянные.

Порядок добавления операндов не имеет значения. Например, операнд выходных данных модели может быть добавлен первым. Главное – использовать правильное значение индекса при ссылке на операнд.

У операндов есть типы. Они указываются при добавлении в модель.

Операнд не может использоваться и как входные, и как выходные данные модели.

Каждый операнд должен быть входным значением модели, константой или выходным операндом ровно одной операции.

Подробнее об использовании операндов…

Операции

Операция определяет, какие вычисления нужно выполнить. Каждая операция состоит из следующих элементов:

  • тип операции (например, сложение, умножение, свертка);
  • список индексов операндов, которые операция использует для ввода;
  • список индексов операндов, которые операция использует для вывода.

Порядок в этих списках имеет значение. Ожидаемые входные и выходные данные для каждого типа операции можно посмотреть в документации по NNAPI API.

Прежде чем добавлять операцию, необходимо добавить в модель операнды, которые она использует или создает.

Порядок добавления операций не имеет значения. NNAPI определяет порядок выполнения операций на основе зависимостей, установленных графом вычислений операндов и операций.

Операции, поддерживаемые NNAPI, приведены в таблице ниже.

Категория Операции
Поэлементные математические операции
Манипуляции с тензорами
Операции с изображениями
Операции поиска
Операции нормализации
Операции свертки
Операции объединения
Операции активации
Другие операции

Известная проблема на уровне API 28. При передаче тензоров ANEURALNETWORKS_TENSOR_QUANT8_ASYMM в операцию ANEURALNETWORKS_PAD, которая доступна в Android 9 (уровень API 28) и более поздних версиях, выходные данные NNAPI могут не совпадать с выходными данными фреймворков машинного обучения более высокого уровня, таких как TensorFlow Lite. Вместо этого следует передавать только ANEURALNETWORKS_TENSOR_FLOAT32. Проблема устранена в Android 10 (уровень API 29) и более поздних версиях.

Создание моделей

В следующем примере мы создаем модель с двумя операциями, показанную на рисунке 3.

Чтобы создать модель, выполните следующие действия:

  1. Вызовите функцию ANeuralNetworksModel_create(), чтобы определить пустую модель.

    ANeuralNetworksModel* model = NULL;
    ANeuralNetworksModel_create(&model);
  2. Добавьте операнды в модель, вызвав ANeuralNetworks_addOperand(). Типы данных определяются с помощью структуры данных ANeuralNetworksOperandType.

    // 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. Для операндов с постоянными значениями, например весов и смещений, которые приложение получает в процессе обучения, используйте функции ANeuralNetworksModel_setOperandValue() и ANeuralNetworksModel_setOperandValueFromMemory().

    В примере ниже мы задаем постоянные значения из файла с данными для обучения, которые соответствуют буферу памяти, созданному в разделе Предоставление доступа к данным для обучения.

    // 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. Для каждой операции в ориентированном графе, которую вы хотите вычислить, добавьте операцию в модель, вызвав функцию ANeuralNetworksModel_addOperation().

    В качестве параметров для этого вызова приложение должно предоставить:

    • тип операции;
    • количество значений ввода
    • массив индексов для входных операндов;
    • количество выходных значений;
    • массив индексов для выходных операндов;

    Обратите внимание, что операнд нельзя использовать одновременно для ввода и вывода одной и той же операции.

    // 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. Определите, какие операнды модель должна рассматривать как входные и выходные данные, вызвав функцию 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. При необходимости укажите, можно ли рассчитывать ANEURALNETWORKS_TENSOR_FLOAT32 с диапазоном или точностью, которые не превышают аналогичные показатели 16-битного формата с записью с плавающей запятой IEEE 754, вызвав ANeuralNetworksModel_relaxComputationFloat32toFloat16().

  7. Вызовите ANeuralNetworksModel_finish(), чтобы завершить определение модели. Если ошибок нет, функция возвращает код результата ANEURALNETWORKS_NO_ERROR.

    ANeuralNetworksModel_finish(model);

Созданную модель можно компилировать и выполнять сколько угодно раз.

Управление

Чтобы включить поток управления в модель NNAPI, сделайте следующее:

  1. Создайте соответствующие подграфы выполнения (подграфы then и else для оператора IF, подграфы condition и body для цикла WHILE) как отдельные модели ANeuralNetworksModel*:

    ANeuralNetworksModel* thenModel = makeThenModel();
    ANeuralNetworksModel* elseModel = makeElseModel();
  2. Создайте операнды, которые ссылаются на эти модели в модели, содержащей поток управления:

    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. Добавьте операцию управления потоком:

    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);

Сборник

На этапе компиляции определяется, на каких процессорах будет выполняться модель, и соответствующим драйверам отправляется запрос на подготовку к ее выполнению. Это может включать генерацию машинного кода, специфичного для процессоров, на которых будет работать ваша модель.

Чтобы скомпилировать модель, выполните следующие действия:

  1. Вызовите функцию ANeuralNetworksCompilation_create(), чтобы создать новый экземпляр компиляции.

    // Compile the model
    ANeuralNetworksCompilation* compilation;
    ANeuralNetworksCompilation_create(model, &compilation);

    При желании вы можете использовать назначение устройств, чтобы явно выбрать, на каких устройствах будет выполняться код.

  2. Вы можете влиять на то, как среда выполнения балансирует между энергопотреблением и скоростью выполнения. Для этого вызовите метод ANeuralNetworksCompilation_setPreference().

    // Ask to optimize for low power consumption
    ANeuralNetworksCompilation_setPreference(compilation, ANEURALNETWORKS_PREFER_LOW_POWER);

    Вы можете указать следующие предпочтения:

    • ANEURALNETWORKS_PREFER_LOW_POWER – предпочтительно выполнять задачи так, чтобы минимизировать расход заряда батареи. Это желательно для компиляций, которые выполняются часто.
    • ANEURALNETWORKS_PREFER_FAST_SINGLE_ANSWER – как можно быстрее возвращать один ответ, даже если это приведет к повышенному энергопотреблению. Это значение используется по умолчанию.
    • ANEURALNETWORKS_PREFER_SUSTAINED_SPEED – предпочтительно максимизировать пропускную способность последовательных кадров, например при обработке последовательных кадров, поступающих с камеры.
  3. Вы можете настроить кеширование компиляции, вызвав ANeuralNetworksCompilation_setCaching.

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

    Используйте getCodeCacheDir() для cacheDir. Значение token должно быть уникальным для каждой модели в приложении.

  4. Завершите определение компиляции, вызвав ANeuralNetworksCompilation_finish(). Если ошибок нет, функция возвращает код результата ANEURALNETWORKS_NO_ERROR.

    ANeuralNetworksCompilation_finish(compilation);

Обнаружение и назначение устройств

На устройствах Android с Android 10 (уровень API 29) и более поздних версий NNAPI предоставляет функции, которые позволяют библиотекам фреймворков машинного обучения и приложениям получать информацию о доступных устройствах и указывать, какие из них следует использовать для выполнения. Предоставление информации о доступных устройствах позволяет приложениям получать точную версию драйверов, установленных на устройстве, чтобы избежать известных проблем с совместимостью. Возможность указывать, на каких устройствах должны выполняться разные разделы модели, позволяет оптимизировать приложения для устройств Android, на которых они развернуты.

Обнаружение устройств

Используйте ANeuralNetworks_getDeviceCount, чтобы узнать количество доступных устройств. Для каждого устройства используйте ANeuralNetworks_getDevice, чтобы задать экземпляр ANeuralNetworksDevice для ссылки на это устройство.

Получив ссылку на устройство, вы можете узнать о нем дополнительную информацию с помощью следующих функций:

Назначение устройств

Используйте ANeuralNetworksModel_getSupportedOperationsForDevices, чтобы узнать, какие операции модели можно выполнять на определенных устройствах.

Чтобы указать, какие ускорители использовать для выполнения, вызовите ANeuralNetworksCompilation_createForDevices вместо ANeuralNetworksCompilation_create. Используйте полученный объект ANeuralNetworksCompilation как обычно. Функция возвращает ошибку, если указанная модель содержит операции, которые не поддерживаются выбранными устройствами.

Если указано несколько устройств, среда выполнения отвечает за распределение работы между ними.

Как и в случае с другими устройствами, реализация NNAPI CPU представлена объектом ANeuralNetworksDevice с названием nnapi-reference и типом ANEURALNETWORKS_DEVICE_TYPE_CPU. При вызове ANeuralNetworksCompilation_createForDevices реализация ЦП не используется для обработки сбоев компиляции и выполнения модели.

Приложение должно разделить модель на подмодели, которые могут работать на указанных устройствах. Если вам не нужно вручную разбивать модель на разделы, продолжайте вызывать более простой метод ANeuralNetworksCompilation_create, чтобы использовать все доступные устройства (включая ЦП) для ускорения модели. Если модель не поддерживается полностью на устройствах, указанных в ANeuralNetworksCompilation_createForDevices, возвращается ANEURALNETWORKS_BAD_DATA.

Разбиение модели

Если для модели доступно несколько устройств, среда выполнения NNAPI распределяет работу между ними. Например, если в ANeuralNetworksCompilation_createForDevices указано несколько устройств, все они будут учитываться при распределении работы. Обратите внимание, что если устройства ЦП нет в списке, выполнение на ЦП будет отключено. При использовании ANeuralNetworksCompilation_create будут учитываться все доступные устройства, включая ЦП.

Для каждой операции в модели выбирается устройство, которое поддерживает эту операцию и обеспечивает наилучшую производительность, то есть самое быстрое время выполнения или самое низкое энергопотребление, в зависимости от предпочтений клиента. Этот алгоритм разбиения не учитывает возможную неэффективность, вызванную вводом-выводом между различными процессорами, поэтому при указании нескольких процессоров (явно при использовании ANeuralNetworksCompilation_createForDevices или неявно при использовании ANeuralNetworksCompilation_create) важно профилировать результирующее приложение.

Чтобы узнать, как NNAPI разделил вашу модель, найдите в журналах Android сообщение (на уровне INFO с тегом ExecutionPlan):

ModelBuilder::findBestDeviceForEachOperation(op-name): device-index

op-name – описательное название операции на графике, а device-index – индекс устройства-кандидата в списке устройств. Этот список передается в ANeuralNetworksCompilation_createForDevices или, если используется ANeuralNetworksCompilation_createForDevices, возвращается при итерации по всем устройствам с помощью ANeuralNetworks_getDeviceCount и ANeuralNetworks_getDevice.

Сообщение (на уровне INFO с тегом ExecutionPlan):

ModelBuilder::partitionTheWork: only one best device: device-name

Это сообщение означает, что весь граф был ускорен на устройстве. device-name

Выполнение

На этапе выполнения модель применяется к набору входных данных, а результаты вычислений сохраняются в одном или нескольких пользовательских буферах или областях памяти, выделенных вашим приложением.

Чтобы выполнить скомпилированную модель, выполните следующие действия:

  1. Вызовите функцию ANeuralNetworksExecution_create(), чтобы создать новый экземпляр выполнения.

    // Run the compiled model against a set of inputs
    ANeuralNetworksExecution* run1 = NULL;
    ANeuralNetworksExecution_create(compilation, &run1);
  2. Укажите, откуда приложение считывает входные значения для вычислений. Приложение может считывать входные значения из пользовательского буфера или выделенной области памяти, вызывая ANeuralNetworksExecution_setInput() или 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. Укажите, куда приложение записывает выходные значения. Приложение может записывать выходные значения в пользовательский буфер или выделенную область памяти, вызывая ANeuralNetworksExecution_setOutput() или ANeuralNetworksExecution_setOutputFromMemory() соответственно.

    // Set the output
    float32 myOutput[3][4];
    ANeuralNetworksExecution_setOutput(run1, 0, NULL, myOutput, sizeof(myOutput));
  4. Запланируйте запуск выполнения, вызвав функцию ANeuralNetworksExecution_startCompute(). Если ошибок нет, функция возвращает код результата ANEURALNETWORKS_NO_ERROR.

    // Starts the work. The work proceeds asynchronously
    ANeuralNetworksEvent* run1_end = NULL;
    ANeuralNetworksExecution_startCompute(run1, &run1_end);
  5. Вызовите функцию ANeuralNetworksEvent_wait(), чтобы дождаться завершения выполнения. Если выполнение прошло успешно, функция возвращает код результата ANEURALNETWORKS_NO_ERROR. Ожидание может выполняться в потоке, отличном от того, в котором началось выполнение.

    // 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. При необходимости вы можете применить другой набор входных данных к скомпилированной модели, используя тот же экземпляр компиляции для создания нового экземпляра ANeuralNetworksExecution.

    // 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);

Синхронное выполнение

При асинхронном выполнении время тратится на создание и синхронизацию потоков. Кроме того, задержка может сильно варьироваться, а самые длинные задержки достигают 500 микросекунд между моментом уведомления или пробуждения потока и моментом, когда он в конечном итоге привязывается к ядру ЦП.

Чтобы уменьшить задержку, вы можете настроить приложение так, чтобы оно выполняло синхронный вызов среды выполнения для получения ответа. Этот вызов вернется только после завершения вывода, а не после его начала. Вместо вызова ANeuralNetworksExecution_startCompute для асинхронного вызова инференса в среду выполнения приложение вызывает ANeuralNetworksExecution_compute для синхронного вызова среды выполнения. Вызов ANeuralNetworksExecution_compute не принимает ANeuralNetworksEvent и не связан с вызовом ANeuralNetworksEvent_wait.

Выполнение импульсных кампаний

На устройствах Android с Android 10 (уровень API 29) и более поздних версий NNAPI поддерживает пакетное выполнение с помощью объекта ANeuralNetworksBurst. Серийные выполнения – это последовательность выполнений одной и той же компиляции, которые происходят быстро друг за другом, например при обработке кадров с камеры или последовательных аудиосемплов. Использование объектов ANeuralNetworksBurst может ускорить выполнение, поскольку они указывают ускорителям, что ресурсы могут повторно использоваться между выполнениями и что ускорители должны оставаться в высокопроизводительном состоянии на протяжении всего пакета.

ANeuralNetworksBurst вносит лишь небольшое изменение в обычный путь выполнения. Объект Burst создается с помощью ANeuralNetworksBurst_create, как показано в следующем фрагменте кода:

// Create burst object to be reused across a sequence of executions
ANeuralNetworksBurst* burst = NULL;
ANeuralNetworksBurst_create(compilation, &burst);

Пакетные выполнения являются синхронными. Однако вместо того, чтобы использовать ANeuralNetworksExecution_compute для каждого инференса, вы связываете различные объекты ANeuralNetworksExecution с одним и тем же ANeuralNetworksBurst в вызовах функции 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
// ...

Освободите объект ANeuralNetworksBurst с помощью ANeuralNetworksBurst_free, когда он больше не нужен.

// Cleanup
ANeuralNetworksBurst_free(burst);

Асинхронные очереди команд и изолированное выполнение

В Android 11 и более поздних версиях NNAPI поддерживает дополнительный способ планирования асинхронного выполнения с помощью метода ANeuralNetworksExecution_startComputeWithDependencies(). При использовании этого метода выполнение ожидает, пока все зависимые события не будут сигнализированы, прежде чем начать оценку. Когда выполнение завершится и выходные данные будут готовы к использованию, возвращенное событие будет сигнализировано.

В зависимости от того, какие устройства обрабатывают выполнение, событие может быть подкреплено барьером синхронизации. Чтобы дождаться события и восстановить ресурсы, которые использовались при выполнении, необходимо вызвать ANeuralNetworksEvent_wait(). Вы можете импортировать барьеры синхронизации в объект события с помощью ANeuralNetworksEvent_createFromSyncFenceFd() и экспортировать их из объекта события с помощью ANeuralNetworksEvent_getSyncFenceFd().

Выходные данные динамического размера

Чтобы поддерживать модели, в которых размер выходных данных зависит от входных данных (то есть размер нельзя определить во время выполнения модели), используйте ANeuralNetworksExecution_getOutputOperandRank и ANeuralNetworksExecution_getOutputOperandDimensions.

В приведенном ниже фрагменте кода показано, как это сделать:

// 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());

Очистка

На этапе очистки освобождаются внутренние ресурсы, использованные для вычислений.

// Cleanup
ANeuralNetworksCompilation_free(compilation);
ANeuralNetworksModel_free(model);
ANeuralNetworksMemory_free(mem1);

Управление ошибками и резервный ЦП

Если во время разбиения на разделы возникает ошибка, если драйверу не удается скомпилировать модель (или ее часть) или выполнить скомпилированную модель (или ее часть), NNAPI может вернуться к собственной реализации одной или нескольких операций на ЦП.

Если клиент NNAPI содержит оптимизированные версии операции (например, TFLite), может быть полезно отключить резервный ЦП и обрабатывать сбои с помощью оптимизированной реализации операции клиента.

Если в Android 10 компиляция выполняется с помощью ANeuralNetworksCompilation_createForDevices, резервный ЦП будет отключен.

В Android P выполнение NNAPI возвращается к ЦП, если выполнение на драйвере завершается неудачно. Это также относится к Android 10, если используется ANeuralNetworksCompilation_create, а не ANeuralNetworksCompilation_createForDevices.

Сначала резервный вариант используется для одного раздела, а если это не помогает, то вся модель повторно запускается на ЦП.

Если разбиение или компиляция не удастся, вся модель будет запущена на ЦП.

В некоторых случаях определенные операции не поддерживаются на ЦП, и в таких ситуациях компиляция или выполнение завершатся ошибкой, а не резервным копированием.

Даже после отключения резервного ЦП в модели могут остаться операции, запланированные на ЦП. Если ЦП есть в списке процессоров, предоставленном ANeuralNetworksCompilation_createForDevices, и он является единственным процессором, поддерживающим эти операции, или процессором, который обеспечивает наилучшую производительность для этих операций, он будет выбран в качестве основного (не резервного) исполнителя.

Чтобы избежать выполнения на ЦП, используйте ANeuralNetworksCompilation_createForDevices, исключив nnapi-reference из списка устройств. Начиная с Android P, можно отключить резервный вариант во время выполнения в отладочных сборках, установив для свойства debug.nn.partition значение 2.

Домены памяти

В Android 11 и более поздних версиях NNAPI поддерживает домены памяти, которые предоставляют интерфейсы распределителя для непрозрачной памяти. Это позволяет приложениям передавать данные из памяти устройства между выполнениями, чтобы NNAPI не копировал и не преобразовывал данные без необходимости при последовательных выполнениях на одном и том же драйвере.

Функция домена памяти предназначена для тензоров, которые в основном являются внутренними для драйвера и не требуют частого доступа к клиентской стороне. Примерами таких тензоров являются тензоры состояния в моделях последовательностей. Для тензоров, которым требуется частый доступ к ЦП на стороне клиента, используйте пулы общей памяти.

Чтобы выделить непрозрачную память, выполните следующие действия:

  1. Вызовите функцию ANeuralNetworksMemoryDesc_create(), чтобы создать новый дескриптор памяти:

    // Create a memory descriptor
    ANeuralNetworksMemoryDesc* desc;
    ANeuralNetworksMemoryDesc_create(&desc);
  2. Укажите все роли входных и выходных данных, вызвав ANeuralNetworksMemoryDesc_addInputRole() и 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. При необходимости укажите размеры памяти, вызвав ANeuralNetworksMemoryDesc_setDimensions().

    // Specify the memory dimensions
    uint32_t dims[] = {3, 4};
    ANeuralNetworksMemoryDesc_setDimensions(desc, 2, dims);
  4. Завершите определение дескриптора, вызвав ANeuralNetworksMemoryDesc_finish().

    ANeuralNetworksMemoryDesc_finish(desc);
  5. Выделите столько памяти, сколько вам нужно, передав дескриптор в ANeuralNetworksMemory_createFromDesc().

    // Allocate two opaque memories with the descriptor
    ANeuralNetworksMemory* opaqueMem;
    ANeuralNetworksMemory_createFromDesc(desc, &opaqueMem);
  6. Освободите дескриптор памяти, когда он больше не нужен.

    ANeuralNetworksMemoryDesc_free(desc);

Клиент может использовать созданный объект ANeuralNetworksMemory только с ANeuralNetworksExecution_setInputFromMemory() или ANeuralNetworksExecution_setOutputFromMemory() в соответствии с ролями, указанными в объекте ANeuralNetworksMemoryDesc. Аргументы offset и length должны быть равны 0, что означает использование всей памяти. Клиент также может явно задать или извлечь содержимое памяти с помощью ANeuralNetworksMemory_copy().

Вы можете создавать непрозрачные воспоминания с ролями неопределенных размеров или рангов. В этом случае создание памяти может завершиться ошибкой со статусом ANEURALNETWORKS_OP_FAILED, если оно не поддерживается базовым драйвером. Клиенту рекомендуется реализовать резервную логику, выделив достаточно большой буфер, поддерживаемый Ashmem или AHardwareBuffer в режиме BLOB.

Когда NNAPI больше не нужен доступ к объекту непрозрачной памяти, освободите соответствующий экземпляр ANeuralNetworksMemory:

ANeuralNetworksMemory_free(opaqueMem);

Оценка эффективности

Чтобы оценить производительность приложения, можно измерить время выполнения или провести профилирование.

Время выполнения

Чтобы определить общее время выполнения, можно использовать API синхронного выполнения и измерить время, затраченное на вызов. Если вы хотите определить общее время выполнения на более низком уровне стека ПО, используйте ANeuralNetworksExecution_setMeasureTiming и ANeuralNetworksExecution_getDuration, чтобы получить:

  • время выполнения на ускорителе (не в драйвере, который работает на хост-процессоре).
  • время выполнения в драйвере, включая время на ускорителе.

Время выполнения в драйвере не включает накладные расходы, например на саму среду выполнения и IPC, необходимые для связи среды выполнения с драйвером.

Эти API измеряют продолжительность между событиями отправки работы и завершения работы, а не время, которое драйвер или ускоритель посвящает выполнению инференса, возможно, прерванного сменой контекста.

Например, если начинается инференс 1, затем драйвер останавливает работу, чтобы выполнить инференс 2, а затем возобновляет и завершает инференс 1, время выполнения инференса 1 будет включать время, когда работа была остановлена для выполнения инференса 2.

Эта информация может быть полезна для развертывания приложения в рабочей среде, чтобы собирать телеметрию для использования в офлайн-режиме. Вы можете использовать эти данные, чтобы повысить производительность приложения.

При использовании этой функции учитывайте следующее:

  • Сбор информации о времени может повлиять на производительность.
  • Только драйвер может рассчитать время, потраченное на выполнение операций в нем самом или на ускорителе, за исключением времени, потраченного в среде выполнения NNAPI и в IPC.
  • Эти API можно использовать только с ANeuralNetworksExecution, созданным с помощью ANeuralNetworksCompilation_createForDevices с numDevices = 1.
  • Для передачи информации о времени не требуется драйвер.

Как профилировать приложение с помощью Android Systrace

Начиная с Android 10 NNAPI автоматически генерирует события systrace, которые можно использовать для профилирования приложения.

В NNAPI Source есть утилита parse_systrace, которая обрабатывает события systrace, созданные вашим приложением, и генерирует таблицу с данными о времени, затраченном на разные этапы жизненного цикла модели (создание экземпляра, подготовка, компиляция, выполнение и завершение) и разные уровни приложений. Приложение разделено на следующие уровни:

  • Application – основной код приложения;
  • Runtime: среда выполнения NNAPI
  • IPC – межпроцессное взаимодействие между средой выполнения NNAPI и кодом драйвера.
  • Driver – процесс драйвера ускорителя.

Создать данные анализа профилирования

Предположим, вы извлекли дерево исходного кода AOSP в $ANDROID_BUILD_TOP и используете пример классификации изображений TFLite в качестве целевого приложения. Чтобы сгенерировать данные профилирования NNAPI, выполните следующие действия:

  1. Запустите Android systrace с помощью следующей команды:
$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

Параметр -o trace.html указывает, что трассировки будут записаны в trace.html. При профилировании собственного приложения вам нужно будет заменить org.tensorflow.lite.examples.classification на название процесса, указанное в манифесте приложения.

Это займет одну из консолей оболочки. Не запускайте команду в фоновом режиме, поскольку она ожидает завершения enter в интерактивном режиме.

  1. После запуска сборщика systrace запустите приложение и выполните тест производительности.

В нашем случае вы можете запустить приложение Image Classification из Android Studio или непосредственно из интерфейса тестового телефона, если приложение уже установлено. Чтобы сгенерировать данные NNAPI, необходимо настроить приложение на использование NNAPI, выбрав NNAPI в качестве целевого устройства в диалоговом окне конфигурации приложения.

  1. Когда тестирование завершится, остановите Systrace, нажав enter в терминале консоли, который был активен с шага 1.

  2. Запустите утилиту systrace_parser, чтобы создать сводную статистику:

$ANDROID_BUILD_TOP/frameworks/ml/nn/tools/systrace_parser/parse_systrace.py --total-times trace.html

Синтаксический анализатор принимает следующие параметры: - --total-times: показывает общее время, проведенное на уровне, включая время, потраченное на ожидание выполнения вызова базового уровня. - --print-detail: выводит все события, собранные из systrace. - --per-execution: выводит только выполнение и его подфазы (как время на выполнение), а не статистику для всех фаз. - --json: выводит данные в формате JSON.

Пример выходных данных:

===========================================================================================================================================
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

Синтаксический анализатор может не сработать, если собранные события не представляют собой полную трассировку приложения. В частности, это может произойти, если в трассировке присутствуют события systrace, сгенерированные для обозначения конца раздела, без связанного события начала раздела. Обычно это происходит, если при запуске сборщика systrace генерируются события из предыдущего сеанса профилирования. В этом случае вам придется запустить профилирование заново.

Добавьте статистику по коду приложения в выходные данные systrace_parser.

Приложение parse_systrace основано на встроенной функции systrace в Android. Вы можете добавить трассировку для определенных операций в приложении, используя API systrace (для Java, для нативных приложений) с собственными названиями событий.

Чтобы связать специальные события с этапами жизненного цикла приложения, добавьте в начало названия события одну из следующих строк:

  • [NN_LA_PI]: событие инициализации на уровне приложения
  • [NN_LA_PP]: событие на уровне приложения для подготовки
  • [NN_LA_PC]: событие на уровне приложения для компиляции
  • [NN_LA_PE]: событие на уровне приложения для выполнения

Ниже показано, как можно изменить пример кода классификации изображений TFLite, добавив раздел runInferenceModel для этапа Execution и слой Application, содержащий другие разделы preprocessBitmap, которые не будут учитываться в трассировках NNAPI. Раздел runInferenceModel будет частью событий systrace, обработанных синтаксическим анализатором 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;
}

Качество обслуживания

В Android 11 и более поздних версиях NNAPI обеспечивает лучшее качество обслуживания (QoS), позволяя приложению указывать относительные приоритеты своих моделей, максимальное количество времени, ожидаемое для подготовки данной модели, и максимальное количество времени, ожидаемое для завершения данного вычисления. В Android 11 также появились дополнительные коды результатов NNAPI, которые позволяют приложениям распознавать сбои, например пропущенные сроки выполнения.

Как задать приоритет рабочей нагрузки

Чтобы задать приоритет рабочей нагрузки NNAPI, вызовите ANeuralNetworksCompilation_setPriority() перед вызовом ANeuralNetworksCompilation_finish().

Устанавливайте сроки

Приложения могут устанавливать сроки как для компиляции модели, так и для вывода.

Подробнее об операндах

В этом разделе рассматриваются дополнительные темы, связанные с использованием операндов.

Квантованные тензоры

Квантованный тензор – это компактный способ представления n-мерного массива значений с плавающей запятой.

NNAPI поддерживает 8-битные асимметричные квантованные тензоры. Для этих тензоров значение каждой ячейки представлено 8-битным целым числом. С тензором связаны масштаб и значение нулевой точки. Они используются для преобразования 8-битных целых чисел в значения с плавающей запятой.

Формула:

(cellValue - zeroPoint) * scale

где значение zeroPoint – это 32-битное целое число, а scale – 32-битное число с плавающей запятой.

По сравнению с тензорами 32-битных значений с плавающей запятой, 8-битные квантованные тензоры имеют два преимущества:

  • Приложение занимает меньше места, поскольку обученные веса занимают четверть размера 32-битных тензоров.
  • Вычисления часто выполняются быстрее. Это связано с меньшим объемом данных, которые необходимо извлечь из памяти, и эффективностью процессоров, таких как DSP, при выполнении целочисленных математических вычислений.

Хотя модель с плавающей запятой можно преобразовать в квантованную, наш опыт показывает, что лучшие результаты достигаются при непосредственном обучении квантованной модели. В результате нейронная сеть учится компенсировать повышенную детализацию каждого значения. Для каждого квантованного тензора значения scale и zeroPoint определяются в процессе обучения.

В NNAPI типы квантованных тензоров определяются путем установки поля типа структуры данных ANeuralNetworksOperandType в значение ANEURALNETWORKS_TENSOR_QUANT8_ASYMM. Вы также указываете масштаб и значение zeroPoint тензора в этой структуре данных.

Помимо 8-битных асимметричных квантованных тензоров, NNAPI поддерживает следующие:

Необязательные операнды

Некоторые операции, например ANEURALNETWORKS_LSH_PROJECTION, принимают необязательные операнды. Чтобы указать в модели, что необязательный операнд опущен, вызовите функцию ANeuralNetworksModel_setOperandValue(), передав NULL для буфера и 0 для длины.

Если решение о том, присутствует ли операнд, меняется при каждом выполнении, вы указываете, что операнд опущен, используя функции ANeuralNetworksExecution_setInput() или ANeuralNetworksExecution_setOutput(), передавая NULL для буфера и 0 для длины.

Тензоры неизвестного ранга

В Android 9 (уровень API 28) появились операнды модели с неизвестными размерами, но известным рангом (количеством размеров). В Android 10 (уровень API 29) появились тензоры неизвестного ранга, как показано в ANeuralNetworksOperandType.

Тест производительности NNAPI

Тест NNAPI доступен в AOSP в platform/test/mlts/benchmark (приложение для тестирования) и platform/test/mlts/models (модели и наборы данных).

Тест оценивает задержку и точность и сравнивает драйверы с той же работой, выполненной с использованием Tensorflow Lite, работающего на ЦП, для тех же моделей и наборов данных.

Чтобы использовать контрольный показатель, выполните следующие действия:

  1. Подключите целевое устройство Android к компьютеру, откройте окно терминала и убедитесь, что устройство доступно через ADB.

  2. Если подключено несколько устройств Android, экспортируйте переменную среды целевого устройства ANDROID_SERIAL.

  3. Перейдите в каталог с исходными файлами Android верхнего уровня.

  4. Выполните следующие команды:

    lunch aosp_arm-userdebug # Or aosp_arm64-userdebug if available
    ./test/mlts/benchmark/build_and_run_benchmark.sh
    

    По окончании тестирования его результаты будут представлены в виде HTML-страницы, переданной в xdg-open.

Журналы NNAPI

NNAPI генерирует полезную диагностическую информацию в системных журналах. Для анализа журналов используйте утилиту logcat.

Чтобы включить подробное ведение журнала NNAPI для определенных этапов или компонентов, задайте для свойства debug.nn.vlog (с помощью adb shell) следующие значения, разделенные пробелом, двоеточием или запятой:

  • model: построение модели
  • compilation: создание плана выполнения модели и компиляция.
  • execution: выполнение модели
  • cpuexe – выполнение операций с помощью реализации NNAPI CPU.
  • manager: расширения NNAPI, доступные интерфейсы и информация о возможностях
  • all или 1: все элементы, перечисленные выше.

Например, чтобы включить подробное ведение журнала, используйте команду adb shell setprop debug.nn.vlog all. Чтобы отключить подробное ведение журнала, используйте команду adb shell setprop debug.nn.vlog '""'.

После включения подробного ведения журналов создаются записи в журнале на уровне INFO с тегом, установленным для имени этапа или компонента.

Помимо debug.nn.vlog контролируемых сообщений, компоненты API NNAPI предоставляют другие записи в журнале на разных уровнях, каждая из которых использует определенный тег журнала.

Чтобы получить список компонентов, выполните поиск по дереву исходного кода, используя следующее выражение:

grep -R 'define LOG_TAG' | awk -F '"' '{print $2}' | sort -u | egrep -v "Sample|FileTag|test"

В настоящее время это выражение возвращает следующие теги:

  • BurstBuilder
  • Обратные вызовы
  • CompilationBuilder
  • CpuExecutor
  • ExecutionBuilder
  • ExecutionBurstController
  • ExecutionBurstServer
  • ExecutionPlan
  • FibonacciDriver
  • GraphDump
  • IndexedShapeWrapper
  • IonWatcher
  • Менеджер
  • Память
  • MemoryUtils
  • MetaModel
  • ModelArgumentInfo
  • ModelBuilder
  • Нейросети
  • OperationResolver
  • Операции
  • OperationsUtils
  • PackageInfo
  • TokenHasher
  • TypeManager
  • Utils
  • ValidateHal
  • VersionedInterfaces

Чтобы управлять уровнем сообщений в журнале, которые показывает logcat, используйте переменную среды ANDROID_LOG_TAGS.

Чтобы показывать полный набор сообщений в журнале NNAPI и отключить все остальные, задайте для ANDROID_LOG_TAGS следующее значение:

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.

Вы можете задать ANDROID_LOG_TAGS с помощью следующей команды:

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')

Обратите внимание, что это просто фильтр, который применяется к logcat. Чтобы создать подробный журнал, вам по-прежнему нужно задать для свойства debug.nn.vlog значение all.