LLaMA 3 ist nach Mistral eines der vielversprechendsten Open-Source-Modelle und löst ein breites Aufgabenspektrum. Ich habe zuvor auf Medium einen Blog über die Erstellung eines LLM mit über 2,3 Millionen Parametern von Grund auf mithilfe der LLaMA-Architektur geschrieben. Nachdem LLaMA-3 nun veröffentlicht ist, werden wir es auf einfachere Weise neu erstellen.
Wir werden für diesen Blog keine GPU verwenden, aber Sie benötigen mindestens 17 GB RAM, da wir einige Dateien laden werden, die mehr als 15 GB groß sind. Wenn dies ein Problem für Sie ist, können Sie Kaggle als Lösung verwenden. Da wir keine GPU benötigen, bietet Kaggle 30 GB RAM und nutzt als Beschleuniger ausschließlich CPU-Kerne.
Hier ist der Blog-Link, der Sie durch die Erstellung eines LLM mit mehr als 2,3 Millionen Parametern von Grund auf führt: LLM mit mehr als 2,3 Millionen Parametern von Grund auf neu
Das Gute daran ist, dass wir keine objektorientierte Programmierung (OOP) verwenden werden, sondern nur reine Python-Programmierung. Sie sollten jedoch über ein grundlegendes Verständnis neuronaler Netze und der Transformer-Architektur verfügen. Dies sind die einzigen beiden Voraussetzungen, die erforderlich sind, um dem Blog folgen zu können.
Thema | Link |
---|---|
Transformatortheorie | Videolink |
Theorie neuronaler Netze | Videolink |
Python-Grundlagen | Videolink |
Bevor Sie sich mit den technischen Details befassen, müssen Sie zunächst wissen, dass die gesamte Architektur von LLaMA 3 mit der von LLaMA 2 identisch ist. Wenn Sie sich also noch nicht mit den technischen Details von LLaMA 3 befasst haben, wird dies nicht der Fall sein ein Problem für Sie, diesem Blog zu folgen. Auch wenn Sie kein Verständnis für die LLaMA 2-Architektur haben, machen Sie sich keine Sorgen, wir werden uns auch einen allgemeinen Überblick über die technischen Details ansehen. Dieser Blog ist in jedem Fall für Sie gedacht.
Hier sind einige wichtige Punkte zu LLaMA 2 und LLaMA 3. Falls Sie bereits mit deren Architektur vertraut sind:
BESONDERHEIT | Lama 3 | Lama 2 |
---|---|---|
Tokenizer | Tiktoken (entwickelt von OpenAI) | Satzstück |
Anzahl der Parameter | 8B, 70B | 70B, 13B, 7B |
Trainingsdaten | 15T-Token | 2,2T-Token |
Kontextlänge | 8192 Token | 4096 Token |
Aufmerksamkeitsmechanismus | Gruppierte Abfrage-Aufmerksamkeit | Gruppierte Abfrage-Aufmerksamkeit |
Feinabgestimmte Modelle | Ja | Ja |
Leistung | In allen Benchmarks besser als Llama 2 | Bei den meisten Benchmarks besser als Llama 1 |
Rechenanforderungen | Sehr hoch (70B-Modell) | Sehr hoch (70B-Modell) |
Verfügbarkeit | Open Source | Open Source |
Verstärkendes Lernen aus menschlichem Feedback | Ja | Ja |
Anzahl der unterstützten Sprachen | 30 Sprachen | 20 Sprachen |
Geeignet für | Am besten geeignet für anspruchsvollere Aufgaben wie Argumentation, Codierung und Eignungstests | Gut für anspruchsvollere Aufgaben wie logisches Denken, Codieren und Eignungstests |
Es ist wichtig, die Architektur von LLaMA 3 zu verstehen, bevor man sich mit der Codierung beschäftigt. Zum besseren visuellen Verständnis finden Sie hier ein Vergleichsdiagramm zwischen dem Vanilla Transformer, LLaMA 2/3 und Mistral.
Schauen wir uns die wichtigsten Komponenten von LLaMA 3 etwas genauer an:
Im LLaMA 3-Ansatz, der mit LLaMA 2 identisch ist, wird eine Technik namens RMSNorm verwendet, um den Eingang jeder Transformator-Unterschicht zu normalisieren.
Stellen Sie sich vor, Sie lernen für eine große Prüfung und haben ein riesiges Lehrbuch voller Kapitel. Jedes Kapitel stellt ein anderes Thema dar, aber einige Kapitel sind für das Verständnis des Themas wichtiger als andere. Bevor Sie nun in das gesamte Lehrbuch eintauchen, beschließen Sie, die Bedeutung jedes einzelnen Kapitels zu bewerten. Sie möchten nicht für jedes Kapitel gleich viel Zeit aufwenden; Sie möchten sich mehr auf die kritischen Punkte konzentrieren. Hier kommt die Vornormalisierung mit RMSNorm für große Sprachmodelle (LLMs) wie ChatGPT ins Spiel. Es ist, als würde man jedem Kapitel anhand seiner Bedeutung eine Gewichtung zuweisen. Kapitel, die für das Thema von grundlegender Bedeutung sind, erhalten eine höhere Gewichtung, während weniger wichtige Kapitel eine niedrigere Gewichtung erhalten.
Bevor Sie sich also intensiv mit dem Lernen befassen, passen Sie Ihren Studienplan an die gewichtete Bedeutung jedes Kapitels an. Sie widmen den Kapiteln mit höherer Gewichtung mehr Zeit und Mühe und stellen so sicher, dass Sie die Kernkonzepte gründlich verstehen.
In ähnlicher Weise hilft die Vornormalisierung mithilfe von RMSNorm LLMs dabei, Prioritäten zu setzen, welche Teile des Textes für das Verständnis des Kontexts und der Bedeutung wichtiger sind. Es weist wesentlichen Elementen eine höhere Gewichtung und weniger wichtigen Elementen eine niedrigere Gewichtung zu und stellt so sicher, dass das Modell seine Aufmerksamkeit dort richtet, wo sie für ein genaues Verständnis am meisten benötigt wird. Interessierte Leser können hier die detaillierte Implementierung von RMSNorm erkunden.
LLaMA führt die SwiGLU-Aktivierungsfunktion ein und lässt sich dabei von PaLM inspirieren.
Stellen Sie sich vor, Sie sind ein Lehrer, der seinen Schülern ein komplexes Thema erklären möchte. Sie haben ein großes Whiteboard, auf dem Sie wichtige Punkte aufschreiben und Diagramme zeichnen, um die Dinge klarer zu machen. Aber manchmal ist Ihre Handschrift möglicherweise nicht sehr sauber oder Ihre Diagramme sind nicht perfekt gezeichnet. Dadurch kann es für Ihre Schüler schwieriger werden, den Stoff zu verstehen.
Stellen Sie sich nun vor, Sie hätten einen magischen Stift, der Größe und Stil Ihrer Handschrift automatisch anpasst, je nachdem, wie wichtig jeder Punkt ist. Wenn etwas wirklich entscheidend ist, schreibt der Stift es größer und klarer, sodass es hervorsticht. Wenn es weniger wichtig ist, schreibt der Stift es kleiner, aber immer noch lesbar. SwiGLU ist wie ein Zauberstift für große Sprachmodelle (LLMs) wie ChatGPT. Vor der Textgenerierung passt SwiGLU die Wichtigkeit jedes Wortes oder Satzes basierend auf seiner Relevanz für den Kontext an. So wie der Zauberstift die Größe und den Stil Ihrer Schrift anpasst, passt SwiGLU die Betonung jedes Wortes oder Satzes an.
Wenn das LLM also Text generiert, kann es die wichtigen Teile stärker hervorheben, wodurch sie besser wahrgenommen werden und sichergestellt wird, dass sie besser zum Gesamtverständnis des Textes beitragen. Auf diese Weise hilft SwiGLU LLMs dabei, Texte zu erstellen, die klarer und leichter verständlich sind, ähnlich wie der Zauberstift Ihnen dabei hilft, klarere Erklärungen für Ihre Schüler auf dem Whiteboard zu erstellen. Weitere Details zu SwiGLU finden Sie im zugehörigen Papier.
Rotary Embeddings, oder RoPE, ist eine Art Positionseinbettung, die in LLaMA 3 verwendet wird.
Stellen Sie sich vor, Sie befinden sich in einem Klassenzimmer und möchten den Schülern Sitzplätze für Gruppendiskussionen zuweisen. Typischerweise werden Sie die Sitze in Reihen und Spalten anordnen, wobei jeder Schüler eine feste Position einnimmt. In manchen Fällen möchten Sie jedoch eine dynamischere Sitzordnung schaffen, in der sich die Schüler freier bewegen und interagieren können.
ROPE ist wie eine spezielle Sitzanordnung, die es den Schülern ermöglicht, sich zu drehen und ihre Position zu ändern, während sie gleichzeitig ihre relative Position zueinander beibehalten. Anstatt an einem Ort fixiert zu sein, können sich die Schüler jetzt in kreisenden Bewegungen bewegen, was eine flüssigere Interaktion ermöglicht.
In diesem Szenario stellt jeder Schüler ein Wort oder einen Token in einer Textsequenz dar und seine Position entspricht seiner Position in der Sequenz. So wie ROPE es den Schülern ermöglicht, sich zu drehen und ihre Position zu ändern, ermöglicht ROPE, dass sich die Positionseinbettungen von Wörtern in einer Textsequenz basierend auf ihrer relativen Position zueinander dynamisch ändern. Bei der Verarbeitung von Text führt ROPE also einen Rotationsaspekt ein, anstatt Positionseinbettungen als fest und statisch zu behandeln, was flexiblere Darstellungen ermöglicht, die die dynamischen Beziehungen zwischen Wörtern in der Sequenz erfassen. Diese Flexibilität hilft Modellen wie ChatGPT, Text besser zu verstehen und zu generieren, der natürlich fließt und die Kohärenz beibehält, ähnlich wie eine dynamische Sitzordnung interaktivere Diskussionen in einem Klassenzimmer fördert. Wer sich für die mathematischen Details interessiert, kann sich auf das RoPE-Papier beziehen.
LLaMA 3 verwendet Byte Pair Encoding (BPE) aus der von OpenAI eingeführten Tiktoken-Bibliothek, während der LLaMA 2 Tokenizer BPE auf der Satzstück-Bibliothek basiert. Es gibt einen kleinen Unterschied zwischen ihnen, aber
Lassen Sie uns zunächst lernen, was BPE eigentlich ist.
Beginnen wir mit einem einfachen Beispiel. Angenommen, wir haben einen Textkorpus mit den Wörtern „ab“, „bc“, „bcd“ und „cde“. Wir beginnen mit der Initialisierung unseres Vokabulars mit allen einzelnen Zeichen im Textkorpus, sodass unser anfängliches Vokabular {„a“, „b“, „c“, „d“, „e“} ist.
Als nächstes berechnen wir die Häufigkeit jedes Zeichens im Textkorpus. Für unser Beispiel sind die Häufigkeiten: {"a": 1, "b": 3, "c": 3, "d": 2, "e": 1}.
Jetzt beginnen wir mit dem Zusammenführungsprozess. Wir wiederholen die folgenden Schritte, bis unser Wortschatz die gewünschte Größe erreicht hat:
Zunächst ermitteln wir das häufigste Paar aufeinanderfolgender Zeichen. In diesem Fall ist das häufigste Paar „bc“ mit einer Häufigkeit von 2. Anschließend führen wir dieses Paar zusammen, um eine neue Unterworteinheit „bc“ zu erstellen. Nach dem Zusammenführen aktualisieren wir die Häufigkeitszahlen, um die neue Unterworteinheit widerzuspiegeln. Die aktualisierte Häufigkeit ist {"a": 1, "b": 2, "c": 2, "d": 2, "e": 1, "bc": 2}. Wir fügen unserem Vokabular die neue Unterworteinheit „bc“ hinzu, die nun zu {„a“, „b“, „c“, „d“, „e“, „bc“} wird.
Wir wiederholen den Vorgang. Das zweithäufigste Paar ist „cd“. Wir verschmelzen „cd“ zu einer neuen Unterworteinheit „cd“ und aktualisieren die Häufigkeitszahlen. Die aktualisierte Häufigkeit ist {"a": 1, "b": 2, "c": 1, "d": 1, "e": 1, "bc": 2, "cd": 2}. Wir fügen „cd“ zum Vokabular hinzu, was zu {„a“, „b“, „c“, „d“, „e“, „bc“, „cd“} führt.
Wenn wir den Prozess fortsetzen, ist das nächste häufige Paar „de“. Wir verschmelzen „de“, um die Unterworteinheit „de“ zu bilden, und aktualisieren die Häufigkeitszahlen auf {„a“: 1, „b“: 2, „c“: 1, „d“: 1, „e“: 0, „bc“: 2, „cd“: 1, „de“: 1}. Wir fügen dem Vokabular „de“ hinzu und machen daraus {„a“, „b“, „c“, „d“, „e“, „bc“, „cd“, „de“}.
Als nächstes finden wir „ab“ als das häufigste Paar. Wir verschmelzen „ab“, um die Unterworteinheit „ab“ zu bilden, und aktualisieren die Häufigkeitszahlen auf {„a“: 0, „b“: 1, „c“: 1, „d“: 1, „e“: 0, „bc“: 2, „cd“: 1, „de“: 1, „ab“: 1}.
Wir fügen dem Vokabular „ab“ hinzu, was zu {„a“, „b“, „c“, „d“, „e“, „bc“, „cd“, „de“, „ab“} wird.
Dann ist das nächste häufige Paar „bcd“. Wir verschmelzen „bcd“, um die Unterworteinheit „bcd“ zu bilden, und aktualisieren die Häufigkeitszahlen auf {„a“: 0, „b“: 0, „c“: 0, „d“: 0, „e“: 0, „bc“: 1, „cd“: 0, „de“: 1, „ab“: 1, „bcd“: 1}. Wir fügen „bcd“ zum Vokabular hinzu, was zu {„a“, „b“, „c“, „d“, „e“, „bc“, „cd“, „de“, „ab“, „bcd“ führt "}.
Das häufigste Paar schließlich ist „cde“. Wir verschmelzen „cde“, um die Unterworteinheit „cde“ zu bilden, und aktualisieren die Häufigkeitszahlen auf {„a“: 0, „b“: 0, „c“: 0, „d“: 0, „e“: 0, „bc“: 1, „cd“: 0, „de“: 0, „ab“: 1, „bcd“: 1, „cde“: 1}. Wir fügen „cde“ zum Vokabular hinzu und machen daraus {„a“, „b“, „c“, „d“, „e“, „bc“, „cd“, „de“, „ab“, „bcd“. ", "cde"}.
Diese Technik kann die Leistung von LLMs verbessern und seltene und nicht im Wortschatz vorkommende Wörter verarbeiten. Der große Unterschied zwischen TikToken BPE und Satzstück-BPE besteht darin, dass TikToken BPE Wörter nicht immer in kleinere Teile aufteilt, wenn das ganze Wort bereits bekannt ist. Wenn zum Beispiel „Umarmung“ im Vokabular vorkommt, bleibt es als ein Zeichen, anstatt sich in [„Umarmung“, „Ging“] aufzuteilen.
Wir werden mit einer kleinen Auswahl an Python-Bibliotheken arbeiten, aber es ist besser, diese zu installieren, um Fehler „Kein Modul gefunden“ zu vermeiden.
!p ip install sentencepiece tiktoken torch blobfile matplotlib huggingface_hub
Requirement already satisfied: sentencepiece in /opt/conda/lib/python3.10/site-packages (0.2.0)
Requirement already satisfied: tiktoken in /opt/conda/lib/python3.10/site-packages (0.7.0)
Requirement already satisfied: torch in /opt/conda/lib/python3.10/site-packages (2.1.2+cpu)
Requirement already satisfied: blobfile in /opt/conda/lib/python3.10/site-packages (2.1.1)
Requirement already satisfied: matplotlib in /opt/conda/lib/python3.10/site-packages (3.7.5)
Requirement already satisfied: huggingface_hub in /opt/conda/lib/python3.10/site-packages (0.22.2)
Requirement already satisfied: regex>=2022.1.18 in /opt/conda/lib/python3.10/site-packages (from tiktoken) (2023.12.25)
Requirement already satisfied: requests>=2.26.0 in /opt/conda/lib/python3.10/site-packages (from tiktoken) (2.31.0)
Requirement already satisfied: filelock in /opt/conda/lib/python3.10/site-packages (from torch) (3.13.1)
Requirement already satisfied: typing-extensions in /opt/conda/lib/python3.10/site-packages (from torch) (4.9.0)
Requirement already satisfied: sympy in /opt/conda/lib/python3.10/site-packages (from torch) (1.12)
Requirement already satisfied: networkx in /opt/conda/lib/python3.10/site-packages (from torch) (3.2.1)
Requirement already satisfied: jinja2 in /opt/conda/lib/python3.10/site-packages (from torch) (3.1.2)
Requirement already satisfied: fsspec in /opt/conda/lib/python3.10/site-packages (from torch) (2024.2.0)
Requirement already satisfied: pycryptodomex~=3.8 in /opt/conda/lib/python3.10/site-packages (from blobfile) (3.20.0)
Requirement already satisfied: urllib3<3,>=1.25.3 in /opt/conda/lib/python3.10/site-packages (from blobfile) (1.26.18)
Requirement already satisfied: lxml~=4.9 in /opt/conda/lib/python3.10/site-packages (from blobfile) (4.9.4)
Requirement already satisfied: contourpy>=1.0.1 in /opt/conda/lib/python3.10/site-packages (from matplotlib) (1.2.0)
Requirement already satisfied: cycler>=0.10 in /opt/conda/lib/python3.10/site-packages (from matplotlib) (0.12.1)
Requirement already satisfied: fonttools>=4.22.0 in /opt/conda/lib/python3.10/site-packages (from matplotlib) (4.47.0)
Requirement already satisfied: kiwisolver>=1.0.1 in /opt/conda/lib/python3.10/site-packages (from matplotlib) (1.4.5)
Requirement already satisfied: numpy<2,>=1.20 in /opt/conda/lib/python3.10/site-packages (from matplotlib) (1.26.4)
Requirement already satisfied: packaging>=20.0 in /opt/conda/lib/python3.10/site-packages (from matplotlib) (21.3)
Requirement already satisfied: pillow>=6.2.0 in /opt/conda/lib/python3.10/site-packages (from matplotlib) (9.5.0)
Requirement already satisfied: pyparsing>=2.3.1 in /opt/conda/lib/python3.10/site-packages (from matplotlib) (3.1.1)
Requirement already satisfied: python-dateutil>=2.7 in /opt/conda/lib/python3.10/site-packages (from matplotlib) (2.9.0.post0)
Requirement already satisfied: pyyaml>=5.1 in /opt/conda/lib/python3.10/site-packages (from huggingface_hub) (6.0.1)
Requirement already satisfied: tqdm>=4.42.1 in /opt/conda/lib/python3.10/site-packages (from huggingface_hub) (4.66.1)
Requirement already satisfied: six>=1.5 in /opt/conda/lib/python3.10/site-packages (from python-dateutil>=2.7->matplotlib) (1.16.0)
Requirement already satisfied: charset-normalizer<4,>=2 in /opt/conda/lib/python3.10/site-packages (from requests>=2.26.0->tiktoken) (3.3.2)
Requirement already satisfied: idna<4,>=2.5 in /opt/conda/lib/python3.10/site-packages (from requests>=2.26.0->tiktoken) (3.6)
Requirement already satisfied: certifi>=2017.4.17 in /opt/conda/lib/python3.10/site-packages (from requests>=2.26.0->tiktoken) (2024.2.2)
Requirement already satisfied: MarkupSafe>=2.0 in /opt/conda/lib/python3.10/site-packages (from jinja2->torch) (2.1.3)
Requirement already satisfied: mpmath>=0.19 in /opt/conda/lib/python3.10/site-packages (from sympy->torch) (1.3.0)
Nach der Installation der erforderlichen Bibliotheken müssen wir einige Dateien herunterladen. Da wir die Architektur von Lama-3–8B replizieren werden, müssen Sie über ein Konto bei HuggingFace verfügen. Da es sich bei llama-3 um ein geschlossenes Modell handelt, müssen Sie außerdem deren Geschäftsbedingungen akzeptieren, um auf Modellinhalte zugreifen zu können.
Hier sind die Schritte:
Nachdem Sie beide Schritte abgeschlossen haben, müssen wir nun einige Dateien herunterladen. Dafür gibt es zwei Möglichkeiten:
(Option 1: Manuell) Gehen Sie über diesen Link zum Verzeichnis „llama-3–8B HF“ und laden Sie jede dieser drei Dateien manuell herunter.
(Optionen 2: Codierung) Wir können die Bibliothek „hugging_face“, die wir zuvor installiert haben, verwenden, um alle diese Dateien herunterzuladen. Allerdings müssen wir uns zunächst in unserem Arbeitsnotizbuch mit unserem HF-Token beim HuggingFace Hub anmelden. Sie können ein neues Token erstellen oder über diesen Link darauf zugreifen.
# Import the `notebook_login` function from the `huggingface_hub` module.
from huggingface_hub import notebook_login
# Execute the `notebook_login` function to log in to the Hugging Face Hub.
notebook_login ()
VBox(children=(HTML(value='<center> <imgnsrc=https://huggingface.co/front/assets/huggingface_logo-noborder.sv…
Sobald Sie diese Zelle ausführen, werden Sie aufgefordert, das Token einzugeben. Wenn beim Anmelden ein Fehler auftritt, versuchen Sie es erneut, aber stellen Sie sicher, dass Sie das Kontrollkästchen „Token als Git-Anmeldeinformationen hinzufügen“ deaktivieren. Danach müssen wir nur noch einen einfachen Python-Code ausführen, um die drei Dateien herunterzuladen, die das Rückgrat der Lama-3–8B-Architektur bilden.
# Import the necessary function from the huggingface_hub library
from huggingface_hub import hf_hub_download
# Define the repository information
repo_id = "meta-llama/Meta-Llama-3-8B"
subfolder = "original" # Specify the subfolder within the repository
# List of filenames to download
filenames = [ "params.json" , "tokenizer.model" , "consolidated.00.pth" ]
# Specify the directory where you want to save the downloaded files
save_directory = "llama-3-8B/" # Replace with your desired path
# Download each file
for filename in filenames :
hf_hub_download (
repo_id = repo_id , # Repository ID
filename = filename , # Name of the file to download
subfolder = subfolder , # Subfolder within the repository
local_dir = save_directory # Directory to save the downloaded file
)
original/params.json: 0%| | 0.00/211 [00:00<?, ?B/s]
original/tokenizer.model: 0%| | 0.00/2.18M [00:00<?, ?B/s]
original/consolidated.00.pth: 0%| | 0.00/16.1G [00:00<?, ?B/s]
Sobald alle Dateien heruntergeladen sind, müssen wir die Bibliotheken importieren, die wir in diesem Blog verwenden werden.
# File system paths
from pathlib import Path
# Tokenization library
import tiktoken
# BPE loading function
from tiktoken . load import load_tiktoken_bpe
# PyTorch library
import torch
# JSON handling
import json
# Plotting library
import matplotlib . pyplot as plt
Als nächstes müssen wir verstehen, wofür jede Datei verwendet wird.
Da wir eine exakte Nachbildung von Lama-3 anstreben, bedeutet dies, dass unser Eingabetext eine aussagekräftige Ausgabe liefern muss. Wenn unsere Eingabe beispielsweise „Welche Farbe hat die Sonne?“ lautet, muss die Ausgabe „Weiß“ sein. Um dies zu erreichen, müssen wir unser LLM auf einem großen Datensatz trainieren, was eine hohe Rechenleistung erfordert und daher für uns nicht durchführbar ist.
Meta hat jedoch seine Lama-3-Architekturdateien, oder komplexer ausgedrückt, seine vorab trainierten Gewichte, öffentlich zur Verwendung freigegeben. Wir haben diese Dateien gerade heruntergeladen, sodass wir ihre Architektur replizieren können, ohne dass Schulungen oder ein großer Datensatz erforderlich sind. Alles ist bereits vorbereitet, wir müssen nur noch die richtigen Komponenten an den richtigen Stellen einsetzen.
Schauen Sie sich jede dieser Dateien und ihre Bedeutung an:
tokenizer.model – Wie bereits erwähnt, verwendet LLaMA-3 den Byte Pair Encoding (BPE)-Tokenizer von tiktoken, trainiert auf einem Datensatz mit 15 Billionen Token – siebenmal größer als der für LLaMA-2 verwendete Datensatz. Laden wir diese Datei und sehen, was sie enthält.
# Loading the tokenizer from llama-3-8B
tokenizer_model = load_tiktoken_bpe ( "/kaggle/working/llama-3-8B/original/tokenizer.model" )
# Get the length of the tokenizer model
len ( tokenizer_model )
# OUTPUT: 128000
# Get the type of the `tokenizer_model` object.
type ( tokenizer_model )
# OUTPUT: dictionary
dict
Das Längenattribut zeigt die Gesamtgröße des Vokabulars an, also die eindeutige Anzahl von Zeichen in den Trainingsdaten. Der Typ von tokenizer_model ist ein Wörterbuch.
# Printing the first 10 items of tokenizer model
dict ( list ( tokenizer_model . items ())[ 5600 : 5610 ])
{b'mitted': 5600,
b" $('#": 5601,
b' saw': 5602,
b' approach': 5603,
b'ICE': 5604,
b' saying': 5605,
b' anyone': 5606,
b'meta': 5607,
b'SD': 5608,
b' song': 5609}
Wenn wir daraus 10 zufällige Elemente drucken, sehen Sie Zeichenfolgen, die mithilfe des BPE-Algorithmus gebildet wurden, ähnlich dem Beispiel, das wir zuvor besprochen haben. Schlüssel stellen Bytesequenzen aus dem BPE-Training dar, während Werte Zusammenführungsränge basierend auf der Häufigkeit darstellen.
konsolidiert.00.pth – enthält die gelernten Parameter (Gewichte) von Llama-3–8B. Zu diesen Parametern gehören Informationen darüber, wie das Modell Sprache versteht und verarbeitet, z. B. wie es Token darstellt, Aufmerksamkeit berechnet, Feed-Forward-Transformationen durchführt und seine Ausgaben normalisiert.
# Loading a PyTorch model of LLaMA-3-8B
model = torch . load ( "/kaggle/working/llama-3-8B/original/consolidated.00.pth" )
# printing first 11 layers of the architecture
list ( model . keys ())[: 11 ]
['tok_embeddings.weight',
'layers.0.attention.wq.weight',
'layers.0.attention.wk.weight',
'layers.0.attention.wv.weight',
'layers.0.attention.wo.weight',
'layers.0.feed_forward.w1.weight',
'layers.0.feed_forward.w3.weight',
'layers.0.feed_forward.w2.weight',
'layers.0.attention_norm.weight',
'layers.0.ffn_norm.weight',
'layers.1.attention.wq.weight']
Wenn Sie mit der Transformer-Architektur vertraut sind, kennen Sie sich mit Abfragen, Schlüsselmatrizen und mehr aus. Später werden wir diese Schichten/Gewichte verwenden, um solche Matrizen innerhalb der Architektur von Llama-3 zu erstellen.
params.json – enthält verschiedene Parameterwerte, wie zum Beispiel:
# Opening the parameters JSON file
with open ( "/kaggle/working/llama-3-8B/original/params.json" , "r" ) as f :
config = json . load ( f )
# Printing the content
print ( config )
{'dim': 4096, 'n_layers': 32, 'n_heads': 32, 'n_kv_heads': 8, 'vocab_size': 128256, 'multiple_of': 1024, 'ffn_dim_multiplier': 1.3, 'norm_eps': 1e-05, 'rope_theta': 500000.0}
Diese Werte helfen uns, die Llama-3-Architektur zu replizieren, indem sie Details wie die Anzahl der Köpfe, die Dimension des Einbettungsvektors und mehr angeben.
Speichern wir diese Werte, damit wir sie später verwenden können.
# Dimension
dim = config [ "dim" ]
# Layers
n_layers = config [ "n_layers" ]
# Heads
n_heads = config [ "n_heads" ]
# KV_heads
n_kv_heads = config [ "n_kv_heads" ]
# Vocabulary
vocab_size = config [ "vocab_size" ]
# Multiple
multiple_of = config [ "multiple_of" ]
# Multiplier
ffn_dim_multiplier = config [ "ffn_dim_multiplier" ]
# Epsilon
norm_eps = config [ "norm_eps" ]
# RoPE
rope_theta = torch . tensor ( config [ "rope_theta" ])
Nachdem wir nun das Tokenizer-Modell, das Architekturmodell mit Gewichtungen und Konfigurationsparameter haben, beginnen wir mit der Codierung unseres eigenen Llama-3 von Grund auf.
Das allererste, was wir tun müssen, ist die Konvertierung unseres Eingabetextes in Token. Um dies zu erreichen, müssen wir zunächst einige spezielle Token erstellen, die erforderlich sind, um strukturierte Markierungen innerhalb des tokenisierten Textes bereitzustellen, damit der Tokenizer bestimmte Bedingungen erkennen und verarbeiten kann oder Anweisungen.
special_tokens = [
"<|begin_of_text|>" , # Marks the beginning of a text sequence.
"<|end_of_text|>" , # Marks the end of a text sequence.
"<|reserved_special_token_0|>" , # Reserved for future use.
"<|reserved_special_token_1|>" , # Reserved for future use.
"<|reserved_special_token_2|>" , # Reserved for future use.
"<|reserved_special_token_3|>" , # Reserved for future use.
"<|start_header_id|>" , # Indicates the start of a header ID.
"<|end_header_id|>" , # Indicates the end of a header ID.
"<|reserved_special_token_4|>" , # Reserved for future use.
"<|eot_id|>" , # Marks the end of a turn (in a conversational context).
] + [ f"<|reserved_special_token_ { i } |>" for i in range ( 5 , 256 - 5 )] # A large set of tokens reserved for future use.
Als Nächstes definieren wir die Regeln für die Aufteilung von Text in Token, indem wir verschiedene Muster angeben, die verschiedenen Arten von Teilzeichenfolgen im Eingabetext entsprechen. Hier erfahren Sie, wie wir das tun können.
# patterns based on which text will be break into tokens
tokenize_breaker = r"(?i:'s|'t|'re|'ve|'m|'ll|'d)|[^rnp{L}p{N}]?p{L}+|p{N}{1,3}| ?[^sp{L}p{N}]+[rn]*|s*[rn]+|s+(?!S)|s+"
Es kann Wörter, Kontraktionen, Zahlen (bis zu drei Ziffern) und Sequenzen von Nicht-Leerzeichen aus dem Eingabetext extrahieren und Sie können es entsprechend Ihren Anforderungen anpassen. Wir müssen eine einfache Tokenizer-Funktion mit dem TikToken BPE programmieren, die drei Eingaben benötigt: tokenizer_model, tokenize_breaker und special_tokens. Diese Funktion kodiert/dekodiert unseren Eingabetext entsprechend.
# Initialize tokenizer with specified parameters
tokenizer = tiktoken . Encoding (
# make sure to set path to tokenizer.model file
name = "/kaggle/working/llama-3-8B/original/tokenizer.model" ,
# Define tokenization pattern string
pat_str = tokenize_breaker ,
# Assign BPE mergeable ranks from tokenizer_model of LLaMA-3
mergeable_ranks = tokenizer_model ,
# Set special tokens with indices
special_tokens = { token : len ( tokenizer_model ) + i for i , token in enumerate ( special_tokens )},
)
# Encode "hello world!" and decode tokens to string
tokenizer . decode ( tokenizer . encode ( "hello world!" ))
'hello world!'
Um zu überprüfen, ob unsere Encoder-Funktionsmethoden korrekt funktionieren, übergeben wir „Hello World“. Zunächst kodiert es den Text und wandelt ihn in numerische Werte um. Anschließend wird es wieder in Text dekodiert, was zu „Hallo Welt!“ führt. Dies bestätigt, dass die Funktion ordnungsgemäß funktioniert. Lassen Sie uns unsere Eingabe tokenisieren.
# input prompt
prompt = "the answer to the ultimate question of life, the universe, and everything is "
# Encode the prompt using the tokenizer and prepend a special token (128000)
tokens = [ 128000 ] + tokenizer . encode ( prompt )
print ( tokens ) # Print the encoded tokens
# Convert the list of tokens into a PyTorch tensor
tokens = torch . tensor ( tokens )
# Decode each token back into its corresponding string
prompt_split_as_tokens = [ tokenizer . decode ([ token . item ()]) for token in tokens ]
print ( prompt_split_as_tokens ) # Print the decoded tokens
[128000, 1820, 4320, 311, 279, 17139, 3488, 315, 2324, 11, 279, 15861, 11, 323, 4395, 374, 220]
['<|begin_of_text|>', 'the', ' answer', ' to', ' the', ' ultimate', ' question', ' of', ' life', ',', ' the', ' universe', ',', ' and', ' everything', ' is', ' ']
Wir haben unseren Eingabetext „Die Antwort auf die ultimative Frage nach dem Leben, dem Universum und allem“ codiert, beginnend mit einem speziellen Token.
Wenn wir die Länge unseres Eingabevektors überprüfen, wäre es:
# checking dimension of input vector and embedding vector from llama-3 architecture
print ( dim , len ( tokens ))
4096 17
Unsere Eingabevektoren, die derzeit die Dimension (17x1) haben, müssen für jedes tokenisierte Wort in Einbettungen umgewandelt werden. Das bedeutet, dass unsere (17x1)-Tokens zu (17x4096) werden, wobei jedes Token eine entsprechende Einbettung mit der Länge 4096 hat.
# Define embedding layer with vocab size and embedding dimension
embedding_layer = torch . nn . Embedding ( vocab_size , dim )
# Copy pre-trained token embeddings to the embedding layer
embedding_layer . weight . data . copy_ ( model [ "tok_embeddings.weight" ])
# Get token embeddings for given tokens, converting to torch.bfloat16 format
token_embeddings_unnormalized = embedding_layer ( tokens ). to ( torch . bfloat16 )
# Print shape of resulting token embeddings
token_embeddings_unnormalized . shape
torch.Size([17, 4096])
Diese Einbettungen sind nicht normalisiert und es wird schwerwiegende Auswirkungen haben, wenn wir sie nicht normalisieren. Im nächsten Abschnitt führen wir eine Normalisierung unserer Eingabevektoren durch.
Wir werden die Eingabevektoren mit der gleichen Formel normalisieren, die wir zuvor für RMSNorm gesehen haben, um sicherzustellen, dass unsere Eingaben normalisiert sind.
# Calculating RMSNorm
def rms_norm ( tensor , norm_weights ):
# Calculate the mean of the square of tensor values along the last dimension
squared_mean = tensor . pow ( 2 ). mean ( - 1 , keepdim = True )
# Add a small value to avoid division by zero
normalized = torch . rsqrt ( squared_mean + norm_eps )
# Multiply normalized tensor by the provided normalization weights
return ( tensor * normalized ) * norm_weights
Wir werden die Aufmerksamkeitsgewichte von Schichten_0 verwenden, um unsere nicht normalisierten Einbettungen zu normalisieren. Der Grund für die Verwendung von Schicht_0 besteht darin, dass wir jetzt die erste Schicht unserer LLaMA-3-Transformator-Architektur erstellen.
# using RMS normalization and provided normalization weights
token_embeddings = rms_norm ( token_embeddings_unnormalized ,
model [ "layers.0.attention_norm.weight" ])
# Print the shape of the resulting token embeddings
token_embeddings . shape
torch.Size([17, 4096])
Möglicherweise wissen Sie bereits, dass sich die Dimension nicht ändert, da wir nur die Vektoren normalisieren und sonst nichts.
Laden wir zunächst die Abfrage-, Schlüssel-, Wert- und Ausgabevektoren aus dem Modell.
# Print the shapes of different weights
print (
# Query weight shape
model [ "layers.0.attention.wq.weight" ]. shape ,
# Key weight shape
model [ "layers.0.attention.wk.weight" ]. shape ,
# Value weight shape
model [ "layers.0.attention.wv.weight" ]. shape ,
# Output weight shape
model [ "layers.0.attention.wo.weight" ]. shape
)
torch.Size([4096, 4096]) torch.Size([1024, 4096]) torch.Size([1024, 4096]) torch.Size([4096, 4096])
Die Abmessungen zeigen, dass die von uns heruntergeladenen Modellgewichte nicht für jeden Kopf einzeln gelten, sondern aufgrund der Implementierung eines parallelen Ansatzes/Trainings für mehrere Aufmerksamkeitsköpfe. Wir können diese Matrizen jedoch entpacken, um sie nur für einen einzelnen Kopf verfügbar zu machen.
# Retrieve query weight for the first layer of attention
q_layer0 = model [ "layers.0.attention.wq.weight" ]
# Calculate dimension per head
head_dim = q_layer0 . shape [ 0 ] // n_heads
# Reshape query weight to separate heads
q_layer0 = q_layer0 . view ( n_heads , head_dim , dim )
# Print the shape of the reshaped query weight tensor
q_layer0 . shape
torch.Size([32, 128, 4096])
Hier ist 32 die Anzahl der Aufmerksamkeitsköpfe in Llama-3, 128 die Größe des Abfragevektors und 4096 die Größe der Token-Einbettung. Wir können auf die Abfragegewichtsmatrix des ersten Kopfes der ersten Ebene zugreifen, indem wir Folgendes verwenden:
# Extract the query weight for the first head of the first layer of attention
q_layer0_head0 = q_layer0 [ 0 ]
# Print the shape of the extracted query weight tensor for the first head
q_layer0_head0 . shape
torch.Size([128, 4096])
Um den Abfragevektor für jedes Token zu finden, multiplizieren wir die Abfragegewichte mit der Token-Einbettung.
# Matrix multiplication: token embeddings with transpose of query weight for first head
q_per_token = torch . matmul ( token_embeddings , q_layer0_head0 . T )
# Shape of resulting tensor: queries per token
q_per_token . shape
torch.Size([17, 128])
Die Abfragevektoren kennen ihre Position in der Eingabeaufforderung nicht von Natur aus, daher verwenden wir RoPE, um sie darauf aufmerksam zu machen.
Wir spl