Compartilhar via


Inferência de modelos usando os Transformadores de Detecção Facial Hugging para NLP

Este artigo mostra como usar os Transformers do Hugging Face para inferência de modelos de processamento de linguagem natural.

Os Transformers do Hugging Face fornece a classe de pipelines para usar o modelo pré-treinado para inferência. 🤗 Os pipelines dos Transformers oferecem suporte a uma ampla gama de tarefas de processamento de linguagem natural que você pode usar facilmente no Azure Databricks.

Requisitos

  • MLflow 2.3
  • Qualquer cluster com a biblioteca transformers do Hugging Face instalada pode ser usado para inferência em lote. A biblioteca transformers vem pré-instalada no Databricks Runtime 10.4 LTS ML e superior. Muitos dos modelos populares de processamento de linguagem natural funcionam melhor em hardware de GPU, portanto, você pode obter o melhor desempenho usando hardware de GPU recente, a menos que use um modelo otimizado especificamente para uso em CPUs.

Use UDFs do Pandas para distribuir a computação do modelo em um cluster Spark

Ao fazer experimentos com modelos pré-treinados, você pode usar UDFs do Pandas para encapsular o modelo e executar computação em CPUs ou GPUs de trabalho. As UDFs do Pandas distribuem o modelo para cada trabalho.

Você também pode criar um pipeline dos Transformers do Hugging Face para tradução automática e usar uma UDF do Pandas para executar o pipeline nos trabalhos de um cluster Spark:

import pandas as pd
from transformers import pipeline
import torch
from pyspark.sql.functions import pandas_udf

device = 0 if torch.cuda.is_available() else -1
translation_pipeline = pipeline(task="translation_en_to_fr", model="t5-base", device=device)

@pandas_udf('string')
def translation_udf(texts: pd.Series) -> pd.Series:
  translations = [result['translation_text'] for result in translation_pipeline(texts.to_list(), batch_size=1)]
  return pd.Series(translations)

A configuração de device dessa maneira garante que as GPUs sejam usadas se estiverem disponíveis no cluster.

Os pipelines do Hugging Face para tradução retornam uma lista de objetos dict Python, cada um com uma única chave translation_text e um valor contendo o texto traduzido. Essa UDF extrai a tradução dos resultados para retornar uma série do Pandas com apenas o texto traduzido. Se o seu pipeline foi construído para usar GPUs configurando device=0, o Spark reatribuirá automaticamente as GPUs nos nós de trabalho se o cluster tiver instâncias com várias GPUs.

Para usar a UDF para traduzir uma coluna de texto, você pode chamar a UDF em uma instrução select:

texts = ["Hugging Face is a French company based in New York City.", "Databricks is based in San Francisco."]
df = spark.createDataFrame(pd.DataFrame(texts, columns=["texts"]))
display(df.select(df.texts, translation_udf(df.texts).alias('translation')))

Retornar tipos de resultados complexos

Usando as UDFs do Pandas, você também pode retornar uma saída mais estruturada. Por exemplo, no reconhecimento de entidades nomeadas, os pipelines retornam uma lista de objetos dict contendo a entidade, seu intervalor, tipo e uma pontuação associada. Embora semelhante ao exemplo de tradução, o tipo de retorno da anotação @pandas_udf é mais complexo no caso do reconhecimento de entidade nomeada.

Você pode ter uma ideia dos tipos de retorno a serem usados por meio da inspeção dos resultados do pipeline, por exemplo, executando o pipeline no driver.

Neste exemplo, use o seguinte código:

from transformers import pipeline
import torch
device = 0 if torch.cuda.is_available() else -1
ner_pipeline = pipeline(task="ner", model="Davlan/bert-base-multilingual-cased-ner-hrl", aggregation_strategy="simple", device=device)
ner_pipeline(texts)

Para produzir as anotações:

[[{'entity_group': 'ORG',
   'score': 0.99933606,
   'word': 'Hugging Face',
   'start': 0,
   'end': 12},
  {'entity_group': 'LOC',
   'score': 0.99967843,
   'word': 'New York City',
   'start': 42,
   'end': 55}],
 [{'entity_group': 'ORG',
   'score': 0.9996372,
   'word': 'Databricks',
   'start': 0,
   'end': 10},
  {'entity_group': 'LOC',
   'score': 0.999588,
   'word': 'San Francisco',
   'start': 23,
   'end': 36}]]

Para representar isso como um tipo de retorno, você pode usar um array de campos struct, listando as entradas dict como os campos do struct:

import pandas as pd
from pyspark.sql.functions import pandas_udf

@pandas_udf('array<struct<word string, entity_group string, score float, start integer, end integer>>')
def ner_udf(texts: pd.Series) -> pd.Series:
  return pd.Series(ner_pipeline(texts.to_list(), batch_size=1))

display(df.select(df.texts, ner_udf(df.texts).alias('entities')))

Desempenho de ajuste

Há vários aspectos importantes para ajustar o desempenho da UDF. A primeira é usar cada GPU de forma eficaz, o que pode ser ajustado alterando o tamanho dos lotes enviados à GPU pelo pipeline dos Transformers. A segunda é garantir que o DataFrame esteja bem particionado para utilizar todo o cluster.

Por fim, talvez você queira armazenar em cache o modelo do Hugging Face para economizar tempo de carregamento do modelo ou custos de entrada.

Escolher um tamanho de lote

Embora as UDFs descritas acima devam funcionar imediatamente com um batch_size de 1, isso pode não usar os recursos disponíveis para os trabalhos de forma eficiente. Para melhorar o desempenho, ajuste o tamanho do lote de acordo com o modelo e o hardware do cluster. O Databricks recomenda experimentar vários tamanhos de lote para o pipeline em seu cluster para encontrar o melhor desempenho. Leia mais sobre o envio em lote de pipeline e outras opções de desempenho na documentação do Hugging Face.

Tente encontrar um tamanho de lote que seja grande o suficiente para que ele impulsione a utilização total da GPU, mas que não resulte em erros CUDA out of memory. Ao receber erros CUDA out of memory durante o ajuste, é necessário desconectar e reconectar o notebook para liberar a memória usada pelo modelo e os dados na GPU.

Monitore o desempenho da GPU exibindo as métricas de cluster dinâmico para um cluster e escolhendo uma métrica, como gpu0-util para utilização do processador da GPU ou gpu0_mem_util para utilização da memória da GPU.

Ajuste o paralelismo com agendamento em nível de estágio

Por padrão, o Spark agenda uma tarefa por GPU em cada computador. Para aumentar o paralelismo, você pode usar o agendamento em nível de estágio para informar ao Spark quantas tarefas devem ser executadas por GPU. Por exemplo, se você quiser que o Spark execute duas tarefas por GPU, poderá especificar isso da seguinte maneira:

from pyspark.resource import TaskResourceRequests, ResourceProfileBuilder

task_requests = TaskResourceRequests().resource("gpu", 0.5)

builder = ResourceProfileBuilder()
resource_profile = builder.require(task_requests).build

rdd = df.withColumn('predictions', loaded_model(struct(*map(col, df.columns)))).rdd.withResources(resource_profile)

Reparticionar os dados para usar todo o hardware disponível

A segunda consideração sobre o desempenho é fazer uso total do hardware no cluster. Em geral, um pequeno múltiplo do número de GPUs em seus trabalhos (para clusters de GPU) ou do número de núcleos entre os trabalhos no cluster (para clusters de CPU) funciona bem. Seu DataFrame de entrada pode já ter partições suficientes para aproveitar o paralelismo do cluster. Para ver quantas partições o DataFrame contém, use df.rdd.getNumPartitions(). Você pode reparticionar um DataFrame usando repartitioned_df = df.repartition(desired_partition_count).

Armazenar o modelo em cache no DBFS ou em pontos de montagem

Se você estiver carregando frequentemente um modelo de clusters diferentes ou reiniciados, talvez queira armazenar em cache o modelo do Hugging Face no volume raiz do DBFS ou em um ponto de montagem. Isso pode diminuir os custos de entrada e reduzir o tempo de carregamento do modelo em um cluster novo ou reiniciado. Para fazer isso, defina a variável de ambiente TRANSFORMERS_CACHE em seu código antes de carregar o pipeline.

Por exemplo:

import os
os.environ['TRANSFORMERS_CACHE'] = '/dbfs/hugging_face_transformers_cache/'

Como alternativa, é possível obter resultados semelhantes registrando em log o modelo no MLflow com o sabor transformers do MLflow.

Notebook: inferência dos Transformers do Hugging Face e registro em log do MLflow

Para começar a usar rapidamente o código de exemplo, este notebook é um exemplo completo de resumo de texto usando a inferência de pipelines dos Transformers do Hugging Face e o registro em log do MLflow.

Notebook de inferência de pipelines dos Transformers do Hugging Face

Obter notebook

Recursos adicionais

Você pode ajustar seu modelo do Hugging Face com os seguintes guias:

Saiba mais sobre O que são os Transformers do Hugging Face?