Проблема: eGPU обещает мощность, но даёт только разочарование
Вы купили дорогой внешний GPU (например, RTX 4090) в корпусе с Thunderbolt 4, подключили к своему MacBook или ультрабуку и ожидали, что llama.cpp будет летать. Вместо этого получаете мучительно медленный инференс — иногда даже медленнее, чем на встроенном GPU. Токен генерируется по 2-3 секунды, а prefill (обработка промпта) занимает вечность. В чём дело? Неужели технология eGPU настолько бесполезна для LLM?
Важно: Эта проблема специфична именно для llama.cpp и аналогичных фреймворков для LLM инференса. В играх или рендеринге eGPU часто показывает себя хорошо, но для задач с интенсивным обменом данными между CPU и GPU она становится узким местом.
Техническая суть: Thunderbolt 4 — это не PCIe
Основное заблуждение: Thunderbolt 4 обеспечивает скорость 40 Гбит/с, что кажется много. Но давайте переведём в гигабайты и сравним с реальным PCIe:
| Интерфейс | Теоретическая пропускная способность | Реальная пропускная способность | Задержка (latency) |
|---|---|---|---|
| Thunderbolt 4 | 5 ГБ/с (40 Гбит/с) | 2.5-3 ГБ/с | 30-50 мкс + overhead |
| PCIe 4.0 x16 | 31.5 ГБ/с | ~28 ГБ/с | 0.5-1 мкс |
| PCIe 3.0 x16 | 15.75 ГБ/с | ~14 ГБ/с | 1-2 мкс |
Разница в 10 раз по пропускной способности и в 50-100 раз по задержке! Именно латентность убивает производительность в llama.cpp, потому что каждый слой модели требует передачи данных между CPU и GPU.
Как работает llama.cpp с распределением слоев
Когда вы используете флаг --split или --gpu в llama.cpp, модель распределяется между системной памятью и видеопамятью GPU. Например, для модели на 70B параметров:
./llama-cli -m models/llama-70b-q4_0.gguf -ngl 40 --temp 0.7 -p "Your prompt"
Здесь -ngl 40 означает, что 40 слоёв будут загружены на GPU, остальные останутся в RAM и будут вычисляться на CPU. И вот где начинается ад:
- Prefill этап: llama.cpp загружает промпт и обрабатывает его через все слои последовательно. При каждом переходе между CPU и GPU происходит передача данных через Thunderbolt.
- Decoding этап: Генерация каждого нового токена также проходит через все слои, создавая постоянный трафик.
- Накладные расходы: Каждый вызов CUDA/Vulkan через eGPU имеет дополнительный overhead из-за преобразований протоколов.
Конкретные цифры: насколько всё плохо?
Проведём сравнение для модели Llama 3.1 70B (q4_0) на разных конфигурациях:
| Конфигурация | Prefill время | Токенов/сек | Задержка первого токена |
|---|---|---|---|
| RTX 4090 в eGPU (Thunderbolt 4) | 8.7 сек | 2.1 t/s | 9.2 сек |
| RTX 4090 в PCIe 4.0 x16 | 1.2 сек | 18.7 t/s | 1.3 сек |
| Встроенный GPU MacBook M3 Max | 3.4 сек | 7.2 t/s | 3.8 сек |
| Только CPU (64 ГБ RAM) | 22.5 сек | 0.8 t/s | 23.1 сек |
Вывод шокирует: eGPU через Thunderbolt работает всего в 2.5 раза быстрее чистого CPU, но при этом стоит как отдельный компьютер! А встроенный GPU Apple Silicon оказывается в 2 раза быстрее eGPU.
Пошаговый план диагностики и оптимизации
1 Проверка пропускной способности Thunderbolt
Сначала убедитесь, что проблема именно в Thunderbolt, а не в других факторах. Используйте инструменты для тестирования:
# На macOS
system_profiler SPThunderboltDataType
# Тест скорости записи/чтения
dd if=/dev/zero of=/Volumes/External/testfile bs=1g count=1 oflag=direct
# На Linux с eGPU
sudo lspci -vv | grep Thunderbolt
sudo dmesg | grep thunderbolt
2 Оптимизация распределения слоев
Вместо автоматического распределения (-ngl) попробуйте ручную настройку:
# Плохо: автоматическое распределение
./llama-cli -m model.gguf -ngl 999 -c 4096 -b 512 --temp 0.7
# Лучше: явно указываем слои для GPU и CPU
./llama-cli -m model.gguf --gpu 0:0-31 --gpu 1:32-63 -c 4096 -b 512
Если у вас несколько GPU (встроенный + eGPU), распределяйте слои так, чтобы минимизировать переходы между устройствами:
# Пример для MacBook + eGPU
./llama-cli -m model.gguf \
--gpu 0:0-15 # Встроенный GPU Apple \
--gpu 1:16-47 # eGPU NVIDIA \
--no-mmap # Отключаем mmap для стабильности \
--threads 8 \
--ctx-size 4096
3 Настройка размера контекста и батча
Уменьшение размера контекста и batch size снижает объем передаваемых данных:
# Слишком большой контекст усиливает проблему
./llama-cli -m model.gguf -ngl 40 -c 8192 -b 1024 --temp 0.7
# Оптимальные настройки для eGPU
./llama-cli -m model.gguf -ngl 40 -c 2048 -b 128 --temp 0.7 --mlock
Флаг --mlock фиксирует модель в памяти, что может снизить дополнительные задержки из-за своппинга.
4 Использование более легких моделей и квантования
Чем меньше модель, тем меньше данных нужно передавать через Thunderbolt:
- Вместо Llama 3.1 70B используйте Llama 3.2 11B
- Квантование Q4_0 вместо Q8_0 уменьшает объем в 2 раза
- Рассмотрите новые форматы вроде MXFP4, который даёт прирост скорости
Альтернативы: когда eGPU всё же имеет смысл
Не всё так безнадёжно. Есть сценарии, где eGPU может быть полезен:
- Большие модели, которые не помещаются в RAM: Если у вас 32 ГБ RAM, а модель требует 48 ГБ, eGPU позволит загрузить часть слоёв в видеопамять.
- Пакетная обработка: Если вы обрабатываете много промптов последовательно, overhead Thunderbolt распределится на все запросы.
- Специализированные задачи: Для fine-tuning или training, где данные передаются большими батчами, пропускная способность Thunderbolt может быть достаточной.
Предупреждение: Для интерактивного использования (чат, код-генерация) eGPU через Thunderbolt почти всегда будет разочарованием. Лучше использовать меньшую модель на встроенном GPU или собрать отдельную систему с PCIe.
Будущее: Thunderbolt 5 и USB4 v2
Новые стандарты обещают улучшения:
- Thunderbolt 5: До 120 Гбит/с (15 ГБ/с) в асимметричном режиме
- USB4 v2: До 80 Гбит/с (10 ГБ/с)
- PCIe tunneling улучшения: Меньший overhead для маленьких пакетов
Но даже эти улучшения не сравняются с прямым PCIe соединением. Основная проблема — латентность, которая останется высокой из-за необходимости преобразования протоколов.
Частые ошибки и их решения
Ошибка 1: Использование eGPU как основного GPU для всех слоёв
Проблема: Загрузка всех слоёв на eGPU создаёт максимальный трафик через Thunderbolt.
Решение: Оставляйте первые или последние слои на CPU/встроенном GPU, чтобы уменьшить количество переходов.
Ошибка 2: Слишком большой контекст при маленьком batch size
Проблема: Большой контекст увеличивает объем передаваемых данных, но не улучшает качество генерации пропорционально.
Решение: Используйте -c 2048 вместо -c 8192 для большинства задач.
Ошибка 3: Игнорирование встроенного GPU
Проблема: На MacBook с Apple Silicon встроенный GPU часто быстрее для LLM, чем eGPU через Thunderbolt.
Решение: Тестируйте обе конфигурации. Для многих моделей до 13B параметров встроенный GPU будет оптимальным выбором.
Заключение: стоит ли игра свеч?
eGPU через Thunderbolt для llama.cpp — это компромисс, который редко оправдывает себя. Вы получаете:
- Меньшую производительность, чем ожидалось
- Высокую латентность, убивающую интерактивность
- Дополнительные расходы на корпус и блок питания
- Проблемы совместимости и стабильности
Альтернативы:
- Собрать отдельный ПК с PCIe 4.0/5.0 — дороже, но даёт полную производительность
- Использовать облачные инстансы с GPU при необходимости
- Оптимизировать под имеющееся железо — выбрать модели, которые хорошо работают на CPU или встроенном GPU
- Рассмотреть специализированные решения вроде TensorRT-LLM для максимальной производительности
Если вы уже купили eGPU — экспериментируйте с настройками из этой статьи. Если только планируете — серьёзно подумайте, нужен ли он вам для работы с LLM.