中文翻译暂不可用,显示俄语原文。
Как вы дебажите низкую GPU utilization (например, 40% на A100)?
Краткий тезис
Низкая утилизация GPU (40% на A100) — сигнал, что модель или пайплайн неэффективно используют вычислительные ресурсы. Основные причины: memory-bound операции (особенно в decode-стадии), overhead|launch overhead|launch overhead|launch overhead|launch kernel overhead|launch overhead|launch overhead, неоптимальное batching, узкое место по передаче данных (PCIe vs NVLink) и CPU bottleneck. Дебаг начинается с профилирования (Nsight Systems, Nsight Compute), анализа warp stall reasons и последовательного применения оптимизаций: увеличение batch size, speculative decoding, CUDA graphs, continuous batching, асинхронный preprocessing.
1. Термин: GPU Utilization (утилизация GPU)
Что это Процент времени, в течение которого вычислительные блоки GPU (SM, Streaming Multiprocessors) заняты полезной работой. Утилизация 40% означает, что GPU простаивает 60% времени.
Почему это плохо
- Вы платите за A100, а используете меньше половины её мощности.
- Низкая утилизация → низкий throughput (запросов/сек) и высокий latency на запрос.
- Часто указывает на узкое место вне GPU (CPU, память, шина).
Важно Утилизация 100% не всегда цель — для latency-sensitive сервисов может быть выгодно держать запас. Но 40% — явный признак проблемы.
2. Основные причины низкой GPU utilization
2.1 Memory-bound (decode stage)
Memory-bound — операция, скорость которой ограничена пропускной способностью памяти (HBM), а не вычислительной мощностью. В decode-стадии генерации LLM каждый шаг обрабатывает один токен, batch size часто мал (1–4). Арифметическая интенсивность (FLOPs / bytes) низкая — GPU тратит время на загрузку весов из памяти, а не на вычисления.
Признаки
- В профилировщике (ncu) видно, что warp stalls по причине
long scoreboard(ожидание данных из памяти). - Occupancy (загрузка SM) может быть высокой, но утилизация низкая.
Решения
- Увеличить batch size — больше запросов обрабатывается параллельно, повышается арифметическая интенсивность.
- Использовать speculative decoding — генерировать несколько токенов за шаг с помощью маленького draft-модели, уменьшая число обращений к большой модели.
- Применить FlashAttention — оптимизирует чтение/запись в HBM.
2.2 Kernel launch overhead
Каждый вызов ядра CUDA (kernel launch) требует накладных расходов CPU: подготовка аргументов, отправка команд в GPU. Если ядер много и они маленькие (например, element-wise операции, LayerNorm), overhead становится значительным.
Признаки
- В Nsight Systems видно много маленьких «пустых» промежутков между kernel.
- CPU utilization высок, но GPU простаивает.
Решения
- CUDA graphs — захват последовательности ядер в граф, который запускается одной командой. Убирает kernel launch overhead.
- Fusion — объединение нескольких маленьких ядер в одно (например, fused kernel для LayerNorm + residual).
2.3 Неоптимальное batching
Static batching (фиксированный batch size) приводит к тому, что GPU простаивает, пока набирается batch. Continuous batching (динамическое пакетирование) — добавляет запросы в batch по мере поступления, не дожидаясь заполнения.
Признаки
- В логах видно, что batch size часто меньше максимального.
- GPU utilization падает при низкой нагрузке.
Решения
- Внедрить continuous batching (поддерживается в vLLM, TensorRT-LLM).
- Настроить dynamic batching в inference-сервере (Triton Inference Server).
2.4 Data transfer bottleneck
Передача данных между CPU и GPU через PCIe медленнее, чем через NVLink (прямое соединение GPU-GPU). Если модель или данные часто копируются туда-сюда, утилизация падает.
Признаки
- В Nsight Systems видны большие блоки
MemCpy(H2D или D2H). - Используется
pin_memory=False(медленные копии).
Решения
- Использовать NVLink для multi-GPU (если доступно).
- Минимизировать копирования: передавать данные заранее (prefetch), использовать CUDA streams для асинхронных копий.
- Для inference: держать модель в GPU, не выгружать.
2.5 CPU bottleneck
Preprocessing (токенизация, эмбеддинги) или postprocessing (декодирование) выполняются на CPU и не успевают за GPU. GPU простаивает в ожидании данных.
Признаки
- CPU utilization 100%, GPU utilization низкая.
- В профиле видны длинные промежутки между batch.
Решения
- Асинхронный preprocessing — использовать несколько CPU-потоков (DataLoader с
num_workers). - Pipeline parallelism — разделить preprocessing и inference на разные потоки.
- Для LLM: использовать token streaming (например, Hugging Face
TextStreamer).
3. Инструменты профилирования
| Инструмент | Назначение | Команда |
|---|---|---|
| nvidia-smi | Быстрый просмотр утилизации, памяти, температуры | nvidia-smi -l 1 |
| Nsight Systems (nsys) | Системное профилирование: kernel, memcpy, CPU activity | nsys profile -o output -t cuda,nvtx python script.py |
| Nsight Compute (ncu) | Детальный анализ ядер: warp stall, occupancy, memory throughput | ncu --set full -o output python script.py |
| dcgmi (DCGM) | Мониторинг кластера GPU | dcgmi monitor -e 1001,1002 |
Пример использования ncu для поиска warp stall:
ncu --set full --target-processes all -o profile python run_inference.py
После открытия отчёта в Nsight Compute смотрим раздел Warp Stall Reasons:
long scoreboard— ожидание данных из памяти (memory-bound)not selected— warp не выбран из-за занятости ресурсов (occupancy issue)waiting— синхронизация
4. Пошаговая методика дебага
Шаг 1: Быстрая диагностика
nvidia-smi -l 1
Смотрим:
- GPU-Util: 40%
- Memory-Usage: если низкая (<50%) — проблема не в памяти.
- Volatile GPU-Util: может быть 0% → kernel launch overhead.
Шаг 2: Системное профилирование (nsys)
nsys profile -o trace -t cuda,nvtx python run.py
В Nsight Systems ищем:
- Большие промежутки между kernel → kernel launch overhead.
- Длинные MemCpy → data transfer bottleneck.
- CPU activity между batch → CPU bottleneck.
Шаг 3: Детальный анализ ядер (ncu)
ncu --set full -o kernel python run.py
В Nsight Compute:
- Memory Throughput vs Compute Throughput — если memory throughput насыщен, а compute нет → memory-bound.
- Occupancy — если низкая (<50%), возможно, слишком много регистров или shared memory.
- Warp Stall Reasons — основной индикатор.
Шаг 4: Проверка batch size и batching strategy
- Логируйте batch size в каждом forward pass.
- Если batch size часто = 1 → проблема decode stage.
- Если batch size колеблется → внедрите continuous batching.
Шаг 5: Проверка CPU bottleneck
- Замерьте время preprocessing (токенизация) и inference.
- Если preprocessing > inference → CPU bottleneck.
5. Решения в таблице
| Причина | Инструмент диагностики | Решение |
|---|---|---|
| Memory-bound (decode) | ncu: warp stall long scoreboard | Увеличить batch size, speculative decoding, FlashAttention |
| Kernel launch overhead | nsys: много маленьких kernel | CUDA graphs, kernel fusion |
| Неоптимальное batching | Логи batch size | Continuous batching (vLLM, TensorRT-LLM) |
| Data transfer bottleneck | nsys: длинные MemCpy | NVLink, асинхронные копии, prefetch |
| CPU bottleneck | nsys: CPU activity между batch | Асинхронный preprocessing, pipeline parallelism |
6. Дополнительные оптимизации
- FlashAttention — снижает memory reads/writes в attention.
- PagedAttention (vLLM) — эффективное управление KV cache, уменьшает memory fragmentation.
- FP8/INT8 quantization — снижает объём данных, передаваемых в память.
- Tensor parallelism — распределение модели на несколько GPU через NVLink.
Пет-проект для закрепления
Задача Написать скрипт, который запускает инференс маленькой LLM (например, GPT-2) с разными batch size и профилирует утилизацию GPU. Затем применить одну оптимизацию (CUDA graphs или continuous batching) и сравнить результаты.
Инструменты Python, PyTorch, Hugging Face Transformers, Nsight Systems, Nsight Compute.
Шаги:
- Загрузите модель
gpt2и токенизатор. - Напишите функцию инференса с фиксированным batch size (1, 4, 16).
- Запустите профилирование:
nsys profile -o baseline -t cuda python inference.py --batch_size 1 - Откройте отчёт в Nsight Systems, найдите узкое место.
- Реализуйте CUDA graphs для forward pass (используя
torch.cuda.CUDAGraph). - Повторите профилирование с CUDA graphs.
- Сравните GPU utilization и latency.
Ожидаемый результат Вы увидите, что при batch_size=1 утилизация низкая (memory-bound), а CUDA graphs уменьшают kernel launch overhead, повышая утилизацию на 10–20%.
Связь с другими вопросами
| Вопрос | Тема |
|---|---|
| 460 | Как вы оптимизируете throughput LLM-инференса? |
| 461 | Что такое speculative decoding и как он работает? |
| 462 | Как continuous batching влияет на latency и throughput? |
| 463 | Когда использовать CUDA graphs для инференса? |
| 464 | Как профилировать multi-GPU инференс с помощью Nsight? |
Навигация
- Предыдущий: 458
- Следующий: 460
- Индекс: 00. Индекс разборов