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

เนื่องจากไลบรารีโอเปอเรเตอร์ในตัวของ TensorFlow Lite รองรับโอเปอเรเตอร์ TensorFlow ในจำนวนจำกัดเท่านั้น จึงไม่สามารถแปลงได้ครบทุกโมเดล ดูรายละเอียดได้ที่ความเข้ากันได้ของโอเปอเรเตอร์

หากต้องการอนุญาต Conversion ผู้ใช้จะติดตั้งใช้งานโอเปอเรเตอร์ TensorFlow ที่ไม่รองรับด้วยตนเองใน TensorFlow Lite หรือที่เรียกว่าโอเปอเรเตอร์ที่กำหนดเองได้ แต่หากคุณต้องการรวมชุดโอเปอเรเตอร์ TensorFlow ที่ไม่รองรับ (หรือรองรับ) เป็นโอเปอเรเตอร์แบบกำหนดเองที่เพิ่มประสิทธิภาพ Fused รายการเดียว โปรดดูการรวมโอเปอเรเตอร์

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

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

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

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

ลองดูตัวอย่างการรองรับโอเปอเรเตอร์ TensorFlow ที่ TensorFlow Lite ไม่มี สมมติว่าเราใช้โอเปอเรเตอร์ 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

เมื่อถึงจุดนี้หากคุณพยายามสร้างโมเดล TensorFlow Lite ด้วยแฟล็กตัวแปลงเริ่มต้น คุณจะได้รับข้อความแสดงข้อผิดพลาดต่อไปนี้

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

แปลงเป็นโมเดล TensorFlow Lite

สร้างโมเดล TensorFlow Lite ด้วยโอเปอเรเตอร์ที่กำหนดเองโดยการตั้งค่าแอตทริบิวต์ตัวแปลง 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"

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

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

typedef struct TfLiteRegistrationExternal TfLiteRegistrationExternal;

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

อินสแตนซ์ประเภทนี้สร้างขึ้นโดยมีการเรียกไปยัง 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);

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

แต่เราขอแนะนำให้ใส่ชื่อโอเปอเรเตอร์เป็นเนมสเปซหรือคำนำหน้าชื่อฟังก์ชันต่อไปนี้

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.
      

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

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

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

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

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

การดำเนินการที่กำหนดเองสามารถนำมาใช้ได้โดยกำหนดฟังก์ชัน "method" เหล่านั้น จากนั้นกำหนดฟังก์ชันที่ส่งคืนอินสแตนซ์ของ TfLiteRegistrationExternal ที่สร้างขึ้นโดยการเรียกใช้ TfLiteRegistrationExternalCreate จากนั้นตามด้วยเมธอดตัวตั้งค่าที่เกี่ยวข้อง

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) จะดูแลการจดทะเบียนบิวท์อิน แต่จะต้องมีการรวบรวมการดำเนินการที่กำหนดเองไว้ในไลบรารีที่กำหนดเองแยกต่างหาก

การกำหนดเคอร์เนลในรันไทม์ของ TensorFlow Lite

สิ่งที่เราต้องทำเพื่อใช้ Op ใน TensorFlow Lite คือการกำหนดฟังก์ชัน 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(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;
}
      

เมื่อเริ่มต้น OpResolver ให้เพิ่มตัวเลือกที่กำหนดเองลงในรีโซลเวอร์ (ดูตัวอย่างด้านล่าง) การดำเนินการนี้จะลงทะเบียนโอเปอเรเตอร์กับ Tensorflow Lite เพื่อให้ TensorFlow Lite ใช้การติดตั้งใช้งานใหม่ได้ โปรดทราบว่าอาร์กิวเมนต์ 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 ใหม่ตามชุดย่อยของ Ops ที่ระบุ โดยอาจเป็นเฉพาะรายการที่อยู่ในโมเดลดังกล่าวเท่านั้น ซึ่งเทียบเท่ากับการลงทะเบียนที่เลือกของ TensorFlow (และจะมีเวอร์ชันธรรมดาอยู่ในไดเรกทอรี tools)

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

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

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

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

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

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

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

    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 กล่าวคือ ควรใช้มาโครเหล่านี้ก่อนที่จะมีการจัดสรรทรัพยากรที่จะทำให้เกิดการรั่วไหล