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

API โมเดลที่คอมไพล์แล้วของ LiteRT พร้อมให้บริการใน C++ ซึ่งช่วยให้นักพัฒนาแอป Android ควบคุมการจัดสรรหน่วยความจำและการพัฒนาในระดับต่ำได้อย่างละเอียด

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

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

ทำตามขั้นตอนต่อไปนี้เพื่อเพิ่ม LiteRT Compiled Model API ลงในแอปพลิเคชัน 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 แอตทริบิวต์ (การขึ้นต่อกันขณะรันไทม์): แอตทริบิวต์นี้มีความสำคัญอย่างยิ่งต่อการแพ็กเกจ ไลบรารีและชิ้นงานที่แชร์ซึ่งแอปพลิเคชันโหลดขณะรันไทม์
    • รันไทม์หลักของ LiteRT: ไลบรารีที่ใช้ร่วมกันของ LiteRT C API หลัก (เช่น //litert/c:litert_runtime_c_api_shared_lib)
    • ไลบรารีการจัดส่ง: ไลบรารีที่ใช้ร่วมกันเฉพาะผู้ให้บริการที่ 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)
    • ไฟล์และชิ้นงานโมเดล: ไฟล์โมเดลที่ฝึกแล้ว รูปภาพทดสอบ Shader หรือข้อมูลอื่นๆ ที่จำเป็นในขณะรันไทม์ (เช่น :model_files :shader_files)
  • deps แอตทริบิวต์ (Dependency เวลาคอมไพล์): แสดงรายการไลบรารีที่โค้ดของคุณต้องคอมไพล์ด้วย
    • API และยูทิลิตี LiteRT: ส่วนหัวและไลบรารีแบบคงที่สำหรับคอมโพเนนต์ LiteRT เช่น บัฟเฟอร์เทนเซอร์ (เช่น //litert/cc:litert_tensor_buffer)
    • ไลบรารีกราฟิก (สำหรับ GPU): การขึ้นต่อกันที่เกี่ยวข้องกับกราฟิก API หากตัวเร่ง GPU ใช้ไลบรารีเหล่านั้น (เช่น gles_deps())
  • linkopts แอตทริบิวต์: ระบุตัวเลือกที่ส่งไปยัง Linker ซึ่งอาจรวมถึงการลิงก์กับไลบรารีของระบบ (เช่น -landroid สำหรับ Android build หรือไลบรารี 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

แนวคิดและคอมโพเนนต์หลัก

ดูข้อมูลเกี่ยวกับแนวคิดและคอมโพเนนต์หลักของ API โมเดลที่คอมไพล์แล้วของ LiteRT ได้ในส่วนต่อไปนี้

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

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 มีการรองรับในตัวสำหรับความสามารถในการทำงานร่วมกันของบัฟเฟอร์ I/O โดยใช้ Tensor Buffer API (TensorBuffer) เพื่อจัดการโฟลว์ของข้อมูลเข้าและออกจากโมเดลที่คอมไพล์แล้ว Tensor Buffer API ช่วยให้เขียน (Write<T>()) อ่าน (Read<T>()) และล็อกหน่วยความจำ CPU ได้

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

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

โดยปกติแล้วข้อกำหนดในการจัดสรรบัฟเฟอร์ Tensor (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 Buffer (การคัดลอกเป็นศูนย์) ให้ใช้ข้อมูลโค้ดต่อไปนี้

// 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 อนุญาตให้ทำงานร่วมกับบัฟเฟอร์ประเภทอื่นๆ ได้ ตัวอย่างเช่น คุณสามารถสร้างบัฟเฟอร์ 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 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);