C++ की मदद से, Android पर LiteRT Compiled Model API चलाना

LiteRT के कंपाइल किए गए मॉडल के एपीआई, C++ में उपलब्ध हैं. इससे Android डेवलपर को मेमोरी के बंटवारे और लो-लेवल डेवलपमेंट पर बेहतर कंट्रोल मिलता है.

C++ में LiteRT ऐप्लिकेशन के उदाहरण के लिए, C++ के साथ एसिंक्रोनस सेगमेंटेशन का डेमो देखें.

शुरू करें

अपने Android ऐप्लिकेशन में LiteRT Compiled Model API जोड़ने के लिए, यह तरीका अपनाएं.

बिल्ड कॉन्फ़िगरेशन अपडेट करना

Bazel का इस्तेमाल करके, जीपीयू, एनपीयू, और सीपीयू की मदद से C++ ऐप्लिकेशन बनाने के लिए, cc_binary नियम तय करना ज़रूरी है. इससे यह पक्का किया जा सकेगा कि सभी ज़रूरी कॉम्पोनेंट कंपाइल, लिंक, और पैकेज किए गए हैं. नीचे दिए गए उदाहरण में, सेटअप करने का तरीका बताया गया है. इससे आपका ऐप्लिकेशन, जीपीयू, एनपीयू, और सीपीयू ऐक्सेलरेटर को डाइनैमिक तरीके से चुन सकता है या उनका इस्तेमाल कर सकता है.

Bazel के बिल्ड कॉन्फ़िगरेशन में ये मुख्य कॉम्पोनेंट शामिल होते हैं:

  • cc_binary नियम: यह Bazel का बुनियादी नियम है. इसका इस्तेमाल, C++ के एक्ज़ीक्यूटेबल टारगेट (जैसे, name = "your_application_name").
  • srcs एट्रिब्यूट: इस एट्रिब्यूट में, आपके ऐप्लिकेशन की C++ सोर्स फ़ाइलें शामिल होती हैं. उदाहरण के लिए, main.cc और अन्य .cc या .h फ़ाइलें).
  • data एट्रिब्यूट (रनटाइम डिपेंडेंसी): यह शेयर की गई लाइब्रेरी और ऐसेट को पैकेज करने के लिए ज़रूरी है जिन्हें आपका ऐप्लिकेशन रनटाइम में लोड करता है.
    • LiteRT Core Runtime: यह LiteRT C API की मुख्य शेयर की गई लाइब्रेरी है. उदाहरण के लिए, //litert/c:litert_runtime_c_api_shared_lib).
    • डिस्पैच लाइब्रेरी: ये वेंडर के हिसाब से शेयर की गई लाइब्रेरी होती हैं. इनका इस्तेमाल LiteRT, हार्डवेयर ड्राइवर (जैसे, //litert/vendors/qualcomm/dispatch:dispatch_api_so).
    • जीपीयू बैकएंड लाइब्रेरी: जीपीयू की मदद से तेज़ी से काम करने के लिए शेयर की गई लाइब्रेरी (जैसे, "@litert_gpu//:jni/arm64-v8a/libLiteRtGpuAccelerator.so).
    • एनपीयू बैकएंड लाइब्रेरी: एनपीयू की मदद से तेज़ी से काम करने के लिए, शेयर की गई खास लाइब्रेरी. जैसे, Qualcomm की QNN HTP लाइब्रेरी (उदाहरण के लिए, @qairt//:lib/aarch64-android/libQnnHtp.so, @qairt//:lib/hexagon-v79/unsigned/libQnnHtpV79Skel.so).
    • मॉडल फ़ाइलें और ऐसेट: आपकी ट्रेन की गई मॉडल फ़ाइलें, टेस्ट इमेज, शेडर या रनटाइम के दौरान ज़रूरी कोई अन्य डेटा (जैसे, :model_files, :shader_files).
  • deps एट्रिब्यूट (कंपाइल-टाइम डिपेंडेंसी): इसमें उन लाइब्रेरी की सूची होती है जिनके हिसाब से आपके कोड को कंपाइल करना होता है.
    • LiteRT API और यूटिलिटी: LiteRT के कॉम्पोनेंट के लिए हेडर और स्टैटिक लाइब्रेरी, जैसे कि टेंसर बफ़र (उदाहरण के लिए, //litert/cc:litert_tensor_buffer).
    • ग्राफ़िक्स लाइब्रेरी (जीपीयू के लिए): अगर जीपीयू ऐक्सेलरेटर इनका इस्तेमाल करता है, तो ग्राफ़िक्स एपीआई से जुड़ी डिपेंडेंसी (जैसे, gles_deps()).
  • linkopts एट्रिब्यूट: यह लिंकर को पास किए गए विकल्पों के बारे में बताता है.इनमें सिस्टम लाइब्रेरी के साथ लिंक करना शामिल हो सकता है. जैसे, -landroid या GLES लाइब्रेरी के साथ gles_linkopts()).

यहां cc_binary के नियम का एक उदाहरण दिया गया है:

cc_binary(
    name = "your_application",
    srcs = [
        "main.cc",
    ],
    data = [
        ...
        # litert c api shared library
        "//litert/c:litert_runtime_c_api_shared_lib",
        # GPU accelerator shared library
        "@litert_gpu//:jni/arm64-v8a/libLiteRtGpuAccelerator.so",
        # NPU accelerator shared library
        "//litert/vendors/qualcomm/dispatch:dispatch_api_so",
    ],
    linkopts = select({
        "@org_tensorflow//tensorflow:android": ["-landroid"],
        "//conditions:default": [],
    }) + gles_linkopts(), # gles link options
    deps = [
        ...
        "//litert/cc:litert_tensor_buffer", # litert cc library
        ...
    ] + gles_deps(), # gles dependencies
)

मॉडल लोड करना

LiteRT मॉडल पाने या किसी मॉडल को .tflite फ़ॉर्मैट में बदलने के बाद, Model ऑब्जेक्ट बनाकर मॉडल लोड करें.

LITERT_ASSIGN_OR_RETURN(auto model, Model::CreateFromFile("mymodel.tflite"));

एनवायरमेंट बनाना

Environment ऑब्जेक्ट, एक रनटाइम एनवायरमेंट उपलब्ध कराता है. इसमें कंपाइलर प्लगिन का पाथ और जीपीयू कॉन्टेक्स्ट जैसे कॉम्पोनेंट शामिल होते हैं. CompiledModel और TensorBuffer बनाते समय, Environment देना ज़रूरी है. नीचे दिया गया कोड, बिना किसी विकल्प के सीपीयू और जीपीयू पर एक्ज़ीक्यूशन के लिए Environment बनाता है:

LITERT_ASSIGN_OR_RETURN(auto env, Environment::Create({}));

कंपाइल किया गया मॉडल बनाना

CompiledModel एपीआई का इस्तेमाल करके, नए बनाए गए Model ऑब्जेक्ट के साथ रनटाइम शुरू करें. इस पॉइंट पर हार्डवेयर से तेज़ी लाने की सुविधा के बारे में बताया जा सकता है (kLiteRtHwAcceleratorCpu या kLiteRtHwAcceleratorGpu):

LITERT_ASSIGN_OR_RETURN(auto compiled_model,
  CompiledModel::Create(env, model, kLiteRtHwAcceleratorCpu));

इनपुट और आउटपुट बफ़र बनाना

इनपुट डेटा को सेव करने के लिए ज़रूरी डेटा स्ट्रक्चर (बफ़र) बनाएं. इस डेटा को मॉडल में इनफ़रेंस के लिए डाला जाएगा. साथ ही, आउटपुट डेटा को सेव करने के लिए ज़रूरी डेटा स्ट्रक्चर (बफ़र) बनाएं. यह डेटा, मॉडल में इनफ़रेंस चलाने के बाद जनरेट होगा.

LITERT_ASSIGN_OR_RETURN(auto input_buffers, compiled_model.CreateInputBuffers());
LITERT_ASSIGN_OR_RETURN(auto output_buffers, compiled_model.CreateOutputBuffers());

अगर सीपीयू मेमोरी का इस्तेमाल किया जा रहा है, तो पहले इनपुट बफ़र में सीधे डेटा लिखकर इनपुट भरें.

input_buffers[0].Write<float>(absl::MakeConstSpan(input_data, input_size));

मॉडल को चालू करना

इनपुट और आउटपुट बफ़र उपलब्ध कराएं. इसके बाद, पिछले चरणों में बताए गए मॉडल और हार्डवेयर की मदद से, कंपाइल किए गए मॉडल को चलाएं.

compiled_model.Run(input_buffers, output_buffers);

आउटपुट वापस पाना

मेमोरी से मॉडल के आउटपुट को सीधे तौर पर पढ़कर, आउटपुट वापस पाना.

std::vector<float> data(output_data_size);
output_buffers[0].Read<float>(absl::MakeSpan(data));
// ... process output data

मुख्य कॉन्सेप्ट और कॉम्पोनेंट

LiteRT Compiled Model API के मुख्य कॉन्सेप्ट और कॉम्पोनेंट के बारे में जानने के लिए, यहां दिए गए सेक्शन देखें.

गड़बड़ी ठीक करना

LiteRT, litert::Expected का इस्तेमाल करके वैल्यू दिखाता है या गड़बड़ियों को absl::StatusOr या std::expected की तरह ही आगे बढ़ाता है. गड़बड़ी की जांच मैन्युअल तरीके से की जा सकती है.

LiteRT, इस्तेमाल में आसानी के लिए ये मैक्रो उपलब्ध कराता है:

  • LITERT_ASSIGN_OR_RETURN(lhs, expr), expr के नतीजे को lhs को असाइन करता है. हालांकि, ऐसा तब होता है, जब expr से कोई गड़बड़ी न हो. अगर गड़बड़ी होती है, तो LITERT_ASSIGN_OR_RETURN(lhs, expr) गड़बड़ी दिखाता है.

    यह स्निपेट कुछ इस तरह दिखेगा.

    auto maybe_model = Model::CreateFromFile("mymodel.tflite");
    if (!maybe_model) {
      return maybe_model.Error();
    }
    auto model = std::move(maybe_model.Value());
    
  • LITERT_ASSIGN_OR_ABORT(lhs, expr) वही काम करता है जो LITERT_ASSIGN_OR_RETURN करता है, लेकिन गड़बड़ी होने पर प्रोग्राम बंद कर देता है.

  • अगर LITERT_RETURN_IF_ERROR(expr) का आकलन करने पर कोई गड़बड़ी होती है, तो LITERT_RETURN_IF_ERROR(expr) expr दिखाता है.

  • LITERT_ABORT_IF_ERROR(expr), LITERT_RETURN_IF_ERROR की तरह ही काम करता है. हालांकि, गड़बड़ी होने पर प्रोग्राम बंद हो जाता है.

LiteRT मैक्रो के बारे में ज़्यादा जानने के लिए, litert_macros.h देखें.

कंपाइल किया गया मॉडल (CompiledModel)

Compiled Model API (CompiledModel) का काम मॉडल को लोड करना, हार्डवेयर ऐक्सलरेशन लागू करना, रनटाइम को इंस्टैंशिएट करना, इनपुट और आउटपुट बफ़र बनाना, और अनुमान लगाना है.

नीचे दिए गए कोड स्निपेट में, यह दिखाया गया है कि कंपाइल किया गया मॉडल एपीआई, LiteRT मॉडल (.tflite) और टारगेट हार्डवेयर ऐक्सेलरेटर (जीपीयू) को कैसे लेता है. साथ ही, यह कंपाइल किया गया मॉडल बनाता है, जो अनुमान लगाने के लिए तैयार होता है.

// Load model and initialize runtime
LITERT_ASSIGN_OR_RETURN(auto model, Model::CreateFromFile("mymodel.tflite"));
LITERT_ASSIGN_OR_RETURN(auto env, Environment::Create({}));
LITERT_ASSIGN_OR_RETURN(auto compiled_model,
  CompiledModel::Create(env, model, kLiteRtHwAcceleratorCpu));

नीचे दिए गए कोड स्निपेट में बताया गया है कि Compiled Model API, इनपुट और आउटपुट बफ़र कैसे लेता है. साथ ही, कंपाइल किए गए मॉडल के साथ अनुमान कैसे लगाता है.

// Preallocate input/output buffers
LITERT_ASSIGN_OR_RETURN(auto input_buffers, compiled_model.CreateInputBuffers());
LITERT_ASSIGN_OR_RETURN(auto output_buffers, compiled_model.CreateOutputBuffers());

// Fill the first input
float input_values[] = { /* your data */ };
LITERT_RETURN_IF_ERROR(
  input_buffers[0].Write<float>(absl::MakeConstSpan(input_values, /*size*/)));

// Invoke
LITERT_RETURN_IF_ERROR(compiled_model.Run(input_buffers, output_buffers));

// Read the output
std::vector<float> data(output_data_size);
LITERT_RETURN_IF_ERROR(
  output_buffers[0].Read<float>(absl::MakeSpan(data)));

CompiledModel एपीआई को लागू करने के तरीके के बारे में ज़्यादा जानकारी के लिए, litert_compiled_model.h का सोर्स कोड देखें.

टेंसर बफ़र (TensorBuffer)

LiteRT, I/O बफ़र इंटरऑपरेबिलिटी के लिए पहले से मौजूद सहायता उपलब्ध कराता है. यह कंपाइल किए गए मॉडल में डेटा के फ़्लो को मैनेज करने के लिए, Tensor Buffer API (TensorBuffer) का इस्तेमाल करता है. Tensor Buffer API, सीपीयू मेमोरी को लॉक करने के साथ-साथ, लिखने (Write<T>()) और पढ़ने (Read<T>()) की सुविधा देता है.

TensorBuffer एपीआई को लागू करने के तरीके के बारे में ज़्यादा जानकारी पाने के लिए, litert_tensor_buffer.h का सोर्स कोड देखें.

क्वेरी मॉडल के इनपुट/आउटपुट की ज़रूरी शर्तें

आम तौर पर, हार्डवेयर ऐक्सलरेटर, Tensor Buffer (TensorBuffer) को असाइन करने की ज़रूरी शर्तें तय करता है. इनपुट और आउटपुट के लिए बफ़र में अलाइनमेंट, बफ़र स्ट्राइड, और मेमोरी टाइप से जुड़ी ज़रूरी शर्तें हो सकती हैं. इन ज़रूरी शर्तों को अपने-आप पूरा करने के लिए, CreateInputBuffers जैसे हेल्पर फ़ंक्शन का इस्तेमाल किया जा सकता है.

नीचे दिए गए कोड स्निपेट में, इनपुट डेटा के लिए बफ़र की ज़रूरी शर्तों को वापस पाने का तरीका बताया गया है:

LITERT_ASSIGN_OR_RETURN(auto reqs, compiled_model.GetInputBufferRequirements(signature_index, input_index));

TensorBufferRequirements एपीआई को लागू करने के तरीके के बारे में ज़्यादा जानकारी पाने के लिए, litert_tensor_buffer_requirements.h का सोर्स कोड देखें.

मैनेज किए गए टेंसर बफ़र (TensorBuffer) बनाना

यहां दिए गए कोड स्निपेट में, मैनेज किए गए Tensor Buffer बनाने का तरीका बताया गया है. इसमें TensorBuffer API, बफ़र असाइन करता है:

LITERT_ASSIGN_OR_RETURN(auto tensor_buffer_cpu,
TensorBuffer::CreateManaged(env, /*buffer_type=*/kLiteRtTensorBufferTypeHostMemory,
  ranked_tensor_type, buffer_size));

LITERT_ASSIGN_OR_RETURN(auto tensor_buffer_gl, TensorBuffer::CreateManaged(env,
  /*buffer_type=*/kLiteRtTensorBufferTypeGlBuffer, ranked_tensor_type, buffer_size));

LITERT_ASSIGN_OR_RETURN(auto tensor_buffer_ahwb, TensorBuffer::CreateManaged(env,
  /*buffer_type=*/kLiteRtTensorBufferTypeAhwb, ranked_tensor_type, buffer_size));

बिना कॉपी किए, Tensor Buffer बनाना

किसी मौजूदा बफ़र को Tensor बफ़र (बिना कॉपी किए) के तौर पर रैप करने के लिए, इस कोड स्निपेट का इस्तेमाल करें:

// Create a TensorBuffer from host memory
LITERT_ASSIGN_OR_RETURN(auto tensor_buffer_from_host,
  TensorBuffer::CreateFromHostMemory(env, ranked_tensor_type,
  ptr_to_host_memory, buffer_size));

// Create a TensorBuffer from GlBuffer
LITERT_ASSIGN_OR_RETURN(auto tensor_buffer_from_gl,
  TensorBuffer::CreateFromGlBuffer(env, ranked_tensor_type, gl_target, gl_id,
  size_bytes, offset));

// Create a TensorBuffer from AHardware Buffer
LITERT_ASSIGN_OR_RETURN(auto tensor_buffer_from_ahwb,
  TensorBuffer::CreateFromAhwb(env, ranked_tensor_type, ahardware_buffer, offset));

टेंसर बफ़र से पढ़ना और लिखना

यहां दिए गए स्निपेट में, इनपुट बफ़र से पढ़ने और आउटपुट बफ़र में लिखने का तरीका बताया गया है:

// Example of reading to input buffer:
std::vector<float> input_tensor_data = {1,2};
LITERT_ASSIGN_OR_RETURN(auto write_success,
  input_tensor_buffer.Write<float>(absl::MakeConstSpan(input_tensor_data)));
if(write_success){
  /* Continue after successful write... */
}

// Example of writing to output buffer:
std::vector<float> data(total_elements);
LITERT_ASSIGN_OR_RETURN(auto read_success,
  output_tensor_buffer.Read<float>(absl::MakeSpan(data)));
if(read_success){
  /* Continue after successful read */
}

ऐडवांस: खास हार्डवेयर बफ़र टाइप के लिए, ज़ीरो-कॉपी बफ़र इंटरऑप

कुछ बफ़र टाइप, जैसे कि AHardwareBuffer, अन्य बफ़र टाइप के साथ इंटरऑपरेबिलिटी (दूसरे सिस्टम के साथ काम करना) की अनुमति देते हैं. उदाहरण के लिए, किसी AHardwareBuffer से OpenGL बफ़र बनाया जा सकता है. इसके लिए, कॉपी करने की ज़रूरत नहीं होती. यहां दिए गए कोड-स्निपेट में एक उदाहरण दिखाया गया है:

LITERT_ASSIGN_OR_RETURN(auto tensor_buffer_ahwb,
  TensorBuffer::CreateManaged(env, kLiteRtTensorBufferTypeAhwb,
  ranked_tensor_type, buffer_size));
// Buffer interop: Get OpenGL buffer from AHWB,
// internally creating an OpenGL buffer backed by AHWB memory.
LITERT_ASSIGN_OR_RETURN(auto gl_buffer, tensor_buffer_ahwb.GetGlBuffer());

OpenCL बफ़र, AHardwareBuffer से भी बनाए जा सकते हैं:

LITERT_ASSIGN_OR_RETURN(auto cl_buffer, tensor_buffer_ahwb.GetOpenClMemory());

OpenCL और OpenGL के बीच इंटरऑपरेबिलिटी की सुविधा देने वाले मोबाइल डिवाइसों पर, GL बफ़र से CL बफ़र बनाए जा सकते हैं:

LITERT_ASSIGN_OR_RETURN(auto tensor_buffer_from_gl,
  TensorBuffer::CreateFromGlBuffer(env, ranked_tensor_type, gl_target, gl_id,
  size_bytes, offset));

// Creates an OpenCL buffer from the OpenGL buffer, zero-copy.
LITERT_ASSIGN_OR_RETURN(auto cl_buffer, tensor_buffer_from_gl.GetOpenClMemory());

लागू करने का उदाहरण

C++ में LiteRT को लागू करने के ये तरीके देखें.

बुनियादी अनुमान (सीपीयू)

यहां शुरू करें सेक्शन के कोड स्निपेट का छोटा वर्शन दिया गया है. यह LiteRT के साथ अनुमान लगाने की सुविधा को लागू करने का सबसे आसान तरीका है.

// Load model and initialize runtime
LITERT_ASSIGN_OR_RETURN(auto model, Model::CreateFromFile("mymodel.tflite"));
LITERT_ASSIGN_OR_RETURN(auto env, Environment::Create({}));
LITERT_ASSIGN_OR_RETURN(auto compiled_model, CompiledModel::Create(env, model,
  kLiteRtHwAcceleratorCpu));

// Preallocate input/output buffers
LITERT_ASSIGN_OR_RETURN(auto input_buffers, compiled_model.CreateInputBuffers());
LITERT_ASSIGN_OR_RETURN(auto output_buffers, compiled_model.CreateOutputBuffers());

// Fill the first input
float input_values[] = { /* your data */ };
input_buffers[0].Write<float>(absl::MakeConstSpan(input_values, /*size*/));

// Invoke
compiled_model.Run(input_buffers, output_buffers);

// Read the output
std::vector<float> data(output_data_size);
output_buffers[0].Read<float>(absl::MakeSpan(data));

होस्ट मेमोरी के साथ ज़ीरो-कॉपी

LiteRT Compiled Model API, अनुमान लगाने वाली पाइपलाइन की जटिलता को कम करता है. खास तौर पर, जब कई हार्डवेयर बैकएंड और ज़ीरो-कॉपी फ़्लो का इस्तेमाल किया जा रहा हो. इनपुट बफ़र बनाते समय, यहां दिए गए कोड स्निपेट में CreateFromHostMemory तरीके का इस्तेमाल किया गया है. यह होस्ट मेमोरी के साथ ज़ीरो-कॉपी का इस्तेमाल करता है.

// Define an LiteRT environment to use existing EGL display and context.
const std::vector<Environment::Option> environment_options = {
   {OptionTag::EglDisplay, user_egl_display},
   {OptionTag::EglContext, user_egl_context}};
LITERT_ASSIGN_OR_RETURN(auto env,
   Environment::Create(absl::MakeConstSpan(environment_options)));

// Load model1 and initialize runtime.
LITERT_ASSIGN_OR_RETURN(auto model1, Model::CreateFromFile("model1.tflite"));
LITERT_ASSIGN_OR_RETURN(auto compiled_model1, CompiledModel::Create(env, model1, kLiteRtHwAcceleratorGpu));

// Prepare I/O buffers. opengl_buffer is given outside from the producer.
LITERT_ASSIGN_OR_RETURN(auto tensor_type, model.GetInputTensorType("input_name0"));
// Create an input TensorBuffer based on tensor_type that wraps the given OpenGL Buffer.
LITERT_ASSIGN_OR_RETURN(auto tensor_buffer_from_opengl,
    litert::TensorBuffer::CreateFromGlBuffer(env, tensor_type, opengl_buffer));

// Create an input event and attach it to the input buffer. Internally, it creates
// and inserts a fence sync object into the current EGL command queue.
LITERT_ASSIGN_OR_RETURN(auto input_event, Event::CreateManaged(env, LiteRtEventTypeEglSyncFence));
tensor_buffer_from_opengl.SetEvent(std::move(input_event));

std::vector<TensorBuffer> input_buffers;
input_buffers.push_back(std::move(tensor_buffer_from_opengl));

// Create an output TensorBuffer of the model1. It's also used as an input of the model2.
LITERT_ASSIGN_OR_RETURN(auto intermedidate_buffers,  compiled_model1.CreateOutputBuffers());

// Load model2 and initialize runtime.
LITERT_ASSIGN_OR_RETURN(auto model2, Model::CreateFromFile("model2.tflite"));
LITERT_ASSIGN_OR_RETURN(auto compiled_model2, CompiledModel::Create(env, model2, kLiteRtHwAcceleratorGpu));
LITERT_ASSIGN_OR_RETURN(auto output_buffers, compiled_model2.CreateOutputBuffers());

compiled_model1.RunAsync(input_buffers, intermedidate_buffers);
compiled_model2.RunAsync(intermedidate_buffers, output_buffers);