Зачем и когда использовать видеокарты
Что проверяют
Понимаете ли вы, какие задачи вообще ложатся на GPU, и умеете ли прикинуть, окупится ли он. Следом обычно спрашивают, почему видеокарта загружена на треть и что делать, если модель не влезает в память.
Как отвечать
Суть в архитектуре. CPU это относительно немного мощных ядер с большим кэшем и хорошим предсказанием ветвлений, GPU это тысячи простых ядер, которые выполняют одну операцию над большим массивом данных. Отсюда критерий: GPU выигрывает там, где работа сводится к плотной линейной алгебре над большими тензорами.
Когда он нужен:
- обучение и инференс нейросетей: умножения матриц и свёртки через CUDA и cuDNN под PyTorch и JAX, тензорные ядра и смешанная точность (
torch.autocastс bf16 или fp16); - инференс больших языковых моделей: без GPU разумной задержки не получить, отсюда vLLM и подобные серверы;
- бустинги на больших таблицах: XGBoost с
device="cuda", CatBoost сtask_type="GPU", у LightGBM тоже есть GPU-режим; - датафреймы и классический ML: RAPIDS (cuDF, cuML), ускоритель RAPIDS для Spark.
Когда не нужен: небольшие табличные данные и scikit-learn, который считает на CPU; код с ветвлениями и циклами на Python; пайплайны, где узкое место в чтении данных; инференс по одному объекту, где копирование через PCIe съедает выигрыш.
Главный ограничитель это видеопамять. При обучении со смешанной точностью и Adam на каждый параметр уходит около 16 байт (веса, градиенты, мастер-копия в fp32, два момента), плюс активации. Если не влезает: меньший батч с накоплением градиентов, gradient checkpointing, для инференса квантизация.
Закончите про загрузку: nvidia-smi показывает утилизацию, и если она низкая, GPU голодает. Помогают num_workers и pin_memory=True в DataLoader, формат данных и префетчинг.
Где спотыкаются
Говорят «GPU всегда быстрее». На маленьких данных накладные расходы на передачу и запуск ядер делают его медленнее процессора.
Не упоминают память. Первая реальная проблема на проекте обычно не скорость, а CUDA out of memory.
Гоняют тензоры между устройствами в цикле: .item() или .cpu() на каждом шаге заставляют ждать синхронизации и убивают параллельность.