LiteRT CompiledModel C++ API

رابط برنامه‌نویسی CompiledModel LiteRT به زبان ++C در دسترس است و به توسعه‌دهندگان کنترل دقیقی بر تخصیص حافظه و توسعه‌ی سطح پایین می‌دهد. برای مثال، به برنامه‌ی ++C بخش‌بندی تصویر مراجعه کنید.

راهنمای زیر استنتاج اولیه CPU از رابط برنامه‌نویسی کاربردی کاتلین CompiledModel را نشان می‌دهد. برای ویژگی‌های پیشرفته شتاب‌دهی، به راهنمای شتاب‌دهی GPU و شتاب‌دهی NPU مراجعه کنید.

وابستگی ساخت را اضافه کنید

مسیری را انتخاب کنید که متناسب با پروژه شما باشد:

  • استفاده از کتابخانه پیش‌ساخته (چند پلتفرمی) : از کتابخانه پیش‌ساخته LiteRT برای راه‌اندازی فوری استفاده کنید. نحوه استفاده از کتابخانه پیش‌ساخته C++ از بسته LiteRT Maven در اندروید را ببینید، یا فایل باینری C++ پیش‌ساخته را در اندروید، iOS، macOS، لینوکس و ویندوز دانلود/ادغام کنید.

  • ساخت از منبع (چند پلتفرمی) : برای کنترل کامل و پشتیبانی از چند پلتفرم (اندروید، iOS، macOS، لینوکس، ویندوز) با CMake از منبع بسازید. جزئیات را در این راهنما ببینید.

استنتاج پایه

این بخش نشان می‌دهد که چگونه استنتاج اساسی انجام می‌شود.

محیط را ایجاد کنید

شیء Environment یک محیط زمان اجرا فراهم می‌کند که شامل اجزایی مانند مسیر افزونه کامپایلر و زمینه‌های GPU است. Environment هنگام ایجاد CompiledModel و TensorBuffer مورد نیاز است. کد زیر یک Environment برای اجرای CPU و GPU بدون هیچ گزینه‌ای ایجاد می‌کند:

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

ایجاد CompiledModel

پس از دریافت مدل LiteRT یا تبدیل مدل به فرمت .tflite ، با استفاده از CompiledModel API، زمان اجرا را با فایل مدل مقداردهی اولیه کنید. در این مرحله می‌توانید شتاب سخت‌افزاری ( 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());

اگر از حافظه 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

مفاهیم و اجزای کلیدی

برای اطلاعات در مورد مفاهیم کلیدی و اجزای LiteRT CompiledModel API به بخش‌های زیر مراجعه کنید.

مدیریت خطا

LiteRT از litert::Expected برای برگرداندن مقادیر یا انتشار خطاها به روشی مشابه absl::StatusOr یا std::expected استفاده می‌کند. می‌توانید خودتان به صورت دستی خطا را بررسی کنید.

برای راحتی، LiteRT ماکروهای زیر را ارائه می‌دهد:

  • LITERT_ASSIGN_OR_RETURN(lhs, expr) در صورتی که خطایی تولید نکند، نتیجه expr را به lhs اختصاص می‌دهد و در غیر این صورت، خطا را برمی‌گرداند.

    به چیزی شبیه به قطعه کد زیر گسترش خواهد یافت.

    auto maybe_model = CompiledModel::Create(env, "mymodel.tflite", HwAccelerators::kCpu);
    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 مراجعه کنید.

بافر تنسور (TensorBuffer)

LiteRT با استفاده از API Tensor Buffer ( TensorBuffer ) برای مدیریت جریان داده‌ها به داخل و خارج از مدل کامپایل شده، پشتیبانی داخلی برای قابلیت همکاری بافر I/O ارائه می‌دهد. API Tensor Buffer امکان نوشتن ( Write<T>() ) و خواندن ( Read<T>() ) و قفل کردن حافظه CPU را فراهم می‌کند.

برای مشاهده کامل‌تر نحوه پیاده‌سازی API TensorBuffer ، به کد منبع litert_tensor_buffer.h مراجعه کنید.

الزامات ورودی/خروجی مدل پرس و جو

الزامات تخصیص یک بافر تنسور ( TensorBuffer ) معمولاً توسط شتاب‌دهنده سخت‌افزاری مشخص می‌شود. بافرهای ورودی و خروجی می‌توانند الزاماتی در مورد ترازبندی، گام‌های بافر و نوع حافظه داشته باشند. می‌توانید از توابع کمکی مانند CreateInputBuffers برای مدیریت خودکار این الزامات استفاده کنید.

قطعه کد ساده‌شده‌ی زیر نشان می‌دهد که چگونه می‌توانید الزامات بافر برای داده‌های ورودی را بازیابی کنید:

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

برای مشاهده کامل‌تر نحوه پیاده‌سازی API مربوط به TensorBufferRequirements ، به کد منبع litert_tensor_buffer_requirements.h مراجعه کنید.

ایجاد بافرهای تنسور مدیریت‌شده (TensorBuffers)

قطعه کد ساده‌شده‌ی زیر نحوه‌ی ایجاد بافرهای مدیریت‌شده‌ی تنسور را نشان می‌دهد، که در آن API TensorBuffer بافرهای مربوطه را تخصیص می‌دهد:

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

خواندن و نوشتن از Tensor Buffer

قطعه کد زیر نحوه خواندن از یک بافر ورودی و نوشتن در یک بافر خروجی را نشان می‌دهد:

// 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 ، امکان همکاری با سایر انواع بافر را فراهم می‌کنند. برای مثال، یک بافر OpenGL می‌تواند از یک AHardwareBuffer با کپی صفر ایجاد شود. قطعه کد زیر مثالی از این مورد را نشان می‌دهد:

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 پشتیبانی می‌کنند، بافرهای CL را می‌توان از بافرهای GL ایجاد کرد:

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

پیاده‌سازی‌های نمونه

به پیاده‌سازی‌های زیر از LiteRT در C++ مراجعه کنید.

استنتاج پایه (CPU)

در ادامه، نسخه‌ی خلاصه‌شده‌ای از قطعه کدهای بخش «شروع به کار» آمده است. این ساده‌ترین پیاده‌سازی استنتاج با LiteRT است.

// Load model and initialize runtime
LITERT_ASSIGN_OR_RETURN(auto env, Environment::Create({}));
LITERT_ASSIGN_OR_RETURN(auto compiled_model, CompiledModel::Create(env, "mymodel.tflite",
  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 CompiledModel ، اصطکاک خطوط لوله استنتاج را کاهش می‌دهد، به خصوص هنگام مواجهه با چندین backend سخت‌افزاری و جریان‌های zero-copy. قطعه کد زیر هنگام ایجاد بافر ورودی از متد CreateFromHostMemory استفاده می‌کند که از zero-copy با حافظه میزبان استفاده می‌کند.

// 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 compiled_model1, CompiledModel::Create(env, "model1.tflite", 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 compiled_model2, CompiledModel::Create(env, "model2.tflite", 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);