Inferenz von Textgenerierungsmodellen

Download this notebook

Auf JupyterHub

Im ersten Teil unseres Tutorials über LLMs werden wir lernen, wie man ein Texterzeugungsmodell 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 transformers installieren musst, verwende pip in deiner Standard-Python-Umgebung:

pip install transformers

Torch sollte vorinstalliert sein (JupyterHub) oder in Ihrer 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 (oder einen PALMA-User hast), 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 ihr hier Fehler bekommt, kann das an den Berechtigungen liegen. Verwenden Sie dann den Standard-Cache von Huggingface.
  • Für größere Modelle kannst du auch einen OpenStack Usershare /cloud/wwu1/{group}/{share}/cache verwenden (aber PALMA scratch könnte schneller sein)
  • 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 in den CPU-Speicher und dann in den GPU-Speicher geladen
# Um dies zu vermeiden, kannst du das Modell direkt in den GPU-Speicher laden mit der Bibliothek accelerate

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
# Um den von CUDA insgesamt genutzten Speicher zu ermitteln, verwende 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 Textgenerationsstrategien zu erfahren, kannst du die Huggingface-Seite über Textgenerationsstrategien 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,                             
    #to test how long we can generate and it be coherent
    #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,                             
                    #to test how long we can generate and it be coherent
                    #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) # or answer[0]['generated_text'] if you want to print only the generated text

Nun waren wir hoffentlich in der Lage:

  • ein Textgenerationsmodell 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 finden Sie unter 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 ~/2_2_LLMs/text-generation/data/prompts.txt und einer Ausgabedatei mit

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

wo du eine schöne csv deiner prompts und des vom Modell generierten 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 finden.

Wechsel auf PALMA

Falls alles gut gegangen ist, möchten wir zu PALMA wechseln. 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, kannst 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_2_LLMs/text-generation/jobs verwenden, um Text aus unseren Modellen zu generieren.

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 /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 pythia-70m-test.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/2_2_LLMs/text-generation/models/

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/2_2_LLMs/text-generation/jobs/ und starte den Job mit dem Befehl sbatch pythia-70m-test.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.

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.

Wenn du bereit bist, kannst du diese Änderungen in der Datei pythia.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.

Llama-2 und andere Modelle

Der Llama Text-Generator wird von Meta bereitgestellt. Um die Huggingface-Version herunterzuladen, musst du dich bei Meta für die Verwendung der Llama-2 Modelle registrieren und einen Zugangskey von Huggingface haben. Nachdem du dieses Modell zum Beispiel auf JupyterHub heruntergeladen und in deiner Benutzerfreigabe oder Scratch-Verzeichnis gecacht hast, kannst du darauf auf PALMA zugreifen. Das kleinste Modell könnte zu groß für JupyterHub sein und deinen Kernel abstürzen lassen, aber es könnte eine bequeme Möglichkeit sein, es herunterzuladen.

Wenn du das hast, sieh dir das llama.py Skript und das entsprechende Jobskript an. Die einzige Änderung in der Python-Datei ist die Modellauswahl. So kannst du dein Skript für jedes Huggingface text-generation Modell, das du verwenden möchtest, ändern.

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
model_checkpoint = "consciousAI/question-answering-roberta-base-s-v2"
del classifier

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.