मुझे कंपाइलर प्लगिन कब बनाना चाहिए?
जब आपको LiteRT फ़्रेमवर्क में, कंपाइलर डिपेंडेंसी के साथ किसी खास हार्डवेयर ऐक्सलरेटर को इंटिग्रेट करना हो, तब LiteRT कंपाइलर प्लगिन ज़रूरी होता है.
आपको कंपाइलर प्लगिन तब बनाना चाहिए, जब:
- आपने ऐसे नए हार्डवेयर बैकएंड को टारगेट किया है जो काम नहीं करता.
- आपको परफ़ॉर्मेंस या पावर की बचत के लिए, मॉडल के कुछ ऑपरेशनों को उस हार्डवेयर ऐक्सलरेटर पर ट्रांसफ़र करना है.
- आपको वर्कस्टेशन पर एओटी कंपाइलेशन या डिवाइस पर कंपाइलेशन की सुविधा की ज़रूरत होगी.
यह प्लगिन एक ब्रिज की तरह काम करता है. यह मशीन लर्निंग मॉडल के कुछ हिस्सों को लेता है और उन्हें ऐसे फ़ॉर्मैट में बदलता है जिसे आपका टारगेट हार्डवेयर एक्ज़ीक्यूट कर सकता है. इसके लिए, यह बैकएंड के कंपाइलर को कॉल करता है. LiteRT, प्लगिन से जनरेट किए गए कस्टम बाइटकोड को .tflite मॉडल में बंडल करता है. इससे LiteRT रनटाइम का इस्तेमाल करके, इसे एक्ज़ीक्यूट किया जा सकता है.
कंपाइलर प्लगिन कैसे काम करते हैं?
LiteRT फ़्रेमवर्क, मॉडल लोड करने या ऑफ़लाइन प्री-प्रोसेसिंग फ़ेज़ के दौरान कंपाइलर प्लगिन का इस्तेमाल करता है. इससे, टारगेट हार्डवेयर पर एक्ज़ीक्यूट करने के लिए मॉडल सबग्राफ़ की पहचान की जा सकती है और उन्हें तैयार किया जा सकता है.
इस प्रोसेस में दो मुख्य चरण शामिल होते हैं. इन्हें फ़्रेमवर्क मैनेज करता है. इसके लिए, प्लगिन के एक्सपोर्ट किए गए फ़ंक्शन का इस्तेमाल किया जाता है:
- पार्टिशनिंग: यह प्लगिन, पूरे मॉडल ग्राफ़ की जांच करता है. साथ ही, उन ऑपरेशनों के सबसेट की पहचान करता है जिन्हें यह प्लगिन सपोर्ट करता है और टारगेट हार्डवेयर पर बेहतर तरीके से तेज़ी ला सकता है. इन सबग्राफ़ को कंपाइल करने और आउटलाइन करने के लिए, "पार्टिशन" (मार्क) किया जाता है.
- कंपाइलेशन: LiteRT फ़्रेमवर्क, बांटे गए सबग्राफ़ को वापस प्लगिन पर भेजता है. इसके बाद, प्लगिन अपने इंटरनल लॉजिक और शायद बाहरी टूलचेन (कंपाइलर) का इस्तेमाल करके, एक या उससे ज़्यादा हार्डवेयर के हिसाब से बाइटकोड मॉड्यूल जनरेट करता है. ये मॉड्यूल, पार्टीशन लागू करते हैं. यह बाइटकोड, टारगेट हार्डवेयर के रनटाइम (HAL/ड्राइवर) को लोड और एक्ज़ीक्यूट करेगा.
यह फ़्रेमवर्क, ओरिजनल सबग्राफ़ को कस्टम कार्रवाइयों से बदल देता है. ये कार्रवाइयां, हार्डवेयर ड्राइवर को शुरू करती हैं. साथ ही, प्लगिन से बनाए गए कंपाइल किए गए बाइटकोड को पास करती हैं.
LiteRT Dispatch, कंपाइलर प्लगिन के लिए रनटाइम ऐनलॉग है. ये कंपाइलर के दिए गए आउटपुट के आधार पर, HAL को कॉल करने का तरीका बताते हैं. ज़्यादा जानकारी के लिए, डिस्पैच का दस्तावेज़ देखें.
AOT बनाम उपयोगकर्ता के डिवाइस पर
LiteRT, कंपाइलर प्लगिन का इस्तेमाल करके, हमारे टूलिंग के ज़रिए एओटी कंपाइलेशन के साथ-साथ डिवाइस पर कंपाइलेशन की सुविधा भी देता है. डिवाइस पर कंपाइलेशन ज़्यादा फ़्लेक्सिबल होता है. यह LiteRT रनटाइम एपीआई में पूरी तरह से शामिल होता है. इसके लिए, सिर्फ़ एक मॉडल को मैनेज करने की ज़रूरत होती है. जब डिवाइस पर कंपाइल करने के लिए बहुत ज़्यादा संसाधनों की ज़रूरत होती है, तब एओटी फ़्लो कंपाइलेशन को अनब्लॉक कर सकता है. ऐसा कई बड़े मॉडल के साथ हो सकता है.
फ़ॉलबैक
LiteRT को अलग-अलग तरह के ग्राफ़ के साथ काम करने के लिए बनाया गया है. प्लगिन की ओर से नहीं चुनी गई किसी भी कार्रवाई को सीपीयू पर छोड़ दिया जाएगा या किसी अन्य बैकएंड पर तेज़ी से प्रोसेस करने के लिए उपलब्ध कराया जाएगा.
कंपाइलर प्लगइन लागू करना
LiteRT कंपाइलर प्लगिन को शेयर की गई लाइब्रेरी के तौर पर लागू किया जाता है. यह LiteRT C API में तय किए गए C फ़ंक्शन के किसी खास सेट को एक्सपोर्ट करता है.
ज़रूरी इंटरफ़ेस फ़ंक्शन
मुख्य फ़ंक्शन, कंपाइल करने के दो मुख्य चरणों के इर्द-गिर्द घूमता है:
LiteRtCompilerPluginPartition और LiteRtCompilerPluginCompile.
| फ़ंक्शन | मकसद |
|---|---|
| LiteRtCompilerPluginPartition | यह दिए गए मॉडल सबग्राफ़ (Partition चरण) में, काम करने वाली सभी कार्रवाइयों को चुनता है और उन्हें मार्क करता है. |
| LiteRtCompilerPluginCompile$ | यह पहले से चुने गए पार्टीशन के लिए, हार्डवेयर के हिसाब से बाइटकोड जनरेट करता है (कंपाइल करें चरण). |
C API स्निपेट
// Name associated with the manufacturer this plugin relates to.
LITERT_CAPI_EXPORT const char* LiteRtGetCompilerPluginSocManufacturer();
// Create and initialize the plugin instance.
LITERT_CAPI_EXPORT LiteRtStatus
LiteRtCreateCompilerPlugin(LiteRtCompilerPlugin* compiler_plugin,
LiteRtEnvironmentOptions env, LiteRtOptions options);
// Choose ops for compilation.
// This is the PARTITION step.
LITERT_CAPI_EXPORT LiteRtStatus LiteRtCompilerPluginPartition(
LiteRtCompilerPlugin compiler_plugin, const char* soc_model,
LiteRtSubgraph subgraph, LiteRtOpList selected_ops);
// Prepare result to pass to the runtime for given model containing partitioned
// subgraphs. This is the COMPILE step.
LITERT_CAPI_EXPORT LiteRtStatus LiteRtCompilerPluginCompile(
LiteRtCompilerPlugin compiler_plugin, const char* soc_model,
LiteRtModel partitions, LiteRtCompiledResult* compiled_result);
1. पार्टिशन फ़ंक्शन
फ़ंक्शन का सिग्नेचर यह है:
LITERT_CAPI_EXPORT LiteRtStatus LiteRtCompilerPluginPartition(
LiteRtCompilerPlugin compiler_plugin, const char* soc_model,
LiteRtSubgraph subgraph, LiteRtOpList selected_ops);
partition फ़ंक्शन क्या करता है: यह चुना गया फ़ेज़ है. यह प्लगिन, इनपुट LiteRtSubgraph में मौजूद कार्रवाइयों को दोहराता है. टारगेट किए गए हर उस हार्डवेयर के लिए जो किसी ऑपरेशन को सपोर्ट करता है और उसे तेज़ कर सकता है, प्लगिन selected_ops पैरामीटर में दिए गए LiteRtOpList$में उस ऑपरेशन को जोड़ता है. LiteRt फ़्रेमवर्क इस सूची का इस्तेमाल, उन पार्टीशन की सीमाओं को तय करने के लिए करता है जिन्हें फ़ाइनल कंपाइलेशन के चरण के लिए भेजा जाएगा.
डिफ़ॉल्ट रूप से, LiteRT चुने गए सभी ऑप्स को सबसे बड़े सब-डीएजी में ग्रुप करेगा. ज़्यादा सटीक तरीके से पार्टीशन करने के लिए, इंडेक्स को तब जोड़ा जा सकता है, जब ऐसे ऑप्स चुने जा रहे हों जो इन सबग्राफ़ को और ज़्यादा हिस्सों में बांटते हों.
2. कंपाइल फ़ंक्शन
फ़ंक्शन का सिग्नेचर यह है:
LITERT_CAPI_EXPORT LiteRtStatus LiteRtCompilerPluginCompile(
LiteRtCompilerPlugin compiler_plugin, const char* soc_model,
LiteRtModel partitions, LiteRtCompiledResult* compiled_result);
compile फ़ंक्शन क्या करता है: यह जनरेशन फ़ेज़ है. इनपुट partitions एक ऐसा मॉडल है जिसमें चुने गए सभी सबग्राफ़ को अलग कर दिया गया है. प्लगिन इन पार्टिशन को प्रोसेस करता है. साथ ही, टारगेट हार्डवेयर के लिए बाइटकोड जनरेट करने के लिए, अपने खास टूलचेन को शुरू करता है. यह उम्मीद की जाती है कि प्लगिन का आउटपुट, कंपाइल करने के लिए पास किए गए हर सबग्राफ़ के लिए एक एंट्री पॉइंट उपलब्ध कराता है. ज़्यादातर मामलों में, यह हर इनपुट सबग्राफ़ के लिए अलग-अलग बाइट कोड मॉड्यूल होता है या एक से ज़्यादा एंट्री पॉइंट वाला एक बाइट कोड मॉड्यूल होता है.
compile से मिले डेटा का टाइप: LiteRtCompilerPluginCompile फ़ंक्शन, आउट-पैरामीटर LiteRtCompiledResult का इस्तेमाल करके अपना आउटपुट दिखाता है.
LiteRtCompiledResult, प्लगिन के ज़रिए मैनेज किए गए स्ट्रक्चर का ओपेक (LiteRT के हिसाब से) हैंडल है. यह कंपाइलेशन का आउटपुट दिखाता है. इसमें दो मुख्य तरह की जानकारी होती है:
- बाइट कोड मॉड्यूल: एक या उससे ज़्यादा रॉ मेमोरी बफ़र, जिनमें हार्डवेयर के हिसाब से काम करने वाला एक्ज़ीक्यूटेबल बाइटकोड (यानी कि कंपाइल किए गए निर्देश) शामिल होते हैं.
- कॉल की जानकारी: हर पार्टीशन के लिए मेटाडेटा. इससे,
iवें इनपुट सबग्राफ़ से लेकर नतीजे के बाइट कोड मॉड्यूल और उस मॉड्यूल में एंट्री पॉइंट आइडेंटिफ़ायर तक की मैपिंग मिलती है.
लागू करने का उदाहरण
यहां दिए गए स्निपेट से पता चलता है कि कोई बुनियादी प्लगिन, मुख्य फ़ंक्शन को कैसे लागू कर सकता है. यह उदाहरण, litert/vendors/examples/ में दिए गए पूरी तरह से काम करने वाले उदाहरण से लिया गया है
प्लगिन की पहचान करना और उसे सेटअप करना
ये फ़ंक्शन, फ़्रेमवर्क को प्लगिन और हार्डवेयर के बारे में बुनियादी जानकारी देते हैं.
// Define the plugin's internal state structure
struct LiteRtCompilerPluginT {};
// Identify the manufacturer
const char* LiteRtGetCompilerPluginSocManufacturer() {
return "AcmeCorp"; // Example manufacturer name
}
// Specify the supported hardware (in this example, it supports kLiteRtHwAcceleratorNpu)
LiteRtStatus LiteRtGetCompilerPluginSupportedHardware(
LiteRtCompilerPlugin compiler_plugin,
LiteRtHwAccelerators* supported_hardware) {
// ... argument checking ...
*supported_hardware = kLiteRtHwAcceleratorNpu;
return kLiteRtStatusOk;
}
पार्टिशनिंग लॉजिक (LiteRtCompilerPluginPartition)
इस उदाहरण में दिखाया गया है कि प्लगिन, सिर्फ़ तब सीमित संख्या में कार्रवाइयां (mul, sub, और एक खास कंपोज़िट ऑप) चुनता है, जब सभी इनपुट और आउटपुट 32 बिट फ़्लोट हों. आम तौर पर, यह तय करने के लिए कि किसी ऑपरेशन को चुना जाना चाहिए या नहीं, बैकएंड के कंपाइलर टूलचेन में पुष्टि करने वाले हुक को कॉल किया जाता है.
LiteRtStatus LiteRtCompilerPluginPartition(LiteRtCompilerPlugin compiler_plugin,
const char* soc_model,
LiteRtSubgraph subgraph,
LiteRtOpList selected_ops) {
// Iterate over ops and check criteria for selection
// (using a C++ wrapper namespace '::litert' for convenience).
// `subgraph` is a single subgraph from the original model, as such
// this function will be called for each subgraph in the original model.
::litert::Subgraph main_subgraph(subgraph);
for (const auto& op : main_subgraph.Ops()) {
// 1. Check a constraint: require all tensors to be Float32
bool only_f32 = true;
// ... logic to check input/output types ...
if (!only_f32) {
continue;
}
// 2. Check op codes and push to selected_ops list
if (op.Code() == kLiteRtOpCodeTflMul) {
LITERT_RETURN_IF_ERROR(LiteRtPushOp(selected_ops, op.Get(), 0));
} else if (op.Code() == kLiteRtOpCodeTflSub) {
LITERT_RETURN_IF_ERROR(LiteRtPushOp(selected_ops, op.Get(), 0));
} else if (op.Code() == kLiteRtOpCodeShloComposite) {
// Example of checking composite op options
// ... logic to check for "odml.rms_norm" name ...
LITERT_RETURN_IF_ERROR(LiteRtPushOp(selected_ops, op.Get(), 0));
}
}
return kLiteRtStatusOk;
}
कंपाइलेशन को कॉल करने से पहले, LiteRT चुने गए सभी ऑपरेशंस की पुष्टि करेगा और उन्हें नए इंटरमीडिएट मॉडल में नए सबग्राफ़ में "आउटलाइन" करेगा. इस इंटरमीडिएट मॉडल को कंपाइलेशन के लिए पास किया जाता है.
कंपाइलेशन लॉजिक (LiteRtCompilerPluginCompile)
यह फ़ंक्शन, बांटे गए सबग्राफ़ लेता है और एक कस्टम LiteRtCompiledResult जनरेट करता है. इस उदाहरण में, कंपाइल किए जाने वाले हर पार्टीशन के लिए, स्टैंडअलोन बाइटकोड मॉड्यूल जनरेट किया जाता है. असल मामलों में, इसमें आम तौर पर LiteRT ऑप्स को बैकएंड कंपाइलर लाइब्रेरी के टाइप में बदलना शामिल होता है. फ़ंक्शनल उदाहरण वाले प्लगिन का "कंपाइलेशन" फ़ंक्शन, ऐसी स्ट्रिंग बनाता है जिसे कोई भी व्यक्ति आसानी से पढ़ सकता है. यह स्ट्रिंग, ग्राफ़ को कोड में बदलती है.
// Internal structure defining the compiled output
struct LiteRtCompiledResultT {
std::vector<std::string> byte_code; // The hardware bytecode buffers
std::vector<std::string> per_op_data; // Per-call metadata (CallInfo)
};
LiteRtStatus LiteRtCompilerPluginCompile(
LiteRtCompilerPlugin compiler_plugin, const char* soc_model,
LiteRtModel partitions, LiteRtCompiledResult* compiled_result) {
// 1. Create the internal result structure
auto model = litert::Model::CreateFromNonOwnedHandle(partitions);
const auto num_partitions = model.NumSubgraphs();
auto result = std::make_unique<LiteRtCompiledResultT>();
result->byte_code.resize(num_partitions);
result->per_op_data.resize(num_partitions);
// 2. Iterate and compile each partition
for (auto i = 0; i < num_partitions; ++i) {
// CompileSinglePartition is an internal helper that converts the subgraph
// into the target hardware's format and stores it in result->byte_code.
// In the case of the example this is just a stringification of the graph.
// ... internal call to CompileSinglePartition ...
// Example: result.byte_code[i] = generated_hw_code;
// Example: result.per_op_data[i] = absl::StrFormat("Partition_%d", i);
// The "per_op_data" is a unique identifier associated to the `ith` partition.
// This is analogous to the name of a function in a library.
// This is only meaningful when the plugin is preparing single modules with multiple entry points.
}
// 3. Pass ownership of the result back to the framework
*compiled_result = result.release();
return kLiteRtStatusOk;
}
// Functions to expose the compiled result data to the framework
LiteRtStatus LiteRtGetCompiledResultByteCode(
LiteRtCompiledResult compiled_result, LiteRtParamIndex byte_code_idx,
const void** byte_code, size_t* byte_code_size) {
// ... implementation reads from compiled_result->byte_code ...
}
// ... other LiteRtGetCompiledResult* functions ...
इस्तेमाल और पुष्टि करना
LiteRT, मॉडल फ़ाइलों पर कंपाइलर प्लगिन लागू करने, नतीजे को लागू करने, और पुष्टि करने/बेंचमार्किंग के लिए कई टूल उपलब्ध कराता है. ऐक्सलरेटर टेस्ट सुइट के दस्तावेज़ और बेंचमार्किंग और प्रोफ़ाइलिंग के दस्तावेज़ देखें.