Criando um módulo que descobre novos caminhos de serviço

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 de 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 encerramento 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 é lançado 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 ).