Inférence de modèle à l’aide des transformateurs Hugging Face pour le traitement du langage naturel
Cet article vous montre comment utiliser Hugging Face Transformers pour l’inférence de modèle de traitement du langage naturel (NLP).
Les transformateurs de visages d’étreinte fournissent la classe pipelines pour utiliser le modèle préentraîné pour l’inférence. 🤗 Les pipelines transformateurs prennent en charge un large éventail de tâches NLP que vous pouvez facilement utiliser sur Azure Databricks.
Spécifications
- MLflow 2.3
- Tout cluster sur lequel la bibliothèque Hugging Face
transformers
est installée peut être utilisé pour l’inférence par lots. La bibliothèquetransformers
est préinstallée sur Databricks Runtime 10.4 LTS ML et versions ultérieures. La plupart des modèles de traitement du langage naturel populaires fonctionnent mieux sur le matériel GPU. Vous pouvez donc obtenir les meilleures performances à l’aide du matériel GPU récent, sauf si vous utilisez un modèle spécifiquement optimisé pour une utilisation sur les processeurs.
Utiliser les fonctions définies par l’utilisateur Pandas pour distribuer le calcul de modèle sur un cluster Spark
Lorsque vous expérimentez des modèles préentraînés, vous pouvez utiliser des fonctions UDF Pandas pour encapsuler le modèle et effectuer des calculs sur des processeurs de travail ou des GPU. Les fonctions définies par l’utilisateur Pandas distribuent le modèle à chaque worker.
Vous pouvez également créer un pipeline Hugging Face Transformers pour la traduction automatique et utiliser une fonction UDF Pandas pour exécuter le pipeline sur les workers d’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)
La définition de device
cette manière garantit que les GPU sont utilisés s’ils sont disponibles sur le cluster.
Les pipelines Hugging Face pour la traduction retournent une liste d’objets Python dict
, chacun avec une clé translation_text
unique et une valeur contenant le texte traduit. Cette fonction UDF extrait la traduction des résultats pour renvoyer une série Pandas avec uniquement le texte traduit. Si votre pipeline a été construit pour utiliser des GPU en définissant device=0
, Spark réaffecte automatiquement les GPU sur les nœuds Worker si votre cluster a des instances avec plusieurs GPU.
Pour utiliser la fonction UDF pour traduire une colonne de texte, vous pouvez appeler la fonction UDF dans une instruction 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')))
Retourner des types de résultats complexes
À l’aide des fonctions définies par l’utilisateur Pandas, vous pouvez également retourner une sortie plus structurée. Par exemple, dans la reconnaissance d’entité nommée, les pipelines retournent une liste d’objets dict
contenant l’entité, son étendue, son type et un score associé. Bien que similaire à l’exemple de traduction, le type de retour de l’annotation @pandas_udf
est plus complexe dans le cas de la reconnaissance d’entité nommée.
Vous pouvez avoir une idée des types de retour à utiliser via l’inspection des résultats du pipeline, par exemple en exécutant le pipeline sur le pilote.
Dans cet exemple, utilisez le code suivant :
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)
Pour générer les annotations :
[[{'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}]]
Pour représenter ce type de retour, vous pouvez utiliser un array
de struct
champs, en répertoriant les dict
entrées en tant que champs du 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')))
Régler les performances
Il existe plusieurs aspects clés du réglage des performances de la fonction UDF. La première consiste à utiliser efficacement chaque GPU, que vous pouvez ajuster en modifiant la taille des lots envoyés au GPU par le pipeline Transformers. La deuxième consiste à s’assurer que le DataFrame est bien partitionné pour utiliser l’ensemble du cluster.
Enfin, vous souhaiterez peut-être mettre en cache le modèle Hugging Face pour réduire le temps de chargement du modèle ou les coûts d’entrée.
Choisir une taille de lot
Bien que les fonctions définies par l’utilisateur décrites ci-dessus doivent fonctionner prêtes à l’emploi avec une batch_size
valeur de 1, cela peut ne pas utiliser efficacement les ressources disponibles pour les travailleurs. Pour améliorer les performances, réglez la taille du lot sur le modèle et le matériel du cluster. Databricks recommande d’essayer différentes tailles de lots pour le pipeline sur votre cluster afin de trouver les meilleures performances. Pour plus d’informations sur le traitement par lots de pipelines et d’autres options de performances, consultez la documentation visage hugging.
Essayez de trouver une taille de lot suffisamment grande pour qu’elle pilote l’utilisation complète du GPU, mais ne génère pas d’erreurs CUDA out of memory
. Lorsque vous recevez CUDA out of memory
des erreurs pendant le réglage, vous devez détacher et rattacher le notebook pour libérer la mémoire utilisée par le modèle et les données dans le GPU.
Surveillez les performances GPU en affichant les métriques de cluster en direct pour un cluster et en choisissant une métrique, par gpu0-util
exemple pour l’utilisation du processeur GPU ou gpu0_mem_util
pour l’utilisation de la mémoire GPU.
Régler le parallélisme avec la planification au niveau des étapes
Par défaut, Spark planifie une tâche par GPU sur chaque ordinateur. Pour augmenter le parallélisme, vous pouvez utiliser la planification au niveau des étapes pour indiquer à Spark le nombre de tâches à exécuter par GPU. Par exemple, si vous souhaitez que Spark exécute deux tâches par GPU, vous pouvez le spécifier de la manière suivante :
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)
Données de répartition pour utiliser tout le matériel disponible
Le deuxième facteur à prendre en compte pour les performances est d’utiliser pleinement le matériel de votre cluster. En règle générale, un petit multiple du nombre de GPU sur vos workers (pour les clusters GPU) ou du nombre de cœurs sur les workers de votre cluster (pour les clusters d’UC) fonctionne bien. Votre DataFrame d’entrée a peut-être déjà suffisamment de partitions pour tirer parti du parallélisme du cluster. Pour voir le nombre de partitions que contient le DataFrame, utilisez df.rdd.getNumPartitions()
. Vous pouvez repartitionner un DataFrame à l’aide de repartitioned_df = df.repartition(desired_partition_count)
.
Mettre en cache le modèle dans DBFS ou sur des points de montage
Si vous chargez fréquemment un modèle à partir de clusters différents ou redémarrés, vous pouvez également mettre en cache le modèle Hugging Face dans le volume racine DBFS ou sur un point de montage. Cela peut réduire les coûts d’entrée et réduire le temps de chargement du modèle sur un cluster nouveau ou redémarré. Pour ce faire, définissez la variable d’environnement TRANSFORMERS_CACHE
dans votre code avant de charger le pipeline.
Par exemple :
import os
os.environ['TRANSFORMERS_CACHE'] = '/dbfs/hugging_face_transformers_cache/'
Vous pouvez également obtenir des résultats similaires en journalisant le modèle dans MLflow avec la saveurtransformers
MLflow.
Notebook: Hugging Face Transformers inférence et MLflow logging
Pour commencer rapidement avec un exemple de code, ce notebook est un exemple de bout en bout de synthèse de texte à l’aide de l’inférence de pipelines Hugging Face Transformers et de la journalisation MLflow.
Hugging Face Transformers pipelines inférence notebook
Ressources supplémentaires
Vous pouvez affiner votre modèle de Hugging Face avec les repères suivants :
- Préparer des données pour le réglage des modèles Hugging Face
- Ajuster les modèles Hugging Face pour un seul GPU
En savoir plus sur Qu’est-ce que Hugging Face Transformers ?