O blog da AWS

Obtenha uma taxa de transferência 12 vezes maior e menor latência em aplicativos usando framework de Linguagem Natural PyTorch com AWS Inferentia

Por Fabio Nonato de Paula, Senior Manager do time Arquitetos de Soluções na AWS e
Mahadevan Balasubramaniam , Principal Solutions Architect para o time de Computação Autônoma  na AWS

 

Clientes da AWS como Snap, Alexa e Autodesk têm usado o AWS Inferentia para obter o mais alto desempenho e menor custo em uma ampla variedade de aplicações de Aprendizado de Máquina. Os modelos de processamento de linguagem natural (NLP) estão crescendo em popularidade para casos de uso em tempo real ou de processamento em massa offline. Nossos clientes implementam esses modelos em aplicativos como chatbots de suporte, busca e pesquisa, classificação, resumo de documentos e compreensão de linguagem natural. Com a AWS Inferentia, você também pode obter alto desempenho e o menor custo em modelos NLP de código fonte aberto sem a necessidade de adequações.

Você aprenderá nesse artigo como maximizar a taxa de transferência para aplicativos em tempo real com latência baixa e que processam dados em massa, nos cenários que maximizar a taxa de transferência e diminuir custo são as principais metas de desempenho. Você implementará uma solução baseada em NLP usando modelos pré-treinados BERT Base do HuggingFace Transformers , sem modificações no modelo ou no código PyTorch utilizado. A solução alcança uma taxa de transferência 12 vezes maior a um custo 70% menor usando AWS Inferentia em comparação com a implementação do mesmo modelo aplicado em GPUs.

 

 

Para maximizarmos o desempenho da inferência dos modelos HuggingFace no AWS Inferentia, você utiliza o AWS Neuron para PyTorch. AWS Neuron é um kit de desenvolvimento de software (SDK) que se integra à frameworks populares de ML como TensorFlow e PyTorch, extendendo essas APIs para que você possa executar inferência de alto desempenho de forma fácil e econômica em instâncias Amazon EC2 Inf1. Com uma alteração mínima de código, você pode compilar e otimizar seus modelos pré-treinados para serem executados no AWS Inferentia. A equipe do AWS Neuron está constantemente lançando atualizações com novos recursos e melhor desempenho de modelos. Com a versão v1.13, o desempenho dos modelos baseados em transformadores melhorou em 10% a 15% adicionais, ultrapassando os limites de latência mínima e taxa de transferência máxima, mesmo para cargas de trabalho de NLP maiores.

Para testar os recursos do Neuron SDK, confira as mais recentes funcionalidades utilizando Neuron para PyTorch.

O modo Pipeline NeuronCore explicado

Cada chip AWS Inferentia, disponível por meio da família de instâncias Inf1, contém quatro NeuronCores. As opções de instância disponíveis fornecem de 1 a 16 chips, totalizando 64 NeuronCores no maior tamanho de instância, a inf1.24xlarge. O NeuronCore é uma unidade de computação que executa as operações do grafo de Rede Neural (NN).

Quando você compila um modelo sem o modo Pipeline, o compilador Neuron otimiza as operações NN suportadas para executar em um único NeuronCore. Você pode combinar os NeuronCores em grupos, mesmo em chips AWS Inferentia distintos, para executar o seu modelo compilado. Essa configuração permite que você use vários NeuronCores em vários chips AWS Inferentia em paralelo. Isso significa que, mesmo no menor tamanho de instância, quatro modelos podem estar ativos a qualquer momento. A implementação de paralelismo de dados para quatro (ou mais) modelos fornece a maior taxa de transferência e o menor custo na maioria dos casos. Esse aumento de desempenho vem com um mínimo impacto na latência, pois o AWS Inferentia é otimizado para maximizar a taxa de transferência em processamentos em lote pequenos.

Com o modo Pipeline, o compilador Neuron otimiza o particionamento e a colocação de um único grafo de rede neural através de um número solicitado de NeuronCores, em um processo completamente automático. Ele permite um uso eficiente do hardware porque os NeuronCores no pipeline executam solicitações de inferência em modo streaming, usando uma memória cache rápida no chip para guardar os parâmetros do modelo. Quando um dos núcleos do pipeline termina o processamento da sua primeira solicitação, ele pode começar a processar as solicitações seguintes, sem esperar que o último núcleo conclua o processamento da primeira solicitação. Essa inferência feita via pipeline em streaming aumenta a utilização de cada núcleo do processador, mesmo quando executa inferências de processamentos em lote reduzidos de aplicativos em tempo real, como um processamento de lote 1.

Encontrar o número ideal de NeuronCores para um único modelo grande é um processo empírico. Um bom ponto de partida é usar a seguinte fórmula aproximada, porém recomendamos experimentar várias configurações para alcançar uma implementação ideal:

neuronCore_pipeline_cores = 4*round(number-of-weights-in-model/(2E7))

O compilador usa a configuração neuroncore-pipeline-cores diretamente. Para habilitar o modo pipeline, adicione o argumento ao fluxo de compilação usual da estrutura desejada.

Com o TensorFlow Neuron, use o seguinte código:

import numpy as np 
import tensorflow.neuron as tfn
 
example_input = np.zeros([1,224,224,3], dtype='float16') 
tfn.saved_model.compile("<Path to your saved model>",
                        "<Path to write compiled model>/1", 
                        model_feed_dict={'input_1:0' : example_input }, 
                        compiler_args = ['--neuroncore-pipeline-cores', '8'])

Com o PyTorch Neuron, use o seguinte código:

import torch 
import torch_neuron 

model = torch.jit.load(<Path to your traced model>) 
inputs = torch.zeros([1, 3, 224, 224], dtype=torch.float32) 
model_compiled = torch.neuron.trace(model, 
                                    example_inputs=inputs, 
                                    compiler_args = ['--neuroncore-pipeline-cores', '8'])

Para obter mais informações sobre o NeuronCore Pipeline e outros recursos do Neuron, consulte a nossa documentação sobre Neuron Features.

 

Execute modelos de resposta a perguntas do HuggingFace na AWS Inferentia

Para executar um modelo Hugging Face BertForQuestionAnswering no AWS Inferentia, você só precisa adicionar uma única linha extra de código à implementação usual dos Transformers, além de importar o framework torch_neuron . Você pode adaptar o seguinte exemplo da documentação de acordo com o seguinte trecho de código:

from transformers import BertTokenizer, BertForQuestionAnswering 
import torch 
import torch_neuron 

tokenizer = BertTokenizer.from_pretrained('twmkn9/bert-base-uncased-squad2') 
model = BertForQuestionAnswering.from_pretrained('twmkn9/bert-base-uncased-squad2',return_dict=False) 

question, text = "Who was Jim Henson?", "Jim Henson was a nice puppet" 
inputs = tokenizer(question, text, return_tensors='pt') 

neuron_model = torch.neuron.trace(model, 
                                  example_inputs = (inputs['input_ids'],inputs['attention_mask']), 
                                  verbose=1) 
                                  
outputs = neuron_model(*(inputs['input_ids'],inputs['attention_mask']))

A única linha extra no código é a chamada para o método toch.neuron.trace() . Esta chamada compila o modelo e retorna um novo método neuron_model() que você pode usar para executar inferência com as entradas originais, como mostrado na última linha do script. Se você quiser testar este exemplo, consulte a documentação PyTorch Hugging Face pré-treinado BERT Tutorial.

A capacidade de compilar e executar inferência usando modelos pré-treinados — ou mesmo ajustados, como no exemplo de código anterior — diretamente do repositório do modelo Hugging Face é o passo inicial para otimizar implementações em produção. Este primeiro passo já pode produzir desempenho duas vezes maior com custo 70% menor quando comparado a uma alternativa usando GPU (que discutiremos mais adiante neste artigo). Quando você combina os recursos NeuronCore Groups e Pipelines, você pode explorar muitas outras maneiras de empacotar os seus modelos em uma única instância Inf1.

Otimize a implantação do modelo com grupos e pipelines NeuronCore

A implementação do modelo pré-treinado BERT HuggingFace requer que alguns dos parâmetros sejam definidos a priori. O Neuron é um compilador ahead-of-time (AOT) que requer conhecimento das formas dos tensores em tempo de compilação. Para isso, definimos o tamanho do lote e o comprimento da sequência para a implantação do nosso modelo. No exemplo anterior, o framework Neuron inferiu esses parâmetros baseados nas entradas utilizadas no exemplo da chamada compilação: (inputs['input_ids'], inputs['attention_mask']).

Além desses dois parâmetros do modelo, você pode definir o argumento do compilador ‘—neuroncore-pipeline-cores‘ e a variável de ambiente ‘NEURONCORE_GROUP_SIZES‘ para ajustar como seu servidor consome os NeuronCores no chip AWS Inferentia.

Por exemplo, para maximizar o número de threads concorrentes processando requisições de inferência em um único chip AWS Inferentia — quatro núcleos — você define os argumentos NEURONCORE_GROUP_SIZES=" 1,1,1,1” e ‘—neuroncore-pipeline-cores ‘ como 1. A imagem a seguir mostra como é essa divisão para uma implantação de paralelismo de dados.

Para mínima latência, você pode definir os argumentos ‘—neuroncore-pipeline-cores‘ para 4 e NEURONCORE_GROUP_SIZES=” 4” para que o processo consuma todos os quatro NeuronCores de uma só vez para um único modelo. O chip AWS Inferentia pode processar quatro solicitações de inferência simultaneamente, como um fluxo único. A implantação paralela do pipeline do modelo se dá como a figura a seguir.

As implantações paralela de dados favorecem a taxa de transferência com várias threads processando solicitações concorrentes. No entanto, o pipeline paralelo favorece a latência, mas também pode melhorar a taxa de transferência devido ao comportamento de processamento em fluxo. Usando os dois parâmetros extras, você pode ajustar a arquitetura da sua aplicação de acordo com as métricas mais importantes para o seu caso de uso.

Otimize para latência mínima: pipeline multi-core paralelo

Considere um aplicativo que requer mínima latência, como uma classificação de sequência de um chatbot online. À medida que o usuário envia texto, um modelo executado no backend classifica a intenção de uma entrada de um usuário e é limitado pela rapidez com que ele pode inferir. O modelo muito provavelmente terá que fornecer respostas a solicitações de entrada única (tamanho de lote = 1).

A tabela a seguir, com base em instâncias Inf-1 na região us-east-1 (Virginia) compara o desempenho e o custo destas instâncias contra a g4dn.xlarge — a família de instâncias de GPU mais otimizada para inferência na nuvem — enquanto executa o modelo base BERT HuggingFace em uma configuração paralela de dados versus pipeline e tamanho de lote 1 no AWS Inferentia. Olhando para o percentil 95 (p95) de latência, temos valores mais baixos no modo Pipeline para as instâncias inf1.xlarge de 4 núcleos e inf1.6xlarge de 16 núcleos . A melhor configuração das instâncias Inf1 nesse caso é a de 16 núcleos, com uma redução de 58% na latência, atingindo 6 milissegundos.

A B C D E F G H I
1 Instância Tamanho do Lote Modelo de inferência Número de NeuronCores por modelo Taxa de transferência
(sequências/segundo)
Latência p95 [segundos] Custo por 1M inferências Razão da taxa de transferência [inf1/g4dn] Razão do custo [inf1/g4dn]
2 inf1.xlarge 1 Data Parallel 1 245 0.0165 $0.259 1.6 26%
3 inf1.xlarge 1 Pipeline Parallel 4 291 0.0138 $0.218 2.0 22%
4 inf1.6xlarge 1 Data Parallel 1 974 0.0166 $0.337 6.5 34%
5 inf1.6xlarge 1 Pipeline Parallel 16 1793 0.0069 $0.183 12.0 19%
6 g4dn.xlarge 1 149 0.0082 $0.981
7

O modelo testado foi a versão PyTorch do HuggingFace bert-base-uncase, com comprimento de sequência 128. No AWS Inferentia, compilamos o modelo para usarmos todos os núcleos disponíveis e executarmos o pipeline completo paralelo. Para os casos paralelos de dados, compilamos os modelos para um único núcleo e configuramos os Grupos NeuronCore para executar um modelo baseado em threads por núcleo. A implantação da GPU usou a mesma configuração que o AWS Inferentia, onde o modelo foi rastreado com o TorchScript JIT e convertido para precisão mista usando o PyTorch AMP Autocast .

A taxa de transferência também aumentou 1,84 vezes com o modo Pipeline no AWS Inferentia, atingindo 1.793 frases por segundo, que é 12 vezes a taxa de transferência da instância g4dn.xlarge. O custo de inferência nessa configuração também favorece o inf1.6xlarge em relação à opção de GPU mais econômica, mesmo a um custo maior por hora. O custo por milhão de frases é até 81% menor com base na definição de preço de instância On-Demand do Amazon Elastic Compute Cloud (Amazon EC2). Para aplicativos sensíveis à latência que não podem utilizar a taxa de transferência completa do inf1.6xlarge, ou para modelos menores, como BERT Small, recomendamos usar o modo Pipeline em inf1.xlarge para uma implantação econômica.

Otimize para máxima taxa de transferência: Dados de núcleo único paralelos

Um caso de uso de NLP que requer aumento da taxa de transferência ao invés de latência mínima é o de tarefas extrativas de resposta a perguntas como parte de um pipeline de busca e recuperação de documentos. Nesse caso, aumentar o número de seções de documentos processadas em paralelo pode acelerar o resultado da busca ou melhorar a qualidade e a amplitude das respostas pesquisadas. Em tal configuração, as inferências são mais propensas a serem executadas em lotes maiores (tamanho de lote maior que 1).

Para alcançar o máximo de taxa de transferência, descobrimos por meio da experimentação que o tamanho ideal do lote é 6 para o mesmo modelo testado anteriormente no AWS Inferentia. Com a instância g4dn.xlarge, executamos o lote com tamanho 64 sem ficar sem memória na GPU. Os resultados a seguir ajudam a demonstrar como o tamanho do lote 6 pode fornecer 9,2 vezes mais taxa de transferência em uma instância inf1.6xlarge a um custo 76% menor, quando comparado à uma instância com GPUs.

A A C B D F G H I
1 Instância Tamanho do Lote Modelo de inferência Número de NeuronCores por modelo Taxa de transferência
(sequências/segundo)
Latência p95 [segundos] Custo por 1M inferências Razão da taxa de transferência [inf1/g4dn] Razão do custo [inf1/g4dn]
2 inf1.xlarge 6 Data Parallel 1 985 0.228 $0.064 2.3 19%
3 inf1.xlarge 6 Pipeline Parallel 4 945 0.228 $0.067 2.2 19%
4 inf1.6xlarge 6 Data Parallel 1 3880 1.18 $0.084 9.2 24%
5 inf1.6xlarge 6 Pipeline Parallel 16 2302 1.18 $0.142 5.5 41%
6 g4dn.xlarge 64 422 0.526 $0.346

Nesse exemplo, as considerações de custo também podem afetar o projeto de infra-estrutura de serviço final. A maneira mais econômica de executar as inferências em lote é usar a instância inf1.xlarge. Ele alcança uma taxa de transferência 2,3 vezes maior do que a alternativa de GPU, a um custo 81% menor. Escolher entre inf1.xlarge e inf1.6xlarge depende apenas do objetivo principal: custo mínimo ou taxa de transferência máxima.

Para testar o recurso NeuronCore Pipeline and Groups, confira os tutoriais recentes utilizando as capacidades do Neuron para PyTorch.

Conclusão

Neste artigo exploramos maneiras de otimizar suas implantações de NLP usando os recursos NeuronCore Groups e Pipeline. A integração nativa do AWS Neuron SDK e PyTorch permitiu que você compilasse e otimizasse o modelo HuggingFace Transformers para ser executado no AWS Inferentia com alterações mínimas de código. Ao ajustar a arquitetura de implantação para ser paralela via pipeline, os modelos BERT alcançaram latência mínima para aplicativos em tempo real, com taxa de transferência 12 vezes maior do que uma instância g4dn.xlarge, enquanto custam 81% menos para executar. Para a inferência em lote, alcançamos uma taxa de transferência 9,2 vezes maior a um custo 76% menor.

Os recursos do Neuron SDK descritos neste post também se aplicam a outros tipos de modelos e estruturas de ML. Para obter mais informações, consulte a documentação do AWS Neuron.

Saiba mais sobre o chip AWS Inferentia e as instâncias do Amazon EC2 Inf1 para começar a executar seus próprios pipelines de ML personalizados no AWS Inferentia usando o Neuron SDK.

Este artigo foi traduzido do Blog da AWS em Inglês.

 


Sobre os autores

Fabio Nonato de Paula é um Senior Manager do time Arquitetos de Soluções para o Laboratório de Annapurna na AWS . Ele ajuda clientes a utilizar o AWS Inferentia e o SDK AWS Neuron para acelerar e escalar cargas de trabalho de Aprendizado de Máquina na AWS. Fabio é apaixonado por democratizar acesso acelerado ao Aprendizado de Máquina e colocar modelos de Deep Learning em produção. Fora do trabalho, você encontrar o Fábio dirigindo sua motocicleta pelas colinas do vale de Livermore ou lendo ComiXology.

 

 

 

 

Mahadevan Balasubramaniam é um Principal Solutions Architect para o time de Computação Autônoma com aproximadamente 20 anos de experiência na área de Deep Learning voltada a Física e construindo e implementando Digital Twins para sistemas industriais em escala. Mahadevan obteve seu PhD em Engenharia Mecânica pelo Massachusetts Institute of Technology e tem mais de 25 patentes e publicações acreditadas a ele.

 

 

 

 

Sobre os tradutores

Augusto Toniolo Breowicz é Arquiteto de Soluções para Empresas na AWS.

 

 

 

 

 

 

Fabio Balancin é Arquiteto de Soluções Senior para Startup na AWS.