AI開源十六:TensorFlow Lite 在GPU環境下調試運行

TensorFlow Lite支持多種硬件加速器。本文檔介紹如何在安卓系統(要求OpenGL ES 3.1或更高版本)和iOS(要求iOS 8 或更高版本)的GPU後端(backend)使用TensorFLow Lite delegate APIs。

使用GPU加速的優勢

速度

GPUs 設計為具有高吞吐量、可大規模並行化的工作負載(workloads)。因此,它們非常適合於一個由大量運算符組成的深度神經網絡,其中每一個GPU都可以處理一些輸入張量(tensor)並且容易劃分為較小的工作負載(workloads),然後並行執行。這樣並行性通常能夠有較低的延遲。在最好的情況下,在GPU上推斷(inference)可以運行得足夠快,以適應實時程序,這在以前是不可能的。

精度

GPU使用16位或32位浮點數進行運算,並且(與CPU不同)不需要量化(quantization)以獲得最佳的性能。如果精度降低使得模型的量化(quantization)無法達到要求,那麼在GPU上運行神經網絡可能可以消除這種擔憂。

能效

使用GPU進行推斷(inference)的另一個好處在於它的能效。GPU能以非常有效和優化方法來進行運算,比在CPU上運行相同任務消耗更少的能源併產生更少的發熱量。

支持的Ops

TensorFlow Lite 在GPU上支持16位和32位浮點精度中的以下操作:

  • ADD v1
  • AVERAGE_POOL_2D v1
  • CONCATENATION v1
  • CONV_2D v1
  • DEPTHWISE_CONV_2D v1-2
  • FULLY_CONNECTED v1
  • LOGISTIC v1
  • MAX_POOL_2D v1
  • MUL v1
  • PAD v1
  • PRELU v1
  • RELU v1
  • RELU6 v1
  • RESHAPE v1
  • RESIZE_BILINEAR v1
  • SOFTMAX v1
  • STRIDED_SLICE v1
  • SUB v1
  • TRANSPOSE_CONV v1

基本用法

Android (Java)

使用TfLiteDelegate在GPU上運行TensorFlow Lite,在Java中,您可以通過Interpreter.Options來指定GpuDelegate。

<code>// NEW: Prepare GPU delegate.GpuDelegate delegate = new GpuDelegate();Interpreter.Options options = (new Interpreter.Options()).addDelegate(delegate);// Set up interpreter.Interpreter interpreter = new Interpreter(model, options);// Run inference.writeToInputTensor(inputTensor);interpreter.run(inputTensor, outputTensor);readFromOutputTensor(outputTensor);// Clean up.delegate.close();/<code> 

Android (C/C++)

在Android GPU上使用C/C++語言的TensorFlow Lite,可以使用TfLiteGpuDelegateCreate()創建,並使用TfLiteGpuDelegateDelete()銷燬。

<code>// 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.const TfLiteGpuDelegateOptions options = {  .metadata = NULL,  .compile_options = {    .precision_loss_allowed = 1,  // FP16    .preferred_gl_object_type = TFLITE_GL_OBJECT_TYPE_FASTEST,    .dynamic_batch_enabled = 0,   // Not fully functional yet  },};auto* delegate = TfLiteGpuDelegateCreate(&options);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.TfLiteGpuDelegateDelete(delegate);/<float>/<float>/<interpreter>/<code>

適用於Android C / C ++的TFLite GPU使用Bazel構建系統。例如,可以使用以下命令構建委託(delegate):

<code>bazel build -c opt --config android_arm64 tensorflow/lite/delegates/gpu:gl_delegate                  # for static librarybazel build -c opt --config android_arm64 tensorflow/lite/delegates/gpu:libtensorflowlite_gpu_gl.so  # for dynamic library/<code>

iOS(ObjC++)

要在GPU上運行TensorFlow Lite,需要通過NewGpuDelegate()對GPU委託(delegate),然後將其傳遞給Interpreter::ModifyGraphWithDelegate()(而不是調用Interpreter::AllocateTensors())

<code>// 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.const GpuDelegateOptions options = {  .allow_precision_loss = false,  .wait_type = kGpuDelegateOptions::WaitType::Passive,};auto* delegate = NewGpuDelegate(options);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.DeleteGpuDelegate(delegate);/<float>/<float>/<interpreter>/<code>

注意:調用Interpreter::ModifyGraphWithDelegate()或Interpreter::Invoke()時,調用者必須在當前線程中有一個EGLContext,並且從同一個EGLContext中調用Interpreter::Invoke()。如果EGLContext不存在,委託(delegate)將在內部創建一個,但是開發人員必須確保始終從調用Interpreter::Invoke()的同一個線程調用Interpreter::ModifyGraphWithDelegate()。

高級用法

委託(Delegate)iOS 選項

NewGpuDelegate()接受一個 struct 選項。

<code>struct GpuDelegateOptions {  // Allows to quantify tensors, downcast values, process in float16 etc.  bool allow_precision_loss;
  enum class WaitType {    // waitUntilCompleted    kPassive,    // Minimize latency. It uses active spinning instead of mutex and consumes    // additional CPU resources.    kActive,    // Useful when the output is used with GPU pipeline then or if external    // command encoder is set    kDoNotWait,  };  WaitType wait_type;};/<code>

將nullptr傳遞給NewGpuDelegate(),並設置默認選項(即在上面的基本用法示例中闡述)。

<code>// THIS:const GpuDelegateOptions options = {  .allow_precision_loss = false,  .wait_type = kGpuDelegateOptions::WaitType::Passive,};auto* delegate = NewGpuDelegate(options);// IS THE SAME AS THIS:auto* delegate = NewGpuDelegate(nullptr);/<code>

雖然使用nullptr很方便,但我們建議您指定設置選項,以避免在以後更改默認值時出現任何異常情況。

輸入/輸出緩衝器

要想在GPU上進行計算,數據必須能夠讓GPU可見。這通常需要進行內存複製。如果可以的話,最好不要交叉CPU / GPU內存邊界,因為這會佔用大量時間。通常來說,這種交叉是不可避免的,但在某些特殊情況下,可以忽略其中一個。

如果網絡的輸入是已經加載到GPU內存中的圖像(例如,包含相機傳輸的GPU紋理),那麼可以直接保留在GPU內存中而無需進入到CPU內存。同樣,如果網絡的輸出採用可渲染圖像的格式(例如, image style transfer_),那麼它可以直接顯示在屏幕上。

為了獲得最佳性能,TensorFlow Lite讓用戶可以直接讀取和寫入TensorFlow硬件緩衝區並繞過可避免的內存副本。

Android

假設圖像送入在GPU存儲器中,則必須首先將其轉換為OpenGL著色器存儲緩衝區對象(SSBO)。您可以使用Interpreter.bindGlBufferToTensor()將TfLiteTensor與用戶準備的SSBO相關聯。注意:Interpreter.bindGlBufferToTensor()必須在Interpreter.modifyGraphWithDelegate()之前調用。

<code>// Ensure a valid EGL rendering context.EGLContext eglContext = eglGetCurrentContext();if (eglContext.equals(EGL_NO_CONTEXT)) return false;// Create an SSBO.int[] id = new int[1];glGenBuffers(id.length, id, 0);glBindBuffer(GL_SHADER_STORAGE_BUFFER, id[0]);glBufferData(GL_SHADER_STORAGE_BUFFER, inputSize, null, GL_STREAM_COPY);glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0);  // unbindint inputSsboId = id[0];// Create interpreter.Interpreter interpreter = new Interpreter(tfliteModel);Tensor inputTensor = interpreter.getInputTensor(0);GpuDelegate gpuDelegate = new GpuDelegate();// The buffer must be bound before the delegate is installed.gpuDelegate.bindGlBufferToTensor(inputTensor, inputSsboId);interpreter.modifyGraphWithDelegate(gpuDelegate);// Run inference; the null input argument indicates use of the bound buffer for input.fillSsboWithCameraImageTexture(inputSsboId);float[] outputArray = new float[outputSize];interpreter.runInference(null, outputArray);/<code> 

類似的方法可以應用於輸出張量(tensor)。在這種情況下,Interpreter.Options.setAllowBufferHandleOutput(true)應該被用來傳遞,來禁用從GPU內存到CPU內存的網絡輸出複製的默認操作。

<code>// Ensure a valid EGL rendering context.EGLContext eglContext = eglGetCurrentContext();if (eglContext.equals(EGL_NO_CONTEXT)) return false;// Create a SSBO.int[] id = new int[1];glGenBuffers(id.length, id, 0);glBindBuffer(GL_SHADER_STORAGE_BUFFER, id[0]);glBufferData(GL_SHADER_STORAGE_BUFFER, outputSize, null, GL_STREAM_COPY);glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0);  // unbindint outputSsboId = id[0];// Create interpreter.Interpreter.Options options = (new Interpreter.Options()).setAllowBufferHandleOutput(true);Interpreter interpreter = new Interpreter(tfliteModel, options);Tensor outputTensor = interpreter.getOutputTensor(0);GpuDelegate gpuDelegate = new GpuDelegate();// The buffer must be bound before the delegate is installed.gpuDelegate.bindGlBufferToTensor(outputTensor, outputSsboId);interpreter.modifyGraphWithDelegate(gpuDelegate);// Run inference; the null output argument indicates use of the bound buffer for output.ByteBuffer input = getCameraImageByteBuffer();interpreter.runInference(input, null);renderOutputSsbo(outputSsboId);/<code>

iOS

假設圖像送入在GPU存儲器中,則必須首先將其轉換為Metal的MTLBuffer對象。您可以將TfLiteTensor與用戶準備的MTLBuffer和BindMetalBufferToTensor()相關聯。注意:必須在Interpreter::ModifyGraphWithDelegate()之前調用BindMetalBufferToTensor()。此外,默認情況下,推斷(inference)結果的輸出,會從GPU內存複製到CPU內存。在初始化期間調用Interpreter::SetAllowBufferHandleOutput(true)可以關閉該操作。

<code>// Prepare GPU delegate.auto* delegate = NewGpuDelegate(nullptr);interpreter->SetAllowBufferHandleOutput(true);  // disable default gpu->cpu copyif (!BindMetalBufferToTensor(delegate, interpreter->inputs()[0], user_provided_input_buffer)) return false;if (!BindMetalBufferToTensor(delegate, interpreter->outputs()[0], user_provided_output_buffer)) return false;if (interpreter->ModifyGraphWithDelegate(delegate) != kTfLiteOk) return false;// Run inference.if (interpreter->Invoke() != kTfLiteOk) return false;/<code>

注意:一旦關閉從GPU內存複製到CPU內存的操作後,將推斷(inference)結果輸出從GPU內存複製到CPU內存需要為每個輸出張量顯式調用Interpreter::EnsureTensorDataIsReadable()。

提示與技巧

  • 在CPU上執行一些微不足道的操作可能需要非常高的代價,譬如各種形式的reshape操作(包括BATCH_TO_SPACE,SPACE_TO_BATCH,SPACE_TO_DEPTH和其他類似的操作)。如果不需要這些操作(比如使用這些操作是為了幫助理解網絡架構和了解整個系統但不會影響輸出),那麼值得刪除它們以提高性能。
  • 在GPU上,張量(tensor)數據被劃分為4個通道(channel)。因此對形狀為[B, H, W, 5] 的張量(tensor)的計算量大致與[B, H, W, 8]相同,但明顯比[B, H, W, 4]要大。比如:如果相機的硬件支持RGBA,那麼傳輸4通道(channel)數據的速度要快得多,因為可以避免內存複製(從3通道RGB到4通道RGBX)。
  • 為了獲得最佳性能,請不要猶豫使用移動優化過(mobile-optimized)的網絡架構重新訓練您的分類器。 這是設備推斷(inference)優化的重要部分。


分享到:


相關文章: