GPU-ускорение FFmpeg. Видите прибавку в скорости? И я нет. А она должна быть…

1ab8982dead2212e1ae6da59e2cb0313.jpg

Привет, Хабр! С вами Матвей Мочалов, и сегодня у нас небольшая лабораторная работа. Вспомним, что GPU нужны не только для нейронок и AI — еще они могут ускорять много других полезных задач. А конкретно мы сравним разницу в скорости между работой FFmpeg на процессоре и на видеокарте Nvidia.

В ролях у нас гибридный ноутбук под Linux с мобильной видеокартой RTX 3050Ti и процессором Ryzen 5 5600H. Также в массовке участвует удалённый тестовый сервер с Xeon и заглушкой в PCI слот, которую дядя Дженсен Хуанг решил по доброте сердечной добавить в линейку Quadro.
К слову, ранее мы уже немного работали с FFmpeg, разбираясь с транскодированием и устройством видеоконтейнеров тут.

Подготовка к тесту

Сначала посмотрим, на чём будем проводить тесты.
Начнём с моей личной машины, на которой крутится EndeavourOS (Arch):

Ядро — 6.9.3-arch1–1, 8 гигов DDR4 на частоте 3200Mhz + 8 гигов физического swap и ещё 8 за счёт zram.  CPU — ryzen 5600h 6/12 с теплопакетом 35w (хотя, если верить nvtop, то доходит до 45w) и GPU — RTX 3050ti на 4gb VRAM с теплопакетом 60w.

Версия драйвера  Nvidia — 550.78–7, FFmpeg — 2:6.1.1–7.

Тестовый сервер — AlmaLinux 9.3, ядро 6.6.11–1.el9.x86_64, 16 гигов DDR3 и 2 гига swap.
CPU — Xeon 2630, 6 ядер с отключенным SMT, GPU — Quadro P400.

Версия драйвера Nvidia — 550.54.15,   FFmpeg — свой билд с оптимизациями под CUDA.

Отдельно сделаю ещё акцент на том, что Xeon — это старичок 2012 года, а Quadro — 2017, но, не смотря на возраст, результаты будут весьма неоднозначными.

Теперь скачаем тестовое видео. Пусть это будет детище Blender Foundation — Big Buck Bunny.

```bash
wget https://download.blender.org/demo/movies/BBB/bbb_sunflower_2160p_30fps_normal.mp4.zip
unzip bbb_sunflower_2160p_30fps_normal.mp4.zip
```

97d89b5de75a907ec108a1f5fe893984.png

Тестирование

Для начала прогоним сравнительный тест на моём ноуте без GPU-ускорения.
Тестировать будем рескейлинг с разрешения 2160p до 1080p.

```bash
ffmpeg -i bbb_sunflower_2160p_30fps_normal.mp4 -vf scale=1920:1080 -c:v mpeg4 -preset medium output_no_acc.mp4
```

И повторим так 5 раз для надёжности, получая вот такие результаты:

Номер теста

Скорость

1

4.09x

2

3.95x

3

4.22x

4

4.17x

5

4.26x

Средний результат — 4.138x.

Однако тест выходит не очень честным, так как, судя по нагрузке на GPU, в nvtop резко растёт потребление питания у iGPU и процент нагрузки на графическое ядро. При этом потребление видеопамяти почти не меняется. Xeon же в тестовом сервере не обладает встроенной графикой вовсе.

74b6da9ba7b5932cd0fb756a2a47e43d.pngd0ca7cb8d4304e85dac7dbea6a32c961.png

Теперь на тестовом сервере:

Номер теста

Скорость

1

3.54x

2

3.52x

3

3.52x

4

3.55х

5

3.54х

В итоге среднее — 3.534x.

Современный Ryzen 5600H на 14.59% оказался быстрее старичка Xeon 2630 — результат, откровенно говоря, не сильно впечатляющий. Между процессорами пропасть почти 10 лет (ряженка вышла в 2021 году). Так ещё к тому же на её стороне была включенная гиперпоточность, хотя что-то мне подсказывает, что в этой операции FFmpeg работал в однопоточном режиме. Кроме того, ей помогала интегрированная графика. Серверный труженик и любитель сборщиков ПК с Алиэкспресс интеграшкой не обладает, ещё и гиперпоточность была задушена.

Впрочем, опустим странности и сомнительный прогресс производительности процессоров для другого поста. Перейдём к тому, что интересно в первую очередь — GPU-ускорению.

CUDA

Начнём снова с моего ноутбука и повторим тесты также по 5 раз.

```bash
ffmpeg -hwaccel cuda -i input.mp4 -vf "scale=1920:1080,format=yuv420p" -c:v mpeg4 -preset medium cuda.mp4
``` 

Номер теста

Скорость

1

4.75х

Как можно заметить, что-то тут не сходится. GPU-ускорение должно быть по определению заметно быстрее в обработке видеоматериала, однако, мы получили результат лишь слегка быстрее, чем у процессора с интегрированной графикой. Как так?
При этом, если посмотреть на мониторинг nvtop, дискретная графика явно была нагружена.

c03a5f736bef8c39689f750d65a582fd.png

Хорошо, один раз что-то пошло не так, но впереди ещё 4 теста. На всякий случай с помощью инструмента prime-run дополнительно укажем использование непосредственно дискретной графики. 

```bash
prime-run ffmpeg -hwaccel cuda -i input.mp4 -vf "scale=1920:1080,format=yuv420p" -c:v mpeg4 -preset medium cuda.mp4
```

И мы получаем… 4.77?

Номер теста

Скорость

2

4.77х

My honest reaction:

3f7cfd88ee54ae3ac7fce8a8d328d21b.jpg

Так, ну хорошо, допустим, что и prime-run недостаточно, хотя nvtop опять же показал, что GPU был загружен. Перейдём к тяжёлой артиллерии и с помощью EnvyControl заставим систему использовать только дискретную графику. Заодно по новой проведём тест процессора, которому iGPU, надеюсь, уже не сможет помочь сжульничать против старичка Xeon. 

```bash
sudo envycontrol -s nvidia --force-comp --coolbits 24
reboot
```

Чудесно, теперь у нас чёрный экран и на Wayland сессии, и на X11.
То FFmpeg удивляет, то теперь EnvyControl, до этого эксперимента работавший стабильно.

Et tu, Brute?

f00549a37a09e6678f2daea72c47149c.png

Хорошо, идём в TTY на Ctrl+Alt+F3, вводим логин, пароль и возвращаем всё назад через EnvyControl:

```bash
sudo envycontrol --reset
``

И, Слава Богу, Wayland сессия Кедов снова запустилась, ура!
Только вот по тесту я так и не продвинулся.
Попытаюсь снова переключиться только на дискретку, но и через указание экранного менеджера SDDM:  

```bash
sudo envycontrol -s nvidia --dm sddm 
reboot 
```

* Е[ДАННЫЕ УДАЛЕНЫ]ёт. Возможно…

* Е[ДАННЫЕ УДАЛЕНЫ]ёт. Возможно…

Wayland вновь порадовал чёрным экраном при попытке зайти в систему, в X11же зашёл без проблем, да вот только nvtop показывает, что iGPU продолжает работать вместе с дискреткой.
Ладно, EnvyControl отчего-то не радует на текущий момент. Прибегнем к его страшному старшему кузену — OptimusManager.

Работает он только с Иксами, в отличие от EnvyControl, который ранее нормально работал и под Wayland. Пробуем:  

```bash
optimus-manager --switch nvidia
``

И получаем ошибку с указанием читать логи с букетом других ошибок, от чтения которых понятнее не стало.

Ладно, видимо придётся продолжать тест дальше без переключения в режим Nvidia.

```bash
ERROR: the latest GPU setup attempt failed at Xorg pre-start hook.
Log at /var/log/optimus-manager/switch/switch-20240603T140533.log
Cannot execute command because of previous errors.
```

75602f4af9cf37fc64f9f848f85b4ca1.png

Продолжаем экзекуцию FFmpeg и меня в попытках понять, почему всё так, как оно есть, а не так, как казалось должно быть. Так и быть, буду дальше использовать prime-run, хотя он, кажется, ничего и не меняет.

```bash
prime-run ffmpeg -hwaccel cuda -i input.mp4 -vf "scale=1920:1080,format=yuv420p" -c:v mpeg4 -preset medium cuda.mp4
```

Номер теста

Скорость

3

4.78х

4

4.79х

5

4.81х

Итого среднее — 4.78x.

Мда, я откровенно говоря ожидал иных результатов. В моём представлении всё должно было ускориться раза в 2 минимум. Также как это происходит в условном DaVinci Resolve, KdenLive и прочих видеоредакторах, где под капотом в бэкенде, скорее всего, также где-то зарыт FFmpeg.

В итоге же разница всего лишь 15.5% относительно Ryzen 5600H. 
Ну что ж, ладно, вернёмся к удалённому серверу и запустим тест там:  

```bash
ffmpeg -hwaccel cuda -i input.mp4 -vf "scale=1920:1080,format=yuv420p" -c:v mpeg4 -preset medium cuda.mp4
```

Начало уже радует: обошлось без сюрпризов, и появилась хоть какая-то закономерность. Затычка для PCI-слота с 2 гигами VRAM оказалась слабее, как процессоров, так и своего более свежего собрата из RTX-линейки. Правда, разница не такая уж и впечатляющая.
У Quadro P400 — в FP32 производительность 0.64 TFLOPS. А у мобильной RTX 3050TI в зависимости от теплопакета должна быть около 8.7 TFLOPS.

Номер теста

Скорость

1

2.99x

2

2.99x

3

2.99x

4

2.99x

5

2.99x

Стабильно, даже подозрительно стабильно — 2.99x.

И при разнице в 13.59 раз по сухой производительности в TFLOPS в FFmpeg мы наблюдаем разницу всего лишь в 1.59 раз.

Различия в работе FFmpeg на CPU и GPU

От практики перейдём теперь к теории, как это всё по идее должно работать.
Обещания теории, что GPU должны быть заметно быстрее, упоминать как-то странно. На примере двух разных систем убедились, что по крайней мере в их случае — это не так.
Судя по тому, как редко кто-то вспоминает про GPU-ускорение в FFmpeg, и когда до этого всё-таки доходит, то в гугле видны другие, такие же несчастные, у которых GPU оказался едва быстрее, либо вовсе медленнее.

Процесс работы на процессоре

При транскодировании на процессоре FFmpeg выполняет следующие действия:

  1. Разделение контейнера на отдельные потоки (аудио, видео).

  2. Декодирование потоков в их сырые форматы.

  3. Применение фильтров к этим потокам (например, масштабирование до 720p для уменьшения размера файла).

  4. Кодирование потоков в указанные форматы.

  5. Мультиплексирование потоков обратно в один файл.

186687e56485bbe0e95f56b4de766359.jpg

Процесс работы на Видеокарте

96e3703292b0d231cb244f9326a83f3c.png

NVIDIA Video Codec SDK

Когда мы включаем аппаратное ускорение, некоторые из этих шагов могут выполняться на GPU:

  1. Декодирование потоков происходит на NVDEC (Nvidia Video Decoder).

  2. Фильтрация (например, масштабирование) может выполняться на GPU с использованием CUDA.

  3. Кодирование потоков осуществляется на NVENC (Nvidia Video Encoder).

  4. При этом аудиопотоки всё равно обрабатываются на CPU, так как NVENC/NVDEC предназначены только для видео. 

  5. После декодирования сырые видеокадры отправляются в VRAM для ускоренной фильтрации. 

  6. После фильтрации кадры кодируются и возвращаются в основную память системы для мультиплексирования и завершения процесса.

16934394660663ad0e35a98cc1b3e04f.jpg

Закрадывающиеся сомнения

В моих тестах явно есть ошибка…
Может быть со злосчастным видео от Blender Foundation что-то не так — надо бы посмотреть, какой у него вообще кодек.

```bash
ffprobe -v error -select_streams v:0 -show_entries stream=codec_name -of default=noprint_wrappers=1:nokey=1 bbb_sunflower_2160p_30fps_normal.mp4
```

Используется h264. Так, h264, ну, всё верно, норма для mpeg4 контейн… стоп.
Контейнер у нас .mp4, что и расшифровывается как mpeg4, но кодеки то у нас h264/h265/266, либо софтверный libx264 и т.д.
Так, а у меня?  

*приходит осознание что сам себя поймал в ловушку Джокера*

Чёрт, а я по привычке везде проставил mpeg4, привыкнув считать его синонимичным с контейнером для видео .mp4 в целом.

Хотя, mpeg4 в документации Nvidia за 2023 всё равно указан как также аппаратно ускоряемый, в отличие от приведённой мною выше схемы в разделе »Процесс работы на Видеокарте».
Впрочем, на тормознутости обработки старичка mpeg4, вероятно, сказывается, что он, будучи из 1999 года, сам по себе был менее эффективен в сжатии информации. А также, вероятно, обделён вниманием и со стороны самой Nvidia, так как на его замену есть более эффективные программные и аппаратные кодеки, приходящиеся ему потомками.

49aec380b9e3cbdff096502a8fe94f1f.png

Давай по новой

e6ce15cc4d3f87371ee60709eaacdf65.png

Продолжаем сизифов труд и попробуем теперь прогнать с нормальным кодеком (h265) тесты на ноуте и тестовом сервере.
Заодно в процессе чтения документации я понял, что масштабирование разрешения у меня проходило на CPU, а можно было также задействовать CUDA.

```bash
ffmpeg-cuda -hwaccel cuda -hwaccel_output_format cuda -i bbb_sunflower_2160p_30fps_normal.mp4 -vf "scale_cuda=1920:1080" -c:v hevc_nvenc -preset medium output_cuda.mp4
``
  1. RTX 3050Ti:

Номер теста

Скорость

1

6.59x

2

6.59x

3

6.57x

4

6.56x

5

6.56x

— Среднее: 6.576x
-Разница h265 vs mpeg 4: +37.5%
-Разница с Ryzen 5600H: +297% 
— Разница с Quadro P400: +245.7%

  1. Quadro P400:

Номер теста

Скорость

1

2.68x

2

2.68x

3

2.67x

4

2.67x

5

2.67x

— Среднее: 2.676x
-Разница h265 vs mpeg 4: -11.7%
-Разница с Ryzen 5600H: +92.5%
-Разница с RTX 3050Ti: -245.7%

```bash
ffmpeg -i bbb_sunflower_2160p_30fps_normal.mp4 -vf "scale=1920:1080" -c:v libx265 -preset medium output_libx265.mp4
```
  1. Ryzen 5600H:

Номер теста

Скорость

1

1.4x

2

1.37x

3

1.42x

4

1.35x

5

1.41x

— Среднее: 1.39
-Разница h265 vs mpeg 4: -297%
-Разница с Quadro P400: -92.5%
-Разница с RTX 3050Ti: -473%

  1. Xeon 2630:

  • К сожалению, наш билд FFmpeg не имеет поддержки libx265/264, так что Xeon выбывает.

Результаты уже больше похожи на правду. Но и без аномалий не обошлось — Quardo P400 в h265 внезапно показала себя на 11.7% хуже.

Интересно, а как себя относительно ноутбучного железа покажет не престарелое железо разряда мечта сборщика БУ компов «для учёбы», а что-то помощнее из серверного сегмента? Давайте попробуем?!

Рубрика

Рубрика «Эксперименты», зашла слишком далеко. Проснитесь, мр. Фримэн.

Тестовый сервер 2 — AlmaLinux 9.4, ядро  6.6.31–1.el9.x86_64, 7 гигов DDR4 и 2 гига swap.
CPU — EPYC 7551P, 32 ядра с отключенным Hyper-threading, GPU — RTX A2000.

Версия драйвера Nvidia — 555.42.02, 6 гигов VRAM,   FFmpeg — свой билд с оптимизациями под CUDA.

В FP32 у мобильной RTX 3050Ti и RTX A2000 должен быть примерно одинаковый результат. Разве что видеопамяти у A2000 побольше — 6 гигов против 4.

```bash
ffmpeg-cuda -hwaccel cuda -hwaccel_output_format cuda -i bbb_sunflower_2160p_30fps_normal.mp4 -vf "scale_cuda=1920:1080" -c:v hevc_nvenc -preset medium output_cuda.mp4
``

RTX A2000:

Номер теста

Скорость

1

6.24x

2

6.81x

3

6.8x

4

6.79x

5

6.76x

— Среднее:   6.68x
-Разница RTX 3050Ti: +1.58%
— Разница с Ryzen 5600H: +6.43%
-Разница с Xeon 2630:   +89%
-Разница с Quadro P400: +249%
— Разница с EPYC 7551P: +237%

В итоге получаем схожий результат в пределах погрешности. В этот раз Терафлопсы соотнеслись с реальностью, а вот с EPYC есть загвоздка: AMD AMF на сервере нет, а без него Эпики в аппаратное ускорение не умеют и аналогов libx264/265 не имеют.

Ну что ж, тогда попробуем снова старичка mpeg4.

```bash
ffmpeg -i bbb_sunflower_2160p_30fps_normal.mp4 -vf scale=1920:1080 -c:v mpeg4 -preset medium output_no_acc.mp4
```

EPYC 7551P:

Номер теста

Скорость

1

2.89x

2

2.79x

3

2.83x

4

2.77x

5

2.81x

— Среднее:   2.818x
-Разница RTX 3050Ti: -301%
-Разница с Ryzen 5600H: -89.8%
-Разница с Xeon 2630: -62%
-Разница с Quadro P400: +5.3%
-Разница с A2000: +57%

```bash
ffmpeg-cuda -hwaccel cuda -i bbb_sunflower_2160p_30fps_normal.mp4 -vf "scale=1920:1080,format=yuv420p" -c:v mpeg4 -preset medium output_no_cuda_scale.mp4
```

И на GPU —

RTX A2000:  

Номер теста

Скорость

1

1.76x

2

1.81x

3

1.8x

4

1.79x

5

1.77x

— Среднее:   1.786
-Разница RTX 3050Ti: -267%
-Разница с Ryzen 5600H: -231%
-Разница с Xeon 2630:   -97.8%
-Разница с Quadro P400: -67.4%
-Разница с EPYC 7551P: -57%

AMD, конечно, умеет удивлять, но нередко любит и разочаровывать. Иного результата я ожидал от 32 ядер. Видимо Эпики под работу с мультимедиа контентом совсем не заточены. И смею предположить, подозрительно хороший результат Ryzen5600H связан был с использованием iGPU.
Впрочем, и с Nvidia не обошлось без мистики и странностей — с mpeg4 A2000 справилась куда хуже, чем RTX 3050Ti. По производительности они почти идентичны, так что предположу, что разрыв вызван разными версиями ПО на моём ноутбуке и тестовом сервере.

Теперь в h264

Для полноты эксперимента заодно посмотрим, изменится ли что-то, если сохранить кодек как и у оригинального видео — h264?

46b3474a5cddf2540e122698aaa87051.png

```bash
ffmpeg -hwaccel cuda -hwaccel_output_format cuda -i bbb_sunflower_2160p_30fps_normal.mp4 -vf "scale_cuda=1920:1080" -c:v h264_nvenc -preset medium output_cuda.mp4
```
  1. RTX A2000:  

Номер теста

Скорость

1

6.24x

2

6.79x

3

6.81x

4

6.8x

5

6.8x

— Среднее: 6.68x
— Разница c RTX A2000(H265): 0%

  1. Quadro P400:

Номер теста

Скорость

1

2.66x

2

2.66x

3

2.66x

4

2.66x

5

2.66x

— Среднее:   2.66x
— Разница c Quadro P400(H265): -12.4%

  1. RTX 3050Ti:

Номер теста

Скорость

1

6.66x

2

6.66x

3

6.69x

4

6.65x

5

6.66x

— Среднее:   6.66x
— Разница c RTX 3050Ti (H265): +1.27%

```bash
ffmpeg -i bbb_sunflower_2160p_30fps_normal.mp4 -vf "scale=1920:1080" -c:v libx264 -preset medium output_cpu.mp4
```
  1. Ryzen 5600H:

Номер теста

Скорость

1

2.49x

2

2.48x

3

2.47

4

2.46

5

2.48

— Среднее:   2.476x
— Разница c Ryzen5600H (H265): +78.12%

Результаты, с одной стороны, закономерные, а с другой — снова удивляющие. RTX A2000 не показала вообще изменений между H264 и H265, у RTX 3050Ti в пределах погрешности, Quadro P400 внезапно показала себя заметно хуже с H264, а Ryzen 5600H наоборот — намного лучше справился с H264.

Итоговая таблица

Было

Тестовая конфигурация

Изначальный кодек

Конечный кодек

Среднее

Ryzen 5600H

h264

mpeg4

4.138x

RTX 3050Ti

h264

mpeg4

4.78x

Xeon 2630

h264

mpeg4

3.534x

Quadro P400

h264

mpeg4

2.99x

EPYC 7551P

h264

mpeg4

2.818x

RTX A2000

h264

mpeg4

1.786x

Стало (H265)

Тестовая конфигурация

Изначальный кодек

Конечный кодек

Среднее

RTX A2000

h264

h265

6.68x

RTX 3050Ti

h264

h265

6.576x

Quadro P400

h264

h265

2.676x

Ryzen 5600H

h264

h265

1.39x

Стало (H264)

Тестовая конфигурация

Изначальный кодек

Конечный кодек

Среднее

RTX A2000

h264

h264

6.68x

RTX 3050Ti

h264

h264

6.66x

Quadro P400

h264

h264

2.66x

Ryzen 5600H

h264

h264

2.476x

Выводы

Я знаю, что ничего не знаю. © Возможно, Сократ, но и это — не точно.

77d27094050f8a35fc986a9d6594b993.png

Скелетор Я же вернусь в следующей серии, где, возможно, снова затронем FFmpeg с транскодированием. Либо с уже новой, менее болезненной к разбору темой.
Спасибо за прочтение, с нетерпением жду ваших комментариев!

278c7fe10cc79bbac82c78f373e1bd51.png

© Habrahabr.ru