Ваша модель ResNet обучается в 3 раза медленнее, чем у коллеги с таким же GPU. Llama-инференс простаивает 40% времени. Вы обновили даталоадер, переписали препроцессинг, но FPS не растет.
Проблема не в слоях, не в оптимизаторе, не в learning rate. Проблема в том, что GPU ждет данные. Простаивает. Сжигает ваши деньги на аренду инстансов впустую.
Две технологии обещают помочь: NVIDIA Nsight Systems и PyTorch Profiler. Обе показывают хронологию выполнения. Обе рисуют красивые графики. Но одна видит то, что скрыто от другой.
GPU ждет - вы платите
Откройте htop во время обучения. CPU загружен на 90%, GPU - на 30%. Знакомо? Это классический data transfer bottleneck. GPU простаивает, пока CPU готовит следующий батч.
Почему это происходит? Потому что:
- DataLoader с num_workers=0 (самоубийство производительности)
- Слишком тяжелый препроцессинг в основном процессе
- pin_memory=False (потеря 15-20% скорости)
- Неправильный размер батча для вашей PCIe шины
- Конфликт за память между процессами DataLoader
Но как это доказать? Как найти конкретную операцию, которая тормозит весь пайплайн?
PyTorch Profiler: быстрый диагноз сверху
PyTorch Profiler - это как термометр. Быстро, просто, показывает температуру. Но не говорит, какая именно бактерия вызвала воспаление.
Вот как выглядит типичное использование:
import torch
from torch.profiler import profile, record_function, ProfilerActivity
with profile(
activities=[ProfilerActivity.CPU, ProfilerActivity.CUDA],
schedule=torch.profiler.schedule(wait=1, warmup=1, active=3),
on_trace_ready=torch.profiler.tensorboard_trace_handler('./logs'),
record_shapes=True,
profile_memory=True,
with_stack=True
) as prof:
for batch_idx, (data, target) in enumerate(train_loader):
if batch_idx >= 5:
break
data = data.cuda(non_blocking=True)
target = target.cuda(non_blocking=True)
with record_function("forward"):
output = model(data)
with record_function("loss"):
loss = criterion(output, target)
with record_function("backward"):
optimizer.zero_grad()
loss.backward()
optimizer.step()
prof.step()
Запускаете, открываете TensorBoard, видите timeline. И тут начинается самое интересное.
1 Что PyTorch Profiler видит хорошо
Он отлично показывает:
- Время выполнения каждого оператора (conv2d, linear, etc.)
- Потребление памяти GPU на каждом этапе
- Цепочку вызовов Python (благодаря with_stack=True)
- Куча удобных готовых отчетов в TensorBoard
| Сильные стороны | Слабые стороны |
|---|---|
| Интеграция в одну строку кода | Не видит системные вызовы |
| Работает в Jupyter ноутбуках | Слеп к DMA операциям |
| Готовые отчеты TensorBoard | Нет детализации по PCIe |
| Трассировка памяти Python | Ограниченная глубина стека |
Но вот что он НЕ видит. И это критично.
2 Слепые зоны PyTorch Profiler
Попробуйте найти ответы на эти вопросы в отчете PyTorch Profiler:
- Сколько времени заняла передача данных из host memory в GPU memory?
- Были ли задержки из-за синхронизации cudaStreamSynchronize?
- Какой процесс потребляет CPU time во время подготовки данных?
- Есть ли contention между процессами DataLoader за системные ресурсы?
- Загружена ли PCIe шина на 100% или простаивает?
Не найдете. Потому что PyTorch Profiler работает на уровне PyTorch операций, а не на уровне системы. Он видит "data.cuda()", но не видит, что происходит внутри этого вызова.
Это как пытаться диагностировать болезнь двигателя, глядя только на спидометр.
NVIDIA Nsight Systems: рентген для вашего пайплайна
Nsight Systems - это хирургический инструмент. Он режет глубоко, до кости. Видит все: от системных вызовов до колебаний напряжения на шине PCIe.
Установка проще, чем кажется:
# Установка (Ubuntu/Debian)
wget https://developer.nvidia.com/downloads/assets/tools/secure/nsight-systems/2024_1_2/nsight-systems-linux-2024.1.2.deb
sudo apt install ./nsight-systems-linux-2024.1.2.deb
# Или через контейнер (рекомендуется)
docker run --gpus all --rm -it \
-v $(pwd):/workspace \
-v /tmp/.X11-unix:/tmp/.X11-unix \
-e DISPLAY=$DISPLAY \
nvcr.io/nvidia/nsight-systems:2024.1 \
/bin/bash
Запуск профилирования:
# Самый простой запуск
nsys profile -o my_report \
--trace=cuda,nvtx,osrt,cudnn,cublas \
python train.py
# С дополнительными опциями для data transfer анализа
nsys profile -o detailed_report \
--trace=cuda,nvtx,osrt \
--sample=cpu \
--cpuctxsw=true \
--cudabacktrace=true \
--gpu-metrics-device=all \
--force-overwrite true \
python train.py --batch-size 64 --workers 4
3 Что вы увидите в отчете Nsight
Откройте .nsys-rep файл в Nsight Systems GUI. Перед вами - хронологическая лента времени. Вот на что смотреть:
- CPU Threads - каждый поток отображается отдельной строкой. Видно, когда DataLoader worker простаивает, ожидая GIL.
- GPU Streams - отдельные потоки выполнения на GPU. Видно разрывы в вычислениях (когда GPU ждет данные).
- Memory Transfers - синие полоски HtoD (Host to Device) и DtoH (Device to Host). Их длина - время передачи.
- PCIe Utilization - график загрузки шины. Если он не достигает 90% - у вас проблемы с настройкой.
- Context Switches - переключения между процессами. Каждое такое переключение стоит 1-2 микросекунды простоя.
Вот реальный пример из проекта по обучению Llama 3.2:
Nsight показал, что DataLoader workers тратят 60% времени на ожидание pthread_mutex_lock. Причина - глобальная блокировка в пользовательском augmentator. Исправление ускорило training на 2.3x.
4 Как читать Nsight отчет: практический кейс
Допустим, вы видите такую картину:
- GPU вычисляет 5ms
- Потом простаивает 15ms
- Потом получает новый батч за 2ms
- Снова вычисляет 5ms
Ваш GPU utilization = 5/(5+15+2) = 22.7%. Ужасно.
Смотрите на CPU threads. Видите, что DataLoader worker 0 работает 10ms, потом sleep 10ms. Почему?
Раскрываете стек вызовов (Ctrl+клик на операции). Видите:
# В стеке вызовов:
dataloader.py:get_batch()
custom_augmentor.py:heavy_transform() # 8ms!
image_utils.py:apply_filter() # 2ms
libjpeg-turbo:decode_jpeg() # уже быстро
Бинго! heavy_transform() съедает 8ms. Это и есть bottleneck.
Но Nsight показывает больше. Смотрите на Memory Transfers. Видите, что после heavy_transform() идет memcpy HtoD на 1.5ms. А PCIe utilization в этот момент - всего 30%.
Значит, проблема не в шине. Проблема в том, что данные не готовы для передачи. Или pin_memory не настроен.
Совместное использование: убийственная комбинация
Один инструмент не заменяет другой. Они дополняют. Вот рабочий процесс:
5 Шаг 1: Быстрый скрининг PyTorch Profiler
Запускаете на 10-20 итерациях. Смотрите в TensorBoard:
# Минимальный код для проверки
torch.profiler.record_function("data_loading")
data = next(iter(train_loader))
torch.profiler.record_function("to_gpu")
data = data.cuda()
# ... остальной код
Если видите, что "data_loading" занимает 50% времени итерации - проблема очевидна. Переходите к шагу 2.
6 Шаг 2: Глубокий анализ Nsight Systems
Запускаете с флагами для детального анализа:
nsys profile -o deep_analysis \
--trace=cuda,nvtx,osrt \
--sample=cpu \
--cpuctxsw=true \
--cuda-memory-usage=true \
--gpu-metrics-device=0 \
--gpu-metrics-set=ga10x \
python train.py --epochs 1 --steps 100
В GUI анализируете:
- Ищете разрывы в GPU execution timeline
- Смотрите, что происходит на CPU в эти моменты
- Проверяете длительность memcpy операций
- Анализируете contention между потоками
7 Шаг 3: Внедрение NVTX маркеров
Чтобы Nsight показывал понятные названия операций, добавьте NVTX маркеры:
import torch.cuda.nvtx as nvtx
class CustomDataLoader:
def __iter__(self):
for batch in self.dataset:
nvtx.range_push("data_preprocessing")
# тяжелые трансформации
batch = self.augment(batch)
nvtx.range_pop()
nvtx.range_push("to_gpu_async")
batch = batch.to('cuda', non_blocking=True)
nvtx.range_pop()
yield batch
Теперь в Nsight отчете вместо анонимных блоков увидите "data_preprocessing" и "to_gpu_async".
Чеклист: 7 самых частых data transfer проблем
Вот что искать в отчетах (и как исправлять):
| Проблема | Как обнаружить | Решение |
|---|---|---|
| GIL contention в DataLoader | Nsight: worker threads часто в состоянии "Wait" | Использовать multiprocessing с spawn (не fork) |
| Медленный HtoD transfer | Nsight: длинные синие memcpy блоки | Включить pin_memory=True в DataLoader |
| CPU-bound preprocessing | PyTorch Profiler: долгий "__getitem__" | Вынести в отдельный процесс или использовать DALI |
| Синхронные cuda() вызовы | Nsight: разрывы между CPU и GPU работой | Использовать non_blocking=True + torch.cuda.Stream |
| Неоптимальный batch size | PCIe utilization < 70% при передаче | Увеличить batch или использовать gradient accumulation |
| Memory fragmentation | PyTorch Profiler: growing GPU memory | Использовать TraceML для отслеживания утечек |
| Конфликт за системную память | Nsight: high context switch rate | Ограничить num_workers или использовать cgroups |
Когда что использовать: decision tree
Не знаете, с чего начать? Вот алгоритм:
- GPU utilization < 50%? → Берите Nsight Systems сразу. Проблема системная.
- Хотите быстро проверить конкретную модель? → PyTorch Profiler на 20 итерациях.
- Работаете с кастомными CUDA ядрами? → Обязательно Nsight. Кастомные ядра требуют низкоуровневого анализа.
- Отлаживаете distributed training? → Nsight с trace=mpi,nccl.
- Просто проверяете, нет ли явных ошибок? → PyTorch Profiler хватит.
Ошибки, которые все совершают (и вы тоже)
Ошибка 1: Профилирование на слишком маленьком dataset. DataLoader ведет себя иначе на первом эпохе (кеширование) и на последующих.
Решение: профилируйте минимум 100-200 итераций. Или используйте --steps 200 в nsys.
Ошибка 2: Запуск профилирования в debug mode. PyTorch в debug режиме добавляет проверки, которые замедляют все в 10 раз.
Решение: убедитесь, что torch.__version__ не заканчивается на +cpu или +debug.
Ошибка 3: Игнорирование thermal throttling. GPU на 84°C работает медленнее, чем на 70°C.
Решение: смотрите GPU Temperature в Nsight Metrics. Если выше 80°C - проблема не в софте, а в охлаждении.
Производительность в продакшене: что меняется
В development среде все работает. В продакшене - падает. Почему?
Потому что:
- На продакшене другие ограничения памяти (контейнеры, cgroups)
- Другие версии драйверов CUDA
- Конкуренция за ресурсы с другими процессами
- Сетевые задержки при чтении данных из удаленного хранилища
Nsight Systems видит это. PyTorch Profiler - нет.
Особенно критично для инференса с TensorRT-LLM, где каждый миллисекунд на счету.
Будущее: что ждет профайлеры в 2024-2025
Nsight Systems становится тяжелее. Требует больше памяти. Но и дает больше данных.
PyTorch Profiler движется к интеграции с фреймворками вроде AETHER-X для кросс-платформенного профилирования.
Мой прогноз: через год мы увидим:
- Автоматические рекомендации по оптимизации на основе ML анализа профилей
- Интеграцию профилирования данных и модели в единую timeline
- Поддержку NPU профилирования наравне с GPU
- Real-time profiling в продакшене без overhead
А пока - берите Nsight для серьезной работы. PyTorch Profiler - для быстрых проверок.
И помните: простаивающий GPU - это не просто потеря производительности. Это сожженные деньги. Ваши деньги.