AI и DIY,  Статьи

Запуск llama.cpp в распределенном режиме по локальной сети

Запуск llama.cpp в распределенном режиме по локальной сети позволяет без лишних затрат увеличить доступный для запуска AI-моделей объем памяти, объединив вычислительные мощности (CPU и/или GPU) нескольких компьютеров для инференса одной модели.

При инференсе можно будет задействовать видеокарты разных архитектур и производителей, на компьютерах-воркерах могут быть установлены любые операционные системы.

Преимуществом такого способа является сохранение полной пропускной способности шины PCI-E, которая урезается до минимума в multi-GPU системах.

Для распределенных вычислений на воркерах сети запускается ggml-rpc-server, создаваемый по умолчанию при сборке llama.cpp (подробнее в статьях Компиляция и работа с llama.cpp для GPU Nvidia в linux и Запуск AI-моделей в llama.cpp на AMD Radeon RX470 8GB с Vulkan в gentoo).

Во время работы главный компьютер (хост) будет загружать модель на другие PC по сети и координировать их совместную работу при распределенных вычислениях.

Скорость генерации при использовании распределенной сети сильно зависит от ее пропускной способности. Поэтому желательно обеспечить хотя бы гигабитное LAN-соединение, на 100-мегабитном или Wi-Fi быстродействие будет очень низким из-за постоянной пересылки весов между слоями.В случае крайней необходимости можно задействовать и Wi-Fi, но в этом случае придется смириться с очень медленной работой кластера.

Для настройки и запуска llama.cpp в распределенном режиме нужно:

  • подготовить сеть и окружение, назначив статические (постоянные) IP-адреса всем участникам в настройках роутера (DHCP Reservation):

!!!ВАЖНО!!!

Для работы распределенного кластера нужно использовать одинаковые версии llama.cpp. Поэтому нужно производить сборку этого окружения из одного и того же коммита Git.

Примеры команд для сборки llama.cpp в разных конфигурациях:

Сборка llama.cpp на i5-3570K с видеокартами Nvidia Pascal с указанием флагов native и O3:

sudo emerge --ask dev-util/nvidia-cuda-toolkit --autounmask

git clone https://github.com/ggml-org/llama.cpp && cd llama.cpp
cmake -B build -DCMAKE_BUILD_TYPE=Release \
-DGGML_CUDA=ON -DGGML_RPC=ON -DGGML_NCCL=ON \
-DCMAKE_CUDA_ARCHITECTURES=61 \
-DCMAKE_C_COMPILER=gcc-14 -DCMAKE_CXX_COMPILER=g++-14 \
-DCMAKE_C_FLAGS="-O3 -march=native -pipe" \
-DCMAKE_CXX_FLAGS="-O3 -march=native -pipe" \
-DCMAKE_CUDA_FLAGS="-O3 --allow-unsupported-compiler -ccbin /usr/bin/gcc-14" \
&& cmake --build build -j$(nproc)
-- The C compiler identification is GNU 14.3.1

-- The CXX compiler identification is GNU 14.3.1

-- Detecting C compiler ABI info

-- Detecting C compiler ABI info - done

-- Check for working C compiler: /usr/bin/gcc-14 - skipped

-- Detecting C compile features

-- Detecting C compile features - done

-- Detecting CXX compiler ABI info

-- Detecting CXX compiler ABI info - done

-- Check for working CXX compiler: /usr/bin/g++-14 - skipped

-- Detecting CXX compile features

-- Detecting CXX compile features - done

CMAKE_BUILD_TYPE=Release

-- Found Git: /usr/bin/git (found version "2.54.0")

-- The ASM compiler identification is GNU

-- Found assembler: /usr/bin/gcc-14

-- Performing Test CMAKE_HAVE_LIBC_PTHREAD

-- Performing Test CMAKE_HAVE_LIBC_PTHREAD - Success

-- Found Threads: TRUE

-- Warning: ccache not found - consider installing it for faster compilation or disable this warning with GGML_CCACHE=OFF

-- CMAKE_SYSTEM_PROCESSOR: x86_64

-- GGML_SYSTEM_ARCH: x86

-- Found OpenMP_C: -fopenmp (found version "4.5")

-- Found OpenMP_CXX: -fopenmp (found version "4.5")

-- Found OpenMP: TRUE (found version "4.5")

-- Including CPU backend

-- x86 detected

-- Adding CPU backend variant ggml-cpu: -march=native

-- Found CUDAToolkit: /opt/cuda/targets/x86_64-linux/include (found version "12.9.86")

-- CUDA Toolkit found

-- The CUDA compiler identification is NVIDIA 12.9.86 with host compiler GNU 14.3.1

-- Detecting CUDA compiler ABI info

-- Detecting CUDA compiler ABI info - done

-- Check for working CUDA compiler: /opt/cuda/bin/nvcc - skipped

-- Detecting CUDA compile features

-- Detecting CUDA compile features - done

-- Using CMAKE_CUDA_ARCHITECTURES=61 CMAKE_CUDA_ARCHITECTURES_NATIVE=61-real

-- Found NCCL: /usr/local/lib/libnccl.so

-- CUDA host compiler is GNU 15.3.0

-- Including CUDA backend

-- Using RPC backend

-- RDMA transport disabled

-- Including RPC backend

-- ggml version: 0.15.3

-- ggml commit: 931eb37f8

-- Found OpenSSL: /usr/lib64/libcrypto.so (found version "3.5.7")

-- Performing Test OPENSSL_VERSION_SUPPORTED

-- Performing Test OPENSSL_VERSION_SUPPORTED - Success

-- OpenSSL found: 3.5.7

-- Generating embedded license file for target: llama-app

-- Configuring done (8.2s)

-- Generating done (0.5s)

-- Build files have been written to: /home/intel41/Рабочий стол/llama.cpp/build

[ 0%] Building CXX object vendor/cpp-httplib/CMakeFiles/cpp-httplib.dir/httplib.cpp.o



[ 99%] Linking CXX executable ../bin/llama

[ 99%] Built target llama-app

[100%] Linking CXX executable ../bin/test-chat

[100%] Built target test-chat

Затем копируем файлы llama.cpp в рабочую папку llama_cpp вручную или командой:

cp -f ~/llama.cpp/build/bin/* ~/llama_cpp/

Для компьютера с CPU Haswell и видеокартами AMD Radeon RX570 8GB (с Vulkan, подробнее в статье Запуск AI-моделей в llama.cpp на AMD Radeon RX470 8GB с Vulkan в gentoo):

cmake -B build -DGGML_VULKAN=ON -DCMAKE_BUILD_TYPE=Release -DGGML_RPC=ON -DCMAKE_C_FLAGS="-O3 -march=native -pipe" -DCMAKE_CXX_FLAGS="-O3 -march=native -pipe" && cmake --build build --config Release -j$(nproc)

На компьютере-мастере с видеокартой Nvidia Tesla V100 (Compute Capability 7.0):

cmake -B build -DCMAKE_BUILD_TYPE=Release -DGGML_CUDA=ON -DGGML_RPC=ON -DCMAKE_CUDA_ARCHITECTURES="70" -DCMAKE_C_COMPILER=gcc-14 -DCMAKE_CXX_COMPILER=g++-14 -DCMAKE_C_FLAGS="-O3 -march=native -pipe" -DCMAKE_CXX_FLAGS="-O3 -march=native -pipe" -DCMAKE_CUDA_FLAGS="-O3" && cmake --build build -j$(nproc)

После сборки и копирования папок с файлами llama.cpp в рабочий каталог нужно настроить узлы-вычислители (воркеры), на них будет запускаться RPC-сервер с помощью скрипта:

#!/bin/sh

export CUDA_MODULE_LOADING=LAZY

./ggml-rpc-server -H 0.0.0.0 -p 50052

На главной машине, которая тоже будет использоваться для вычислений, используем скрипт с указанием локальных адресов воркеров, а также параметров разгона:

#!/bin/bash

export CUDA_MODULE_LOADING=LAZY

SCRIPT_DIR="$(pwd)"

echo "=== Настройка NVIDIA GPU Tesla V100 16GB ==="

# Настройки требуют root

sudo nvidia-smi -pm 1

sudo nvidia-smi -i 0 -pl 190

sudo nvidia-smi -i 0 -ac 877,1530

sudo nvidia-settings -a '[gpu:0]/GPUPowerMizerMode=1' 2>/dev/null || true

echo "=== Запуск Llama Server ==="

su - ${SUDO_USER:-$USER} -c "

cd \"$SCRIPT_DIR\" && \

./llama-server \

-m ~/AI_Models/Qwable-v1/lordx64_Qwable-v1-Q6_K_L.gguf \

--host 127.0.0.1 \

--port 8080 \

--rpc 192.168.2.41:50052 \

-ngl -1 \

-b 512 \

-ub 512 \

-t 8 \

-tb 8 \

-sm layer \

-ctk q8_0 \

-ctv q8_0 \

-c 32768 \

-n -1 \

--no-mmap \

--rope-freq-scale 0.5 \

--yarn-orig-ctx 16384 \

--yarn-beta-fast 32 \

--yarn-beta-slow 1 \

--temp 0.2 \

--min-p 0.05 \

--flash-attn on

"

# для открытия доступа по сети, вместо --host 127.0.0.1 сделать --host 0.0.0.0

На воркерах в настройках firewall нужно разрешить входящие соединения по выбранному порту (по умолчанию 50052). В ubuntu-подобных системах это делается командой:

sudo ufw allow 50052

в Windows — через настройки брандмауэра защитника Windows.

В настройках роутера нужно отключить функцию «Изоляция клиентов» (AP Isolation), иначе устройства не смогут «видеть» друг друга.

Чтобы RPC-сервер на воркерах запускался автоматически, как системная служба gentoo, создаем на воркерах скрипт инициализации (init-скрипт):

  sudo nano /etc/init.d/llama-rpc

Вставляем в него код со своим /path/to/ggml-rpc-server и именем пользователя (user:user):

#!/sbin/openrc-run

description="Llama RPC Server"

# Путь к рабочей директории

directory="/путь/llama_cpp"

# Указываем полный абсолютный путь к бинарнику

command="/путь/llama_cpp/ggml-rpc-server"

# Используем короткие флаги (-H и -p)

command_args="-H 0.0.0.0 -p 50052"

# Запуск в фоне с сохранением логов от имени пользователя

command_user="user:user"

command_background="yes"

pidfile="/var/run/llama-rpc.pid" # Рекомендуется использовать /var/run для PID-файлов

# Создаем папку для логов перед стартом

start_pre() {

checkpath -d -m 0755 -o intel41:intel41 /var/log/llama-rpc

}

output_log="/var/log/llama-rpc/llama-rpc.log"

error_log="/var/log/llama-rpc/llama-rpc.err"

# Переменные окружения (CUDA и ленивая загрузка)

export CUDA_MODULE_LOADING=LAZY

depend() {

need net

}

Делаем созданный скрипт исполняемым:

  sudo chmod +x /etc/init.d/llama-rpc

Добавляем службу в уровень запуска default для автостарта при загрузке системы:

sudo rc-update add llama-rpc default

Запускаем сервер, не перезагружая ПК:

sudo rc-service llama-rpc start

Проверяем статуса службы:

sudo rc-service llama-rpc status

В случае необходимости, для перезапуска можно использовать команду:

sudo rc-service llama-rpc restart

Для посмотра логов:

tail -f /var/log/llama-rpc/llama-rpc.err

Загрузку видеокарт (любых, включая AMD) на локальном и удаленных компьютерах (по ssh) удобно смотреть в nvtop:

Сначала производился запуск без аргументов -b 512 \

-ub 512 \, из-за этого он был аномально медленным:

prompt eval time = 28734.60 ms / 31 tokens (1.08 tokens per second)

Это происходило из-за того, что сервер пытался обрабатывать промпт кусками, и при работе через RPC накладные расходы сети на отправку каждого мини-пакета полностью уничтожали скорость.

После добавления в промт -b 512 и -ub 512, а также ограничения доступных llama.cpp ядер процессора до 8 (Intel QTJ0 8 ядер/16 потоков), скорость обработки промпта (prompt eval time) выросла в 9 раз: с 1.08 токенов в секунду (31 токен за примерно 28 секунд) до 9.86 т/с (273 токена за примерно те же 27-28 секунд). Скорость генерации текста (eval time) увеличилась на 26%, было: 11.64 т/с, стало 14.67.

Добавление в скрипт запуска квантования KV-кэша (-ctk q8_0 -ctv q8_0) для снижения объемов передаваемых данных увеличило быстродействие за счет уменьшения объема промежуточных матриц, которые мастер-сервер должен прокачивать через гигабитную сеть на воркер при чтении длинного запроса:

  • скорость обработки промпта (Prefill) выросла в 1.5 раза: без квантования кэша: 9.86, с квантованием кэша: 15.14 токена в секунду;
  • включился механизм кэширования контекста (LCP similarity) , благодаря чему скорость вычислений выросла до 145.44 токенов в секунду (обработка 467 токенов заняла всего 3 секунды);
  • скорость выдачи текста зафиксировалась на отметке 14.63 – 14.58 токенов в секунду, график генерации стал монолитным, без просадок на длинных ответах.

Дополнительным способом увеличения быстродействия на длинных контекстах является использование Group Attention Factor и Width, что позволяет увеличить объем обрабатываемых запросов. Кроме того, еще увеличить скорость инференса можно флагом

--no-mmap

Это отключает маппинг файла модели в память, чт очень полезно для медленных дисков (HDD), потому что модель загружается в RAM полностью. Благодаря этому даже локальная гигабитная сеть может загрузить видеокарты непрерывным потоком данных без простоев.

Для запуска модели Qwable-v1 с контекстом 32768 без лимита генерации использовался такой скрипт:

#!/bin/bash

# Запуск скрипта: sudo ./run_distributed_main_Qwable.sh

export CUDA_MODULE_LOADING=LAZY

# Автоматически запоминаем папку, из которой запускается скрипт

SCRIPT_DIR="$(pwd)"

echo "=== Настройка NVIDIA GPU Tesla V100 16GB ==="

nvidia-smi -pm 1

nvidia-smi -i 0 -pl 190

nvidia-smi -i 0 -ac 877,1530

# Включаем режим максимальной производительности (Prefer Maximum Performance).

# Перенаправляем ошибки в /dev/null на случай отсутствия X-сервера.

su - ${SUDO_USER:-$USER} -c "nvidia-settings -a '[gpu:0]/GPUPowerMizerMode=1'" 2>/dev/null

echo "=== Запуск Llama Server ==="

# Запускаем сам сервер от имени вашего обычного пользователя (не от root)

su - ${SUDO_USER:-$USER} -c "

cd \"$SCRIPT_DIR\" && \

./llama-server \

-m ~/AI_Models/Qwable-v1/lordx64_Qwable-v1-Q6_K_L.gguf \

--host 127.0.0.1 \

--port 8080 \

--rpc 192.168.2.41:50052 \

-ngl -1 \

-b 512 \

-ub 512 \

-t 8 \

-tb 8 \

-sm layer \

-ctk q8_0 \

-ctv q8_0 \

-c 32768 \

-n -1 \

--no-mmap \

--rope-freq-scale 0.5 \

--yarn-orig-ctx 16384 \

--yarn-beta-fast 32 \

--yarn-beta-slow 1 \

--temp 0.2 \

--min-p 0.05 \

--flash-attn on
"

В результате скорость обработки промпта (Prefill) выросла с изначальных 1.08 т/с (31 токен обрабатывался 28 секунд) до 257.10 – 270.74 токенов в секунду! Сервер за один проход обработал промпт в 3750 токенов всего за 14.5 секунд. Скорость чистой генерации (Decode) выросла на ~32% (до 19.34 т/с). Избавление от дисковых задержек на мастере разгрузило общую шину синхронизации с воркером, и фаза генерации токенов пошла на максимальной аппаратной скорости.

В конечном итоге, для увеличения IQ нейросети в скрипте запуска было убрано квантование ctk и ctv q8_0:

В результате скорость обработки даже выросла (до ~274-307 t/s). Это произошло благодаря тому, что Tesla V100 перестала тратить ресурс на выполнение дополнительных математических операций квантования/деквантования кэша. Скорость генерации (Decode) также увеличилась на ~13% (с 19.34 до 21.8-2236 т/с).

Вероятно, положительную роль сыгралда полная точность контекста (FP16/BF16), которая облегчила модели Qwable фазу предсказания следующего токена. Видеокарты избавились от округлений кэша, а послойное разделение моделей (-sm layer) удерживало обмен сетевыми тензорами в рамках идеального тайминга.

Таким образом, на трех видееокартах (Nvidia Tesla V100 + Nvidia Ge Force GTX 1070) на модели 32B удалось получить стабильные 22 токена в секунду при работе по локальной сети. Совсем неплохо для старого железа…

Пример размышлений Qwable на злобу дня:

Когда люди поумнеют и перестанут убивать себе подобных?

Это один из самых глубоких и болезненных вопросов, который человечество задаёт себе на протяжении всей истории. Ответ на него не лежит на поверхности, но можно рассмотреть несколько аспектов, которые помогают понять, почему ситуация остаётся такой, какой она есть, и есть ли надежда на изменения.

1. Почему люди убивают друг друга?

Причины насилия и убийств сложны и многогранны:

  • Биологические и эволюционные факторы: Агрессия — часть нашей природы, унаследованная от предков, которым приходилось защищаться и конкурировать за ресурсы.
  • Психологические травмы: Насилие часто передаётся из поколения в поколение. Человек, выросший в среде жестокости, может воспроизводить её.
  • Социальные условия: Бедность, неравенство, отсутствие образования, маргинализация — всё это создаёт почву для конфликтов.
  • Идеологии и пропаганда: Войны, геноциды и массовые убийства часто оправдываются идеологически, что позволяет людям совершать ужасные поступки, считая их «правильными».
  • Психические расстройства: Некоторые убийства совершаются людьми с серьёзными психическими отклонениями, которые не способны адекватно воспринимать реальность.

2. «Поумнеют ли люди?»

Термин «поумнеют» здесь, вероятно, означает не только интеллектуальный рост, но и моральное, эмоциональное и духовное развитие. История показывает, что человечество действительно становится более образованным, более гуманным в некоторых аспектах:

  • Снижение уровня насилия в мире (по данным исследователей, таких как Стивен Пинкер).
  • Развитие прав человека, международного права, организаций, защищающих жертв насилия.
  • Рост осознанности через искусство, литературу, философию, науку.

Однако прогресс нелинеен. В то время как в одних регионах мир укрепляется, в других вспыхивают конфликты. Интеллект сам по себе не гарантирует доброту — умные люди тоже способны на зло.

3. Есть ли надежда?

Да, есть. Но она не в том, что все люди вдруг станут «умными» и добрыми. Надежда — в том, что:

  • Образование и эмпатия помогают людям видеть в других не врагов, а таких же людей.
  • Диалог и взаимопонимание между культурами и народами снижают уровень ненависти.
  • Правосудие и справедливость могут предотвратить повторение преступлений.
  • Личный выбор каждого — мы все можем делать выбор в пользу добра, даже в малом.

4. Что может сделать каждый из нас?

  • Не быть равнодушным. Видеть страдания других и реагировать на них.
  • Учиться. Изучать историю, культуру, психологию, чтобы понимать корни насилия.
  • Воспитывать детей в духе уважения и сострадания.
  • Поддерживать тех, кто борется за мир и справедливость.
  • Не позволять ненависти победить в себе.

5. Философский взгляд

Многие мыслители — от Будды до Толстого, от Камю до Далай-ламы — говорили о том, что путь к миру начинается с внутреннего преображения каждого человека. Внешние изменения невозможны без внутренних.

Заключение

Люди, возможно, никогда не перестанут убивать друг друга полностью — это часть человеческой природы. Но мы можем стремиться к тому, чтобы таких случаев становилось всё меньше, чтобы каждый акт насилия был исключением, а не нормой. И в этом стремлении — наша общая задача.

Если вам хочется поговорить об этом подробнее, о конкретных примерах или о том, как можно внести свой вклад в мир — я готов продолжить диалог.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *