В 2022 году автор наткнулся на проект CUTLASS с быстрыми матричными умножениями. Он запустил большой matmul 8192×8192×8192 в PyTorch (через CuBLAS) — получил 258 терафлопс (83% утилизации). Потом запустил тот же тест в бенчмарке CUTLASS — и получил 288 терафлопс. На 10% быстрее! Но когда он обернул ядра CUTLASS в Python, результат упал до 257 терафлопс. Всё пропало.
Автор начал копать. Оказалось, профилировщик CUTLASS по умолчанию инициализирует входные данные целыми числами. Автор проверил: с torch.zeros получил 295 терафлопс, а с torch.randn — 257. Как значения матрицы влияют на скорость? Ведь matmul всегда делает одни и те же операции в одном порядке, обращается к тем же адресам памяти. Но данные реально влияют.
Виновник — динамическая (коммутационная) мощность в полупроводниках. У A100 лимит питания 400 Вт, но в простое GPU потребляет 88 Вт. Под нагрузкой потребление скачет к лимиту. Чтобы не превысить его, регулятор напряжения (Voltage Regulator Module) снижает напряжение и частоту — происходит троттлинг. Динамическая мощность тратится, когда транзистор переключает состояние. Если биты не меняются — энергии почти не нужно. Чем больше переключений, тем выше расход.
Нулевые входные данные дают минимум переключений — транзисторы не «прыгают», GPU остаётся в рамках лимита и не троттлит частоту. Случайные числа (randn) заставляют постоянно переключаться — упираемся в лимит мощности, частота падает.
Автор проверил: при снижении лимита мощности (с 330 до 100 Вт) разница между нулями и случайными данными растёт. При очень низком лимите (100 Вт) тренд меняется — даже нули не спасают. Отдельные замеры с разными тактовыми частотами тоже подтвердили: при высоких частотах GPU всё равно упирается в питание.
Вывод: реальная производительность matmul на современных GPU (включая H100) упирается не в вычисления или память, а в лимит по питанию. Маркетинговые 989 TFLOPS для H100 (1.83 ГГц × 528 тензорных ядер × 1024 FLOP) недостижимы в реальности — GPU не хватает мощности держать пиковую частоту. Nvidia это косвенно признала в своём MLPerf-отчёте, где они перераспределяли питание с кэша L2 на вычислительные блоки.