ตัวดำเนินการที่กำหนดเอง

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

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

การใช้ตัวดำเนินการแบบกำหนดเองประกอบด้วยสี่ขั้นตอน

  • สร้างโมเดล TensorFlow ตรวจสอบให้แน่ใจว่าโมเดลที่บันทึกไว้ (หรือ Graph Def) อ้างอิงถึงตัวดำเนินการ TensorFlow Lite ที่มีชื่ออย่างถูกต้อง

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

  • สร้างและลงทะเบียนตัวดำเนินการ ทั้งนี้เพื่อให้รันไทม์ TensorFlow Lite รู้วิธีแมปตัวดำเนินการและพารามิเตอร์ในกราฟของคุณกับโค้ด C/C++ ที่รันได้

  • ทดสอบและโปรไฟล์ผู้ให้บริการของคุณ หากคุณต้องการทดสอบเฉพาะโอเปอเรเตอร์ที่คุณกำหนดเอง วิธีที่ดีที่สุดคือสร้างโมเดลด้วยโอเปอเรเตอร์ที่คุณกำหนดเองและใช้โปรแกรม benchmark_model

มาดูตัวอย่างแบบครบวงจรของการรันโมเดลด้วยโอเปอเรเตอร์ที่กำหนดเอง tf.atan (ชื่อ Atan อ้างอิงถึง #create_a_tensorflow_model) ซึ่งได้รับการรองรับใน 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 "tensorflow/lite/c/c_api.h"
#include "tensorflow/lite/c/c_api_opaque.h"

ตัวดำเนินการแบบกำหนดเองของ TensorFlow Lite ได้รับการกำหนดโดยใช้ pure-C API ธรรมดาที่ประกอบด้วยประเภททึบแสง ( 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.
);

การใช้งานตัวดำเนินการสามารถกำหนด "วิธีการ" ด้วยลายเซ็นต่อไปนี้ วิธีการเหล่านี้ทั้งหมดเป็นทางเลือก แต่เพื่อให้ผู้ปฏิบัติงานได้รับการประเมินได้สำเร็จ การใช้งานตัวดำเนินการจำเป็นต้องกำหนดและตั้งค่า (โดยใช้ฟังก์ชัน setter) อย่างน้อยก็ 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++) ในการใช้งาน op ของคุณไม่จำเป็นต้องตรงกับชื่อฟังก์ชันในข้อมูลโค้ดข้างต้น เนื่องจาก API การดำเนินการที่กำหนดเองของ TF Lite จะใช้เฉพาะที่อยู่เท่านั้น เราขอแนะนำให้คุณประกาศไว้ในเนมสเปซที่ไม่ระบุชื่อหรือเป็นฟังก์ชันคงที่

แต่เป็นความคิดที่ดีที่จะรวมชื่อโอเปอเรเตอร์ของคุณเป็นเนมสเปซหรือคำนำหน้าชื่อฟังก์ชันเหล่านี้:

ซี++

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

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

เนื่องจากนี่คือ C API "วิธีการ" เหล่านี้จึงถูกนำมาใช้เป็นตัวชี้ฟังก์ชัน C ในประเภท TfLiteRegistrationExternal ซึ่งตั้งค่าโดยการส่งที่อยู่ของฟังก์ชันการใช้งานของคุณไปยังฟังก์ชัน setter ที่เกี่ยวข้อง TfLiteRegistrationExternalSet MethodName :

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 อำนวยความสะดวกในการรายงานข้อผิดพลาดและการเข้าถึงอ็อบเจ็กต์ส่วนกลาง รวมถึงเทนเซอร์ทั้งหมด TfLiteNode ช่วยให้ผู้ปฏิบัติงานสามารถเข้าถึงอินพุตและเอาต์พุตได้

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

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

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

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

ซี++

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
      

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

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

สิ่งที่เราต้องทำเพื่อใช้ op ใน TensorFlow Lite คือการกำหนดสองฟังก์ชัน ( Prepare และ Eval ) และฟังก์ชันที่สามเพื่อสร้าง TfLiteRegistrationExternal :

ซี++

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
      

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 ให้เพิ่ม op ที่กำหนดเองลงในตัวแก้ไข (ดูตัวอย่างด้านล่าง) การดำเนินการนี้จะลงทะเบียนโอเปอเรเตอร์กับ Tensorflow Lite เพื่อให้ TensorFlow Lite สามารถใช้การใช้งานใหม่ได้ โปรดทราบว่าอาร์กิวเมนต์สองรายการสุดท้ายใน TfLiteRegistration สอดคล้องกับฟังก์ชัน AtanPrepare และ AtanEval ที่คุณกำหนดไว้สำหรับ op ที่กำหนดเอง หากคุณใช้ฟังก์ชัน AtanInit และ AtanFree เพื่อเตรียมใช้งานตัวแปรที่ใช้ใน op และเพิ่มพื้นที่ว่าง ตามลำดับ ฟังก์ชันเหล่านี้จะถูกเพิ่มเข้าไปในอาร์กิวเมนต์สองตัวแรกของ 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;

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

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

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

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

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

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

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

ปฏิบัติที่ดีที่สุด

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

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

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