Toán tử tuỳ chỉnh

Do thư viện toán tử tích hợp LiteRT chỉ hỗ trợ một số hạn chế số lượng toán tử TensorFlow, không phải mô hình nào cũng có thể chuyển đổi. Để biết thông tin chi tiết, tham chiếu đế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ể cung cấp cách triển khai tuỳ chỉnh của riêng họ cho một toán tử TensorFlow không được hỗ trợ trong LiteRT, còn được gọi là toán tử tuỳ chỉnh. Nếu thay vào đó, bạn muốn kết hợp một loạt các cookie không được hỗ trợ (hoặc được hỗ trợ) Các toán tử TensorFlow thành một toán tử tuỳ chỉnh tối ưu hoá kết hợp, tham chiếu đến hợp nhất toán tử.

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

Hãy xem qua ví dụ toàn diện về cách chạy một mô hình có tuỳ chỉnh toán tử tf.atan (có tên là Atan, tham khảo bài viết Tạo Mô hình TensorFlow). được hỗ trợ trong TensorFlow, nhưng không được hỗ trợ trong LiteRT.

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

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

Hãy xem một ví dụ về việc hỗ trợ toán tử TensorFlow LiteRT 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ô hình rất đơn giản cho hàm y = atan(x + offset), trong đó offset có thể huấn luyện.

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à 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 bạn cố tạo mô hình LiteRT bằng giá trị mặc định cờ chuyển đổi, 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 sang Mô hình LiteRT

Tạo mô hình LiteRT với các toán tử tuỳ chỉnh bằng cách đặt bộ chuyển đổi thuộc tính 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 chương trình bằng 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 vẫn sẽ 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"

Toán tử tuỳ chỉnh LiteRT được xác định bằng một API thuần C đơn giản mà bao gồm 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 toán tử. (Lưu ý rằng toán tử khác với toán hạng của nó, vốn được lưu trữ trong Các nút đồ thị LiteRT cho các nút gọi toán tử.)

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

Thông tin nhận dạng của toán tử được đặt thông qua các tham số trong 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.
);

Việc triển khai toán tử có thể xác định "phương thức" bằng 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 để toán tử thành công được đánh giá, thì việc triển khai toán tử cần xác định và thiết lập (bằng phương thức setter hàm) ít nhất là 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);

Tên hàm (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ì TF API hoạt động tuỳ chỉnh Lite sẽ chỉ sử dụng địa chỉ của chúng. Chúng tôi khuyên bạn nên khai báo chúng 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 dưới dạng không gian tên hoặc tiền tố trên các tên hàm này:

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 đặt bằng cách truyền địa chỉ của các hàm triển khai của bạn với 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));

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

Khi tải một mô hình, trình phiên dịch sẽ gọi phương thức Init() một lần cho mỗi lần trong biểu đồ. Một Init() nhất định sẽ được gọi nhiều lần nếu hoạt động là được sử dụng nhiều lần trong biểu đồ. Đối với hoạt động tuỳ chỉnh, vùng đệm cấu hình sẽ là được cung cấp, chứa một bộ đệm linh hoạt giúp ánh xạ tên tham số với giá trị của các tham số đó. Chiến lược phát hành đĩa đơn bộ đệm trống đối với hoạt động tích hợp vì trình thông dịch đã phân tích cú pháp tệp thông số hoạt động. Các phương thức triển khai kernel yêu cầu trạng thái sẽ được khởi chạy tại đây và chuyển quyền sở hữu cho người gọi. Đối với mỗi cuộc gọi Init(), sẽ có một lệnh gọi tương ứng đến Free(), cho phép các phương thức triển khai loại bỏ vùng đệm mà chúng 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 phiên dịch sẽ chuyển sang biểu đồ thông báo về việc triển khai thay đổi này. Điều này mang đến cho họ cơ hội đổi kích thước bộ đệm nội bộ, kiểm tra tính hợp lệ của các hình dạng và kiểu đầu vào, và tính toán lại các hình dạng đầu ra. Bạn có thể thực hiện việc này thông qua phương thức Prepare() và có thể truy cập vào trạng thái của chúng bằng cách sử dụng TfLiteOpaqueNodeGetUserData(node).

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

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

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à là lệnh gọi rõ ràng đến Bạn cần tạo hàm MyCustomOpRegistration (xem thông tin chi tiết ở bên dưới). Trong khi BuiltinOpResolver tiêu chuẩn (có sẵn từ mục tiêu :builtin_ops) sẽ lấy phụ trách đăng ký nội dung tích hợp, 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 LiteRT

Tất cả những gì chúng ta cần làm để sử dụng op trong LiteRT là xác định hai hàm (PrepareEval) và một phần 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<float*>(TfLiteOpaqueTensorData(input));
      float* output_data = static_cast<float*>(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<float*>(TfLiteOpaqueTensorData(input));
  float* output_data = static_cast<float*>(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 tạo OpResolver, hãy thêm vận hành tuỳ chỉnh vào trình phân giải (xem bên dưới để xem ví dụ). Thao tác này sẽ đăng ký nhà cung cấp dịch vụ với LiteRT để mà LiteRT có thể sử dụng cách triển khai mới. Lưu ý rằng hai URL cuối cùng đối số trong TfLiteRegistration tương ứng với AtanPrepareAtanEval các hàm mà bạn đã xác định cho hoạt động tuỳ chỉnh. Nếu bạn đã dùng AtanInitAtanFree để khởi tạo các biến được sử dụng trong hoạt động và để giải phóng dung lượng, thì chúng sẽ được thêm vào hai đối số đầu tiên của TfLiteRegistration; các đối số đó được đặt thành nullptr trong ví dụ này.

Đă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 nhân. Việc này được thực hiện bằng OpResolver. Ở hậu trường, trình phiên dịch sẽ tải 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 hạt nhân tích hợp, nhưng bạn có thể thay thế/tăng cường bằng toán tử hoạt động của thư viện tuỳ chỉnh.

Lớp OpResolver giúp dịch mã và tên toán tử thành tên và mã toán tử thực tế mã, được xác định như sau:

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

Lưu ý rằng để có khả năng tương thích ngược, lớp này sẽ sử dụng loại cụ thể cũ TfLiteRegistration thay vì loại mờ TfLiteRegistrationExternal, nhưng cấu trúc TfLiteRegistration chứa trường registration_external của 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.
};

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

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

Để thêm hoạt động tuỳ chỉnh được 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 nhóm hoạt động tích hợp sẵn được cho là quá lớn, thì OpResolver mới có thể bị được tạo dựa trên một tập hợp con hoạt động nhất định, có thể chỉ những hoạt động được chứa trong một mô hình nhất định. Điều này tương đương với quy trình đăng ký có chọn lọc của TensorFlow (và một phiên bản đơn giản của thư mục này 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 phải tạo lớp JNI tuỳ chỉnh của riêng bạn 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ể hãy đặt thông tin đăng ký của bạn trong Mã trình 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 thay vì một toán tử đơn lẻ. Bạn chỉ cần thêm số lượng toán tử AddCustom tối đa khi bạn cần. Ngoài ra, MutableOpResolver cũng cho phép bạn ghi đè bằng cách sử dụng AddBuiltin để triển khai tích hợp.

Kiểm thử và phân tích tài nguyên cho nhà cung cấp dịch vụ

Để phân tích hiệu suất của bạn bằng công cụ đo điểm chuẩn LiteRT, bạn có thể sử dụng công cụ mô hình điểm chuẩn cho LiteRT. Đối với mục đích thử nghiệm, bạn có thể tạo bản dựng cục bộ của LiteRT nhận biết được hoạt động tuỳ chỉnh của bạn bằng cách thêm AddCustom thích hợp gọi (như trình bày ở trên) đến register.cc

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

  1. Tối ưu hoá quá trình phân bổ bộ nhớ và phân bổ lại một cách thận trọng. Đang phân bổ bộ nhớ trong Prepare hiệu quả hơn trong Invoke và phân bổ bộ nhớ trước khi lặp lại hiệu quả hơn trong mỗi lần lặp lại. Sử dụng dữ liệu tensor tạm thời thay vì tự quyết định (xem mục 2). Thay vào đó, hãy dùng con trỏ/tham chiếu sao chép nhiều nhất có thể.

  2. Nếu cấu trúc dữ liệu vẫn tồn tại trong toàn bộ quá trình hoạt động, bạn nên phân bổ trước bộ nhớ bằng các 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 nhân cho tích chập. Dưới đây là đ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 bộ nhớ không bị lãng phí quá nhiều, hãy ưu tiên sử dụng kích thước tĩnh cố định (hoặc một std::vector được phân bổ trước trong Resize) thay vì sử dụng phân bổ động std::vector mỗi lần lặp lại quá trình 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 chưa có vì chúng ảnh hưởng đến kích thước nhị phân. Ví dụ: nếu bạn cần một std::map trong toán tử không tồn tại trong các hạt nhân khác, sử dụng một std::vector có ánh xạ lập chỉ mục trực tiếp có thể hoạt động trong khi vẫn duy trì kích thước nhị phân nhỏ. Xem các hạt nhân khác sử dụng gì để thu thập 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, bạn không nên thực hiện thao tác nào bằng con trỏ đó. Nếu bạn malloc trong một hàm và có lỗi thoát, giải phóng bộ nhớ trước bạn thoát.

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