Este documento explica como estender o TensorFlow Serving para monitorar diferentes sistemas de armazenamento para descobrir novos (versões de) modelos ou dados para servir. Em particular, aborda como criar e usar um módulo que monitora um caminho de sistema de armazenamento para o aparecimento de novos subcaminhos, onde cada subcaminho representa uma nova versão utilizável para carregar. Esse tipo de módulo é chamado de Source<StoragePath>
, porque emite objetos do tipo StoragePath
(typedefed para string
). Ele pode ser composto com um SourceAdapter
que cria um Loader
utilizável a partir de um determinado caminho que a fonte descobre.
Primeiro, uma nota sobre generalidade
Não é necessário usar caminhos como identificadores para dados utilizáveis; apenas ilustra uma maneira de ingerir serviços no sistema. Mesmo que seu ambiente não encapsula dados utilizáveis em caminhos, este documento irá familiarizá-lo com as principais abstrações. Você tem a opção de criar módulos Source<T>
e SourceAdapter<T1, T2>
para tipos adequados ao seu ambiente (por exemplo, mensagens RPC ou pub/sub, registros de banco de dados) ou simplesmente criar um Source<std::unique_ptr<Loader>>
monolítico Source<std::unique_ptr<Loader>>
que emite carregadores utilizáveis diretamente.
É claro que, qualquer que seja o tipo de dados que sua fonte emite (sejam caminhos POSIX, caminhos do Google Cloud Storage ou identificadores RPC), é necessário que haja módulo(s) de acompanhamento que sejam capazes de carregar serviços com base nisso. Esses módulos são chamados SourceAdapters
. A criação de um personalizado é descrita no documento Custom Servable . O TensorFlow Serving vem com um para instanciar sessões do TensorFlow com base em caminhos em sistemas de arquivos compatíveis com o TensorFlow. Pode-se adicionar suporte para sistemas de arquivos adicionais ao TensorFlow estendendo a abstração RandomAccessFile
( tensorflow/core/public/env.h
).
Este documento se concentra na criação de uma fonte que emite caminhos em um sistema de arquivos compatível com TensorFlow. Ele termina com um passo a passo sobre como usar sua fonte em conjunto com módulos pré-existentes para servir modelos do TensorFlow.
Criando sua fonte
Temos uma implementação de referência de Source<StoragePath>
, chamada FileSystemStoragePathSource
(em sources/storage_path/file_system_storage_path_source*
). FileSystemStoragePathSource
monitora um caminho específico do sistema de arquivos, observa subdiretórios numéricos e relata o mais recente deles como a versão que deseja carregar. Este documento aborda os aspectos mais importantes de FileSystemStoragePathSource
. Você pode achar conveniente fazer uma cópia de FileSystemStoragePathSource
e modificá-la para atender às suas necessidades.
Primeiro, FileSystemStoragePathSource
implementa a API Source<StoragePath>
, que é uma especialização da API Source<T>
com T
vinculado a StoragePath
. A API consiste em um único método SetAspiredVersionsCallback()
, que fornece um fechamento que a fonte pode invocar para comunicar que deseja que um determinado conjunto de versões utilizáveis seja carregado.
FileSystemStoragePathSource
usa o retorno de chamada de versões aspiradas de uma maneira muito simples: ele inspeciona periodicamente o sistema de arquivos (fazendo um ls
, essencialmente) e se encontrar um ou mais caminhos que se pareçam com versões utilizáveis, ele determina qual é a versão mais recente e invoca o retorno de chamada com uma lista de tamanho um contendo apenas essa versão (na configuração padrão). Portanto, a qualquer momento, FileSystemStoragePathSource
solicita no máximo um serviço para ser carregado, e sua implementação aproveita a idempotência do retorno de chamada para se manter sem estado (não há mal nenhum em invocar o retorno de chamada repetidamente com os mesmos argumentos).
FileSystemStoragePathSource
possui uma fábrica de inicialização estática (o método Create()
), que recebe uma mensagem do protocolo de configuração. A mensagem de configuração inclui detalhes como o caminho base a ser monitorado e o intervalo de monitoramento. Também inclui o nome do fluxo utilizável a ser emitido. (Abordagens alternativas podem extrair o nome do fluxo de serviço do caminho base, para emitir vários fluxos de serviço com base na observação de uma hierarquia de diretórios mais profunda; essas variantes estão além do escopo da implementação de referência.)
A maior parte da implementação consiste em um thread que examina periodicamente o sistema de arquivos, juntamente com alguma lógica para identificar e classificar quaisquer subcaminhos numéricos que descobrir. O thread é iniciado dentro de SetAspiredVersionsCallback()
(não em Create()
) porque esse é o ponto em que a fonte deve "iniciar" e sabe para onde enviar solicitações de versão aspirada.
Usando sua fonte para carregar sessões do TensorFlow
Você provavelmente desejará usar seu novo módulo de origem em conjunto com SavedModelBundleSourceAdapter
( servables/tensorflow/saved_model_bundle_source_adapter*
), que interpretará cada caminho que sua fonte emite como uma exportação do TensorFlow e converterá cada caminho em um carregador para um TensorFlow SavedModelBundle
utilizável. Você provavelmente conectará o adaptador SavedModelBundle
a um AspiredVersionsManager
, que se encarrega de realmente carregar e servir os serviços. Uma boa ilustração de encadeamento desses três tipos de módulos para obter uma biblioteca de servidor funcional é encontrada em servables/tensorflow/simple_servers.cc
. Aqui está um passo a passo do fluxo de código principal (com tratamento incorreto de erros; o código real deve ser mais cuidadoso):
Primeiro, crie um gerente:
std::unique_ptr<AspiredVersionsManager> manager = ...;
Em seguida, crie um adaptador de origem SavedModelBundle
e conecte-o ao gerenciador:
std::unique_ptr<SavedModelBundleSourceAdapter> bundle_adapter;
SavedModelBundleSourceAdapterConfig config;
// ... populate 'config' with TensorFlow options.
TF_CHECK_OK(SavedModelBundleSourceAdapter::Create(config, &bundle_adapter));
ConnectSourceToTarget(bundle_adapter.get(), manager.get());
Por último, crie sua fonte de caminho e conecte-a ao adaptador SavedModelBundle
:
auto your_source = new YourPathSource(...);
ConnectSourceToTarget(your_source, bundle_adapter.get());
A função ConnectSourceToTarget()
(definida em core/target.h
) apenas invoca SetAspiredVersionsCallback()
para conectar um Source<T>
a um Target<T>
(um Target
é um módulo que captura solicitações de versão aspirada, ou seja, um adaptador ou gerenciador ).