Delegado de aceleração de GPU para iOS

O uso de unidades de processamento gráfico (GPUs) para executar modelos de machine learning (ML) pode melhorar muito o desempenho do modelo e a experiência do usuário nos aplicativos habilitados para ML. Em dispositivos iOS, é possível ativar o uso da execução acelerada pela GPU dos modelos usando um delegado. Os delegados atuam como drivers de hardware para o TensorFlow Lite, permitindo que você execute o código do modelo em processadores GPU.

Nesta página, descrevemos como ativar a aceleração de GPU para modelos do TensorFlow Lite em apps iOS. Para saber mais sobre como usar o delegado de GPU para o TensorFlow Lite, incluindo práticas recomendadas e técnicas avançadas, consulte a página Delegados de GPU.

Usar a GPU com a API Interpreter

A API Interpreter do TensorFlow Lite oferece um conjunto de APIs de uso geral para criar aplicativos de machine learning. As instruções a seguir orientam você sobre como adicionar suporte a GPUs a um app iOS. Neste guia, presume-se que você já tenha um app iOS que possa executar um modelo de ML com o TensorFlow Lite.

Modifique o Podfile para incluir suporte a GPUs

A partir da versão 2.3.0 do TensorFlow Lite, o delegado da GPU é excluído do pod para reduzir o tamanho binário. É possível incluí-los especificando um subspec para o pod TensorFlowLiteSwift:

pod 'TensorFlowLiteSwift/Metal', '~> 0.0.1-nightly',

OU

pod 'TensorFlowLiteSwift', '~> 0.0.1-nightly', :subspecs => ['Metal']

Também é possível usar TensorFlowLiteObjC ou TensorFlowLiteC se você quiser usar o Objective-C, disponível para as versões 2.4.0 e mais recentes, ou a API C.

Inicializar e usar o delegado da GPU

É possível usar o delegado da GPU com a API Interpreter do TensorFlow Lite com várias linguagens de programação. Swift e Objective-C são recomendados, mas você também pode usar C++ e C. É necessário usar C se você estiver usando uma versão do TensorFlow Lite anterior à 2.4. Os exemplos de código a seguir descrevem como usar o delegado com cada uma dessas linguagens.

Swift

import TensorFlowLite

// Load model ...

// Initialize TensorFlow Lite interpreter with the GPU delegate.
let delegate = MetalDelegate()
if let interpreter = try Interpreter(modelPath: modelPath,
                                      delegates: [delegate]) {
  // Run inference ...
}
      

Objective-C

// Import module when using CocoaPods with module support
@import TFLTensorFlowLite;

// Or import following headers manually
#import "tensorflow/lite/objc/apis/TFLMetalDelegate.h"
#import "tensorflow/lite/objc/apis/TFLTensorFlowLite.h"

// Initialize GPU delegate
TFLMetalDelegate* metalDelegate = [[TFLMetalDelegate alloc] init];

// Initialize interpreter with model path and GPU delegate
TFLInterpreterOptions* options = [[TFLInterpreterOptions alloc] init];
NSError* error = nil;
TFLInterpreter* interpreter = [[TFLInterpreter alloc]
                                initWithModelPath:modelPath
                                          options:options
                                        delegates:@[ metalDelegate ]
                                            error:&error];
if (error != nil) { /* Error handling... */ }

if (![interpreter allocateTensorsWithError:&error]) { /* Error handling... */ }
if (error != nil) { /* Error handling... */ }

// Run inference ...
      

C++

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

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

C (antes da 2.4.0)

#include "tensorflow/lite/c/c_api.h"
#include "tensorflow/lite/delegates/gpu/metal_delegate.h"

// Initialize model
TfLiteModel* model = TfLiteModelCreateFromFile(model_path);

// Initialize interpreter with GPU delegate
TfLiteInterpreterOptions* options = TfLiteInterpreterOptionsCreate();
TfLiteDelegate* delegate = TFLGPUDelegateCreate(nil);  // default config
TfLiteInterpreterOptionsAddDelegate(options, metal_delegate);
TfLiteInterpreter* interpreter = TfLiteInterpreterCreate(model, options);
TfLiteInterpreterOptionsDelete(options);

TfLiteInterpreterAllocateTensors(interpreter);

NSMutableData *input_data = [NSMutableData dataWithLength:input_size * sizeof(float)];
NSMutableData *output_data = [NSMutableData dataWithLength:output_size * sizeof(float)];
TfLiteTensor* input = TfLiteInterpreterGetInputTensor(interpreter, 0);
const TfLiteTensor* output = TfLiteInterpreterGetOutputTensor(interpreter, 0);

// Run inference
TfLiteTensorCopyFromBuffer(input, inputData.bytes, inputData.length);
TfLiteInterpreterInvoke(interpreter);
TfLiteTensorCopyToBuffer(output, outputData.mutableBytes, outputData.length);

// Clean up
TfLiteInterpreterDelete(interpreter);
TFLGpuDelegateDelete(metal_delegate);
TfLiteModelDelete(model);
      

Observações sobre o uso da linguagem da API GPU

  • As versões do TensorFlow Lite anteriores à 2.4.0 só podem usar a API C para Objective-C.
  • A API C++ só está disponível quando você usa o Bazel ou cria o TensorFlow Lite por conta própria. A API C++ não pode ser usada com CocoaPods.
  • Ao usar o TensorFlow Lite com o delegado da GPU com C++, consiga o delegado da GPU usando a função TFLGpuDelegateCreate() e, em seguida, transmita-o para Interpreter::ModifyGraphWithDelegate(), em vez de chamar Interpreter::AllocateTensors().

Criar e testar com o modo de lançamento

Mude para um build de lançamento com as configurações apropriadas do acelerador da API Metal para melhorar a performance e para os testes finais. Esta seção explica como ativar um build de lançamento e definir as configurações de aceleração do Metal.

Para mudar para um build de lançamento:

  1. Para editar as configurações de build, selecione Produto > Esquema > Editar esquema... e depois Executar.
  2. Na guia Info, mude Build Configuration para Release e desmarque Debug executável. configuração da versão
  3. Clique na guia Options e mude GPU Frame Capture para Disabled e Metal API Validation para Disabled.
    a criação de opções
de metal
  4. Selecione builds somente para lançamento na arquitetura de 64 bits. Em Project Navigator > tflite_camera_example > PROJECT > your_project_name > Build Settings, defina Build Active Architecture Only > Release como Yes. configurar opções de lançamento

Suporte avançado a GPUs

Esta seção aborda usos avançados do delegado de GPU para iOS, incluindo opções de delegação, buffers de entrada e saída e o uso de modelos quantizados.

Opções de delegação para iOS

O construtor do delegado da GPU aceita um struct de opções na API Swift, na API Objective-C e na API C. Transmitir nullptr (API C) ou nada (API Objective-C e Swift) ao inicializador define as opções padrão, explicadas no exemplo de uso básico acima.

Swift

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

Objective-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];
      

C

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

Buffers de entrada/saída usando a API C++

A computação na GPU exige que os dados estejam disponíveis para ela. Esse requisito geralmente significa que você precisa executar uma cópia da memória. Evite que os dados ultrapassem o limite de memória da CPU/GPU, se possível, porque isso pode demorar muito. Normalmente, esse cruzamento é inevitável, mas, em alguns casos especiais, um ou outro pode ser omitido.

Se a entrada da rede for uma imagem já carregada na memória da GPU (por exemplo, uma textura da GPU que contém o feed da câmera), ela poderá permanecer na memória da GPU sem entrar na memória da CPU. Da mesma forma, se a saída da rede estiver na forma de uma imagem renderizável, como uma operação de transferência de estilo de imagem, será possível exibir o resultado diretamente na tela.

Para ter o melhor desempenho, o TensorFlow Lite possibilita que os usuários leiam e gravem diretamente no buffer de hardware do TensorFlow e ignorem cópias de memória evitáveis.

Supondo que a entrada da imagem esteja na memória da GPU, você precisa primeiro convertê-la em um objeto MTLBuffer para Metal. É possível associar um TfLiteTensor a um MTLBuffer preparado pelo usuário com a função TFLGpuDelegateBindMetalBufferToTensor(). Observe que essa função precisa ser chamada após Interpreter::ModifyGraphWithDelegate(). Além disso, a saída de inferência é, por padrão, copiada da memória da GPU para a memória da CPU. É possível desativar esse comportamento chamando Interpreter::SetAllowBufferHandleOutput(true) durante a inicialização.

C++

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

Depois que o comportamento padrão é desativado, a cópia da saída de inferência da memória da GPU para a memória da CPU requer uma chamada explícita de Interpreter::EnsureTensorDataIsReadable() para cada tensor de saída. Essa abordagem também funciona para modelos quantizados, mas você ainda precisa usar um buffer de tamanho float32 com dados float32, porque o buffer está vinculado ao buffer desquantizado interno.

Modelos quantizados

As bibliotecas delegadas de GPU do iOS oferecem suporte a modelos quantizados por padrão. Não é necessário fazer nenhuma mudança no código para usar modelos quantizados com o delegado da GPU. A seção a seguir explica como desativar o suporte quantizado para fins de teste ou experimentais.

Desativar suporte a modelos quantizados

O código a seguir mostra como desativar o suporte para modelos quantizados.

Swift

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

Objective-C

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

C

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

    TfLiteDelegate* delegate = TFLGpuDelegateCreate(options);
      

Para saber mais sobre como executar modelos quantizados com aceleração de GPU, consulte a visão geral do delegado de GPU.