Udostępnij przez


Wnioskowanie modelu przy użyciu Hugging Face Transformers dla NLP

Ważne

W tym artykule pokazano, jak używać Transformers Hugging Face do wnioskowania modelu przetwarzania języka naturalnego (NLP).

Hugging Face Transformers udostępnia klasę potoków do używania wstępnie wytrenowanego modelu do wnioskowania. 🤗 Potoki modeli Transformer obsługują szeroki zakres zadań NLP, których można łatwo używać na platformie Azure Databricks.

Wymagania

  • MLflow 2.3
  • Dowolny klaster z zainstalowaną biblioteką Hugging Face transformers może być używany do wnioskowania wsadowego. Biblioteka transformers jest wstępnie zainstalowana w środowisku Databricks Runtime 10.4 LTS ML lub nowszym. Wiele popularnych modeli NLP działa najlepiej na sprzęcie gpu, więc możesz uzyskać najlepszą wydajność przy użyciu ostatniego sprzętu procesora GPU, chyba że używasz modelu specjalnie zoptymalizowanego do użycia na procesorach CPU.

Użyj funkcji UDF Pandas do dystrybuowania obliczeń modelu w klastrze Spark

Podczas eksperymentowania ze wstępnie wytrenowanymi modelami można użyć Pandas UDF do opakowania modelu i wykonywania obliczeń na CPU lub GPU. Funkcje zdefiniowane przez użytkownika Pandas dystrybuują model do każdego procesu roboczego.

Możesz również utworzyć pipeline Hugging Face Transformers na potrzeby tłumaczenia maszynowego i użyć funkcji zdefiniowanej przez użytkownika biblioteki Pandas do uruchomienia pipeline'u na pracownikach klastra 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)

Ustawienie właściwości device w ten sposób gwarantuje, że procesory GPU są używane, jeśli są dostępne w klastrze.

Potoki Hugging Face do tłumaczenia zwracają listę obiektów języka Python dict, z których każdy ma jeden klucz translation_text i wartość zawierającą przetłumaczony tekst. Funkcja UDF wyodrębnia tłumaczenie z wyników, aby zwrócić serię z biblioteki Pandas zawierającą tylko przetłumaczony tekst. Jeśli potok został skonstruowany tak, aby używał procesorów GPU przez ustawienie device=0, platforma Spark automatycznie ponownie przypisuje procesory GPU w węzłach roboczych, jeśli klaster ma wystąpienia z wieloma procesorami GPU.

Aby przetłumaczyć kolumnę tekstową przy użyciu UDF, możesz wywołać UDF w instrukcji 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')))

Zwracanie złożonych typów wyników

Korzystając z UDF Pandas, można również zwrócić bardziej zorganizowane dane wyjściowe. Na przykład w rozpoznawaniu jednostek nazwanych potoki przetwarzania zwracają listę dict obiektów zawierających jednostkę, jej zakres, typ i skojarzony wynik. Podobnie jak w przypadku tłumaczenia, zwracany typ adnotacji @pandas_udf jest bardziej skomplikowany w przypadku rozpoznawania jednostek nazwanych.

Można zorientować się w typach zwracanych, które mają być używane, poprzez analizę wyników potoku, na przykład przez uruchomienie potoku na sterowniku.

W tym przykładzie użyj następującego kodu:

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)

Aby uzyskać adnotacje:

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

Aby przedstawić to jako typ zwracany, możesz użyć array w postaci pól struct, wymieniając wpisy dict jako pola elementu 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')))

Dostrajanie wydajności

Istnieje kilka kluczowych aspektów dostrajania wydajności funkcji zdefiniowanej przez użytkownika (UDF). Pierwszym z nich jest efektywne użycie każdego GPU, co można dostosować, zmieniając rozmiar partii wysyłanych do GPU dostarczanego przez moduł Transformers. Drugim krokiem jest zapewnienie, aby DataFrame był dobrze podzielony na partycje, umożliwiając wykorzystanie całego klastra.

Na koniec możesz buforować model Hugging Face, aby zaoszczędzić czas ładowania modelu lub koszty transferu danych.

Wybieranie rozmiaru partii

Chociaż opisane powyżej funkcje zdefiniowane przez użytkownika powinny działać od razu z wartością batch_size równą 1, to może nie wykorzystywać efektywnie zasobów dostępnych dla procesów roboczych. Aby zwiększyć wydajność, dostosuj rozmiar partii do modelu i sprzętu w klastrze. Databricks zaleca wypróbowanie różnych rozmiarów partii dla przepływu w klastrze, aby uzyskać najlepszą wydajność. Przeczytaj więcej na temat przetwarzania wsadowego potoku i innych możliwości wydajnościowych w dokumentacji Hugging Face.

Spróbuj znaleźć rozmiar partii, który jest wystarczająco duży, aby był oparty na pełnym wykorzystaniu procesora GPU, ale nie powoduje CUDA out of memory błędów. W przypadku wystąpienia CUDA out of memory błędów podczas dostrajania należy uruchomić nową sesję, aby zwolnić pamięć używaną przez model i dane w procesorze GPU.

Monitoruj wydajność GPU, wyświetlając na żywo metryki klastra i wybierając metrykę, taką jak wykorzystanie procesora GPU lub wykorzystanie pamięci GPU.

Dostrajanie równoległości przy użyciu planowania na poziomie etapu

Domyślnie platforma Spark planuje jedno zadanie na procesor GPU na każdej maszynie. Aby zwiększyć równoległość, możesz użyć harmonogramowania na poziomie etapu, aby określić platformie Spark, ile zadań uruchomić na każdym procesorze GPU. Jeśli na przykład chcesz, aby platforma Spark uruchamiała dwa zadania na procesor GPU, możesz to określić w następujący sposób:

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)

Ponowne partycjonowanie danych do pełnego wykorzystania dostępnego sprzętu

Druga kwestia wydajności polega na pełnym wykorzystaniu sprzętu w klastrze. Ogólnie rzecz biorąc, niewielka wielokrotność liczby procesorów GPU na jednostkach roboczych (w przypadku klastrów GPU) lub liczby rdzeni na jednostkach roboczych w klastrze (w przypadku klastrów CPU) działa dobrze. Ramka danych wejściowych może już mieć wystarczającą liczbę partycji, aby skorzystać z równoległości klastra. Aby zobaczyć, ile partycji zawiera ramka danych, użyj polecenia df.rdd.getNumPartitions(). Możesz ponownie partycjonować ramkę danych przy użyciu polecenia repartitioned_df = df.repartition(desired_partition_count).

Buforowanie modelu w systemie DBFS lub w punktach instalacji

Jeśli często ładujesz model z różnych lub ponownie uruchomionych klastrów, możesz również buforować model Hugging Face w woluminie głównym systemu DBFS lub w punkcie instalacji. To może zmniejszyć koszty wprowadzania danych i skrócić czas wczytania modelu w nowym lub ponownie uruchomionym klastrze. W tym celu ustaw w kodzie zmienną środowiskową TRANSFORMERS_CACHE przed załadowaniem pipeline.

Na przykład:

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

Alternatywnie możesz osiągnąć podobne wyniki, rejestrując model w MLflow przy użyciu flavor MLflow transformers.

Notebook: Wnioskowanie przy użyciu Hugging Face Transformers i rejestrowanie w MLflow

Aby szybko rozpocząć pracę z przykładowym kodem, ten notebook jest przykładem end-to-end do podsumowania tekstu przy użyciu potoków inferencyjnych Hugging Face Transformers i rejestrowania MLflow.

Notebook do wnioskowania z użyciem potoków Transformers firmy Hugging Face

Zdobądź notatnik

Dodatkowe zasoby

Model Hugging Face można dostosować za pomocą następujących przewodników:

Dowiedz się więcej o Hugging Face Transformers