LiteRT Derleyici Eklentileri

Ne zaman derleyici eklentisi oluşturmalıyım?

LiteRT Compiler Plugin, belirli bir donanım hızlandırıcıyı derleyici bağımlılığıyla birlikte LiteRT çerçevesine entegre etmeniz gerektiğinde gereklidir.

Aşağıdaki durumlarda derleyici eklentisi oluşturmanız gerekir:

  1. Desteklenmeyen bir yeni donanım arka ucunu hedefliyorsunuz.
  2. Performans veya güç verimliliği için belirli model işlemlerini bu donanım hızlandırıcıya yüklemek istiyorsunuz.
  3. AOT derlemesi (iş istasyonunda) veya cihaz üzerinde derleme için destek gerekir.

Eklenti, makine öğrenimi modelinin bölümlerini alıp arka uç derleyicisine yapılan bir çağrıyla hedef donanımınızın çalıştırabileceği bir biçime dönüştürerek köprü görevi görür. LiteRT, eklenti tarafından oluşturulan özel bayt kodunu .tflite modelinde paketleyerek LiteRT çalışma zamanı kullanılarak yürütülebilir hale getirir.

Derleyici Eklentileri Nasıl Çalışır?

LiteRT çerçevesi, hedef donanımda yürütülmek üzere model alt grafikleri tanımlamak ve hazırlamak için model yükleme veya çevrimdışı ön işleme aşamasında derleyici eklentisini kullanır.

Bu süreç, eklentinin dışa aktarılan işlevleri kullanılarak çerçeve tarafından düzenlenen iki ana aşamadan oluşur:

  1. Bölümleme: Eklenti, model grafiğinin tamamını inceler ve desteklediği, hedef donanımda verimli bir şekilde hızlandırabileceği işlem alt kümelerini tanımlar. Bu desteklenen alt grafikler, derleme için "bölümlenir" (işaretlenir) ve ana hatları çizilir.
  2. Derleme: LiteRT çerçevesi, bölümlendirilmiş alt grafikleri eklentiye geri geçirir. Eklenti daha sonra dahili mantığını ve muhtemelen harici araç zincirlerini (derleyiciler) kullanarak bölümleri uygulayan bir veya daha fazla donanıma özgü bayt kodu modülü oluşturur. Bu bayt kodu, hedef donanımın çalışma zamanının (HAL/sürücü) sonunda yükleyip yürüteceği koddur.

Çerçeve, orijinal alt grafikleri, donanım sürücüsünü çağıran özel işlemlerle değiştirir ve eklenti tarafından oluşturulan derlenmiş bayt kodunu iletir.

LiteRT Dispatch, derleyici eklentisinin çalışma zamanı analogudur. Derleyici çıkışı verilen HAL'ye çağrı yapma olanağı sağlar. Daha fazla bilgi için gönderim belgelerine bakın.

AOT ve Cihaz Üzerinde

LiteRT, araçlarımız aracılığıyla AOT derlemesini ve cihaz üzerinde derlemeyi desteklemek için derleyici eklentilerini kullanabilir. Cihaz üzerinde derleme daha esnektir, LiteRT çalışma zamanı API'leri içinde tamamen dahili olarak çalışır ve yalnızca tek bir modelin yönetilmesini gerektirir. AOT akışı, cihazda çalıştırılması kaynak açısından çok yoğun olduğunda derlemeyi engelleyebilir. Bu durum, birçok modern büyük modelde görülebilir.

Yedek

LiteRT, heterojen grafikler için destekle oluşturulmuştur. Eklenti tarafından seçilmeyen tüm işlemler CPU'ya bırakılır veya başka bir arka uçta hızlandırma için kullanılabilir.

Derleyici eklentisi uygulama

LiteRT derleyici eklentisi, LiteRT C API'sinde tanımlanan belirli bir C işlevleri grubunu dışa aktaran paylaşılan bir kitaplık olarak uygulanır.

Temel Arayüz İşlevleri

Temel işlev, iki temel derleme adımı etrafında döner: LiteRtCompilerPluginPartition ve LiteRtCompilerPluginCompile.

İşlev Amaç
LiteRtCompilerPluginPartition Belirli bir model alt grafiğindeki tüm desteklenen işlemleri seçer ve işaretler (Bölümleme adımı).
LiteRtCompilerPluginCompile$ Önceden seçilmiş bölümler için donanıma özgü bayt kodu oluşturur (Derleme adımı).

C API Snippets

// 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. Bölümleme İşlevi

İşlev imzası:

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

partition işlevinin yaptığı işlem: Bu, seçim aşamasıdır. Eklenti, giriş LiteRtSubgraph içindeki işlemleri yineler. Eklenti, hedef donanımın desteklediği ve hızlandırabileceği her işlem için selected_ops parametresinde sağlanan LiteRtOpList$öğesine bu işlemi ekler. LiteRt çerçevesi, son derleme adımında gönderilecek bölümlerin sınırlarını tanımlamak için bu listeyi kullanır.

Varsayılan olarak LiteRT, seçilen tüm işlemleri mümkün olan en büyük alt DAG'lerde gruplandırır. Daha ayrıntılı bir bölümleme için, bu alt grafikleri daha da parçalamaya yarayan işlemleri seçerken bir dizin ilişkilendirilebilir.

2. Derleme İşlevi

İşlev imzası:

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

compile işlevinin yaptığı işlemler: Bu, üretim aşamasıdır. Giriş partitions, seçilen tüm alt grafiklerin izole edildiği bir modeli temsil eder. Eklenti, bu bölümleri işler ve hedef donanım için bayt kodunu oluşturmak üzere kendi özel araç zincirini çağırır. Eklentinin çıktısının, derleme için iletilen her alt grafik için bir giriş noktası sağlaması beklenir. Çoğu durumda bu, her giriş alt grafiği için ayrı ayrı bayt kodu modülleri veya birden fazla giriş noktası olan tek bir bayt kodu modülüdür.

compile tarafından döndürülen verilerin türü: LiteRtCompilerPluginCompile işlevi, çıkışını LiteRtCompiledResult out-parametresini kullanarak döndürür.

LiteRtCompiledResult, eklenti tarafından yönetilen bir yapıya yönelik opak (LiteRT açısından) bir tutamaktır. Derlemenin çıktısını temsil eder ve iki ana bilgi içerir:

  1. Bayt Kodu Modülleri: Donanıma özgü yürütülebilir bayt kodunu (ör. derlenmiş talimatlar) içeren bir veya daha fazla ham bellek arabelleği.
  2. Çağrı bilgileri: Her bölümün meta verileri. Bu, i. giriş alt grafiğinden sonuç bayt kodu modülüne ve bu modüldeki giriş noktası tanımlayıcısına eşlemeyi sağlar.

Örnek Uygulama

Aşağıdaki snippet'lerde, temel bir eklentinin temel işlevleri nasıl uygulayabileceği gösterilmektedir. Bu örnek, litert/vendors/examples/ adresindeki tam işlevli bir örnekten alınmıştır.

Eklenti Tanımlama ve Kurulum

Bu işlevler, eklenti ve donanımla ilgili temel bilgileri çerçeveye sağlar.

// 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;
}

Bölümleme Mantığı (LiteRtCompilerPluginPartition)

Bu örnekte, eklentinin yalnızca tüm girişler ve çıkışlar 32 bit kayan nokta ise sınırlı bir işlem grubu (mul, sub ve belirli bir bileşik işlem) seçtiği gösterilmektedir. Genellikle bir işlemin seçilip seçilmeyeceğini belirlemek için arka uçtaki derleyici araç zincirinde bir doğrulama kancası çağrısı yapılır.

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;
}

LiteRT, derleme çağrılmadan önce seçilen tüm işlemleri yeni bir ara modeldeki yeni alt grafikler halinde doğrulayıp "ana hatlarını" oluşturur. Bu ara model, derlemeye iletilen modeldir.

Derleme mantığı (LiteRtCompilerPluginCompile)

Bu işlev, bölümlenmiş alt grafikleri alır ve özel bir LiteRtCompiledResult oluşturur. Bu örnek, derlenecek her bölüm için bağımsız bir bayt kodu modülü oluşturur. Gerçek durumlarda bu genellikle LiteRT işlemlerini arka uç derleyici kitaplığına dönüştürmeyi içerir. İşlevsel örnek eklentisinin "derleme" özelliği, grafiği kodlayan, okunabilir bir dize oluşturur.

// 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 ...

Kullanım ve Doğrulama

LiteRT, derleyici eklentilerini model dosyalarına uygulama, sonucu yürütme ve doğrulama/karşılaştırma için çeşitli araçlar sunar. Hızlandırıcı test paketi belgeleri ile karşılaştırma ve profil oluşturma belgelerini inceleyin.