- PVSM.RU - https://www.pvsm.ru -

Большие языковые модели (LLM) произвели настоящую революцию в мире ML. Все больше компаний стремятся так или иначе извлечь из них пользу. Например, в Selectel мы оцениваем рациональность развертывания частной LLM для помощи сотрудникам техподдержки в поиске ответов на вопросы клиентов. Эту задачу мы решили совместить с тестом нового железа — видеокарты Ada с 48 ГБ RAM [1]. В соперники ей выбрали А100 на 40 ГБ.
Сразу оговоримся, что нормально обучить LLM на одной GPU практически невозможно, но в качестве теста производительности такая задача вполне подходит. Под катом рассказываем, как мы проводили тест-драйв двух GPU и к каким выводам пришли.
Используйте навигацию, если не хотите читать текст полностью:
→ Почему нельзя просто взять самые мощные GPU [2]
→ За что мы любим А100 и А6000 Ada [3]
→ Тест — два GPU решают одни и те же задачи в одинаковых условиях [4]
→ Результаты теста [5]
→ Заключение [6]
Опираясь только на заявленные производителем характеристики видеокарт, легко попасть в ловушку, посчитав, что чем больше у карты памяти и CUDA-ядер, а также чем шире шина, тем лучше. С одной стороны, очевидно, что более сложные задачи требуют больше памяти. Кроме RAM GPU на скорость и сложность расчетов влияют еще и ядра. С другой стороны, покупать или арендовать карту с топовыми характеристиками на все случаи жизни — не лучшая идея. И вот почему…
Для обучения больших моделей и работы с массивными датасетами нужны действительно дорогие видеокарты. Цена выбранной конфигурации часто заставляет задуматься: чуть меньше памяти и ядер замедлят работу, но будет ли это замедление настолько критичным, чтобы переплачивать миллионы каждый месяц? К тому же, несколько лишних гигабайт могут так и не пригодиться.
Вы купили одну или несколько видеокарт или арендовали готовый сервер с GPU [1]. Конечно, вы тут же загрузили его на 100% обучением ML-моделей. Но GPU вряд ли будет работать в режиме 24/7/365, даже если решение сложных задач поставить на поток.
Скорее всего, картина будет такой. Дата-сайентист забрал себе весь ресурс, но использовал GPU, скажем, четыре часа в сутки. В остальное время он обучал модель на CPU, пил кофе или решал другие задачи. Чтобы оплаченные ресурсы в это время не пропадали, их стоит отдать другим специалистам. И тут мы плавно подходим к следующему пункту.
Шеринг GPU [7] — отличный ход, когда нужно одновременно решать несколько задач, ни одна из которых не требует всех доступных ресурсов. Берем карту, делим ее на несколько изолированных кусочков, в которых есть своя память, ядра, кэш и прочее, и отдаем по одному каждому из дата-сайентистов под его задачу. Но, конечно, не все так просто.
Во-первых, каждый из способов шеринга имеет свои плюсы и минусы. Во-вторых, одна и та же технология, например MIG, может поделить А100 максимум на семь логических блоков, а А30 — максимум на четыре.

В-третьих, даже при шеринге часть ресурсов может простаивать. Допустим, мы берем карту А100 на 40 ГБ и через MIG делим на максимально возможные семь партиций (кстати, именно в этом случае мы получим минимальную задержку и максимальную пропускную способность). В таком случае мы используем только 35 ГБ памяти из 40 доступных. Да, не всегда будет именно так, ведь можно поделить карту и на 10+10+20 ГБ, но и это лишь одна из опций, а не панацея.
Резюмируем: если вы не готовы к радикальным мерам, например к эксплуатации нескольких десятков А100 или H100, подключенных к материнской плате через NVSwitch, вопрос выбора железа для ML-задач станет весьма непростым. По крайней мере, одних только технических характеристик железа для принятия решения мало, так как важно обращать внимание на дополнительные возможности GPU в плане совместного использования и мониторинга эксплуатации. А идти эмпирическим путем, перебирая все возможные варианты, долго и дорого.

До недавнего времени мы бы не задумываясь выбрали уже проверенную А100. У этой карты GPU с 6 912 CUDA-ядрами, 40 ГБ видеопамяти, поддержкой Tensor Cores и технологий виртуализации. Отличное решение для облачных вычислений, рендеринга, обучения нейронных сетей и моделирования. Достаточно следить за подсказками в PyTorch, ориентироваться на свободный объем памяти и наслаждаться. Кроме того, две А100 можно объединить через NVLink, чтобы увеличить доступный к использованию ресурс.
Однако недавно мы получили доступ к новой видеокарте — А6000 Ada. При беглом взгляде на ее характеристики можно предположить, что это А100 на стероидах: в миниатюрном корпусе спрятаны 10 752 CUDA-ядер и 48 ГБ видеопамяти. При этом тепловыделение А6000 Ada составляет 300 Вт, тогда как у А100 — 400. А одно из ключевых различий карт в том, что A6000 Ada не поддерживает подключение через NVLink и шеринг через MIG — то есть это устройства разного уровня функциональности.
Мы видим, что по характеристикам А6000 Ada выглядит перспективнее, чем А100. Но она дороже, следовательно, возникает закономерный вопрос: стоит ли переплачивать? Чтобы найти ответ, мы взяли LLM в трех размерах и устроили тест-драйв нашим видеокартам.
Видеокарты мы тестируем в дообучении (файнтюнинге) большой языковой модели в разных размерах:
В идеале для обучения LLM на каждый миллиард ее параметров нужно около 24 ГБ видеопамяти — в RAM помещается не только модель, но и другие компоненты: градиенты, состояния оптимизатора, временные буферы и так далее. То есть используя только одну видеокарту, как в нашем эксперименте, мы не сможем завершить обучение очень многих LLM. Выходы есть разные, например объединить две карты через NVLink (или большее количество карт через NVSwitch), применить квантование [11] или другой способ уменьшения размера модели. И так как нас интересует честный тест каждой карты, объединять их и увеличивать вычислительные ресурсы мы не будем, а пойдем как раз по пути квантования (и столкнемся с нехваткой памяти, но об этом позже).
Итак, модели загружаются в квантованном виде с помощью библиотеки BitSandBytes [12]. Чтобы обучение в принципе было возможно, применяем подход LoRA [13], имплементированный в библиотеке peft [14]. Конфигурация LoRA остается неизменной (ранг множителей = 16) за исключением запуска обучения самой большой модели на А100 — мы снизили ранг разложенных матриц, чтобы сэкономить память и сделать возможной хотя бы загрузку в память GPU модели и батча.
Подготовим рабочее окружение для проведения тестов. Нам нужно установить довольно много пакетов, но это делается буквально двумя скриптами.
Сначала запускаем скрипт apt.sh. С его помощью:
#!/bin/bash
set -e
set -o xtrace
# Отключаем автообновления ядра
cat <<EOF > /etc/apt/apt.conf.d/51unattended-upgrades
Unattended-Upgrade::Package-Blacklist {
"nvidia-";
"linux-";
};
EOF
apt update
apt install -y
apt-transport-https
ca-certificates
curl
software-properties-common
pwgen
Скрипт apt.sh.
Далее запускаем скрипт nvidia-drivers-install.sh. С его помощью мы:
#!/bin/bash
set -e
set -o xtrace
# Опционально, возможно понадобится удаление устаревшего пакета
# linux-version list
# uname -r
# dpkg --list | grep -E -i --color 'linux-image|linux-headers'
# apt-get --purge autoremove
# apt --purge autoremove
# uname -a
# apt purge linux-image-5.4.0-166-generic
# dpkg --list | grep -E -i --color 'linux-image|linux-headers'
# Install Nvidia drivers for all kernels
for kernel in $(linux-version list); do
apt install -y "linux-headers-${kernel}"
done
apt install -y nvidia-driver-510 htop python3-pip git
pip install nvitop==1.3.1 jupyter==1.0.0 accelerate==0.21.0 peft==0.4.0 bitsandbytes==0.40.2 transformers==4.31.0 trl==0.4.7 scipy==1.11.3 tensorboard==2.15.1 evaluate==0.4.1 scikit-learn==1.3.2
Скрипт nvidia-drivers-install.sh.
absl-py==2.0.0
accelerate==0.21.0
aiohttp==3.8.6
aiosignal==1.3.1
anyio==4.0.0
argon2-cffi==23.1.0
argon2-cffi-bindings==21.2.0
arrow==1.3.0
asttokens==2.4.1
async-lru==2.0.4
async-timeout==4.0.3
attrs==23.1.0
Babel==2.13.1
beautifulsoup4==4.12.2
bitsandbytes==0.40.2
bleach==6.1.0
blinker==1.4
cachetools==5.3.2
certifi==2023.7.22
cffi==1.16.0
charset-normalizer==3.3.2
comm==0.2.0
command-not-found==0.3
contourpy==1.2.0
cryptography==3.4.8
cycler==0.12.1
datasets==2.14.7
dbus-python==1.2.18
debugpy==1.8.0
decorator==5.1.1
defusedxml==0.7.1
dill==0.3.7
distro==1.7.0
distro-info==1.1+ubuntu0.1
evaluate==0.4.1
exceptiongroup==1.1.3
executing==2.0.1
fastjsonschema==2.19.0
filelock==3.13.1
fonttools==4.45.0
fqdn==1.5.1
frozenlist==1.4.0
fsspec==2023.10.0
google-auth==2.23.4
google-auth-oauthlib==1.1.0
grpcio==1.59.2
httplib2==0.20.2
huggingface-hub==0.19.2
idna==3.4
importlib-metadata==4.6.4
ipykernel==6.26.0
ipython==8.17.2
ipywidgets==8.1.1
isoduration==20.11.0
jedi==0.19.1
jeepney==0.7.1
Jinja2==3.1.2
joblib==1.3.2
json5==0.9.14
jsonpointer==2.4
jsonschema==4.19.2
jsonschema-specifications==2023.11.1
jupyter==1.0.0
jupyter-console==6.6.3
jupyter-events==0.9.0
jupyter-lsp==2.2.0
jupyter_client==8.6.0
jupyter_core==5.5.0
jupyter_server==2.10.0
jupyter_server_terminals==0.4.4
jupyterlab==4.0.8
jupyterlab-pygments==0.2.2
jupyterlab-widgets==3.0.9
jupyterlab_server==2.25.1
keyring==23.5.0
kiwisolver==1.4.5
language-selector==0.1
launchpadlib==1.10.16
lazr.restfulclient==0.14.4
lazr.uri==1.0.6
Markdown==3.5.1
MarkupSafe==2.1.3
matplotlib==3.8.2
matplotlib-inline==0.1.6
mistune==3.0.2
more-itertools==8.10.0
mpmath==1.3.0
multidict==6.0.4
multiprocess==0.70.15
nbclient==0.9.0
nbconvert==7.11.0
nbformat==5.9.2
nest-asyncio==1.5.8
netifaces==0.11.0
networkx==3.2.1
notebook==7.0.6
notebook_shim==0.2.3
numpy==1.26.2
nvidia-cublas-cu12==12.1.3.1
nvidia-cuda-cupti-cu12==12.1.105
nvidia-cuda-nvrtc-cu12==12.1.105
nvidia-cuda-runtime-cu12==12.1.105
nvidia-cudnn-cu12==8.9.2.26
nvidia-cufft-cu12==11.0.2.54
nvidia-curand-cu12==10.3.2.106
nvidia-cusolver-cu12==11.4.5.107
nvidia-cusparse-cu12==12.1.0.106
nvidia-ml-py==12.535.133
nvidia-nccl-cu12==2.18.1
nvidia-nvjitlink-cu12==12.3.52
nvidia-nvtx-cu12==12.1.105
nvitop==1.3.1
oauthlib==3.2.0
overrides==7.4.0
packaging==23.2
pandas==2.1.3
pandocfilters==1.5.0
parso==0.8.3
peft==0.4.0
pexpect==4.8.0
Pillow==10.1.0
platformdirs==4.0.0
prometheus-client==0.18.0
prompt-toolkit==3.0.41
protobuf==4.23.4
psutil==5.9.6
ptyprocess==0.7.0
pure-eval==0.2.2
pyarrow==14.0.1
pyarrow-hotfix==0.5
pyasn1==0.5.0
pyasn1-modules==0.3.0
pycparser==2.21
Pygments==2.16.1
PyGObject==3.42.1
PyJWT==2.3.0
pymacaroons==0.13.0
PyNaCl==1.5.0
pyparsing==2.4.7
python-apt==2.4.0+ubuntu2
python-dateutil==2.8.2
python-json-logger==2.0.7
pytz==2023.3.post1
PyYAML==5.4.1
pyzmq==25.1.1
qtconsole==5.5.0
QtPy==2.4.1
referencing==0.31.0
regex==2023.10.3
requests==2.31.0
requests-oauthlib==1.3.1
responses==0.18.0
rfc3339-validator==0.1.4
rfc3986-validator==0.1.1
rpds-py==0.12.0
rsa==4.9
safetensors==0.4.0
scikit-learn==1.3.2
scipy==1.11.3
screen-resolution-extra==0.0.0
SecretStorage==3.3.1
Send2Trash==1.8.2
six==1.16.0
sklearn==0.0.post11
sniffio==1.3.0
soupsieve==2.5
ssh-import-id==5.11
stack-data==0.6.3
sympy==1.12
tensorboard==2.15.1
tensorboard-data-server==0.7.2
termcolor==2.3.0
terminado==0.18.0
threadpoolctl==3.2.0
tinycss2==1.2.1
tokenizers==0.13.3
tomli==2.0.1
torch==2.1.0
tornado==6.3.3
tqdm==4.66.1
traitlets==5.13.0
transformers==4.31.0
triton==2.1.0
trl==0.4.7
types-python-dateutil==2.8.19.14
typing_extensions==4.8.0
tzdata==2023.3
ubuntu-advantage-tools==8001
ufw==0.36.1
unattended-upgrades==0.1
uri-template==1.3.0
urllib3==2.1.0
wadllib==1.3.6
wcwidth==0.2.10
webcolors==1.13
webencodings==0.5.1
websocket-client==1.6.4
Werkzeug==3.0.1
widgetsnbextension==4.0.9
xkit==0.0.0
xxhash==3.4.1
yarl==1.9.2
zipp==1.0.0
Теперь у нас есть виртуальный сервер с драйверами Nvidia, набор инструментов, пайтоновское окружение с библиотеками, необходимыми для запуска LLM.
Для тестирования использовалась тренировочная часть набора данных mlabonne/guanaco-llama2-1k [15] с 1 000 текстов объемом от 58 до 11 400 символов.
Распределение размеров текстов показано на графике:

Разумеется, обе видеокарты мы тестировали с использованием одного и того же скрипта. Результаты обучения каждой модели и результаты генерации текста мы свели в таблицы.
Ошибка Out of Memory (CUDA OOM) говорит о том, что видеокарте не хватило памяти для выполнения задачи. Здесь мы не будем подробно детализировать загрузку RAM, так как цель теста — сравнить возможности видеокарт, а не найти оптимальный способ обучения LLM. Если вам интересно узнать больше о том, как расходуется память GPU при обучении больших языковых моделей, рекомендуем эту статью [16].
Читая все таблицы ниже, стоит учитывать, что процентовка памяти и использования ядер GPU берутся из утилиты nvitop.

Как мы видим, при работе с небольшими батчами обе видеокарты успешно справляются с нагрузкой. Кстати, сравнение наглядно показывает, почему нельзя опираться только на технические характеристики при выборе видеокарты — обладающая меньшим объемом памяти и количеством ядер А100 справилась с одним из тестов куда лучше, чем более мощная А6000 Ada, хотя и задействовала свой вычислительный ресурс на 97,7%.
Впрочем, при отгрузке батчей по 25 и 27 текстов А100 уже ушла в CUDA OOM, тогда как А6000 Ada продолжила работать, и мы завершили обучение модели. Поэтому однозначный вывод сформулировать не получится. Если карта стабильно будет отдаваться целиком под одну задачу, а батчи датасетов будут содержать не более 20 текстов, стоит присмотреться к А100. Однако в пользу А6000 Ada говорит тот факт, что больший объем памяти позволит тестировать больше вариантов размера батча, которые могут влиять на сходимость обучения.

Как и в случае с предыдущей моделью, до момента исчерпания доступной памяти А100 показывает очень хорошие результаты, опережая в скорости обучении А6000 Ada. Мы приблизились к пороговому значению RAM на А100 (96,3% доступной памяти) только при отгрузке батчей по 15 текстов.
Попытка отгружать батчи по 25 и более текстов отправила обеих испытуемых в CUDA OOM. Очевидно, при использовании только одной видеокарты, будь то А100 или А6000 Ada, не хватит ее памяти. Да, причина все та же: слишком тяжелые батчи вместе с большой моделью и необходимыми для обучения компонентами не помещаются ни в 40, ни в 48 ГБ.

Этот тест мы провели скорее из любопытства, чтобы узнать, справится ли одна А6000 Ada с обучением модели с 70 миллиардами параметров. Вывод: справится, но только если мы будем отдавать ей по одному тексту за раз. Попытка отдавать по два текста в батче ведет к исчерпанию RAM.
От А100 мы не ждали многого. Здесь одного квантования мало — чтобы сэкономить немного памяти и просто загрузить модель, пришлось понизить ранг разложенных матриц до двух и восьми (тот самый случай изменения конфигурации LoRA, о котором шла речь в начале) и также отгружать по одному тексту. Однако с практической точки зрения в этом нет смысла — целесообразнее взять либо одну А6000 Ada, либо две А100 и NVLink.

С точки зрения генерации текста А6000 Ada оказалась лидером в каждом из тестов.
Ожидаемо, при увеличении количества токенов становится все более заметной разница в скорости вывода данных. Для наглядности мы преобразовали табличные значения в графики. Независимо от размера обучаемой модели и количества токенов А6000 Ada предсказуемо оказывается быстрее.



Как мы уже отметили, обучение самой большой модели, meta-llama/Llama-2-70b-chat-hf, на А100 удалось запустить только при изменении конфигурации LoRA. Но даже в этом случае оно не было завершено. Вместе с тем, это ни в коем случае не говорит о непригодности А100 для генерации текста большими языковыми моделями. В рамках тестирования мы рассмотрели не все варианты. Например, не стали объединять две А100 через NVLink.
Напрашивается очевидный вывод: А6000 Ada обладает более высокими характеристиками, чем А100. Для ресурсоемких задач используйте ее. Но ради такого вывода не стоило и проводить тест.
Как видите, у нас нет однозначного вывода о том, какая карта лучше подойдет для работы с LLM. Мы провели тестирование, чтобы увидеть реальные возможности А100 в сравнении с А6000 Ada в одинаковых условиях. Выбор той или иной модели GPU стоит делать исходя из количества и сложности реальных задач, возможностей шеринга ресурсов и, конечно, доступных финансов.
Впрочем, мы планируем запустить еще несколько тестов различных конфигураций GPU для решения ML-задач. В комментариях предлагайте кандидатов для тестирования.
Возможно, эти тексты тоже вас заинтересуют:
→ AMD решила пойти по пути Nvidia и выпустила урезанный ИИ-чип. Но что-то пошло не так [17]
→ Питомцы и их айтишники: 6 историй ко дню кошек [18]
→ Что такое Data diode и зачем он нужен? [19]
Автор: Александр Шилов
Источник [20]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/selectel/390486
Ссылки в тексте:
[1] видеокарты Ada с 48 ГБ RAM: https://selectel.ru/services/dedicated/gpu/?utm_source=habr.com&utm_medium=referral&utm_campaign=dedicated_article_a100a6000_120324_content
[2] Почему нельзя просто взять самые мощные GPU: #1
[3] За что мы любим А100 и А6000 Ada: #2
[4] Тест — два GPU решают одни и те же задачи в одинаковых условиях: #3
[5] Результаты теста: #4
[6] Заключение: #5
[7] Шеринг GPU: https://habr.com/ru/companies/selectel/articles/748544/
[8] meta-llama/Llama-2-7b-chat-hf: https://huggingface.co/meta-llama/Llama-2-7b-chat-hf
[9] meta-llama/Llama-2-13b-chat-hf: https://huggingface.co/meta-llama/Llama-2-13b-chat-hf
[10] meta-llama/Llama-2-70b-chat-hf: https://huggingface.co/meta-llama/Llama-2-70b-chat-hf
[11] квантование: https://huggingface.co/docs/optimum/concept_guides/quantization
[12] BitSandBytes: https://github.com/TimDettmers/bitsandbytes
[13] LoRA: https://huggingface.co/docs/peft/main/en/conceptual_guides/lora
[14] peft: https://huggingface.co/docs/peft/index
[15] mlabonne/guanaco-llama2-1k: https://huggingface.co/datasets/mlabonne/guanaco-llama2-1k
[16] статью: https://huggingface.co/docs/transformers/model_memory_anatomy
[17] AMD решила пойти по пути Nvidia и выпустила урезанный ИИ-чип. Но что-то пошло не так: https://habr.com/ru/company/selectel/blog/798839
[18] Питомцы и их айтишники: 6 историй ко дню кошек: https://habr.com/ru/company/selectel/blog/797253
[19] Что такое Data diode и зачем он нужен?: https://habr.com/ru/company/selectel/blog/799403
[20] Источник: https://habr.com/ru/companies/selectel/articles/799685/?utm_source=habrahabr&utm_medium=rss&utm_campaign=799685
Нажмите здесь для печати.