Всем привет! Хочу поделиться одной небольшой заметкой о том как обучить модель ruGPT-3 на своей видеокарте при помощи датасета с текстами книг Фёдора Михайловича Достоевского.
Развитие темы в публикации: Как LangChain и ruGPT-3 подружить.
Публикация построена следующим образом: сначала рассмотрим как подготовить изолированное окружение при помощи venv, далее скачаем и подготовим датасет, после чего обучим модель и выполним проверку результата.
Введение
На самом деле обучить свою модель на базе ruGPT-3 очень легко, для этого не нужно обладать выдающимися познания в области Machine Learning и большим опытом разработки на Python. Единственная сложность с которой вы скорее всего столкнётесь - это подготовка специального датасета, но эту тему я оставлю для одной из следующих статей, а в этот раз мы возьмём готовый датасет, который я нашёл на GitHub.
Для тех кому не терпится посмотреть исходники вот ссылочка.
Рекомендованные системные требования
- Операционная система на базе ядра Linux, у меня например Debian
- Консольный git
- Python 3.10, в принципе подойдёт и 3.11 и 3.9 или 3.8, но мне привычнее использовать для нейронок 3.10
- Современная видеокарта Nvidia с 8Гб+ VRAM, подойдёт RTX 3050 или новее
- Свежий драйвер Nvidia, желательно 520.xx или выше
- CUDA тулкит, желательно 11.8 или выше (версию запомните, она пригодится дальше)
- Прямые руки (опционально)
Подготовка PyEnv-окружения
На всякий случай, я заранее подготовил Dockerfile в котором все указанные процедуры выполняются автоматически на этапе сборки, поэтому если столкнётесь с трудностями, то можете обратиться к нему. Там же вы можете найти docker-compose.yml с необходимыми параметрами для упрощения процедуры сборки и запуска.
Приступим, создадим пустую папку, назовём её скажем rugpt3-custom, далее перейдём в неё и создадим venv, после чего переключим контекст на него:
mkdir rugpt3-custom
cd rugpt3-custom
python3.10 -m venv venv
. ./venv/bin/activate
Начнём с самой неприятной части, а именно процесс установки Nvidia Apex, зачем это расширение было добавлено в проект, можно ли было сделать (как другие) без него - для меня загадка, но имеет, что имеем.
Скачаем пару пакетов из репозитория pytorсh:
pip install packaging==23.0 torch==2.0.1+cu118 -f https://download.pytorch.org/whl/torch_stable.html
В команде выше очень важно указать правильную версию CUDA в суффиксе cuXXX (в нашем случае используется CUDA 11.8, поэтому суффикс будет cu118), главное чтобы она совпадала с той, что установлена в вашей системе.
Теперь поставим компиляторы, скачаем исходники Nvidia Apex, выполним сборку и установку при помощи pip:
sudo apt install build-essential
git clone https://github.com/NVIDIA/apex.git
cd ./apex
# UPD. 2023-08-12
# Пришлось чуть поправить статью спустя пару
# месяцев после публикации, дело в том, что
# разработчики apex сломали свою библиотеку в одном
# из последних коммитов, так что я добавил git checkout
# на версию которая была у меня на момент создания
# публикации
git checkout 8b7a1ff183741dd8f9b87e7bafd04cfde99cea28
pip install -v --no-cache-dir --global-option="--cpp_ext" --global-option="--cuda_ext" .
cd ..
Далее потребуется установить некоторые дополнительные модули:
pip install numpy==1.24.2 pandas==1.5.3 transformers==2.8.0 urllib3==1.25.4 tensorboard==2.12.0
Можно сделать иначе и создать в папке файл requirements.txt со следующим содержимым:
numpy==1.24.2
pandas==1.5.3
transformers==2.8.0
urllib3==1.25.4
tensorboard==2.12.0
После чего выполнить установку командой:
pip install -r requirements.txt
На этом шаге наше окружение готово и можно двигаться дальше.
Подготовка датасета из книг Достоевского
В данной главе предполагается, что будет использован уже готовый датасет из проекта K7chyp/DostoevskyDoesntWriteIt, в упомянутом проекте автор решал похожую задачу, но при помощи Jupyiter Notebooks (кстати, про настройку данной платформу у меня имеется соответствующая публикация).
И так, создадим папку data и скачаем в неё датасет output.csv.
mkdir data
wget -P data https://raw.githubusercontent.com/K7chyp/DostoevskyDoesntWriteIt/master/Data/output.csv
Теперь надо распарсить скачанный датасет на два файла: train.txt и valid.txt, первый будет использоваться для обучения сети, а второй для проверки качества работы модели.
Для этого в корне проекта создадим скрипт prepare.py следующего содержания:
В нём выполняется три простых шага:
- Чтение строк из файла output.csv в массив
- Разделением массива: 70% это train, а 30% это valid
- Сохранение файлов data/train.txt и data/valid.txt
Теперь выполним этот скрипт и посмотрим, что получилось:
ls -la ./data/
python prepare.py
ls -la ./data/
В результате своей работы prepare.py разделит датасет на две части и сохранит в соответствующие файлы.
Тренировка модели
Теперь мы подошли вплотную к вопросу обучения модели ruGPT-3, для того чтобы данную процедуру выполнить нам понадобится скрипт pretrain_transformers.py который можно скачать из репозитория ai-forever/ru-gpts (ранее известный, как sberbank-ai/ru-gpts, но поскольку в GitHub понабрали по квоте, теперь OpenSource это только кого-надо OpenSource и Сберу больше низя, поэтому проект просто переименовали дабы его не забанили на самой "демократичной" площадке).
wget https://raw.githubusercontent.com/ai-forever/ru-gpts/master/pretrain_transformers.py
И рядышком положим скрипт train.sh следующего содержания:
Быстренько пробежимся по списку параметров:
- --output_dir=dostoevsky_doesnt_write_it - Определяет директорию для сохранения обученной модели и других выходных данных обучения, она будет находиться в корне папки rugpt3-custom.
- --model_type=gpt2 - Определяет тип модели, которую следует использовать. В данном случае используется gpt2.
- --model_name_or_path=ai-forever/rugpt3small_based_on_gpt2 - Определяет название модели или путь к модели. В данном случае используется rugpt3small_based_on_gpt2, но можно указать любую другую семейства rugpt (смотри полный список тут) или путь к папке с обученной/скачанной моделью.
- --do_train - Определяет, следует ли обучать модель.
- --train_data_file=data/train.txt - Определяет файл с данными для обучения.
- --do_eval - Определяет, следует ли проводить оценку модели после обучения.
- --eval_data_file=data/valid.txt - Определяет файл с данными для оценки модели.
- --fp16 - Определяет, следует ли использовать 16-битное плавающее точное число (half-precision floating point number) для обучения для увеличения скорости обучения и уменьшения использования памяти.
- --per_gpu_train_batch_size 1 - Определяет размер партии для обучения на каждую графическую процессорную единицу (GPU).
- --gradient_accumulation_steps 1 - Определяет количество шагов, на которых градиенты должны накапливаться перед обратным распространением ошибки.
- --num_train_epochs 5 - Определяет количество эпох обучения.
- --block_size 512 - Определяет размер блока входных данных для модели.
- --overwrite_output_dir - Если указан, перезаписывает содержимое выходной директории во время обучения. Если не указан и в выходной директории уже существуют файлы, скрипт завершит работу.
Хочу сказать пару слов про модель rugpt3small_based_on_gpt2 для учебных целей её возможностей более чем достаточно, однако, для решения более сложных задач, например для создания полноценного чат-бота желательно использовать rugpt3large_based_on_gpt2, правда для её обучения потребуется видеокарта уровня 3090 или 4090 (нужно где-то 20Гб VRAM), но результаты приятно порадуют.
Опытным путём я выявил, что оптимальнее всего по соотношению качество/скорость использовать:
- Для обучения small модели любой --block_size вплоть до 2048, но желательно не меньше 512, а --num_train_epochs в районе 15 или больше.
- Для обучения medium модели также как и для small подойдёт любой --block_size, но количество эпох можно оставить в районе 10 или больше.
- Для обучения large модели использовать --block_size равный 1024 и количество эпох в районе 5-10.
Небольшая ремарка про --num_train_epochs лучше не делать меньше 3 иначе авторский стиль не успеет закрепиться в модели, а на 15-16 эпохах тренировки large модели у меня получались самые интересные результаты, правда на 4090 ждать долго, где-то 4-5 часов.
Скрипты настроены, запускаем тренировку и идём отдыхать, так как обучение займёт некоторое время:
sh train.sh
В выводе команды nvidia-smi можно посмотреть сколько VRAM кушает тренировка модели (в данном примере модели small).
После завершения тренировки система отрапортует, что всё прошло удачно, а в корне директории rugpt3-custom мы увидем поддиректорию dostoevsky_doesnt_write_it, посмотрим что в ней командой ls.
В директориях с префиксом checkpoint-XXX находятся промежуточные состяния модели, их можно использовать также как и любую другую модель. Обычно чекпоинты интересны для того чтобы проследить как менялась точность модели в процессе обучения, но можно с этим не заморачиваться и просто удалить все эти директории.
И так, наша модель готова, переходим к испытаниям.
Тестирование обученной модели
Мы на финишной прямой, осталась самая малось, для запуска модели нам понадобится файл generate_transformers.py из упомянутого ранее репозитория ai-forever/ru-gpts, скачаем его:
wget https://raw.githubusercontent.com/ai-forever/ru-gpts/master/generate_transformers.py
Далее создадим файл prompt.sh следующего содержания:
В нём видно, что выполняется запуск скрипта generate_transformers.py, а на вход передаются следующие параметры:
- --model_type=gpt2 - Определяет тип модели, которую следует использовать. В данном случае используется gpt2.
- --model_name_or_path=dostoevsky_doesnt_write_it - Определяет название модели или путь к модели. В данном случае используется директория dostoevsky_doesnt_write_it.
- --k=5 - Параметр k для стратегии генерации текста, известной как Top-K sampling. Это означает, что при генерации каждого следующего слова модель рассматривает только топ-5 наиболее вероятных слов. Если увеличить данное значение сеть начнёт давать более "творческие" ответы.
- --p=0.95 - Параметр p для стратегии генерации текста, известной как Top-P или Nucleus sampling. Это означает, что модель будет рассматривать минимальное количество наиболее вероятных слов, совокупная вероятность которых превышает 0.95. Если сделать меньше сеть начнёт давать более "творческие" ответы.
- --length=100 - Определяет длину генерируемого текста, для своих моделей я обычно использую значение 512 или больше.
В принципе можно добавить ещё пару параметров, они помогут модели вести себя чуть более адекватно:
- --repetition_penalty=1.1 - Параметр, который штрафует повторения в генерируемом тексте. Если значение больше 1.0, то модель будет стараться избегать повторений. Значение 1.1 указывает на небольшой штраф за повторение.
- --padding_text="Пользователь: Привет, какой сегодня день? \n Искусственный интеллект: Привет! Сегодня понедельник. \n Пользователь: Спасибо, а какая погода на улице? \n Искусственный интеллект: Сегодня солнечно и тепло. \n Пользователь: " - Этот параметр позволяет задать текст, который будет добавлен перед входными данными, но не будет учитываться при генерации выходных данных. Используется для предварительного "прогрева" модели перед началом генерации. Это может быть полезно, если вы хотите, чтобы модель была в определенном "контексте" перед началом генерации. Для искусственного интеллекта, который отвечает на запросы пользователя, в качестве padding_text можно использовать примеры предыдущих запросов и ответов.
Запустим оболочку и попробуем передать ей несколько затравок:
Как видно модель исправно работает!
Завершение
Под занавес скажу, что можно пытаться бесконечно улучшать датасеты и процедуту обучения модели ruGPT-3, потенциал возможностей почти безграничен, спектр применения модели широк, можно настроить её для работы в паре с чат-ботом или обучить писать скрипты на bash, или же скормить ей научные публикации и получить незаменимого помощника для учёбы. С моей скромной точки зрения намного сложнее придумать саму задачу и найти пару свободных вечеров, а уж решение этой задачи с помощью описанных тут методов не составит большого труда :)
Если вам интересно узнать больше или обсудить что-то конкретное, присоединяйтесь к моему Telegram-каналу. Там я регулярно делюсь своими мыслями и новостями в области IT, а также отвечаю на ваши вопросы.
К тому же, если вы хотите поддержать мои усилия и вклад в развитие общества знаний, вы можете сделать пожертвование на CloudTips. Ваша поддержка поможет мне продолжать свою работу и делиться новыми открытиями с вами.
Спасибо за то, что провели время со мной, увидимся в следующей публикации! До встречи!