TensorFlow Lite trên GPU

TensorFlow Lite hỗ trợ một số trình tăng tốc phần cứng. Tài liệu này mô tả cách sử dụng phụ trợ GPU bằng API ủy quyền TensorFlow Lite trên Android (yêu cầu OpenCL hoặc OpenGL ES 3.1 trở lên) và iOS (yêu cầu iOS 8 trở lên).

Lợi ích của việc tăng tốc GPU

Tốc độ

GPU được thiết kế để có thông lượng cao cho khối lượng công việc có thể song song hóa khối lượng lớn. Do đó, chúng rất phù hợp với mạng nơ-ron sâu, bao gồm một số lượng lớn các toán tử, mỗi toán tử làm việc trên một số tensor đầu vào có thể dễ dàng chia thành các khối lượng công việc nhỏ hơn và thực hiện song song. Sự song song này thường dẫn đến độ trễ thấp hơn. Trong trường hợp tốt nhất, suy luận trên GPU có thể chạy đủ nhanh để trở nên phù hợp với các ứng dụng thời gian thực mà trước đây không thể thực hiện được.

Sự chính xác

GPU thực hiện tính toán của chúng với số dấu phẩy động 16 bit hoặc 32 bit và (không giống như CPU) không yêu cầu lượng tử hóa để có hiệu suất tối ưu. Nếu độ chính xác giảm khiến cho các mô hình của bạn không thể lượng tử hóa, thì việc chạy mạng nơ-ron của bạn trên GPU có thể loại bỏ mối lo ngại này.

Hiệu suất năng lượng

Một lợi ích khác đi kèm với suy luận của GPU là hiệu quả sử dụng năng lượng của nó. GPU thực hiện các phép tính theo cách rất hiệu quả và được tối ưu hóa, tiêu thụ ít năng lượng hơn và tạo ra ít nhiệt hơn so với cùng một tác vụ chạy trên CPU.

Hoạt động được hỗ trợ

TensorFlow Lite trên GPU hỗ trợ các hoạt động sau ở độ chính xác float 16 bit và 32 bit:

  • 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

Theo mặc định, tất cả các hoạt động chỉ được hỗ trợ ở phiên bản 1. Việc kích hoạt hỗ trợ lượng tử hóa thử nghiệm sẽ cho phép các phiên bản thích hợp; ví dụ: ADD v2.

Cách sử dụng cơ bản

Có hai cách để gọi tăng tốc mô hình trong Android tùy thuộc vào việc bạn đang sử dụng Android Studio ML Model Binding hay TensorFlow Lite Interpreter.

Android thông qua TensorFlow Lite Interpreter

Thêm tensorflow-lite-gpu cùng với gói tensorflow-lite hiện có trong khối dependencies hiện có.

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

Sau đó chạy TensorFlow Lite trên GPU với TfLiteDelegate . Trong Java, bạn có thể chỉ định GpuDelegate thông qua Interpreter.Options .

Kotlin

    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)
      

Java

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

Đối với việc sử dụng C / C ++ của GPU TensorFlow Lite trên Android, có thể tạo đại biểu GPU bằng TfLiteGpuDelegateV2Create() và bị hủy bằng 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);

Hãy xem TfLiteGpuDelegateOptionsV2 để tạo một phiên bản ủy quyền với các tùy chọn tùy chỉnh. Bạn có thể khởi tạo các tùy chọn mặc định với TfLiteGpuDelegateOptionsV2Default() và sau đó sửa đổi chúng nếu cần.

GPU TFLite cho Android C / C ++ sử dụng hệ thống xây dựng Bazel . Ví dụ: có thể tạo ủy quyền bằng cách sử dụng lệnh sau:

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 (C ++)

Để sử dụng TensorFlow Lite trên GPU, hãy nhận ủy quyền GPU qua TFLGpuDelegateCreate() và sau đó chuyển nó đến Interpreter::ModifyGraphWithDelegate() (thay vì gọi 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);

Sử dụng nâng cao

Tùy chọn ủy quyền cho iOS

Bộ tạo cho đại biểu GPU chấp nhận một struct các tùy chọn. ( API Swift, API Objective-C, API C )

nullptr (API C) hoặc không có gì (Objective-C và Swift API) vào trình khởi tạo sẽ đặt các tùy chọn mặc định (được giải thích trong ví dụ về Cách sử dụng cơ bản ở trên).

Nhanh

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

Mặc dù thuận tiện khi sử dụng nullptr hoặc các hàm tạo mặc định, chúng tôi khuyên bạn nên đặt các tùy chọn một cách rõ ràng, để tránh bất kỳ hành vi không mong muốn nào nếu các giá trị mặc định được thay đổi trong tương lai.

Chạy các mô hình lượng tử hóa trên GPU

Phần này giải thích cách đại biểu GPU tăng tốc các mô hình lượng tử hóa 8-bit. Điều này bao gồm tất cả các hương vị của lượng tử hóa, bao gồm:

Để tối ưu hóa hiệu suất, hãy sử dụng các mô hình có bộ căng đầu vào và đầu ra dấu phẩy động.

Cái này hoạt động ra sao?

Vì phần phụ trợ GPU chỉ hỗ trợ thực thi dấu phẩy động, chúng tôi chạy các mô hình lượng tử hóa bằng cách cung cấp cho nó 'chế độ xem dấu phẩy động' của mô hình gốc. Ở cấp độ cao, điều này đòi hỏi các bước sau:

  • Các yếu tố căng không đổi (chẳng hạn như trọng lượng / độ lệch) được xác định hóa một lần vào bộ nhớ GPU. Điều này xảy ra khi đại biểu được áp dụng cho Trình thông dịch TFLite.

  • Các đầu vào và đầu ra của chương trình GPU, nếu được lượng tử hóa 8-bit, được mã hóa và lượng tử hóa (tương ứng) cho mỗi suy luận. Điều này được thực hiện trên CPU bằng cách sử dụng các nhân được tối ưu hóa của TFLite.

  • Chương trình GPU được sửa đổi để bắt chước hành vi lượng tử hóa bằng cách chèn trình mô phỏng lượng tử hóa giữa các hoạt động. Điều này là cần thiết cho các mô hình trong đó các hoạt động mong đợi các kích hoạt tuân theo các giới hạn đã học trong quá trình lượng tử hóa.

Tính năng này có thể được bật bằng cách sử dụng các tùy chọn ủy quyền như sau:

Android

API Android hỗ trợ các mô hình lượng tử hóa theo mặc định. Để tắt, hãy làm như sau:

API C ++

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

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

API Java

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

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

iOS

Các API iOS hỗ trợ các mô hình lượng tử hóa theo mặc định. Để tắt, hãy làm như sau:

Nhanh

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

Bộ đệm đầu vào / đầu ra (chỉ dành cho iOS, C ++ API)

Để thực hiện tính toán trên GPU, dữ liệu phải được cung cấp cho GPU. Điều này thường yêu cầu thực hiện sao chép bộ nhớ. Bạn không nên vượt qua ranh giới bộ nhớ CPU / GPU nếu có thể, vì điều này có thể chiếm một lượng thời gian đáng kể. Thông thường, việc băng qua đường như vậy là không thể tránh khỏi, nhưng trong một số trường hợp đặc biệt, có thể bỏ qua cái này hoặc cái kia.

Nếu đầu vào của mạng là hình ảnh đã được tải trong bộ nhớ GPU (ví dụ: kết cấu GPU chứa nguồn cấp máy ảnh) thì nó có thể ở trong bộ nhớ GPU mà không cần nhập bộ nhớ CPU. Tương tự, nếu đầu ra của mạng ở dạng hình ảnh có thể kết xuất được (ví dụ, chuyển kiểu hình ảnh ) thì nó có thể được hiển thị trực tiếp trên màn hình.

Để đạt được hiệu suất tốt nhất, TensorFlow Lite giúp người dùng có thể đọc và ghi trực tiếp vào bộ đệm phần cứng TensorFlow và bỏ qua các bản sao bộ nhớ có thể tránh được.

Giả sử đầu vào hình ảnh nằm trong bộ nhớ GPU, trước tiên nó phải được chuyển đổi thành đối tượng MTLBuffer cho Metal. Bạn có thể liên kết TfLiteTensor với MTLBuffer do người dùng chuẩn bị bằng MTLBuffer TFLGpuDelegateBindMetalBufferToTensor() . Lưu ý rằng TFLGpuDelegateBindMetalBufferToTensor() phải được gọi sau Interpreter::ModifyGraphWithDelegate() . Ngoài ra, theo mặc định, đầu ra suy luận được sao chép từ bộ nhớ GPU sang bộ nhớ CPU. Hành vi này có thể được tắt bằng cách gọi Interpreter::SetAllowBufferHandleOutput(true) trong quá trình khởi tạo.

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

Chuỗi tuần tự hóa ủy quyền GPU

Sử dụng tuần tự hóa mã nhân GPU và dữ liệu mô hình từ các lần khởi chạy trước có thể giảm độ trễ khi khởi tạo của đại biểu GPU lên đến 90%. Cải tiến này đạt được bằng cách trao đổi không gian đĩa để tiết kiệm thời gian. Bạn có thể bật tính năng này với một số tùy chọn cấu hình, như được hiển thị trong các ví dụ mã sau:

C ++

    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;
      

Java

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

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

Khi sử dụng tính năng tuần tự hóa, hãy đảm bảo mã của bạn tuân thủ các quy tắc triển khai sau:

  • Lưu trữ dữ liệu tuần tự hóa trong một thư mục mà các ứng dụng khác không thể truy cập được. Trên thiết bị Android, sử dụng getCodeCacheDir() trỏ đến vị trí riêng tư đối với ứng dụng hiện tại.
  • Mã thông báo mô hình phải là duy nhất cho thiết bị cho mô hình cụ thể. Bạn có thể tính toán mã thông báo mô hình bằng cách tạo dấu vân tay từ dữ liệu mô hình (ví dụ: sử dụng farmhash::Fingerprint64 ).

Các mẹo và thủ thuật

  • Một số hoạt động nhỏ trên CPU có thể có chi phí cao trên GPU. Một lớp của hoạt động như vậy bao gồm các dạng hoạt động định hình lại khác nhau (bao gồm BATCH_TO_SPACE , SPACE_TO_BATCH , SPACE_TO_DEPTH và hoạt động tương tự). Nếu các hoạt động này không được yêu cầu (ví dụ, chúng được chèn vào để giúp kiến ​​trúc sư mạng lý giải về hệ thống nhưng không ảnh hưởng đến kết quả đầu ra), thì nên loại bỏ chúng để đảm bảo hiệu suất.

  • Trên GPU, dữ liệu tensor được chia thành 4 kênh. Do đó, một phép tính trên tensor hình [B, H, W, 5] sẽ thực hiện tương tự trên tensor hình [B, H, W, 8] , nhưng kém hơn đáng kể so với [B, H, W, 4] .

    • Ví dụ: nếu phần cứng máy ảnh hỗ trợ khung hình ảnh trong RGBA, việc cấp dữ liệu đầu vào 4 kênh đó nhanh hơn đáng kể, vì có thể tránh được bản sao bộ nhớ (từ RGB 3 kênh sang RGBX 4 kênh).
  • Để có hiệu suất tốt nhất, đừng ngần ngại đào tạo lại bộ phân loại của bạn với kiến ​​trúc mạng được tối ưu hóa cho thiết bị di động. Đó là một phần quan trọng của việc tối ưu hóa cho suy luận trên thiết bị.