LLMs em C/CUDA simples e puro, sem necessidade de 245 MB de PyTorch ou 107 MB de cPython. O foco atual está no pré-treinamento, em particular na reprodução das minisséries GPT-2 e GPT-3, juntamente com uma implementação de referência paralela do PyTorch em train_gpt2.py. Você reconhecerá este arquivo como um nanoGPT ligeiramente ajustado, um projeto anterior meu. Atualmente, llm.c é um pouco mais rápido que PyTorch Nightly (cerca de 7%). Além do código principal de última geração em train_gpt2.cu, temos uma implementação de CPU fp32 de referência simples em aproximadamente 1.000 linhas de código limpo em um arquivo train_gpt2.c. Gostaria que este repositório mantivesse apenas código C e CUDA. Portagens para outros idiomas ou repositórios são muito bem-vindas, mas devem ser feitas em repositórios separados, e tenho prazer em linká-los abaixo na seção "garfos notáveis". A coordenação do desenvolvedor acontece nas Discussões e no Discord, seja no canal #llmc
no canal Zero to Hero, ou no #llmdotc
no GPU MODE Discord.
A melhor introdução ao repositório llm.c hoje é a reprodução do modelo GPT-2 (124M). A discussão nº 481 aborda isso em detalhes. Podemos reproduzir outros modelos das séries GPT-2 e GPT-3 tanto no llm.c quanto na implementação paralela do PyTorch. Dê uma olhada no README dos scripts.
dica de depuração: ao executar o comando make
para construir o binário, modifique-o substituindo -O3
por -g
para que você possa percorrer o código em seu IDE favorito (por exemplo, vscode).
Se você não treinará em vários nós, não estiver interessado em precisão mista e estiver interessado em aprender CUDA, os arquivos fp32 (legado) podem ser do seu interesse. Esses são arquivos que foram "marcados" no início da história do llm.c e congelados no tempo. Eles são mais simples, mais portáteis e possivelmente mais fáceis de entender. Execute o código 1 GPU, fp32 assim:
chmod u+x ./dev/download_starter_pack.sh ./dev/download_starter_pack.sh faça train_gpt2fp32cu ./train_gpt2fp32cu
O script download_starter_pack.sh é uma maneira rápida e fácil de começar e baixa vários arquivos .bin que ajudam você a decolar. Eles contêm: 1) o modelo GPT-2 124M salvo em fp32, em bfloat16, 2) um "estado de depuração" usado em testes de unidade (um pequeno lote de dados e ativações e gradientes de destino), 3) o tokenizer GPT-2 e 3) o conjunto de dados tokenizado de tinyshakespeare. Alternativamente, em vez de executar o script .sh, você pode recriar esses artefatos manualmente da seguinte maneira:
pip instalar -r requisitos.txt python dev/data/tinyshakespeare.py python train_gpt2.py
A seção "Sou tão pobre em GPU que nem tenho uma GPU". Você ainda pode gostar de ver o trem llm.c! Mas você não irá longe demais. Assim como a versão fp32 acima, a versão CPU é um ponto de verificação ainda anterior na história do llm.c, quando era apenas uma simples implementação de referência em C. Por exemplo, em vez de treinar do zero, você pode ajustar um GPT- 2 pequenos (124M) para produzir texto semelhante ao de Shakespeare, por exemplo:
chmod u+x ./dev/download_starter_pack.sh ./dev/download_starter_pack.sh fazer train_gpt2 OMP_NUM_THREADS=8 ./train_gpt2
Se preferir evitar a execução do script do pacote inicial, conforme mencionado na seção anterior, você pode reproduzir exatamente os mesmos arquivos e artefatos .bin executando python dev/data/tinyshakespeare.py
e depois python train_gpt2.py
.
As linhas acima (1) baixam um conjunto de dados tinyshakespeare já tokenizado e baixam os pesos GPT-2 (124M), (3) iniciam deles em C e treinam para 40 etapas em tineshakespeare com AdamW (usando tamanho de lote 4, comprimento de contexto apenas 64 ), avaliar a perda de validação e provar algum texto. Honestamente, a menos que você tenha uma CPU robusta (e possa aumentar o número de threads OMP no comando de inicialização), você não irá tão longe nos LLMs de treinamento de CPU, mas pode ser uma boa demonstração/referência. A saída é assim no meu 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 ---
Os arquivos de dados dentro de /dev/data/(dataset).py
são responsáveis por baixar, tokenizar e salvar os tokens em arquivos .bin, facilmente legíveis em C. Por exemplo, quando você executa:
python dev/data/tinyshakespeare.py
Baixamos e tokenizamos o conjunto de dados tinyshakespeare. A saída disso é semelhante a esta:
writing 32,768 tokens to ./dev/data/tinyshakespeare/tiny_shakespeare_val.bin writing 305,260 tokens to ./dev/data/tinyshakespeare/tiny_shakespeare_train.bin
Os arquivos .bin contêm um cabeçalho curto (1024 bytes) e, em seguida, um fluxo de tokens em uint16, indicando os IDs dos tokens com o tokenizer GPT-2. Mais conjuntos de dados estão disponíveis em /dev/data
.
Também estou anexando um teste de unidade simples para garantir que nosso código C esteja de acordo com o código PyTorch. Na CPU como exemplo, compile e execute com:
faça test_gpt2 ./test_gpt2
Isso agora carrega o arquivo gpt2_124M_debug_state.bin
que é escrito por train_gpt2.py, executa uma passagem direta, compara os logits e as perdas com a implementação de referência do PyTorch, depois faz 10 iterações de treinamento com Adam e garante que as perdas correspondam ao PyTorch. Para testar a versão da GPU, executamos:
# teste fp32 (cudnn não suportado)make test_gpt2cu PRECISION=FP32 && ./test_gpt2cu# precisão mista cudnn testmake test_gpt2cu USE_CUDNN=1 && ./test_gpt2cu
Isso testa o caminho fp32 e o caminho de precisão mista. O teste deve passar e a impressão overall okay: 1
.
Anexei um pequeno tutorial aqui, em doc/layernorm/layernorm.md. É um guia simples e passo a passo para implementar uma única camada do modelo GPT-2, a camada layernorm. Este é um bom ponto de partida para entender como as camadas são implementadas em C.
flash de atenção . A partir de 1º de maio de 2024, usaremos o Flash Attention da cuDNN. Como o cuDNN aumenta o tempo de compilação de alguns segundos para ~ minutos e esse caminho de código é muito novo no momento, ele está desabilitado por padrão. Você pode habilitá-lo compilando assim:
faça train_gpt2cu USE_CUDNN = 1
Isso tentará compilar com cudnn e executá-lo. Você precisa ter o cuDNN instalado em seu sistema. As instruções de instalação do cuDNN com apt-get pegarão o conjunto padrão de pacotes cuDNN. Para uma configuração mínima, o pacote cuDNN dev é suficiente, por exemplo, no 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 atualização sudo apt-get -y instalar libcudnn9-dev-cuda-12
Além disso, você precisa do frontend cuDNN, mas são apenas arquivos de cabeçalho. Basta clonar o repositório em seu disco. O Makefile atualmente procura por ele em seu diretório inicial ou no diretório atual. Se você o colocou em outro lugar, adicione CUDNN_FRONTEND_PATH=/path/to/your/cudnn-frontend/include
à linha de comando make
.
Certifique-se de instalar MPI e NCCL, por exemplo, no Linux:
sudo apt instalar openmpi-bin openmpi-doc libopenmpi-dev
Para NCCL siga as instruções do site oficial (por exemplo, instalador de rede)
e então:
faça train_gpt2cu mpirun -np <número de GPUs> ./train_gpt2cu
ou simplesmente execute um de nossos scripts em ./scripts/
.
Certifique-se de ter instalado NCCL
seguindo as instruções da seção multi-GPU.
Atualmente oferecemos suporte a três maneiras que permitem executar treinamento de vários nós:
Use OpenMPI para trocar o ID nccl e inicializar o NCCL. Consulte, por exemplo, o script ./scripts/multi_node/run_gpt2_124M_mpi.sh
para obter detalhes.
Use o sistema de arquivos compartilhado para iniciar o NCCL. Consulte o script ./scripts/multi_node/run_gpt2_124M_fs.sbatch
para obter detalhes.
Use soquetes TCP para iniciar o NCCL. Consulte o script ./scripts/multi_node/run_gpt2_124M_tcp.sbatch
para obter detalhes.
Observação:
Se você estiver executando em um ambiente slurm e seu slurm não suportar PMIx (o que presumimos ser uma situação comum, visto que slurm-wlm
abandonou o suporte PMIx), você terá que usar a abordagem FS (2) ou TCP (3) . Para testar se o seu slurm suporta a execução do PMIx: srun --mpi=list
e veja se você obtém pmix
na saída.
Se você não tiver o slurm configurado, poderá iniciar uma execução de vários nós usando mpirun
- MPI (1).
Nenhum desses 3 métodos é superior, apenas oferecemos opções para que você possa rodar em seu ambiente específico.
Apenas como exemplo de processo para varrer as taxas de aprendizado em uma máquina com 4 GPUs no TinyStories. Execute um script de shell sweep.sh
(após você, é claro, chmod u+x sweep.sh
):
#!/bin/bashlearning_rates=(3e-5 1e-4 3e-4 1e-3)para i em {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 histórias$i.log "pronto# você pode derrubá-los com# screen -ls | grep -E "tr[0-3]" | cortar -d. -f1 | xargs -I {} tela -X -S {} sair
Este exemplo abre 4 sessões de tela e executa os quatro comandos com LRs diferentes. Isso grava os arquivos de log stories$i.log
com todas as perdas, que você pode plotar como desejar em Python. Um exemplo rápido de como analisar e plotar esses arquivos de log está em dev/vislog.ipynb.
Mais algumas palavras sobre o que desejo que este repositório seja:
Primeiro, quero que llm.c
seja um lugar de educação. Por exemplo, nossa pasta dev/cuda
é um local para uma biblioteca de kernels para todas as camadas que são escritas manualmente à mão e muito bem documentadas, desde kernels muito simples até kernels mais complexos/mais rápidos. Se você tiver um novo kernel com várias compensações diferentes, sinta-se à vontade para contribuir aqui.
Dito isso, também quero que llm.c
seja muito rápido, até mesmo útil na prática para treinar redes. Por exemplo, para começar, deveríamos ser capazes de reproduzir a grande execução de treinamento do GPT-2 (1.6B). Isso requer que incorporemos quaisquer kernels mais rápidos que existam, incluindo o uso de bibliotecas como cuBLAS, cuBLASLt, CUTLASS, cuDNN, etc. Também acho que isso serve a um propósito educacional para estabelecer um limite superior especializado e uma unidade de medida, por exemplo, você poderia dizer que seus kernels escritos manualmente têm 80% da velocidade do cuBLAS, etc. Então você pode optar por fazer uma execução super rápida ou pode optar por "arrastar e soltar" quaisquer kernels manuais que deseja usar, e corra com eles.
No entanto, como restrição, quero manter a linha principal llm.c
na pasta raiz simples e legível. Se houver um PR que, por exemplo, melhora o desempenho em 2%, mas "custa" 500 linhas de código C complexo e talvez uma dependência exótica de terceiros, posso rejeitar o PR porque a complexidade não vale a pena. Como um exemplo concreto - tornar cuBLAS para matmuls o padrão no loop de treinamento raiz é óbvio: torna o código da linha principal muito mais rápido, é uma única linha de código interpretável e é uma dependência muito comum. Além disso, podemos ter implementações manuais que podem competir com cuBLAS em dev/cuda
.
Por fim, serei muito mais sensível à complexidade na pasta raiz do projeto, que contém os arquivos principais/padrão do projeto. Em comparação, a pasta dev/
é um pouco mais como um espaço de trabalho para desenvolvermos uma biblioteca de kernels ou classes e compartilhar código útil, relacionado ou educacional, e parte desse código pode ser aceitável (localmente) complexo.
Suporte AMD
llm.c por @anthonix: suporte para dispositivos AMD, como o 7900 XTX
C#
llm.cs por @azret: uma porta C# deste projeto
Llm.cs por @nietras: uma versão C# deste projeto com foco na facilidade de introdução em qualquer plataforma. Clone e execute ✅
CUDA C++
Uma apresentação deste fork foi abordada nesta palestra no GPU MODE Discord Server
llm.cpp por @gevtushenko: uma versão deste projeto usando as bibliotecas principais CUDA C++
C++/CUDA
llm.cpp de @zhangpiu: uma versão deste projeto usando o Eigen, com suporte para CPU/CUDA.
WebGPU C++
gpu.cpp de @austinvhuang: uma biblioteca para computação GPU portátil em C++ usando WebGPU nativo. Pretende ser uma biblioteca de uso geral, mas também portar kernels llm.c para WGSL.
C++
llm.cpp por @GaoYusong: uma versão deste projeto apresentando uma biblioteca C++ de cabeçalho único tinytorch.hpp
Ir
llm.go por @joshcarp: uma versão Go deste projeto
Java
llm.java por @harryjackson: uma versão Java deste projeto
Metal
llm.metal por @regrettable-username: treinamento LLM em linguagem C/Metal Shading simples e bruta
Mojo
sim. por @dorjeduck: uma versão Mojo deste projeto
OpenCL
llm.c por @krrishnarraj: uma versão OpenCL deste projeto
Ferrugem
llm.rs por @Yijun Yu: uma reescrita de Rust com o objetivo de ter o mesmo desempenho
llm.rs por @ToJen: uma versão Rust deste projeto
Rápido
llm.swift por @otabuzzman: uma versão Swift deste projeto
Ziguezague
llm.zig por @saimirbaci: uma versão Zig deste projeto
Havana Gaudi2
llm.tpc por @abhilash1910: uma versão Habana Gaudi2 deste projeto
Nim
llm.nim por @Vindaar: uma versão Nim deste projeto
Formas de organizar o desenvolvimento:
Está enfrentando um problema concreto com o repo? Use problemas.
Tem algum código para contribuir? Abra um PR
Conversar sobre o repositório, fazer perguntas, etc.? Veja Discussões.
Algo mais rápido? Criei um novo canal #llmc
no meu canal Zero to Hero Discord.
MIT