โอเปอเรเตอร์ที่กำหนดเอง

เนื่องจากไลบรารีโอเปอเรเตอร์ LiteRT ในตัวรองรับเฉพาะ จำนวนโอเปอเรเตอร์ TensorFlow ไม่ใช่บางโมเดลที่แปลงได้ โปรดดูรายละเอียด ดูความเข้ากันได้ของโอเปอเรเตอร์

หากต้องการทำให้เกิด Conversion ผู้ใช้สามารถระบุการติดตั้งใช้งาน โอเปอเรเตอร์ TensorFlow ที่ไม่รองรับใน LiteRT เรียกว่าโอเปอเรเตอร์ที่กำหนดเอง หากต้องการรวมชุดข้อมูลที่ไม่รองรับ (หรือรองรับ) โอเปอเรเตอร์ TensorFlow เข้าเป็นโอเปอเรเตอร์แบบกำหนดเองที่มีการเพิ่มประสิทธิภาพ Fused เดี่ยวอ้างอิงไปยัง Operator Fusing

การใช้โอเปอเรเตอร์ที่กำหนดเองมี 4 ขั้นตอน

มาดูตัวอย่างจากต้นทางถึงปลายทางของการเรียกใช้โมเดลที่มี โอเปอเรเตอร์ tf.atan (ชื่อว่า Atan โปรดดูสร้างโมเดล TensorFlow) รองรับ TensorFlow แต่ไม่รองรับใน LiteRT

โอเปอเรเตอร์ TensorFlow Text เป็นตัวอย่างของโอเปอเรเตอร์ที่กำหนดเอง โปรดดู บทแนะนำแปลงข้อความ TF เป็น LiteRT สำหรับตัวอย่างโค้ด

ตัวอย่าง: โอเปอเรเตอร์ Atan ที่กําหนดเอง

ลองมาดูตัวอย่างการรองรับโอเปอเรเตอร์ TensorFlow ที่ LiteRT ไม่มี สมมติว่าเราใช้โอเปอเรเตอร์ Atan และ เรากำลังสร้างโมเดลง่ายๆ สำหรับฟังก์ชัน y = atan(x + offset) โดยที่ offset ฝึกได้

สร้างโมเดล TensorFlow

ข้อมูลโค้ดต่อไปนี้จะฝึกโมเดล TensorFlow แบบง่าย โมเดลนี้เพิ่ง มีโอเปอเรเตอร์ที่กำหนดเองชื่อ Atan ซึ่งเป็นฟังก์ชัน y = atan(x + offset) โดยที่ offset ฝึกได้

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

ณ จุดนี้หากคุณพยายามสร้างโมเดล LiteRT โดยใช้ค่าเริ่มต้น ตัวแปลงสัญญาณ คุณจะได้รับข้อความแสดงข้อผิดพลาดต่อไปนี้

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

แปลงเป็นโมเดล LiteRT

สร้างโมเดล LiteRT ด้วยโอเปอเรเตอร์ที่กำหนดเอง โดยการตั้งค่าตัวแปลง แอตทริบิวต์ allow_custom_ops ตามที่แสดงด้านล่าง

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

ณ จุดนี้ หากคุณเรียกใช้กับล่ามเริ่มต้นโดยใช้คำสั่งต่างๆ เช่น ดังต่อไปนี้:

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

คุณจะยังคงได้รับข้อผิดพลาด:

Encountered unresolved custom op: Atan.

สร้างและลงทะเบียนโอเปอเรเตอร์

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

โอเปอเรเตอร์ LiteRT ที่กำหนดเองกำหนดโดยใช้ Pure-C API แบบง่ายที่ ประกอบด้วยประเภททึบ (TfLiteRegistrationExternal) และฟังก์ชันที่เกี่ยวข้อง

TfLiteRegistrationExternal เป็นประเภททึบ:

typedef struct TfLiteRegistrationExternal TfLiteRegistrationExternal;

TfLiteRegistrationExternal จะจัดเก็บข้อมูลประจำตัวและการใช้งานของโอเปอเรเตอร์ (โปรดทราบว่าโอเปอเรเตอร์แตกต่างจากตัวถูกดำเนินการ ซึ่งจัดเก็บไว้ใน โหนดกราฟ LiteRT สำหรับโหนดที่เรียกใช้โอเปอเรเตอร์)

อินสแตนซ์ของประเภทนี้สร้างขึ้นโดยเรียกไปยัง TfLiteRegistrationExternalCreate และทำลายได้โดยการเรียกใช้ TfLiteRegistrationExternalDelete

ข้อมูลประจำตัวของโอเปอเรเตอร์จะตั้งค่าผ่านพารามิเตอร์ไปยังฟังก์ชันตัวสร้าง 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.
);

การใช้งานโอเปอเรเตอร์สามารถกำหนด "เมธอด" ด้วยลายเซ็นต่อไปนี้ วิธีการทั้งหมดเหล่านี้เป็นตัวเลือกที่ไม่บังคับ แต่สำหรับโอเปอเรเตอร์ ประเมินแล้ว การใช้งานโอเปอเรเตอร์จะต้องกำหนดและตั้งค่า (โดยใช้ตัวตั้งค่า ) อย่างน้อย เมธอด Prepare และ Invoke

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

ชื่อฟังก์ชัน (หรือคำนำหน้าเนมสเปซสำหรับ C++) ในการใช้งานการดำเนินการของคุณ ไม่จำเป็นต้องตรงกับชื่อฟังก์ชันในข้อมูลโค้ดด้านบน เนื่องจาก API การดำเนินการที่กำหนดเองของ Lite จะใช้เฉพาะที่อยู่เท่านั้น เราขอแนะนำให้คุณ โดยประกาศในเนมสเปซที่ไม่ระบุตัวตนหรือเป็นฟังก์ชันคงที่

แต่คุณควรใส่ชื่อโอเปอเรเตอร์เป็นเนมสเปซหรือคำนำหน้าใน ชื่อฟังก์ชันเหล่านี้

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.
      

เนื่องจากเป็น C API ดังนั้น "เมธอด" เหล่านี้ ถูกใช้เป็นตัวชี้ฟังก์ชัน C ใน ประเภท TfLiteRegistrationExternal ซึ่งตั้งค่าโดยการส่งที่อยู่ของ ของฟังก์ชันการติดตั้งใช้งานกับฟังก์ชัน Setter ที่เกี่ยวข้อง 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));

โปรดดู common.h เพื่อดูรายละเอียดเกี่ยวกับ TfLiteContext และ TfLiteNode TfLiteContext แสดงข้อผิดพลาด หน่วยงานด้านการรายงานข่าวและการเข้าถึง วัตถุจากทั่วโลก รวมถึง Tensor ทั้งหมด TfLiteNode อนุญาตให้การติดตั้งใช้งานโอเปอเรเตอร์เข้าถึงอินพุตและเอาต์พุต

เมื่อล่ามโหลดโมเดล โมเดลจะเรียกเมธอด Init() 1 ครั้งต่อโมเดล ในกราฟ ระบบจะเรียก Init() ที่ระบุมากกว่า 1 ครั้งหากการดำเนินการคือ ใช้หลายครั้งในกราฟ สำหรับ Ops ที่กำหนดเอง บัฟเฟอร์การกำหนดค่าจะเป็น ที่ระบุ ซึ่งมี Flexbuffer ที่แมปชื่อพารามิเตอร์กับค่าของพารามิเตอร์นั้น บัฟเฟอร์ว่างเปล่าสำหรับการดำเนินการในตัวเนื่องจากล่ามได้แยกวิเคราะห์ พารามิเตอร์การดำเนินการ การติดตั้งใช้งานเคอร์เนลที่ต้องการสถานะควรเริ่มต้น ที่นี่และโอนการเป็นเจ้าของให้ผู้โทร สำหรับการโทร Init() แต่ละครั้งจะมี การเรียกที่เกี่ยวข้องไปยัง Free() ซึ่งช่วยให้การติดตั้งใช้งานกำจัด บัฟเฟอร์ที่อาจจัดสรรไว้ใน Init()

เมื่อใดก็ตามที่ Tensor อินพุตได้รับการปรับขนาด อินเทอร์พรีเตอร์จะดำเนินการผ่าน กราฟที่แจ้งเตือน การติดตั้งใช้งานการเปลี่ยนแปลง ซึ่งเปิดโอกาสให้พวกเขา ปรับขนาดบัฟเฟอร์ภายใน ตรวจสอบความถูกต้องของรูปร่างและประเภทอินพุต และ คำนวณรูปร่างเอาต์พุตใหม่ ซึ่งทำได้ด้วยเมธอด Prepare() และ การติดตั้งใช้งานจะเข้าถึงสถานะของตนเองได้โดยใช้ TfLiteOpaqueNodeGetUserData(node)

สุดท้าย ทุกครั้งที่เรียกใช้การอนุมาน ล่ามจะข้ามผ่านการเรียกกราฟ เมธอด Invoke() และที่นี่ก็มีสถานะเป็น TfLiteOpaqueNodeGetUserData(node)

คุณสามารถนำการดำเนินการที่กำหนดเองมาใช้ได้โดยกำหนด "เมธอด" เหล่านั้น แล้ว การกำหนดฟังก์ชันที่ส่งคืนอินสแตนซ์ของ TfLiteRegistrationExternal สร้างขึ้นโดยการเรียกใช้ TfLiteRegistrationExternalCreate จากนั้นแท็ก เมธอด 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;
}
      

โปรดทราบว่าการลงทะเบียนไม่ได้เกิดขึ้นโดยอัตโนมัติ และเป็นการติดต่อ ควรสร้างฟังก์ชัน MyCustomOpRegistration (ดูรายละเอียดด้านล่าง) ในขณะที่ ใช้เวลา BuiltinOpResolver มาตรฐาน (มีให้จากเป้าหมาย :builtin_ops) ในการจดทะเบียนบิวท์อิน จะต้องมีการรวบรวมฝ่ายดำเนินการที่กำหนดเองใน ไลบรารีที่กำหนดเองแยกต่างหาก

การกำหนดเคอร์เนลในรันไทม์ LiteRT

สิ่งที่เราต้องทำเพื่อใช้ปฏิบัติการใน LiteRT คือการกำหนดฟังก์ชัน 2 รายการ (Prepare และ Eval) และรายการที่ 3 เพื่อสร้าง 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;
}
      

เมื่อเริ่มต้น OpResolver ให้เพิ่มการดำเนินการที่กำหนดเองลงในรีโซลเวอร์ (โปรดดู ด้านล่าง) การดำเนินการนี้จะลงทะเบียนโอเปอเรเตอร์กับ LiteRT LiteRT สามารถใช้ การติดตั้งใหม่นี้ได้ โปรดทราบว่าตัวเลือก 2 รายการสุดท้าย อาร์กิวเมนต์ใน TfLiteRegistration สอดคล้องกับ AtanPrepare และ AtanEval ฟังก์ชันที่คุณกำหนดสำหรับการดำเนินการที่กำหนดเอง หากคุณเคยใช้ AtanInit และ AtanFree เพื่อเริ่มต้นตัวแปรที่ใช้ใน op และเพิ่มพื้นที่ว่าง ต่อมาจะถูกเพิ่มลงในอาร์กิวเมนต์ 2 รายการแรกของ TfLiteRegistration; ในตัวอย่างนี้กำหนดอาร์กิวเมนต์เหล่านั้นเป็น nullptr

ลงทะเบียนโอเปอเรเตอร์กับไลบรารีเคอร์เนล

ตอนนี้เราจำเป็นต้องลงทะเบียนโอเปอเรเตอร์กับไลบรารีเคอร์เนล คุณสามารถดำเนินการนี้กับ OpResolver ในเบื้องหลัง ล่ามจะโหลดไลบรารี เคอร์เนลที่จะได้รับการกำหนดเพื่อเรียกใช้โอเปอเรเตอร์แต่ละรายการในโมเดล แม้ว่าไลบรารีเริ่มต้นจะมีเพียงเคอร์เนลในตัวเท่านั้น แต่คุณสามารถ แทนที่/เสริมด้วยโอเปอเรเตอร์ไลบรารีที่กำหนดเอง

คลาส OpResolver ซึ่งแปลรหัสและชื่อโอเปอเรเตอร์ตามจริง มีคำจำกัดความดังนี้

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

โปรดทราบว่าสำหรับความเข้ากันได้แบบย้อนหลัง คลาสนี้จะใช้ประเภทคอนกรีตแบบเก่า TfLiteRegistration แทนที่จะเป็นประเภททึบแสง TfLiteRegistrationExternal แต่โครงสร้าง TfLiteRegistration มีฟิลด์ registration_external ของ ประเภท TfLiteRegistrationExternal*

คลาส MutableOpResolver และ BuiltinOpResolver มาจาก 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.
};

ในการใช้งานปกติ (ไม่มีการดำเนินการที่กำหนดเอง) คุณต้องใช้ BuiltinOpResolver และเขียนว่า

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

หากต้องการเพิ่มการดำเนินการที่กำหนดเองที่สร้างขึ้นด้านบน คุณสามารถใช้ MutableOpResolver แทน และเรียกใช้ AddCustom (ก่อนที่คุณจะส่งรีโซลเวอร์ไปยังฟังก์ชัน InterpreterBuilder)

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

หากชุดการดำเนินการในตัวมีขนาดใหญ่เกินไป OpResolver ใหม่อาจ โค้ดที่สร้างขึ้นจากชุดย่อยของการดำเนินการที่ระบุ อาจเป็นเพราะมีเพียงชุดย่อยของการดำเนินการ ในโมเดลที่ระบุ ค่านี้เทียบเท่ากับการลงทะเบียนเฉพาะจุดของ TensorFlow (และเวอร์ชันที่เรียบง่ายมีอยู่ในไดเรกทอรี tools)

หากต้องการกำหนดโอเปอเรเตอร์ที่กำหนดเองใน Java คุณจะต้องดำเนินการต่อไปนี้ สร้างเลเยอร์ JNI ที่กำหนดเองและคอมไพล์ AAR ของคุณเอง ในโค้ด Jni นี้ ในทำนองเดียวกัน หากต้องการกำหนดโอเปอเรเตอร์เหล่านี้ที่มีใน Python คุณสามารถ ให้ใส่การลงทะเบียนของคุณใน โค้ด Wrapper ของ Python

โปรดทราบว่าคุณสามารถปฏิบัติตามกระบวนการที่คล้ายกับข้างต้นเพื่อสนับสนุนชุดของ แทนที่จะเป็นโอเปอเรเตอร์เดียว เพียงเพิ่มโอเปอเรเตอร์ AddCustom จำนวนมาก ได้ตามต้องการ นอกจากนี้ MutableOpResolver ยังให้คุณลบล้าง การติดตั้งใช้งานบิวท์อินโดยใช้ AddBuiltin

ทดสอบและสร้างโปรไฟล์ผู้ให้บริการ

หากต้องการสร้างโปรไฟล์โดยใช้เครื่องมือการเปรียบเทียบ LiteRT คุณสามารถใช้ เครื่องมือโมเดลการเปรียบเทียบ สำหรับ LiteRT สำหรับการทดสอบ คุณสามารถทำให้ ผู้รู้เกี่ยวกับกระบวนการที่คุณกำหนดเองด้วยการเพิ่ม AddCustom ที่เหมาะสม โทรหา (ตามที่แสดงด้านบน) ไปยัง register.cc

แนวทางปฏิบัติแนะนำ

  1. เพิ่มประสิทธิภาพการจัดสรรหน่วยความจำและการยกเลิกการจัดสรรด้วยความระมัดระวัง กำลังจัดสรรความทรงจำ ใน Prepare มีประสิทธิภาพมากกว่าใน Invoke และจัดสรรหน่วยความจำ ก่อนที่การวนซ้ำจะดีกว่าในทุกๆ การทำซ้ำ ใช้ข้อมูล Tensor ชั่วคราว แทนการดึงตัวเอง (ดูข้อ 2) ใช้เคอร์เซอร์/การอ้างอิงแทน ให้ได้มากที่สุด

  2. หากโครงสร้างข้อมูลจะยังคงอยู่ตลอดการทำงาน เราแนะนำให้ จัดสรรหน่วยความจำล่วงหน้าโดยใช้ Tensor ชั่วคราว คุณอาจต้องใช้ โครงสร้าง OpData เพื่ออ้างอิงดัชนี tensor ในฟังก์ชันอื่นๆ โปรดดู ตัวอย่างใน เคอร์เนลสำหรับ Convolution ดูตัวอย่างข้อมูลโค้ดได้ที่ด้านล่าง

    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. หากไม่ต้องเปลืองหน่วยความจำมากจนเกินไป ขอแนะนำให้ใช้ขนาดคงที่ (หรือ std::vector ที่จัดสรรไว้ล่วงหน้าใน Resize) แทนที่จะใช้ จัดสรรแบบไดนามิกของ std::vector ทุกครั้งที่มีการเรียกใช้ซ้ำ

  4. หลีกเลี่ยงการสร้างอินสแตนซ์เทมเพลตคอนเทนเนอร์ไลบรารีมาตรฐานที่ยังไม่ได้ดำเนินการ ที่มีอยู่ เพราะส่งผลต่อขนาดไบนารี ตัวอย่างเช่น หากคุณต้องการ std::map ในการดำเนินการของคุณซึ่งไม่มีอยู่ในเคอร์เนลอื่นๆ โดยใช้ std::vector ที่มีการแมปการจัดทำดัชนีโดยตรงจะทำงานในขณะที่เก็บ เลขฐานสองมีขนาดเล็ก ดูว่าเคอร์เนลอื่นๆ ใช้สิ่งใดเพื่อรับข้อมูลเชิงลึก (หรือถาม)

  5. ตรวจสอบตัวชี้ไปยังหน่วยความจำที่ malloc แสดงผล หากเคอร์เซอร์นี้เป็น nullptr ไม่มีการดำเนินการที่ควรทำโดยใช้ตัวชี้นี้ หากคุณ mallocในฟังก์ชันและมีข้อผิดพลาด ให้จัดการหน่วยความจำก่อนคุณ ออก

  6. ใช้ TF_LITE_OPAQUE_ENSURE(context, condition) เพื่อตรวจสอบ โค้ดจะต้องไม่ค้างเอาไว้ระหว่างหน่วยความจำเมื่อ TF_LITE_OPAQUE_ENSURE กล่าวคือ ควรใช้มาโครเหล่านี้ก่อน ทรัพยากรที่มีอยู่ซึ่งจะรั่วไหล