เรียกใช้ LiteRT Next บน Android ด้วย C++

LiteRT Next API พร้อมใช้งานใน C++ และช่วยให้นักพัฒนาแอป Android ควบคุมการจัดสรรหน่วยความจำและการพัฒนาระดับล่างได้มากกว่า Kotlin API

ดูตัวอย่างแอปพลิเคชัน LiteRT Next ใน C++ ได้ที่การแบ่งกลุ่มแบบไม่ต่อเนื่องด้วย C++ เดโม

เริ่มต้นใช้งาน

ทําตามขั้นตอนต่อไปนี้เพื่อเพิ่ม LiteRT Next ลงในแอปพลิเคชัน Android

อัปเดตการกำหนดค่าบิลด์

การสร้างแอปพลิเคชัน C++ ด้วย LiteRT สำหรับการเร่งความเร็ว GPU, NPU และ CPU โดยใช้ Bazel เกี่ยวข้องกับการกำหนดกฎ cc_binary เพื่อให้แน่ใจว่าคอมโพเนนต์ที่จำเป็นทั้งหมดได้รับการคอมไพล์ ลิงก์ และแพ็กเกจแล้ว การตั้งค่าตัวอย่างต่อไปนี้ช่วยให้แอปพลิเคชันเลือกหรือใช้ตัวเร่ง GPU, NPU และ CPU แบบไดนามิกได้

องค์ประกอบหลักในการกําหนดค่าบิลด์ Bazel มีดังนี้

  • cc_binary กฎ: กฎ Bazel พื้นฐานนี้ใช้เพื่อกำหนดเป้าหมายที่เรียกใช้งานได้ของ C++ (เช่น name = "your_application_name")
  • แอตทริบิวต์ srcs: แสดงรายการไฟล์ซอร์ส C++ ของแอปพลิเคชัน (เช่น main.cc และไฟล์ .cc หรือ .h อื่นๆ)
  • แอตทริบิวต์ data (Dependency รันไทม์): แอตทริบิวต์นี้สำคัญอย่างยิ่งสำหรับการจัดแพ็กเกจไฟล์ที่มีการใช้งานร่วมกันและชิ้นงานที่แอปพลิเคชันโหลดเมื่อรันไทม์
    • LiteRT Core Runtime: ไลบรารีที่ใช้ร่วมกันหลักของ LiteRT C API (เช่น //litert/c:litert_runtime_c_api_shared_lib)
    • ไลบรารี Dispatch: ไลบรารีที่ใช้ร่วมกันเฉพาะของผู้ให้บริการที่ LiteRT ใช้เพื่อสื่อสารกับไดรเวอร์ฮาร์ดแวร์ (เช่น //litert/vendors/qualcomm/dispatch:dispatch_api_so)
    • ไลบรารีแบ็กเอนด์ GPU: ไลบรารีที่ใช้ร่วมกันสำหรับการเร่งความเร็ว GPU (เช่น "@litert_gpu//:jni/arm64-v8a/libLiteRtGpuAccelerator.so)
    • ไลบรารีแบ็กเอนด์ NPU: ไลบรารีที่ใช้ร่วมกันที่เฉพาะเจาะจงสำหรับการเร่งความเร็ว NPU เช่น ไลบรารี QNN HTP ของ Qualcomm (เช่น @qairt//:lib/aarch64-android/libQnnHtp.so, @qairt//:lib/hexagon-v79/unsigned/libQnnHtpV79Skel.so)
    • ไฟล์และชิ้นงานโมเดล: ไฟล์โมเดลที่ผ่านการฝึกอบรม รูปภาพทดสอบ ชิดเดอร์ หรือข้อมูลอื่นๆ ที่จำเป็นในรันไทม์ (เช่น :model_files, :shader_files)
  • แอตทริบิวต์ deps (Dependency Compile-time): แสดงรายการไลบรารีที่โค้ดของคุณต้องคอมไพล์
    • LiteRT API และยูทิลิตี: ส่วนหัวและไลบรารีแบบคงที่สำหรับคอมโพเนนต์ LiteRT เช่น บัฟเฟอร์เทนเซอร์ (เช่น //litert/cc:litert_tensor_buffer)
    • ไลบรารีกราฟิก (สำหรับ GPU): ไลบรารีที่เชื่อมโยงกับ API กราฟิกหากตัวเร่ง GPU ใช้ไลบรารีดังกล่าว (เช่น gles_deps())
  • linkopts แอตทริบิวต์: ระบุตัวเลือกที่ส่งไปยังตัวลิงก์ ซึ่งอาจรวมถึงการลิงก์กับไลบรารีของระบบ (เช่น -landroid สำหรับบิลด์ Android หรือไลบรารี 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 ให้สภาพแวดล้อมรันไทม์ที่มีคอมโพเนนต์ต่างๆ เช่น เส้นทางของปลั๊กอินคอมไพเลอร์และบริบท GPU ต้องระบุ Environment เมื่อสร้าง CompiledModel และ TensorBuffer โค้ดต่อไปนี้จะสร้าง Environment สําหรับการเรียกใช้ CPU และ GPU โดยไม่มีตัวเลือกใดๆ

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

สร้างโมเดลที่คอมไพล์แล้ว

ใช้ CompiledModel API เพื่อเริ่มต้นรันไทม์ด้วยออบเจ็กต์ 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());

หากใช้หน่วยความจําของ 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 Next API ได้ที่ส่วนต่อไปนี้

การจัดการข้อผิดพลาด

LiteRT ใช้ litert::Expected เพื่อแสดงผลค่าหรือส่งต่อข้อผิดพลาดในลักษณะที่คล้ายกับ absl::StatusOr หรือ std::expected คุณตรวจสอบข้อผิดพลาดด้วยตนเองได้

LiteRT มีมาโครต่อไปนี้เพื่อความสะดวก

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

Compiled Model API (CompiledModel) มีหน้าที่โหลดโมเดล ใช้การเร่งฮาร์ดแวร์ สร้างอินสแตนซ์รันไทม์ สร้างบัฟเฟอร์อินพุตและเอาต์พุต รวมถึงเรียกใช้การอนุมาน

ข้อมูลโค้ดที่เขียนง่ายขึ้นต่อไปนี้แสดงวิธีที่ Compiled Model API นำโมเดล LiteRT (.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));

ข้อมูลโค้ดที่เรียบง่ายต่อไปนี้แสดงวิธีที่ 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 API ได้ที่ซอร์สโค้ดของ litert_compiled_model.h

บัฟเฟอร์ Tensor (TensorBuffer)

LiteRT Next รองรับการทำงานร่วมกันของบัฟเฟอร์ I/O ในตัวโดยใช้ Tensor Buffer API (TensorBuffer) เพื่อจัดการการไหลเข้าและออกจากโมเดลที่คอมไพล์ Tensor Buffer API ช่วยให้คุณเขียน (Write<T>()) และอ่าน (Read<T>()) รวมถึงล็อกหน่วยความจำของ CPU ได้

ดูมุมมองที่สมบูรณ์ยิ่งขึ้นเกี่ยวกับวิธีติดตั้งใช้งาน TensorBuffer API ได้ที่ซอร์สโค้ดของ litert_tensor_buffer.h

ข้อกําหนดอินพุต/เอาต์พุตของโมเดลการค้นหา

โดยปกติแล้วข้อกำหนดในการจอง Tensor Buffer (TensorBuffer) จะระบุโดยตัวเร่งฮาร์ดแวร์ บัฟเฟอร์สำหรับอินพุตและเอาต์พุตอาจมีข้อกำหนดเกี่ยวกับการจัดแนว ระยะห่างของบัฟเฟอร์ และประเภทหน่วยความจำ คุณสามารถใช้ฟังก์ชันตัวช่วยอย่าง CreateInputBuffers เพื่อจัดการข้อกําหนดเหล่านี้โดยอัตโนมัติ

ข้อมูลโค้ดที่เรียบง่ายต่อไปนี้แสดงวิธีเรียกข้อมูลข้อกำหนดบัฟเฟอร์สำหรับข้อมูลอินพุต

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

ดูภาพรวมที่สมบูรณ์ยิ่งขึ้นเกี่ยวกับวิธีติดตั้งใช้งาน TensorBufferRequirements API ได้ที่ซอร์สโค้ดของ litert_tensor_buffer_requirements.h

สร้างบัฟเฟอร์ Tensor ที่มีการจัดการ (TensorBuffers)

ข้อมูลโค้ดที่เรียบง่ายต่อไปนี้แสดงวิธีสร้าง Managed Tensor Buffers โดยที่ 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 ด้วยการทำงานแบบไม่คัดลอก

หากต้องการตัดบัฟเฟอร์ที่มีอยู่เป็นบัฟเฟอร์ 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));

การอ่านและเขียนจากบัฟเฟอร์ Tensor

ข้อมูลโค้ดต่อไปนี้แสดงวิธีอ่านจากบัฟเฟอร์อินพุตและเขียนไปยังบัฟเฟอร์เอาต์พุต

// 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 Next ใน C++ ต่อไปนี้

การทำนายพื้นฐาน (CPU)

ต่อไปนี้คือข้อมูลโค้ดฉบับย่อจากส่วนเริ่มต้นใช้งาน ซึ่งเป็นการใช้งานการอนุมานที่ง่ายที่สุดด้วย LiteRT Next

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