Как стать автором
Обновить

Как запустить локально LLM, если ее веса не помещаются в [видео]память

Уровень сложностиСредний
Время на прочтение4 мин
Количество просмотров12K

Некоторые люди предпочитают пользоваться не только облачными сервисами, но и запускать LLM у себя дома. Например, так можно запустить дообученные модели без цензуры, или не посылать в облако свои личные документы. А то и запускать бесчеловечные эксперименты над LLM так, чтобы superintelligence/skynet потом это не припомнил.



Есть много моделей, оптимизированых для быстрой работы на устройствах с небольшой памятью. Но, к сожалению, веса самых продвинутых моделей, которые играют в одной лиге с лучшими онлайн моделями, занимают сотни гигабайт. Например, 8-битные веса Deepseek R1-671B занимают 700 гигабайт, квантованые q4 — 350 гигов. Можно квантовать и в 1 бит, размер тогда будет около 90 гигов, но такая модель почти бесполезна. Еще есть много качественных finetunes на основе Mistral-Large-instruct-130B, Qwen2.5-72B, llama3.3-70B, веса которых также не помещаются в память старших моделей видеокарт.


Если веса модели не помещаются в ОЗУ (или, еще лучше, в видеопамять), то пользоваться моделью практически невозможно. При вычислении каждого токена все веса придется заново читать с диска, и минимальную задержку легко посчитать, просто разделив размер модели на скорость чтения. Но даже если у Вас дома совершенно случайно не завалялись парочка Nvidia B100 или Mac Studio Ultra/512GB RAM, все еще есть возможность запустить большую LLM.


Сначала разберемся, от чего зависит производительность инференса LLM (в токенах в секунду). Я уже немного писал об этом на Хабре, так что эксперты могут пропустить пару следующих абзацев.


Операции, которые занимают большую часть времени инференса при использовании KV-кэша — скалярное произведение матрицы на вектор при вычислении внимания, и умножение матриц в линейной части слоя. При вычислении внимания один вектор запроса взаимодействует с кэшированной матрицей ключей и значений. Эта операция крайне зависима от пропускной способности памяти, так как количество вычислений на загруженный байт данных весов минимально. В линейной стадии (умножение матриц) пропускная способность памяти менее критична, но всё равно загрузка частей матриц в кэш процессора/видеокарты занимает значительное время.


Каждый слой декодера трансформера состоит из нескольких стадий: сначала вычисляется внимание через KV-кэш, затем нормализация, после чего выполняется линейная проекция через полносвязную сеть. Внимание и линейная проекция используют веса слоя, причём для каждого из нескольких десятков одинаковых слоев они свои. Эти веса занимают много памяти, и их загрузка занимает основное время обработки. Передача промежуточных данных между слоями — гораздо менее объёмная (примерно в 10 000 раз меньше по размеру, чем веса) и практически не влияет на производительность.


Благодаря этому архитектурному свойству можно распределить вычисление слоев между несколькими машинами в локальной сети. Каждый компьютер хранит веса нескольких слоёв и выполняет вычисления локально. Передача данных последовательно между машинами минимальна по сравнению с загрузкой весов и не становится узким местом. Основное ограничение производительности остаётся на стороне памяти, а не сети, поэтому при быстрой локальной сети распределение слоёв по разным компьютерам практически не снижает общую скорость инференса. Можно работать даже с Wifi, хотя проводная сеть быстрее и надежнее.


Теперь вернемся к практике. Есть как минимум два open source LLM проекта, которые позволяют достаточно легко распределить (к сожалению, не распараллелить, так как слои вычисляются последовательно) инференс в локальной сети. Это всем известный llama.cpp и менее популярный exo.


В llama.cpp еще два года назад добавили распределенный инференс поверх MPI, но процесс настройки был сложноват, и пользователям не нравился. Поэтому ту реализацию заменили на новую, поверх RPC. Я бы не сказал, что все работает на 100% стабильно, и последнее время в этой подсистеме было много изменений. Поэтому во-первых, надо на каждом хосте иметь один и тот же билд llama.cpp, и иметь в виду, что по умолчанию поддержка распределенного инференса не поддерживается, так что при компиляции надо добавить -DGGML_RPC. После этого на каждом хосте, кроме главного, надо запустить rpc-server -H $IP_ADDR -c -m $USE_RAM_MB, а на главном запустить инференс как обычно llama-cli или llama-server [обычные опции и -m $MODEL_PATH] --rpc ${IP1}:50052, ${IP2}:50052, ${IP3}:50052, и т.д. Еще, если хост работает с Apple Silicon Mx CPU/GPU, полезно отдать побольше общей памяти GPU командой "sudo sysctl iogpu.wired_limit_mb=$GPU_GPU_RAM_MB". Все обертки над llama.cpp/llama-server будут работать совершенно прозрачно.


Llama.cpp включает много разных реализаций инференса — на Apple, Nvidia, AMD GPU, на x86 с разными векторизациями и ARM CPU. При распределенной обработке можно иметь хосты с разной архитектурой. На моей картинке выше — главный MacBook Pro с 128 ГБ RAM, два MacBook Air с 24 ГБ, и один Mac Mini с 24 ГБ — всего около 200 ГБ доступной видеопамяти Apple GPUs. (Раньше у меня была скидка на Apple-железо, сейчас бы, возможно, взял бы старые Xeon с кучей RAM или DGX Spark.) Работает квантованный Deepseek R1 через llama.cpp по гигабитному Ethernet. Накладные расходы ~20% относительно Мака с 192 ГБ RAM, который я тестировал в прошлом году. Проблема только в пассивном охлаждении MBA, из-за чего они троттлят, но тут уж ничего не поделаешь.


С exo всё ещё проще: запускаете exo на каждом хосте в LAN — и они магическим образом (через Zeroconf?) соединяются. Работает само. Минус в том, что у exo нет столько разных оптимизированных бэкендов, как у llama.cpp, и возможны проблемы с webui или SillyTavern. Вручную прописать IP — не такой большой труд, поэтому я лично предпочитаю llama.cpp.


Если модель — MoE (Mixture of Experts), то это добавляет немного сложности в распределение слоев, но выигрыш в пропускной способности не уменьшается по сравнению с одной машиной.


Итого — чудес не бывает, и все равно нужно столько [видео]памяти, сколько занимают веса модели (и еще чуть-чуть, в зависимости от величины контекста). Но эта память не обязана быть в одной машине. Инференс больших моделей можно распределить (но, к сожалению, невозможно распараллелить) по локалке без серьёзных потерь производительности. Так как цена видеопамяти в одной машине растет нелинейно, а цена распределенной видеопамяти растет линейно, то для личного использования распределенная система гораздо выгоднее.

Теги:
Хабы:
+27
Комментарии27

Публикации

Ближайшие события