Toán tử tuỳ chỉnh

Vì thư viện toán tử tích hợp của TensorFlow Lite chỉ hỗ trợ một số ít toán tử, nên không phải mô hình nào cũng có thể chuyển đổi. Để biết thông tin chi tiết, hãy tham khảo phần khả năng tương thích của toán tử.

Để cho phép chuyển đổi, người dùng có thể tự cung cấp phương thức triển khai tuỳ chỉnh cho một toán tử TensorFlow không được hỗ trợ trong TensorFlow Lite (còn gọi là toán tử tuỳ chỉnh). Nếu bạn muốn kết hợp một loạt các toán tử TensorFlow không được hỗ trợ (hoặc được hỗ trợ) vào một toán tử tuỳ chỉnh được tối ưu hoá kết hợp, hãy tham khảo bài viết hợp nhất toán tử.

Việc sử dụng toán tử tuỳ chỉnh bao gồm 4 bước.

Hãy cùng tìm hiểu một ví dụ toàn diện về cách chạy mô hình bằng toán tử tuỳ chỉnh tf.atan (có tên là Atan, đề cập đến Tạo mô hình TensorFlow). Tính năng này được hỗ trợ trong TensorFlow Lite nhưng không được hỗ trợ trong TensorFlow Lite.

Toán tử TensorFlow Text là một ví dụ về toán tử tuỳ chỉnh. Hãy xem hướng dẫn Chuyển đổi văn bản TF sang TF Lite để biết ví dụ về mã.

Ví dụ: Toán tử Atan tuỳ chỉnh

Hãy cùng tìm hiểu một ví dụ về việc hỗ trợ toán tử TensorFlow mà TensorFlow Lite không có. Giả sử chúng ta đang sử dụng toán tử Atan và chúng ta đang xây dựng một mô hình rất đơn giản cho một hàm y = atan(x + offset), trong đó offset có thể huấn luyện được.

Tạo mô hình TensorFlow

Đoạn mã sau đây huấn luyện một mô hình TensorFlow đơn giản. Mô hình này chỉ chứa một toán tử tuỳ chỉnh có tên Atan. Đây là một hàm y = atan(x + offset), trong đó offset có thể huấn luyện.

import tensorflow as tf

# Define training dataset and variables
x = [-8, 0.5, 2, 2.2, 201]
y = [-1.4288993, 0.98279375, 1.2490457, 1.2679114, 1.5658458]
offset = tf.Variable(0.0)

# Define a simple model which just contains a custom operator named `Atan`
@tf.function(input_signature=[tf.TensorSpec.from_tensor(tf.constant(x))])
def atan(x):
  return tf.atan(x + offset, name="Atan")

# Train model
optimizer = tf.optimizers.Adam(0.01)
def train(x, y):
    with tf.GradientTape() as t:
      predicted_y = atan(x)
      loss = tf.reduce_sum(tf.square(predicted_y - y))
    grads = t.gradient(loss, [offset])
    optimizer.apply_gradients(zip(grads, [offset]))

for i in range(1000):
    train(x, y)

print("The actual offset is: 1.0")
print("The predicted offset is:", offset.numpy())
The actual offset is: 1.0
The predicted offset is: 0.99999905

Tại thời điểm này, nếu cố gắng tạo mô hình TensorFlow Lite với các cờ chuyển đổi mặc định, bạn sẽ nhận được thông báo lỗi sau:

Error:
error: 'tf.Atan' op is neither a custom op nor a flex op.

Chuyển đổi thành Mô hình TensorFlow Lite

Tạo mô hình TensorFlow Lite với các toán tử tuỳ chỉnh bằng cách đặt thuộc tính bộ chuyển đổi allow_custom_ops như minh hoạ dưới đây:

converter = tf.lite.TFLiteConverter.from_concrete_functions([atan.get_concrete_function()], atan)
converter.allow_custom_ops = True
tflite_model = converter.convert()

Tại thời điểm này, nếu bạn chạy lệnh đó với trình thông dịch mặc định bằng các lệnh như sau:

interpreter = tf.lite.Interpreter(model_content=tflite_model)
interpreter.allocate_tensors()

Bạn sẽ vẫn gặp lỗi:

Encountered unresolved custom op: Atan.

Tạo và đăng ký toán tử.

#include "third_party/tensorflow/lite/c/c_api.h"
#include "third_party/tensorflow/lite/c/c_api_opaque.h"

Các toán tử tuỳ chỉnh của TensorFlow Lite được xác định bằng một API thuần tuý C đơn giản bao gồm một loại mờ (TfLiteRegistrationExternal) và các hàm có liên quan.

TfLiteRegistrationExternal là một loại mờ:

typedef struct TfLiteRegistrationExternal TfLiteRegistrationExternal;

TfLiteRegistrationExternal lưu trữ danh tính và cách triển khai của người vận hành. (Lưu ý rằng toán tử khác với toán hạng của nó, được lưu trữ trong các nút biểu đồ TF Lite cho các nút gọi toán tử.)

Các thực thể loại này được tạo bằng lệnh gọi đến TfLiteRegistrationExternalCreate và có thể bị huỷ bằng cách gọi TfLiteRegistrationExternalDelete.

Danh tính của toán tử được đặt thông qua các tham số đối với hàm hàm khởi tạo TfLiteRegistrationExternalCreate:

TfLiteRegistrationExternal*
TfLiteRegistrationExternalCreate(
    TfLiteBuiltinOperator builtin_code,  // Normally `TfLiteBuiltinCustom`.
    const char* custom_name,  // The name of the custom op.
    int version  // Normally `1` for the first version of a custom op.
);

Quá trình triển khai toán tử có thể xác định "phương thức" có các chữ ký sau. Tất cả các phương thức này là không bắt buộc, nhưng để đánh giá thành công một toán tử, quá trình triển khai toán tử cần xác định và đặt (sử dụng các hàm setter) ít nhất là các phương thức PrepareInvoke.

// Initializes the op from serialized data.
void* Init(TfLiteOpaqueContext* context, const char* buffer, size_t length);

// Deallocates the op.
// The pointer `buffer` is the data previously returned by an Init invocation.
void Free(TfLiteOpaqueContext* context, void* buffer);

// Called when the inputs that this node depends on have been resized.
TfLiteStatus Prepare(TfLiteOpaqueContext* context, TfLiteOpaqueNode* node);

// Called when the node is executed. (Should read node inputs and write to
// node outputs).
TfLiteStatus Invoke(TfLiteOpaqueContext* context, TfLiteOpaqueNode* node);

// Retrieves the async kernel.
TfLiteAsyncKernel AsyncKernel(TfLiteOpaqueContext* context,
                              TfLiteOpaqueNode* node);

names (hoặc tiền tố không gian tên, đối với C++) trong quá trình triển khai hoạt động của bạn không cần phải khớp với tên hàm trong đoạn mã ở trên, vì API hoạt động tuỳ chỉnh TF Lite sẽ chỉ sử dụng địa chỉ của chúng. Bạn nên khai báo các hàm này trong không gian tên ẩn danh hoặc dưới dạng hàm tĩnh.

Tuy nhiên, bạn nên đưa tên toán tử của mình vào làm không gian tên hoặc tiền tố trên các tên hàm sau:

C++

namespace my_namespace::my_custom_op {
  void* Init(TfLiteOpaqueContext* context,
             const char* buffer, size_t length) { ... }
  // ... plus definitions of Free, Prepare, and Invoke ...
}
      

C

void* MyCustomOpInit(TfLiteOpaqueContext* context,
                     const char* buffer, size_t length) { ... }
// ... plus definitions of MyCustomOpFree, MyCustomOpPrepare, and
// MyCustomOpInvoke.
      

Vì đây là API C, nên các "phương thức" này được triển khai dưới dạng con trỏ hàm C trong loại TfLiteRegistrationExternal, được thiết lập bằng cách truyền địa chỉ của các hàm triển khai đến các hàm setter tương ứng TfLiteRegistrationExternalSetMethodName:

void TfLiteRegistrationExternalSetInit(
    TfLiteRegistrationExternal* registration,
    void* (*init)(TfLiteOpaqueContext* context, const char* buffer,
                  size_t length));
void TfLiteRegistrationExternalSetFree(
    TfLiteRegistrationExternal* registration,
    void (*free)(TfLiteOpaqueContext* context, void* data));
void TfLiteRegistrationExternalSetPrepare(
    TfLiteRegistrationExternal* registration,
    TfLiteStatus (*prepare)(TfLiteOpaqueContext* context,
                            TfLiteOpaqueNode* node));
void TfLiteRegistrationExternalSetInvoke(
    TfLiteRegistrationExternal* registration,
    TfLiteStatus (*invoke)(TfLiteOpaqueContext* context,
                           TfLiteOpaqueNode* node));
void TfLiteRegistrationExternalSetAsyncKernel(
    TfLiteRegistrationExternal* registration,
    struct TfLiteAsyncKernel* (*async_kernel)(TfLiteOpaqueContext* context,
                                              TfLiteOpaqueNode* node));

Hãy tham khảo common.h để biết thông tin chi tiết về TfLiteContextTfLiteNode. TfLiteContext cung cấp phương tiện báo cáo lỗi và quyền truy cập vào các đối tượng chung, bao gồm tất cả tensor. TfLiteNode cho phép các quá trình triển khai toán tử truy cập dữ liệu đầu vào và đầu ra của chúng.

Khi tải một mô hình, trình thông dịch sẽ gọi phương thức Init() một lần cho mỗi nút trong biểu đồ. Một Init() nhất định sẽ được gọi nhiều lần nếu toán tử được sử dụng nhiều lần trong biểu đồ. Đối với các hoạt động tuỳ chỉnh, một vùng đệm cấu hình sẽ được cung cấp, chứa một vùng đệm linh hoạt để liên kết tên tham số với giá trị của các tham số đó. Bộ đệm trống cho các hoạt động tích hợp vì trình phiên dịch đã phân tích cú pháp các thông số hoạt động. Các hoạt động triển khai hạt nhân yêu cầu trạng thái phải khởi chạy ở đây và chuyển quyền sở hữu cho phương thức gọi. Đối với mỗi lệnh gọi Init(), sẽ có một lệnh gọi tương ứng đến Free(), cho phép các quá trình triển khai loại bỏ vùng đệm có thể đã phân bổ trong Init().

Bất cứ khi nào tensor đầu vào được đổi kích thước, trình thông dịch sẽ trải qua biểu đồ thông báo các phương thức triển khai thay đổi. Điều này giúp chúng có cơ hội đổi kích thước vùng đệm nội bộ, kiểm tra tính hợp lệ của hình dạng và loại dữ liệu đầu vào, cũng như tính toán lại hình dạng đầu ra. Tất cả việc này được thực hiện thông qua phương thức Prepare() và các quá trình triển khai có thể truy cập vào trạng thái của các phương thức đó bằng TfLiteOpaqueNodeGetUserData(node).

Cuối cùng, mỗi lần dự đoán chạy, trình thông dịch sẽ truyền tải biểu đồ gọi phương thức Invoke() và tại đây, trạng thái sẽ có sẵn dưới dạng TfLiteOpaqueNodeGetUserData(node).

Bạn có thể triển khai hoạt động tuỳ chỉnh bằng cách xác định các hàm "phương thức" đó, sau đó xác định một hàm trả về phiên bản của TfLiteRegistrationExternal được tạo bằng cách gọi TfLiteRegistrationExternalCreate, sau đó gọi các phương thức setter có liên quan:

C++

namespace my_namespace::my_custom_op {
  namespace {
    void* Init(TfLiteOpaqueContext* context,
               const char* buffer, size_t length) { ... }
    void Free(TfLiteOpaqueContext* context, void* buffer) { ... }
    TfLiteStatus Prepare(TfLiteOpaqueContext* context,
                         TfLiteOpaqueNode* node) { ... }
    TfLiteStatus Invoke(TfLiteOpaqueContext* context,
                        TfLiteOpaqueNode* node) {... }
  };

  const TfLiteRegistrationExternal* MyCustomOpRegistrationExternal() {
    // Singleton instance, intentionally never destroyed.
    static const TfLiteRegistrationExternal* my_custom_op = ()[] {
        TfLiteRegistrationExternal* r =
            TfLiteRegistrationExternalCreate(
                kTfLiteBuiltinCustom, "MyCustomOp", /*version=*/ 1);
        TfLiteRegistrationExternalSetInit(r, Init);
        TfLiteRegistrationExternalSetFree(r, Free);
        TfLiteRegistrationExternalSetPrepare(r, Prepare);
        TfLiteRegistrationExternalSetInvoke(r, Eval);
        return r;
      };
    return my_custom_op;
  }

  const TfLiteRegistration* MyCustomOpRegistration() {
    static const TfLiteRegistration my_custom_op {
      .registration_external = MyCustomOpRegistrationExternal();
    };
    return my_custom_op;
  }
}  // namespace my_namespace
      

C

static void* MyCustomOpInit(TfLiteOpaqueContext* context, const char* buffer,
                     size_t length) { ... }
static void MyCustomOpFree(TfLiteOpaqueContext* context, void* buffer) { ... }
static TfLiteStatus MyCustomOpPrepare(TfLiteOpaqueContext* context,
                                      TfLiteOpaqueNode* node) { ... }
static TfLiteStatus MyCustomOpInvoke(TfLiteOpaqueContext* context,
                                     TfLiteOpaqueNode* node) {... }

static TfLiteRegistrationExternal* MyCustomOpCreate() {
  const TfLiteRegistrationExternal* r =
      TfLiteRegistrationExternalCreate(
          kTfLiteBuiltinCustom, "MyCustomOp", /*version=*/ 1);
  TfLiteRegistrationExternalSetInit(r, MyCustomOpInit);
  TfLiteRegistrationExternalSetFree(r, MyCustomOpFree);
  TfLiteRegistrationExternalSetPrepare(r, MyCustomOpPrepare);
  TfLiteRegistrationExternalSetInvoke(r, MyCustomOpEval);
  return r;
}

const TfLiteRegistrationExternal* MyCustomOpRegistrationExternal() {
  // Singleton instance, intentionally never destroyed.
  static const TfLiteRegistrationExternal* my_custom_op = MyCustomOpCreate();
  return my_custom_op;
}

const TfLiteRegistration MyCustomOpRegistration() {
  static const TfLiteRegistration my_custom_op {
    .registration_external = MyCustomOpRegistrationExternal();
  };
  return my_custom_op;
}
      

Xin lưu ý rằng việc đăng ký không tự động và bạn phải thực hiện lệnh gọi rõ ràng đến hàm MyCustomOpRegistration (xem thông tin chi tiết bên dưới). Mặc dù BuiltinOpResolver tiêu chuẩn (có sẵn từ mục tiêu :builtin_ops) đảm nhận việc đăng ký tích hợp sẵn, nhưng các hoạt động tuỳ chỉnh sẽ phải được thu thập trong các thư viện tuỳ chỉnh riêng biệt.

Xác định hạt nhân trong thời gian chạy TensorFlow Lite

Tất cả những gì chúng ta cần làm để sử dụng khoảng không quảng cáo trong TensorFlow Lite là xác định 2 hàm (PrepareEval), và 1 hàm thứ ba để tạo TfLiteRegistrationExternal:

C++

namespace atan_op {
  namespace {
    TfLiteStatus AtanPrepare(TfLiteOpaqueContext* context, TfLiteOpaqueNode* node) {
      TF_LITE_OPAQUE_ENSURE_EQ(context, TfLiteOpaqueNodeNumInputs(node), 1);
      TF_LITE_OPAQUE_ENSURE_EQ(context, TfLiteOpaqueNodeNumOutputs(node), 1);

      const TfLiteOpaqueTensor* input = TfLiteOpaqueNodeGetInput(context, node, 0);
      TfLiteOpaqueTensor* output = TfLiteOpaqueNodeGetOutput(context, node, 0);

      int num_dims = TfLiteOpaqueTensorNumDimensions(input);

      TfLiteIntArray* output_size = TfLiteIntArrayCreate(num_dims);
      for (int i=0; i < num_dims; ++i) {
        output_size->data[i] = input->dims->data[i];
      }

      return TfLiteOpaqueContextResizeTensor(context, output, output_size);
    }

    TfLiteStatus AtanEval(TfLiteOpaqueContext* context, TfLiteOpaqueNode* node) {
      const TfLiteOpaqueTensor* input = TfLiteOpaqueNodeGetInput(context, node, 0);
      TfLiteOpaqueTensor* output = TfLiteOpaqueNodeGetOutput(context, node, 0);

      float* input_data = static_cast(TfLiteOpaqueTensorData(input));
      float* output_data = static_cast(TfLiteOpaqueTensorData(output));

      size_t count = 1;
      int num_dims = TfLiteOpaqueTensorNumDimensions(input);
      for (int i = 0; i < num_dims; ++i) {
        count *= input->dims->data[i];
      }

      for (size_t i = 0; i < count; ++i) {
        output_data[i] = atan(input_data[i]);
      }
      return kTfLiteOk;
    }
  }  // anonymous namespace

  const TfLiteRegistrationExternal* AtanOpRegistrationExternal() {
    // Singleton instance, intentionally never destroyed.
    static const TfLiteRegistrationExternal* atan_op = ()[] {
        auto* r = TfLiteRegistrationExternalCreate(
            kTfLiteBuiltinCustom, "ATAN", /*version=*/ 1);
        TfLiteRegistrationExternalSetPrepare(r, Prepare);
        TfLiteRegistrationExternalSetInvoke(r, Eval);
        return r;
      };
    return atan_op;
  }

  const TfLiteRegistration AtanOpRegistration() {
    static const TfLiteRegistration atan_op {
      .registration_external = AtanOpRegistrationExternal();
    };
    return atan_op;
  }
}  // namespace atan_op
      

C

static TfLiteStatus AtanPrepare(TfLiteOpaqueContext* context, TfLiteOpaqueNode* node) {
  TF_LITE_OPAQUE_ENSURE_EQ(context, TfLiteOpaqueNodeNumInputs(node), 1);
  TF_LITE_OPAQUE_ENSURE_EQ(context, TfLiteOpaqueNodeNumOutputs(node), 1);

  const TfLiteOpaqueTensor* input = TfLiteOpaqueNodeGetInput(context, node, 0);
  TfLiteOpaqueTensor* output = TfLiteOpaqueNodeGetOutput(context, node, 0);

  int num_dims = TfLiteOpaqueTensorNumDimensions(input);

  TfLiteIntArray* output_size = TfLiteIntArrayCreate(num_dims);
  for (int i = 0; i < num_dims; ++i) {
    output_size->data[i] = input->dims->data[i];
  }

  return TfLiteOpaqueContextResizeTensor(context, output, output_size);
}

static TfLiteStatus AtanEval(TfLiteOpaqueContext* context, TfLiteOpaqueNode* node) {
  const TfLiteOpaqueTensor* input = TfLiteOpaqueNodeGetInput(context, node, 0);
  TfLiteOpaqueTensor* output = TfLiteOpaqueNodeGetOutput(context, node, 0);

  float* input_data = static_cast(TfLiteOpaqueTensorData(input));
  float* output_data = static_cast(TfLiteOpaqueTensorData(output));

  size_t count = 1;
  int num_dims = TfLiteOpaqueTensorNumDimensions(input);
  for (int i = 0; i < num_dims; ++i) {
    count *= input->dims->data[i];
  }

  for (size_t i = 0; i < count; ++i) {
    output_data[i] = atan(input_data[i]);
  }
  return kTfLiteOk;
}

static const TfLiteRegistrationExternal* AtanOpCreate() {
  TfLiteRegistrationExternal* r = TfLiteRegistrationExternalCreate(
          kTfLiteBuiltinCustom, "ATAN", /*version=*/ 1);
  TfLiteRegistrationExternalSetPrepare(r, Prepare);
  TfLiteRegistrationExternalSetInvoke(r, Eval);
  return r;
}

const TfLiteRegistrationExternal* AtanOpRegistrationExternal() {
  // Singleton instance, intentionally never destroyed.
  static const TfLiteRegistrationExternal* atan_op = AtanOpCreate();
  return atan_op;
}

const TfLiteRegistration AtanOpRegistration() {
  static const TfLiteRegistration atan_op {
    .registration_external = AtanOpRegistrationExternal();
  };
  return atan_op;
}
      

Khi khởi chạy OpResolver, hãy thêm op tuỳ chỉnh vào trình phân giải (xem ví dụ dưới đây). Thao tác này sẽ đăng ký toán tử với Tensorflow Lite để TensorFlow Lite có thể sử dụng phương thức triển khai mới. Lưu ý rằng 2 đối số cuối cùng trong TfLiteRegistration tương ứng với các hàm AtanPrepareAtanEval mà bạn đã xác định cho hoạt động tuỳ chỉnh. Nếu tương ứng, bạn sử dụng hàm AtanInitAtanFree để khởi tạo các biến dùng trong hoạt động và giải phóng không gian, thì các đối số đó sẽ được thêm vào 2 đối số đầu tiên của TfLiteRegistration; trong ví dụ này, các đối số đó được đặt thành nullptr.

Đăng ký toán tử với thư viện kernel

Bây giờ, chúng ta cần đăng ký toán tử với thư viện kernel. Bạn có thể thực hiện việc này bằng OpResolver. Trong hậu trường, trình thông dịch sẽ tải một thư viện hạt nhân sẽ được chỉ định để thực thi từng toán tử trong mô hình. Mặc dù thư viện mặc định chỉ chứa các nhân tích hợp sẵn, nhưng bạn có thể thay thế/tăng cường bằng một toán tử vận hành thư viện tuỳ chỉnh.

Lớp OpResolver giúp chuyển đổi mã và tên toán tử thành mã thực tế như sau:

class OpResolver {
 public:
  virtual TfLiteRegistration* FindOp(tflite::BuiltinOperator op) const = 0;
  virtual TfLiteRegistration* FindOp(const char* op) const = 0;
  ...
};

Xin lưu ý rằng để tương thích ngược, lớp này sử dụng loại bê tông cũ TfLiteRegistration thay vì loại mờ TfLiteRegistrationExternal, nhưng cấu trúc TfLiteRegistration chứa trường registration_external thuộc loại TfLiteRegistrationExternal*.

Các lớp MutableOpResolverBuiltinOpResolver bắt nguồn từ OpResolver:

class MutableOpResolver : public OpResolver {
 public:
  MutableOpResolver();  // Constructs an initially empty op resolver.
  void AddBuiltin(tflite::BuiltinOperator op, const TfLiteRegistration* registration) = 0;
  void AddCustom(const char* op, const TfLiteRegistration* registration) = 0;
  void AddAll(const MutableOpResolver& other);
  ...
};

class BuiltinOpResolver : public MutableOpResolver {
 public:
  BuiltinOpResolver();  // Constructs an op resolver with all the builtin ops.
};

Để sử dụng thông thường (không có hoạt động tuỳ chỉnh), bạn phải sử dụng BuiltinOpResolver và ghi:

tflite::ops::builtin::BuiltinOpResolver resolver;

Để thêm toán tử tuỳ chỉnh đã tạo ở trên, bạn có thể sử dụng MutableOpResolver và gọi AddCustom (trước khi truyền trình phân giải đến InterpreterBuilder):

tflite::ops::builtin::MutableOpResolver resolver;
resolver.AddAll(tflite::ops::builtin::BuiltinOpResolver());
resolver.AddCustom("Atan", AtanOpRegistration());

Nếu tập hợp các hoạt động tích hợp bị coi là quá lớn, thì OpResolver mới có thể được tạo dựa trên một tập hợp con hoạt động nhất định, có thể chỉ là những hoạt động có trong một mô hình nhất định. Phương thức này tương đương với việc đăng ký có chọn lọc của TensorFlow (và một phiên bản đơn giản của TensorFlow có sẵn trong thư mục tools).

Nếu muốn xác định các toán tử tuỳ chỉnh trong Java, hiện tại, bạn cần xây dựng lớp JNI tuỳ chỉnh của riêng mình và biên dịch AAR của riêng bạn trong mã jni này. Tương tự, nếu muốn xác định các toán tử này có sẵn trong Python, bạn có thể đặt các yêu cầu đăng ký của mình trong mã bao bọc Python.

Lưu ý rằng bạn có thể tuân theo quy trình tương tự như trên để hỗ trợ một tập hợp thao tác thay vì một toán tử duy nhất. Bạn chỉ cần thêm số lượng toán tử AddCustom cần thiết. Ngoài ra, MutableOpResolver cũng cho phép bạn ghi đè các phương thức triển khai của tính năng tích hợp sẵn bằng cách sử dụng AddBuiltin.

Kiểm thử và lập hồ sơ nhà cung cấp dịch vụ

Để lập hồ sơ hoạt động của bạn bằng công cụ đo điểm chuẩn TensorFlow Lite, bạn có thể sử dụng công cụ mô hình điểm chuẩn cho TensorFlow Lite. Đối với mục đích kiểm thử, bạn có thể làm cho bản dựng cục bộ của TensorFlow Lite biết đến hoạt động tuỳ chỉnh của bạn bằng cách thêm lệnh gọi AddCustom thích hợp (như minh hoạ ở trên) vào register.cc

Các phương pháp hay nhất

  1. Tối ưu hoá quá trình phân bổ và phân bổ bộ nhớ một cách thận trọng. Việc phân bổ bộ nhớ trong Prepare hiệu quả hơn trong Invoke và việc phân bổ bộ nhớ trước vòng lặp sẽ hiệu quả hơn trong mỗi vòng lặp. Sử dụng dữ liệu tensor tạm thời thay vì tự tăm tối (xem mục 2). Sử dụng con trỏ/tham chiếu thay vì sao chép nhiều nhất có thể.

  2. Nếu một cấu trúc dữ liệu vẫn tồn tại trong toàn bộ hoạt động, bạn nên phân bổ trước bộ nhớ bằng tensor tạm thời. Bạn có thể cần phải sử dụng cấu trúc OpData để tham chiếu các chỉ mục tensor trong các hàm khác. Xem ví dụ trong hạt nhân cho tích chập. Dưới đây là một đoạn mã mẫu.

    struct MyOpData {
      int temp_tensor_index;
      ...
    };
    
    void* Init(TfLiteOpaqueContext* context,
        const char* buffer, size_t length) {
      auto* op_data = new MyOpData{};
      ...
      return op_data;
    }
    void Free(TfLiteOpaqueContext* context, void* buffer) {
      ...
      delete reinterpret_cast<MyOpData*>(buffer);
    }
    TfLiteStatus Prepare(TfLiteOpaqueContext* context,
                         TfLiteOpaqueNode* node) {
      ...
      auto* op_data =
          reinterpret_cast<MyOpData*>(TfLiteOpaqueNodeGetUserData(node));
      const int num_temporaries = 1;
      int temporary_tensor_indices[num_temporaries];
      TfLiteOpaqueTensorBuilder* builder = TfLiteOpaqueTensorBuilderCreate();
      TfLiteOpaqueTensorBuilderSetType(builder, kTfLiteFloat32);
      TfLiteOpaqueTensorBuilderSetAllocationType(builder, kTfLiteArenaRw);
      TfLiteOpaqueContextAddTensor(context, builder,
          &temporary_tensor_indices[0]);
      TfLiteOpaqueTensorBuilderDelete(builder);
      TfLiteOpaqueNodeSetTemporaries(node, temporary_tensor_indices,
          num_temporaries);
      op_data->temp_tensor_index = temporary_tensor_indices[0];
      ...
      return kTfLiteOk;
    }
    TfLiteStatus Invoke(TfLiteOpaqueContext* context,
                        TfLiteOpaqueNode* node) {
      ...
      auto* op_data = reinterpret_cast<MyOpData*>(
          TfLiteOpaqueNodeGetUserData(node));
      TfLiteOpaqueTensor* temp_tensor =
          TfLiteOpaqueContextGetOpaqueTensor(context,
              op_data->temp_tensor_index);
      TF_LITE_OPAQUE_ENSURE(context,
          TfLiteTensorType(temp_tensor) == kTfLiteFloat32);
      TF_LITE_OPAQUE_ENSURE(context,
          TfLiteTensorGetAllocationType(temp_Tensor) == kTfLiteArenaRw);
      void *temp_data = TfLiteTensorData(temp_tensor);
      TF_LITE_OPAQUE_ENSURE(context, temp_data != nullptr);
      ...
      return kTfLiteOk;
    }
    
  3. Nếu không tốn quá nhiều bộ nhớ bị lãng phí, hãy ưu tiên dùng mảng kích thước tĩnh cố định (hoặc std::vector được phân bổ trước trong Resize) thay vì sử dụng std::vector được phân bổ động cho mỗi lần thực thi.

  4. Tránh tạo thực thể cho các mẫu vùng chứa thư viện chuẩn không tồn tại, vì các mẫu này ảnh hưởng đến kích thước nhị phân. Ví dụ: nếu bạn cần một std::map trong thao tác của mình mà không tồn tại ở các nhân khác, thì việc sử dụng std::vector với ánh xạ lập chỉ mục trực tiếp có thể hiệu quả trong khi vẫn duy trì kích thước tệp nhị phân nhỏ. Xem các hạt nhân khác sử dụng những gì để có được thông tin chi tiết (hoặc hỏi).

  5. Kiểm tra con trỏ đến bộ nhớ do malloc trả về. Nếu con trỏ này là nullptr, thì không được thực hiện thao tác nào bằng con trỏ đó. Nếu bạn malloc trong một hàm và gặp lỗi, hãy phân bổ bộ nhớ trước khi thoát.

  6. Sử dụng TF_LITE_OPAQUE_ENSURE(context, condition) để kiểm tra một điều kiện cụ thể. Mã của bạn không được để bộ nhớ treo khi sử dụng TF_LITE_OPAQUE_ENSURE, tức là bạn phải sử dụng các macro này trước khi phân bổ bất kỳ tài nguyên nào sẽ bị rò rỉ.