Repositório para distribuição da Stack de ML do Prodest. A finalidade desta Stack é possibilitar a publicação de modelos de ML de forma padronizada através de APIs.
Essa é uma versão standalone da Stack que pode ser utilizada para rodá-la no seu próprio computador, para realização de testes de utilização da Stack e testes de publicação de modelos de ML.
IMPORTANTE: Este procedimento de deploy baseia-se em um modelo de testes que está disponível aqui. Entretanto, se o intuito é testar um modelo próprio, é necessário utilizar a biblioteca mllibprodest (também disponível aqui) e adaptar o código do seu modelo de acordo com as premissas definidas pela biblioteca.
Componentes. São implementados através de containers utilizando o Docker:
- API: Recebe as requisições das aplicações clientes; gera os Jobs; e os enfileira para que os 'Workers Pub' os retirem da fila e processem.
- Queue: Recebe os Jobs gerados pela API e os organiza em filas. Os 'Workers Pub' retiram estes Jobs das respectivas filas; realizam o processamento; e enviam os resultados diretamente para API, sem enfileirá-los.
- Database: Armazena os Jobs gerados pelas requisições das aplicações clientes. Também guarda a estrutura que define qual fila um determinado Worker consumirá.
- Storage: Local onde o 'Model Registry' persiste os artefatos dos modelos registrados.
- Model Registry: Componente onde os modelos e seus artefatos são registrados para serem utilizados pelos Workers Pub e Retrain.
- Worker Pub: Utiliza os modelos registrados para atender aos Jobs gerados e enfileirados pela API, provenientes das requisições das aplicações clientes.
- Worker Retrain: Avalia periodicamente os modelos registrados e, se necessário, faz o retreino e registra o novo modelo no 'Model Registry'. Entretanto, o modelo não entra em produção automaticamente. Antes, deve ser revisado pelo desenvolvedor responsável pelo modelo, e se for o caso, colocado em produção (conforme explicado no passo 2, mais adiante).
NOTA: Por conta das especificidades de cada modelo de ML, a implementação dos workers Pub e Retrain é de responsabilidade do desenvolvedor do modelo que deve seguir a padronização definida na biblioteca de ML do Prodest (mllibprodest).
Interações:
A imagem abaixo ilustra as interações entre os componentes da Stack (à esquerda); e entre a aplicação cliente e a Stack (à direita). Cabe observar que:
- Nem todos os componentes da Stack se comunicam, por exemplo, somente a API interage com o componente Database. Por outro lado, o Model Registry não se comunica com o componente Database nem com o Queue.
- Os workers Retrain não se comunicam com a API.
- Apesar de não estar representado na imagem (para simplificar), os workers Pub e Retrain se comunicam com o componente de Storage para salvar os artefatos na rotina de registro dos modelos.
- A interação entre a aplicação cliente e a Stack acontece assincronamente, onde a aplicação cliente envia a requisição para a API e recebe o ID do Job para consultar o status posteriormente. Dessa forma, a aplicação cliente não ficará bloqueada aguardando retorno da API.
- As requisições de status e para feedback são atendidas diretamente pela API, ou seja, não há necessidade de enfileiramento.
NOTA: Também para simplificar, nem todas as interações da aplicação cliente com a API foram representadas, porém, a lógica é a mesma para a maioria das interações: O cliente envia a requisição; recebe o ID do Job; e consulta posteriormente para verificar se o processamento do job terminou, caso tenha terminado, recebe a resposta.
- Computador com sistema Linux; ou Windows utilizando WSL - Windows Subsystem for Linux.
- Python >= 3.10. Instruções: Linux (Geralmente já vem instalado por padrão) ou Windows.
- Docker: Caso ainda não possua em seu computador, será necessário instalar. DICA: Instale o Docker dentro do Linux do WSL, utilizando o procedimento, por exemplo, para o Ubuntu (distribuição padrão do WSL). Caso tenha escolhido outra distribuição, utilize o procedimento específico para ela (escolha aqui).
NOTA: Se o seu computador navega na Internet através de um proxy, será necessário fazer estas configurações: no Linux (WSL) e Docker, ambas via linha de comando. Porém, alterando as configurações conforme as informações do proxy da sua rede e de suas credenciais de usuário, caso o proxy solicite autenticação.
O Portainer é, em resumo, uma interface WEB para gerenciamento de ambientes Docker. Caso queira simplificar a gestão dos containers da Stack e não se ater muito à linha de comandos do Docker, provisione o Portainer. Para isso será preciso rodar os comandos Docker abaixo (não dá para fugir totalmente da linha de comando!):
Crie o volume para o Portainer persistir os dados.
docker volume create portainer_data_4a75ef64b5
Provisione o Portainer.
docker run -d -p 8000:8044 -p 9443:9443 --name portainer_4a75ef64b5 --restart=always -v /var/run/docker.sock:/var/run/docker.sock -v portainer_data_4a75ef64b5:/data portainer/portainer-ce:latest
Depois do deploy, acesse o Portainer: https://localhost:9443
Para fazer o setup inicial siga as instruções em: https://docs.portainer.io/start/install-ce/server/setup
Depois do setup inicial, na tela principal (Quick Setup); clique em 'Get Started' e em seguida clique em 'local' (fica ao lado da logo azul do Docker). Do lado esquerdo da tela clique em 'Containers', para visualizar os containers que estão rodando no computador (observe que o próprio Portainer é um container!).
NOTA: O procedimento para subir a Stack é baseado na linha de comandos do Docker e Shell Script, porém os comandos já estão prontos e apenas será preciso copiar e colar. Adicionalmente, utilizando o Portainer, será mais fácil acompanhar os containers rodando no ambiente e executar atividades simples de start / stop / restart; verificar gráficos de utilização e logs dos containers, etc. Explore a interface do Portainer e veja as possibilidades de uso!
Clone a release mais atual.
git clone https://github.com/prodest/prodest-ml-stack.git
Ou, se for extremamente necessário, escolha outra mais antiga. No comando abaixo, substitua 1.6.8 (que é a tag da release mais atual) pela tag da release que deseja clonar.
git clone -b 1.6.8 --single-branch https://github.com/prodest/prodest-ml-stack.git
ATENÇÃO: Se for fazer os testes utilizando um modelo próprio; ou um que foi disponibilizado para publicação:
- Descompacte o arquivo 'publicar.zip';
- Copie a pasta publicar (a pasta toda, não somente o conteúdo dela) para dentro da pasta prodest-ml-stack;
- Ao seguir o passo 1.2, responda 'no' para a pergunta 'Deseja clonar e utilizar o modelo de exemplo...', feita pelo script build.sh, e continue seguindo as instruções dele.
cd prodest-ml-stack
./build.sh
Se o processo de build ocorrer com sucesso, no final você terá uma tela conforme abaixo. Caso o processo falhe, acompanhe as mensagens de erro e atenda ao que for solicitado. Em caso de erro, o script build.sh fará uma limpeza do ambiente para evitar que containers e imagens que foram criados no processo e não estão sendo utilizados, fiquem ocupando espaço no seu computador.
NOTA: Obtenha as credenciais para acesso aos recursos da Stack através do arquivo 'credentials_stack.txt'. Ele se encontra no caminho: '../temp_builder/' (considerando que a pasta atual seja 'Stack').
Abra outro terminal; entre na pasta criada no processo de clonagem do repositório e rode os comandos de lá. Dessa forma você conseguirá seguir os passos em um terminal e acompanhar os logs em outro.
cd stack
./docker-compose logs -f --tail 1 &
Considerando que: A rotina de treinamento é executada esporadicamente; e que a economia de recursos computacionais é bem-vinda; os containers stack-mltraining-model-1 (treinamento) e stack-worker-retrain-1 (retreino) estão parados e deverão ser iniciados somente para fazer o treino/retreino e, logo após, serem parados novamente.
Ao executar o comando abaixo, o container é iniciado; o treino é executado; e o container é parado.
docker start stack-mltraining-model-1 && docker exec stack-mltraining-model-1 python /training_model/train.py && docker stop stack-mltraining-model-1
Se o treino/retreino forem ok, uma mensagem informando o sucesso será exibida na tela.
ATENÇÃO: Se o treino e/ou retreino derem erro por falta de capacidade de memória RAM/CPU (o script simplesmente para sem apresentar a mensagem de sucesso), acesse a console do container de treinamento, conforme passo opcional 2.2, edite o arquivo 'configs.py' e altere o parâmetro 'qtd_exemplos' de 50000 para um valor menor que possibilite que o seu computador rode o exemplo de modelo.
nano configs.py
Para sair do editor nano: Pressione CTRL+x; digite y e pressione ENTER para salvar as alterações.
Após alterar a configuração, saia do container (exit); rode novamente o comando para realizar o treino; registre o novo modelo e altere o 'Stage' para 'Production'.
- Acesse o MLflow (http://localhost:5000) e clique no experimento CLF-Cyberbullying_Tweets;
- Clique no link para o experimento que está na coluna 'Run Name';
- Clique no botão 'Register Model' e escolha a opção 'Create New Model';
- Dê o nome CLF_CYBER_BULLYING_TWEETS para o modelo e clique em 'Register';
- Na barra superior clique em 'Models';
- Clique no link para a última versão do modelo que está em 'Latest Version';
- Na opção 'Aliases', clique em 'Add';
- Digite production e clique em 'Save aliases'.
Rode o comando abaixo para acessar a console do container.
docker start stack-mltraining-model-1 && docker exec -it stack-mltraining-model-1 bash
# Altere configurações; verifique logs; rode os comandos que desejar; etc.
Após encerrar a utilização, rode os comandos para sair e parar o container.
exit
docker stop stack-mltraining-model-1
3.1. Acesse o RabbitMQ (http://localhost:15672)
Utilize as credenciais criadas no processo de deploy da Stack para acessar; clique em 'Queues and Streams' e verifique que ainda NÃO existem filas criadas.
Este serviço subiu, mas depois caiu porque o modelo, que nesse exemplo é CLF_CYBER_BULLYING_TWEETS, ainda não havia sido treinado e registrado no MLflow.
Certifique-se de que a pasta atual seja prodest-ml-stack; e execute o comando para entrar na pasta stack.
cd stack
Rode o comando para subir o serviço.
./docker-compose restart worker-pub
Consulte novamente a aba 'Queues and Streams' e perceba que: após o reinício do serviço worker-pub, uma fila foi criada.
Substitua os valores das chaves abaixo, que serão usadas nas chamadas utilizando o client curl, como segue:
- 'Authorization': substitua COLE_AQUI_O_TOKEN pelo token de acesso à API (está no arquivo 'credentials_stack.txt').
- 'model_name': troque COLE_AQUI_O_NOME_DO_MODELO pelo nome do modelo que será utilizado para atender às requisições. Nesse exemplo, será CLF_CYBER_BULLYING_TWEETS
- 'job_id': altere COLE_AQUI_O_JOB_ID, conforme valor retornado nas requisições feitas à API.
Abra um prompt de comando ou terminal; copie e cole os exemplos a seguir (passos 3.3.x) para testar as requisições. Acompanhe os logs através do terminal aberto no passo 1.3.
Envie esta requisição e observe o valor retornado na chave 'job_id' para consultar o status utilizando o passo 3.3.2.
curl -X 'POST' \
'http://localhost:8080/inference' \
-H 'accept: application/json' \
-H 'Authorization: Bearer COLE_AQUI_O_TOKEN' \
-H 'Content-Type: application/json' \
-d '{
"model_name": "COLE_AQUI_O_NOME_DO_MODELO",
"features": [
"yes exactly the police can murder black people and we can be okay with it because it’s in the past and they’re dead now."
],
"method": "predict"
}'
Utilize o exemplo de requisição abaixo para obter o status/resposta referente ao processamento dos jobs.
curl -X 'POST' \
'http://localhost:8080/status' \
-H 'accept: application/json' \
-H 'Authorization: Bearer COLE_AQUI_O_TOKEN' \
-H 'Content-Type: application/json' \
-d '{
"job_id": "COLE_AQUI_O_JOB_ID"
}'
Aqui também é preciso observar a chave 'job_id' para consultar o status/resposta.
curl -X 'POST' \
'http://localhost:8080/inference' \
-H 'accept: application/json' \
-H 'Authorization: Bearer COLE_AQUI_O_TOKEN' \
-H 'Content-Type: application/json' \
-d '{
"model_name": "COLE_AQUI_O_NOME_DO_MODELO",
"method": "info"
}'
Observe a chave 'job_id' para consultar o status/resposta.
curl -X 'POST' \
'http://localhost:8080/inference' \
-H 'accept: application/json' \
-H 'Authorization: Bearer COLE_AQUI_O_TOKEN' \
-H 'Content-Type: application/json' \
-d '{
"model_name": "COLE_AQUI_O_NOME_DO_MODELO",
"features": [
"Today’s society so sensitive it’s sad they joke about everything but they take out the gay jokes before race, rape, and other 'sensitive' jokes",
"aposto que vou sofrer bullying depois do meu próximo tweet"
],
"targets": [
"gender",
"not_cyberbullying"
],
"method": "evaluate"
}'
O campo 'feedback' deve ser preenchido com um ou mais labels que o usuário acredita que estão corretos. Os labels devem ser os mesmos utilizados no treinamento (mesma escrita e tipo de 'caixa' das letras', case sensitive). No caso de um usuário perceber um label no qual o modelo não foi treinado, a instrução é procurar o responsável pelo modelo.
NOTA: A quantidade de labels dever ser a mesma respondida no retorno da requisição que gerou o 'job_id' que está sendo consultado. Nesse exemplo específico, está sendo informado o feedback para o retorno da primeira requisição de teste (passo 3.3.1).
curl -X 'POST' \
'http://localhost:8080/feedback' \
-H 'accept: application/json' \
-H 'Authorization: Bearer COLE_AQUI_O_TOKEN' \
-H 'Content-Type: application/json' \
-d '{
"job_id": "COLE_AQUI_O_JOB_ID",
"feedback": [
"ethnicity"
]
}'
Nesse caso, não é gerado um 'job_id', pois a API responde diretamente.
Observe a chave 'job_id' para consultar o status/resposta, de acordo com passo 3.3.2.
curl -X 'POST' \
'http://localhost:8080/get_feedback' \
-H 'accept: application/json' \
-H 'Authorization: Bearer COLE_AQUI_O_TOKEN' \
-H 'Content-Type: application/json' \
-d '{
"model_name": "COLE_AQUI_O_NOME_DO_MODELO",
"initial_date": "dd/mm/yyyy",
"end_date": "dd/mm/yyyy"
}'
NOTA: Também é possivel executar os seis testes anteriores (3.3.x) através da 'Swagger UI' da API (http://localhost:8080/docs). Lembre-se de informar o token de acesso através do botão de autorização (ícone de cadeado aberto) antes de testar os endpoints.
O container stack-worker-retrain-1 (retreino), assim como o de treinamento, deve ficar parado e ser iniciado somente para fazer o retreino e ser parado novamente.
Ao executar o comando abaixo, o container é iniciado; o retreino é executado; e o container é parado.
docker start stack-worker-retrain-1 && docker exec stack-worker-retrain-1 python /worker_retrain/retrain_46b1c135cdef278ddc3b2.py && docker stop stack-worker-retrain-1
NOTA: Caso deseje automatizar esta rotina de retreino, basta criar um agendamento no sistema operacional; incluir os comandos acima; e escolher a frequência de execução (diária, semanal, etc.).
- Acesse o MLflow (http://localhost:5000) e clique no experimento CLF-Cyberbullying_Tweets;
- Observe se na coluna 'Run Name' tem uma execução chamada 'self_training'.
Se a nova execução foi criada com sucesso, ela é passível de ser registrada (conforme descrito no passo 2.1) como um novo modelo no estágio de produção, para ser utilizado pela API.
Rode o comando abaixo para acessar a console do container.
docker start stack-worker-retrain-1 && docker exec -it stack-worker-retrain-1 bash
# Verifique logs, rode os comandos que desejar, etc.
Após encerrar a utilização, rode os comandos para sair e parar o container.
exit
docker stop stack-worker-retrain-1
A execução desses passos não é necessária para o deploy da Stack, porém, caso você tenha interesse de explorar um pouco mais, siga em frente!
curl http://localhost:8080/version
Utilize os comandos abaixo para fazer uma verificação da utilização e dos dados salvos no banco de dados.
NOTA: Esses comandos são simples e permitem fazer uma interação bem básica com o banco de dados. Acesse a documentação completa para mais informações.
docker exec -it stack-database-1 bash
Utilize o cliente mongosh para abrir uma conexão com o banco (será solicitado a senha).
mongosh mongodb://localhost --username NOME_DO_USUÁRIO
Verifique o tamanho atual das bases de dados. Observe o tamanho da base 'ml_api_db'.
show dbs
Rode os comandos abaixo para: Usar o banco de dados da API; listar todos os registros de fila; e listar todos os jobs gerados pelas chamadas à API.
use ml_api_db
db.col_queue_registry.find({})
db.col_jobs.find({})
Verifique a quantidade de jobs que estão armazenados.
db.col_jobs.countDocuments()
Verifique os 4 últimos jobs armazenados.
db.col_jobs.find().sort({$natural: -1}).limit(4)
(Opcional) Criação de um usuário, com permissão somente para leitura, para consulta dos dados. Copie o comando abaixo e cole na console do mongosh. Crie uma senha para o usuário, conforme solicitado.
db.createUser(
{
user: "ro_user",
pwd: passwordPrompt(),
roles: [ { role: "read", db: "ml_api_db" }]
}
)
Finalize a sessão do mongosh.
exit
Se você criou o usuário 'ro_user', teste o login com o comando abaixo:
mongosh mongodb://localhost --authenticationDatabase 'ml_api_db' --username ro_user
Finalize a interação.
exit
exit # sai da console do container
Se o deploy da Stack ocorreu com sucesso e se você quiser saber o desempenho dela, dada certa carga de trabalho, execute as instruções abaixo para realizar um teste simples de stress.
- Abra a pasta 'prodest-ml-stack/optional';
- Edite o arquivo de configuração 'test_configs.py' e informe o token da API (leia os comentários do arquivo e, se desejar, altere também outros parâmetros);
- Salve as alterações realizadas no arquivo 'test_configs.py';
- Abra um prompt de comando ou terminal;
- Entre na pasta 'prodest-ml-stack/optional';
- Crie e ative um ambiente virtual Python, conforme instruções: Linux e Windows (escolha o sistema na página);
- Atualize o pip e o setuptools;
- Instale o pacote requests;
pip install --upgrade pip setuptools
pip install requests==2.31.0
- Execute o script que fará o teste de stress; observe a execução e as mensagens na tela.
NOTA: Edite o script 'simple_stress_testing.py' e leia os comentários no início dele para obter informações sobre as métricas utilizadas no teste.
python simple_stress_testing.py
Se você instalou o Portainer, utilize-o para monitorar as estatísticas dos containers durante o teste (principalmente as do Worker Pub).
- Do lado esquerdo da interface do Portainer, clique em Containers e em seguida clique no ícone (destaque em amarelo) para abrir as estatísticas do Worker Pub. Faça o mesmo para outros containers que você deseja monitorar.
Utilize o Portainer para monitorar logs; abrir a console dos containers; criar outras cópias dos Workers Pub para testar o desempenho; etc. Explore bem a interface!
NOTA: Dependendo da intensidade dos testes, por exemplo: valor de DELAY muito pequeno (verifique essa opção no arquivo de configuração 'test_configs.py'), podem ocorrer reinicializações do container Worker Pub por conta de exaustão de recursos computacionais. Se isso ocorrer, diminua o valor do delay para que o Worker Pub consiga atender às requisições. Outra alternativa é aumentar a quantidade de Workers Pub através do Portainer (clique no container stack-worker-pub-1; depois clique na opção para 'Duplicar/Editar'; altere o nome para stack-worker-pub-2 e clique no botão para fazer o depoly do container). Após o deploy, haverá uma cópia do Worker Pub para auxiliá-lo no atendimento das requisições.
No ciclo de vida do container Worker PUB estão previstos alguns status que são referentes às versões dos modelos que estão em produção. Estes status podem ser consultados:
Via linha de comando:
docker ps -a
Ou através do Portainer. Segue abaixo uma breve descrição de cada um desses status:
- starting: Indica que o container foi iniciado (já consegue processar os jobs), mas ainda não ocorreu a verificação das versões dos modelos. A primeira verificação ocorrerá, aproximadamente, 12 minutos após o início do container.
- healthy: O container foi iniciado e todos os modelos estão na sua versão mais atual. Este status será o padrão enquanto o container estiver rodando e os modelos estiverem atualizados.
- unhealthy: Indica que o container Worker PUB está rodando com um ou mais modelos desatualizados e precisa ser reiniciado para carregar os modelos atualizados.
Para verificar o motivo do status estar na condição unhealthy:
Via linha de comando:
docker inspect stack-worker-pub-1
Para obter os detalhes que levaram a este status unhealthy, procure na saida do comando acima pela chave "Health": {.
Utilizando o Portainer:
Clique no nome do container, conforme destacado em verde na figura anterior, e procure a seção 'Container health'; na caixa 'Last output', verifique a mensagem que informa o motivo do status está como unhealthy.
Caso o Worker PUB esteja com status unhealthy, rode o comando abaixo para reiniciar o container e carregar a versão mais atual dos modelos:
NOTA: Este comando deve ser executado de dentro da pasta prodest-ml-stack/stack/.
./docker-compose restart worker-pub
-
Documentação da API: http://localhost:8080/docs
-
Model Registry (MLflow): http://localhost:5000
-
Console de administração do Storage (Minio): http://localhost:9001
-
Queue (RabbitMQ): http://localhost:15672
./destroy.sh