เนื่องจากไลบรารีโอเปอเรเตอร์ LiteRT ในตัวรองรับเฉพาะ จำนวนโอเปอเรเตอร์ TensorFlow ไม่ใช่บางโมเดลที่แปลงได้ โปรดดูรายละเอียด ดูความเข้ากันได้ของโอเปอเรเตอร์
หากต้องการทำให้เกิด Conversion ผู้ใช้สามารถระบุการติดตั้งใช้งาน โอเปอเรเตอร์ TensorFlow ที่ไม่รองรับใน LiteRT เรียกว่าโอเปอเรเตอร์ที่กำหนดเอง หากต้องการรวมชุดข้อมูลที่ไม่รองรับ (หรือรองรับ) โอเปอเรเตอร์ TensorFlow เข้าเป็นโอเปอเรเตอร์แบบกำหนดเองที่มีการเพิ่มประสิทธิภาพ Fused เดี่ยวอ้างอิงไปยัง Operator Fusing
การใช้โอเปอเรเตอร์ที่กำหนดเองมี 4 ขั้นตอน
สร้างโมเดล TensorFlow ตรวจสอบว่าได้บันทึก โมเดล (หรือ Graph Def) หมายถึงโอเปอเรเตอร์ LiteRT ที่มีชื่อถูกต้อง
แปลงเป็นโมเดล LiteRT โปรดตรวจสอบว่าคุณตั้งค่าแอตทริบิวต์ตัวแปลง LiteRT ที่ถูกต้องเพื่อ แปลงโมเดลเสร็จสมบูรณ์
สร้างและลงทะเบียนโอเปอเรเตอร์ ช่วงเวลานี้ เพื่อให้รันไทม์ LiteRT รู้วิธีแมปโอเปอเรเตอร์และ พารามิเตอร์ในกราฟไปยังโค้ดปฏิบัติการ C/C++
ทดสอบและสร้างโปรไฟล์ผู้ให้บริการ หากคุณ ต้องการทดสอบโอเปอเรเตอร์ที่กำหนดเองเท่านั้น คุณควรสร้างโมเดลด้วย เพียงโอเปอเรเตอร์ที่กำหนดเองแล้วใช้ benchmark_model ของโปรแกรม
มาดูตัวอย่างจากต้นทางถึงปลายทางของการเรียกใช้โมเดลที่มี
โอเปอเรเตอร์ 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 ที่เกี่ยวข้อง
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
แสดงข้อผิดพลาด
หน่วยงานด้านการรายงานข่าวและการเข้าถึง วัตถุจากทั่วโลก รวมถึง 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
แนวทางปฏิบัติแนะนำ
เพิ่มประสิทธิภาพการจัดสรรหน่วยความจำและการยกเลิกการจัดสรรด้วยความระมัดระวัง กำลังจัดสรรความทรงจำ ใน
Prepare
มีประสิทธิภาพมากกว่าในInvoke
และจัดสรรหน่วยความจำ ก่อนที่การวนซ้ำจะดีกว่าในทุกๆ การทำซ้ำ ใช้ข้อมูล Tensor ชั่วคราว แทนการดึงตัวเอง (ดูข้อ 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; }
หากไม่ต้องเปลืองหน่วยความจำมากจนเกินไป ขอแนะนำให้ใช้ขนาดคงที่ (หรือ
std::vector
ที่จัดสรรไว้ล่วงหน้าในResize
) แทนที่จะใช้ จัดสรรแบบไดนามิกของstd::vector
ทุกครั้งที่มีการเรียกใช้ซ้ำหลีกเลี่ยงการสร้างอินสแตนซ์เทมเพลตคอนเทนเนอร์ไลบรารีมาตรฐานที่ยังไม่ได้ดำเนินการ ที่มีอยู่ เพราะส่งผลต่อขนาดไบนารี ตัวอย่างเช่น หากคุณต้องการ
std::map
ในการดำเนินการของคุณซึ่งไม่มีอยู่ในเคอร์เนลอื่นๆ โดยใช้std::vector
ที่มีการแมปการจัดทำดัชนีโดยตรงจะทำงานในขณะที่เก็บ เลขฐานสองมีขนาดเล็ก ดูว่าเคอร์เนลอื่นๆ ใช้สิ่งใดเพื่อรับข้อมูลเชิงลึก (หรือถาม)ตรวจสอบตัวชี้ไปยังหน่วยความจำที่
malloc
แสดงผล หากเคอร์เซอร์นี้เป็นnullptr
ไม่มีการดำเนินการที่ควรทำโดยใช้ตัวชี้นี้ หากคุณmalloc
ในฟังก์ชันและมีข้อผิดพลาด ให้จัดการหน่วยความจำก่อนคุณ ออกใช้
TF_LITE_OPAQUE_ENSURE(context, condition)
เพื่อตรวจสอบ โค้ดจะต้องไม่ค้างเอาไว้ระหว่างหน่วยความจำเมื่อTF_LITE_OPAQUE_ENSURE
กล่าวคือ ควรใช้มาโครเหล่านี้ก่อน ทรัพยากรที่มีอยู่ซึ่งจะรั่วไหล