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