LiteRT Next API는 C++에서 사용할 수 있으며 Android 개발자가 Kotlin API보다 메모리 할당 및 하위 수준 개발을 더 효과적으로 제어할 수 있습니다.
C++의 LiteRT Next 애플리케이션 예시는 C++ 데모를 사용한 비동기 세분화를 참고하세요.
시작하기
Android 애플리케이션에 LiteRT Next를 추가하려면 다음 단계를 따르세요.
빌드 구성 업데이트
Bazel을 사용하여 GPU, NPU, CPU 가속을 위한 LiteRT로 C++ 애플리케이션을 빌드하려면 필요한 모든 구성요소가 컴파일, 연결, 패키징되도록 cc_binary
규칙을 정의해야 합니다. 다음 설정 예시를 사용하면 애플리케이션에서 GPU, NPU, CPU 가속기를 동적으로 선택하거나 활용할 수 있습니다.
Bazel 빌드 구성의 주요 구성요소는 다음과 같습니다.
cc_binary
규칙: C++ 실행 파일 타겟 (예:name = "your_application_name"
)에 복사합니다.srcs
속성: 애플리케이션의 C++ 소스 파일 (예:main.cc
및 기타.cc
또는.h
파일).data
속성 (런타임 종속 항목): 애플리케이션이 런타임에 로드하는 공유 라이브러리 및 애셋을 패키징하는 데 중요합니다.- LiteRT Core Runtime: 기본 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 백엔드 라이브러리: Qualcomm의 QNN HTP 라이브러리 (예:
@qairt//:lib/aarch64-android/libQnnHtp.so
,@qairt//:lib/hexagon-v79/unsigned/libQnnHtpV79Skel.so
)의 파일에만 라벨을 지정할 수 있습니다. - 모델 파일 및 애셋: 학습된 모델 파일, 테스트 이미지, 셰이더 또는 런타임에 필요한 기타 데이터 (예:
:model_files
,:shader_files
)의 파일에만 라벨을 지정할 수 있습니다.
- LiteRT Core Runtime: 기본 LiteRT C API 공유 라이브러리 (예:
deps
속성 (컴파일 시간 종속 항목): 코드를 컴파일할 때 필요한 라이브러리가 표시됩니다.- LiteRT API 및 유틸리티: 텐서 버퍼와 같은 LiteRT 구성요소의 헤더 및 정적 라이브러리 (예:
//litert/cc:litert_tensor_buffer
))를 제공합니다. - 그래픽 라이브러리 (GPU용): GPU 가속기에서 사용하는 경우 그래픽 API와 관련된 종속 항목입니다 (예:
gles_deps()
))를 제공합니다.
- LiteRT API 및 유틸리티: 텐서 버퍼와 같은 LiteRT 구성요소의 헤더 및 정적 라이브러리 (예:
linkopts
속성: 링커에 전달되는 옵션을 지정합니다. 여기에는 시스템 라이브러리 (예: Android 빌드의 경우-landroid
,gles_linkopts()
의 경우 GLES 라이브러리)를 사용합니다.
다음은 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
를 만들 때 필요합니다. 다음 코드는 옵션 없이 CPU 및 GPU 실행을 위한 Environment
를 만듭니다.
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)
컴파일된 모델 API (CompiledModel
)는 모델을 로드하고, 하드웨어 가속을 적용하고, 런타임을 인스턴스화하고, 입력 및 출력 버퍼를 만들고, 추론을 실행합니다.
다음의 단순화된 코드 스니펫은 컴파일된 모델 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));
다음의 단순화된 코드 스니펫은 컴파일된 모델 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의 소스 코드를 참고하세요.
텐서 버퍼 (TensorBuffer)
LiteRT Next는 Tensor Buffer API (TensorBuffer
)를 사용하여 컴파일된 모델의 데이터 흐름을 처리하는 I/O 버퍼 상호 운용성을 위한 기본 제공 지원을 제공합니다. Tensor Buffer 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가 각 버퍼를 할당하는 관리형 Tensor Buffers를 만드는 방법을 보여줍니다.
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 버퍼 (제로 복사)로 래핑하려면 다음 코드 스니펫을 사용하세요.
// 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 버퍼는 0 복사로 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 Next의 다음 구현을 참고하세요.
기본 추론 (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);