建立新型可伺服物件

本文說明如何使用新型可伺服物件擴充 TensorFlow Serving。最常見的可伺服物件類型是 SavedModelBundle,但定義其他類型的可伺服物件也相當實用,以便提供與模型搭配使用的資料。範例包括:詞彙查閱表、特徵轉換邏輯。任何 C++ 類別都可以是可伺服物件,例如 intstd::map<string, int> 或二進位檔中定義的任何類別 (我們將其稱為 YourServable)。

YourServable 定義 LoaderSourceAdapter

如要讓 TensorFlow Serving 管理及提供 YourServable 服務,您需要定義兩件事:

  1. Loader 類別,用於載入、提供存取權及卸載 YourServable 的執行個體。

  2. SourceAdapter,可從某些基礎資料格式 (例如檔案系統路徑) 具現化載入器。除了 SourceAdapter 之外,您也可以編寫完整的 Source。不過,由於 SourceAdapter 方法更常見且更模組化,因此我們在此著重說明這個方法。

Loader 抽象化定義於 core/loader.h 中。您必須定義方法,才能載入、存取及卸載您的可伺服物件類型。載入可伺服物件的資料可能來自任何位置,但通常來自儲存系統路徑。假設 YourServable 的情況就是如此。進一步假設您已經有符合需求的 Source<StoragePath> (如果沒有,請參閱自訂 Source 文件)。

除了 Loader 之外,您還需要定義 SourceAdapter,以便從指定的儲存路徑具現化 LoaderSimpleLoaderSourceAdapter 類別 (位於 core/simple_loader.h 中) 可簡潔地指定大多數簡單的使用情境。進階使用情境可能會選擇使用較低階的 API 分別指定 LoaderSourceAdapter 類別,例如,如果 SourceAdapter 需要保留某些狀態,及/或狀態需要在 Loader 執行個體之間共用。

servables/hashmap/hashmap_source_adapter.cc 中,有使用 SimpleLoaderSourceAdapter 的簡易雜湊對應表可伺服物件的參考實作範例。您可能會覺得複製 HashmapSourceAdapter,然後修改成符合自身需求的形式會很方便。

HashmapSourceAdapter 的實作包含兩個部分:

  1. 從檔案載入雜湊對應表的邏輯,位於 LoadHashmapFromFile() 中。

  2. 使用 SimpleLoaderSourceAdapter 定義 SourceAdapter,以便根據 LoadHashmapFromFile() 產生雜湊對應表載入器。新的 SourceAdapter 可以從 HashmapSourceAdapterConfig 類型的設定通訊協定訊息中具現化。目前,設定訊息僅包含檔案格式,且為了參考實作範例,僅支援單一簡易格式。

    請注意解構函式中對 Detach() 的呼叫。這個呼叫是避免在拆解狀態與其他執行緒中 Creator lambda 的任何進行中調用之間發生競爭狀況所必要的。(即使這個簡易來源配接器沒有任何狀態,基底類別仍然會強制呼叫 Detach()。)

安排將 YourServable 物件載入管理工具

以下說明如何將新的 YourServable 載入器 SourceAdapter 連結到儲存路徑的基本來源和管理工具 (錯誤處理不佳;實際程式碼應更加謹慎)

首先,建立管理工具

std::unique_ptr<AspiredVersionsManager> manager = ...;

接著,建立 YourServable 來源配接器,並將其插入管理工具

auto your_adapter = new YourServableSourceAdapter(...);
ConnectSourceToTarget(your_adapter, manager.get());

最後,建立簡易路徑來源,並將其插入配接器

std::unique_ptr<FileSystemStoragePathSource> path_source;
// Here are some FileSystemStoragePathSource config settings that ought to get
// it working, but for details please see its documentation.
FileSystemStoragePathSourceConfig config;
// We just have a single servable stream. Call it "default".
config.set_servable_name("default");
config.set_base_path(FLAGS::base_path /* base path for our servable files */);
config.set_file_system_poll_wait_seconds(1);
TF_CHECK_OK(FileSystemStoragePathSource::Create(config, &path_source));
ConnectSourceToTarget(path_source.get(), your_adapter.get());

存取已載入的 YourServable 物件

以下說明如何取得已載入 YourServable 的控制代碼並加以使用

auto handle_request = serving::ServableRequest::Latest("default");
ServableHandle<YourServable*> servable;
Status status = manager->GetServableHandle(handle_request, &servable);
if (!status.ok()) {
  LOG(INFO) << "Zero versions of 'default' servable have been loaded so far";
  return;
}
// Use the servable.
(*servable)->SomeYourServableMethod();

進階:安排讓多個可伺服物件執行個體共用狀態

SourceAdapter 可以存放多個發出的可伺服物件之間共用的狀態。例如:

  • 多個可伺服物件使用的共用執行緒集區或其他資源。

  • 多個可伺服物件使用的共用唯讀資料結構,以避免在每個可伺服物件執行個體中複製資料結構的時間和空間成本。

初始化時間和大小可忽略不計的共用狀態 (例如執行緒集區) 可由 SourceAdapter 搶先建立,然後 SourceAdapter 會在每個發出的可伺服物件載入器中嵌入指向該狀態的指標。昂貴或大型共用狀態的建立作業應延後到第一個適用的 Loader::Load() 呼叫,也就是由管理工具控管。對稱地,對使用昂貴/大型共用狀態的最終可伺服物件發出的 Loader::Unload() 呼叫應將其拆解。