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

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

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

การใช้โอเปอเรเตอร์ที่กำหนดเองมี 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 จะกำหนดโดยใช้ API แบบ C ล้วนที่เรียบง่าย ซึ่งประกอบด้วยประเภทที่ทึบแสง (TfLiteOperator) และฟังก์ชันที่เกี่ยวข้อง

TfLiteOperator เป็นประเภทที่ไม่ชัดเจน

typedef struct TfLiteOperator TfLiteOperator;

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

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

ระบบจะตั้งค่าข้อมูลประจำตัวของผู้ให้บริการผ่านพารามิเตอร์ไปยังฟังก์ชันตัวสร้าง TfLiteOperatorCreate ดังนี้

TfLiteOperator*
TfLiteOperatorCreate(
    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 ไม่จำเป็นต้องตรงกับชื่อฟังก์ชันในโค้ดตัวอย่างด้านบน เนื่องจาก TF Lite Custom 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.
      

เนื่องจากเป็น C API "เมธอด" เหล่านี้จึงได้รับการติดตั้งใช้งานเป็นตัวชี้ฟังก์ชัน C ใน ประเภท TfLiteOperator ซึ่งตั้งค่าโดยการส่งที่อยู่ของ ฟังก์ชันการติดตั้งใช้งานไปยังฟังก์ชัน Setter ที่เกี่ยวข้อง TfLiteOperatorSetMethodName:

void TfLiteOperatorSetInit(
    TfLiteOperator* operator,
    void* (*init)(TfLiteOpaqueContext* context, const char* buffer,
                  size_t length));
void TfLiteOperatorSetFree(
    TfLiteOperator* operator,
    void (*free)(TfLiteOpaqueContext* context, void* data));
void TfLiteOperatorSetPrepare(
    TfLiteOperator* operator,
    TfLiteStatus (*prepare)(TfLiteOpaqueContext* context,
                            TfLiteOpaqueNode* node));
void TfLiteOperatorSetInvoke(
    TfLiteOperator* operator,
    TfLiteStatus (*invoke)(TfLiteOpaqueContext* context,
                           TfLiteOpaqueNode* node));
void TfLiteOperatorSetAsyncKernel(
    TfLiteOperator* operator,
    struct TfLiteAsyncKernel* (*async_kernel)(TfLiteOpaqueContext* context,
                                              TfLiteOpaqueNode* node));

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

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

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

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

คุณสามารถใช้การดำเนินการที่กำหนดเองได้โดยการกำหนดฟังก์ชัน "เมธอด" เหล่านั้น จากนั้น กำหนดฟังก์ชันที่แสดงผลอินสแตนซ์ของ TfLiteOperator ที่สร้างขึ้นโดยการเรียกใช้ TfLiteOperatorCreate และเมธอดตัวตั้งค่าที่เกี่ยวข้อง

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 TfLiteOperator* MyCustomOperator() {
    // Singleton instance, intentionally never destroyed.
    static const TfLiteOperator* my_custom_op = ()[] {
        TfLiteOperator* r =
            TfLiteOperatorCreate(
                kTfLiteBuiltinCustom, "MyCustomOp", /*version=*/ 1);
        TfLiteOperatorSetInit(r, Init);
        TfLiteOperatorSetFree(r, Free);
        TfLiteOperatorSetPrepare(r, Prepare);
        TfLiteOperatorSetInvoke(r, Eval);
        return r;
      };
    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 TfLiteOperator* MyCustomOpCreate() {
  const TfLiteOperator* r =
      TfLiteOperatorCreate(
          kTfLiteBuiltinCustom, "MyCustomOp", /*version=*/ 1);
  TfLiteOperatorSetInit(r, MyCustomOpInit);
  TfLiteOperatorSetFree(r, MyCustomOpFree);
  TfLiteOperatorSetPrepare(r, MyCustomOpPrepare);
  TfLiteOperatorSetInvoke(r, MyCustomOpEval);
  return r;
}

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

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

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

สิ่งที่เราต้องทำเพื่อใช้ Op ใน LiteRT คือการกำหนดฟังก์ชัน 2 ฟังก์ชัน (Prepare และ Eval) และฟังก์ชันที่ 3 เพื่อสร้าง TfLiteOperator

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 TfLiteOperator* AtanOperator() {
    // Singleton instance, intentionally never destroyed.
    static const TfLiteOperator* atan_op = ()[] {
        auto* r = TfLiteOperatorCreate(
            kTfLiteBuiltinCustom, "ATAN", /*version=*/ 1);
        TfLiteOperatorSetPrepare(r, Prepare);
        TfLiteOperatorSetInvoke(r, Eval);
        return r;
      };
    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 TfLiteOperator* AtanOpCreate() {
  TfLiteOperator* r = TfLiteOperatorCreate(
          kTfLiteBuiltinCustom, "ATAN", /*version=*/ 1);
  TfLiteOperatorSetPrepare(r, Prepare);
  TfLiteOperatorSetInvoke(r, Eval);
  return r;
}

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

เมื่อเริ่มต้น OpResolver ให้เพิ่ม Op ที่กำหนดเองลงในตัวแก้ไข (ดูตัวอย่างด้านล่าง) ซึ่งจะเป็นการลงทะเบียนผู้ให้บริการกับ LiteRT เพื่อให้ LiteRT ใช้การติดตั้งใช้งานใหม่ได้

ลงทะเบียนตัวดำเนินการกับไลบรารีเคอร์เนล

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

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

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

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

คลาส MutableOpResolver และ BuiltinOpResolver ได้มาจาก OpResolver

class MutableOpResolver : public OpResolver {
 public:
  MutableOpResolver();  // Constructs an initially empty op resolver.
  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 แทน และเรียกใช้ tflite::AddOp (ก่อนที่จะส่งตัวแก้ไขไปยัง InterpreterBuilder) ดังนี้

tflite::ops::builtin::MutableOpResolver resolver;
resolver.AddAll(tflite::ops::builtin::BuiltinOpResolver());
tflite::AddOp(&resolver, AtanOpRegistration());

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

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

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

ทดสอบและสร้างโปรไฟล์โอเปอเรเตอร์

หากต้องการสร้างโปรไฟล์ Op ด้วยเครื่องมือเปรียบเทียบ LiteRT คุณสามารถใช้เครื่องมือโมเดลเปรียบเทียบ สำหรับ LiteRT ได้ เพื่อวัตถุประสงค์ในการทดสอบ คุณสามารถทำให้บิลด์ LiteRT ในเครื่องรู้จักการดำเนินการที่กำหนดเองได้โดยเพิ่ม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 กล่าวคือ ควรใช้มาโครเหล่านี้ก่อน จัดสรรทรัพยากรใดๆ ที่จะรั่วไหล