C++ দিয়ে অ্যান্ড্রয়েডে LiterRT কম্পাইলড মডেল API চালান

LiterRT কম্পাইলড মডেল API গুলি C++ এ উপলব্ধ, যা অ্যান্ড্রয়েড ডেভেলপারদের মেমরি বরাদ্দ এবং নিম্ন-স্তরের ডেভেলপমেন্টের উপর সূক্ষ্ম নিয়ন্ত্রণ প্রদান করে।

C++ এ LiterRT অ্যাপ্লিকেশনের উদাহরণের জন্য, C++ ডেমো সহ অ্যাসিঙ্ক্রোনাস সেগমেন্টেশন দেখুন।

শুরু করুন

আপনার অ্যান্ড্রয়েড অ্যাপ্লিকেশনে LiterRT কম্পাইলড মডেল API যোগ করতে নিম্নলিখিত ধাপগুলি অনুসরণ করুন।

বিল্ড কনফিগারেশন আপডেট করুন

Bazel ব্যবহার করে GPU, NPU এবং CPU ত্বরণের জন্য LiterRT দিয়ে একটি C++ অ্যাপ্লিকেশন তৈরি করার জন্য একটি cc_binary নিয়ম সংজ্ঞায়িত করা প্রয়োজন যাতে সমস্ত প্রয়োজনীয় উপাদান কম্পাইল, লিঙ্ক এবং প্যাকেজ করা হয়। নিম্নলিখিত উদাহরণ সেটআপটি আপনার অ্যাপ্লিকেশনকে গতিশীলভাবে GPU, NPU এবং CPU ত্বরণকারী নির্বাচন বা ব্যবহার করতে দেয়।

আপনার Bazel বিল্ড কনফিগারেশনের মূল উপাদানগুলি এখানে দেওয়া হল:

  • cc_binary নিয়ম: এটি আপনার C++ এক্সিকিউটেবল টার্গেট (যেমন, name = "your_application_name" ) সংজ্ঞায়িত করতে ব্যবহৃত মৌলিক Bazel নিয়ম।
  • srcs অ্যাট্রিবিউট: আপনার অ্যাপ্লিকেশনের C++ সোর্স ফাইলগুলি (যেমন, main.cc , এবং অন্যান্য .cc বা .h ফাইল) তালিকাভুক্ত করে।
  • data অ্যাট্রিবিউট (রানটাইম ডিপেন্ডেন্সি): রানটাইমে আপনার অ্যাপ্লিকেশন লোড হওয়া শেয়ার্ড লাইব্রেরি এবং সম্পদের প্যাকেজিংয়ের জন্য এটি অত্যন্ত গুরুত্বপূর্ণ।
    • LiteRT কোর রানটাইম: প্রধান LiteRT C API ভাগ করা লাইব্রেরি (যেমন, //litert/c:litert_runtime_c_api_shared_lib )।
    • ডিসপ্যাচ লাইব্রেরি: বিক্রেতা-নির্দিষ্ট শেয়ার্ড লাইব্রেরি যা LiterRT হার্ডওয়্যার ড্রাইভারের সাথে যোগাযোগ করতে ব্যবহার করে (যেমন, //litert/vendors/qualcomm/dispatch:dispatch_api_so )।
    • GPU ব্যাকএন্ড লাইব্রেরি: GPU ত্বরণের জন্য শেয়ার্ড লাইব্রেরি (যেমন, "@litert_gpu//:jni/arm64-v8a/libLiteRtGpuAccelerator.so )।
    • NPU ব্যাকএন্ড লাইব্রেরি: NPU ত্বরণের জন্য নির্দিষ্ট শেয়ার্ড লাইব্রেরি, যেমন Qualcomm এর QNN HTP লাইব্রেরি (যেমন, @qairt//:lib/aarch64-android/libQnnHtp.so , @qairt//:lib/hexagon-v79/unsigned/libQnnHtpV79Skel.so )।
    • মডেল ফাইল এবং সম্পদ: আপনার প্রশিক্ষিত মডেল ফাইল, পরীক্ষার ছবি, শেডার, অথবা রানটাইমের সময় প্রয়োজনীয় অন্য যেকোনো ডেটা (যেমন, :model_files , :shader_files )।
  • deps অ্যাট্রিবিউট (কম্পাইল-টাইম ডিপেন্ডেন্সি): এটি আপনার কোডের জন্য প্রয়োজনীয় লাইব্রেরিগুলির তালিকা তৈরি করে।
    • LiterRT API এবং ইউটিলিটি: টেনসর বাফারের মতো LiterRT উপাদানগুলির জন্য হেডার এবং স্ট্যাটিক লাইব্রেরি (যেমন, //litert/cc:litert_tensor_buffer )।
    • গ্রাফিক্স লাইব্রেরি (GPU-এর জন্য): গ্রাফিক্স API-এর সাথে সম্পর্কিত নির্ভরতা যদি GPU অ্যাক্সিলারেটর সেগুলি ব্যবহার করে (যেমন, gles_deps() )।
  • linkopts অ্যাট্রিবিউট: লিঙ্কারে পাঠানো বিকল্পগুলি নির্দিষ্ট করে, যার মধ্যে সিস্টেম লাইব্রেরির সাথে লিঙ্কিং অন্তর্ভুক্ত থাকতে পারে (যেমন, অ্যান্ড্রয়েড বিল্ডের জন্য -landroid , অথবা gles_linkopts() সহ GLES লাইব্রেরি)।

নিচে 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
)

মডেলটি লোড করুন

একটি LiterRT মডেল পাওয়ার পর, অথবা একটি মডেলকে .tflite ফর্ম্যাটে রূপান্তর করার পর, একটি Model অবজেক্ট তৈরি করে মডেলটি লোড করুন।

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

পরিবেশ তৈরি করুন

Environment অবজেক্ট একটি রানটাইম এনভায়রনমেন্ট প্রদান করে যার মধ্যে কম্পাইলার প্লাগইনের পাথ এবং GPU কনটেক্সটের মতো উপাদান অন্তর্ভুক্ত থাকে। CompiledModel এবং TensorBuffer তৈরি করার সময় Environment প্রয়োজন। নিম্নলিখিত কোডটি কোনও বিকল্প ছাড়াই CPU এবং GPU এক্সিকিউশনের জন্য একটি Environment তৈরি করে:

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

কম্পাইল করা মডেল তৈরি করুন

CompiledModel API ব্যবহার করে, নতুন তৈরি Model অবজেক্ট দিয়ে রানটাইম শুরু করুন। আপনি এই বিন্দুতে হার্ডওয়্যার ত্বরণ নির্দিষ্ট করতে পারেন ( kLiteRtHwAcceleratorCpu or 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());

যদি আপনি CPU মেমোরি ব্যবহার করেন, তাহলে প্রথম ইনপুট বাফারে সরাসরি ডেটা লিখে ইনপুটগুলি পূরণ করুন।

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

মূল ধারণা এবং উপাদান

LiterRT কম্পাইলড মডেল API-এর মূল ধারণা এবং উপাদান সম্পর্কে তথ্যের জন্য নিম্নলিখিত বিভাগগুলি দেখুন।

ত্রুটি পরিচালনা

LiterRT litert::Expected ব্যবহার করে absl::StatusOr অথবা std::expected এর মতো একইভাবে মান ফেরত পাঠাতে অথবা ত্রুটি প্রচার করতে পারে। আপনি নিজে নিজে ত্রুটি পরীক্ষা করতে পারেন।

সুবিধার জন্য, LiterRT নিম্নলিখিত ম্যাক্রোগুলি প্রদান করে:

  • LITERT_ASSIGN_OR_RETURN(lhs, expr) expr এর ফলাফল lhs কে বরাদ্দ করে যদি এটি কোনও ত্রুটি তৈরি না করে এবং অন্যথায় ত্রুটিটি ফেরত দেয়।

    এটি নিম্নলিখিত স্নিপেটের মতো কিছুতে প্রসারিত হবে।

    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) এর মূল্যায়নে ত্রুটি দেখা দিলে expr প্রদান করে।

  • LITERT_ABORT_IF_ERROR(expr) LITERT_RETURN_IF_ERROR এর মতোই কাজ করে কিন্তু ত্রুটির ক্ষেত্রে প্রোগ্রামটি বাতিল করে দেয়।

LiteRT ম্যাক্রো সম্পর্কে আরও তথ্যের জন্য, litert_macros.h দেখুন।

কম্পাইলড মডেল (কম্পাইলডমডেল)

কম্পাইলড মডেল এপিআই ( CompiledModel ) একটি মডেল লোড করা, হার্ডওয়্যার অ্যাক্সিলারেশন প্রয়োগ করা, রানটাইম ইনস্ট্যান্টিয়েট করা, ইনপুট এবং আউটপুট বাফার তৈরি করা এবং ইনফারেন্স চালানোর জন্য দায়ী।

নিম্নলিখিত সরলীকৃত কোড স্নিপেটটি দেখায় যে কীভাবে কম্পাইলড মডেল API একটি LiterRT মডেল ( .tflite ) এবং টার্গেট হার্ডওয়্যার অ্যাক্সিলারেটর (GPU) গ্রহণ করে এবং একটি কম্পাইলড মডেল তৈরি করে যা ইনফারেন্স চালানোর জন্য প্রস্তুত।

// 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 */ };
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 API কীভাবে বাস্তবায়িত হয় তার আরও সম্পূর্ণ ধারণার জন্য, littert_compiled_model.h এর সোর্স কোডটি দেখুন।

টেনসর বাফার (টেনসরবাফার)

LiterRT I/O বাফার ইন্টারঅপারেবিলিটির জন্য অন্তর্নির্মিত সমর্থন প্রদান করে, যা সংকলিত মডেলের ভিতরে এবং বাইরে ডেটা প্রবাহ পরিচালনা করার জন্য Tensor Buffer API ( TensorBuffer ) ব্যবহার করে। Tensor Buffer API ( Write<T>() ) লেখা এবং পড়ার ( Read<T>() ) ক্ষমতা প্রদান করে এবং CPU মেমরি লক করে।

TensorBuffer API কীভাবে বাস্তবায়িত হয় তার আরও সম্পূর্ণ দৃশ্যের জন্য, littert_tensor_buffer.h এর সোর্স কোডটি দেখুন।

কোয়েরি মডেল ইনপুট/আউটপুট প্রয়োজনীয়তা

একটি টেনসর বাফার ( TensorBuffer ) বরাদ্দ করার জন্য প্রয়োজনীয়তাগুলি সাধারণত হার্ডওয়্যার অ্যাক্সিলারেটর দ্বারা নির্দিষ্ট করা হয়। ইনপুট এবং আউটপুটের জন্য বাফারগুলির অ্যালাইনমেন্ট, বাফার স্ট্রাইড এবং মেমরির ধরণ সম্পর্কিত প্রয়োজনীয়তা থাকতে পারে। আপনি এই প্রয়োজনীয়তাগুলি স্বয়ংক্রিয়ভাবে পরিচালনা করতে CreateInputBuffers এর মতো সহায়ক ফাংশন ব্যবহার করতে পারেন।

নিম্নলিখিত সরলীকৃত কোড স্নিপেটটি দেখায় যে আপনি কীভাবে ইনপুট ডেটার জন্য বাফার প্রয়োজনীয়তাগুলি পুনরুদ্ধার করতে পারেন:

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

TensorBufferRequirements API কীভাবে বাস্তবায়িত হয় তার আরও সম্পূর্ণ দৃশ্যের জন্য, littert_tensor_buffer_requirements.h এর সোর্স কোডটি দেখুন।

পরিচালিত টেনসর বাফার (টেনসরবাফার) তৈরি করুন

নিম্নলিখিত সরলীকৃত কোড স্নিপেটটি দেখায় কিভাবে পরিচালিত টেনসর বাফার তৈরি করতে হয়, যেখানে 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));

জিরো-কপি দিয়ে টেনসর বাফার তৈরি করুন

একটি বিদ্যমান বাফারকে টেনসর বাফার (শূন্য-কপি) হিসেবে মোড়ানোর জন্য, নিম্নলিখিত কোড স্নিপেটটি ব্যবহার করুন:

// 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());

AHardwareBuffer থেকেও OpenCL বাফার তৈরি করা যেতে পারে:

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++ এ LiterRT এর নিম্নলিখিত বাস্তবায়নগুলি দেখুন।

মৌলিক অনুমান (CPU)

নিচে "শুরু করুন " বিভাগের কোড স্নিপেটগুলির একটি সংক্ষিপ্ত সংস্করণ দেওয়া হল। এটি LiterRT-এর সাহায্যে অনুমানের সবচেয়ে সহজ বাস্তবায়ন।

// 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));

হোস্ট মেমোরি সহ জিরো-কপি

LiterRT কম্পাইলড মডেল 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);