Este tutorial mostra como usar componentes do TensorFlow Serving para criar o TensorFlow ModelServer padrão que descobre e fornece dinamicamente novas versões de um modelo treinado do TensorFlow. Se você quiser apenas usar o servidor padrão para servir seus modelos, consulte Tutorial básico do TensorFlow Serving .
Este tutorial usa o modelo simples de regressão Softmax apresentado no tutorial do TensorFlow para classificação de imagens manuscritas (dados MNIST). Se você não sabe o que é TensorFlow ou MNIST, consulte o tutorial MNIST para iniciantes em ML .
O código deste tutorial consiste em duas partes:
Um arquivo Python mnist_saved_model.py que treina e exporta diversas versões do modelo.
Um arquivo C++ main.cc que é o TensorFlow ModelServer padrão que descobre novos modelos exportados e executa um serviço gRPC para atendê-los.
Este tutorial percorre as seguintes tarefas:
- Treine e exporte um modelo do TensorFlow.
- Gerencie o controle de versão do modelo com o TensorFlow Serving
ServerCore
. - Configure o lote usando
SavedModelBundleSourceAdapterConfig
. - Servir solicitação com TensorFlow Serving
ServerCore
. - Execute e teste o serviço.
Antes de começar, primeiro instale o Docker
Treinar e exportar modelo do TensorFlow
Primeiro, se ainda não fez isso, clone este repositório em sua máquina local:
git clone https://github.com/tensorflow/serving.git
cd serving
Limpe o diretório de exportação se ele já existir:
rm -rf /tmp/models
Treine (com 100 iterações) e exporte a primeira versão do modelo:
tools/run_in_docker.sh python tensorflow_serving/example/mnist_saved_model.py \
--training_iteration=100 --model_version=1 /tmp/mnist
Treine (com 2.000 iterações) e exporte a segunda versão do modelo:
tools/run_in_docker.sh python tensorflow_serving/example/mnist_saved_model.py \
--training_iteration=2000 --model_version=2 /tmp/mnist
Como você pode ver em mnist_saved_model.py
, o treinamento e a exportação são feitos da mesma forma que no tutorial básico do TensorFlow Serving . Para fins de demonstração, você está intencionalmente diminuindo as iterações de treinamento para a primeira execução e exportando-as como v1, enquanto treina normalmente para a segunda execução e exportando-as como v2 para o mesmo diretório pai - como esperamos que o último alcance melhor precisão de classificação devido ao treinamento mais intensivo. Você deverá ver os dados de treinamento para cada execução de treinamento em seu diretório /tmp/mnist
:
$ ls /tmp/mnist
1 2
ServerCore
Agora imagine que v1 e v2 do modelo sejam gerados dinamicamente em tempo de execução, à medida que novos algoritmos estão sendo experimentados ou à medida que o modelo é treinado com um novo conjunto de dados. Em um ambiente de produção, você pode querer construir um servidor que possa suportar implementação gradual, no qual a v2 possa ser descoberta, carregada, experimentada, monitorada ou revertida enquanto atende a v1. Alternativamente, você pode querer desmontar a v1 antes de abrir a v2. O TensorFlow Serving oferece suporte a ambas as opções: enquanto uma é boa para manter a disponibilidade durante a transição, a outra é boa para minimizar o uso de recursos (por exemplo, RAM).
O TensorFlow Serving Manager
faz exatamente isso. Ele lida com todo o ciclo de vida dos modelos do TensorFlow, incluindo carregamento, veiculação e descarregamento, bem como transições de versão. Neste tutorial, você criará seu servidor sobre um TensorFlow Serving ServerCore
, que encapsula internamente um AspiredVersionsManager
.
int main(int argc, char** argv) {
...
ServerCore::Options options;
options.model_server_config = model_server_config;
options.servable_state_monitor_creator = &CreateServableStateMonitor;
options.custom_model_config_loader = &LoadCustomModelConfig;
::google::protobuf::Any source_adapter_config;
SavedModelBundleSourceAdapterConfig
saved_model_bundle_source_adapter_config;
source_adapter_config.PackFrom(saved_model_bundle_source_adapter_config);
(*(*options.platform_config_map.mutable_platform_configs())
[kTensorFlowModelPlatform].mutable_source_adapter_config()) =
source_adapter_config;
std::unique_ptr<ServerCore> core;
TF_CHECK_OK(ServerCore::Create(options, &core));
RunServer(port, std::move(core));
return 0;
}
ServerCore::Create()
usa um parâmetro ServerCore::Options. Aqui estão algumas opções comumente usadas:
-
ModelServerConfig
que especifica modelos a serem carregados. Os modelos são declarados por meio demodel_config_list
, que declara uma lista estática de modelos, ou por meio decustom_model_config
, que define uma maneira personalizada de declarar uma lista de modelos que podem ser atualizados em tempo de execução. -
PlatformConfigMap
que mapeia do nome da plataforma (comotensorflow
) paraPlatformConfig
, que é usado para criar oSourceAdapter
.SourceAdapter
adaptaStoragePath
(o caminho onde uma versão do modelo é descoberta) ao modelLoader
(carrega a versão do modelo do caminho de armazenamento e fornece interfaces de transição de estado para oManager
). SePlatformConfig
contiverSavedModelBundleSourceAdapterConfig
, será criado umSavedModelBundleSourceAdapter
, que explicaremos mais tarde.
SavedModelBundle
é um componente chave do TensorFlow Serving. Ele representa um modelo do TensorFlow carregado de um determinado caminho e fornece a mesma Session::Run
que o TensorFlow para executar a inferência. SavedModelBundleSourceAdapter
adapta o caminho de armazenamento para Loader<SavedModelBundle>
para que o tempo de vida do modelo possa ser gerenciado por Manager
. Observe que SavedModelBundle
é o sucessor do obsoleto SessionBundle
. Os usuários são incentivados a usar SavedModelBundle
pois o suporte para SessionBundle
será removido em breve.
Com tudo isso, ServerCore
faz internamente o seguinte:
- Instancia um
FileSystemStoragePathSource
que monitora os caminhos de exportação do modelo declarados emmodel_config_list
. - Instancia um
SourceAdapter
usandoPlatformConfigMap
com a plataforma de modelo declarada emmodel_config_list
e conectaFileSystemStoragePathSource
a ele. Dessa forma, sempre que uma nova versão do modelo é descoberta no caminho de exportação, oSavedModelBundleSourceAdapter
a adapta para umLoader<SavedModelBundle>
. - Instancia uma implementação específica do
Manager
chamadaAspiredVersionsManager
que gerencia todas as instânciasLoader
criadas peloSavedModelBundleSourceAdapter
.ServerCore
exporta a interfaceManager
delegando as chamadas paraAspiredVersionsManager
.
Sempre que uma nova versão estiver disponível, este AspiredVersionsManager
carrega a nova versão e, sob seu comportamento padrão, descarrega a antiga. Se quiser começar a personalizar, recomendamos que você entenda os componentes criados internamente e como configurá-los.
Vale ressaltar que o TensorFlow Serving foi projetado do zero para ser muito flexível e extensível. Você pode construir vários plug-ins para personalizar o comportamento do sistema, enquanto aproveita os componentes principais genéricos como ServerCore
e AspiredVersionsManager
. Por exemplo, você pode criar um plug-in de fonte de dados que monitore o armazenamento em nuvem em vez do armazenamento local, ou pode criar um plug-in de política de versão que faça a transição de versão de uma maneira diferente. Na verdade, você pode até criar um plug-in de modelo personalizado que atenda modelos não TensorFlow. Esses tópicos estão fora do escopo deste tutorial. No entanto, você pode consultar os tutoriais de origem customizada e de serviço customizado para obter mais informações.
Lote
Outro recurso típico de servidor que desejamos em um ambiente de produção é o processamento em lote. Aceleradores de hardware modernos (GPUs, etc.) usados para fazer inferência de aprendizado de máquina geralmente alcançam melhor eficiência computacional quando solicitações de inferência são executadas em grandes lotes.
O lote pode ser ativado fornecendo SessionBundleConfig
adequado ao criar o SavedModelBundleSourceAdapter
. Neste caso, definimos BatchingParameters
com valores praticamente padrão. O lote pode ser ajustado definindo valores personalizados de tempo limite, batch_size, etc. Para obter detalhes, consulte BatchingParameters
.
SessionBundleConfig session_bundle_config;
// Batching config
if (enable_batching) {
BatchingParameters* batching_parameters =
session_bundle_config.mutable_batching_parameters();
batching_parameters->mutable_thread_pool_name()->set_value(
"model_server_batch_threads");
}
*saved_model_bundle_source_adapter_config.mutable_legacy_config() =
session_bundle_config;
Ao atingir o lote completo, as solicitações de inferência são mescladas internamente em uma única solicitação grande (tensor) e tensorflow::Session::Run()
é invocado (de onde vem o ganho real de eficiência nas GPUs).
Servir com o gerente
Conforme mencionado acima, o TensorFlow Serving Manager
foi projetado para ser um componente genérico que pode lidar com carregamento, serviço, descarregamento e transição de versão de modelos gerados por sistemas arbitrários de aprendizado de máquina. Suas APIs são construídas em torno dos seguintes conceitos-chave:
Servable : Servable é qualquer objeto opaco que pode ser usado para atender solicitações de clientes. O tamanho e a granularidade de um serviço são flexíveis, de modo que um único serviço pode incluir qualquer coisa, desde um único fragmento de uma tabela de pesquisa até um único modelo aprendido por máquina e uma tupla de modelos. Um serviço pode ser de qualquer tipo e interface.
Versão de serviço : os serviços são versionados e o TensorFlow Serving
Manager
pode gerenciar uma ou mais versões de um serviço. O controle de versão permite que mais de uma versão de um serviço seja carregada simultaneamente, suportando implementação e experimentação graduais.Fluxo de serviço : um fluxo de serviço é a sequência de versões de um serviço, com números de versão crescentes.
Modelo : um modelo aprendido por máquina é representado por um ou mais serviços. Exemplos de serviços são:
- Sessão do TensorFlow ou wrappers em torno deles, como
SavedModelBundle
. - Outros tipos de modelos aprendidos por máquina.
- Tabelas de pesquisa de vocabulário.
- Incorporação de tabelas de pesquisa.
Um modelo composto pode ser representado como vários serviços independentes ou como um único serviço composto. Um serviço também pode corresponder a uma fração de um Modelo, por exemplo, com uma grande tabela de pesquisa fragmentada em muitas instâncias
Manager
.- Sessão do TensorFlow ou wrappers em torno deles, como
Para colocar tudo isso no contexto deste tutorial:
Os modelos do TensorFlow são representados por um tipo de serviço –
SavedModelBundle
.SavedModelBundle
consiste internamente em umtensorflow:Session
emparelhado com alguns metadados sobre qual gráfico é carregado na sessão e como executá-lo para inferência.Há um diretório do sistema de arquivos que contém um fluxo de exportações do TensorFlow, cada uma em seu próprio subdiretório cujo nome é um número de versão. O diretório externo pode ser considerado como a representação serializada do fluxo que pode ser servido para o modelo do TensorFlow que está sendo servido. Cada exportação corresponde a um serviço que pode ser carregado.
AspiredVersionsManager
monitora o fluxo de exportação e gerencia o ciclo de vida de todos os serviçosSavedModelBundle
dinamicamente.
TensorflowPredictImpl::Predict
então apenas:
- Solicita
SavedModelBundle
do gerenciador (por meio do ServerCore). - Usa as
generic signatures
para mapear nomes de tensores lógicos emPredictRequest
para nomes de tensores reais e vincular valores a tensores. - Executa inferência.
Teste e execute o servidor
Copie a primeira versão da exportação para a pasta monitorada:
mkdir /tmp/monitored
cp -r /tmp/mnist/1 /tmp/monitored
Então inicie o servidor:
docker run -p 8500:8500 \
--mount type=bind,source=/tmp/monitored,target=/models/mnist \
-t --entrypoint=tensorflow_model_server tensorflow/serving --enable_batching \
--port=8500 --model_name=mnist --model_base_path=/models/mnist &
O servidor emitirá mensagens de log a cada segundo que dizem "Versão aspirante para serviço ...", o que significa que encontrou a exportação e está rastreando sua existência contínua.
Vamos executar o cliente com --concurrency=10
. Isso enviará solicitações simultâneas ao servidor e, assim, acionará sua lógica de lote.
tools/run_in_docker.sh python tensorflow_serving/example/mnist_client.py \
--num_tests=1000 --server=127.0.0.1:8500 --concurrency=10
O que resulta em uma saída semelhante a:
...
Inference error rate: 13.1%
Em seguida, copiamos a segunda versão da exportação para a pasta monitorada e executamos novamente o teste:
cp -r /tmp/mnist/2 /tmp/monitored
tools/run_in_docker.sh python tensorflow_serving/example/mnist_client.py \
--num_tests=1000 --server=127.0.0.1:8500 --concurrency=10
O que resulta em uma saída semelhante a:
...
Inference error rate: 9.5%
Isso confirma que seu servidor descobre automaticamente a nova versão e a utiliza para servir!