LiteRT CompiledModel C++ API

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

คำแนะนำต่อไปนี้แสดงการอนุมาน CPU พื้นฐานของ CompiledModel Kotlin API ดูคำแนะนำเกี่ยวกับการเร่งด้วย GPU และการเร่งด้วย NPU สำหรับ ฟีเจอร์การเร่งขั้นสูง

เพิ่มการพึ่งพิงบิวด์

เลือกเส้นทางที่เหมาะกับโปรเจ็กต์ของคุณ

  • ใช้ไลบรารีที่สร้างไว้ล่วงหน้า (ข้ามแพลตฟอร์ม): ใช้ไลบรารี LiteRT ที่สร้างไว้ล่วงหน้า เพื่อการตั้งค่าทันที ดูวิธีใช้ไลบรารี C++ ที่สร้างไว้ล่วงหน้า จากแพ็กเกจ LiteRT Maven ใน Android หรือดาวน์โหลด/ผสานรวม ไบนารี C++ ที่สร้างไว้ล่วงหน้าใน Android, iOS, macOS, Linux และ Windows

  • สร้างจากแหล่งที่มา (ข้ามแพลตฟอร์ม): สร้างจากแหล่งที่มาด้วย CMake เพื่อการควบคุมอย่างเต็มรูปแบบและการรองรับหลายแพลตฟอร์ม (Android, iOS, macOS, Linux, Windows) ดูรายละเอียดในคำแนะนำนี้

การอนุมานพื้นฐาน

ส่วนนี้แสดงวิธีดำเนินการอนุมานพื้นฐาน

สร้างสภาพแวดล้อม

ออบเจ็กต์ 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

บัฟเฟอร์ 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 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));

การคัดลอกเป็นศูนย์ด้วยหน่วยความจำของโฮสต์

CompiledModel API ของ LiteRT ช่วยลดอุปสรรคของไปป์ไลน์การอนุมาน โดยเฉพาะอย่างยิ่งเมื่อต้องจัดการกับแบ็กเอนด์ฮาร์ดแวร์หลายรายการและโฟลว์แบบไม่มีการคัดลอก ข้อมูลโค้ดต่อไปนี้ใช้เมธอด 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 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);