Трансляция GPU внутрь OpenStack

Перевод с собственной редакцией статьи из блога https://www.jimmdenton.com/gpu-offloading-openstack/

Для решения данной задачи необходима поддержка аппаратной виртуализации (Hardware-assisted virtualization) и виртуализации ввода/вывода (IOMMU virtualization).
Поддержка этих технологий должна быть реализована как на уровне аппаратной платформы, так и на уровне программной реализации (гипервизора).
Суть аппаратной виртуализации заключается в поддержке процессором специальных инструкций, позволяющих организовать эмуляцию всех аппаратных устройств для виртуальной машины.
При этом ВМ не зависит от архитектуры аппаратной платформы хоста, но может использовать напрямую его устройства, а ее производительность сравнима с производительностью хоста.
На основе виртуализации ввода/вывода виртуальной машине с помощью «проброса» (pass-through) можно предоставить прямой доступ к устройствам на шине PCI и PCI-E. Так можно организовать прямой доступ к видеокарте и USB-контроллерам и другим устройствам хост-машины.
Чтобы передать GPU на виртуальные машины, вам нужно включить расширения VT-d или AMD-Vi (IOMMU) в BIOS. Для установки видеокарты в сервер может понадобиться переходник (raiser).
Необходимо иметь ввиду, что при использовании гипервизора Xen нельзя «прокинуть» в ВМ видеокарты на чипах Nvidia (кроме Quadro и Grid).
В KVM таких ограничений нет, кроме очень старых моделей видеокарт.

IOMMU

Проверим наличие поддержки IOMMU, выполнив команду:

$ egrep -c "svm|vmx" /proc/cpuinfo
$ dmesg | grep IOMMU
Затем обновите конфигурацию grub, /etc/default/grub чтобы включить поддержку IOMMU.

Intel grub

Для Intel добавьте intel_iommu=on в конец GRUB_CMDLINE_LINUX строки в файле конфигурации grub /etc/default/grub

GRUB_CMDLINE_LINUX="splash=quiet console=tty0 ... intel_iommu=on

AMD grub

GRUB_CMDLINE_LINUX="splash=quiet console=tty0 ... amd_iommu=on
Обновите конфигурацию /etc/default/grub:

# for AMD CPU, set [amd_iommu=on]
# for Intel CPU, set [intel_iommu=on]
GRUB_CMDLINE_LINUX="intel_iommu=on"

Пересобираем ядро:

grub-mkconfig -o /boot/grub/grub.cfg
reboot

Проверим, что IOMMU драйвер активирован в ядре:

dmesg | grep -E "DMAR|IOMMU"

Пример успешного вывода:

[    1.456466] pci 0000:60:00.2: AMD-Vi: IOMMU performance counters supported
[    1.456519] pci 0000:40:00.2: AMD-Vi: IOMMU performance counters supported
[    1.456545] pci 0000:20:00.2: AMD-Vi: IOMMU performance counters supported
[    1.456565] pci 0000:00:00.2: AMD-Vi: IOMMU performance counters supported
[    1.456625] pci 0000:e0:00.2: AMD-Vi: IOMMU performance counters supported
[    1.456661] pci 0000:c0:00.2: AMD-Vi: IOMMU performance counters supported
[    1.456696] pci 0000:a0:00.2: AMD-Vi: IOMMU performance counters supported
[    1.456726] pci 0000:80:00.2: AMD-Vi: IOMMU performance counters supported
[    1.532953] pci 0000:60:00.2: AMD-Vi: Found IOMMU cap 0x40
[    1.532962] pci 0000:40:00.2: AMD-Vi: Found IOMMU cap 0x40
[    1.532968] pci 0000:20:00.2: AMD-Vi: Found IOMMU cap 0x40
[    1.532974] pci 0000:00:00.2: AMD-Vi: Found IOMMU cap 0x40
[    1.532980] pci 0000:e0:00.2: AMD-Vi: Found IOMMU cap 0x40
[    1.532986] pci 0000:c0:00.2: AMD-Vi: Found IOMMU cap 0x40
[    1.532991] pci 0000:a0:00.2: AMD-Vi: Found IOMMU cap 0x40
[    1.532997] pci 0000:80:00.2: AMD-Vi: Found IOMMU cap 0x40
[    1.547155] perf/amd_iommu: Detected AMD IOMMU #0 (2 banks, 4 counters/bank).
[    1.547171] perf/amd_iommu: Detected AMD IOMMU #1 (2 banks, 4 counters/bank).
[    1.547188] perf/amd_iommu: Detected AMD IOMMU #2 (2 banks, 4 counters/bank).
[    1.547204] perf/amd_iommu: Detected AMD IOMMU #3 (2 banks, 4 counters/bank).
[    1.547220] perf/amd_iommu: Detected AMD IOMMU #4 (2 banks, 4 counters/bank).
[    1.547235] perf/amd_iommu: Detected AMD IOMMU #5 (2 banks, 4 counters/bank).
[    1.547251] perf/amd_iommu: Detected AMD IOMMU #6 (2 banks, 4 counters/bank).
[    1.547270] perf/amd_iommu: Detected AMD IOMMU #7 (2 banks, 4 counters/bank).
[    1.670363] AMD-Vi: AMD IOMMUv2 loaded and initialized
Если появится сообщения типа AMD-Vi: AMD IOMMUv2 functionality not available on this system - This is not a bug., то необходимо проверить в BIOS, включены-ли два режима: - IOMMU -> Enabled
- DMAr -> Enabled
Для AMD серверов ищите эти настройки в AMD CBS -> NBIO Common Options

Проверим линк на группу iommu:

readlink /sys/bus/pci/devices/0000:01:00.0/iommu_group
../../../../kernel/iommu_groups/26

Проверим, что драйвер vfio-pci разрешен в системе:

dmesg | grep -i vfio
Если все хорошо, будет примерно такой вывод:
[    3.243739] VFIO - User Level meta-driver version: 0.3
[    3.252081] vfio-pci 0000:01:00.0: vgaarb: changed VGA decodes: olddecodes=io+mem,decodes=io+mem:owns=io+mem
[    3.276061] vfio_pci: add [10de:1c03[ffffffff:ffffffff]] class 0x000000/00000000
[    3.296056] vfio_pci: add [10de:10f1[ffffffff:ffffffff]] class 0x000000/00000000

Манипуляции с драйвером

После перезагрузки хоста следующим шагом в подготовке графического процессора к сквозной передаче является проверка правильности настройки драйверов. Вот несколько параметров, которые потребуются по мере прохождения процесса:

  • Vendor ID
  • Product ID
  • PCI Bus ID

Все эти параметры определяем с помощью lspci:

$ sudo lspci -nn | grep NVIDIA
24:00.0 VGA compatible controller [0300]: NVIDIA Corporation GP106GL [Quadro P2000] [10de:1c30] (rev a1)
24:00.1 Audio device [0403]: NVIDIA Corporation GP106 High Definition Audio Controller [10de:10f1] (rev a1)

В этом примере PCI Bus ID is 24:00.0, Vendor ID is 10de и Product ID is 1c30.

Из листинга видно, что ядро обнаружило GPU и загрузило графический драйвер nouveau (в случае карт NVIDIA).:

$ sudo lspci -s 24:00.0 -k
24:00.0 VGA compatible controller: NVIDIA Corporation GP106GL [Quadro P2000] (rev a1)
    Subsystem: Dell GP106GL [Quadro P2000]
    Kernel driver in use: nouveau
    Kernel modules: nvidiafb, nouveau

Важный момент заключается в том, что пока видеокарта привязана к графическому драйверу на хосте, транзит GPU внутрь ВМ невозможен.

Blacklist

Чтобы предотвратить загрузку драйверов при старте системы, нужно внести их в черный список. Для этого добавляем в файл /etc/modprobe.d/blacklist-nvidia.conf две строки с драйверами.

blacklist nouveau
blacklist nvidiafb

Whitelist

Теперь нам надо внести в белый список драйвер vfio-pci , который будет осуществлять привязку видеокарты внутрь гипервизора.

Определим номер шины PCI и идентификаторы прпоизводителя и устройства Vendor-ID:Device-ID графического адаптера, например:

- PCI identification ⇒ [01:00.*]  
- [Vendor-ID : Device-ID] ⇒ [10de:***]  

lspci -nn | grep -i nvidia
01:00.0 VGA compatible controller [0300]: NVIDIA Corporation GP106 [GeForce GTX 1060 6GB] [10de:1c03] (rev a1)
01:00.1 Audio device [0403]: NVIDIA Corporation GP106 High Definition Audio Controller [10de:10f1] (rev a1)
Создадим файл /etc/modprobe.d/vfio.conf со следующим содержимым:

# create new : for [ids=***], specify [Vendor-ID : Device-ID]
options vfio-pci ids=10de:1c03,10de:10f1

Напомню, что 10de - это Vendor ID, а 1c30 - Product ID видеокарты, а 10f1 - Product ID аудиокарты графического процессора.
Теперь добавим vfio-pci драйвер в файл /etc/modules-load.d/modules.conf для автоматической загрузки при старте системы.

echo 'vfio-pci' > /etc/modules-load.d/vfio-pci.conf
reboot

После перезагрузки хоста, проверим, что ядро ОС теперь использует именно драйвер vfio-pci

$ sudo lspci -s 24:00.0 -nnk
24:00.0 VGA compatible controller [0300]: NVIDIA Corporation GP106GL [Quadro P2000] [10de:1c30] (rev a1)
    Subsystem: Dell GP106GL [Quadro P2000] [1028:11b3]
    Kernel driver in use: vfio-pci
    Kernel modules: nvidiafb, nouveau

Подстройка Nova

Службу Nova Compute необходимо настроить в двух местах, чтобы распознать и использовать GPU.

Во-первых, настройте белый список транзитной передачи PCI на вычислительном узле, где находится графический процессор.
Обновите раздел [pci] /etc/nova/nova.conf файла следующим образом:

[pci]
passthrough_whitelist = { "vendor_id": "10de", "product_id": "1c30" }

Убедитесь, что vendor_id и product_id совпадают с полученными ранее. Белый список может быть ограничен ссылкой на конкретный слот PCI, если это необходимо, но, поскольку в нашем примере установлена только одна видеокарта, в этом нет необходимости.
Перезапустите nova-compute:

$ sudo systemctl restart nova-compute

Затем настройте псевдоним PCI (PF) в файле /etc/nova/nova.conf на узле, в котором размещается служба Nova API - обычно это контроллер.

Для Nvidia:

[pci]
alias = { "vendor_id":"10de", "product_id":"1c30", "device_type":"type-PCI", "name":"quadro-p2000" }
Для Nvidia Tesla:

[pci]
alias = { "vendor_id":"10de", "product_id":"1c30", "device_type":"type-PF", "name":"tesla-t4" }

Перезагружаем Nova API:

$ sudo systemctl restart nova-api

Теперь, необходимо указать планировщику Nova, чтобы он применял фильтр PCI Passthrough. Для этого надо проверить, есть ли фильтр PciPassthroughFilter в параметре enabled_filters и/или available_filters в файле /etc/nova/nova.conf. Если такого фильтра нет, то его необходимо добавить по следущему примеру:

[filter_scheduler]
enabled_filters = RetryFilter, AvailabilityZoneFilter, ComputeFilter, ComputeCapabilitiesFilter, ImagePropertiesFilter, ServerGroupAntiAffinityFilter, ServerGroupAffinityFilter, PciPassthroughFilter
available_filters = nova.scheduler.filters.all_filters
После этого, не забываем перестартовать службу nova scheduler.

$ sudo systemctl restart nova-scheduler

Кастомизация flavor

Nova использует метаданные и свойства образа, чтобы определить, какие ресурсы должны быть связаны с экземпляром. В случае сквозной передачи GPU необходимо настроить вариант со свойством pci_passthrough:alias.

Создаем новый вариант флавора с 6 виртуальными ядрами, 8 ГБ ОЗУ, диском на 40 ГБ и свойствомpci_passthrough:alias, которое использует псевдоним нашей видеокарты quadro-p2000.

openstack flavor create \
--vcpus 6 \
--ram 8192 \
--disk 40 \
--property "pci_passthrough:alias"="quadro-p2000:1" \
6-8-40-gpu

По спецификации nova, флавору может быть сопоставлен только один графический процессор.

Установка Nvidia driver в имидже ОС

Описание установки CUDA на сайте производителя

Ставим на Ubuntu 20

wget https://developer.download.nvidia.com/compute/cuda/repos/ubuntu2004/x86_64/cuda-ubuntu2004.pin
sudo mv cuda-ubuntu2004.pin /etc/apt/preferences.d/cuda-repository-pin-600
sudo apt-key adv --fetch-keys https://developer.download.nvidia.com/compute/cuda/repos/ubuntu2004/x86_64/7fa2af80.pub
sudo add-apt-repository "deb https://developer.download.nvidia.com/compute/cuda/repos/ubuntu2004/x86_64/ /"
sudo apt-get update
sudo apt-get -y install cuda

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

apt clean; apt update; apt purge cuda; apt purge nvidia-*; apt autoremove; apt install cuda

Создание инстанса

Чтобы протестировать новые возможности сквозной передачи графического процессора, разверните экземпляр виртуальной машины:

openstack server create \
--flavor 6-8-40-gpu \
--image ubuntu-bionic \
--network LAN \
--key-name imac-rsa \
--security-group plex \
vm-with-gpu

После создания ВМ, увидим ее в списке.

# openstack server list
+--------------------------------------+-------------------+---------+-------------------+---------------+---------------+
| ID                                   | Name              | Status  | Networks          | Image         | Flavor        |
+--------------------------------------+-------------------+---------+-------------------+---------------+---------------+
| 854ba22b-52c0-44a1-ab66-b78eba142bc5 | vm-with-gpu   | ACTIVE  | LAN=192.168.2.111 | ubuntu-bionic | 6-8-40-gpu    |
+--------------------------------------+-------------------+---------+-------------------+---------------+---------------+

Войдите в систему и убедитесь, что GPU присутствует:

ubuntu@vm-with-gpu:~$ lspci | grep NVIDIA
00:06.0 VGA compatible controller: NVIDIA Corporation GP106GL [Quadro P2000] (rev a1)

Для лучшей производительности обновленные драйверы можно установить из исходного репозитория Ubuntu или с веб-сайта NVIDIA. В данном случае установим драйверы из репозитория:

$ sudo apt update
$ sudo apt install ubuntu-drivers-common

$ ubuntu-drivers devices
== /sys/devices/pci0000:00/0000:00:06.0 ==
modalias : pci:v000010DEd00001C30sv00001028sd000011B3bc03sc00i00
vendor   : NVIDIA Corporation
model    : GP106GL [Quadro P2000]
driver   : nvidia-driver-390 - distro non-free recommended
driver   : xserver-xorg-video-nouveau - distro free builtin

Утилита рекомендует nvidia-driver-390 драйвер, но могут быть доступны более новые драйверы.

$ sudo apt install nvidia-driver-390
$ sudo reboot

После перезагрузки экземпляра убедитесь, что новые драйверы были установлены с помощью lspci и nvidia-smi:

ubuntu@vm-with-gpu:~$ sudo lspci -nnk -s 00:06.0
00:06.0 VGA compatible controller [0300]: NVIDIA Corporation GP106GL [Quadro P2000] [10de:1c30] (rev a1)
    Subsystem: Dell GP106GL [Quadro P2000] [1028:11b3]
    Kernel driver in use: nvidia <---
    Kernel modules: nvidiafb, nvidia_drm, nvidia

ubuntu@vm-with-gpu:~$ nvidia-smi
Wed May  8 18:08:14 2019
+-----------------------------------------------------------------------------+
| NVIDIA-SMI 390.116                Driver Version: 390.116                   |
|-------------------------------+----------------------+----------------------+
| GPU  Name        Persistence-M| Bus-Id        Disp.A | Volatile Uncorr. ECC |
| Fan  Temp  Perf  Pwr:Usage/Cap|         Memory-Usage | GPU-Util  Compute M. |
|===============================+======================+======================|
|   0  Quadro P2000        Off  | 00000000:00:06.0 Off |                  N/A |
|  0%   38C    P0    19W /  75W |      0MiB /  5059MiB |      0%      Default |
+-------------------------------+----------------------+----------------------+

+-----------------------------------------------------------------------------+
| Processes:                                                       GPU Memory |
|  GPU       PID   Type   Process name                             Usage      |
|=============================================================================|
|  No running processes found                                                 |
+-----------------------------------------------------------------------------+

Все хорошо, теперь приступаем к тестированию.

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

Лучший способ сравнить результаты - запустить транскодер. Запускаем AppleTV 4 (HD), чтобы заставить Plex перекодировать фильм 4K в 1080p. Для этого вам понадобится Plex Pass для включения аппаратного ускорения, но могут быть и альтернативные методы проверки.

Посмотрите на статистику ЦП перед воспроизведением фильма:

transcode cpu noop

Как только фильм начал воспроизводиться, загрузка процессора увеличилась более чем на 85%:

transcode cpu software

Взгляд на панель инструментов Plex отражает программную транскодировку (обратите внимание на отсутствие «hw»):

dashboard software

Мы видим, что Quadro P2000 просто стоит и не используется:

Wed May  8 19:04:40 2019
+-----------------------------------------------------------------------------+
| NVIDIA-SMI 390.116                Driver Version: 390.116                   |
|-------------------------------+----------------------+----------------------+
| GPU  Name        Persistence-M| Bus-Id        Disp.A | Volatile Uncorr. ECC |
| Fan  Temp  Perf  Pwr:Usage/Cap|         Memory-Usage | GPU-Util  Compute M. |
|===============================+======================+======================|
|   0  Quadro P2000        Off  | 00000000:00:06.0 Off |                  N/A |
|  0%   38C    P0    19W /  75W |      0MiB /  5059MiB |      0%      Default |
+-------------------------------+----------------------+----------------------+

+-----------------------------------------------------------------------------+
| Processes:                                                       GPU Memory |
|  GPU       PID   Type   Process name                             Usage      |
|=============================================================================|
|  No running processes found                                                 |
+-----------------------------------------------------------------------------+

Включение аппаратного ускорения в настройках сервера Plex и перезапуск фильма отразили значительное снижение загрузки ЦП:

transcode cpu hardware

Падение с 80% до ~50%? Неплохо, но не очень впечатляет. Панель инструментов Plex показывает, что аппаратное перекодирование включено:

dashboard hardware

Мы видим, что Quadro P2000 работает, но не в полную мощь:

Wed May  8 19:11:03 2019
+-----------------------------------------------------------------------------+
| NVIDIA-SMI 390.116                Driver Version: 390.116                   |
|-------------------------------+----------------------+----------------------+
| GPU  Name        Persistence-M| Bus-Id        Disp.A | Volatile Uncorr. ECC |
| Fan  Temp  Perf  Pwr:Usage/Cap|         Memory-Usage | GPU-Util  Compute M. |
|===============================+======================+======================|
|   0  Quadro P2000        Off  | 00000000:00:06.0 Off |                  N/A |
| 49%   42C    P0    19W /  75W |    161MiB /  5059MiB |      1%      Default |
+-------------------------------+----------------------+----------------------+

+-----------------------------------------------------------------------------+
| Processes:                                                       GPU Memory |
|  GPU       PID   Type   Process name                             Usage      |
|=============================================================================|
|    0     26847      C   /usr/lib/plexmediaserver/Plex Transcoder     151MiB |
+-----------------------------------------------------------------------------+

Оказывается, транскодирование с полной разгрузкой еще не поддерживается в Plex Media Server для Linux. Дальнейшие исследования показалт, что можно значительно больше нагрузить видеокарту, если использовать некоторые флаги FFmpeg.

Чтобы это задействовать, можно использовать скрипт.

Включаем NVDEC

Чтобы улучшить “занятость” видеопроцессора, добавим флаг для включения механизма NVDEC.

#!/bin/bash
marap=$(cut -c 10-14 <<<"$@")
if [ $marap == "mpeg4" ]; then
     exec /usr/lib/plexmediaserver/PlexTranscoder "$@"
else
     exec /usr/lib/plexmediaserver/PlexTranscoder -hwaccel nvdec "$@"
fi

Перезапустив процесс, мы видим, что загрузка ЦП падает ниже 20%:

transcode cpu wrapper

nvidia-smiпоказывает, что GPU почти не потеет, но работает превосходно:

Wed May  8 19:25:14 2019
+-----------------------------------------------------------------------------+
| NVIDIA-SMI 390.116                Driver Version: 390.116                   |
|-------------------------------+----------------------+----------------------+
| GPU  Name        Persistence-M| Bus-Id        Disp.A | Volatile Uncorr. ECC |
| Fan  Temp  Perf  Pwr:Usage/Cap|         Memory-Usage | GPU-Util  Compute M. |
|===============================+======================+======================|
|   0  Quadro P2000        Off  | 00000000:00:06.0 Off |                  N/A |
| 49%   41C    P0    21W /  75W |   1034MiB /  5059MiB |     11%      Default |
+-------------------------------+----------------------+----------------------+

+-----------------------------------------------------------------------------+
| Processes:                                                       GPU Memory |
|  GPU       PID   Type   Process name                             Usage      |
|=============================================================================|
|    0     27118      C   ...ib/plexmediaserver/Plex Transcoder Orig  1024MiB |
+-----------------------------------------------------------------------------+

20.02.2022 translated by aizaro@mail.ru