LLM на простом чистом C/CUDA без необходимости использования 245 МБ PyTorch или 107 МБ cPython. В настоящее время основное внимание уделяется предварительному обучению, в частности воспроизведению мини-сериалов GPT-2 и GPT-3, а также параллельной эталонной реализации PyTorch в train_gpt2.py. Вы узнаете в этом файле слегка измененный nanoGPT, мой более ранний проект. На данный момент llm.c немного быстрее PyTorch Nightly (примерно на 7%). В дополнение к новейшему основному коду в train_gpt2.cu у нас есть простая эталонная реализация fp32 для ЦП, состоящая примерно из 1000 строк чистого кода в одном файле train_gpt2.c. Я бы хотел, чтобы это репо поддерживало только код C и CUDA. Порты на другие языки или репозитории очень приветствуются, но их следует делать в отдельных репозиториях, и я рад дать ссылку на них ниже в разделе «примечательные форки». Координация действий разработчиков происходит в обсуждениях и в Discord: либо на канале #llmc
на канале Zero to Hero, либо на канале #llmdotc
на Discord в режиме GPU.
Лучшее знакомство с репозиторием llm.c на сегодняшний день — это воспроизведение модели GPT-2 (124M). Обсуждение № 481 подробно описывает это. Другие модели из серий GPT-2 и GPT-3 мы можем воспроизвести как в llm.c, так и в параллельной реализации PyTorch. Посмотрите README скриптов.
Совет по отладке: когда вы запускаете команду make
для сборки двоичного файла, измените ее, заменив -O3
на -g
, чтобы можно было выполнять код в вашей любимой IDE (например, vscode).
Если вы не будете обучаться на нескольких узлах, не заинтересованы в смешанной точности и заинтересованы в изучении CUDA, файлы fp32 (устаревшие) могут вас заинтересовать. Это файлы, которые были «проверены» на ранних этапах истории llm.c и заморожены во времени. Они проще, портативнее и, возможно, их легче понять. Запустите код 1 GPU, fp32 следующим образом:
chmod u+x ./dev/download_starter_pack.sh ./dev/download_starter_pack.sh сделать train_gpt2fp32cu ./train_gpt2fp32cu
Скрипт download_starter_pack.sh — это быстрый и простой способ начать работу. Он загружает несколько .bin-файлов, которые помогут вам начать работу. Они содержат: 1) модель GPT-2 124M, сохраненную в fp32, в bfloat16, 2) «состояние отладки», используемое при модульном тестировании (небольшой пакет данных, целевые активации и градиенты), 3) токенизатор GPT-2. и 3) токенизированный набор данных tinyshakespeare. Альтернативно, вместо запуска сценария .sh вы можете воссоздать эти артефакты вручную следующим образом:
pip install -r требования.txt Python dev/data/tinyshakespeare.py python train_gpt2.py
Раздел «Я настолько беден графическим процессором, что у меня нет даже одного графического процессора». Вы все еще можете наслаждаться поездом llm.c! Но вы не зайдете слишком далеко. Как и версия fp32, указанная выше, версия ЦП является еще более ранней контрольной точкой в истории llm.c, когда это была всего лишь простая эталонная реализация на C. Например, вместо обучения с нуля вы можете точно настроить GPT- 2 маленьких (124M) для вывода текста в стиле Шекспира, например:
chmod u+x ./dev/download_starter_pack.sh ./dev/download_starter_pack.sh сделать train_gpt2 OMP_NUM_THREADS=8 ./train_gpt2
Если вы предпочитаете избегать запуска сценария стартового пакета, то, как упоминалось в предыдущем разделе, вы можете воспроизвести те же самые файлы .bin и артефакты, запустив python dev/data/tinyshakespeare.py
, а затем python train_gpt2.py
.
Вышеупомянутые строки (1) загружают уже токенизированный набор данных tinyshakespeare и загружают веса GPT-2 (124M), (3) инициализируют их на C и обучают 40 шагов на Tineshakespeare с AdamW (с использованием размера пакета 4, длина контекста только 64 ), оценить потери при проверке и взять образец текста. Честно говоря, если у вас нет мощного процессора (и вы не можете увеличить количество потоков OMP в команде запуска), вы не продвинетесь так далеко в LLM для обучения процессора, но это может быть хорошей демонстрацией/справочником. На моем 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 ---
Файлы данных внутри /dev/data/(dataset).py
отвечают за загрузку, токенизацию и сохранение токенов в файлы .bin, которые легко читаются из C. Например, когда вы запускаете:
Python dev/data/tinyshakespeare.py
Мы загружаем и токенизируем набор данных tinyshakespeare. Результат этого выглядит следующим образом:
writing 32,768 tokens to ./dev/data/tinyshakespeare/tiny_shakespeare_val.bin writing 305,260 tokens to ./dev/data/tinyshakespeare/tiny_shakespeare_train.bin
Файлы .bin содержат короткий заголовок (1024 байта), а затем поток токенов в uint16, указывающий идентификаторы токенов с помощью токенизатора GPT-2. Дополнительные наборы данных доступны в /dev/data
.
Я также прилагаю простой модульный тест, позволяющий убедиться, что наш код C согласуется с кодом PyTorch. Например, на процессоре скомпилируйте и запустите с помощью:
сделать test_gpt2 ./test_gpt2
Теперь загружается файл gpt2_124M_debug_state.bin
, который записывается train_gpt2.py, выполняется прямой проход, сравниваются логиты и потери с эталонной реализацией PyTorch, затем выполняется 10 итераций обучения с Адамом и проверяется, соответствуют ли потери PyTorch. Чтобы проверить версию графического процессора, мы запускаем:
# тест fp32 (cudnn не поддерживается) make test_gpt2cu PRECISION=FP32 && ./test_gpt2cu# cudnn смешанной точности testmake test_gpt2cu USE_CUDNN=1 && ./test_gpt2cu
Это тестирует как путь fp32, так и путь смешанной точности. Тест должен пройти и печататься overall okay: 1
.
Я прикрепил очень маленькое руководство здесь, в doc/layernorm/layernorm.md. Это простое пошаговое руководство по реализации одного уровня модели GPT-2 — уровня Layernorm. Это хорошая отправная точка для понимания того, как уровни реализованы в C.
вспышка внимания . С 1 мая 2024 г. мы используем Flash Attention от cuDNN. Поскольку cuDNN увеличивает время компиляции с нескольких секунд до ~минуты, а этот путь кода сейчас очень новый, по умолчанию он отключен. Вы можете включить его, скомпилировав следующим образом:
сделать train_gpt2cu USE_CUDNN=1
Это попытается скомпилировать с помощью cudnn и запустить его. В вашей системе должен быть установлен cuDNN. Инструкции по установке cuDNN с помощью apt-get получат набор пакетов cuDNN по умолчанию. Для минимальной настройки достаточно пакета разработчика cuDNN, например, в Ubuntu 22.04 для 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 sudo apt-get -y install libcudnn9-dev-cuda-12
Кроме того, вам понадобится интерфейс cuDNN, но это всего лишь файлы заголовков. Просто клонируйте репозиторий на свой диск. В настоящее время Makefile ищет его либо в вашем домашнем каталоге, либо в текущем каталоге. Если вы поместили его в другое место, добавьте CUDNN_FRONTEND_PATH=/path/to/your/cudnn-frontend/include
в командную строку make
.
Убедитесь, что вы установили MPI и NCCL, например, в Linux:
sudo apt установить openmpi-bin openmpi-doc libopenmpi-dev
Для NCCL следуйте инструкциям с официального сайта (например, сетевой установщик)
а потом:
сделать train_gpt2cu mpirun -np <количество графических процессоров> ./train_gpt2cu
или просто запустите один из наших скриптов в папке ./scripts/
.
Убедитесь, что вы установили NCCL
, следуя инструкциям из раздела «Мульти-GPU».
В настоящее время мы поддерживаем 3 способа, позволяющих проводить многоузловое обучение:
Используйте OpenMPI для обмена идентификатором nccl и инициализации NCCL. Подробности см., например, в сценарии ./scripts/multi_node/run_gpt2_124M_mpi.sh
.
Используйте общую файловую систему для инициализации NCCL. Подробности см. в сценарии ./scripts/multi_node/run_gpt2_124M_fs.sbatch
.
Используйте TCP-сокеты для инициализации NCCL. Подробности см. в сценарии ./scripts/multi_node/run_gpt2_124M_tcp.sbatch
.
Примечание:
Если вы работаете в среде slurm и ваш slurm не поддерживает PMIx (что, как мы предполагаем, будет распространенной ситуацией, учитывая, что slurm-wlm
отказался от поддержки PMIx), вам придется использовать подход FS (2) или TCP (3). . Чтобы проверить, поддерживает ли ваш slurm PMIx, запустите: srun --mpi=list
и посмотрите, получите ли вы pmix
на выходе.
Если у вас не настроен slurm, вы можете запустить многоузловой запуск с помощью mpirun
- MPI (1).
Ни один из этих трех методов не является лучшим, мы просто предлагаем вам варианты, которые вы сможете использовать в своей конкретной среде.
Просто в качестве примера процесса оценки скорости обучения на машине с 4 графическими процессорами в TinyStories. Запустите скрипт sweep.sh
(конечно, после того, как вы выполните chmod u+x sweep.sh
):
#!/bin/bashlearning_rates=(3e-5 1e-4 3e-4 1e-3)для i в {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 "done# вы можете отключить их с помощью# screen -ls | grep -E "tr[0-3]" | вырезать -д. -f1 | xargs -I {} screen -X -S {} выйти
В этом примере открываются 4 сеанса экрана и выполняются четыре команды с разными LR. При этом в файлы журналов записаны stories$i.log
со всеми потерями, которые вы можете построить в Python по своему усмотрению. Краткий пример того, как анализировать и отображать эти файлы журналов, можно найти в dev/vislog.ipynb.
Еще несколько слов о том, каким я хочу видеть это репо:
Во-первых, я хочу, чтобы llm.c
стал местом обучения. Например, наша папка dev/cuda
— это место для библиотеки ядер для всех слоев, которые написаны вручную и очень хорошо документированы, начиная от очень простых ядер и заканчивая более сложными/быстрыми ядрами. Если у вас есть новое ядро с различными компромиссами, пожалуйста, поделитесь им здесь.
Тем не менее, я также хочу, чтобы llm.c
был очень быстрым и даже практически полезным для обучения сетей. Например, для начала мы должны быть в состоянии воспроизвести большой тренировочный прогон GPT-2 (1.6B). Это требует, чтобы мы включили все возможные самые быстрые ядра, включая использование таких библиотек, как cuBLAS, cuBLASLt, CUTLASS, cuDNN и т. д. Я также думаю, что это служит образовательной цели, чтобы установить экспертную верхнюю границу и единицу измерения. например, вы можете сказать, что ваши ядра, написанные вручную, составляют 80% скорости cuBLAS и т. д. Затем вы можете выбрать сверхбыстрый запуск или «перетащить» любые ядра, которые вы используете вручную. хотите использовать и работать с ними.
Однако в качестве ограничения я хочу, чтобы основная строка llm.c
в корневой папке была простой и читаемой. Если есть PR, который, например, повышает производительность на 2%, но «стоит» 500 строк сложного кода C и, возможно, экзотической сторонней зависимости, я могу отклонить PR, потому что сложность того не стоит. В качестве конкретного примера: сделать cuBLAS для matmuls по умолчанию в корневом цикле обучения не составляет труда: это делает основной код намного быстрее, это одна строка интерпретируемого кода, и это очень распространенная зависимость. Кроме того, у нас могут быть ручные реализации, которые могут конкурировать с cuBLAS в dev/cuda
.
Наконец, я буду гораздо более чувствителен к сложности корневой папки проекта, которая содержит основные файлы проекта/файлы по умолчанию. Для сравнения, папка dev/
— это немного больше места, где мы можем разработать библиотеку ядер или классов и поделиться полезным, связанным или образовательным кодом, и часть этого кода может быть (локально) сложной.
Поддержка AMD
llm.c от @anthonix: поддержка устройств AMD, таких как 7900 XTX.
С#
llm.cs от @azret: порт этого проекта на C#.
Llm.cs от @nietras: порт этого проекта на C#, ориентированный на простоту начала работы на любой платформе. Клонируйте и запускайте ✅
КУДА С++
Презентация этого форка была рассмотрена в этой лекции на сервере Discord в режиме GPU.
llm.cpp от @gevtushенко: порт этого проекта с использованием основных библиотек CUDA C++.
С++/CUDA
llm.cpp от @zhangpiu: порт этого проекта с использованием Eigen и поддержкой CPU/CUDA.
Веб-GPU С++
gpu.cpp от @austinvhuang: библиотека для портативных вычислений на графическом процессоре на C++ с использованием встроенного WebGPU. Цель — стать библиотекой общего назначения, но также портировать ядра llm.c на WGSL.
С++
llm.cpp от @GaoYusong: порт этого проекта с библиотекой tinytorch.hpp с одним заголовком C++.
Идти
llm.go от @joshcarp: порт этого проекта на Go.
Ява
llm.java от @harryjackson: порт этого проекта на Java.
Металл
llm.metal от @regrettable-username: обучение LLM простому сырому языку шейдинга C/Metal.
Моджо
лм. автор @dorjeduck: порт этого проекта на Mojo
OpenCL
llm.c от @krrishnarraj: порт этого проекта на OpenCL.
Ржавчина
llm.rs от @Yijun Yu: переписанный Rust с целью добиться той же производительности
llm.rs от @ToJen: порт этого проекта на Rust.
Быстрый
llm.swift от @otabuzzman: порт этого проекта на Swift.
Зиг
llm.zig от @saimirbaci: Zig-порт этого проекта.
Гавана Гауди2
llm.tpc от @abhilash1910: порт этого проекта Habana Gaudi2.
Ним
llm.nim от @Vindaar: порт этого проекта на Nim.
Способы организации разработки:
Возникли конкретные проблемы с репо? Используйте «Проблемы».
Есть какой-нибудь код, чтобы внести свой вклад? Открыть PR
Общаться о репо, задавать вопросы и т.д.? Посмотрите Обсуждения.
Что-то быстрее? Я создал новый канал #llmc
на своем канале Zero to Hero Discord.
Массачусетский технологический институт