LiteRT CompiledModel C++ API

LiteRT CompiledModel API は C++ で利用でき、デベロッパーはメモリ割り当てと低レベル開発をきめ細かく制御できます。例については、画像セグメンテーション C++ アプリをご覧ください。

次のガイドでは、CompiledModel Kotlin API の基本的な CPU 推論について説明します。高度なアクセラレーション機能については、GPU アクセラレーションNPU アクセラレーションに関するガイドをご覧ください。

ビルドの依存関係を追加する

プロジェクトに合ったパスを選択します。

基本的な推論

このセクションでは、基本的な推論の実行方法について説明します。

環境を作成する

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::StatusOrstd::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 は、Tensor Buffer API(TensorBuffer)を使用してコンパイル済みモデルとの間でデータの流れを処理することで、I/O バッファの相互運用性を組み込みでサポートします。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 のソースコードをご覧ください。

マネージド テンソル バッファ(TensorBuffer)を作成する

次の簡略化されたコード スニペットは、マネージド テンソル バッファを作成する方法を示しています。ここでは、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 間の相互運用性をサポートするモバイル デバイスでは、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);