Как разогнать Qwen 3.8-27B на Tesla M10: от 8 до 50 токенов в секунду

Мы ускорили Qwen 3.8-27B на старой Tesla M10 в шесть раз на вход и в четыре на выход. Обработка промпта выросла с 7,9 до ~50 токенов в секунду, генерация — с 1,8 до 7–8 токенов в секунду, контекст — 192 тысячи токенов.

Nvidia Tesla M10 — серверная карта 2016 года для VDI: четыре чипа GM107 (Maxwell, sm_50) по 8 ГБ GDDR5 на одной плате. На вторичном рынке она стоит копейки, но для языковых моделей её обычно не рассматривают: архитектура старая, память медленная, между чипами нет быстрой связи. Мы проверили, сколько из неё можно выжать на современной 27-миллиардной модели с агентным клиентом поверх.

Итоговая конфигурация: Qwen 3.8-27B в квантизации Q4_K_M (16,5 ГБ), llama.cpp с тензорным разбиением на четыре GPU, KV-кеш q8_0 в видеопамяти, встроенное MTP-предсказание, разогнанный VBIOS и асинхронный обмен между GPU.

Стенд и стартовая точка

Первый рабочий запуск давал 7,9 токена в секунду на вход и 1,77 на выход: ответ в пару абзацев писался минутами.

Компонент

Что стоит

Сервер

два Xeon на C612, Debian 12, ядро 6.1

GPU

Tesla M10: 4 × GM107 (sm_50), 4 × 8 ГБ GDDR5, 53 Вт на чип

Шина

слот PCIe 3.0 x16 → коммутатор PLX 8747 → по x8 на каждый чип

Драйвер

NVIDIA 550.54.14, CUDA 12.4

Модель

Qwen 3.8-27B, GGUF Q4_K_M (unsloth), 16,5 ГБ, родной контекст 262 144

Инференс

llama.cpp, собранный для sm_50

Модель не помещается ни в один чип, поэтому её нужно делить на четыре GPU. Первый профиль резал её по слоям (--split-mode layer), держал KV-кеш в оперативной памяти (--no-kv-offload), чтобы влезло окно 256k, и работал с ubatch 64.

У такой схемы три узких места:

  • При разбиении по слоям в каждый момент работает один чип из четырёх.

  • Внимание считается на CPU над кешем в RAM.

  • Напрямую чипы обмениваться не умеют: nvidia-smi topo -p2p показывает «not supported» для всех пар, любая копия идёт через память хоста.

Как улучшали

Самый большой рывок дало тензорное разбиение: четыре чипа начали считать одновременно, и prefill вырос с 15 до 40 токенов в секунду.

Этап

Prefill, ток/с

Генерация, ток/с

Старт: разбиение по слоям, KV в RAM, 256k

7,9

1,77

KV-кеш q8_0 на GPU, flash attention, MTP, 128k

15,1

3,14

Тензорное разбиение, ubatch 512

39,7

7,18

Асинхронные копии между GPU

48,2

7,90

Контекст 192k (текущий профиль)

49,4

6,98–7,73

Prefill измерялся на промптах от 1,4 до 8,4 тысячи токенов, генерация — на задачах с кодом при температуре 0. Первые две строки сняты ранней версией теста, поэтому сравнивать их с остальными стоит по порядку величины.

1. KV-кеш на GPU и MTP

Qwen 3.8 — гибридная модель: полное внимание есть только в каждом четвёртом из 65 слоёв, остальные линейные (SSM). Поэтому KV-кеш маленький, и в формате q8_0 он помещается в видеопамять рядом с весами.

Модель также несёт слой MTP (multi-token prediction), который llama.cpp использует как встроенный черновик (--spec-type draft-mtp): никакой отдельной маленькой модели не нужно.

2. Тензорное разбиение

--split-mode tensor делит каждую матрицу на четыре части, и все чипы работают над каждым слоем. Режим в llama.cpp помечен как экспериментальный, но на M10 работает стабильно. Для сравнения: лучшая настройка разбиения по слоям (batch 1024, ubatch 256) давала всего 17,1 ток/с prefill.

3. Разгон через VBIOS

Заводской VBIOS ограничивает напряжение 1,14375 В, из-за этого чипы в нагрузке стояли на 1149–1175 МГц. Мы подняли потолок до 1,2 В (максимум таблицы напряжений) и boost до 1228 МГц. В нагрузке частоты стали 1176–1202 МГц, потребление осталось в пределах 53 Вт.

Выигрыш небольшой, пара процентов, а риск заметный: стоковый nvflash такие образы не принимает из-за подписи, нужна сборка с обходом проверки.

4. Асинхронные копии между GPU

При тензорном разбиении чипы после каждого слоя складывают свои части (all-reduce). Наша сборка была с GGML_CUDA_NO_PEER_COPY=ON, и каждая такая копия блокировала вычисления. Инструментированная сборка показала: из ~92 мс на проход 21 мс уходило на обмен.

После пересборки с асинхронными cudaMemcpyPeerAsync обмен занимает 11 мс. P2P на M10 по-прежнему нет, драйвер гонит данные через память хоста, но теперь параллельно со счётом.

5. Кеш промпта

Для диалога главное не скорость первого prefill, а то, что история не пересчитывается. С cache_prompt: true в запросе первый ответ на промпт в 7,6 тысячи токенов приходит через 165 секунд. Каждый следующий вопрос в том же диалоге — через 8–9 секунд: заново считаются только 25–27 новых токенов.

6. MTP с одним черновым токеном и контекст 192k

--spec-draft-n-max 1 дал +12,6% на коротких ответах (6,40 → 7,20 ток/с), а на ответах в 600 токенов разница в пределах шума. Доля принятых черновиков выросла с ~78% до ~94%.

Контекст 196 608 токенов скорость не снизил: в одной и той же загрузке стенда 128k дал 7,04 ток/с генерации, 192k — 6,98.

Что не сработало и подводные камни

Половина попыток не дала ничего, а самые дорогие проблемы оказались не в модели, а в BIOS и драйверах.

Попытка

Результат

Контекст 262 144 на GPU

CUDA out of memory на первом же запросе: KV-кеш занимает 8037 из 8192 МиБ, рабочим буферам не хватает места

--split-mode row

«CUDA0 does not support split buffers» в этой сборке

Четыре параллельных слота

5,3 ток/с суммарно, 1,5 на запрос, память почти кончилась

Разгон до 1306 МГц

нужно ~1,306 В, а максимум таблицы напряжений 1,2 В

Настоящий P2P между чипами

недоступен: драйвер сообщает «not supported» для всех пар, а на коммутаторе включён ACS

PCIe x16 до чипов

невозможно: каждый GM107 разведён на плате по x8, x16 есть только до коммутатора

Два камня стоит знать заранее:

  • nouveau. Если его не запретить, он захватывает GPU при загрузке. Отвязка на ходу через sysfs один раз сработала, а в другой раз уронила ядро (NULL dereference в nouveau_bo_move): modprobe nvidia завис, и сервер пришлось перезапускать по питанию.

  • Охлаждение. M10 пассивная, ей нужен хороший активный обдув: серверные вентиляторы на высоких оборотах или отдельный вентилятор на плату(Охлаждение для видеокарты NVIDIA TESLA M10, 80мм). При нормальном обдуве чипы в нагрузке держали 40–53 °C, а со слабым быстро доходят до порога замедления 91 °C (аварийное отключение — 96 °C). Разница между самым холодным и самым горячим чипом на одной плате доходит до 40 °C, так что смотрите на максимум, а не на среднее.

Инструкция по настройке

Ниже всё, чтобы повторить текущий профиль на Debian 12 с Tesla M10. Разгон VBIOS сюда не входит: он даёт пару процентов и требует прошивки модифицированного образа.

1. BIOS

  • Включите Above 4G Decoding: без него четыре GPU не получат 64-битные BAR.

  • Проверьте режим вентиляторов: карта пассивная, нужен режим для GPU-нагрузки или полные обороты.

После загрузки убедитесь, что у каждого GPU есть Region 1 и Region 3:

sudo lspci -vv -d 10de:13bd | grep -E "Region (1|3)"

2. Драйвер и nouveau

Запретите nouveau до установки драйвера и не отвязывайте его на ходу:

printf 'blacklist nouveau\noptions nouveau modeset=0\n' | sudo tee /etc/modprobe.d/blacklist-nouveau.conf
sudo update-initramfs -u

Установите драйвер NVIDIA 550 (мы используем 550.54.14) и перезагрузитесь. Проверка: nvidia-smi -L показывает четыре Tesla M10.

3. Сборка llama.cpp

Нужны CUDA 12.4 (старшая версия, которую поддерживает драйвер 550; sm_50 в ней ещё собирается) и CMake. Главное — GGML_CUDA_NO_PEER_COPY=OFF, иначе обмен между GPU станет блокирующим:

cmake -S llama.cpp -B build -DCMAKE_BUILD_TYPE=Release \
  -DCMAKE_CUDA_ARCHITECTURES=50-real -DGGML_CUDA=ON -DGGML_NATIVE=OFF \
  -DGGML_CUDA_FA=ON -DGGML_CUDA_GRAPHS=ON -DGGML_CUDA_NO_VMM=ON \
  -DGGML_CUDA_NO_PEER_COPY=OFF -DGGML_CUDA_NCCL=OFF
cmake --build build -j --target llama-server

4. Модель

Файл Qwen3.8-27B-UD-Q4_K_M.gguf из репозитория unsloth/Qwen3.8-27B-GGUF на Hugging Face, 16,5 ГБ. Сверьте SHA-256 с опубликованным.

5. Запуск

CUDA_DEVICE_ORDER=PCI_BUS_ID ./build/bin/llama-server \
  --model Qwen3.8-27B-UD-Q4_K_M.gguf --ctx-size 196608 \
  --gpu-layers 99 --split-mode tensor --tensor-split 1,1,1,1 \
  --batch-size 512 --ubatch-size 512 --flash-attn on \
  --cache-type-k q8_0 --cache-type-v q8_0 \
  --spec-type draft-mtp --spec-draft-n-max 1 \
  --threads 10 --threads-batch 20 --jinja --no-mmproj \
  --host 127.0.0.1 --port 8080 --parallel 1 --alias qwen3.8-27b

Сервер поднимается за 5–50 секунд и занимает ~7 ГБ на каждом GPU. Если нужен запас памяти, поставьте --ctx-size 131072: освободится ~600 МиБ на GPU. Значение --threads подберите под свои процессоры.

6. Проверка

curl -s http://127.0.0.1:8080/health
nvidia-smi --query-gpu=index,temperature.gpu,clocks.sm,power.draw,memory.used --format=csv -l 5

Во время длинного prefill следите за самым горячим чипом: выше 85 °C — сигнал проверить охлаждение.

7. Подключение клиента

Подходит любой клиент OpenAI API на той же машине с адресом http://127.0.0.1:8080/v1 и моделью qwen3.8-27b. Для агента достаточно такого провайдера:

[providers.local-qwen]
type = "openai-compatible"
base_url = "http://127.0.0.1:8080/v1"
default_model = "qwen3.8-27b"
requires_api_key = false
extra_body = { chat_template_kwargs = { enable_thinking = false } }

[[providers.local-qwen.models]]
context_window = 196608

Два совета для агентов:

  • Поставьте stream_idle_timeout_secs в несколько минут: первый prefill большого контекста идёт долго.

  • Урежьте набор инструментов: их описания идут в каждый запрос, и при 50 ток/с лишние 10 тысяч токенов — это три минуты ожидания.

Итоги

Дешёвая M10 тянет 27B-модель с контекстом 192k на скорости ~50 ток/с на вход и 7–8 ток/с на выход. Этого хватает для чата и неспешного агента с кешем истории, но не для интерактивной работы с целым репозиторием: промпт в 100 тысяч токенов считается полчаса.

Больше всего дали не тонкие настройки, а два решения: держать всё в видеопамяти и заставить четыре чипа считать одновременно. Дальше упираемся в обмен через память хоста (11 мс из ~84 на проход) и в сами чипы Maxwell.

Комментарии

Загрузка комментариев…

Оставить комментарий