创建一种新型可服务对象

本文档介绍如何使用一种新型可服务对象扩展 TensorFlow Serving。最突出的可服务对象类型是 SavedModelBundle,但定义其他类型的可服务对象可能很有用,以便服务与您的模型一起提供的数据。例如:词汇表查找表、特征转换逻辑。任何 C++ 类都可以是可服务对象,例如 intstd::map<string, int> 或您二进制文件中定义的任何类 - 我们将其称为 YourServable

YourServable 定义 LoaderSourceAdapter

要使 TensorFlow Serving 能够管理和服务 YourServable,您需要定义两件事

  1. 一个 Loader 类,用于加载、提供访问权限并卸载 YourServable 的实例。

  2. 一个 SourceAdapter,用于从某些底层数据格式(例如文件系统路径)实例化加载器。作为 SourceAdapter 的替代方案,您可以编写一个完整的 Source。但是,由于 SourceAdapter 方法更常见且更模块化,因此我们在此重点介绍它。

core/loader.h 中定义了 Loader 抽象。它要求您定义用于加载、访问和卸载您的可服务对象类型的方法。可服务对象加载的数据可以来自任何地方,但通常来自存储系统路径。让我们假设 YourServable 就是这种情况。让我们进一步假设您已经拥有一个您满意的 Source<StoragePath>(如果没有,请参阅 自定义源 文档)。

除了您的 Loader 之外,您还需要定义一个 SourceAdapter,用于从给定的存储路径实例化 Loader。大多数简单的用例可以使用 SimpleLoaderSourceAdapter 类(在 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 对象

以下是将新的 SourceAdapter 连接到 YourServable 加载器以获取基本存储路径源和管理器(具有错误的错误处理;实际代码应更加谨慎)的方法。

首先,创建一个管理器

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();

高级:安排多个可服务实例共享状态

SourceAdapters 可以容纳在多个发出的可服务实例之间共享的状态。例如

  • 多个可服务实例使用的共享线程池或其他资源。

  • 多个可服务实例使用的共享只读数据结构,以避免在每个可服务实例中复制数据结构的时间和空间开销。

初始化时间和大小可以忽略不计的共享状态(例如线程池)可以由 SourceAdapter 提前创建,然后在每个发出的可服务加载器中嵌入指向它的指针。昂贵或大型共享状态的创建应推迟到第一个适用的 Loader::Load() 调用,即由管理器控制。对称地,对使用昂贵/大型共享状态的最后一个可服务实例的 Loader::Unload() 调用应将其拆除。