In dem vorherigen Beitrag haben wir gezeigt wie man unterschiedliche
Textformate aufbereiten kann und welche Daten für das LLaMA Training verwendet wurden. Doch wie
wurde das LLaMA Netz von Meta trainiert?
Generell gibt es nicht nur ein LLaMA Netz von Meta. Insgesamt wurden 4 unterschiedlich große
Sprachmodelle trainiert. Diese Varrieren mit einer Größe zwischen 7B und 65B Parametern.
Wir haben gesehen, dass das initiale Training der Netze enorm Ressourcen benötigt. Die LLaMA Netze
ermöglichen es mit
deutlich geringerem Aufwand angepasste Sprachmodelle zu erzeugen, da diese bereits die Grundkenntnisse
erlernt haben.
Sie sind jeodch bis jetzt "nur" Vervollständigungsmodelle.
Dementsprechend muss man mit diesen auch interagieren. In einem späteren Schritt zeigen wir, wie man die
Sprachmodelle zu sogenannten Chat oder Instruction basierten Sprachmodellen umtrainiert.
Zunächst zeigen wir, wie man den Sprachstil aus Johann Wolfgang von Goethes Faust in ein großes
Sprachmodell
eintrainiert, um eine Vervollständigung eines vom Nutzer vorgegebenen Textes im Stile von Faust zu
erhalten.
Wir haben bereits gesehen, dass die LLaMA und Falcon-Modelle lediglich begrenzte Kenntnisse in Deutsch
besitzen. Für
einen ersten Schritt werden wir also ein spezielles Netz verwenden, das mit einer großen Anzahl an
deutschen Daten angepasst wurde: BLOOM-CLP German 6.4B
.
Die deutschen Sprachkentnisse sind hierbei größer und der Tokenizer ist für deutsch optimiert.
Der vom DFKI verwendete Rohdatensatz
enthält teilweise sinnfreie (bspw. Zahlenreihen) und auch nicht jugendfreie Inhalte.
Angeblich wurde der Datensatz vom DFKI nachberarbeitet um solche Inhalte zu entfernen. Das fertig
trainierte Netz
gibt sie jedoch immer noch aus. Exemplarisch dafür stehen die Zahlenreihe
12286;12294 oder der Satzanfang "Die junge Oma", wenn sie im Greedy-Modus eingegeben
werden. Bei einer kommerziellen Anwendung sollte unerwünschte Inhalte im Idealfall bereits bei der Datensammlung
(scrapen) über
eine Ausschussliste, oder bei der Datenaufbereitung entfernt werden. Wenn man sein eigenes Modell auf ein bereits
trainiertes Netz aufbauen will, muss die Ausgabe daher zwingend auf solche Inhalte geprüft werden.
Zum Training verwenden wir die Transformer Bibliothek, welche für das eigentliche Training PyTorch
verwendet.
Da wir als Hardware ein RTX 3090 verwenden, sind noch einige Anpassungen notwendig, unter anderem die
Einbindung der
Bibliothek Deepspeed, da ansonsten trotz 24 GB nicht ausreichend GPU-Speicher vorhanden wäre.
Zuerst müssen wir einen Tokenizer laden. Hierfür verwenden wir auch wieder die Transformers Bibliothek:
from transformers import AutoTokenizer, AutoModelForCausalLM
MODEL_NAME = "malteos/bloom-6b4-clp-german"
tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME, use_fast=False)
tokenizer.pad_token = tokenizer.eos_token
Downloading (…)okenizer_config.json: 0%| | 0.00/700 [00:00<?, ?B/s]
Downloading tokenizer.model: 0%| | 0.00/500k [00:00<?, ?B/s]
Downloading (…)cial_tokens_map.json: 0%| | 0.00/411 [00:00<?, ?B/s]
Das Tokenizing wird verwendet, um die Textdaten für das Sprachmodell verständlich aufzubereiten.
Man kann es
sich wie eine Art Übersetzung vorstellen.
Die einzelnen Textbausteine werden in einzelne Token bzw. Nummern übertragen.
Hier mal ein Beispiel:
from termcolor import colored
from pprint import pprint
def visualize_tokenizer(tokenizer, example_text):
print("Tokenized Text:")
tokens = tokenizer.encode(example_text)
token_colors = {}
colored_tokens = []
str_tokens = []
for i, token in enumerate(tokens):
token_colors[token] = 'on_blue' if i % 2 == 0 else 'on_dark_grey'
colored_token = colored(tokenizer.decode(token), on_color=token_colors[token])
str_tokens.append(colored(token, on_color=token_colors[token]))
colored_tokens.append(colored_token)
print(' '.join(str_tokens))
print(''.join(colored_tokens))
print(len(tokens))
visualize_tokenizer(tokenizer, dataset[0]["text"])
Tokenized Text: 11843 5814 14311 3836 14 186 7740 1783 12 272 875 803 437 1731 12 186 901 3084 273 19890 1052 267 12 7744 2937 12 186 51 1392 12 722 875 1697 295 2621 968 411 186 5348 671 673 46025 21065 31 186 1165 27674 708 207 68 69 82 3868 310 296 8684 12 186 14073 1311 484 6542 273 2714 12177 14 186 601 27759 482 12 272 44519 361 22023 12 186 2951 25494 4832 403 340 2228 14 186 1960 7362 739 345 3563 2305 15546 411 186 16730 703 486 273 2655 4090 10194 411 14 186 1165 1985 12 452 540 413 6199 419 4124 1443 11989 27 186 7264 437 21420 1206 455 1634 3203 14 186 24917 482 484 380 333 2386 395 20911 12 186 33508 484 587 26747 801 6117 14 186 2819 1066 396 2357 83 12 2031 1142 6891 273 885 186 2951 345 4057 421 42263 1561 31 186 10292 20800 3006 455 4090 272 3868 1514 12 186 2040 403 207 68 69 82 3800 501 671 673 49124 32387 12 186 2951 345 29253 36039 474 490 186 35799 568 272 13528 35289 704 2452 3713 9178 27 186 2219 15131 77 1527 12 739 491 276 632 12 186 1630 12453 689 403 620 380 272 12554 294 320 186 2951 12 452 295 49084 433 9697 494 7352 380 21862 405 3954 12 186 3124 340 284 2652 84 403 1939 272 325 1035 265 18186 14 186 9045 6518 5090 361 437 996 660 274 3045 186 917 19053 580 27 1663 2653 12 379 27382 422 1272 1 186 Visualisierte Aufteilung: DIREKTOR. Ihr beiden, die ihr mir so oft, In Not und Trübsal, beigestanden, Sagt, was ihr wohl in deutschen Landen Von unsrer Unternehmung hofft? Ich wünschte sehr der Menge zu behagen, Besonders weil sie lebt und leben läßt. Die Pfosten sind, die Bretter aufgeschlagen, Und jedermann erwartet sich ein Fest. Sie sitzen schon mit hohen Augenbraunen Gelassen da und möchten gern erstaunen. Ich weiß, wie man den Geist des Volks versöhnt; Doch so verlegen bin ich nie gewesen. Zwar sind sie an das Beste nicht gewöhnt, Allein sie haben schrecklich viel gelesen. Wie machen wir’s, daß alles frisch und neu Und mit Bedeutung auch gefällig sei? Denn freilich mag ich gern die Menge sehen, Wenn sich der Strom nach unsrer Bude drängt, Und mit gewaltig wiederholten Wehen Sich durch die enge Gnadenpforte zwängt; Bei hellem Tage, schon vor vieren, Mit Stößen sich bis an die Kasse ficht Und, wie in Hungersnot um Brot an Bäckertüren, Um ein Billet sich fast die Hälse bricht. Dies Wunder wirkt auf so verschiedne Leute Der Dichter nur; mein Freund, o tu es heute!
Wir sehen also, dass nicht jedes Wort automatisch einem Token entspricht. Außerdem ist jeder Tokenizer an
eine Sprache oder einem Datensatz angepasst. Somit braucht ein englischer Tokenizer für den gleichen
Text mehr Tokens. Oder
auch anders herum:
Übersetzen wir den gleichen Text auf englisch und tokenizen diesen dann, werden insgesamt 355 anstatt
280 Tokens benötigt. Im Vergleich hierzu, der deutsch Text hat 179 Wörter und 1056 Zeichen, während der
englisch 194 Wörter und 1011 Zeichen besitzt.
Warum spielt das eine Rolle für uns?
Das Sprachmodell kann lediglich eine bestimmte Anzahl an Tokens die Sekunde ausgeben. Werden mehr Tokens
für die gleiche Länge an Text benötigt, so ist die Ausgabe des Netzes langsamer. Ebenso erfolgt die
Abrechnung kommerzieller API-Schnittstellen in der Regel nach der Tokenanzahl. So zahlt man bei OpenAI
für einen gleich langen Text in Deutsch mehr als für einen englischen Text.
Englischer LLaMA Tokenizer
Deutscher BLOOM Tokenizer
from datasets import Dataset, Features, Value
dataset = Dataset.from_dict({"text": final_text}, features=Features({"text": Value("string")}))
# Aufsplitten in einen Trainings- und Validierungsdatensatz:
# dataset = dataset.train_test_split(test_size=TRAIN_TEST_SPLIT)
# Tokenizen des gesamten Datensatzes:
def tokenize(batch):
return tokenizer(list(batch["text"]))
dataset = dataset.map(tokenize, batched=True, remove_columns=["text"])
# Aneinanderreihen der Texte:
def group_texts(examples):
concatenated_examples = {k: sum(examples[k], []) for k in examples.keys()}
total_length = len(concatenated_examples[list(examples.keys())[0]])
if total_length % BLOCK_SIZE != 0:
padding_length = BLOCK_SIZE - (total_length % BLOCK_SIZE)
for k in concatenated_examples.keys():
concatenated_examples[k] += [tokenizer.pad_token_id] * padding_length
total_length += padding_length
result = {
k: [t[i : i + BLOCK_SIZE] for i in range(0, total_length, BLOCK_SIZE)]
for k, t in concatenated_examples.items()
}
result["labels"] = result["input_ids"].copy()
return result
dataset = dataset.map(group_texts, batched=True)
flat_list = [item for sublist in dataset['input_ids'] for item in sublist]
print("Anzahl der gesamten Token im Datensatz:", len(flat_list))
Map: 0%| | 0/799 [00:00<?, ? examples/s] Anzahl der gesamten Token im Datensatz: 45056
Wie bereits gesagt, müssen einige Einstellungen für das Training vorgenommen werden, da für das Training eine RTX 3090 verwendet wird und der Speicher dadurch auf 24 GB begrenzt ist. Zuerst erzeugen wir eine DeepSpeed-Configuration, damit das Ganzen auf der Grafikkarte lauffähig ist. Anschließend erzeugen wir unsere Trainingskonfiguration, den dazugehörigen Trainer und laden das Sprachmodell.
from transformers import TrainingArguments, Trainer, default_data_collator
print("Vorbereiten der Trainingseinstellungen")
training_args = TrainingArguments(
"./output",
per_device_train_batch_size=BATCH_SIZE,
logging_steps=1,
save_total_limit=2,
save_strategy="epoch",
evaluation_strategy="no",
per_device_eval_batch_size=BATCH_SIZE,
learning_rate=LR,
weight_decay=WEIGHT_DECAY,
warmup_steps=WARMUP_STEPS,
optim="adam",
num_train_epochs=EPOCHS,
push_to_hub=False,
bf16=True,
gradient_checkpointing=True,
deepspeed=deepspeed, # Hier json Konfiguration verlinken oder in Python die Konfiguration definieren
gradient_accumulation_steps=GRADIENT_ACCUMULATION_STEPS
)
print("Laden des Modells")
model = AutoModelForCausalLM.from_pretrained(MODEL_NAME, use_cache=False)
model.resize_token_embeddings(len(tokenizer))
trainer = Trainer(
model=model,
args=training_args,
train_dataset=dataset['train'],
eval_dataset=dataset['test'],
tokenizer=tokenizer,
data_collator=default_data_collator,
)
# Modell trainieren
trainer.train()
Vorbereiten der Trainingseinstellungen 3%|▎ | 1/30 [00:36<17:23, 35.97s/it] {'loss': 3.6562, 'learning_rate': 2e-05, 'epoch': 0.1} 33%|███▎ | 10/30 [06:49<12:00, 36.00s/it] {'loss': 2.9609, 'learning_rate': 2e-05, 'epoch': 0.95} 67%|██████▋ | 20/30 [17:13<06:44, 40.40s/it] {'loss': 1.9102, 'learning_rate': 2e-05, 'epoch': 1.9} 97%|█████████▋| 29/30 [31:31<00:56, 56.02s/it] {'loss': 1.1523, 'learning_rate': 2e-05, 'epoch': 2.76} 100%|██████████| 30/30 [32:14<00:00, 52.11s/it] {'loss': 1.0098, 'learning_rate': 2e-05, 'epoch': 2.86} 100%|██████████| 30/30 [37:10<00:00, 74.33s/it] {'train_runtime': 2230.0221, 'train_samples_per_second': 0.112, 'train_steps_per_second': 0.013, 'train_loss': 2.187890625, 'epoch': 2.86} TrainOutput(global_step=30, training_loss=2.187890625, metrics={'train_runtime': 2230.0221, 'train_samples_per_second': 0.112, 'train_steps_per_second': 0.013, 'train_loss': 2.187890625, 'epoch': 2.86})
Da das Modell als Textvervollständiger trainiert wurden, muss eine Anfangssequenz dem Sprachmodell
zugeführt werden.
Hierfür verwenden wir einen Teil des Originaltextes:
MARGARETE.
Müßte vor dem Herren schamrot werden.
MEPHISTOPHELES.
Der Text geht folgend noch weiter:
Vor keinem Könige der Erden.
Marthe:
Da hinterm Haus in meinem Garten
Wollen wir der Herren heut abend warten.
Nun wenden wir das Modell an. Dazu tokenizen wir den vorgegebenen Text und führen diesen dann dem
Sprachmodell zu. Anschließend müssen wir den zurückgegebenen Text wieder decoden, um Ihn lesbar zu
machen.
enc_txt = tokenizer.encode("MARGARETE.\nMüßte vor dem Herren schamrot werden.\nMEPHISTOPHELES.", return_tensors="pt").to("cuda")
ret_txt = model.generate(enc_txt,
max_length=512,
repetition_penalty=1.05)
print(tokenizer.decode(ret_txt[0]))
MARGARETE. Müßte vor dem Herren schamrot werden. MEPHISTOPHELES. Das kommt nur auf die Weise an, Wie man sich in Gegenwart des Herrn verhält; Ich weiß mich sehr wohl zu betragen— Nur muß ich gleich wieder fort! <|endoftext|>FAUST. Du darfst nicht so von dir gehen! Was fragst du nach deiner Nachbarin? Sie ist doch eine Fremde hier. (Er geht weiter.) CHOR DER ENGEL. Christ ist erstanden! Freudig sei der Welt! Die Sonne steige nun höher denn je und scheine heller als sonst über den Auen, bis sie im Meer versinke. [...]
Wir sehen also, dass wir dem Sprachmodell Informationen von Faust beibringen konnten. Wofür kann das
verwendet werden? Allgemein können damit weitere Informationen in das Sprachmodell eintrainiert werden.
Einige Beispiele hierfür sind:
- unterschiedliche betriebsinterne Dokumente
- verschiedene Code-Bausteine
- Betriebliche Dokumentationen
- Anleitungen zu verschiedenen Tools und Programmen
- Die Kosten des Trainings der LLaMA-Modell varriert je nach Größe des Sprachmodells und beträgt für das
größte Modell ca. 1.5 Mio. $
- Die Stromkosten für das Training würden bei 30 ct/kWh 135t€ betragen.
- Durch die Verwendung kleinerer speziell angepasster Netze können Rechenzeiten, Kosten und
CO2-Emissionen reduziert werden.
- Es wurde aufgezeigt wie ein Tokenizer einen Text für ein Sprachmodell verständlich macht und die
Nachteile eines mehrsprachigen Tokenizers aufgezeigt.
- Es wird gezeigt, wie man ein spezielles deutsches Sprachmodell (BLOOM-CLP German 6.4B) für die
Vervollständigung von Texten im Stil von Johann Wolfgang von Goethes Faust eintrainieren kann.
- Es können unterschiedliche Dokumente, wie betriebsinterne Abläufe, Dokumentationen u.ä. verwendet
werden.
In unserem nächsten Blogbeitrag zeigen wir, wie man ein Sprachmodell für die Übersetzung von großen
Datensätzen verwenden kann.