Чому дві відеокарти RTX 3090 дають 55 tok/s замість 220 за допомогою SGLang та DFlash2.
Детальний огляд запуску Qwen3.8-27B на двох відеокартах 3090 за допомогою SGLang, а також того, як з’єднання PCIe x4, відсутність функції P2P, обмежений кеш KV та різкі сплески потужності знизили оголошену швидкість.
Коротке відео поширилося серед прихильників самостійного хостингу. У ньому показано, як Qwen3.8-27B пише код на двох картах RTX 3090, причому швидкість генерації сягає 220 токенів на секунду. Для цього використовуються SGLang разом із DFlash2 – спекулятивним інструментом для формування тексту. Ця невелика модель одночасно пропонує сім майбутніх токенів, а велика модель приймає їх або відхиляє за один проходження, тож правильні припущення не коштують нічого.
Привабливість цього рішення полягає не лише у демонстрації. З такою швидкістю агент може використовувати локально розміщену модель, запускаючи кілька агентів для кодування одночасно на одному пристрої без необхідності API-ключів, без обмежень у оплаті та без виходу даних за межі приміщення. Домашній сервер із двома додатковими GPU класу 3090 здається ідеальним варіантом: подвоєна пропускна здатність пам’яті гарантує швидкість у 220 токенів на секунду.
Але все не так просто. Причини цього є ще більш показовими, ніж саме відео.
Встановлення SGLang
Усі, хто звик до llama.cpp, де один бінарний файл та один файл моделі дозволяють налагодити чат за десять хвилин, вважатимуть SGLang скромнішим у своїх можливостях. Ось послідовність дій на сервері Ubuntu.
Крок 1: середовище Python. U SGLang є велика дерево залежностей, тому після клонування репозиторію створюється власне віртуальне середовище на Python 3.12.
git clone https://github.com/sgl-project/sglang.git
cd sglang
python3.12 -m venv .venv
source .venv/bin/activate
Крок 2: уперта спроба встановлення. Перша справжня спроба встановлення тривала близько 39 хвилин, заповнивши кеш колесом більшою частиною контенту PyPI, після чого була скасована через помилку.
RuntimeError: cargo is required to discover the Rust extension modules
Компілятор Rust для сервера інференсу GPU. Новіші версії SGLang містять необов’язкові розширення для Rust, і інструменти збірки відмовляються продовжувати роботу без cargo, хоча двигун ніколи їх не використовує. Встановлення однієї змінної середовища дозволяє їх проігнорувати.
SGLANG_BUILD_RUST_EXTS=none pip install -e "./python[all]"
Друга спроба вимагала ще приблизно 30 хвилин, після чого завершилася без проблем. Результатом став sglang 0.5.20, torch, скомпільований для CUDA 13, та triton, автоматично налаштований на версію 3.7.1, що саме потрібно GPU Ampere для уникнення відомої помилки ядра.
Крок 3: моделі. Згідно з рекомендаціями спільноти необхідно завантажити два файли:
cyankiwi/Qwen3.8-27B-AWQ-INT4— це основна модель. Квантація INT4 зменшує її розмір до приблизно 20 ГБ у 5 частин, що достатньо для двох GPU об’ємом 24 ГБ.z-lab/Qwen3.8-27B-DFlash2— це пробна версія, розміром близько 3.9 ГБ.
Крок 4: запуск. Для цього достатньо одної команди, де TP=2 означає паралелізм тензорів між обома GPU моделі 3090.
CUDA_VISIBLE_DEVICES=1,2 python -m sglang.launch_server \
--model-path /mnt/models/Qwen3.8-27B-AWQ-INT4 \
--tp 2 \
--speculative-algorithm DFLASH \
--speculative-draft-model-path /mnt/models/Qwen3.8-27B-DFlash2 \
--speculative-num-draft-tokens 8 \
--mamba-radix-cache-strategy extra_buffer \
--disable-prefill-cuda-graph \
--cuda-graph-max-bs-decode 16 \
--mem-fraction-static 0.85 \
--context-length 65536 \
--reasoning-parser qwen3 \
--host 0.0.0.0 --port 30000
Уважно стежте за двома підступами. Прапорець під назвою --cuda-graph-max-bs заважає запуску, оскільки у новіших версіях його перейменували на окремі варіанти для декодування та попереднього заповнення. Крім того, відсутність параметра --reasoning-parser qwen3 змушує API повертати сирі дані про міркування моделі з вкладеними тегами think у звичайному полі контенту. Додавання цього парсера розділяє результат на організовані поля reasoning_content та content, що є тим самим форматом, який використовують інші сервери, сумісні з OpenAI, і що буде корисно для кожного, хто пише клієнт.
Крок 5: очікування. Запуск під час першої спроби тривав близько шести хвилин, поки не з’явилось повідомлення про готовність. Нічого не зламалося: SGLang своєчасно компілює ядра GPU через Triton та записує графи CUDA на кожній картці. Подальші запуски використовують кеш та завершуються приблизно за 35 секунд. Стрілка прогресу, яка здається застиглою під час першого запуску, — це просто процес компіляції.
Що показали вимірювання
Сервер працював. Він відповідав на запити, транслював reasoning_content та приймав з’єднання агентів. Ось цифри, виміряні безпосередньо на самій машині:
| Metric | Min | Max | Average |
|-------------------------|----------|-----------|-----------|
| Decode speed | 47 t/s | ~100 t/s | ~55 t/s |
| Prompt prefill | 342 t/s | 466 t/s | ~428 t/s |
| Accept length (DFlash) | 3.2 | 6.8 | ~4 |
А ось зображення ємності:
| Capability | Advertised limit | Reality |
|-------------------------|---------------------------|-----------------------|
| Max context per request | 65,536 (I set it) | fits, one at a time |
| Total KV pool | ~131K tokens max | 103K at default flags |
| Parallel slots | 48 concurrent requests | 3–6 with real prompts |
Це приблизно 55 токенів на секунду в середньому, а не 220. Проблема полягає у тому, що одна картка RTX 3090 Ti з програмою llama.cpp досягає приблизно 75 токенів на секунду з тією ж сім’єю моделей. Дві картки з підтримкою тензорного паралелізму та складним інструментом формування тексту програють одній картці, яка використовує старіший підхід.
Рівні прийняття даних пояснюють частину цих коливань. Коли алгоритм DFlash правильно передбачає результат, приймаючи 6,8 з 8 токенів, швидкість на деякий час досягає близько 100 токенів на секунду. Коли ж він помиляється, швидкість знижується до 47. Навіть найкращий пакет даних значно не наближається до показника 220, тож проблемою не був сам інструмент формування тексту. Швидкість знижувалась через щось інше.
Підказка у журналі
Підказка весь час була у журналі запуску:
Setup Custom allreduce failed with CUDART error: peer access is not
supported between these two devices.
За принципом паралелізму тензорів кожна матриця ваг розподіляється між двома GPU, а часткові результати необхідно об’єднувати після кожного блоку уваги та кожного блоку MLP. У моделі обсягом 27 млрд параметрів це означає приблизно 100 поїздок туди-назад для кожного створеного токена. Для такої швидкості об’єднання потрібен швидкий зв’язок між картками, але у цій парі його не було. Два окремі недоліки посилювали один одного.
Відсутній peer-to-peer зв’язок. Карти GeForce більше не підтримують офіційно GPU-direct P2P, тому кожен обмін даними відбувається через системну пам’ять: з GPU 1 у пам’ять, далі до GPU 2 та назад. Це подвоює кількість копій та збільшує затримку утричі.
Вузький зв’язок – справжній ворог. Перевірка стану фізичного з’єднання розкриває суть проблеми:
/sys/bus/pci/devices/05:00.0/current_link_width → 4 (max 16)
/sys/bus/pci/devices/09:00.0/current_link_width → 4 (max 16)
Обидві відеокарти працювали за протоколом PCIe x4, а не x16. Чотири канали зв’язку через міст між пристроями забезпечують швидкість передачі даних лише 1–2 ГБ/с між картами, проте обсяг даних, який потрібно передати, перевищує цей ліміт приблизно у 100 разів для кожного пакета даних. Відеокарти перебували у стані очікування під час копіювання даних. Це пояснює те, чому швидкість попереднього заповнення даних становить 428 т/с замість очікуваних 1 200 або більше, а процес декодування — близько 55.
Відеокарти є швидкими. Кабель між ними є повільним. У режимі тензорного паралелізму кабель фактично стає частиною самої моделі.
Як хтось досягає 220
Ніхто не демонструє свою материнську плату під час демонстрації. Добре підключена пара слотів x16 із функціональним режимом P2P забезпечує приблизно в десять разів більшу пропускну здатність між картами, ніж у цій конфігурації. Вказана цифра, ймовірно, стосується короткочасної роботи з використанням прогнозованого коду, з активними кешами та ідеальними умовами для виконання завдань, а не постійної швидкості роботи. Програмний склад цієї конфігурації є надійним, але він мовчки передбачає наявність обладнання, яке є рідкістю серед домашніх комп’ютерів.
Ще два висновки щодо повсякденного використання
Кімната контексту менша, ніж очікувалося. Дві картки по 24 ГБ здаються достатніми. Однак потрібно вирахувати ваги INT4 (приблизно 10 ГБ на кожній картці), модуль формування тексту, графи CUDA, а також пам’ять для рекурентного стану через використання гібридної уваги у Qwen3.8. У результаті для кешу KV залишилося приблизно 103 тис. токенів загалом, або 131 тис. при більш ризикованих налаштуваннях. Одна заявка на 65 тис. токенів вписується без проблем. Оголошені 48 одночасних сеансів не є реалістичними, оскільки кожна активна розмова також використовує частину пам’яті стану mamba, і насправді може вестися лише три–шість сеансів. На відміну від цього, llama.cpp передає майже всю VRAM одній моделі та без проблем обробляє великі контексти.
Перезавантаження через проблеми з живленням. Коли обидві картки 3090 працюють одночасно, виконуючи завдання з попереднього заповнення та створення документів під повною навантажувальною силою тензорного паралелізму, споживання енергії стрімко зростає від рівня безнавантаження до приблизно 700 ват протягом кількох мілісекунд. Блоки живлення погано справляються з таким навантаженням. Один такий сплеск призвів до активації захисту та вимкнення сервера посеред обробки даних. Він автоматично перезавантажився, але урок залишається: для такої навантажувальної ситуації потрібен якісний блок живлення з достатньою запасною потужністю, а вказана на упаковці потужність недостатня.
Чи варто пробувати це вдома?
SGLang плюс DFlash2 — це вражаючий інженерний рішення, якщо його припущення справджуються, а ці припущення полягають у переліку вимог до апаратного забезпечення:
- Обидві відеокарти у справжніх слотах x16. Блоки для ігор для пристроїв користувачів зазвичай мають одну відеокарту, можливо, з другою, підключеною через чіпсет за інтерфейсом x4. Обов’язково прочитайте інструкцію перед покупкою, адже саме цей параметр визначає різницю між 220 та 55.
- Функціональна комунікація між відеокартами. Це означає наявність функцій Resizable BAR та налаштувань ACS у BIOS, а також форкованих драйверів для GeForce чи NVLink поза межами продукції для пристроїв користувачів. На практиці це сфера серверних материнських плат.
- Високоякісний блок живлення з достатньою потужністю. Раптові піки навантаження від двох карт потужністю 350 Вт зрештою призведуть до несправності слабкого блоку живлення.
- Чотири відеокарти RTX 3090 для роботи з Qwen3.8-27B у повному обсязі контексту. Завдяки такому бюджету кеш KV перестає бути обмеженням, на відміну від скороченого обсягу кешу 103K.
Програмне забезпечення такого типу не прощає дешевого обладнання. Воно працюватиме повільніше там, де ніхто не подумав перевірити.
Незважаючи на відсутність як програмного, так і апаратного забезпечення, яке він передбачає, llama.cpp все одно залишається розумним вибором. Це означає одну карту, один бінарний файл, приблизно 75 токенів на секунду через звичайний PCIe, відсутність інструментарію Rust та непланових перезавантажень. Пара GPU може знову працювати незалежно з прискоренням x4.
Цифра 220 була правдивою. Вона просто не стосується цієї системи, і, ймовірно, не стосуватиметься багатьох інших, поки не буде перевірено з’єднання між картами.