Inferência de modelo usando Hugging Face Transformers para PNL

Importante

  • Esta documentação foi desativada e pode não ser atualizada. Os produtos, serviços ou tecnologias mencionados neste conteúdo não são mais suportados.
  • Em vez disso, o Databricks recomenda o uso ai_query para inferência em lote. Consulte Executar inferência em lote usando ai_query.

Este artigo mostra como usar o Hugging Face Transformers para inferência de modelo de processamento de linguagem natural (NLP).

Os transformadores Hugging Face fornecem a classe pipelines para usar o modelo pré-treinado para inferência. 🤗 Os pipelines do Transformers dão suporte a uma ampla gama de tarefas de PNL que você pode usar facilmente no Azure Databricks.

Requisitos

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

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

Ao experimentar modelos pré-treinados, você pode usar Pandas UDFs para envolver o modelo e executar cálculos em CPUs ou GPUs de trabalho. Pandas UDFs distribuem o modelo para cada trabalhador.

Você também pode criar um pipeline Hugging Face Transformers para tradução automática e usar um Pandas UDF para executar o pipeline nos trabalhadores 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)

Definir o dessa maneira garante que as device GPUs sejam usadas se estiverem disponíveis no cluster.

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

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

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')))

Tipos de resultados complexos de retorno

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

Você pode ter uma noção 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 dos struct campos, listando as dict entradas 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')))

Otimizar o desempenho

Existem vários aspetos fundamentais para ajustar o desempenho da UDF. A primeira é usar cada GPU de forma eficaz, que você pode ajustar alterando o tamanho dos lotes enviados para a GPU pelo pipeline Transformers. O segundo é certificar-se de que o DataFrame está bem particionado para utilizar todo o cluster.

Finalmente, você pode querer armazenar em cache o modelo Hugging Face para economizar tempo de carregamento do modelo ou custos de entrada.

Escolha um tamanho de lote

Embora as UDFs descritas acima devam funcionar fora da caixa com um batch_size de 1, isso pode não usar os recursos disponíveis para os trabalhadores de forma eficiente. Para melhorar o desempenho, ajuste o tamanho do lote ao modelo e ao hardware no cluster. A Databricks recomenda tentar vários tamanhos de lote para o pipeline em seu cluster para encontrar o melhor desempenho. Leia mais sobre lotes 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 não resulte em CUDA out of memory erros. Quando você recebe CUDA out of memory erros durante o ajuste, você precisa desanexar 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 ao vivo para um cluster e escolhendo uma métrica, como gpu0-util para utilização do processador GPU ou gpu0_mem_util para utilização da memória GPU.

Ajuste o paralelismo com o agendamento no nível do palco

Por padrão, o Spark agenda uma tarefa por GPU em cada máquina. Para aumentar o paralelismo, você pode usar o agendamento no nível do 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 dados para usar todo o hardware disponível

A segunda consideração para o desempenho é fazer pleno uso do hardware em seu cluster. Geralmente, um pequeno múltiplo do número de GPUs em seus trabalhadores (para clusters de GPU) ou número de núcleos entre os trabalhadores em seu cluster (para clusters de CPU) funciona bem. Seu DataFrame de entrada já pode 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)o .

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

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

Por exemplo:

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

Como alternativa, você pode obter resultados semelhantes registrando o modelo no MLflow com o sabor MLflowtransformers.

Notebook: Inferência de Transformadores de Rosto e registro de MLflow

Para começar rapidamente com código de exemplo, este bloco de anotações é um exemplo de ponta a ponta para resumo de texto usando inferência de pipelines do Hugging Face Transformers e registro de MLflow.

Abraçando Face Transformers pipelines inferência notebook

Obter o bloco de notas

Recursos adicionais

Pode ajustar o seu modelo Hugging Face com os seguintes guias:

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