Постмортем інциденту рідко має в заголовку слово «CUDA». Так ніколи не роблять. Замість цього пишуть щось на кшталт «пайплайн навчання зупинився», «GPU-ноди недовикористані» або, мій улюблений, «регрес моделі через дрейф середовища».
Але ви копаєте глибше — і от воно: одне оновлення драйвера, одна невідповідність тулкіта, одна «дрібна» перестройка контейнера — і дорогі GPU відмінно виконують роль обігрівача.
CUDA не просто зробила GPU корисними для загальних обчислень. Вона зробила їх операційно передбачуваними в межах своїх правил, а потім так вибудувала екосистему, що більшість серйозних навантажень почали дотримуватися цих правил. Ось що таке локальна прив’язка. Не зло. Не магія. Просто невпинна продуктова стратегія плюс ефект накопичення в екосистемі.
Що таке CUDA насправді (і чого вона не є)
CUDA — це не «GPU». CUDA — це платформа паралельних обчислень і модель програмування від NVIDIA, а також набори бібліотек користувацького простору, компіляторний тулчейн і рантайм, які роблять GPU NVIDIA стабільною ціллю для розробників.
Якщо ви працюєте з ML, то в більшості випадків взаємодієте з CUDA опосередковано: PyTorch викликає cuDNN і cuBLAS; розподілене навчання використовує NCCL; inference-сервер застосовує TensorRT; ваші ядра можливо компілюються через NVCC; а драйвер лежить в основі й робить вигляд, що так і треба.
Стратегічна геніальність CUDA у тому, що вона одночасно є:
- Екосистемою мов (CUDA C/C++, PTX, device intrinsics, потоки компіляції).
- Рантайм-контрактом (driver API vs runtime API, управління контекстом, модель пам’яті, streams/events).
- Імперією бібліотек (cuBLAS, cuDNN, NCCL, cuFFT, TensorRT, CUTLASS тощо).
- Оповіддю про продуктивність (ядра, оптимізовані під мікроархітектуру, fused ops, tensor cores, graph capture).
- Операційною оповіддю (nvidia-smi, NVML, MIG, DCGM, пакування драйверів, контейнери).
Локальна прив’язка не вимагає зловмисних намірів. Потрібна одностороння двері: ви заходите, бо швидше й простіше, а пізніше виявляєте, що вихід — це вузькі сходи за автоматом.
Ключова абстракція: «написати один раз, працювати достатньо швидко»
CUDA дала розробникам стабільну, відносно високорівневу модель для обчислень на GPU. Ви пишете ядра. Керуєте передачами пам’яті. Запускаєте сітки/блоки. Це досить низькорівнево, щоб отримати швидкодію, і досить високорівнево, щоб доставляти продукт.
Потім NVIDIA постійно додавала архітектурні можливості (tensor cores, покращення unified memory, нові кеш-ієрархії), підтримуючи історію сумісності, яка зазвичай працює, поки ви їй не перечите.
Чого CUDA не є
- Це не просто компілятор. Компілятор — видима частина; бібліотеки і контракт драйвера — це гравітація.
- Це не «просто краще в математиці». Це краще в тому, щоб перетворити математику в продукт — від ідей до швидкодії й деплою.
- Це не одиночний номер версії. Є версії драйвера, тулкіта, бібліотек, збірок фреймворків і базових образів контейнерів. Вони переговорюють під час виконання, немов комітет з власними інтересами.
Жарт №1: Якщо ви думаєте, що ваша версія CUDA — це «те, що встановив pip», я маю вам міст — скомпільований з невірною compute capability.
Як виникає прив’язка: технічні механіки
«Локальна прив’язка» звучить як закупівельна драма. На практиці це інженерна математика: вартість переходу = (переписування + втрата продуктивності + операційний ризик) × (кількість навантажень) ÷ (ваша толерантність до болю).
CUDA збільшує цю вартість по кількох осях одночасно.
1) Буксир бібліотек: cuDNN, cuBLAS, TensorRT, NCCL
Більшість продакшн-коду не викликає сирі CUDA-ядра. Він викликає фреймворки, які в свою чергу викликають бібліотеки від вендора. Ці бібліотеки тонко налаштовані під мікроархітектурні деталі:
розміри тайлів tensor core, шаблони доступу до пам’яті, fused epilogues, алгоритми вибору ядер, навіть «ця форма згортки любить це ядро на цьому GPU».
Альтернативні стеки можуть реалізувати еквіваленти, але досягти парності важко, бо «API» — це легка частина; останні 30% продуктивності — це роки профілювання, хірургії ядер і уваги до дивних крайніх випадків.
2) Компіляційний пайплайн: PTX, SASS, fatbins і сумісність уперед
CUDA компілює код пристрою в суміш:
- PTX (віртуальна ISA, ніби «асемблерний IR» для GPU).
- SASS (реальний машинний код для конкретної архітектури GPU).
- Fat binaries, що містять кілька варіантів.
Це має операційні наслідки. Якщо ви відвантажуєте лише SASS для однієї архітектури, ви прив’язуєтесь до тих GPU. Якщо відвантажуєте PTX, ви покладаєтесь на JIT драйвера для компіляції під встановлений GPU, що прив’язує вас до можливостей драйвера і може додати затримку на старті.
3) Контракт драйвера: «новіший драйвер запускає стару CUDA» (в більшості випадків)
Модель сумісності NVIDIA — велика частина прив’язки, бо зменшує щоденний страх. Ви можете стандартизувати гілку драйвера, а в контейнерах дозволити діапазон тулкітів.
Індустрія навчилася будувати операційні практики навколо цього: «драйвер на хості, тулкіт в контейнері». Цей патерн уже став м’язовою пам’яттю.
Ловушка — у слові «в більшості випадків». Крайові випадки — це ваші пагери:
нові GPU потребують нових драйверів, фреймворки очікують нові libcudart, NCCL потребує певної поведінки драйвера, а старі тулчейни зіштовхуються з сучасними ядрами як на невдалому побаченні.
4) Гравітація екосистеми: фреймворки, туторіали, найм і CI
CUDA стала дефолтом у ML. Це означає:
- Більшість ML-інженерів навчалися на CUDA-перших робочих процесах.
- Більшість сторонніх бібліотек спочатку випускають CUDA-wheel пакети.
- Більшість порад з продуктивності припускають NVIDIA лічильники й профайлери.
- Більшість гайдів з розподіленого навчання передбачають NCCL.
Це тихіша прив’язка: вартість перенавчання людей і рефакторингу систем збірки. Це не показується в бенчмарках, але проявляється в строках доставки.
5) Аппаратні можливості, відкриті через інструменти CUDA
Коли NVIDIA випускає нову апаратну можливість, зазвичай вона супроводжується історією CUDA: API, бібліотеки і підтримка в профайлері. Якщо ви хочете цю можливість — ви приймаєте тулінг.
Приклади: tensor cores (і рівні підтримки в бібліотеках), MIG для мультиоренду, та колективи, що враховують NVLink для масштабування multi-GPU.
Факти й історія, що пояснюють домінування
Ось конкретні контекстні пункти, які люди забувають, коли зводять CUDA до «вендорської прив’язки». Це цікавіше, ніж здається.
- CUDA запустили в 2007 році і вона зробила програмування загального призначення на GPU схожим на C, а не на хак з графічним API.
- До CUDA GPGPU часто означало зловживання шейдерами: запаковування обчислень в pixel/vertex шейдери через OpenGL/DirectX — це було кмітливо, але некомфортно.
- cuDNN (2014) став переломним моментом: для deep learning з’явилася оптимізована, підтримувана вендором бібліотека ядра, на яку могли спиратися фреймворки.
- NCCL зробив multi-GPU «нормальним», забезпечивши колективи кільцевого/деревного типу, настроєні під інтерконекти NVIDIA. Розподілене навчання перестало бути справою лише HPC.
- Tensor cores (ера Volta) змінили гру з «GPU рахує FLOP-и» на «GPU дуже добре виконує ML-подібну матричну математику», і бібліотеки CUDA навчилися це використовувати.
- Стек профайлерів/телеметрії CUDA дозрів рано: NVML, nvidia-smi, Nsight — оператори отримали важелі й видимість.
- Академічна та open-source інерція зійшлися з NVIDIA: ранні прориви DL часто відтворювалися на CUDA-графіці, бо саме такі лабораторії могли її отримати і саме такі фреймворки підтримували.
- Патерн «драйвер на хості, тулкіт у контейнері» став дефолтним стандартом для продакшену й зменшив фрікції деплою, що посилило довіру до CUDA.
- Історія зворотної сумісності CUDA зробила підприємницьке впровадження менш страшним, ніж «перекомпілювати все для кожного драйвера».
Мораль: NVIDIA продавала не лише чіпи. Вони продавали шлях від ідеї до робочої системи і продовжували прокладати дорогу.
Продукційний стек CUDA: де реально боляче
У продакшені CUDA — це багатошаровий торт. Смачний, поки ви не залишите його в спекотній машині.
Шар 0: апаратне забезпечення й топологія
Продуктивність GPU — це не лише «скільки GPU». Це:
- генерація PCIe і ширина ліній
- розміщення NUMA (GPU приєднано до якого сокета CPU)
- наявність і провідка NVLink/NVSwitch
- розмір і пропускна здатність пам’яті GPU
- налаштування ECC і поведінка при помилках
Шар 1: модуль ядра + користувацький драйвер
NVIDIA-драйвер — це справжня платформа. Він відкриває пристрій ОС, реалізує driver API CUDA і посередничи між CUDA-рантаймом у вашому контейнері та реальним GPU.
Якщо драйвер неправильний, нічого іншого не має значення. Ви можете встановити всі тулкіти світу і все одно отримати «no devices found».
Шар 2: CUDA-рантайм + бібліотеки
У вашому контейнері можуть бути libcudart, cuDNN, cuBLAS, NCCL тощо. Вони повинні бути достатньо сумісні з драйвером хоста.
«Достатньо сумісні» — ось що перетворює рутинні оновлення на інцидентні квитки.
Шар 3: фреймворки і артефакти збірки
Бінарні збірки PyTorch/TensorFlow/JAX збудовані для конкретних версій CUDA і часто очікують певний діапазон ABI бібліотек.
А ще є кастомні CUDA-розширення (поширені в recommender-системах, прискоренні inference і в дослідницьких прототипах, які виросли й отримали пагер).
Шар 4: оркестрація й мультиоренда
Kubernetes device plugins, MIG-розбивки, квоти на GPU, MPS, планувальники задач, налаштування cgroup — тут «працює на моїй машині» йде на аудит.
Чим більший ваш фліт, тим більше ваша проблема перетворюється на забезпечення політик і контроль дрейфу.
Шар 5: сховище та датапайплайни
Як SRE/фахівець зі сховища, скажу тиху правду вголос: багато «проблем з продуктивністю GPU» фактично є проблемами даних.
Якщо GPU чекає на даталоадери, повільне об’єктне сховище, IOPS для дрібних файлів або декомпресію на неправильному ядрі CPU — то вузьке місце не CUDA; просто там видно час простою.
Одна цитата, яку варто тримати на стікері біля дашборда кластера:
Парафразована ідея: «Затримка — властивість усієї системи, а не окремого компонента.»
— Вернер Вогельс (парафразована ідея)
Швидкий план діагностики: що перевіряти першим/другим/третім
Коли GPU-навантеження працюють повільно або падають, не починайте з перевстановлення CUDA. Це шлях до створення другого інциденту. Почніть з обмежень і спостережуваності.
Перше: «Чи бачимо GPU і чи він здоровий?»
- Перевірте видимість GPU: чи вузол бачить пристрої, чи завантажений правильний драйвер, немає «ECC storm».
- Перевірте активні процеси: чи щось інше захопило GPU, чи MIG нарізування відповідає очікуваному.
- Перевірте частоти/ліміти потужності: чи не відбувається теплове тротлінг або обмеження потужності.
Друге: «Чи завантаження GPU-залежне чи обмежене вхідними даними?»
- Подивіться на утилізацію і проксі зайнятості SM (саме по собі використання — оманливий показник, але це швидкий сигнал).
- Перевірте PCIe RX/TX: якщо обсяг передач великий, ви можете бути обмежені конвеєром передач.
- Перевірте завантаження CPU і iowait: даталоадери й препроцесинг часто домінують.
Третє: «Це несумісність?»
- Діапазон драйвер ↔ тулкіт: чи підтримує драйвер хоста CUDA-рантайм контейнера.
- NCCL + топологія: неправильні налаштування можуть безшумно змусити повільні алгоритми працювати.
- Очікування збірки фреймворку: PyTorch CUDA wheel-і вибагливі; кастомні розширення ще вибагливіші.
Четверте: «Це вибір ядра / регрес продуктивності?»
- cuDNN автоналаштування змінюється між версіями.
- Перемикання TF32/FP16/BF16 змінює обчислювальні маршрути.
- Нові драйвери інколи змінюють поведінку JIT або евристики планування.
Таке упорядкування запобігає класичній помилці: шість годин оптимізувати ядра для задачі, яка заблокована через повільний NFS-монтування.
Практичні завдання: команди, що означає вивід і рішення, яке ви приймаєте
Це команди, які ви виконуєте, коли ви на виклику і хтось каже: «GPU повільні», ніби це вже діагноз.
Кожне завдання включає: команду, типовий вивід, що це означає і яке рішення ви приймаєте.
Завдання 1: Перевірити драйвер і видимість GPU
cr0x@server:~$ nvidia-smi
Tue Jan 13 10:02:14 2026
+---------------------------------------------------------------------------------------+
| NVIDIA-SMI 550.54.14 Driver Version: 550.54.14 CUDA Version: 12.4 |
|-----------------------------------------+----------------------+----------------------+
| GPU Name Persistence-M | Bus-Id Disp.A | Volatile Uncorr. ECC |
| Fan Temp Perf Pwr:Usage/Cap | Memory-Usage | GPU-Util Compute M. |
| | | MIG M. |
|=========================================+======================+======================|
| 0 NVIDIA A100-SXM4-40GB On | 00000000:81:00.0 Off | 0 |
| N/A 47C P0 165W / 400W| 1024MiB / 40960MiB | 12% Default |
+-----------------------------------------+----------------------+----------------------+
+---------------------------------------------------------------------------------------+
| Processes: |
| GPU GI CI PID Type Process name GPU Memory |
| ID ID Usage |
|=======================================================================================|
| 0 N/A N/A 19342 C python 1008MiB|
+---------------------------------------------------------------------------------------+
Значення: Драйвер завантажений, GPU видно, процес використовує ~1GB VRAM.
Рішення: Якщо це не вдається (немає пристроїв), припиніть і виправте драйвер/плагін пристрою/апаратне забезпечення перед тим, як чіпати фреймворки.
Завдання 2: Перевірити детальну телеметрію GPU (утилізація, частоти, ознаки тротлінгу)
cr0x@server:~$ nvidia-smi -q -d PERFORMANCE,CLOCK,POWER,TEMPERATURE | sed -n '1,120p'
==============NVSMI LOG==============
Timestamp : Tue Jan 13 10:02:30 2026
Driver Version : 550.54.14
CUDA Version : 12.4
Attached GPUs : 1
GPU 00000000:81:00.0
Power Readings
Power Management : Supported
Power Draw : 165.32 W
Power Limit : 400.00 W
Clocks
Graphics : 1410 MHz
SM : 1410 MHz
Memory : 1215 MHz
Temperature
GPU Current Temp : 47 C
GPU T.Limit Temp : 93 C
Значення: Потужність і частоти виглядають нормально; немає теплового обмеження.
Рішення: Якщо частоти низькі або споживання потужності зфіксовано на низькому рівні, виправте політику живлення/охолодження (або обмеження хмарного інстансу) перш ніж звинувачувати CUDA.
Завдання 3: Визначити, хто тримає GPU
cr0x@server:~$ nvidia-smi pmon -c 1
# gpu pid type sm mem enc dec command
0 19342 C 12 4 0 0 python
Значення: Існує один процес обчислень; зайнятість SM низька.
Рішення: Низька зайнятість SM свідчить про вузьке місце у ввідному конвеєрі, блокування синхронізацією або надто малі batch-и; переходьте до перевірки CPU/I/O.
Завдання 4: Підтвердити, що модуль ядра завантажений і не падає
cr0x@server:~$ lsmod | egrep 'nvidia|nouveau' | head
nvidia_uvm 1769472 0
nvidia_drm 110592 2
nvidia_modeset 1622016 1 nvidia_drm
nvidia 77168640 92 nvidia_uvm,nvidia_modeset
Значення: NVIDIA-модулі завантажені; конфлікту з nouveau не видно.
Рішення: Якщо nouveau присутній, занесіть його в чорний список і перебудуйте initramfs; змішані драйвери викликають дивні симптоми, що виглядають як «CUDA нестабільна».
Завдання 5: Перевірити недавні логи ядра на помилки GPU
cr0x@server:~$ sudo dmesg -T | egrep -i 'nvrm|xid|gpu has fallen|ecc' | tail -n 10
[Tue Jan 13 09:58:07 2026] NVRM: GPU 0000:81:00.0: RmInitAdapter succeeded
[Tue Jan 13 10:01:02 2026] NVRM: Xid (PCI:0000:81:00): 31, pid=19342, Ch 00000028, MMU Fault: ENGINE GRAPHICS
Значення: Xid-помилки свідчать про помилки на рівні GPU/драйвера (тут MMU fault). Це не «ваш код PyTorch», поки не буде доведено інше.
Рішення: Карантин ноди, злив робочих навантажень і розгляд оновлення/відкату драйвера або RMA апаратури, якщо повторюється.
Завдання 6: Підтвердити доступ контейнера до GPU (NVIDIA container runtime)
cr0x@server:~$ docker run --rm --gpus all nvidia/cuda:12.4.1-base-ubuntu22.04 nvidia-smi
Tue Jan 13 10:03:10 2026
+---------------------------------------------------------------------------------------+
| NVIDIA-SMI 550.54.14 Driver Version: 550.54.14 CUDA Version: 12.4 |
+---------------------------------------------------------------------------------------+
Значення: Контейнер бачить GPU і проходить передачу драйвера.
Рішення: Якщо це не вдається, ваша проблема — інтеграція рантайму (device plugin, дозволи, конфігурація контейнерного рантайма), а не ML-фреймворк.
Завдання 7: Перевірити CUDA-рантайм бібліотеки всередині контейнера
cr0x@server:~$ docker run --rm --gpus all myimage:latest bash -lc "ldconfig -p | egrep 'libcudart|libcublas|libcudnn' | head -n 20"
libcudart.so.12 (libc6,x86-64) => /usr/local/cuda/lib64/libcudart.so.12
libcublas.so.12 (libc6,x86-64) => /usr/local/cuda/lib64/libcublas.so.12
libcudnn.so.9 (libc6,x86-64) => /usr/lib/x86_64-linux-gnu/libcudnn.so.9
Значення: Контейнер поставляє конкретні CUDA-бібліотеки.
Рішення: Якщо фреймворк очікує інші мажорні версії (часто з cuDNN), закріпіть базові образи і збірки фреймворку разом. Не «apt upgrade»-теся в ABI-лотерею.
Завдання 8: Підтвердити, що PyTorch бачить CUDA і з якою версією його зібрано
cr0x@server:~$ python3 - <<'PY'
import torch
print("torch", torch.__version__)
print("cuda available", torch.cuda.is_available())
print("torch built cuda", torch.version.cuda)
print("gpu", torch.cuda.get_device_name(0) if torch.cuda.is_available() else None)
PY
torch 2.4.0
cuda available True
torch built cuda 12.1
gpu NVIDIA A100-SXM4-40GB
Значення: PyTorch підтримує CUDA і зібраний для CUDA 12.1, запускаючись на драйвері хоста, який заявляє підтримку CUDA 12.4.
Рішення: Якщо cuda available — false, зупиніться: це або відсутній доступ до пристрою, або wheel лише для CPU, або несумісні бібліотеки.
Завдання 9: Виявити тиск пам’яті GPU і сигнали фрагментації
cr0x@server:~$ nvidia-smi --query-gpu=memory.total,memory.used,memory.free --format=csv
memory.total [MiB], memory.used [MiB], memory.free [MiB]
40960 MiB, 38912 MiB, 2048 MiB
Значення: Ви близькі до насичення VRAM.
Рішення: Якщо завдання падають з OOM, зменшіть batch size, увімкніть activation checkpointing або перейдіть на більші GPU; не намагайтеся «додати swap», бо так VRAM не працює.
Завдання 10: Перевірити пропускну здатність PCIe і розміщення (NUMA)
cr0x@server:~$ nvidia-smi topo -m
GPU0 CPU Affinity NUMA Affinity
GPU0 X 0-31 0
Значення: GPU0 підключений до NUMA-вузла 0; CPU affinity показує, які ядра локальні.
Рішення: Пришпильте даталоадери й потоки CPU до локального NUMA-вузла, якщо можливо; віддалений доступ до пам’яті тиха причина падіння пропускної здатності.
Завдання 11: Виловити класичний випадок «GPU простоює, бо повільний даталоадер»
cr0x@server:~$ mpstat -P ALL 1 3
Linux 6.5.0 (server) 01/13/2026 _x86_64_ (64 CPU)
10:03:58 AM CPU %usr %nice %sys %iowait %irq %soft %steal %idle
10:03:59 AM all 8.12 0.00 2.01 18.44 0.00 0.22 0.00 71.21
10:04:00 AM all 7.55 0.00 1.88 21.10 0.00 0.25 0.00 69.22
Значення: Високий iowait свідчить, що CPU блокується на сховищі.
Рішення: Перед тим як чіпати CUDA, виправте шлях даних: локальний NVMe-кеш, більший read-ahead, менше дрібних файлів, паралельний prefetch або швидший клієнт об’єктного сховища.
Завдання 12: Перевірити пропускну здатність файлової системи на вузлі (реальність датапайплайна)
cr0x@server:~$ dd if=/mnt/dataset/bigfile.bin of=/dev/null bs=64M count=16 iflag=direct,status=progress
1073741824 bytes (1.1 GB, 1.0 GiB) copied, 1.244 s, 863 MB/s
16+0 records in
16+0 records out
1073741824 bytes (1.1 GB, 1.0 GiB) copied, 1.24567 s, 862 MB/s
Значення: Послідовне читання ≈860MB/s; може бути достатньо, але ML-навантеження часто потребують випадкових читань і операцій з метаданими теж.
Рішення: Якщо це низько або нестабільно, ваш «вузький місце GPU» ймовірно сховище або мережа. Виправте це — і утилізація GPU магічно зросте.
Завдання 13: Перевірити стан мережі для тренувань у кількох вузлах (чутливість NCCL)
cr0x@server:~$ ip -s link show dev eth0 | sed -n '1,12p'
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9000 qdisc mq state UP mode DEFAULT group default qlen 1000
link/ether 52:54:00:aa:bb:cc brd ff:ff:ff:ff:ff:ff
RX: bytes packets errors dropped missed mcast
9876543210 1234567 0 12 0 0
TX: bytes packets errors dropped carrier collsns
8765432109 2345678 0 0 0 0
Значення: Існують відкинуті RX-пакети. На завантаженому кластері «12» може бути шумом або початком проблем.
Рішення: Якщо відкидання зростає під час тренування, досліджуйте буфери NIC, невідповідності MTU, контроль перевантаження й порти комутаторів. NCCL карає нестабільні мережі сповільненням, що виглядає як «погане масштабування GPU».
Завдання 14: Швидкий NCCL-debug для помилок топології/транспорту
cr0x@server:~$ NCCL_DEBUG=INFO NCCL_DEBUG_SUBSYS=INIT,NET python3 - <<'PY'
import os
print("NCCL_DEBUG", os.environ.get("NCCL_DEBUG"))
print("NCCL_DEBUG_SUBSYS", os.environ.get("NCCL_DEBUG_SUBSYS"))
PY
NCCL_DEBUG INFO
NCCL_DEBUG_SUBSYS INIT,NET
Значення: Ви ввімкнули логи ініціалізації/мережі NCCL для запуску.
Рішення: Використайте це, щоб підтвердити транспорт (InfiniBand vs TCP), вибір інтерфейсу і виявлення топології. Якщо падає в TCP несподівано — виправте конфігурацію fabric, перш ніж щось оптимізувати.
Завдання 15: Перевірити конфігурацію MIG (сюрпризи мультиоренди)
cr0x@server:~$ nvidia-smi -L
GPU 0: NVIDIA A100-SXM4-40GB (UUID: GPU-aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee)
MIG 1g.5gb Device 0: (UUID: MIG-GPU-aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/1/0)
MIG 1g.5gb Device 1: (UUID: MIG-GPU-aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee/2/0)
Значення: GPU розбито на екземпляри MIG; ваша задача може бачити лише частку.
Рішення: Якщо продуктивність «таємниче низька», підтвердіть, що планування запросило правильний MIG-профіль. Не порівнюйте показники зрізу MIG із повним GPU і не звинувачуйте CUDA за невідповідність.
Три корпоративні міні-історії (анонімізовані, правдоподібні й болісно знайомі)
Міні-історія 1: Інцидент через невірне припущення
Середня компанія запускала нічні fine-tune-і на пулі GPU. Вони контейнеризували все і відчували себе зрілими: зафіксовані Python-залежності, закріплений PyTorch-wheel, зафіксований базовий образ.
Хтось запропонував «безпечне» обслуговування хоста: оновити драйвер NVIDIA по всьому фліту, щоб співпасти з новим ядром. У запиті на зміну була заспокійлива фраза «драйвери назад сумісні».
Розгортання почалося нормально. Потім один клас задач почав падати з illegal memory access errors. Не одразу. Через десять–тридцять хвилин після старту тренування, що найгірше: достатньо довго, щоб марнувати гроші, і достатньо коротко, щоб ламати пропускну здатність.
Інженери ганялися за примарами в коді моделі, потім у CUDA-розширеннях, потім у корупції даталоадера. Інцидент тривав цілий день, бо помилки були недетерміновані й корелювали з певними нодами.
Невірне припущення полягало не в тому, що зворотна сумісність існує. Воно було в тому, що це універсальна гарантія для всіх комбінацій: гілка драйвера, мікрокод GPU, фреймворк і кастомні розширення.
Одне розширення використало JIT-компіляцію через PTX і покладалося на поведінку, яка тонко змінилася з оновленням драйвера. JIT згенерував інший код, і при певній формі вхідних даних це спрацювало як крайовий випадок.
Фікс був нудний: зафіксувати гілку драйвера для того кластера, перебудувати розширення з явними arch-мішенями (fatbin, що включає SASS для розгорнутих GPU) і додати canary job, який запускав репрезентативні форми протягом години перед просуванням драйвера.
Урок гостріший: «сумісний» ≠ «ідентичний». У GPU-середовищі саме «ідентичний» — слово, якому ваші інцидентні черги вірять.
Міні-історія 2: Оптимізація, що відплатилася
Інша організація мала pipeline розподіленого навчання, що погано масштабувався понад 4 GPU. Хтось побачив знайомого «злодія»: передачі хост→пристрій. Вони ввімкнули агресивний prefetching і збільшили кількість worker-ів даталоадера.
Перший бенчмарк покращився. Святкування. Потім продакшн почав пропускати SLA, і не лише тренування — інші сервіси на тих же нодах теж сповільнилися.
Оптимізація повернулась бумерангом, бо вона пересунула вузьке місце в спільні ресурси. Більше worker-ів — більше навантаження на CPU, більше churn у page cache і більше метаданих у спільному сховищі.
GPU на деякий час показали вищу утилізацію, але хвости латентності кластера стали поганими. Частина задач була швидкою; інші зупинялися, коли бекенд сховища був перегрітий. Ретраї підсилювали навантаження, бо звісно, так і сталося.
Корінна проблема не в «prefetch — погано». Вона була в prefetch без ізоляції ресурсів і без вимірювання всієї системи.
Вони налаштовувалися на пропускну здатність однієї задачі і випадково DoS-нули власне сховище — класичний спосіб дізнатися, що IOPS — реальна річ, а не порада.
Виправлення включало обмеження кількості worker-ів, додавання throttle-ів I/O на задачу, використання локального NVMe-кешу для гарячих шардів і впровадження пайплайну, який попередньо обробляв і пакував дрібні файли в більші суцільні бінарні об’єкти.
Утилізація GPU трохи впала, але загальна пропускна здатність і передбачуваність системи покращилися — а в продакшені це і є мета.
Міні-історія 3: Нудна, але правильна практика, що врятувала день
Компанія, що запускала inference, мала правило: кожен GPU-сервіс повинен відкривати діагностичний endpoint, який повертає версію драйвера, видимі пристрої і версії CUDA-бібліотек, завантажених у процес.
Нікому це правило не подобалося. Воно виглядало як JSON-паперова робота. Але воно виконувалося.
Потім пішов ребілд іміджу з новою мажорною версією cuDNN. Вона працювала в staging (один вузол, легке навантаження) і падала в продакшні (інша SKU GPU, вища конкурентність). Помилки виглядали як загальні «CUDNN_STATUS_INTERNAL_ERROR».
Онк-колега на виклику не мусив вгадувати. Він влучив у endpoint, побачив несумісність бібліотек і за кілька хвилин зіставив це з хвилею релізів.
Відкат пройшов швидко, бо образи були іммутабельні й версовані, і був відомий baseline tag, який завжди залишався доступним.
Постмортем нічого не гучного: кращі інтеграційні тести проти кількох SKU GPU і політика, що мажорні оновлення cuDNN вимагають gates сумісності.
Це правило — нудний діагностичний endpoint — перетворило потенційний багатогодинний інцидент на контрольований відкат. Надійність часто — це мистецтво бути незахопливою.
Жарт №2: «Ми просто хотфіксимо CUDA-образ у продакшні» — це GPU-еквівалент «я просто пожонглюю бензопилами, щоб заощадити час».
Типові помилки: симптом → корінна причина → виправлення
1) Симптом: torch.cuda.is_available() — false у контейнерах
Корінна причина: Конфігурація рантайму контейнера не підтримує GPU, відсутній device plugin або використовується CPU-only збірка фреймворку.
Виправлення: Перевірте з відомим CUDA-базовим образом + nvidia-smi всередині контейнера; забезпечте правильний рантайм; встановіть CUDA-enabled wheeл-и.
2) Симптом: «Версія драйвера CUDA недостатня для версії рантайму CUDA»
Корінна причина: Контейнер постачає новіший CUDA-рантайм, ніж хост-драйвер підтримує.
Виправлення: Оновіть драйвер хоста або понизьте тулкіт/фреймворк у контейнері; стандартизуйте матрицю сумісності для кластера.
3) Симптом: Випадкові illegal memory access / Xid помилки під навантаженням
Корінна причина: Баг драйвера, нестабільна апаратура, перегрів/проблеми з живленням або крайовий випадок JIT/PTX, викликаний певними ядрами.
Виправлення: Перевірте dmesg на Xid; карантин нод; тестування з фіксацією драйвера; перебудова кастомних розширень з явними arch-мішенями; розгляд діагностики апаратури/RMA.
4) Симптом: Низька утилізація GPU, але CPU і iowait високі
Корінна причина: Вузьке місце в вхідному пайплайні: латентність сховища, забагато дрібних файлів, накладні витрати на декомпресію, недостатній CPU або погане NUMA-розміщення.
Виправлення: Профілюйте даталоадер; пакуйте датасети; використовуйте локальний кеш; пришпильте потоки до NUMA-локальних ядер; зменшуйте накладні на зразок per-sample обробки.
5) Симптом: Мульти-GPU масштабування руйнується після 2–4 GPU
Корінна причина: NCCL відкочується до TCP, погана обізнаність про топологію, насичена мережа, або неправильні змінні оточення, що вибирають неправильний інтерфейс.
Виправлення: Ввімкніть NCCL debug; підтвердьте транспорт; виправте вибір NIC; перевірте MTU; забезпечте алгоритми колективів, відповідні топології; уникайте over-subscription.
6) Симптом: Регрес продуктивності після оновлення cuDNN/cuBLAS
Корінна причина: Зміни в евристиках вибору ядер, поведінці autotune або вимкнення швидких шляхів через відмінності форм/лайаутів.
Виправлення: Перепустіть бенчмарки з репрезентативними формами; зафіксуйте налаштування autotune; закріпіть відомо-дійні версії бібліотек для критичних навантажень; розгляньте явний вибір ядра, якщо він доступний.
7) Симптом: «Працює на A100, повільно на L4 (або навпаки)»
Корінна причина: Архітектурні відмінності: покоління tensor core, розмір/пропускна здатність пам’яті, поведінка частот, підтримувані режими точності.
Виправлення: Компілюйте з правильними arch-мішенями; налаштуйте batch size і режими точності під SKU; зберігайте окремі базові показники продуктивності для кожного класу GPU.
8) Симптом: Сплески latency в inference періодично
Корінна причина: Фрагментація пам’яті GPU, JIT-компіляція при першому запиті, фонова компакція або конкурентні задачі, що відбирають GPU.
Виправлення: Розігрівайте ядра; попередньо збирайте движки (TensorRT), де це можливо; резервуйте пули пам’яті; забезпечте ізоляцію GPU (MIG/MPS/квоти); уникайте ко-оренди для latency-чутливих шляхів.
Чеклісти / покроковий план
Чекліст: працювати з CUDA в продакшені, не ненавидячи життя
- Стандартизувати гілки драйверів по кластерах (не по нодах) і переводити зміни через canary-і.
- Вибрати базу контейнера (OS + сімейство CUDA тулкітів) і ставитися до неї як до платформи, а не пропозиції.
- Закріпити збірки фреймворків (PyTorch/TF/JAX) до платформи; уникайте «latest», якщо не любите сюрпризів.
- Версіонувати і заморожувати CUDA-розширення з явними арх-білдами (fatbins), де можливо.
- Експонувати рантайм-діагностику: версія драйвера, видимі пристрої, завантажені CUDA-бібліотеки і інфо про збірку.
- Відокремити середовища для продуктивності: ноди для бенчмаркінгу не повинні ділитися з шумними сусідами.
- Зробити датапайплайни першокласним SLO: відстежуйте iowait, пропускну здатність читання, операції з метаданими, хіт-рейт кешу.
- Тримайте базові показники по SKU GPU для пропускної здатності і латентності; не порівнюйте яблука з MIG-зрізами.
- Визначте модель мультиоренди: MIG, MPS, exclusive mode або «не використовувати». Потім її забезпечуйте політиками.
- Практикуйте відкат: іммутабельні образи, відомо-дійні теги і протестований шлях даунгрейду драйвера.
Покроково: міграція від CUDA (або хоча б зниження ризиків)
- Інвентаризуйте, що реально специфічне для CUDA: кастомні ядра, TensorRT-движки, NCCL-ассумпції, wheel-и тільки для CUDA.
- Виміряйте «необхідну продуктивність»: визначте прийнятні дельти; 5% може бути нормально для тренування, катастрофічно для маржі inference.
- Почніть з країв: препроцесинг, inference на CPU або ONNX-експорт легше портировать, ніж кастомні CUDA-op-и.
- Відокремте коректність від швидкодії: спочатку досягніть функціональної парності, потім оптимізуйте.
- Побудуйте harness для подвійного запуску: ті самі входи, порівняйте виходи/статистику, виявляйте дрейф; не покладайтесь на «виглядає нормально».
- Плануйте операційні прогалини: телеметрія, інструменти налагодження, звітування про помилки ядер, зрілість пакування.
- Тримайте CUDA як fallback, поки альтернатива не пройде кілька релізних циклів під реальним трафіком.
Покроково: безпечне оновлення драйверів/тулкітів
- Визначте ціль сумісності: які фреймворки і контейнери повинні працювати на новому драйвері.
- Побудуйте canary-suite: репрезентативні задачі, включно з кастомними розширеннями і розподіленими запусками.
- Оновіть невеликий пул нод і запускайте canary протягом годин, а не хвилин.
- Слідкуйте за Xid-помилками і тонкими регресіями: пропускна здатність, хвоста латентність, патерни використання пам’яті.
- Промотуйте поступово з автоматичними тригерами відкату при підвищенні рівня помилок чи падінні продуктивності.
- Документуйте нову благословенну матрицю і забезпечте її в CI.
Поширені питання
1) Чи є CUDA «причиною» домінування NVIDIA в AI?
Це велика причина. CUDA зробила GPU програмованими в масштабі, а потім екосистема бібліотек (cuDNN, cuBLAS, NCCL, TensorRT) зробила продуктивність і деплойт відтворюваними.
Апаратура важлива, але саме програмне забезпечення перетворює апаратуру на галузевий стандарт.
2) Що саме таке локальна прив’язка: код, інструменти чи операції?
Всі три. Кодова прив’язка виникає через CUDA-ядра й розширення. Інструментальна прив’язка — через профайлери й дебагери, налаштовані під модель NVIDIA.
Операційна прив’язка — через патерни драйверів/тулкітів і глибоку залежність від бібліотек NVIDIA для продуктивності й стабільності.
3) Чи можна уникнути прив’язки, пишучи в PyTorch і ніколи не торкаючись CUDA напряму?
Ви зменшите джерело-кодну прив’язку, але не позбудетеся платформної. Ваші wheel-и фреймворку, бекенд для розподілення (часто NCCL) і критичні за продуктивністю ядра все одно зв’язують вас з екосистемою CUDA.
4) Чому патерн «драйвер на хості, тулкіт в контейнері» такий важливий?
Бо він дозволяє стандартизувати драйвери на рівні фліту і одночасно допускати різні тулкіти для додатків.
Це прагматично: драйвери важче оновлювати безпечно, ніж контейнери. Цей патерн зробив деплой GPU більш схожим на звичайну інфраструктуру.
5) Яка найпоширеніша продакшн-форма відмови з CUDA?
Дрейф версій: перебудова контейнера змінює CUDA-бібліотеки або збірки фреймворків, і раптом комбінація драйвер/тулкіт на хості більше не сумісна.
Друга найпоширеніша — «GPU повільний», що насправді є проблемою сховища, CPU або мережі.
6) Як зрозуміти, чи ви compute-bound чи input-bound?
Почніть з утилізації GPU і споживання потужності. Якщо утилізація низька, а CPU iowait високий — ви input-bound.
Якщо потужність GPU висока і утилізація стабільна — швидше за все compute-bound. Тоді оптимізуйте ядра, режими точності і batch size.
7) Чи зменшує MIG прив’язку?
MIG зменшує контенцію ресурсів і підвищує передбачуваність мультиоренди на апаратурі NVIDIA. Воно не зменшує прив’язку до CUDA; навпаки, збільшує вашу залежність від інструментів і інтеграції NVIDIA в операціях.
Тим не менш, його варто використовувати, коли мікс навантажень вимагає ізоляції.
8) Чому оновлення CUDA інколи змінює продуктивність, навіть якщо нічого не падає?
Оновлення бібліотек змінюють евристики вибору ядер і можуть вмикати/вимикати швидкі шляхи.
Оновлення драйвера можуть змінювати JIT-компіляцію і поведінку планувальника. Модель не змінилася, але код, що її виконує, — міг змінитися.
9) Чи реально мігрувати CUDA-навантаження на інший GPU-стек?
Часом — так. Якщо ви переважно покладаєтесь на стандартні операції фреймворків, міграція — більше про валідацію коректності й продуктивності.
Якщо у вас кастомні CUDA-розширення, TensorRT-движки і сильно оптимізовані NCCL-патерни, міграція стає багатоквартальним проєктом з реальними ризиками.
10) Що стандартизувати першим: драйвери, тулкіти чи фреймворки?
Спочатку драйвери на рівні кластера, потім базові образи контейнерів (тулкіти + OS), а вже потім фреймворки.
Стандартизувати фреймворки без контролю драйвера/платформи — шлях до «працює на деяких нодах».
Висновок: практичні кроки на наступний тиждень
CUDA прив’язала індустрію тим же способом, яким надійна інфраструктура прив’язує компанію: вона робить те, що надійно працює в масштабі, а потім навколо неї акумулюються інструменти, звички і припущення про продуктивність.
Якщо ви керуєте продакшен-системами, не боріться з цим ідеологічно. Боріться з ясністю.
- Запишіть вашу благословенну матрицю сумісності: гілка драйвера, базовий образ, версії фреймворків, мажорні версії NCCL/cuDNN.
- Реалізуйте швидкий план діагностики як раннуб і автоматизуйте перші п’ять перевірок (видимість GPU, помилки, утилізація, iowait, мережеві відкидання).
- Додайте canary-навантаження, які працюють достатньо довго, щоб зловити «падає тільки під навантаженням» поведінку, особливо для JIT/PTX і кастомних розширень.
- Припиніть називати проблеми з даними «GPU-проблемами»: відстежуйте метрики сховища і препроцесу поруч із метриками GPU на одному дашборді.
- Якщо хочете опціональність, почніть з ізоляції CUDA-специфічного коду і побудови тестів подвійного виконання. Опціональність — це інженерія, а не бажання.
CUDA — це платформа. Ставтеся до неї як до платформи: версіонуйте, тестуйте, пропускайте через gates і спостерігайте. Прив’язка стає керованою, коли ви припиняєте вдавати, що це просто бібліотека, яку можна випадково оновити в п’ятницю.