
Когда я взялся за этот небольшой эксперимент, мной двигало исключительно любопытство и желание ответить на вопрос — а что, если взять три моих старых ноутбука, объединить их вычислительные ресурсы и использовать их под инференс LLM? Идея запуска на них чего-то более-менее тяжелого выглядела заманчиво: три процессора, 52 ГБ оперативной памяти — в теории должно получиться быстрее, чем на любом из них по отдельности.
Спойлер: почти никогда не быстрее. Однако выяснение причины оказалось интереснее потенциальной истории успеха. Вылезли разные неочевидные вещи: старый процессор был не самым медленным, третий узел не особо помогал, а обработка промпта вела себя противоположно генерации токенов.
Сразу же оговорюсь — любой GPU средней ценовой категории сделает ту же самую работу в десятки раз быстрее. Но моя задача была увидеть узкие места в «естественной среде обитания». Приятного чтения.
Тестовый стенд
Делать упор на бренды ноутбуков не стану — это в целом не так важно. Укажу лишь общие характеристики:
|
Роль |
CPU |
Поколение |
RAM |
Тип |
|
Coordinator |
Core i7-4700MQ |
Haswell (2013) |
16 ГБ |
DDR3 |
|
Worker |
Core i5-8265U |
Whiskey Lake (2018) |
20 ГБ |
DDR4 |
|
Worker |
Core i3-1115G4 |
Tiger Lake (2020) |
16 ГБ |
DDR4 |
Итого — три поколения процессоров Intel с разбросом в семь лет. Тут разнородность не баг, а фича. Так можно увидеть эффект самого слабого узла в чистом виде. Отличие в наборах инструкций минимально — у всей троицы есть AVX2, FMA и F16C. Расширение AVX-512 присутствует только у Tiger Lake (i3), но в разнородном кластере это не станет преимуществом — llama.cpp работает по общему знаменателю.
Что до количества ядер — у i7 и i5 по 4 ядра / 8 потоков, а у i3 всего лишь 2 ядра / 4 потока. Можно подумать, что слабейший найден, но, как выяснится ниже, он вовсе не станет плестись в хвосте.
На всех узлах был развернут следующий софт:
-
Ubuntu 24.04 LTS
-
llama.cpp, собранная из одного коммита 178a6c449 (build b10069). RPC-протоколу важно, чтобы на всех машинах версия была одинаковая.
-
governor выставил в performance (все ноутбуки от розетки)
Что же до сетевого подключения — любопытства ради присоединил все узлы к единому Wi-Fi 2.4 ГГц (увы, старенький i7 столько так умеет) и померял пинги:
rtt min/avg/max/mdev = 20.872/67.360/116.416/28.175 ms
Средний RTT ~67 мс, джиттер ~30 мс. Сразу же звучит как приговор: узлам надо обмениваться данными на каждый токен синхронно, а подобный разброс убьет всю производительность. Далее все узлы я подключил к обычной гигабитной сети:
rtt min/avg/max/mdev = 0.732/0.875/1.316/0.113 ms
Джиттер упал в 300 раз, средний RTT — в 85. Все следующие замеры стану делать, используя проводную сеть, а Wi-Fi оставлю как сюжет на будущее — надо будет выполнить апгрейд i7 до современных стандартов.
Методика измерений
В качестве инструмента взял llama-cli из llama-cpp. Каждую конфигурацию прогонял по три раза, бралась медиана. Для модели с 32b параметров снижал количество токенов, так как совсем медленно. Seed фиксированный, промпт одинаковый — всё, как доктор прописал.
Запуск распределенного инференса реализуется флагом —rpc со списком адресов воркеров. На последних при этом крутится ggml-rpc-server. Это, кстати, оказалось первыми граблями: я сначала по памяти безуспешно пытался запустить rpc-server. Выяснилось, что в свежих сборках его переименовали.
На воркерах:
~/llama.cpp/build/bin/ggml-rpc-server -H 0.0.0.0 -p 50052 -t "$(nproc)"
На координаторе:
~/llama.cpp/build/bin/llama-cli -m model.gguf \--rpc 192.168.88.18:50052,192.168.88.77:50052 \-ngl 99 -n 128 -p "..."
В таком режиме локальная копия модели на воркерах не требуется — координатор сам загружает GGUF и раздает слои по сети. В качестве метрик выбрал обработку входного промпта (prompt eval) и генерацию ответа (token generation). Это важно, так как ведут они себя по-разному.
Результаты
Модель 1B (Llama-3.2-1B-Instruct, Q4_K_M)
Для начала взял небольшую модель, которая легко влезает в каждый узел, а инференс вполне комфортно работает на CPU. Для одиночных узлов получил следующие результаты:

Таблица
|
Узел |
Prompt eval t/s |
Token generation t/s |
|
i3-1115G4 |
127.6 |
29.2 |
|
i7-4700MQ |
103.9 |
26.2 |
|
i5-8265U |
108.5 |
22.8 |
Занятно, но двухъядерный i3 оказался быстрее всех. Тут, очевидно, играет роль не количество ядер, а тактовая частота и скорость ОЗУ. Tiger Lake способен держать до 4,1 ГГц (в boost-режиме), что значительно быстрее старого i7 (базовая 2,4 ГГц, буст 3,4 ГГц) и энергоэффективного i5 (базовая 1,6 ГГц, буст 3,9 ГГц), который в режиме полной нагрузки упирается в теплопакет.
Перед тестом я полагал, что именно i5 будет самым шустрым, но гипотеза не подтвердилась. Как обычно — ожидания vs реальность. Но теперь самое интересное — распределенный инференс. Для сравнения туда же приложил данные лучшего solo теста (А):

Таблица
|
Конфигурация |
Узлы |
Prompt eval t/s |
Token generation t/s |
|
A |
i3 |
127.6 |
29.2 |
|
B1 |
i3 + i5 |
70.3 |
22.5 |
|
B2 |
i3 + i5 + i7 |
67.0 |
22.6 |
|
B3 |
i5 через RPC |
74.5 |
19.6 |
Хорошо видно, что на такой небольшой модели распределенка (22.5) подчистую проигрывает одиночному узлу (29.2) — отставание на 23%. Наиболее интересен B3 — это, по факту, тот же самый i5, который давал в solo 22.8, а обернутый в RPC теперь дает 19.6. Получается, что 14% скорости генерации теряется на то, чтобы поговорить с самим собой по TCP. Это значение — чистый налог RPC-протокола.
Модель 8B (Llama-3.1-8B-Instruct, Q4_K_M)
Теперь попробую модель у которой в восемь раз больше параметров и при этом влезает в каждый узел. В качестве референса запущу тесты по-отдельности в solo, а затем по RPC:

Таблица
|
Конфигурация |
Узлы |
Prompt eval t/s |
Token generation t/s |
|
A1 (solo) |
i3 |
1.5 |
4.9 |
|
A2 (solo) |
i5 |
3.9 |
3.9 |
|
A3 (solo) |
i7 |
2.9 |
4.4 |
|
B1 |
i3+i5 |
10.9 |
4.6 |
|
B2 |
i3+i5+i7 |
10.7 |
4.5 |
|
B3 |
i5 через RPC |
11.3 |
3.9 |
Общая картина начала меняться. Теперь по генерации кластер (4.6) почти догнал лучший solo (4.9), а отставание сократилось с 23% до 6%. Опять результат B3 удивляет — RPC налог на генерации исчез. Объяснение, на мой взгляд, простое: модель стала достаточно тяжелой и поэтому сетевые накладные расходы перестали доминировать над вычислениями.
Еще одна любопытная деталь — обработка промпта. Если в одиночку i5 разгрызает его со скоростью 3.9 t/s, то кластер делает это почти втрое быстрее — 10.9 t/s. Причина в характере нагрузки: промпт может быть обработан батчем и параллельно (на разных узлах), а вот генерация — строго последовательный процесс (по токену за раз).
Исходя из этого можно сделать простой вывод: распределенный инференс способен сильно ускорить обработку промпта, но практически не помогает генерации токенов.
Модель 32B (Qwen2.5-32B-Instruct, Q4_K_M)
Выбор был сделан неслучайно — эта модель уже не влезет ни в один узел. Даже i5 с его 20 ГБ ОЗУ пасует — веса с KV-кешем и накладными расходами уведет ОС в своп (конфигурация A):

Таблица
|
Конфигурация |
Узлы |
Prompt eval t/s |
Token generation t/s |
|
A (solo) |
i5 |
0.7 |
0.3 |
|
B1 |
i3 + i5 |
2.4 |
1.1 |
|
B2 |
i3 + i5 + i7 |
1.9 |
1.1 |
Тот самый момент, когда распределенный инференс перестает быть «налогом» и становится единственно возможным способом запустить большую модель с «адекватной» скоростью. Разумеется 1.1 t/s — это медленно. Но все же в 3,7 раза быстрее, чем на i5, падающим в своп (бутылочным горлышком становится уже дисковый накопитель, а не ОЗУ).
Итоги dense-моделей
Если подвести общие результаты производительности:

Таблица
|
Модель |
Лучший в solo |
Кластер (i3 + i5) |
Победитель |
|
1B |
29.2 |
22.5 |
solo (+30%) |
|
8B |
4.9 |
4.6 |
паритет (solo + 6%) |
|
32B |
0.3 (своп) |
1.1 |
кластер (+268%) |
Хорошо видно, что перелом происходит между 8B и 32B — там, где модель перестает помещаться в один узел. До этого момента — сетевой оверхед есть, а ускорения нет.
Вывод: распределенный инференс оправдан, только когда модель не влезает на один узел. Во всех остальных случаях ее имеет смысл запускать на самом быстром одиночном узле.
MoE (gpt-oss-20b)
«А что там с MoE, позвольте поинтересоваться?» — спросите вы и будете абсолютно правы. Предыдущие примеры — это dense-архитектура, где при обработке каждого токена задействуются все параметры.
Eсли взять модель класса Mixture-of-Experts (MoE), вроде gpt-oss-20b, то на каждый токен будет активироваться лишь примерно 3,6B параметров (32 эксперта, топ-4 на токен). Таким образом, в теории MoE должна работать на скорости маленькой модели, но со знаниями большой. Предлагаю взглянуть на реальность пока что на одном узле (i5) в solo:

Таблица
|
Модель |
Активных параметров |
Token generation t/s |
|
Llama-3.1-8B-Instruct |
8B |
~4–5 |
|
gpt-oss-20b |
3,6B |
~7–8 |
Первое наблюдение: в генерации MoE действительно быстрее dense-модели той же «весовой категории». Играет роль то, что на каждый токен приходится лишь 3,6B параметров. Эта магия прекрасно работает на CPU. Однако есть и ложка дегтя — MoE ускоряет вычисления, но не влияет на потребление памяти. Несмотря на то что на каждом шаге используется часть весов, все они должны лежать в RAM или VRAM.
На практике это означает, что gpt-oss-20b банально не влезла без свопа ни в один узел. Даже 20 Гб в i5 ей оказалось мало, а поэтому все данные solo-замеров получились «грязными». Взгляните на скорость обработки промпта:
prompt eval t/s по повторам на i5: 5.5 ; 17.2 ; 3.3
Один и тот же тест — от 3 до 17 токенов в секунду — характерный признак того, что модель то подгружается с диска, то берется из оперативной памяти. В кластере же все работает стабильно, без какого-либо разброса:

Таблица
|
Конфигурация |
Узлы |
Prompt eval t/s |
Token generation t/s |
Разброс генерации |
|
A (i5, solo) |
i5 |
5.5 |
7.3 |
нестабильно |
|
B1 |
i3 + i5 |
15.4 |
8.1 |
стабильно |
|
B2 |
i3 + i5 + i7 |
15.9 |
8.0 |
стабильно |
Ни один из узлов кластера не пользуется свопом — каждый берет данные из ОЗУ. Обработка промпта вновь выросла почти втрое (15.4 против 5.5) — сказывается параллельная работа узлов.
Подводя итог, можно сделать вывод, что на CPU заявленные преимущества MoE работают лишь наполовину. Да, скорость выше dense-модели, однако памяти требуется как для полной 20B. Выходит, что на таком железе получить адекватную производительность можно лишь в кластере, распределяющем веса по узлам.
Побочные наблюдения
Перед началом эксперимента мне казалось, что каждый новый узел будет добавлять полезную мощность, но в реальности все они становятся участниками синхронного обмена. Любое «слабое звено» станет просаживать показатели всего кластера. В моем случае старый Haswell с DDR3 не добавлял полезной мощности — дополнительная сетевая координация нивелировала его усилия. А в случае с 32B лишний узел увеличил обмен данными, но не скорость.
Обработка промпта и генерация токенов — совершенно разные истории. Распределенный инференс ускоряет первый процесс, но не влияет на второй. Получается, что если вам нужно обрабатывать длинные промпты с короткими ответами (например, суммаризация или RAG), то построение кластера действительно даст заметный прирост скорости. В случае же чат-бота с длинной генерацией — почти никак не повлияет.
В будущем хочу попробовать проделать те же тесты на Wi-Fi 7 (6 ГГц), а также на 2.5GBASE-T. У меня есть гипотеза, что с более быстрой сетью переломный момент сдвинется в сторону меньших моделей.
Ну а если захотите повторить этот тест — буду рад, если поделитесь цифрами и наблюдениями в комментариях.
ссылка на оригинал статьи https://habr.com/ru/articles/1066364/