Pular para o conteúdo principal

Inferência de modelos usando Hugging Face Transformers para NLP

important
  • Essa documentação foi descontinuada e pode não estar atualizada. O produto, serviço ou tecnologia mencionados neste conteúdo não são mais suportados.
  • Databricks recomenda o uso de ai_query para a inferência de lotes. Consulte Realizar inferência de lotes LLM usando AI Functions.

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

Hugging Face transformers fornece a classe de pipeline para usar o modelo pré-treinado para inferência. O pipeline de transformadores suporta uma ampla gama de tarefas de PNL que o senhor pode usar facilmente em Databricks.

Requisitos

  • MLflow 2.3
  • Quaisquer clusters com a biblioteca Hugging Face transformers instalada podem ser usados para inferência de lotes. A biblioteca transformers vem pré-instalada no Databricks Runtime 10.4 LTS MLe acima. Muitos dos modelos populares de NLP 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.

Use os UDFs do Pandas para distribuir o cálculo do modelo em um clustering do Spark

Ao fazer experimentos com modelos pré-treinados, o senhor pode usar os UDFs doPandas para envolver o modelo e realizar cálculos em CPUs ou GPUs do worker. Pandas Os UDFs distribuem o modelo para cada worker.

O senhor também pode criar um Hugging Face Transformers pipeline para tradução automática e usar um Pandas UDF para executar o pipeline no worker de um Spark clustering:

Python
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 device dessa maneira garante que as GPUs sejam usadas se estiverem disponíveis em o clustering.

O pipeline Hugging Face para tradução retorna uma lista de objetos Python dict, cada um com um único key translation_text e um valor contendo o texto traduzido. Esse 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 com a configuração device=0, então o Spark reatribui automaticamente as GPUs nos nós do worker se o seu clustering tiver instâncias com várias GPUs.

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

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

Devolver tipos de resultados complexos

Usando as UDFs do Pandas, o senhor também pode retornar uma saída mais estruturada. Por exemplo, no reconhecimento de entidades nomeadas, o pipeline retorna uma lista de objetos dict contendo a entidade, sua extensão, tipo e uma pontuação associada. Embora semelhante ao exemplo da tradução, o tipo de retorno para a anotação @pandas_udf é mais complexo no caso do reconhecimento de entidades nomeadas.

O senhor 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 código a seguir:

Python
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 a anotação:

Python
[[{'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 campos struct, listando as entradas dict como os campos do struct:

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

Ajuste de desempenho

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

Por fim, o senhor pode desejar armazenar em cache o modelo Hugging Face para economizar tempo de carregamento do modelo ou custos de entrada.

Escolha um tamanho de lote

Embora os UDFs descritos acima devam funcionar imediatamente com um batch_size de 1, isso pode não usar o recurso disponível para o trabalhador de forma eficiente. Para melhorar o desempenho, ajuste o tamanho dos lotes de acordo com o modelo e o hardware no clustering. Databricks recomenda experimentar vários tamanhos de lotes para o pipeline em seu clustering para encontrar o melhor desempenho. Leia mais sobre pipeline lotes e outras opções de desempenho na documentação 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 no site CUDA out of memory. Quando o senhor recebe erros no site 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 visualizando as métricas de clustering ao vivo para um clustering 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 programação em nível de estágio

Por default, Spark programar uma tarefa por GPU em cada máquina. Para aumentar o paralelismo, o senhor pode usar a programação em nível de estágio para informar ao Spark quantas tarefas devem ser executadas por GPU. Por exemplo, se o senhor quiser que o Spark execute duas tarefas por GPU, poderá especificar isso da seguinte forma:

Python
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 sobre o desempenho é fazer uso total do hardware em seu clustering. Em geral, um pequeno múltiplo do número de GPUs em seu worker (para clustering de GPU) ou do número de núcleos em todo o worker em seu clustering (para clustering de CPU) funciona bem. Sua entrada DataFrame pode já ter partições suficientes para aproveitar o paralelismo do clustering. Para ver quantas partições o DataFrame contém, use df.rdd.getNumPartitions(). O senhor 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 estiver carregando com frequência um modelo de um cluster diferente ou reiniciado, o senhor também pode desejar armazenar em cache o modelo Hugging Face no volumeDBFS root 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:

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

Como alternativa, o senhor pode obter resultados semelhantes registrando o modelo no MLflow com o flavor MLflow transformers.

Notebook: Hugging Face Inferência de transformadores e MLflow logging

Para começar rapidamente com o código de exemplo, este Notebook é um exemplo completo de resumo de texto usando a inferência de pipeline do Hugging Face Transformers e o registro MLflow.

Hugging Face Caderno de inferência do pipeline de Transformers

Open notebook in new tab

Recurso adicional

O senhor pode fazer o ajuste fino do seu modelo Hugging Face com o seguinte guia:

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