Condividi tramite


Inferenza del modello con Hugging Face Transformers per NLP

Importante

  • Questa documentazione è stata ritirata e potrebbe non essere aggiornata. Il prodotto, il servizio o la tecnologia citati in questo contenuto non sono più supportati.
  • Databricks consiglia invece di usare per l'inferenza ai_query batch. Vedere Eseguire l'inferenza batch usando ai_query.

Questo articolo illustra come usare Hugging Face Transformers per l’inferenza del modello NLP (Natural Language Processing).

Hugging Face transformers fornisce la classe pipelines per usare il modello con training preliminare per l’inferenza. 🤗 Le pipeline di trasformatori supportano un’ampia gamma di attività NLP che è possibile usare facilmente in Azure Databricks.

Requisiti

  • MLflow 2.3
  • Qualsiasi cluster con la libreria Hugging Face transformers installato può essere usato per l’inferenza batch. La transformers libreria è preinstallata in Databricks Runtime 10.4 LTS ML e versioni successive. Molti dei modelli NLP più diffusi funzionano meglio sull'hardware GPU, quindi è possibile ottenere le migliori prestazioni usando hardware GPU recente, a meno che non si usi un modello appositamente ottimizzato per l’uso nelle CPU.

Usare le funzioni definite dall’utente Pandas per distribuire il calcolo dei modelli in un cluster Spark

Quando si sperimentano modelli con training preliminare, è possibile usare le funzioni definite dall’utente Pandas per eseguire il wrapping del modello ed eseguire il calcolo su CPU di lavoro o GPU. Le funzioni definite dall’utente Pandas distribuiscono il modello a ogni ruolo di lavoro.

È anche possibile creare una pipeline Hugging Face Transformers per la traduzione automatica e usare una funzione definita dall’utente Pandas per eseguire la pipeline nei ruoli di lavoro di un 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)

L’impostazione di device in questo modo garantisce che le GPU vengano usate se disponibili nel cluster.

Le pipeline Hugging Face per la traduzione restituiscono un elenco di oggetti Python dict , ognuno con una singola chiave translation_text e un valore contenente il testo tradotto. Questa funzione definita dall'utente estrae la traduzione dai risultati per restituire una serie Pandas con solo il testo tradotto. Se la pipeline è stata costruita per usare GPU impostando device=0, Spark riassegna automaticamente le GPU nei nodi di lavoro se il cluster dispone di istanze con più GPU.

Per usare la funzione definita dall’utente per tradurre una colonna di testo, è possibile chiamare la funzione definita dall’utente tramite istruzioni 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')))

Come restituire tipi complessi

Usando le funzioni definite dall’utente Pandas è anche possibile restituire un output più strutturato. Ad esempio, nel riconoscimento di entità denominate, le pipeline restituiscono un elenco di dict oggetti contenenti l’entità, il relativo intervallo, il tipo e un punteggio associato. Analogamente all’esempio per la traduzione, il tipo restituito per l’annotazione @pandas_udf è più complesso nel caso del riconoscimento di entità denominate.

È possibile ottenere un’idea dei tipi restituiti da usare tramite l’ispezione dei risultati della pipeline, ad esempio eseguendo la pipeline sul driver.

Vedere il codice di esempio seguente:

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)

Per produrre le annotazioni:

[[{'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}]]

Per rappresentare questo come tipo restituito, è possibile usare un array di struct campi, elencando le dict voci come campi di 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')))

Ottimizzare le prestazioni

Esistono diversi aspetti chiave per ottimizzare le prestazioni della funzione definita dall’utente. Il primo consiste nell’usare ogni GPU in modo efficace, che è possibile modificare modificando le dimensioni dei batch inviati alla GPU dalla pipeline Transformers. Il secondo consiste nell’assicurarsi che il dataframe sia ben partizionato per usare l’intero cluster.

Infine, è possibile memorizzare nella cache il modello Hugging Face per risparmiare tempo di caricamento del modello o costi di ingresso.

Scegliere una dimensione per i dati

Anche se le funzioni definite dall’utente descritte in precedenza dovrebbero funzionare correttamente con un batch_size valore pari a 1, ciò potrebbe non usare le risorse disponibili per i lavoratori in modo efficiente. Per migliorare le prestazioni, ottimizzare le dimensioni del batch per il modello e l’hardware nel cluster. Databricks consiglia di provare diverse dimensioni batch per la pipeline nel cluster per trovare le prestazioni migliori. Altre informazioni sull’invio in batch delle pipeline e altre opzioni di prestazioni sono disponibili nella documentazione di Hugging Face.

Provare a trovare una dimensione batch sufficientemente grande in modo che supporti l'utilizzo completo della GPU, ma che non comporti CUDA out of memory errori. Quando si ricevono CUDA out of memory errori durante l’ottimizzazione, è necessario scollegare e ricollegare il notebook per rilasciare la memoria usata dal modello e dai dati nella GPU.

Monitorare le prestazioni della GPU visualizzando le metriche del cluster live per un cluster e scegliendo una metrica, ad esempio gpu0-util per l’utilizzo del processore GPU o gpu0_mem_util per l’utilizzo della memoria GPU.

Ottimizzare il parallelismo con la pianificazione a livello di fase

Per impostazione predefinita, Spark pianifica un’attività per GPU in ogni computer. Per aumentare il parallelismo, è possibile usare la pianificazione a livello di fase per indicare a Spark il numero di attività da eseguire per GPU. Ad esempio, se si vuole che Spark esegua due attività per GPU, è possibile specificarla nel modo seguente:

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)

Ripartizione dei dati per l’uso di tutti gli hardware disponibili

La seconda considerazione per le prestazioni consiste nell’usare completamente l’hardware nel cluster. In genere, un piccolo multiplo del numero di GPU nei ruoli di lavoro (per i cluster GPU) o il numero di core tra i ruoli di lavoro nel cluster (per i cluster CPU) funziona bene. Il dataframe di input potrebbe avere già partizioni sufficienti per sfruttare il parallelismo del cluster. Per visualizzare il numero di partizioni contenute nel dataframe, usare df.rdd.getNumPartitions(). È possibile ripartizionare un dataframe usando repartitioned_df = df.repartition(desired_partition_count).

Memorizzare nella cache il modello in DBFS o nei punti di montaggio

Se si carica spesso un modello da cluster diversi o riavviati, è anche possibile memorizzare nella cache il modello Hugging Face nel volume radice DBFS o in un punto di montaggio. Ciò può ridurre i costi di ingresso e ridurre il tempo necessario per caricare il modello in un cluster nuovo o riavviato. A tale scopo, impostare la TRANSFORMERS_CACHE variabile di ambiente nel codice prima di caricare la pipeline.

Ad esempio:

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

In alternativa, è possibile ottenere risultati simili registrando il modello in MLflow con la versione MLflowtransformers.

Notebook: inferenza hugging face transformers e registrazione MLflow

Per iniziare rapidamente a usare il codice di esempio, questo notebook è un esempio end-to-end per il riepilogo del testo usando l’inferenza delle pipeline hugging Face Transformers e la registrazione MLflow.

Notebook di inferenza delle pipeline Hugging Face Transformers

Ottenere il notebook

Risorse aggiuntive

È possibile ottimizzare il modello Hugging Face con le guide seguenti:

Altre informazioni su Che cosa sono gli Hugging Face Transformers?.