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 LLM 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. Latransformers
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 transformers
MLflow.
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
Risorse aggiuntive
È possibile ottimizzare il modello Hugging Face con le guide seguenti:
- Impostare i dati per ottimizzare i modelli Hugging Face
- Ottimizzare i modelli Hugging Face per una singola GPU
Altre informazioni su Che cosa sono gli Hugging Face Transformers?.