Головна / Статті / Локальна станція для LLM: від 20 до 60 токенів на секунду на двох GPU

Локальна станція для LLM: від 20 до 60 токенів на секунду на двох GPU

Вживана машина на Xeon із двома RTX 5060 Ti, Qwen 27B, тензорний паралелізм і NCCL: правила вибору заліза, реальний приріст швидкості та розрахунок окупності порівняно з цінами API.

2038 слів

Рік експериментів із локальним інференсом багато чого вчить, і здебільшого — розчаруванню. Навіть за чималого оптимізму, щойно треба зробити справжню роботу, перемагає хмарна підписка. Але останнім часом є тиск у протилежний бік. Підписки щомісяця купують дедалі менше роботи. Часи, коли GitHub Copilot чи Cursor можна було використовувати, не стежачи за бюджетом, здаються далекими, а гроші, яких раніше вистачало на місяць, можуть зникнути за день. Цього досить, щоб поставитися до локальної установки серйозно. Далі розповідь про те, як один глючний стек на 20 токенах на секунду перетворився на надійну робочу станцію, що в середньому видає 60 токенів на секунду, а на піках помітно більше.

Залізо, яке має сенс

Інтернет повен людей, які ставлять один на одного кілька Mac Pro із сотнями гігабайтів обʼєднаної памʼяті, запускають моделі на Raspberry Pi (це має своє місце, але це місце — edge AI) або хизуються одним токеном на хвилину, вичавленим із SSD. Це цирк. Справжні правила простіші:

  • Обсяг VRAM на першому місці. Модель і контекст мають уміщатися у VRAM, а 32 ГБ чи 48 ГБ сьогодні — доступні оптимальні варіанти.
  • Додаткові GPU потрібні здебільшого, щоб умістилося більше, а не заради швидкості. Друга карта додає VRAM для більших моделей і довшого контексту, але поділ роботи додає накладні витрати на звʼязок, тож чекайте невеликого сповільнення, а не прискорення.
  • Коли карт кілька, важить топологія. Шукайте звʼязки між GPU з високою пропускною здатністю й низькою затримкою, в ідеалі за одним PCIe-комутатором або хоча б через лінії, підключені до CPU. Маршрут через чипсет набагато гірший, особливо за тензорного паралелізму.
  • Живлення й охолодження — частина конструкції. Дві-три GPU споживають багато й стільки ж тепла викидають у корпус, тому важать потужність блока живлення, обдув, відстань між слотами й TDP відеокарт.

Другий пункт на споживчих платах виконати важко. Два слоти PCIe на папері не гарантують доброго шляху між картами, а плати, які його дають, недешеві.

Альтернатива — вживані бізнес-станції. Багато з них мають процесори Xeon, купу ліній PCIe і штатні блоки живлення на 1 кВт і більше, бо їх будували під кілька професійних GPU. Поставити туди дві-три карти — звична справа, і часто це набагато дешевше за порівнянну сучасну споживчу платформу. Є компроміси: стара DDR4, PCIe Gen4 чи навіть Gen3 і процесори на кілька поколінь позаду.

Інференс сильно відрізняється від ігор, але опубліковані бенчмарки масштабування PCIe для RTX 5090 показують, що навіть дуже швидкі GPU втрачають напрочуд мало швидкості за урізаної пропускної здатності, і цього вистачило, щоб наважитися спробувати. Зрештою, майже все вирішує економіка.

Вибір упав на дві NVIDIA RTX 5060 Ti на 16 ГБ, найдешевший шлях до 32 ГБ VRAM з чудовою енергоефективністю, у станції на Xeon із причин вище. Одна карта фізично не влізла, хоча слоти й розкладка ліній підходили, тож її закріпили на корпусі через PCIe-подовжувач і стяжку. Назвімо це польською інженерією на стяжках.

Моделі, що вміщаються

Це залізо тягне короткий список моделей, у кожної контекстне вікно від 160k до 256k за квантованого KV-кешу. Дві від Google: gemma-4-26b-a4b та її QAT-варіант. Дві від Qwen: qwen3.6-35b-a3b і qwen3.6-27b у q4_k_s.

Щоденною робочою конячкою стала Qwen на 27B, і не просто через особисті вподобання. Рейтинги й бенчмарки можуть вводити в оману, але якщо переглянути кілька, впадають в око дві речі. Мало які моделі працюють на такому скромному залізі, і Qwen лідирує в цій категорії з явним відривом. Крім того, це одна з небагатьох, що можуть позмагатися з невеликими фронтир-моделями, з тих, що вже довели вміння надійно виконувати маленькі чітко поставлені завдання.

З огляду на розмір контексту й можливості найкраще використовувати її в агентній оркестрації в парі з більшою хмарною моделлю, яка планує роботу й роздає окремі завдання. Повністю чи навіть частково автономний кодинг поки не під силу локальному інференсу самотужки. А от гібридна схема дає справді добрі результати.

Софт і чому перша спроба була повільною

Спершу був LM Studio, бо він простий. Він чудово підходить, щоб експериментувати й розбиратися в різних опціях llama.cpp і моделей. Він ще й допомагає налагоджувати шар інтеграції між інструментами, які використовують виставлені моделі. Не завжди очевидно, чи доходять до запитів налаштування, передані через щось на кшталт OpenCode, а LM Studio дає їх переглянути, знайти прогалини й підправити.

Але з Qwen3.6-27B він у середньому видавав близько 20 токенів на секунду навіть з увімкненим flash attention, а стабільність кульгала: раз у раз траплялися дивні таймаути.

Дві зміни, які спрацювали

Тензорний паралелізм

Відтоді CUDA-тензорний паралелізм зʼявився в LM Studio, судячи з журналу змін 0.4.15, але так було не завжди, і протестований тут готовий рантайм однаково залишав продуктивність невикористаною. Спершу — що це таке?

Традиційний спосіб роботи з кількома GPU кладе частину шарів на GPU 0, частину на GPU 1 і передає активації від однієї до іншої:

Model
                   │
          ┌────────┴────────┐
          │                 │
       GPU 0             GPU 1
      layers 0–N        layers N–end
          │                 │
          └───────→─────────┘

Це дає змогу запустити модель, яка не влазить в одну карту, і все. Для одного потоку дві GPU ніколи не працюють разом над одним шаром. Робота рухається ланцюжком: GPU 0 обробляє частину шарів, активації переїжджають, GPU 1 доробляє решту.

Тензорний паралелізм натомість ділить обчислення кожного шару між GPU:

One layer
                     │
             ┌───────┴───────┐
             │               │
          GPU 0           GPU 1
       part of tensor   part of tensor
             │               │
             └───────┬───────┘
                     │
                 next layer

Різницю описано в посібнику з кількох GPU в репозиторії llama.cpp. Конвеєрний паралелізм, режим layer, розміщує різні шари на різних GPU й пропускає токени послідовно. Він мінімізує трафік між картами, але для масштабування йому потрібно багато токенів у роботі одночасно. Тензорний паралелізм, режим tensor, ріже кожен шар між GPU й виконує кілька міжкарткових редукцій на шар. Він може розпаралелити будь-яке навантаження, але сильно залежить від швидкості міжзʼєднання. Конвеєрний режим максимізує пропускну здатність батча, а тензорний мінімізує затримку.

Коли обидві GPU працювали над одним шаром, Qwen3.6-27B вийшла приблизно на 50 токенів на секунду. Але це створює нову проблему. Якщо дві GPU спільно обробляють кожен шар, їм доводиться постійно спілкуватися.

NCCL

NCCL від NVIDIA, Collective Communications Library, створена саме для такого обміну між GPU. Операції на кшталт AllReduce лежать в основі розподіленої роботи на GPU, і без ефективного колективного шару тензорний паралелізм може витрачати забагато часу на перекладання даних замість обчислень.

Сюрпризом стало те, що в готовому рантаймі llama.cpp у протестованій версії LM Studio підтримку NCCL було вимкнено. Тензорному паралелізму NCCL не обовʼязковий, бо llama.cpp уміє виконувати потрібні редукції власним кодом, але NCCL дає для цього трафіку оптимізований бекенд.

Ось що відбувається. За тензорного паралелізму GPU обмінюються проміжними результатами, наприклад після частини множення матриць:

Matrix operation
                       │
             ┌─────────┴─────────┐
             │                   │
          GPU 0               GPU 1
       computes part        computes part
             │                   │
             └─────────┬─────────┘
                       │
                   AllReduce
                       │
                 combined result

AllReduce обʼєднує часткові результати всіх GPU й віддає спільну відповідь кожній із них. llama.cpp має власну реалізацію, тож вбудований варіант працює. Тут AllReduce з NCCL виявився швидшим.

Кілька годин потому

Болісну частину (бракує бібліотек на Fedora, компіляція, жонглювання параметрами llama й моделі) не варто розписувати детально, почасти тому, що цей куточок софту дуже швидко змінюється. Результат у середніх цифрах:

  • Поділ за шарами: близько 20 токенів на секунду
  • Тензорний паралелізм із вбудованим AllReduce: близько 50
  • Тензорний паралелізм із NCCL: близько 60

Піки вищі. Лог llama.cpp одного прогону показує обробку промпту близько 950–970 токенів на секунду й декодування приблизно 78–84 токени на секунду:

113.07.960.883 I slot print_timing: id  0 | task 972 | prompt processing, n_tokens =   4810, progress = 0.79, t =   4.96 s / 969.87 tokens per second
113.08.772.504 I slot print_timing: id  0 | task 972 | prompt processing, n_tokens =   5580, progress = 0.92, t =   5.77 s / 966.90 tokens per second
113.09.409.053 I slot print_timing: id  0 | task 972 | prompt processing, n_tokens =   6092, progress = 1.00, t =   6.41 s / 950.75 tokens per second
...
114.19.200.672 I slot print_timing: id  0 | task 972 | n_decoded =   5384, tg =  77.34 t/s, tg_3s =  83.57 t/s
114.22.229.460 I slot print_timing: id  0 | task 972 | n_decoded =   5639, tg =  77.63 t/s, tg_3s =  84.19 t/s
114.25.259.925 I slot print_timing: id  0 | task 972 | n_decoded =   5894, tg =  77.89 t/s, tg_3s =  84.15 t/s
114.28.274.967 I slot print_timing: id  0 | task 972 | n_decoded =   6149, tg =  78.15 t/s, tg_3s =  84.58 t/s
114.31.308.233 I slot print_timing: id  0 | task 972 | n_decoded =   6405, tg =  78.38 t/s, tg_3s =  84.40 t/s
114.34.343.320 I slot print_timing: id  0 | task 972 | n_decoded =   6661, tg =  78.59 t/s, tg_3s =  84.35 t/s
114.37.376.671 I slot print_timing: id  0 | task 972 | n_decoded =   6913, tg =  78.75 t/s, tg_3s =  83.08 t/s
114.40.405.502 I slot print_timing: id  0 | task 972 | n_decoded =   7169, tg =  78.94 t/s, tg_3s =  84.52 t/s
114.43.413.731 I slot print_timing: id  0 | task 972 | n_decoded =   7421, tg =  79.09 t/s, tg_3s =  83.77 t/s
114.46.451.228 I slot print_timing: id  0 | task 972 | n_decoded =   7673, tg =  79.21 t/s, tg_3s =  82.96 t/s

Усе це на Qwen3.6-27B. Gemma-4-26B-QAT може працювати вдвічі швидше, і хоча вона, можливо, менш точна за Qwen, як асистент вона відчувається майже миттєвою.

Абсолютні цифри важать менше, ніж співвідношення. Це старе вживане залізо, яке коштувало приблизно $1,100, і ці зміни приблизно потроїли початкову продуктивність.

Чи окупається це?

Візьмімо такі припущення:

  • Вартість заліза: $1,100.
  • Ціни API: $0.36 за вхід і $2.80 за вихід за мільйон токенів для хостованої Qwen 27B на OpenRouter. Це не зовсім чесне порівняння з квантованою локальною моделлю, але якесь припущення потрібне.
  • Навантаження кодингу: близько 15 вхідних токенів на один вихідний, бо під час кодингу читають більше, ніж пишуть.
  • Обробка промпту зі швидкістю 700 токенів на секунду.
  • Генерація виводу зі швидкістю 60 токенів на секунду.

Обробка промпту й генерація йдуть одна за одною. Для 15 вхідних токенів плюс 1 вихідного час дорівнює 15 / 700 + 1 / 60, близько 0.0381 секунди. Це приблизно 393.75 вхідних і 26.25 вихідних токенів на секунду, або близько 1.42M вхідних і 94.5K вихідних токенів на годину.

Прогнати те саме навантаження через API коштувало б близько $0.51 за вхід (1.4175 помножити на $0.36) і $0.26 за вихід (0.0945 помножити на $2.80). Отже, машина замінює приблизно $0.775 витрат на API за годину активного інференсу, а точка окупності — $1,100, поділені на $0.775, приблизно 1 420 годин.

Зауважте, мова про години реального інференсу. Ніхто не ганяє модель на повну вісім годин поспіль, тож календарний строк окупності набагато довший. А електрика, обслуговування, поломки й запчастини взагалі не враховані.

То чи окупається? Цей розрахунок кожен має зробити сам. Для простого ШІ-асистента економіка непереконлива, а строк окупності такий довгий, що API може виявитися просто практичнішим. Але додайте оркестрацію агентів, завдання без нагляду й конвеєри, і картина перевертається: локальний інференс раптом виглядає значно розумніше. Тут напівавтономні конвеєри з рідкісними точками ухвалення рішень тримають машину зайнятою близько 12 годин на день, і ця цифра й далі зростає.