TensorFlow Lite на графическом процессоре

TensorFlow Lite поддерживает несколько аппаратных ускорителей. В этом документе описывается, как использовать серверную часть графического процессора с помощью API делегатов TensorFlow Lite на Android (требуется OpenCL или OpenGL ES 3.1 и выше) и iOS (требуется iOS 8 или более поздней версии).

Преимущества GPU-ускорения

Скорость

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

Точность

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

Энергоэффективность

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

Поддерживаемые операции

TensorFlow Lite на GPU поддерживает следующие операции с 16-битной и 32-битной точностью с плавающей запятой:

  • ADD
  • AVERAGE_POOL_2D
  • CONCATENATION
  • CONV_2D
  • DEPTHWISE_CONV_2D v1-2
  • EXP
  • FULLY_CONNECTED
  • LOGISTIC
  • LSTM v2 (Basic LSTM only)
  • MAX_POOL_2D
  • MAXIMUM
  • MINIMUM
  • MUL
  • PAD
  • PRELU
  • RELU
  • RELU6
  • RESHAPE
  • RESIZE_BILINEAR v1-3
  • SOFTMAX
  • STRIDED_SLICE
  • SUB
  • TRANSPOSE_CONV

По умолчанию все операции поддерживаются только в версии 1. Включение поддержки экспериментального квантования разрешает соответствующие версии; например, ДОБАВИТЬ v2.

Основное использование

Существует два способа вызвать ускорение модели в Android в зависимости от того, используете ли вы привязку модели Android Studio ML или интерпретатор TensorFlow Lite.

Android через интерпретатор TensorFlow Lite

Добавьте tensorflow-lite-gpu вместе с существующим tensorflow-lite в существующий блок dependencies .

dependencies {
    ...
    implementation 'org.tensorflow:tensorflow-lite:2.3.0'
    implementation 'org.tensorflow:tensorflow-lite-gpu:2.3.0'
}

Затем запустите TensorFlow Lite на графическом процессоре с помощью TfLiteDelegate . В Java вы можете указать GpuDelegate через Interpreter.Options .

Котлин

    import org.tensorflow.lite.Interpreter
    import org.tensorflow.lite.gpu.CompatibilityList
    import org.tensorflow.lite.gpu.GpuDelegate

    val compatList = CompatibilityList()

    val options = Interpreter.Options().apply{
        if(compatList.isDelegateSupportedOnThisDevice){
            // if the device has a supported GPU, add the GPU delegate
            val delegateOptions = compatList.bestOptionsForThisDevice
            this.addDelegate(GpuDelegate(delegateOptions))
        } else {
            // if the GPU is not supported, run on 4 threads
            this.setNumThreads(4)
        }
    }

    val interpreter = Interpreter(model, options)

    // Run inference
    writeToInput(input)
    interpreter.run(input, output)
    readFromOutput(output)
      

Джава

    import org.tensorflow.lite.Interpreter;
    import org.tensorflow.lite.gpu.CompatibilityList;
    import org.tensorflow.lite.gpu.GpuDelegate;

    // Initialize interpreter with GPU delegate
    Interpreter.Options options = new Interpreter.Options();
    CompatibilityList compatList = CompatibilityList();

    if(compatList.isDelegateSupportedOnThisDevice()){
        // if the device has a supported GPU, add the GPU delegate
        GpuDelegate.Options delegateOptions = compatList.getBestOptionsForThisDevice();
        GpuDelegate gpuDelegate = new GpuDelegate(delegateOptions);
        options.addDelegate(gpuDelegate);
    } else {
        // if the GPU is not supported, run on 4 threads
        options.setNumThreads(4);
    }

    Interpreter interpreter = new Interpreter(model, options);

    // Run inference
    writeToInput(input);
    interpreter.run(input, output);
    readFromOutput(output);
      

Android (С/С++)

Для использования C/C++ графического процессора TensorFlow Lite на Android делегат графического процессора может быть создан с помощью TfLiteGpuDelegateV2Create() и уничтожен с помощью TfLiteGpuDelegateV2Delete() .

// Set up interpreter.
auto model = FlatBufferModel::BuildFromFile(model_path);
if (!model) return false;
ops::builtin::BuiltinOpResolver op_resolver;
std::unique_ptr<Interpreter> interpreter;
InterpreterBuilder(*model, op_resolver)(&interpreter);

// NEW: Prepare GPU delegate.
auto* delegate = TfLiteGpuDelegateV2Create(/*default options=*/nullptr);
if (interpreter->ModifyGraphWithDelegate(delegate) != kTfLiteOk) return false;

// Run inference.
WriteToInputTensor(interpreter->typed_input_tensor<float>(0));
if (interpreter->Invoke() != kTfLiteOk) return false;
ReadFromOutputTensor(interpreter->typed_output_tensor<float>(0));

// NEW: Clean up.
TfLiteGpuDelegateV2Delete(delegate);

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

Графический процессор TFLite для Android C/C++ использует систему сборки Bazel . Делегат можно построить, например, с помощью следующей команды:

bazel build -c opt --config android_arm64 tensorflow/lite/delegates/gpu:delegate                           # for static library
bazel build -c opt --config android_arm64 tensorflow/lite/delegates/gpu:libtensorflowlite_gpu_delegate.so  # for dynamic library

iOS (С++)

Чтобы использовать TensorFlow Lite на GPU, получите делегат GPU через TFLGpuDelegateCreate() , а затем передайте его в Interpreter::ModifyGraphWithDelegate() (вместо вызова Interpreter::AllocateTensors() ).

// Set up interpreter.
auto model = FlatBufferModel::BuildFromFile(model_path);
if (!model) return false;
tflite::ops::builtin::BuiltinOpResolver op_resolver;
std::unique_ptr<Interpreter> interpreter;
InterpreterBuilder(*model, op_resolver)(&interpreter);

// NEW: Prepare GPU delegate.

auto* delegate = TFLGpuDelegateCreate(/*default options=*/nullptr);
if (interpreter->ModifyGraphWithDelegate(delegate) != kTfLiteOk) return false;

// Run inference.
WriteToInputTensor(interpreter->typed_input_tensor<float>(0));
if (interpreter->Invoke() != kTfLiteOk) return false;
ReadFromOutputTensor(interpreter->typed_output_tensor<float>(0));

// Clean up.
TFLGpuDelegateDelete(delegate);

Расширенное использование

Параметры делегирования для iOS

Конструктор делегата GPU принимает struct опций. ( Swift API , Objective-C API , C API )

Передача nullptr (C API) или ничего (Objective-C и Swift API) инициализатору устанавливает параметры по умолчанию (которые подробно описаны в примере базового использования выше).

Быстрый

    // THIS:
    var options = MetalDelegate.Options()
    options.isPrecisionLossAllowed = false
    options.waitType = .passive
    options.isQuantizationEnabled = true
    let delegate = MetalDelegate(options: options)

    // IS THE SAME AS THIS:
    let delegate = MetalDelegate()
      

Цель-C

    // THIS:
    TFLMetalDelegateOptions* options = [[TFLMetalDelegateOptions alloc] init];
    options.precisionLossAllowed = false;
    options.waitType = TFLMetalDelegateThreadWaitTypePassive;
    options.quantizationEnabled = true;

    TFLMetalDelegate* delegate = [[TFLMetalDelegate alloc] initWithOptions:options];

    // IS THE SAME AS THIS:
    TFLMetalDelegate* delegate = [[TFLMetalDelegate alloc] init];
      

С

    // THIS:
    const TFLGpuDelegateOptions options = {
      .allow_precision_loss = false,
      .wait_type = TFLGpuDelegateWaitType::TFLGpuDelegateWaitTypePassive,
      .enable_quantization = true,
    };

    TfLiteDelegate* delegate = TFLGpuDelegateCreate(options);

    // IS THE SAME AS THIS:
    TfLiteDelegate* delegate = TFLGpuDelegateCreate(nullptr);
      

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

Запуск квантованных моделей на GPU

В этом разделе объясняется, как делегат GPU ускоряет 8-битные квантованные модели. Это включает в себя все виды квантования, в том числе:

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

Как это работает?

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

  • Постоянные тензоры (такие как веса/смещения) деквантуются один раз в память графического процессора. Это происходит, когда делегат применяется к интерпретатору TFLite.

  • Входные и выходные данные для программы графического процессора, если они квантуются 8 битами, деквантуются и квантуются (соответственно) для каждого вывода. Это делается на ЦП с использованием оптимизированных ядер TFLite.

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

Эту функцию можно включить с помощью параметров делегирования следующим образом:

Андроид

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

С++ API

TfLiteGpuDelegateOptionsV2 options = TfLiteGpuDelegateOptionsV2Default();
options.experimental_flags = TFLITE_GPU_EXPERIMENTAL_FLAGS_NONE;

auto* delegate = TfLiteGpuDelegateV2Create(options);
if (interpreter->ModifyGraphWithDelegate(delegate) != kTfLiteOk) return false;

Java-API

GpuDelegate delegate = new GpuDelegate(new GpuDelegate.Options().setQuantizedModelsAllowed(false));

Interpreter.Options options = (new Interpreter.Options()).addDelegate(delegate);

iOS

API-интерфейсы iOS по умолчанию поддерживают квантованные модели. Чтобы отключить, сделайте следующее:

Быстрый

    var options = MetalDelegate.Options()
    options.isQuantizationEnabled = false
    let delegate = MetalDelegate(options: options)
      

Цель-C

    TFLMetalDelegateOptions* options = [[TFLMetalDelegateOptions alloc] init];
    options.quantizationEnabled = false;
      

С

    TFLGpuDelegateOptions options = TFLGpuDelegateOptionsDefault();
    options.enable_quantization = false;

    TfLiteDelegate* delegate = TFLGpuDelegateCreate(options);
      

Буферы ввода/вывода (только для iOS, C++ API)

Для выполнения вычислений на графическом процессоре данные должны быть доступны для графического процессора. Это часто требует выполнения копирования памяти. Желательно по возможности не пересекать границу памяти CPU/GPU, так как это может занять значительное количество времени. Обычно такое пересечение неизбежно, но в некоторых особых случаях можно и то, и другое опустить.

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

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

Предполагая, что входное изображение находится в памяти графического процессора, его необходимо сначала преобразовать в объект MTLBuffer для Metal. Вы можете связать TfLiteTensor с подготовленным пользователем MTLBuffer с помощью TFLGpuDelegateBindMetalBufferToTensor() . Обратите внимание, что TFLGpuDelegateBindMetalBufferToTensor() должен вызываться после Interpreter::ModifyGraphWithDelegate() . Кроме того, выходные данные по умолчанию копируются из памяти графического процессора в память ЦП. Это поведение можно отключить, вызвав Interpreter::SetAllowBufferHandleOutput(true) во время инициализации.

#include "tensorflow/lite/delegates/gpu/metal_delegate.h"
#include "tensorflow/lite/delegates/gpu/metal_delegate_internal.h"

// ...

// Prepare GPU delegate.
auto* delegate = TFLGpuDelegateCreate(nullptr);

if (interpreter->ModifyGraphWithDelegate(delegate) != kTfLiteOk) return false;

interpreter->SetAllowBufferHandleOutput(true);  // disable default gpu->cpu copy
if (!TFLGpuDelegateBindMetalBufferToTensor(
        delegate, interpreter->inputs()[0], user_provided_input_buffer)) {
  return false;
}
if (!TFLGpuDelegateBindMetalBufferToTensor(
        delegate, interpreter->outputs()[0], user_provided_output_buffer)) {
  return false;
}

// Run inference.
if (interpreter->Invoke() != kTfLiteOk) return false;

Сериализация делегата графического процессора

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

С++

    TfLiteGpuDelegateOptionsV2 options = TfLiteGpuDelegateOptionsV2Default();
    options.experimental_flags |= TFLITE_GPU_EXPERIMENTAL_FLAGS_ENABLE_SERIALIZATION;
    options.serialization_dir = kTmpDir;
    options.model_token = kModelToken;

    auto* delegate = TfLiteGpuDelegateV2Create(options);
    if (interpreter->ModifyGraphWithDelegate(delegate) != kTfLiteOk) return false;
      

Джава

    GpuDelegate delegate = new GpuDelegate(
      new GpuDelegate.Options().setSerializationParams(
        /* serializationDir= */ serializationDir,
        /* modelToken= */ modelToken));

    Interpreter.Options options = (new Interpreter.Options()).addDelegate(delegate);
      

При использовании функции сериализации убедитесь, что ваш код соответствует этим правилам реализации:

  • Храните данные сериализации в каталоге, недоступном для других приложений. На устройствах Android используйте getCodeCacheDir() , который указывает на расположение, которое является частным для текущего приложения.
  • Маркер модели должен быть уникальным для устройства конкретной модели. Вы можете вычислить токен модели, сгенерировав отпечаток пальца из данных модели (например, используя farmhash::Fingerprint64 ).

Советы и приемы

  • Некоторые операции, которые тривиальны для ЦП, могут быть дорогостоящими для графического процессора. Один класс таких операций включает в себя различные формы операций изменения формы (включая BATCH_TO_SPACE , SPACE_TO_BATCH , SPACE_TO_DEPTH и аналогичные операции). Если эти операции не требуются (например, они были вставлены, чтобы помочь сетевому архитектору разобраться в системе, но никак иначе не влияют на вывод), их стоит удалить для повышения производительности.

  • На графическом процессоре тензорные данные нарезаются на 4 канала. Таким образом, вычисление тензора формы [B, H, W, 5] будет выполняться примерно так же для тензора формы [B, H, W, 8] , но значительно хуже, чем [B, H, W, 4] ]. [B, H, W, 4] .

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