LiteRT बिल्ट-इन ऑपरेटर लाइब्रेरी, सीमित मामलों में ही काम करती है TensorFlow ऑपरेटर की संख्या है, तो हर मॉडल कन्वर्टेबल नहीं होता. जानकारी के लिए, ऑपरेटर के साथ काम करने की सुविधा देखें.
रूपांतरण की अनुमति देने के लिए, उपयोगकर्ता LiteRT में, ऐसा TensorFlow ऑपरेटर है जो काम नहीं करता. इसे कस्टम ऑपरेटर के तौर पर जाना जाता है. अगर इसके बजाय, आप असमर्थित (या समर्थित) की एक श्रेणी को जोड़ना चाहते हैं TensorFlow ऑपरेटर, एक ही तरह से ऑप्टिमाइज़ किए गए एक कस्टम ऑपरेटर में बदलें. इसके बारे में ज़्यादा जानने के लिए, ऑपरेटर फ़्यूज़िंग.
कस्टम ऑपरेटर का इस्तेमाल करने के चार चरण होते हैं.
TensorFlow मॉडल बनाएं. पक्का करें कि आपने मॉडल (या ग्राफ़ डेफ़) का मतलब सही तरीके से LiteRT ऑपरेटर है.
LiteRT मॉडल में बदलें. पक्का करें कि आपने सही LiteRT कन्वर्टर एट्रिब्यूट सेट किया हो मॉडल को बदल दिया जाता है.
ऑपरेटर बनाना और रजिस्टर करना. यह ताकि LiteRT रनटाइम को आपके ऑपरेटर को मैप करने का तरीका पता हो और पैरामीटर को एक्ज़ीक्यूट करने की ज़रूरत नहीं पड़ेगी.
अपने ऑपरेटर की जांच करें और उसकी प्रोफ़ाइल बनाएं. अगर आपको बस अपने कस्टम ऑपरेटर की जांच करना चाहते हैं, तो बेहतर होगा कि मॉडल के साथ मॉडल बनाएं बस अपना कस्टम ऑपरेटर डालें और 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
इस स्थिति में, अगर डिफ़ॉल्ट कन्वर्टर फ़्लैग करते हैं, तो आपको नीचे दिया गया गड़बड़ी का मैसेज मिलेगा:
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 कस्टम ऑपरेटर को, एक ऐसे आसान प्योर-C एपीआई का इस्तेमाल करके तय किया जाता है जो
इसमें ओपेक टाइप (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);
आपके op लागू करने की प्रक्रिया में फ़ंक्शन names (या C++ के लिए नेमस्पेस प्रीफ़िक्स) ऊपर दिए गए कोड स्निपेट में फ़ंक्शन के नामों से मेल खाना ज़रूरी नहीं है, क्योंकि 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.
यह एक C API है, इसलिए ये "तरीके" को 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));
इससे संदर्भ लें
common.h
TfLiteContext
और TfLiteNode
पर जानकारी के लिए. TfLiteContext
गड़बड़ी देता है
रिपोर्टिंग सुविधाओं और सभी टेंसर सहित ग्लोबल ऑब्जेक्ट तक पहुंच.
TfLiteNode
, ऑपरेटर को लागू करने की अनुमति देता है, ताकि वह अपने इनपुट और आउटपुट ऐक्सेस कर सके.
जब अनुवादक किसी मॉडल को लोड करता है, तो वह हर एक मॉडल के लिए एक बार Init()
तरीके को कॉल करता है
नोड पर क्लिक करें. अगर सेशन की इकाई है, तो दिए गए Init()
को एक से ज़्यादा बार कॉल किया जाएगा
ग्राफ़ में कई बार इस्तेमाल किया गया है. कस्टम ऑपरेशन के लिए एक कॉन्फ़िगरेशन बफ़र होगा
इसमें एक फ़्लेक्सबफ़र मौजूद होता है, जो पैरामीटर के नामों को उनकी वैल्यू के हिसाब से मैप करता है. कॉन्टेंट बनाने
बिल्ट-इन ऑपरेशन के लिए बफ़र खाली है, क्योंकि अनुवादक ने इसे पहले ही पार्स कर लिया है
सेशन पैरामीटर. अगर कर्नेल लागू करने के लिए राज्य की ज़रूरत होती है, तो उसे शुरू किया जाना चाहिए
यहां जाएं और कॉलर को मालिकाना हक ट्रांसफ़र करें. हर Init()
कॉल के लिए,
Free()
को किया जाने वाला कॉल, जिसमें लागू करने की प्रोसेस को पूरा करने के लिए,
बफ़र, जो उन्होंने Init()
में तय किया हो.
जब भी इनपुट टेंसर का साइज़ बदला जाएगा, तब अनुवादक
बदलाव लागू करने की सूचना देने वाला ग्राफ़. इससे उन्हें यह मौका मिलता है कि
अपने आंतरिक बफ़र का साइज़ बदलें, इनपुट आकार और टाइप की वैधता की जाँच करें, और
आउटपुट के आकार की फिर से गणना करें. ये सभी काम Prepare()
तरीके से किए जाते हैं और
इंप्लिमेंटेशन की स्थिति का इस्तेमाल करके
TfLiteOpaqueNodeGetUserData(node)
.
आखिर में, हर बार अनुमान चलने पर, अनुवादक ग्राफ़ कॉलिंग
Invoke()
तरीका है, और यहां भी राज्य इस रूप में उपलब्ध है
TfLiteOpaqueNodeGetUserData(node)
.
उन "तरीके" को परिभाषित करके कस्टम ऑपरेशन लागू किए जा सकते हैं फ़ंक्शन और फिर
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
टारगेट से उपलब्ध)
के पंजीकरण के लिए तैयार किया है, तो कस्टम ऑपरेशन को
अलग-अलग कस्टम लाइब्रेरी बनाने की सुविधा मिलती है.
LiteRT रनटाइम में कर्नेल को तय करना
LiteRT में ऑप का इस्तेमाल करने के लिए, हमें बस दो फ़ंक्शन तय करने हैं
(Prepare
और Eval
), और 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 नए इंप्लीमेंटेशन का इस्तेमाल कर सकता है. ध्यान दें कि पिछले दो
TfLiteRegistration
में तर्क AtanPrepare
और AtanEval
के मुताबिक हैं
कस्टम ऑप के लिए तय किए गए फ़ंक्शन. अगर आपने 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;
...
};
ध्यान दें कि पुराने सिस्टम के साथ काम करने की सुविधा के लिए, यह क्लास पुराने कॉन्क्रीट टाइप का इस्तेमाल करती है
ओपेक टाइप TfLiteRegistrationExternal
के बजाय TfLiteRegistration
,
लेकिन 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 में अपने कस्टम ऑपरेटर तय करने हैं, तो आपको फ़िलहाल अपनी पसंद के मुताबिक जेएनआई लेयर बनाएं और अपना एएआर कंपाइल करें इस जेनआई कोड में. इसी तरह, अगर आपको Python में उपलब्ध इन ऑपरेटर के बारे में बताना है, तो अपने रजिस्ट्रेशन को इसमें रखें Python रैपर कोड.
ध्यान दें कि ऊपर बताई गई प्रक्रिया का इस्तेमाल, विज्ञापन देने वाले लोगों या कंपनियों के
किसी एक ऑपरेटर के बजाय कई कार्रवाइयों के लिए इस्तेमाल किया जाता है. बस ज़्यादा से ज़्यादा AddCustom
ऑपरेटर जोड़ें
मदद कर सकता है. इसके अलावा, MutableOpResolver
आपको ओवरराइड करने की भी अनुमति देता है
AddBuiltin
का इस्तेमाल करके बिल्टइन को लागू करना.
अपने ऑपरेटर की जांच करें और उसकी प्रोफ़ाइल बनाएं
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; }
अगर इसका खर्च बहुत ज़्यादा नहीं होता है, तो स्टैटिक तय साइज़ का इस्तेमाल करें अरे (या
Resize
में पहले से तयstd::vector
) के बजाय एक्ज़ीक्यूशन के दौरान हर बार डाइनैमिक तरीके से असाइन किया गयाstd::vector
.स्टैंडर्ड लाइब्रेरी कंटेनर टेंप्लेट को इंस्टैंशिएट करने से बचें, जो पहले से मौजूद नहीं है मौजूद हैं, क्योंकि वे बाइनरी साइज़ पर असर डालते हैं. उदाहरण के लिए, अगर आपको किसी आपके ऑपरेशन में
std::map
जो अन्य कर्नेल में मौजूद नहीं है, इसका इस्तेमाल करके डायरेक्ट इंडेक्स मैपिंग की मदद से,std::vector
की मदद से काम करने के दौरान, छोटा बाइनरी साइज़. देखें कि अन्य कर्नेल अहम जानकारी पाने (या पूछें) के लिए किस चीज़ का इस्तेमाल करते हैं.malloc
से मिली 'यादें' के लिए पॉइंटर की जांच करें. अगर यह पॉइंटरnullptr
, उस पॉइंटर का इस्तेमाल करके कोई कार्रवाई नहीं की जानी चाहिए. अगर आपको फ़ंक्शन मेंmalloc
और गड़बड़ी से बाहर निकलें, इससे पहले मेमोरी दोबारा असाइन करें बंद करें.TF_LITE_OPAQUE_ENSURE(context, condition)
का इस्तेमाल करके देखें कि स्थिति. आपके कोड को मेमोरी निलंबित नहीं होनी चाहिएTF_LITE_OPAQUE_ENSURE
का इस्तेमाल किया गया है, यानी इन मैक्रो का इस्तेमाल पहले का इस्तेमाल किया जा सकता है.