Вычислительные блоки современного графического процессора успевают за такт выполнить миллионы операций, но всё чаще простаивают у кассы: данные из видеопамяти прибывают медленнее, чем ядра успевают их потреблять. Инженер видит это по характерной картине, когда загрузка SM близка к сотне процентов, а скорость растёт вразнобой с ожиданиями. Настоящая причина обычно прячется в пропускной способности памяти, и Linux даёт достаточно инструментов, чтобы измерить её точно, зафиксировать состояние платы и превратить догадку в воспроизводимое число. Ниже собраны команды nvidia-smi, разбор Nsight Compute, утилиты AMD и Intel, расчёты пиковых значений для GDDR6, GDDR6X, GDDR7 и HBM, а также проверенная последовательность замера, после которой цифрам можно верить.
Пиковая полоса видеопамяти считается из ширины шины и частоты
Формула вмещается в одну строку: пиковая полоса равна ширине шины в битах, поделённой на восемь, и умноженной на эффективную частоту передачи по каждому контакту. Ширину берут из спецификации чипа, а частоту считают в гигатрансферах в секунду, потому что память DDR-типа передаёт два блока данных за такт.
Пара примеров показывает всю механику. Плата с 192-битной шиной и GDDR6 на 14 Гбит/с даёт 24 × 14 = 336 ГБ/с, характерный уровень средних карт прошлого поколения. RTX 3080 с шиной 320 бит и GDDR6X на 19 Гбит/с на контакт выдаёт 40 × 19 = 760 ГБ/с. RTX 4090 с 384 битами и 21 Гбит/с выжимает 1008 ГБ/с, а RTX 5090 на GDDR7 с 512-битной шиной и 28 Гбит/с добирается до 1792 ГБ/с. У серверных ускорителей шина шире в разы: A100 с 5120-битной шиной HBM2e при 3.2 Гбит/с даёт около 2048 ГБ/с, MI250X с 8192 битами поднимается к 3277 ГБ/с, а MI300X с той же шириной и 5.2 Гбит/с на контакт добирается до 5325 ГБ/с.
Красивое совпадение сидит в выводе nvidia-smi. Утилита печатает частоту памяти не как эффективную, а как базовую, поэтому заявленную полосу можно проверить прямо в системе: A100 показывает Memory 1593 МГц, и 1593 × 2 × 640 байт дают ровно 2039 ГБ/с из спецификации. RTX 3080 печатает 9501 МГц, и после удвоения и умножения на 40 байт выходит те же 760 ГБ/с. С GDDR6X есть нюанс: сигнализация PAM4 переносит больше одного бита на символ, простое удвоение не всегда сходится с паспортом платы, поэтому итоговую полосу для таких чипов сверяют с официальным числом. Нюанс мелкий, но он спасает от ложных выводов, когда человеку приписывают несуществующие терабайты.
Пиковое значение всё равно остаётся потолком. В реальных приложениях занятость полосы редко превышает 70–85 % от пика, а на GDDR6X из-за особенностей шины фактическая доля ещё ниже. Именно поэтому профилирование смотрит не на паспортную цифру, а на реальную занятость: она отвечает на вопрос, упёрся ли код в память.
Системные команды показывают частоты, память и причины просадок
Отправная точка любого замера выглядит так: nvidia-smi -q -d CLOCK печатает блоки Clocks, Applications Clocks, Default Applications Clocks и Max Clocks с полями Graphics, SM, Memory и Video. Поле Memory и есть текущая частота видеопамяти, по которой пересчитывается фактическая полоса. Список допустимых частот для фиксации выдают ключом nvidia-smi -q -d SUPPORTED_CLOCKS, он же показывает, какие значения драйвер готов принять.
Занятость буфера смотрят ключом nvidia-smi -q -d MEMORY, там видны занятая, свободная и зарезервированная части видеопамяти. Причины просадок производительности читают через nvidia-smi -q -d PERFORMANCE: раздел Clocks Event Reasons называет конкретный фактор, например SwPowerCap означает упор в лимит мощности, а HwSlowdown говорит о принудительном снижении частоты по состоянию железа. Без этого раздела легко спутать тепловой троттлинг с программной ошибкой. Отдельно пригодится ключ nvidia-smi -q -d UTILIZATION: он показывает долю тактов, когда память обслуживает запросы, и вместе с загрузкой SM помогает отличить паузу между задачами от реального голода по данным.
Для скриптов удобнее выборочный запрос: nvidia-smi --query-gpu=clocks.mem,clocks.max.mem,utilization.memory,power.draw,power.limit,temperature.memory --format=csv -l 1 выдаёт таблицу раз в секунду, которую уже можно складывать в файл и строить графики. Поле temperature.memory заслуживает отдельной любви: на платах с GDDR6X модули памяти греются сильнее ядра, и рост этой температуры первым предупреждает о скором снижении частоты.
Слежение dmon в реальном времени и чтение счётчиков PCIe
Статика малоинформативна, поэтому вторым шагом запускают nvidia-smi dmon: команда печатает одну строку за цикл с мощностью, температурами, загрузкой SM и памяти, работой энкодера и декодера. Выбор полей задают ключом -s, где c добавляет частоты ядра и памяти в МГц, m выводит занятость кадрового буфера и BAR1 в МБ, а t открывает счётчики приёма и передачи по PCIe в МБ/с. Команда вида nvidia-smi dmon -s pc -d 1 -c 60 собирает минутный срез мощности и частот, а ключ -o T ставит перед каждой строкой время.
Кстати о ловушке, на которой спотыкаются почти все новички: колонка pci отражает обмен по шине PCIe с хостом, а не полосу видеопамяти. Ядро, целиком живущее на ускорителе, покажет в ней почти ноль при полностью занятой внутренней шине памяти. Низкий трафик PCIe вовсе не признак простоя, и наоборот.
Для длинных прогонов есть фоновый процесс, запускаемый командой nvidia-smi daemon: он пишет те же метрики в сжатые логи в каталоге /var/log/nvstats, а утилита nvidia-smi replay выдаёт их ровно так, будто замер шёл в консоли. Не менее полезна служба nvidia-persistenced, соответствующая ключу nvidia-smi -pm 1: без неё драйвер выгружает состояния при простое, и первый запуск теста после паузы показывает заниженную частоту, ломая всю статистику.
Блокировка частот и лимит мощности делают замеры воспроизводимыми
Живой ускоритель ведёт себя как погода: частоты памяти и ядра плывут от прогона к прогону, и два одинаковых замера дают разброс в проценты. Лечение простое, и ключи уже видели в справке. Команда sudo nvidia-smi -lgc 1500,1500 фиксирует частоту ядра, ключ принимает и пару значений, и одиночное число. Команда sudo nvidia-smi -lmc 1593 закрепляет частоту памяти, например на A100, а на GeForce из списка подходят значения вроде 9501. Проверить, поддерживает ли плата такую фиксацию и в каком виде, позволяет запрос nvidia-smi -lmi, сброс делают ключами -rmc и -rgc.
Тонкость есть на ускорителях архитектуры Hopper: прямой -lmc там не работает, драйвер требует ключ -lmcd, и новая частота памяти вступает в силу при следующей инициализации GPU после перезагрузки модуля. Это ловушка для автоматизации, скрипт с -lmc на H100 просто вернёт ошибку. Хорошая новость в том, что частоту видно обычным путём: nvidia-smi на H100 печатает Memory 2619 МГц, и 2619 × 2 × 640 байт дают заявленные 3.35 ТБ/с.
Второй рычаг воспроизводимости – лимит мощности. Команда sudo nvidia-smi -pl 250 ограничивает бюджет, а допустимые границы печатаются в разделе nvidia-smi -q -d POWER рядом с полями Default Power Limit, Min Power Limit, Max Power Limit и Enforced Power Limit. Свежие платы показывают и отдельную мощность подсистемы памяти в блоке GPU Memory Power Readings, по которому видно, сколько ватт уходит на чипы памяти. Замер на зафиксированных частотах и постоянном лимите даёт разброс уже в доли процента, и на этой почве результаты разных прогонов наконец сравнимы.
Nsight Compute показывает занятость полосы для каждого ядра
Системные счётчики говорят общую картину, а виновника ищут на уровне отдельных ядер, и тут главный инструмент для CUDA – это Nsight Compute. Запуск через командную оболочку выглядит так: ncu --section SpeedOfLight --section MemoryWorkloadAnalysis ./app. Секция SpeedOfLight сводит картину к двум числам, SM Throughput и Memory Throughput в процентах от пика, и сразу видно, чем живёт ядро. Если память занята сильнее вычислений, дальнейший путь лежит в секции MemoryWorkloadAnalysis: она показывает занятость DRAM в процентах от пика, попадания в кэш L2 и диаграмму перемещения данных между уровнями памяти.
Точнее работает прямой запрос метрик: ncu --metrics dram__throughput.avg.pct_of_peak_sustained_elapsed,dram__bytes_read.sum,dram__bytes_write.sum ./app считает байты, ушедшие в DRAM, и делит их на длительность ядра, а полный список доступных счётчиков печатает ncu --query-metrics. Для изучения в интерфейсе отчёт сохраняют ключом -o и открывают в ncu-ui, где рядом видны таблицы обращений по секторам и размеры запросов. Счётчики ядра снимают с оговоркой: профилировщик сериализует работу ядер и переигрывает их несколько раз, поэтому системные метрики dmon и цифры ncu берут из разных прогонов, а не одновременно. Быстрый обзор дают ключом ncu --set basic, а разносторонний разбор ключом --set full, хотя второй заметно замедляет прогон.
Замер чистого пика делают отдельными средствами: утилита nvbandwidth от NVIDIA и классический bandwidthTest из набора cuda-samples гоняют большие блоки между устройством и хостом и внутри самой памяти. Разница между этим потолком и полосой из реального ядра и есть запас для оптимизации, и, честно говоря, самый наглядный мотиватор в работе профилировщика.
На AMD и Intel полосу памяти считают другие инструменты
Сторона AMD держит ту же логику в утилите amd-smi, которая в свежих версиях ROCm заменила классический rocm-smi. Для привычки оставлен режим совместимости amd-smi --rocm-smi. Мониторинг выглядит знакомо: amd-smi monitor -m -v -r -p -w 1 печатает загрузку памяти с текущей частотой в МГц, занятость буфера в МБ, трафик PCIe и мощность. Текущие, средние и максимальные частоты выдаёт amd-smi metric -c, а допустимые уровни частоты памяти перечисляет amd-smi static -C MEM. Управление тоже на месте: sudo amd-smi set -L mclk max <частота> закрепляет верхнюю границу памяти, уровень STABLE_MIN_MCLK из набора amd-smi set -l фиксирует минимальную, лимит мощности задаёт sudo amd-smi set -o 300, сброс выполняют ключами amd-smi reset -c и -o. Счётчики полосы HBM по ядрам собирает ROCm Compute Profiler, прежде известный как Omniperf, он показывает занятость полосы для каждого ядра в процентах от пика, а копирование между CPU и GPU проверяют утилитой rocm-bandwidth-test.
С интегрированной графикой Intel работает intel_gpu_top из пакета intel-gpu-tools: утилита читает счётчики PMU драйвера i915, питание через RAPL и полосу памяти через счётчики Uncore IMC, показывая строки Read и Write в МБ/с. Доступ без root зависит от значения perf_event_paranoid, а ключ -J переключает вывод в JSON для логов. Формула пика у систем с общей памятью считается от каналов ОЗУ: два канала DDR5-5600 дают 8 × 2 × 5600 = 89.6 ГБ/с, и интегрированное ядро никогда не приблизится к дискретным картам вроде Arc A770 с её 512-битной шиной и 560 ГБ/с.
Порядок замера полосы и главные ловушки профилирования
Практика выработала последовательность, которая экономит часы и уберегает от ложных выводов:
- снять исходное состояние командами nvidia-smi -q -d CLOCK и nvidia-smi -q -d POWER;
- зафиксировать частоты ядра и памяти ключами -lgc и -lmc, при необходимости сдвинуть лимит ключом -pl;
- прогреть рабочую нагрузку, чтобы частоты и температуры вышли на рабочий уровень;
- параллельно запустить nvidia-smi dmon -s pcuvt и тестовый прогон, собрав системный фон в один срез;
- сравнить достигнутую полосу с расчётным пиком и ответить, где узкое место;
- вернуть настройки ключами -rmc и -rgc и сбросом лимита мощности.
Пара ловушек, о которых скажем прямо. Повторные прогоны на маленьком объёме данных читают из кэша L2, занятость DRAM проседает, и создаётся ложное ощущение, будто память не при деле, хотя ядро просто перестало ходить в медленную память. Полезно сразу записать первый прогон как базу: Nsight Compute умеет сравнивать отчёты между собой, и любое изменение ядра проверяют против этой базы, а не по памяти. Фоновые процессы рабочего стола и медиасервисы съедают полосу понемногу, но заметно на коротких замерах, поэтому сервер для тестов держат без лишних служб. Нагрев GDDR6X понижает частоту памяти раньше ядра, и летний замер легко отличается от зимнего.
Ощущение контроля приходит, когда три числа сходятся: расчётный пик из ширины шины, потолок из nvbandwidth и фактическая занятость из профилировщика. Дальше меняют код или частоты, но уже по цифрам, а не по интуиции. Именно такая дисциплина отличает инженера, отвечающего за производительность, от человека, пересказывающего маркетинговые терабайты.