LLMs in einfachem, reinem C/CUDA, ohne dass 245 MB PyTorch oder 107 MB cPython erforderlich sind. Der aktuelle Schwerpunkt liegt auf dem Vortraining, insbesondere der Reproduktion der GPT-2- und GPT-3-Miniserien, zusammen mit einer parallelen PyTorch-Referenzimplementierung in train_gpt2.py. Sie erkennen diese Datei als leicht optimiertes nanoGPT, ein früheres Projekt von mir. Derzeit ist llm.c etwas schneller als PyTorch Nightly (um etwa 7 %). Zusätzlich zum hochmodernen Mainline-Code in train_gpt2.cu haben wir eine einfache Referenz-CPU-fp32-Implementierung in ca. 1.000 Zeilen sauberem Code in einer Datei train_gpt2.c. Ich möchte, dass dieses Repo nur C- und CUDA-Code verwaltet. Portierungen auf andere Sprachen oder Repos sind sehr willkommen, sollten aber in separaten Repos erfolgen, und ich verlinke sie gerne weiter unten im Abschnitt „Bemerkenswerte Forks“. Die Koordination der Entwickler erfolgt in den Diskussionen und auf Discord, entweder im #llmc
-Kanal auf dem Zero to Hero-Kanal oder auf #llmdotc
auf GPU MODE Discord.
Die beste Einführung in das llm.c-Repo ist heute die Reproduktion des GPT-2 (124M)-Modells. Diskussion Nr. 481 geht dies im Detail durch. Wir können andere Modelle der GPT-2- und GPT-3-Serie sowohl in llm.c als auch in der parallelen Implementierung von PyTorch reproduzieren. Schauen Sie sich die README-Datei der Skripte an.
Tipp zum Debuggen: Wenn Sie den Befehl make
zum Erstellen der Binärdatei ausführen, ändern Sie ihn, indem Sie -O3
durch -g
ersetzen, damit Sie den Code in Ihrer bevorzugten IDE (z. B. vscode) schrittweise durchgehen können.
Wenn Sie nicht auf mehreren Knoten trainieren, kein Interesse an gemischter Präzision haben und CUDA lernen möchten, könnten die fp32-Dateien (Legacy-Dateien) für Sie von Interesse sein. Hierbei handelt es sich um Dateien, die zu Beginn der Geschichte von llm.c mit einem „Checkpoint“ versehen und zeitlich eingefroren wurden. Sie sind einfacher, tragbarer und möglicherweise leichter zu verstehen. Führen Sie den 1-GPU-FP32-Code wie folgt aus:
chmod u+x ./dev/download_starter_pack.sh ./dev/download_starter_pack.sh Machen Sie train_gpt2fp32cu ./train_gpt2fp32cu
Das Skript „download_starter_pack.sh“ ist ein schneller und einfacher Einstieg und lädt eine Reihe von .bin-Dateien herunter, die Ihnen den Einstieg erleichtern. Diese enthalten: 1) das GPT-2 124M-Modell, das in fp32 in bfloat16 gespeichert ist, 2) einen „Debug-Status“, der beim Unit-Testen verwendet wird (ein kleiner Datenstapel sowie Zielaktivierungen und -verläufe), 3) den GPT-2-Tokenizer und 3) der tokenisierte TinyShakespeare-Datensatz. Anstatt das .sh-Skript auszuführen, können Sie diese Artefakte auch manuell wie folgt neu erstellen:
pip install -r Anforderungen.txt python dev/data/tinyshakespeare.py Python train_gpt2.py
Der Abschnitt „Ich bin so arm an der GPU, dass ich nicht einmal eine GPU habe“. Sie können weiterhin viel Spaß beim Zugsehen von llm.c haben! Aber Sie werden nicht zu weit gehen. Genau wie die fp32-Version oben ist die CPU-Version ein noch früherer Prüfpunkt in der Geschichte von llm.c, als es sich lediglich um eine einfache Referenzimplementierung in C handelte. Anstatt beispielsweise von Grund auf zu trainieren, können Sie eine GPT-Feinabstimmung vornehmen. 2 kleine (124 MB) zur Ausgabe von Shakespeare-ähnlichem Text, als Beispiel:
chmod u+x ./dev/download_starter_pack.sh ./dev/download_starter_pack.sh mache train_gpt2 OMP_NUM_THREADS=8 ./train_gpt2
Wenn Sie die Ausführung des Starterpaket-Skripts lieber vermeiden möchten, können Sie, wie im vorherigen Abschnitt erwähnt, genau dieselben .bin-Dateien und Artefakte reproduzieren, indem Sie python dev/data/tinyshakespeare.py
und dann python train_gpt2.py
ausführen.
Die obigen Zeilen (1) laden einen bereits tokenisierten Tinyshakespeare-Datensatz herunter und laden die GPT-2 (124M)-Gewichte herunter, (3) initiieren daraus in C und trainieren 40 Schritte auf Tineshakespeare mit AdamW (mit Batchgröße 4, Kontextlänge nur 64). ), bewerten Sie den Validierungsverlust und probieren Sie Text aus. Ehrlich gesagt, es sei denn, Sie haben eine leistungsstarke CPU (und können die Anzahl der OMP-Threads im Startbefehl erhöhen), werden Sie mit CPU-Trainings-LLMs nicht so weit kommen, aber es könnte eine gute Demo/Referenz sein. Auf meinem MacBook Pro (Apple Silicon M3 Max) sieht die Ausgabe so aus:
[GPT-2] max_seq_len: 1024 vocab_size: 50257 num_layers: 12 num_heads: 12 channels: 768 num_parameters: 124439808 train dataset num_batches: 1192 val dataset num_batches: 128 num_activations: 73323776 val loss 5.252026 step 0: train loss 5.356189 (took 1452.121000 ms) step 1: train loss 4.301069 (took 1288.673000 ms) step 2: train loss 4.623322 (took 1369.394000 ms) step 3: train loss 4.600470 (took 1290.761000 ms) ... (trunctated) ... step 39: train loss 3.970751 (took 1323.779000 ms) val loss 4.107781 generating: --- Come Running Away, Greater conquer With the Imperial blood the heaviest host of the gods into this wondrous world beyond. I will not back thee, for how sweet after birth Netflix against repounder, will not flourish against the earlocks of Allay ---
Die Datendateien in /dev/data/(dataset).py
sind für das Herunterladen, Tokenisieren und Speichern der Token in .bin-Dateien verantwortlich, die leicht von C aus gelesen werden können. Wenn Sie beispielsweise Folgendes ausführen:
python dev/data/tinyshakespeare.py
Wir laden den Tinyshakespeare-Datensatz herunter und tokenisieren ihn. Die Ausgabe davon sieht so aus:
writing 32,768 tokens to ./dev/data/tinyshakespeare/tiny_shakespeare_val.bin writing 305,260 tokens to ./dev/data/tinyshakespeare/tiny_shakespeare_train.bin
Die .bin-Dateien enthalten einen kurzen Header (1024 Byte) und dann einen Stream von Tokens in uint16, der die Token-IDs mit dem GPT-2-Tokenizer angibt. Weitere Datensätze sind in /dev/data
verfügbar.
Ich füge auch einen einfachen Komponententest bei, um sicherzustellen, dass unser C-Code mit dem PyTorch-Code übereinstimmt. Auf der CPU als Beispiel kompilieren und ausführen mit:
mache test_gpt2 ./test_gpt2
Dies lädt nun die Datei gpt2_124M_debug_state.bin
, die von train_gpt2.py geschrieben wird, führt einen Vorwärtsdurchlauf durch, vergleicht die Logits und Verluste mit der PyTorch-Referenzimplementierung, führt dann 10 Trainingsiterationen mit Adam durch und stellt sicher, dass die Verluste mit PyTorch übereinstimmen. Um die GPU-Version zu testen, führen wir Folgendes aus:
# fp32 test (cudnn nicht unterstützt)make test_gpt2cu PRECISION=FP32 && ./test_gpt2cu# gemischte Präzision cudnn testmake test_gpt2cu USE_CUDNN=1 && ./test_gpt2cu
Dadurch werden sowohl der fp32-Pfad als auch der Pfad mit gemischter Präzision getestet. Der Test sollte bestanden werden und overall okay: 1
.
Ich habe hier ein sehr kleines Tutorial angehängt, in doc/layernorm/layernorm.md. Es handelt sich um eine einfache Schritt-für-Schritt-Anleitung zur Implementierung einer einzelnen Schicht des GPT-2-Modells, der Layernorm-Schicht. Dies ist ein guter Ausgangspunkt, um zu verstehen, wie die Ebenen in C implementiert sind.
Blitzaufmerksamkeit . Ab dem 1. Mai 2024 nutzen wir den Flash Attention von cuDNN. Da cuDNN die Kompilierungszeit von einigen Sekunden auf ~Minuten aufbläht und dieser Codepfad derzeit sehr neu ist, ist dies standardmäßig deaktiviert. Sie können es aktivieren, indem Sie wie folgt kompilieren:
Machen Sie train_gpt2cu USE_CUDNN=1
Dadurch wird versucht, mit cudnn zu kompilieren und auszuführen. Sie müssen cuDNN auf Ihrem System installiert haben. Die cuDNN-Installationsanweisungen mit apt-get greifen auf den Standardsatz von cuDNN-Paketen zu. Für ein minimales Setup reicht das cuDNN-Dev-Paket aus, z. B. auf Ubuntu 22.04 für CUDA 12.x:
wget https://developer.download.nvidia.com/compute/cuda/repos/ubuntu2204/x86_64/cuda-keyring_1.1-1_all.deb sudo dpkg -i cuda-keyring_1.1-1_all.deb sudo apt-get update sudo apt-get -y install libcudnn9-dev-cuda-12
Darüber hinaus benötigen Sie das cuDNN-Frontend, bei dem es sich jedoch nur um Header-Dateien handelt. Klonen Sie das Repo einfach auf Ihre Festplatte. Das Makefile sucht derzeit entweder in Ihrem Home-Verzeichnis oder im aktuellen Verzeichnis danach. Wenn Sie es an anderer Stelle abgelegt haben, fügen Sie CUDNN_FRONTEND_PATH=/path/to/your/cudnn-frontend/include
zur make
-Befehlszeile hinzu.
Stellen Sie sicher, dass Sie MPI und NCCL installieren, z. B. unter Linux:
sudo apt install openmpi-bin openmpi-doc libopenmpi-dev
Befolgen Sie für NCCL die Anweisungen auf der offiziellen Website (z. B. Netzwerkinstallationsprogramm).
und dann:
mache train_gpt2cu mpirun -np <Anzahl der GPUs> ./train_gpt2cu
oder führen Sie einfach eines unserer Skripte unter ./scripts/
aus.
Stellen Sie sicher, dass Sie NCCL
gemäß den Anweisungen im Abschnitt „Multi-GPU“ installiert haben.
Derzeit unterstützen wir drei Möglichkeiten, mit denen Sie ein Training mit mehreren Knoten durchführen können:
Verwenden Sie OpenMPI, um die NCCL-ID auszutauschen und NCCL zu initialisieren. Weitere Informationen finden Sie z. B. im Skript ./scripts/multi_node/run_gpt2_124M_mpi.sh
.
Verwenden Sie ein gemeinsames Dateisystem, um NCCL zu initiieren. Weitere Informationen finden Sie im Skript ./scripts/multi_node/run_gpt2_124M_fs.sbatch
.
Verwenden Sie TCP-Sockets, um NCCL zu initiieren. Weitere Informationen finden Sie im Skript ./scripts/multi_node/run_gpt2_124M_tcp.sbatch
.
Notiz:
Wenn Sie in einer Slurm-Umgebung arbeiten und Ihr Slurm PMIx nicht unterstützt (was unserer Meinung nach eine häufige Situation sein wird, da slurm-wlm
die PMIx-Unterstützung eingestellt hat), müssen Sie den FS (2)- oder TCP (3)-Ansatz verwenden . Um zu testen, ob Ihr Slurm PMIx unterstützt, führen Sie Folgendes aus: srun --mpi=list
und sehen Sie, ob Sie pmix
in der Ausgabe erhalten.
Wenn Sie Slurm nicht eingerichtet haben, können Sie mit mpirun
- MPI (1) einen Lauf mit mehreren Knoten starten.
Keine dieser drei Methoden ist überlegen, wir bieten Ihnen lediglich Optionen, damit Sie sie in Ihrer spezifischen Umgebung ausführen können.
Nur als Beispielprozess zum Durchsuchen der Lernraten auf einer Maschine mit 4 GPUs in TinyStories. Führen Sie ein Shell-Skript sweep.sh
aus (natürlich nachdem Sie chmod u+x sweep.sh
ausgeführt haben):
#!/bin/bashlearning_rates=(3e-5 1e-4 3e-4 1e-3)for i in {0..3}; doexport CUDA_VISIBLE_DEVICES=$iscreen -dmS "tr$i" bash -c "./train_gpt2cu -i data/TinyStories -v 250 -s 250 -g 144 -l ${learning_rates[$i]} -o Stories$i.log "Fertig# Sie können diese mit# screen -ls | herunterfahren grep -E "tr[0-3]" | schneiden -d. -f1 | xargs -I {} screen -X -S {} beenden
Dieses Beispiel öffnet vier Bildschirmsitzungen und führt die vier Befehle mit unterschiedlichen LRs aus. Dadurch werden die Protokolldateien stories$i.log
mit allen Verlusten geschrieben, die Sie in Python beliebig darstellen können. Ein kurzes Beispiel für das Parsen und Plotten dieser Protokolldateien finden Sie in dev/vislog.ipynb.
Noch ein paar Worte dazu, wie dieses Repo aussehen soll:
Erstens möchte ich, dass llm.c
ein Ort der Bildung ist. Unser dev/cuda
-Ordner ist beispielsweise ein Ort für eine Bibliothek von Kerneln für alle Ebenen, die manuell handgeschrieben und sehr gut dokumentiert sind, angefangen bei sehr einfachen Kerneln bis hin zu komplexeren/schnelleren Kerneln. Wenn Sie einen neuen Kernel mit verschiedenen Kompromissen haben, können Sie ihn gerne hier beisteuern.
Allerdings möchte ich auch, dass llm.c
sehr schnell ist und sogar praktisch zum Trainieren von Netzwerken nützlich ist. Zu Beginn sollten wir beispielsweise in der Lage sein, den großen GPT-2-Trainingslauf (1,6B) zu reproduzieren. Dies erfordert, dass wir alle schnellsten Kernel integrieren, die es gibt, einschließlich der Verwendung von Bibliotheken wie cuBLAS, cuBLASLt, CUTLASS, cuDNN usw. Ich denke auch, dass dies einem pädagogischen Zweck dient, um eine Obergrenze für Experten und eine Maßeinheit festzulegen. Sie könnten beispielsweise sagen, dass Ihre manuell geschriebenen Kernel 80 % der cuBLAS-Geschwindigkeit haben usw. Dann können Sie sich für einen superschnellen Lauf entscheiden, oder Sie können die manuellen Kernel, die Sie verwenden möchten, per Drag & Drop verschieben und lauf mit denen.
Als Einschränkung möchte ich jedoch die Hauptzeile llm.c
im Stammordner einfach und lesbar halten. Wenn es eine PR gibt, die beispielsweise die Leistung um 2 % verbessert, aber 500 Zeilen komplexen C-Codes „kostet“ und möglicherweise eine exotische Abhängigkeit von Drittanbietern, lehne ich die PR möglicherweise ab, weil sich die Komplexität nicht lohnt. Als konkretes Beispiel ist es ein Kinderspiel, cuBLAS für Matmuls als Standard in der Root-Trainingsschleife festzulegen: Dadurch wird der Hauptcode viel schneller, es handelt sich um eine einzelne Zeile interpretierbaren Codes und es handelt sich um eine sehr häufige Abhängigkeit. Darüber hinaus können wir manuelle Implementierungen haben, die mit cuBLAS in dev/cuda
konkurrieren können.
Schließlich werde ich viel sensibler auf die Komplexität im Stammordner des Projekts achten, der die Haupt-/Standarddateien des Projekts enthält. Im Vergleich dazu ist der Ordner dev/
eher ein Arbeitsspeicher für uns, um eine Bibliothek von Kerneln oder Klassen zu entwickeln und nützlichen, verwandten oder lehrreichen Code zu teilen, und einige dieser Codes könnten durchaus (lokal) komplex sein.
AMD-Unterstützung
llm.c von @anthonix: Unterstützung für AMD-Geräte wie den 7900 XTX
C#
llm.cs von @azret: eine C#-Portierung dieses Projekts
Llm.cs von @nietras: eine C#-Portierung dieses Projekts mit Schwerpunkt auf einem einfachen Einstieg auf jeder Plattform. Klonen und ausführen ✅
CUDA C++
Eine Präsentation dieses Forks wurde in diesem Vortrag auf dem GPU MODE Discord Server behandelt
llm.cpp von @gevtushenko: eine Portierung dieses Projekts unter Verwendung der CUDA C++ Core Libraries
C++/CUDA
llm.cpp von @zhangpiu: eine Portierung dieses Projekts, die Eigen verwendet und CPU/CUDA unterstützt.
WebGPU C++
gpu.cpp von @austinvhuang: eine Bibliothek für tragbare GPU-Berechnung in C++ mit nativer WebGPU. Ziel ist es, eine Allzweckbibliothek zu sein, aber auch llm.c-Kernel auf WGSL zu portieren.
C++
llm.cpp von @GaoYusong: eine Portierung dieses Projekts mit einer C++-Single-Header-Bibliothek tinytorch.hpp
Gehen
llm.go von @joshcarp: eine Go-Portierung dieses Projekts
Java
llm.java von @harryjackson: eine Java-Portierung dieses Projekts
Metall
llm.metal von @regrettable-username: LLM-Training in einfacher, roher C/Metal Shading Language
Mojo
llm. von @dorjeduck: eine Mojo-Portierung dieses Projekts
OpenCL
llm.c von @krrishnarraj: eine OpenCL-Portierung dieses Projekts
Rost
llm.rs von @Yijun Yu: eine Neufassung von Rust mit dem Ziel, die gleiche Leistung zu erzielen
llm.rs von @ToJen: eine Rust-Portierung dieses Projekts
Schnell
llm.swift von @otabuzzman: eine Swift-Portierung dieses Projekts
Zig
llm.zig von @saimirbaci: eine Zig-Portierung dieses Projekts
Habana Gaudi2
llm.tpc von @abhilash1910: eine Habana Gaudi2-Portierung dieses Projekts
Nim
llm.nim von @Vindaar: eine Nim-Portierung dieses Projekts
Möglichkeiten zur Organisation der Entwicklung:
Haben Sie ein konkretes Problem mit dem Repo? Verwenden Sie Probleme.
Möchten Sie Code beisteuern? Öffnen Sie eine PR
Über das Repo chatten, Fragen stellen usw.? Schauen Sie sich Diskussionen an.
Etwas schnelleres? Ich habe auf meinem Zero to Hero Discord-Kanal einen neuen #llmc
-Kanal erstellt.
MIT