LLM en C/CUDA simple y puro sin necesidad de 245 MB de PyTorch o 107 MB de cPython. Actualmente, la atención se centra en el entrenamiento previo, en particular en la reproducción de las miniseries GPT-2 y GPT-3, junto con una implementación de referencia paralela de PyTorch en train_gpt2.py. Reconocerás este archivo como un nanoGPT ligeramente modificado, un proyecto mío anterior. Actualmente, llm.c es un poco más rápido que PyTorch Nightly (aproximadamente un 7%). Además del código principal de vanguardia en train_gpt2.cu, tenemos una implementación de CPU fp32 de referencia simple en ~1,000 líneas de código limpio en un archivo train_gpt2.c. Me gustaría que este repositorio solo mantenga el código C y CUDA. Las adaptaciones a otros idiomas o repositorios son bienvenidas, pero deben realizarse en repositorios separados y estaré encantado de vincularlos a continuación en la sección "bifurcaciones notables". La coordinación de los desarrolladores ocurre en las Discusiones y en Discord, ya sea en el canal #llmc
en el canal Zero to Hero o en #llmdotc
en GPU MODE Discord.
La mejor introducción al repositorio llm.c hoy es la reproducción del modelo GPT-2 (124M). La discusión #481 explica esto en detalle. Podemos reproducir otros modelos de las series GPT-2 y GPT-3 tanto en llm.c como en la implementación paralela de PyTorch. Eche un vistazo a los scripts README.
Consejo de depuración: cuando ejecute el comando make
para compilar el binario, modifíquelo reemplazando -O3
con -g
para que pueda recorrer el código en su IDE favorito (por ejemplo, vscode).
Si no va a entrenar en varios nodos, no está interesado en la precisión mixta y está interesado en aprender CUDA, los archivos fp32 (heredados) pueden ser de su interés. Estos son archivos que fueron "controlados" al principio de la historia de llm.c y congelados en el tiempo. Son más simples, más portátiles y posiblemente más fáciles de entender. Ejecute el código 1 GPU, fp32 como este:
chmod u+x ./dev/download_starter_pack.sh
./dev/download_starter_pack.sh
make train_gpt2fp32cu
./train_gpt2fp32cu
El script download_starter_pack.sh es una manera rápida y fácil de comenzar y descarga un montón de archivos .bin que lo ayudarán a despegar. Estos contienen: 1) el modelo GPT-2 124M guardado en fp32, en bfloat16, 2) un "estado de depuración" utilizado en pruebas unitarias (un pequeño lote de datos y activaciones y gradientes de destino), 3) el tokenizador GPT-2 y 3) el conjunto de datos tokenizado de tinyshakespeare. Alternativamente, en lugar de ejecutar el script .sh, puede volver a crear estos artefactos manualmente de la siguiente manera:
pip install -r requirements.txt
python dev/data/tinyshakespeare.py
python train_gpt2.py
La sección "Soy tan pobre en GPU que ni siquiera tengo una GPU". ¡Todavía puedes disfrutar viendo el tren llm.c! Pero no irás demasiado lejos. Al igual que la versión fp32 anterior, la versión de CPU es un punto de control incluso anterior en la historia de llm.c, cuando era solo una implementación de referencia simple en C. Por ejemplo, en lugar de entrenar desde cero, puede ajustar un GPT- 2 pequeños (124M) para generar texto similar a Shakespeare, como ejemplo:
chmod u+x ./dev/download_starter_pack.sh
./dev/download_starter_pack.sh
make train_gpt2
OMP_NUM_THREADS=8 ./train_gpt2
Si prefiere evitar ejecutar el script del paquete de inicio, como se mencionó en la sección anterior, puede reproducir exactamente los mismos archivos y artefactos .bin ejecutando python dev/data/tinyshakespeare.py
y luego python train_gpt2.py
.
Las líneas anteriores (1) descargan un conjunto de datos de tinyshakespeare ya tokenizado y descargan los pesos GPT-2 (124M), (3) inician desde ellos en C y entrenan durante 40 pasos en tineshakespeare con AdamW (usando un tamaño de lote 4, longitud de contexto solo 64 ), evaluar la pérdida de validación y probar algo de texto. Honestamente, a menos que tenga una CPU robusta (y pueda aumentar la cantidad de subprocesos OMP en el comando de inicio), no llegará tan lejos en los LLM de entrenamiento de CPU, pero podría ser una buena demostración/referencia. El resultado se ve así en mi MacBook Pro (Apple Silicon M3 Max):
[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
---
Los archivos de datos dentro de /dev/data/(dataset).py
son responsables de descargar, tokenizar y guardar los tokens en archivos .bin, que se pueden leer fácilmente desde C. Entonces, por ejemplo, cuando ejecuta:
python dev/data/tinyshakespeare.py
Descargamos y tokenizamos el conjunto de datos de tinyshakespeare. El resultado de esto se ve así:
writing 32,768 tokens to ./dev/data/tinyshakespeare/tiny_shakespeare_val.bin
writing 305,260 tokens to ./dev/data/tinyshakespeare/tiny_shakespeare_train.bin
Los archivos .bin contienen un encabezado corto (1024 bytes) y luego un flujo de tokens en uint16, que indica los identificadores de los tokens con el tokenizador GPT-2. Hay más conjuntos de datos disponibles en /dev/data
.
También adjunto una prueba unitaria simple para asegurarme de que nuestro código C coincida con el código PyTorch. En la CPU como ejemplo, compila y ejecuta con:
make test_gpt2
./test_gpt2
Esto ahora carga el archivo gpt2_124M_debug_state.bin
escrito por train_gpt2.py, ejecuta un pase hacia adelante, compara los logits y las pérdidas con la implementación de referencia de PyTorch, luego realiza 10 iteraciones de entrenamiento con Adam y se asegura de que las pérdidas coincidan con PyTorch. Para probar la versión de GPU ejecutamos:
# fp32 test (cudnn not supported)
make test_gpt2cu PRECISION=FP32 && ./test_gpt2cu
# mixed precision cudnn test
make test_gpt2cu USE_CUDNN=1 && ./test_gpt2cu
Esto prueba tanto la ruta fp32 como la ruta de precisión mixta. La prueba debe aprobarse e imprimirse overall okay: 1
.
Adjunté un tutorial muy pequeño aquí, en doc/layernorm/layernorm.md. Es una guía sencilla, paso a paso, para implementar una única capa del modelo GPT-2, la capa LayerNorm. Este es un buen punto de partida para comprender cómo se implementan las capas en C.
Atención flash . A partir del 1 de mayo de 2024 utilizamos Flash Atención de cuDNN. Debido a que cuDNN aumenta el tiempo de compilación de unos pocos segundos a ~minuto y esta ruta de código es ahora muy nueva, esto está deshabilitado de forma predeterminada. Puedes habilitarlo compilando así:
make train_gpt2cu USE_CUDNN=1
Esto intentará compilar con cudnn y ejecutarlo. Debe tener cuDNN instalado en su sistema. Las instrucciones de instalación de cuDNN con apt-get tomarán el conjunto predeterminado de paquetes cuDNN. Para una configuración mínima, el paquete de desarrollo cuDNN es suficiente, por ejemplo, en Ubuntu 22.04 para 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
Además de esto, necesita la interfaz cuDNN, pero estos son solo archivos de encabezado. Simplemente clona el repositorio en tu disco. Actualmente, Makefile lo busca en su directorio de inicio o en el directorio actual. Si lo ha puesto en otro lugar, agregue CUDNN_FRONTEND_PATH=/path/to/your/cudnn-frontend/include
a la línea de comando make
.
Asegúrese de instalar MPI y NCCL, por ejemplo en Linux:
sudo apt install openmpi-bin openmpi-doc libopenmpi-dev
Para NCCL, siga las instrucciones del sitio web oficial (por ejemplo, instalador de red)
y luego:
make train_gpt2cu
mpirun -np < number of GPUs > ./train_gpt2cu
o simplemente ejecute uno de nuestros scripts en ./scripts/
.
Asegúrese de haber instalado NCCL
siguiendo las instrucciones de la sección multi-GPU.
Actualmente admitimos tres formas que le permiten ejecutar entrenamiento de múltiples nodos:
./scripts/multi_node/run_gpt2_124M_mpi.sh
para obtener más detalles../scripts/multi_node/run_gpt2_124M_fs.sbatch
para obtener más detalles../scripts/multi_node/run_gpt2_124M_tcp.sbatch
para obtener más detalles.Nota:
slurm-wlm
dejó de admitir PMIx), tendrá que utilizar el enfoque FS (2) o TCP (3). . Para probar si su slurm admite PMIx ejecute: srun --mpi=list
y vea si obtiene pmix
en la salida.mpirun
- MPI (1).Ninguno de estos 3 métodos es superior, solo te ofrecemos opciones para que puedas ejecutar en tu entorno específico.
Solo como un proceso de ejemplo para barrer las tasas de aprendizaje en una máquina con 4 GPU en TinyStories. Ejecute un script de shell sweep.sh
(después de, por supuesto, chmod u+x sweep.sh
):
#! /bin/bash
learning_rates=(3e-5 1e-4 3e-4 1e-3)
for i in {0..3} ; do
export CUDA_VISIBLE_DEVICES= $i
screen -dmS " tr $i " bash -c " ./train_gpt2cu -i data/TinyStories -v 250 -s 250 -g 144 -l ${learning_rates[$i]} -o stories $i .log "
done
# you can bring these down with
# screen -ls | grep -E "tr[0-3]" | cut -d. -f1 | xargs -I {} screen -X -S {} quit
Este ejemplo abre 4 sesiones de pantalla y ejecuta los cuatro comandos con diferentes LR. Esto escribe los archivos de registro stories$i.log
con todas las pérdidas, que puedes trazar como desees en Python. Un ejemplo rápido de cómo analizar y trazar estos archivos de registro se encuentra en dev/vislog.ipynb.
Algunas palabras más sobre lo que quiero que sea este repositorio:
Primero, quiero que llm.c
sea un lugar para la educación. Por ejemplo, nuestra carpeta dev/cuda
es un lugar para una biblioteca de núcleos para todas las capas que están escritas manualmente y muy bien documentadas, desde núcleos muy simples hasta núcleos más complejos/rápidos. Si tiene un nuevo kernel con varias compensaciones diferentes, no dude en contribuir con él aquí.
Dicho esto, también quiero que llm.c
sea muy rápido, incluso prácticamente útil para entrenar redes. Por ejemplo, para empezar, deberíamos poder reproducir la gran tanda de entrenamiento del GPT-2 (1.6B). Esto requiere que incorporemos los núcleos más rápidos que existen, incluido el uso de bibliotecas como cuBLAS, cuBLASLt, CUTLASS, cuDNN, etc. También creo que hacerlo tiene un propósito educativo para establecer un límite superior experto y una unidad de medida. por ejemplo, podría decir que sus núcleos escritos manualmente tienen el 80% de la velocidad de cuBLAS, etc. Luego puede optar por realizar una ejecución súper rápida o puede elegir "arrastrar y soltar" los núcleos manuales que desee utilizar y ejecutar con aquellos.
Sin embargo, como restricción, quiero mantener la línea principal llm.c
en la carpeta raíz simple y legible. Si hay un PR que, por ejemplo, mejora el rendimiento en un 2% pero "cuesta" 500 líneas de código C complejo y tal vez una dependencia exótica de terceros, puedo rechazar el PR porque la complejidad no vale la pena. Como ejemplo concreto, hacer que cuBLAS para matmuls sea el valor predeterminado en el ciclo de entrenamiento raíz es una obviedad: hace que el código de línea principal sea mucho más rápido, es una sola línea de código interpretable y es una dependencia muy común. Además de esto, podemos tener implementaciones manuales que pueden competir con cuBLAS en dev/cuda
.
Por último, seré mucho más sensible a la complejidad en la carpeta raíz del proyecto, que contiene los archivos principales/predeterminados del proyecto. En comparación, la carpeta dev/
es más bien un espacio temporal para que podamos desarrollar una biblioteca de núcleos o clases y compartir código útil, relacionado o educativo, y parte de este código podría ser complejo (localmente).
Soporte AMD
DO#
CUDA-C++
C++/CUDA
WebGPU C++
C++
Ir
Java
Metal
Mojo
OpenCL
Óxido
Rápido
Zig
Habana Gaudí2
nim
Formas de organizar el desarrollo:
#llmc
en mi canal Zero to Hero Discord. MIT