Inferenz von Textgenerierungs-Modellen

Download this notebook

Auf JupyterHub

In diesem Teil unseres Tutorials über LLMs werden wir lernen, wie man ein Modell zur Textgeneration von Huggingface auf JupyterHub verwendet. Wenn du einen lokalen Rechner mit installiertem CUDA hast, sollten alle Schritte gleich sein, aber es kann schwierig sein, die richtige Umgebung zu installieren.

Starte also ein Jupyter-Notebook und wähle den Standard-Kernel. Stelle sicher, dass du beim Start von JupyterHub eine GPU ausgewählt hast. Wenn du auf deine PALMA-Dateien zugreifen willst, wähle auch die Palma storage integration aus. Dies ermöglicht es dir, unter /palma auf das PALMA-Verzeichnis zuzugreifen. Wenn du transformers installieren musst, verwende pip in deiner Standard-Python-Umgebung:

pip install transformers

Torch sollte vorinstalliert sein (JupyterHub) oder in deiner Modulkette (PALMA). Auf einem lokalen Rechner kann es schwierig sein, CUDA und dann Torch in der richtigen Umgebung zu installieren.

import torch
from transformers import AutoTokenizer, AutoModelForCausalLM
print(f'CUDA avail: {torch.cuda.is_available()}')
for i in range(torch.cuda.device_count()):
        device_properties = torch.cuda.get_device_properties(i)
        memory = device_properties.total_memory
        print(f'GPU {i}: {device_properties.name} with 
        {int(memory / 1024**2)}MB RAM')

device = 'cuda:0' if torch.cuda.is_available() else 'cpu'

Hier können wir sehen, ob CUDA verfügbar ist und auf wie viel VRAM wir zugreifen können. Dieses Notizbuch ist für die Verwendung von nur einer GPU geschrieben, normalerweise cuda:0, sollte aber auch mit cpu laufen.

Das Huggingface-Caching-Mysterium

Huggingface bietet eine sehr einfache Schnittstelle für Modelle. Das Paket transformers lädt sie automatisch herunter, aber man weiß nicht, wo diese Modelle auf der Festplatte liegen und sie können riesig sein! Wir sollten also vorsichtig sein, wenn wir die Modelle laden.

Wir haben verschiedene Optionen, wo wir unsere Modelle laden können:

  • Der Standard-Cache von Huggingface ist ein versteckter Ordner ~/.cache/huggingface/models. Da die Modelle aber sehr groß sind, kann dies leicht die Partitionen sprengen!
  • Wenn du PALMA verwendest, kannst du /scratch/tmp/$USER/huggingface/models/ verwenden und es später entfernen
  • Ansonsten können wir für kleine Modelle (!) einfach ein nicht verstecktes Cache-Verzeichnis (z.B. ~/huggingface/models) verwenden und später wieder entfernen. Wenn du hier Fehler bekommst, kann das an den Berechtigungen liegen. Verwende dann den Standard-Cache von Huggingface.
  • Für größere Modelle kannst du auch einen OpenStack Usershare /cloud/wwu1/{group}/{share}/cache verwenden
  • Oder wir lassen es einfach so, wie es ist, aber sind uns dessen bewusst!

Merke dir jetzt, wo du dein Modell gespeichert hast, denn du wirst das Cache-Verzeichnis später noch brauchen. Wir werden weiterhin das Modell in und aus cache_dir = "/cloud/wwu1/d_reachiat/incubai/cache" laden. Unten siehst du, wie du das kleinste Pythia-Modell herunterladen und starten kannst. Pythia ist eine Sammlung von Open Source LLMs für die Texterzeugung, ähnlich wie GPT (Closed Source) oder Llama (eingeschränkte Lizenz).

# Lade das Modell aus dem Huggingface Model Hub herunter
model_name = "EleutherAI/pythia-70m-deduped"
cache_dir = "/cloud/wwu1/d_reachiat/incubai/cache"
tokenizer = AutoTokenizer.from_pretrained(model_name, cache_dir=cache_dir)
model = AutoModelForCausalLM.from_pretrained(model_name, cache_dir=cache_dir)
# Lade ein Modell aus dem Cache anstelle des Huggingface Model Hub
tokenizer = AutoTokenizer.from_pretrained(
    model_name, 
    cache_dir=cache_dir, 
    local_files_only=True
    )

model = AutoModelForCausalLM.from_pretrained(
    model_name, 
    cache_dir=cache_dir, 
    local_files_only=True
    )

# Modell in den GPU-Speicher laden, falls verfügbar
# Beim ersten Ladevorgang wird das Modell erst in den CPU-Speicher und dann   
# in den GPU-Speicher geladen
# Um dies zu vermeiden, kannst du das Modell mit der Bibliothek accelerate 
# direkt in den GPU-Speicher laden 

model = model.to(device)

# Ausgabe des reservierten Speichers, beachte, dass dies der für das 
# Modell reservierte Speicher ist und nicht der von CUDA genutzte Speicher
# Ermittle den von CUDA insgesamt genutzten Speicher mit nvidia-smi

print(f'VRAM reserved: {int(torch.cuda.memory_reserved(0) / 1024**2)}MB')

Nun können wir mit dem Prompting fortfahren. Das bedeutet, wir geben dem Modell einen Satz, auf dessen Grundlage es neuen Text generiert, der logisch an den gegebenen Eingangssatz anschließen sollte.

Wie du sehen wirst, ist der von unserem kleinen Pythia-Modell generierte Text repetitiv und nicht sehr gut. Eine Änderung einiger Parameter kann helfen, aber die hier für Testzwecke verwendeten kleinen Modelle sind nicht geeignet, um gute Parameter zu finden.

Um mehr über Strategien zur Textgeneration zu erfahren, kannst du die Huggingface-Seite über Strategien zur Textgeneration besuchen.

Nun kannst du auf die folgende Weise einen Prompt verwenden:

sentence = """The movie Gladiator was directed by Ridley Scott. 
              The main actor is """

inputs = tokenizer(sentence, return_tensors="pt")
inputs = inputs.to(device)

tokens = model.generate(**inputs,
    do_sample = True, 
    max_length = 50,                             
    #temperature = .8,
    top_k = 50, 
    top_p = 0.85
) 
output = tokenizer.batch_decode(tokens, skip_special_tokens=True)
print(output[0])

Um diesen Prozess zu vereinfachen, können wir eine Pipeline bauen. Das erste Argument der Pipeline ist die Aufgabe, für die wir die Pipeline verwenden wollen, in unserem Fall Textgenerierung. Die anderen Eingaben sind das zuvor definierte Modell und der Tokenizer, sowie die Argumente der model.generate Funktion von oben und unser spezifiziertes device.

from transformers import pipeline

text_generation_pipe = pipeline('text-generation', 
                    model=model, 
                    tokenizer = tokenizer, 
                    do_sample = True, 
                    num_beams = 5,
                    max_length = 100,                             
                    #temperature = .8,
                    top_k = 50, 
                    top_p = 0.85,
                    device = device
                   )
text_generation_pipe(sentence)

Wenn du einige Prompts in einer Textdatei hast, kannst du diese Textdatei laden und eine Pipeline zur Verarbeitung verwenden. Es ist effizienter, die Pipeline über die Daten iterieren zu lassen, als eine Schleife über die Pipeline zu verwenden.

sentences = ["The following essay is about the history of quantum mechanics.", 
             "The movie Gladiator was directed by Ridley Scott. It starred ",
             "The Eiffel Tower is in Paris, France. It is made of",
             """I want you to help me with my homework essay for school.
             The topic is the history of the Roman Empire.""",
             """The following is a list of the tallest buildings in the world.
             The tallest building in the world is the """,
             """The negative binomial distribution has the following pdf
             "$$f(x; r, p) = \binom{x+r-1}{r-1} p^r (1-p)^x,$$ where """,
             ]
def prompts():
        for sentence in sentences:
            yield sentence

for answer in text_generation_pipe(prompts(),
                                   pad_token_id=tokenizer.eos_token_id):
    print(answer) # oder answer[0]['generated_text'] 
                  # falls du nur den generierten Text sehen willst

Nun waren wir hoffentlich in der Lage:

  • ein Textgenerations-Modell herunterzuladen
  • das Modell aus einem selbstdefinierten Cache zu laden
  • mehrere Prompts zu verwenden, um das Modell zu testen

Benutze ein Python-Skript

Um größere Modelle auf PALMA zu implementieren, müssen wir alles in ein Python-Skript umwandeln. Die Skripte findest du unter incubaitor/2_PALMA/2_2_LLMs-text-generation/scripts/. Du kannst nun testen, ob das Skript auch läuft, indem du den folgenden Befehl in das Terminal eingibst:

python pythia.py --cache_dir /cloud/wwu1/d_reachiat/incubai/cache --size 70m --prompt "My sample prompt"

Du könntest auch mit mehreren Prompts experimentieren, wie denen in incubaitor/2_PALMA/2_2_LLMs-text-generation/prompts/prompts.txt und einer Ausgabedatei mit

python pythia.py --cache_dir /cloud/wwu1/d_reachiat/incubai/cache --size 70m --prompt_file ../prompts/prompts.txt --out_file out.csv

wo du eine schöne CSV-Datei deiner Prompts und des vom Modell generierten Text bekommen kannst.

Wenn du das Skript ausführst, bekommst du Informationen über den GPU-Speicher (VRAM) Verbrauch des Modells. Du musst einen CUDA-Overhead von etwa 1 GB hinzufügen, um den erwarteten Speicherverbrauch zu finden. Daher ist das 6.9b-Modell von Pythia zu groß für JupyterHub. Während du die Pipeline ausführst, kannst du ein Terminal öffnen und nvidia-smi eingeben, um den Speicherverbrauch zu sehen.

Wechsel auf PALMA

Falls alles gut gegangen ist, möchten wir zu PALMA wechseln. Bevor du es auf PALMA versuchst, stelle zunächst sicher, dass ein kleines Modell auf dem JupyterHub läuft! Wir wollen dort die GPU-Partitionen nutzen, um größere Modelle zu betreiben, als wir es im JupyterHub können. Anfangs eignet sich die gpuexpress Partition gut zum Testen.

Wenn du noch nie mit PALMA gearbeitet hast, solltest du unser Tutorial in 2_1_PALMA lesen. Auch das HPC Wiki gibt einen guten Überblick darüber, wie man PALMA benutzt.

Installation der Anforderungen

Jetzt wollen wir die Shell-Skripte im Ordner 2_PALMA/2_2_LLMs-text-generation/jobs verwenden, um Text aus unseren Modellen zu generieren.

ACHTUNG: Diese Vorgehensweise funktioniert derzeit nicht, da Transformers Torch >= 2.0 benötigt. Deshalb ist der hier aufgeführte Toolchain grad nicht gültig, das Skript install.sh beachtet dies aber schon. Du kannst dort den (derzeit) geigneten Toolchain einsehen.

Wir verwenden eine spezielle “Toolchain”, um CUDA nutzen zu können. Die folgende Toolchain ist geeignet:

module load palma/2021a
module load foss/2021a
module load PyTorch/1.10.0-CUDA-11.3.1

Du kannst diese Toolchain finden, indem du module spider PyTorch tippst. Da der Login-Knoten jedoch eine andere Architektur hat, musst du ein Jobskript erstellen, um den richtigen Namen und die CUDA-Version auf der anderen Architektur zu finden.

Wenn du diese Befehle in der Kommandozeile eingibst, siehst du, dass das letzte Modul nicht auf dem Login-Knoten verfügbar ist. Deshalb müssen wir, um weitere Pakete zu installieren, in dieser Toolchain sein. Um sicherzustellen, dass die richtigen Python- und PyTorch-Versionen verwendet werden, installieren wir das Paket transformers über pip mit einem Job-Skript namens install.sh. Da wir Torch 1.10 verwenden (eine ziemlich alte Version) müssen wir darauf achten, eine geeignete Version von transformers zu verwenden, zum Beispiel transformers==4.33.1.

Anschließend können wir das Installationsskript auf der richtigen Architektur mit dem Befehl sbatch install.sh im Verzeichnis incubaitor/2_PALMA/2_2_LLMs-text-generation/jobs/ ausführen. Wenn der Job abgeschlossen ist, überprüfe die Ausgabedatei mit vi, um sicherzustellen, dass keine neue Torch-Version installiert wurde (was viele Konflikte verursachen könnte).

Falls etwas schiefgelaufen ist, entferne die installierten Pakete in deinem Home-Verzeichnis (da sie durch die Flag --user in ~/.local/ installiert wurden, kannst du die Ordner dort entfernen).

Das Modell vorbereiten und ausführen

Jetzt sollte hoffentlich alles gut gegangen sein. Überprüfe nun das Skript inference.sh. Wenn du dein Modell in deinem usershare hast, solltest du es im Skript als Modellverzeichnis verwenden. Wenn du keinen usershare hast, kannst du das gesamte Modellverzeichnis in dein Scratch-Verzeichnis kopieren. Zum Beispiel auf PALMA (!) verwende

cp -r ~/cloud/wwu1/u_jupyterhub/home/<first letter of username>/<username>/.cache/huggingface/models/models--EleutherAI--pythia-70m-deduped $WORK/models/huggingface/

wenn du den Standard Huggingface Cache benutzt hast (siehe Huggingface Cache oben). Es gibt keine schöne Möglichkeit, die Huggingface Modelle direkt herunterzuladen, also falls nötig, starte ein Skript (siehe oben), das die Modelle in das Scratch-Verzeichnis herunterlädt, aber nicht startet (oder aufgrund von Limits abstürzt).

Jetzt sollten die Daten in deinem Scratch-Verzeichnis sein. Wir sollten bereit sein, das erste kleine Modell zu starten. Gehe zurück zu ~/incubaitor/incubaitor/2_PALMA/2_2_LLMs-text-generation/jobs/ und starte den Job mit dem Befehl sbatch inference.sh.

In der Ausgabedatei kannst du lesen, ob alles gut gelaufen ist, zum Beispiel über vi slurm-pythia-test-1b-express.out. Außerdem sollte die Ausgabedatei auf deiner Scratch-Partition sein. Du solltest die Inhalte der Ausgabedatei lesen können mit vi /scratch/tmp/<username>/pythia-70m-express.csv oder auf diese Datei zugreifen können, indem du sie zu $WORK/transfer kopierst, wenn du die PALMA Nextcloud Integration vorbereitet hast und sie über die Web-Schnittstelle herunterlädst (noch in Entwicklung). Für weitere Informationen über den Datentransfer besuche die HPC Dokumentation.

Wenn du mit den Ergebnissen zufrieden bist, teste, ob du auch die 1b Version auf die gleiche Weise zum Laufen bekommen kannst indem du die entsprechenden Parameter im Skript inference.sh änderst.

Passe das Skript an deine Bedürfnisse an

Wenn du etwas im Skript ändern möchtest oder andere Funktionen des Modells testen möchtest, kannst du auch mit den kleinen Modellen im JupyterHub spielen. Falls Ressourcen verfügbar sind, kannst du auch das jupyter.sh Skript auf PALMA starten und auf deinem eigenen Rechner herumspielen (passe dieses ggf. selbstständig auf den notwendigen Toolchain an).

Wenn du bereit bist, kannst du diese Änderungen in der Datei inference.py vornehmen (der beste Weg wäre, sie in dein privates Git zu kopieren, Änderungen vorzunehmen, die Änderungen zu PALMA zu ziehen und das Skript zu Testzwecken auf einem kleinen Modell auszuführen).

Dann, wenn Ressourcen verfügbar sind, kannst du versuchen, Inferenzen auf einem größeren Modell auf Palma durchzuführen. Siehe die Jobscripts für das 6.9b und 12b Modell.

Jenseits der Textgenerierung

Es gibt viele andere Arten von Modellen auf Huggingface. Sie alle arbeiten mit ähnlichen Pipelines. Du kannst auf der Modellkarte (oben rechts </> Use in transformers) nachsehen, wie die Modelle geladen und eine Pipeline aufgebaut werden kann. Für die Pipeline solltest du aufgrund der Caching-Probleme einen ähnlichen Ansatz wie oben verwenden. (Denke daran, beim downloaden local_files_only=False zu setzen!)

Dann musst du überprüfen, wie die Pipeline mit Inputs versorgt wird und wie die Outputs aussehen. Dies sollte ebenfalls auf der Modellkarte bereitgestellt werden. Beispielsweise kann das Folgende für Textklassifikation verwendet werden. Stelle sicher, dass das Caching korrekt durchgeführt wird.

from transformers import AutoTokenizer, AutoModelForSequenceClassification

del text_generation_pipe

model_checkpoint = "facebook/bart-large-mnli"

tokenizer = AutoTokenizer.from_pretrained(
    model_checkpoint,
    cache_dir=cache_dir,
    local_files_only=True
)

model = AutoModelForSequenceClassification.from_pretrained(
    model_checkpoint,
    cache_dir=cache_dir,
    local_files_only=True
)

model = model.to(device)

classifier = pipeline(
    "zero-shot-classification",
    model=model,
    tokenizer=tokenizer,
    device=device
)
sequence_to_classify = """Given our strong start to 2021 and underlying 
acquisition retention and monetization of players we are increasing 
our guidance to $1.05 billion to $1.15 billion of revenue for 2021 
which equates to year-over-year growth of 63% to 79% and a 16% 
increase compared to the midpoint of our prior guidance."""

candidate_labels = ['increase', 'decrease']

result = classifier(sequence_to_classify, candidate_labels)

print(result)
from transformers import AutoTokenizer, AutoModelForQuestionAnswering

del classifier

model_checkpoint = "consciousAI/question-answering-roberta-base-s-v2"

tokenizer = AutoTokenizer.from_pretrained(
    model_checkpoint,
    cache_dir=cache_dir,
    local_files_only=True
)

model = AutoModelForQuestionAnswering.from_pretrained(
    model_checkpoint,
    cache_dir=cache_dir,
    local_files_only=True
)

model = model.to(device)

question_answerer = pipeline(
    "question-answering",
    model=model,
    tokenizer=tokenizer,
    device=device
)
sentence = """Our active customer count grew by 57% over the first 
quarter of last year to reach $33 million; and we delivered 
14.7 million orders 49% more than the year prior"""

question = "How many orders?"
result = question_answerer(question=question, context=sentence)
print(result)

Du kannst auch mehrere Fragen (bei mehreren Texten) verwenden, indem du über die pipeline iterierst.

import pandas as pd

df = pd.DataFrame({
    'text': [sentence] * 3,
    'question' : [
        'how many orders?', 
        'how many customers?', 
        'how much revenue?'
    ]
})

def prompts():
    for i, row in df.iterrows():
        yield {
            'context': row['text'], 
            'question': row['question']
        }

for answer in question_answerer(prompts()):
    print(answer['answer'])

Jetzt kannst du Huggingface-Modelle verwenden! Weitere Modelle zur Audio- oder Bilderkennung benötigen zusätzliche Pakete wie OpenCV, die ebenfalls in der Toolchain von PALMA vorhanden sein können.