中文翻译暂不可用,显示俄语原文。

Как вы дебажите низкую 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 падает при низкой нагрузке.

Решения

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 activitynsys profile -o output -t cuda,nvtx python script.py
Nsight Compute (ncu)Детальный анализ ядер: warp stall, occupancy, memory throughputncu --set full -o output python script.py
dcgmi (DCGM)Мониторинг кластера GPUdcgmi 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 selectedwarp не выбран из-за занятости ресурсов (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 ищем:

Шаг 3: Детальный анализ ядер (ncu)

ncu --set full -o kernel python run.py

В Nsight Compute:

Шаг 4: Проверка batch size и batching strategy

  • Логируйте batch size в каждом forward pass.
  • Если batch size часто = 1 → проблема decode stage.
  • Если batch size колеблется → внедрите continuous batching.

Шаг 5: Проверка CPU bottleneck


5. Решения в таблице

ПричинаИнструмент диагностикиРешение
Memory-bound (decode)ncu: warp stall long scoreboardУвеличить batch size, speculative decoding, FlashAttention
Kernel launch overheadnsys: много маленьких kernelCUDA graphs, kernel fusion
Неоптимальное batchingЛоги batch sizeContinuous batching (vLLM, TensorRT-LLM)
Data transfer bottlenecknsys: длинные MemCpyNVLink, асинхронные копии, prefetch
CPU bottlenecknsys: 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.

Шаги:

  1. Загрузите модель gpt2 и токенизатор.
  2. Напишите функцию инференса с фиксированным batch size (1, 4, 16).
  3. Запустите профилирование:
    nsys profile -o baseline -t cuda python inference.py --batch_size 1
    
  4. Откройте отчёт в Nsight Systems, найдите узкое место.
  5. Реализуйте CUDA graphs для forward pass (используя torch.cuda.CUDAGraph).
  6. Повторите профилирование с CUDA graphs.
  7. Сравните 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?

Навигация