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