Plugin Compiler LiteRT

Kapan Saya Harus Membuat Plugin Compiler?

Plugin Compiler LiteRT diperlukan saat Anda perlu mengintegrasikan akselerator hardware tertentu dengan dependensi compiler ke framework LiteRT.

Anda harus membuat plugin compiler jika:

  1. Anda menargetkan backend hardware baru yang tidak didukung.
  2. Anda ingin menurunkan beban operasi model tertentu ke akselerator hardware tersebut untuk performa atau efisiensi daya.
  3. Anda memerlukan dukungan untuk kompilasi AOT (di workstation) atau kompilasi di perangkat.

Plugin ini berfungsi sebagai jembatan, mengambil sebagian model machine learning dan mengonversinya ke dalam format yang dapat dieksekusi oleh hardware target Anda, menggunakan panggilan ke compiler backend. LiteRT menggabungkan bytecode kustom yang dihasilkan oleh plugin ke dalam model .tflite, sehingga dapat dieksekusi menggunakan runtime LiteRT.

Bagaimana Cara Kerja Plugin Compiler?

Framework LiteRT menggunakan plugin compiler selama fase pemuatan model atau pra-pemrosesan offline untuk mengidentifikasi dan menyiapkan subgraf model untuk dieksekusi di hardware target.

Proses ini melibatkan dua fase utama yang diatur oleh framework menggunakan fungsi yang diekspor plugin:

  1. Partisi: Plugin memeriksa seluruh grafik model dan mengidentifikasi subset operasi yang didukung dan dapat dipercepat secara efisien di hardware target. Subgraf yang didukung ini "dipartisi" (ditandai) untuk kompilasi dan diuraikan.
  2. Kompilasi: Framework LiteRT meneruskan subgraf yang dipartisi kembali ke plugin. Plugin kemudian menggunakan logika internalnya dan kemungkinan toolchain (compiler) eksternal untuk menghasilkan satu atau beberapa modul bytecode khusus hardware yang mengimplementasikan partisi. Bytecode ini adalah yang pada akhirnya akan dimuat dan dieksekusi oleh runtime (HAL/driver) hardware target.

Framework menggantikan subgrafik asli dengan operasi kustom yang memanggil driver hardware, meneruskan bytecode yang dikompilasi yang dibuat oleh plugin.

LiteRT Dispatch adalah analog runtime untuk plugin compiler. Mereka menyediakan cara untuk memanggil HAL yang diberikan output compiler. Untuk mengetahui detail selengkapnya, lihat dokumentasi pengiriman.

AOT versus Di Perangkat

LiteRT dapat menggunakan plugin compiler untuk mendukung kompilasi AOT melalui alat kami, serta kompilasi di perangkat. Kompilasi di perangkat lebih fleksibel, sepenuhnya diinternalisasi dalam API runtime LiteRT dan hanya memerlukan pengelolaan satu model. Alur AOT dapat membuka blokir kompilasi saat terlalu banyak resource yang digunakan untuk dijalankan di perangkat, yang mungkin terjadi pada banyak model besar kontemporer.

Pengganti

LiteRT dibuat dengan dukungan untuk grafik heterogen. Operasi apa pun yang tidak dipilih oleh plugin akan diserahkan ke CPU atau tersedia untuk akselerasi di backend lain.

Menerapkan Plugin Compiler

Plugin compiler LiteRT diimplementasikan sebagai library bersama yang mengekspor serangkaian fungsi C tertentu yang ditentukan dalam LiteRT C API.

Fungsi Antarmuka Penting

Fungsi inti berkisar pada dua langkah kompilasi utama: LiteRtCompilerPluginPartition dan LiteRtCompilerPluginCompile.

Fungsi Tujuan
LiteRtCompilerPluginPartition Memilih dan menandai semua operasi yang didukung dalam subgraf model tertentu (langkah Partisi).
LiteRtCompilerPluginCompile$ Membuat bytecode khusus hardware untuk partisi yang telah dipilih sebelumnya (langkah Compile).

Cuplikan C API

// Name associated with the manufacturer this plugin relates to.
LITERT_CAPI_EXPORT const char* LiteRtGetCompilerPluginSocManufacturer();

// Create and initialize the plugin instance.
LITERT_CAPI_EXPORT LiteRtStatus
LiteRtCreateCompilerPlugin(LiteRtCompilerPlugin* compiler_plugin,
                           LiteRtEnvironmentOptions env, LiteRtOptions options);

// Choose ops for compilation.
// This is the PARTITION step.
LITERT_CAPI_EXPORT LiteRtStatus LiteRtCompilerPluginPartition(
    LiteRtCompilerPlugin compiler_plugin, const char* soc_model,
    LiteRtSubgraph subgraph, LiteRtOpList selected_ops);

// Prepare result to pass to the runtime for given model containing partitioned
// subgraphs. This is the COMPILE step.
LITERT_CAPI_EXPORT LiteRtStatus LiteRtCompilerPluginCompile(
    LiteRtCompilerPlugin compiler_plugin, const char* soc_model,
    LiteRtModel partitions, LiteRtCompiledResult* compiled_result);

1. Fungsi Partisi

Tanda tangan fungsi adalah:

LITERT_CAPI_EXPORT LiteRtStatus LiteRtCompilerPluginPartition(
    LiteRtCompilerPlugin compiler_plugin, const char* soc_model,
    LiteRtSubgraph subgraph, LiteRtOpList selected_ops);

Fungsi partition melakukan hal berikut: Ini adalah fase pemilihan. Plugin melakukan iterasi pada operasi dalam input LiteRtSubgraph. Untuk setiap operasi yang didukung dan dapat dipercepat oleh hardware target, plugin menambahkan operasi tersebut ke LiteRtOpList$ yang disediakan dalam parameter selected_ops. Framework LiteRt menggunakan daftar ini untuk menentukan batas partisi yang akan dikirim untuk langkah kompilasi akhir.

Secara default, LiteRT akan mengelompokkan semua operasi yang dipilih ke dalam sub-DAG terbesar yang memungkinkan. Untuk partisi yang lebih terperinci, indeks dapat dikaitkan saat memilih operasi yang berfungsi untuk memecah lebih lanjut subgrafik ini.

2. Fungsi Compile

Tanda tangan fungsi adalah:

LITERT_CAPI_EXPORT LiteRtStatus LiteRtCompilerPluginCompile(
    LiteRtCompilerPlugin compiler_plugin, const char* soc_model,
    LiteRtModel partitions, LiteRtCompiledResult* compiled_result);

Fungsi compile melakukan hal berikut: Ini adalah fase pembuatan. Input partitions merepresentasikan model dengan semua subgraf yang dipilih telah diisolasi. Plugin memproses partisi ini, memanggil toolchain tertentu untuk menghasilkan bytecode untuk hardware target. Output plugin diharapkan menyediakan titik entri untuk setiap subgrafik yang diteruskan untuk kompilasi. Dalam sebagian besar kasus, ini adalah modul kode byte individual untuk setiap subgraf input, atau modul kode byte tunggal dengan beberapa titik entri.

Jenis data yang ditampilkan oleh compile: Fungsi LiteRtCompilerPluginCompile menampilkan outputnya menggunakan parameter keluar LiteRtCompiledResult.

LiteRtCompiledResult adalah handle buram (terkait LiteRT) ke struktur yang dikelola oleh plugin. Representasi output kompilasi dan berisi dua informasi utama:

  1. Modul Byte Code: Satu atau beberapa buffer memori mentah yang berisi bytecode yang dapat dieksekusi khusus hardware (yaitu, petunjuk yang dikompilasi).
  2. Informasi Panggilan: Metadata untuk setiap partisi. Hal ini menyediakan pemetaan dari subgraf input ke-i ke modul kode byte hasil dan ID titik entri ke dalam modul tersebut.

Penerapan Contoh

Cuplikan berikut menggambarkan cara plugin dasar dapat menerapkan fungsi inti. Contoh ini diambil dari contoh yang berfungsi penuh di litert/vendors/examples/

Identifikasi dan Penyiapan Plugin

Fungsi ini memberikan informasi dasar tentang plugin dan hardware ke framework.

// Define the plugin's internal state structure
struct LiteRtCompilerPluginT {};

// Identify the manufacturer
const char* LiteRtGetCompilerPluginSocManufacturer() {
  return "AcmeCorp"; // Example manufacturer name
}

// Specify the supported hardware (in this example, it supports kLiteRtHwAcceleratorNpu)
LiteRtStatus LiteRtGetCompilerPluginSupportedHardware(
    LiteRtCompilerPlugin compiler_plugin,
    LiteRtHwAccelerators* supported_hardware) {
  // ... argument checking ...
  *supported_hardware = kLiteRtHwAcceleratorNpu;
  return kLiteRtStatusOk;
}

Logika Partisi (LiteRtCompilerPluginPartition)

Contoh ini menunjukkan plugin yang memilih serangkaian operasi terbatas (mul, sub, dan operasi komposit tertentu) hanya jika semua input dan output adalah float 32 bit. Biasanya, penentuan apakah suatu operasi harus dipilih atau tidak akan mencakup panggilan ke hook validasi dalam toolchain compiler backend.

LiteRtStatus LiteRtCompilerPluginPartition(LiteRtCompilerPlugin compiler_plugin,
                                          const char* soc_model,
                                          LiteRtSubgraph subgraph,
                                          LiteRtOpList selected_ops) {

  // Iterate over ops and check criteria for selection
  // (using a C++ wrapper namespace '::litert' for convenience).
  // `subgraph` is a single subgraph from the original model, as such
  // this function will be called for each subgraph in the original model.

  ::litert::Subgraph main_subgraph(subgraph);
  for (const auto& op : main_subgraph.Ops()) {
    // 1. Check a constraint: require all tensors to be Float32
    bool only_f32 = true;
    // ... logic to check input/output types ...
    if (!only_f32) {
      continue;
    }

    // 2. Check op codes and push to selected_ops list
    if (op.Code() == kLiteRtOpCodeTflMul) {
      LITERT_RETURN_IF_ERROR(LiteRtPushOp(selected_ops, op.Get(), 0));
    } else if (op.Code() == kLiteRtOpCodeTflSub) {
      LITERT_RETURN_IF_ERROR(LiteRtPushOp(selected_ops, op.Get(), 0));
    } else if (op.Code() == kLiteRtOpCodeShloComposite) {
      // Example of checking composite op options
      // ... logic to check for "odml.rms_norm" name ...
      LITERT_RETURN_IF_ERROR(LiteRtPushOp(selected_ops, op.Get(), 0));
    }
  }
  return kLiteRtStatusOk;
}

Sebelum memanggil kompilasi, LiteRT akan memvalidasi dan "menguraikan" semua operasi yang dipilih ke dalam subgraf baru dalam model perantara baru. Model perantara ini yang diteruskan ke kompilasi.

Logika Kompilasi (LiteRtCompilerPluginCompile)

Fungsi ini mengambil subgraf yang dipartisi dan menghasilkan LiteRtCompiledResult kustom. Contoh ini menghasilkan modul bytecode mandiri untuk setiap partisi yang akan dikompilasi. Dalam kasus nyata, hal ini biasanya melibatkan konversi operasi LiteRT ke jenis ke library compiler backend. "Kompilasi" plugin contoh fungsional membuat string yang dapat dibaca manusia yang mengenkode grafik.

// Internal structure defining the compiled output
struct LiteRtCompiledResultT {
  std::vector<std::string> byte_code;   // The hardware bytecode buffers
  std::vector<std::string> per_op_data; // Per-call metadata (CallInfo)
};

LiteRtStatus LiteRtCompilerPluginCompile(
    LiteRtCompilerPlugin compiler_plugin, const char* soc_model,
    LiteRtModel partitions, LiteRtCompiledResult* compiled_result) {

  // 1. Create the internal result structure
  auto model = litert::Model::CreateFromNonOwnedHandle(partitions);
  const auto num_partitions = model.NumSubgraphs();
  auto result = std::make_unique<LiteRtCompiledResultT>();
  result->byte_code.resize(num_partitions);
  result->per_op_data.resize(num_partitions);

  // 2. Iterate and compile each partition
  for (auto i = 0; i < num_partitions; ++i) {
    // CompileSinglePartition is an internal helper that converts the subgraph
    // into the target hardware's format and stores it in result->byte_code.
    // In the case of the example this is just a stringification of the graph.

    // ... internal call to CompileSinglePartition ...
    // Example: result.byte_code[i] = generated_hw_code;
    // Example: result.per_op_data[i] = absl::StrFormat("Partition_%d", i);

    // The "per_op_data" is a unique identifier associated to the `ith` partition.
    // This is analogous to the name of a function in a library.
    // This is only meaningful when the plugin is preparing single modules with multiple entry points.
  }

  // 3. Pass ownership of the result back to the framework
  *compiled_result = result.release();

  return kLiteRtStatusOk;
}

// Functions to expose the compiled result data to the framework
LiteRtStatus LiteRtGetCompiledResultByteCode(
    LiteRtCompiledResult compiled_result, LiteRtParamIndex byte_code_idx,
    const void** byte_code, size_t* byte_code_size) {
  // ... implementation reads from compiled_result->byte_code ...
}
// ... other LiteRtGetCompiledResult* functions ...

Penggunaan dan Validasi

LiteRT menyediakan berbagai alat untuk menerapkan plugin compiler ke file model, menjalankan hasilnya, dan memvalidasi/melakukan tolok ukur. Lihat dokumentasi rangkaian pengujian akselerator dan dokumentasi pembuatan tolok ukur dan pembuatan profil.