本文档介绍如何使用一种新型可服务对象扩展 TensorFlow Serving。最突出的可服务对象类型是 SavedModelBundle
,但定义其他类型的可服务对象可能很有用,以便服务与您的模型一起提供的数据。例如:词汇表查找表、特征转换逻辑。任何 C++ 类都可以是可服务对象,例如 int
、std::map<string, int>
或您二进制文件中定义的任何类 - 我们将其称为 YourServable
。
为 YourServable
定义 Loader
和 SourceAdapter
要使 TensorFlow Serving 能够管理和服务 YourServable
,您需要定义两件事
一个
Loader
类,用于加载、提供访问权限并卸载YourServable
的实例。一个
SourceAdapter
,用于从某些底层数据格式(例如文件系统路径)实例化加载器。作为SourceAdapter
的替代方案,您可以编写一个完整的Source
。但是,由于SourceAdapter
方法更常见且更模块化,因此我们在此重点介绍它。
在 core/loader.h
中定义了 Loader
抽象。它要求您定义用于加载、访问和卸载您的可服务对象类型的方法。可服务对象加载的数据可以来自任何地方,但通常来自存储系统路径。让我们假设 YourServable
就是这种情况。让我们进一步假设您已经拥有一个您满意的 Source<StoragePath>
(如果没有,请参阅 自定义源 文档)。
除了您的 Loader
之外,您还需要定义一个 SourceAdapter
,用于从给定的存储路径实例化 Loader
。大多数简单的用例可以使用 SimpleLoaderSourceAdapter
类(在 core/simple_loader.h
中)简洁地指定这两个对象。高级用例可以选择使用更低级的 API 分别指定 Loader
和 SourceAdapter
类,例如,如果 SourceAdapter
需要保留一些状态,或者如果需要在 Loader
实例之间共享状态。
在 servables/hashmap/hashmap_source_adapter.cc
中有一个使用 SimpleLoaderSourceAdapter
的简单哈希映射可服务对象的参考实现。您可能会发现复制 HashmapSourceAdapter
并修改它以满足您的需求很方便。
的 HashmapSourceAdapter
实现分为两部分
在
LoadHashmapFromFile()
中从文件加载哈希映射的逻辑。使用
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() 调用应将其拆除。