По работе я привык, что если какая-то нейронка не влезает на GPU-сервер, то первое моё действие — проверить, нет ли модели с квантизацией побольше, либо запросить ещё больше VRAM. Однако не все работают на облачных провайдерах, кто-то у нас ещё и заказывает услуги. А тарифы на GPU-серверы порой заставляют серьёзно задуматься о том, так ли они нужны, особенно когда нет потребности в нейронках на сотни миллиардов параметров. Что, если попробовать делать всё на обычном VPS-тарифе с бюджетом в 5000 рублей в месяц? Да и зачем ограничиваться одними лишь нейронками?
Начнём с настройки тарифа на сайте RUVDS в рамках максимальной экономии. Для начала берём NVMe SSD — да, дороже, но даёт простор для экспериментов с расширением SWAP. По скорости, конечно, и близко не как DDR3, но всяко быстрее SWAP на SATA-SSD и тем более HDD. Плюс, в целом скорость работы с файлами и системой будет выше, да и больше стартовых 30 ГБ нам не потребуется. Самые тяжёлые модели, с которыми мы будем работать, будут в пределах 12 миллиардов параметров. К тому же, с квантизацией до 4 бит, так что их вес в среднем вряд ли будет превышать 10 гигабайт.
Ядер берём 4, хотя хотелось бы и побольше. Впрочем, всего хотелось бы побольше, но тогда уже не поместимся в 5000 рублей в месяц. А вот оперативки не жалеем и берём на весь оставшийся бюджет 12 гигабайт. ОС берём, конечно, Linux. За неимением возможности поставить свой ISO-шник выбираю CentOS — RPM-дистрибутивы мне почему-то нравятся больше, чем Debian и его отпрыск Ubuntu.
Теперь ждём пару минут, пока сервер создастся и на почту упадёт логин с паролем для доступа через SSH.
Чудесно, сервер запущен. Дело за малым — подключимся к системе в консоли:
ssh root@IP-Адрес
И настроим немного систему:
dnf update
dnf install epel-release # Включаем репозиторий с дополнительными пакетами
dnf install tar nano htop ncdu
Так как работать мы будем в этом посте с нейронками, нам потребуется инференс для их запуска. Воспользуемся для этого Ollama:
curl -fsSL https://ollama.com/install.sh | sh
И интереса ради проверим, сколько у нас осталось свободного места на диске через ncdu
.
Как можно заметить, Ollama уже «отъела» 4.2 ГБ из доступных нам 30, хотя ncdu
некорректно показывает доступное дисковое пространство, указывая на 128 ТБ. Что, скорее всего, является фактическим объёмом дискового накопителя на хостовой системе, где крутится наша скромная VPS-ка.
Если же мы посмотрим доступное нам дисковое пространство с помощью duf
, который для начала надо скачать и установить:
curl -LO https://github.com/muesli/duf/releases/download/v0.6.2/duf_0.6.2_linux_amd64.rpm
dnf install duf_0.6.2_linux_amd64.rpm
То мы увидим уже реальное положение дел.
Совет на будущее: ollama rm "Название Модели"
нормально их не удаляет; скачанные модели остаются по расположению /root/.ollama/models/blobs
, и удалять их придётся вручную через rm
. Что не так важно на локальной системе с терабайтами дискового пространства, но может устроить неприятный сюрприз в рамках ограниченных 30 ГБ.
Для начала протестируем на чём-нибудь полегче — запустим вышедшую этой осенью LLaMA 3.2 на 3 миллиарда параметров без квантизации.
Введём в консоли:
ollama run llama3.2:3b-text-fp16
И вот после недолгого ожидания модель на 6.4 ГБ скачалась.
На мониторинге в htop
наблюдаем следующую картину во время инференса введённого запроса.
Полученные результаты, мягко говоря, не впечатляют. Если себя так показывает не квантизированная 3B, то смысла тестировать квантизированные версии и 1B достаточно мало. Разве что есть подозрения, что по умолчанию базовая LLaMA крайне сырая, и чудесами дообучения и донастройки из неё можно сделать конфетку даже в квантизированном виде. По крайней мере, мой активный опыт по работе до этого с генеративными нейронками, производными от Stable Diffusion, заставляет меня верить в подобный сценарий. Спустя 2 года с выхода Stable Diffusion 1.5 она стабильно продолжает выдавать результат лучше и быстрее, но в комбинации с дообучением, LoRA, апскейлерами, заточенными под конкретный стиль, и прочими модификациями.
К слову о Stable Diffusion. Пусть цель цикла этих статей заключается в том, чтобы попробовать с имеющимися ресурсами сделать приближённые к реальности демо-прототипы реальных коммерческих сервисов и применений как нейронок, так и всего остального, что потянет наша VPS. Это не значит, что нельзя это делать с комфортом, а исключительно минималистично в консоли. Та же Stable Diffusion стала доступной для ряда не сильно технически подкованных пользователей благодаря разнообразию и простоте её графических интерфейсов — от легендарного A1111 до комбайна, я бы даже сказал блендера, ComfyUI. Так что для дальнейшей работы с нейронками я предлагаю установить на локальной Linux-машине веб-интерфейсы для взаимодействия с Ollama — Open Web UI.
На моей домашней системе через pip
в venv
ставиться он не захотел, ругаясь на то, что у меня слишком свежая версия Python. Ишь, какой важный! Делать ему venv
с персональной для него версией Python я, конечно же, не буду.
Так что ставить его будем через Docker. На моей локальной системе он уже давно стоит, но если у вас также ОС на основе Arch Linux и он не установлен, то делается это следующим образом:
sudo pacman -S docker
sudo systemctl start docker.service
sudo systemctl enable docker.service
И чтобы каждый раз не писать sudo
вместе с docker
:
sudo usermod -aG docker $USER
newgrp docker
Теперь установим Open Web UI с помощью следующей команды:
docker run -d --network=host -v open-webui:/app/backend/data -e OLLAMA_BASE_URL=http://127.0.0.1:11434 --name open-webui --restart always ghcr.io/open-webui/open-webui:main
Проверим на docker ps
, что всё запустилось, а также через docker logs open-web-ui
. Там мы должны увидеть соответствующие логи.
Переходим по ссылке http://0.0.0.0:8080
, и нас наконец-то встречает заветный веб-интерфейс. Жмём в нём на стрелочку Get started.
И далее, в лучших заветах разработчиков WordPress, с какого-то перепугу в self-host сервисе видим преграду в виде полей, требующих от нас ввода имени, почты и пароля. Как утверждают разработчики Open Web UI, это нужно для удобства, хранится всё локально и при желании отключается. Но одобрять такое я всё равно не буду, однако продолжим.
После этого открываем ещё одну консоль и подключаемся по SSH с пробросом локальных портов:
ssh -L 11434:127.0.0.1:11434 root@IP_Адрес
Эти же порты использует Ollama, когда мы запускаем её сервер на команду ollama serve
.
Далее возвращаемся обратно в Open Web UI, нажимаем на профиль, открываем Настройки администратора и в Ollama API вводим тот же адрес, для которого мы пробрасывали порты.
Нажимаем на кнопку синхронизации, и если высветилось зелёное «Соединение с сервером проверено», то значит всё прошло успешно. И теперь в выборе модели наконец-то должна быть доступна наша LLaMA 3.2B, которую мы, впрочем, скоро удалим.
Дадим ей второй шанс и посмотрим, будет ли разниться обработка запроса через консоль и через Open Web UI.
И… впечатления не очень, словно я веду разговор с генератором рандомных фраз, который очень, очень косвенно отталкивается от запрошенного. Я не смог получить от 3B ни хотя бы просто Hello World, ни банально приветствие.
Но зато 3B по необъяснимой причине очень любит городить гигантские простыни текста, и пример, на который она потратила почти 4 минуты, даже не самый длинный. Что интересно, ведёт она себя также и просто в консоли, без Web UI.
Ну чтож, перейдём тогда к чему-то более серьёзному — к мультимодальной LLaMA 3.2 на 11 миллиардов параметров. Мультимодальность заключается в том, что к ней прикрутили обработку визуальной информации. Что, впрочем, делало и сообщество в многочисленных форках LLaMA до выхода версии 3.2. И вот теперь посмотрим, насколько она будет хороша на практике, надеюсь, хотя бы чуть меньшим недоразумением, чем 3B. Однако, в силу наших ограничений, 11B мы уже возьмём квантизированную.
И перед этим не забудем удалить 3B:
rm -rf /root/.ollama/models/blobs/*
Запускаем:
ollama run llama3.2-vision:11b-instruct-q4_K_M
Пусть и квантизированной до 4-битной точности, но мультимодальной и 11-миллиардной LLaMA на удивление потребовалось даже меньше места на диске, чем не квантизированной версии.
А теперь попробуем её запустить ииии… ничего. Памяти слишком мало. Точнее, её было слишком мало, как можно было заметить ранее в htop
, у сервера 13 ГБ SWAP. Из них 1 ГБ — это физический SWAP по умолчанию, выделенный при создании VPS, а остальные 12 ГБ — заслуга ZRAM. Это модуль ядра Linux, позволяющий создавать виртуальный SWAP в самой оперативной памяти за счёт сжатия и разжатия информации прямо в ней, с платой в виде использования ресурсов процессора на это, но куда большей скоростью, чем SWAP, размещаемый даже на самом быстром NVMe-накопителе.
Подключить ZRAM достаточно просто. Можно либо с помощью дополнительного пакета zram-generator
, либо полностью вручную, что я и сделал.
Создаём файл для загрузки модуля ZRAM в ядре:
sudo nano /etc/modules-load.d/zram.conf
И вставляем туда:
zram
Файл для настройки модуля ZRAM:
sudo nano /etc/modprobe.d/zram.conf
Содержимое:
options zram num_devices=1
Задаём размер раздела:
sudo nano /etc/udev/rules.d/99-zram.rules
Содержимое:
KERNEL=="zram0", ATTR{disksize}="12G", TAG+="systemd"
Создаём файл для процесса в systemd:
nano /etc/systemd/system/zram.service
Содержимое:
[Unit]
Description=Swap with zram
After=multi-user.target
[Service]
Type=oneshot
RemainAfterExit=true
ExecStartPre=/sbin/mkswap /dev/zram0
ExecStart=/sbin/swapon /dev/zram0
ExecStop=/sbin/swapoff /dev/zram0
[Install]
WantedBy=multi-user.target
Включаем процесс:
systemctl enable zram
Далее перезапускаем сервер.
Проверяем командой zramctl
— у вас должен быть аналогичный вывод в консоли.
Ну что ж, поехали! Смотрим для начала в htop
— результаты удивляющие, модель даже не залезла в SWAP, ей хватило всего 8.5 ГБ. Но как же так? Я же только недавно говорил, что памяти не хватило и я нагородил всю эту историю с ZRAM. Всё так и было, но это осталось за кадром, когда я экспериментировал с иной конфигурацией VPS, где было 90 ГБ NVMe, так как хотел сразу установить ряд нейронок, которые собирался протестировать в этом и последующих постах. И не хотелось их удалять и устанавливать по новой, но в итоге всё-таки было принято решение в рамках бюджета 5000 рублей на месяц пожертвовать накопителем и набрать ещё +4 ГБ оперативки вместо изначальных 8 ГБ + 8 ГБ ZRAM.
И 11B Vision порадовала. Ответ, конечно, не идеальный, но на несколько порядков лучше бреда, который несла 3B без квантизации. Да, оперативной памяти ей надо побольше, но в рамках 12 ГБ, из которых она воспользовалась всего 8.5 ГБ, работала довольно шустро. Плюсом ко всему ZRAM не отъедал процессорное время, которое у нас за неимением GPU используется для инференса.
К слову, что интересно, в интернете нигде нет внятных тестов, сколько именно и в какой пропорции ZRAM отъедает от процессора. Хотя модуль по умолчанию включён во многих дистрибутивах, а также по умолчанию работает на всех устройствах с Android, но, видимо, многомиллиардные мегакорпорации в который раз решили забить на бенчмарки и жить по принципу «Ладно! И так сойдёт!».
А теперь проверим, сможет ли с нашими ресурсами 11B Vision справиться с распознанием изображения. В оставшихся за кадром тестах на 8 ГБ RAM + 8 ГБ ZRAM переварить это она, к сожалению, не смогла.
Тут же наблюдается, что реальная RAM полностью занята и пришлось одолжить ей 4 ГБ у ZRAM, то есть будь тут 16 ГБ реальной RAM, в SWAP система, скорее всего, бы не полезла.
С поправкой на то, что часть оперативной памяти нужна ещё и самой ОС, для сравнения при запуске на GPU от NVIDIA квантованная до 4-битной точности LLaMA у меня занимала всего лишь 9 ГБ VRAM во время инференса.
Что же получилось в этот раз? Эм, ну… как бы модель и ответила, и даже в этот раз не упала, пытаясь всё это дело переварить, но описание вообще никак не связано с данной картинкой. Что крайне странно, так как во время инференса на GPU модель достаточно точно определила текст, обстановку, ещё и с японского на русский перевела. Ну что ж, такие результаты всё равно говорят о непригодности использования в рамках демо, которое хотя бы отдалённо было приближено к чему-то коммерческому, даже в самом зачатке. Девять с половиной минут на инференс изображения — это за гранью добра и зла. Хотя результат чисто текстового инференса получился достаточно неплохим.
Ну что ж, попробуем что-нибудь ещё. Не будем отходить слишком далеко от семейства LLaMA и проверим на эффективность один из популярных мультимодальных форков — LLaVA.
Запускаем:
ollama run llava:7b
Пока что радует достаточно скромное потребление оперативной памяти во время инференса текстового запроса — всего лишь чуть больше 5 ГБ.
И ответ наконец-то тоже порадовал — после стольких неудачных попыток языковая модель передала нормальный привет, как и запрашивалось! Ура! Надеюсь, она также порадует и в распознавании изображения… Стоп, почему ты продолжаешь генерировать… Ладно, видимо штраф за частоту для этой модели нужно будет ставить выше, чем по умолчанию. Впрочем, с её скоростью генерации и точностью такое пока прощается.
При запросе описать изображение изменений на мониторинге в htop
пока что подозрительно мало, потребление оперативной памяти возросло совсем незначительно.
Хм, результат кривоватый, но уже неплохо. Интересно теперь перескочить сразу на 13B и посмотреть, что она покажет. Хотя почти 2 минуты на описание картинки — это тоже, ну такое. Впрочем, вокруг этого уже что-то можно придумать.
Но дабы было интереснее и полезнее в рамках условий теста, запустим совсем «кроху», весящую скромные 5 ГБ, квантизированную до 2-битной точности:
ollama run llava:13b-v1.6-vicuna-q2_K
Перейдём с места в карьер и посмотрим на мониторинг при попытке анализа изображения — заметно, что больше потребляется оперативной памяти, уже 7 ГБ.
А вот итоговый результат оказался разочаровывающим, так ещё и по времени занял больше, чем у модели на 7 миллиардов параметров. Видимо, в данном случае квантизация до 2-битной точности всё-таки была перебором. Но не проверишь — не узнаешь, особенно с учётом всего многообразия нейронок в свободном и закрытом доступе, а также их форков, альтернативных фронтендов для них и т. д. В бенчмарках по типу AI-арены и то не всех, и не все форки, не все виды квантизации, так что полезную конкретно для вашего случая модель крайне легко упустить, если вы вообще обратите на неё внимание.
Результаты получились неоднозначные. Может показаться, что я разочарован, однако это не так, а ровно наоборот. Хоть я ещё когда только появилась llama.cpp
видел, как её запускают и на смартфонах, и на Raspberry Pi, всё равно удивляет, что на обычной VPS-ке могут крутиться даже свежие, мультимодальные нейронки, пусть и с переменным успехом. Порадовал ещё и ZRAM, доказав свою полезность в таких экстремально ограниченных условиях.
Что дальше? Вместо того чтобы растягивать эту серию постов на ещё с десяток обзоров, как нейронки разных семейств моделей и разных квантизаций отвечают на 1–2 предложения и пытаются переваривать изображения, подход будет иной. Вне зависимости от того, как хороша или плоха нейронка, буду пытаться придумать, как в рамках условий VPS обернуть её в какое-то полноценное приложение/сервис/внутрикорпоративную службу и на основе этого уже делать выводы. Впрочем, постов выйдет в любом случае немало, так как в процессе создания этой статьи наткнулся на ряд забытых за ненадобностью, но бывших интересных мне проектов, а также нашёл ряд новых.
Буду рад, если поделитесь в комментариях советами и пожеланиями, что стоило бы изменить в статье и что затронуть в новых. Отдельно также будет интересно обсудить, как можно было бы точно замерить и сформулировать бенчмарк о влиянии ZRAM на загрузку процессора.
Всем спасибо за прочтение, до встречи!
Telegram-канал со скидками, розыгрышами призов и новостями IT 💻