LiteRT में मौजूद ऑपरेटर लाइब्रेरी में, TensorFlow के सिर्फ़ कुछ ऑपरेटर इस्तेमाल किए जा सकते हैं. इसलिए, हर मॉडल को कन्वर्ट नहीं किया जा सकता. ज़्यादा जानकारी के लिए, ऑपरेटर के साथ काम करने की सुविधा देखें.
कन्वर्ज़न की अनुमति देने के लिए, उपयोगकर्ता LiteRT में ऐसे TensorFlow ऑपरेटर को कस्टम तौर पर लागू कर सकते हैं जो काम नहीं करता. इसे कस्टम ऑपरेटर कहा जाता है. अगर आपको ऐसे TensorFlow ऑपरेटर को एक साथ जोड़कर, ऑप्टिमाइज़ किया गया कस्टम ऑपरेटर बनाना है जो काम नहीं करते (या काम करते हैं), तो ऑपरेटर फ़्यूज़िंग लेख पढ़ें.
कस्टम ऑपरेटर का इस्तेमाल करने के लिए, ये चार चरण पूरे करें.
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 कस्टम ऑपरेटर, एक सामान्य प्योर-सी एपीआई का इस्तेमाल करके तय किए जाते हैं. इसमें एक अपारदर्शी टाइप (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.
);
ऑपरेटर लागू करने वाला व्यक्ति या कंपनी, इन सिग्नेचर के साथ "तरीके" तय कर सकती है.
इन सभी तरीकों का इस्तेमाल करना ज़रूरी नहीं है. हालांकि, ऑपरेटर का आकलन सही तरीके से करने के लिए, ऑपरेटर को लागू करने के दौरान कम से कम 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++ के लिए नेमस्पेस प्रीफ़िक्स) का, ऊपर दिए गए कोड स्निपेट में मौजूद फ़ंक्शन के नामों से मेल खाना ज़रूरी नहीं है. ऐसा इसलिए, क्योंकि TF Lite कस्टम ऑप 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 है. इसलिए, इन "तरीकों" को TfLiteOperator
टाइप में C फ़ंक्शन पॉइंटर के तौर पर लागू किया जाता है. इन्हें सेट करने के लिए, अपने लागू किए गए फ़ंक्शन के पते को संबंधित सेटर फ़ंक्शन 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()
को एक से ज़्यादा बार कॉल किया जाएगा. कस्टम ऑप्स के लिए कॉन्फ़िगरेशन बफ़र उपलब्ध कराया जाएगा. इसमें एक फ़्लेक्सबफ़र होगा, जो पैरामीटर के नामों को उनकी वैल्यू के साथ मैप करता है. इंटरप्रेटर ने पहले ही ओप पैरामीटर पार्स कर लिए हैं. इसलिए, बिल्ट-इन ऑप के लिए बफ़र खाली है. जिन कर्नल को स्थिति की ज़रूरत होती है उन्हें यहां इसे शुरू करना चाहिए और कॉलर को मालिकाना हक ट्रांसफ़र करना चाहिए. हर Init()
कॉल के लिए, Free()
को एक कॉल किया जाएगा. इससे लागू करने वाले लोग, Init()
में असाइन किए गए बफ़र को हटा सकेंगे.
जब भी इनपुट टेंसर का साइज़ बदला जाता है, तो इंटरप्रेटर ग्राफ़ से होकर गुज़रेगा. साथ ही, बदलाव के बारे में लागू करने की प्रोसेस को सूचना देगा. इससे उन्हें अपने इंटरनल बफ़र का साइज़ बदलने, इनपुट शेप और टाइप की वैधता की जांच करने, और आउटपुट शेप को फिर से कैलकुलेट करने का मौका मिलता है. यह सब Prepare()
तरीके से किया जाता है. साथ ही, लागू करने वाले लोग TfLiteOpaqueNodeGetUserData(node)
का इस्तेमाल करके, अपनी स्थिति ऐक्सेस कर सकते हैं.
आखिर में, हर बार अनुमान लगाने की प्रोसेस शुरू होने पर, इंटरप्रेटर ग्राफ़ को ट्रैवर्स करता है और Invoke()
तरीके को कॉल करता है. यहां भी स्थिति TfLiteOpaqueNodeGetUserData(node)
के तौर पर उपलब्ध होती है.
कस्टम ऑप को लागू करने के लिए, "तरीके" फ़ंक्शन तय किए जा सकते हैं. इसके बाद, एक ऐसा फ़ंक्शन तय किया जा सकता है जो TfLiteOperatorCreate
को कॉल करके बनाए गए TfLiteOperator
का इंस्टेंस दिखाता है. इसके बाद, सेटर के काम के तरीके:
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 रनटाइम में कर्नल तय करना
LiteRT में op का इस्तेमाल करने के लिए, हमें सिर्फ़ दो फ़ंक्शन (Prepare
और Eval
) और 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
को शुरू करते समय, रिज़ॉल्वर में कस्टम ऑप जोड़ें. उदाहरण के लिए, नीचे दिया गया तरीका देखें. इससे ऑपरेटर, LiteRT के साथ रजिस्टर हो जाएगा. इससे LiteRT, नए तरीके का इस्तेमाल कर पाएगा.
ऑपरेटर को कर्नल लाइब्रेरी में रजिस्टर करें
अब हमें ऑपरेटर को कर्नल लाइब्रेरी के साथ रजिस्टर करना होगा. ऐसा OpResolver
की मदद से किया जाता है. बैकग्राउंड में, इंटरप्रेटर कर्नल की लाइब्रेरी लोड करेगा. इसे मॉडल में मौजूद हर ऑपरेटर को लागू करने के लिए असाइन किया जाएगा.
डिफ़ॉल्ट लाइब्रेरी में सिर्फ़ बिल्ट-इन कर्नल होते हैं. हालांकि, इसे कस्टम लाइब्रेरी ऑपरेटरों से बदला/बढ़ाया जा सकता है.
OpResolver
क्लास, ऑपरेटर कोड और नामों को असल कोड में बदलती है. इसे इस तरह से परिभाषित किया गया है:
class OpResolver {
public:
virtual TfLiteRegistration* FindOp(tflite::BuiltinOperator op) const = 0;
virtual TfLiteRegistration* FindOp(const char* op) const = 0;
...
};
ध्यान दें कि पुराने सिस्टम के साथ काम करने की सुविधा के लिए, यह क्लास ओपेक टाइप TfLiteOperator
के बजाय, पुराने कॉन्क्रीट टाइप TfLiteRegistration
का इस्तेमाल करती है. हालांकि, TfLiteRegistration
स्ट्रक्चर में TfLiteOperator*
टाइप का registration_external
फ़ील्ड होता है.
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 लेयर बनानी होगी. साथ ही, इस jni कोड में अपना AAR कंपाइल करना होगा. इसी तरह, अगर आपको Python में उपलब्ध इन ऑपरेटर को तय करना है, तो अपने रजिस्ट्रेशन को Python रैपर कोड में रखें.
ध्यान दें कि ऊपर बताई गई प्रोसेस का इस्तेमाल, एक ऑपरेटर के बजाय कई ऑपरेटर के साथ काम करने के लिए किया जा सकता है. अपनी ज़रूरत के हिसाब से, जितने चाहें उतने AddCustom
ऑपरेटर जोड़ें. इसके अलावा, MutableOpResolver
की मदद से, AddBuiltin
का इस्तेमाल करके, बिल्ट-इन फ़ंक्शन के लागू करने के तरीके को बदला जा सकता है.
अपने ऑपरेटर की जांच करना और उसकी प्रोफ़ाइल बनाना
LiteRT के बेंचमार्क टूल की मदद से अपने ऑप की प्रोफ़ाइल बनाने के लिए, LiteRT के बेंचमार्क मॉडल टूल का इस्तेमाल किया जा सकता है. जांच के लिए, register.cc में ऊपर दिखाए गए तरीके से सही AddCustom
कॉल जोड़कर, LiteRT के स्थानीय बिल्ड को अपने कस्टम ऑप के बारे में जानकारी दी जा सकती है
सबसे सही तरीके
मेमोरी के बंटवारे और बंटवारे को हटाने की प्रोसेस को सावधानी से ऑप्टिमाइज़ करें.
Invoke
की तुलना मेंPrepare
में मेमोरी को ज़्यादा बेहतर तरीके से बांटा जाता है. साथ ही, हर बार लूप में मेमोरी को बांटने के बजाय, लूप से पहले मेमोरी को बांटना ज़्यादा बेहतर होता है. खुद मेमोरी मैनेज करने के बजाय, टेंसर के डेटा का इस्तेमाल करें (दूसरा आइटम देखें). ज़्यादा से ज़्यादा कॉपी करने के बजाय, पॉइंटर/रेफ़रंस का इस्तेमाल करें.अगर डेटा स्ट्रक्चर पूरे ऑपरेशन के दौरान बना रहता है, तो हमारा सुझाव है कि आप अस्थायी टेंसर का इस्तेमाल करके मेमोरी को पहले से ही असाइन कर दें. आपको अन्य फ़ंक्शन में टेंसर इंडेक्स का रेफ़रंस देने के लिए, OpData struct का इस्तेमाल करना पड़ सकता है. कनवोल्यूशन के लिए कर्नेल में दिया गया उदाहरण देखें. यहां कोड स्निपेट का एक सैंपल दिया गया है.
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
का इस्तेमाल करने पर, आपके कोड से मेमोरी हैंग नहीं होनी चाहिए. इसका मतलब है कि इन मैक्रो का इस्तेमाल, लीक होने वाले किसी भी संसाधन को असाइन करने से पहले किया जाना चाहिए.