LiteRT CompiledModel C++ API

LiteRT CompiledModel API는 C++로 제공되므로 개발자는 메모리 할당과 하위 수준 개발을 세부적으로 제어할 수 있습니다. 예시는 이미지 분할 C++ 앱을 참고하세요.

다음 가이드에서는 CompiledModel Kotlin API의 기본 CPU 추론을 보여줍니다. 고급 가속 기능은 GPU 가속NPU 가속 가이드를 참고하세요.

빌드 종속 항목 추가

프로젝트에 맞는 경로를 선택합니다.

  • 미리 빌드된 라이브러리 사용 (크로스 플랫폼): 즉시 설정을 위해 LiteRT 미리 빌드된 라이브러리를 사용합니다. Android에서 LiteRT Maven 패키지의 미리 빌드된 C++ 라이브러리를 사용하는 방법을 알아보거나 Android, iOS, macOS, Linux, Windows에서 미리 빌드된 C++ 바이너리를 다운로드/통합하세요.

  • 소스에서 빌드 (크로스 플랫폼): CMake를 사용하여 소스에서 빌드하여 완전한 제어 및 멀티 플랫폼 지원 (Android, iOS, macOS, Linux, Windows)을 제공합니다. 자세한 내용은 이 가이드를 참고하세요.

기본 추론

이 섹션에서는 기본 추론이 실행되는 방식을 보여줍니다.

환경 만들기

Environment 객체는 컴파일러 플러그인 경로, GPU 컨텍스트와 같은 구성요소가 포함된 런타임 환경을 제공합니다. CompiledModelTensorBuffer를 만들 때는 Environment이 필요합니다. 다음 코드는 옵션 없이 CPU 및 GPU 실행을 위한 Environment을 만듭니다.

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 (TensorBuffer)를 사용하여 I/O 버퍼 상호 운용성을 기본적으로 지원합니다. 텐서 버퍼 API는 쓰기(Write<T>()) 및 읽기 (Read<T>()), CPU 메모리 잠금 기능을 제공합니다.

TensorBuffer API가 구현되는 방식을 더 자세히 알아보려면 litert_tensor_buffer.h의 소스 코드를 참고하세요.

쿼리 모델 입력/출력 요구사항

텐서 버퍼 (TensorBuffer) 할당 요구사항은 일반적으로 하드웨어 가속기에 의해 지정됩니다. 입력 및 출력 버퍼에는 정렬, 버퍼 스트라이드, 메모리 유형에 관한 요구사항이 있을 수 있습니다. CreateInputBuffers와 같은 도우미 함수를 사용하여 이러한 요구사항을 자동으로 처리할 수 있습니다.

다음 간소화된 코드 스니펫은 입력 데이터의 버퍼 요구사항을 가져오는 방법을 보여줍니다.

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

TensorBufferRequirements API가 구현되는 방식을 더 자세히 알아보려면 litert_tensor_buffer_requirements.h의 소스 코드를 참고하세요.

관리형 텐서 버퍼 (TensorBuffers) 만들기

다음 간소화된 코드 스니펫은 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와 같은 특정 버퍼 유형은 다른 버퍼 유형과의 상호 운용성을 허용합니다. 예를 들어 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());

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++의 LiteRT 구현을 참고하세요.

기본 추론 (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 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 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);