Сервер с одной дорогой видеокартой быстро превращается в узкое место: обучение моделей, рендер и тесты выстраиваются в очередь, хотя половина вычислительных блоков простаивает. Современные ускорители умеют делиться сами, без покупки второго устройства. У NVIDIA для этого есть режим MIG, разрезающий чип на изолированные экземпляры с собственной памятью и своими вычислительными слайсами. У Intel и AMD работает классическая схема SR-IOV, когда на шине PCIe появляются отдельные виртуальные функции, каждая со своим адресом и блоками регистров. Ниже разбирается настройка обоих механизмов на Linux: включение режима MIG и создание экземпляров, подъем виртуальных функций через sysfs, привязка их к драйверу vfio-pci и проброс в гостевые системы, а также расчет ресурсов и закрепление настроек после перезагрузки.
Два механизма разделения ускорителя и границы их применения
Путей поделить ускоритель два, похожих снаружи и разных внутри. Первый механизм, SR-IOV, работает на уровне шины PCIe. Физическая функция, она же PF, остается главной, а по команде администратора рядом с ней появляются виртуальные функции, VF, каждая со своим адресом на шине, собственными блоками регистров и очередями. Ядро видит их как отдельные устройства, поэтому любую VF можно отвязать от хостового драйвера и отдать виртуальной машине. Второй механизм, MIG, придуман NVIDIA для ускорителей A100, H100, H200 и A30 и режет сам чип, а не устройство на шине. У A100 вычислительные блоки делятся на семь слайсов по 14 SM из 108, и каждый экземпляр получает свою долю видеопамяти, своего L2 кеша и свои копирующие движки. Изоляция тут железная: задача из одного экземпляра не может занять память или блоки другого, всплеск нагрузки по соседству не отнимает производительность.
Различие в области применения простое: экземпляры MIG обслуживают локальные процессы и контейнеры того же хоста, а виртуальные функции SR-IOV уходят в гостевые системы. NVIDIA собственную схему vGPU для виртуальных машин лицензирует отдельно и SR-IOV на датацентровых картах не применяет, зато ускорители Intel серии Data Center GPU Flex и карты AMD с прошивкой MXGPU отдают VF напрямую. Классический MIG обслуживал только вычислительные нагрузки CUDA, поддержку графики в экземплярах добавили на свежих картах серий RTX PRO Blackwell профилями с суффиксом +gfx, а прямая передача данных между экземплярами по P2P заработала в драйверах ветки R570.
Включение режима MIG и подготовка драйвера NVIDIA
Переключение в режим MIG выполняется одной командой, но требует пустой карты: активные вычисления и удерживаемые контексты мешают, и драйвер ответит "GPU is currently in use". Сначала смотрят текущее состояние:
nvidia-smi -q -i 0 | grep -i -A1 "MIG Mode"
Вывод покажет строку "MIG Mode: Disabled" или "Enabled". Включение и сброс карты выглядят так:
sudo nvidia-smi -i 0 -mig 1
sudo nvidia-smi -i 0 -r
Первая команда включает режим для устройства 0, работает на драйверах от ветки R450 и сохраняет настройку между перезагрузками. Список поддерживаемых моделей короткий: A100, H100, H200, A30 и отдельные серверные карты серий RTX PRO, на GeForce режима MIG нет. Вторая команда выполняет сброс GPU, без него новые устройства в системе не появятся; вместо сброса карты можно перезагрузить сервер целиком. После сброса вывод nvidia-smi -L пока показывает только цельную карту:
GPU 0: NVIDIA A100-SXM4-40GB (UUID: GPU-5a1f3c2e-9d4b-4e2a-8f01-77b0c3a5d9e2)
Отдельных экземпляров в списке еще нет, их создают явно, и это следующий шаг.
Профили экземпляров GPU, их создание и удаление
Название профиля расшифровывается само: MIG 1g.5gb означает один GPU-слайс и 5 ГБ памяти. Для A100 на 40 ГБ доступна линейка от крошечного 1g.5gb до цельной карты 7g.40gb, между ними ступени 1g.10gb, 2g.10gb, 3g.20gb и 4g.20gb. У 1g.5gb доля памяти 1/8, доля SM 1/7, и таких экземпляров помещается до семи; у 2g.10gb доли вдвое больше, а мест три; 3g.20gb занимает половину памяти и три слайса, их влезает два. Список профилей с точными размерами и доступным числом экземпляров печатает команда:
nvidia-smi -i 0 mig -lgip
Создание идет в два шага: сначала по профилю поднимают GPU-экземпляры, затем в каждом выделяют compute-экземпляр:
sudo nvidia-smi -i 0 -cgi 14,14,14,14,14,14,14
sudo nvidia-smi -i 0 -CIG 0,1,2,3,4,5,6
Число 14 в примере это ID профиля 1g.5gb из вывода -lgip на конкретной сборке драйвера, идентификатор берут оттуда, он зависит от модели и прошивки. Вторая команда создает compute-экземпляры в слайсах с номерами от 0 до 6. Удаление выполняется в обратном порядке, иначе драйвер откажет:
sudo nvidia-smi -i 0 -dci
sudo nvidia-smi -i 0 -dgi
Сначала уходят compute-экземпляры, потом GPU-экземпляры, обратный порядок вернет ошибку. Комбинации профилей подчиняются правилам размещения: не выйти за 7 слайсов и за память карты. На A100 целиком заполняют ее варианты 4g.20gb плюс 3g.20gb или 3g.20gb плюс 2g.10gb плюс два экземпляра 1g.5gb, в обоих случаях заняты все 40 ГБ и все семь слайсов. Мелкий профиль пристраивается к крупному, а крупный поверх занятых мелкими слайсов разместить нельзя.
Выдача экземпляров MIG приложениям и контейнерам
Приложение получает экземпляр через UUID из вывода nvidia-smi -L, подставленный в CUDA_VISIBLE_DEVICES:
CUDA_VISIBLE_DEVICES=MIG-9c2e51a7 python3 train.py
Принимается и индексная форма MIG-GPU-5a1f3c2e/0/0, где пара чисел перечисляет GPU-экземпляр и compute-экземпляр внутри него. Тот же UUID принимает Docker в параметре --gpus:
docker run --rm --gpus '"device=MIG-9c2e51a7"' \
nvidia/cuda:12.4.1-base-ubuntu22.04 nvidia-smi -L
Внутри контейнера nvidia-smi покажет только один экземпляр, соседние для процесса недоступны, даже если код попытается перебрать все устройства. В Kubernetes за выдачу MIG отвечает device plugin со стратегией mixed: под запрашивает ресурс вида nvidia.com/mig-1g.5gb, а плагин подбирает свободный экземпляр сам. Память экземпляра жесткая: попытка выделить 6 ГБ внутри 1g.5gb завершится ошибкой выделения, драйвер не даст занять чужую долю, и перегруженный сосед помочь не сможет.
Виртуальные функции SR-IOV через файлы sysfs
Схема SR-IOV начинается с проверки устройства. Для ускорителя Intel на адресе 0000:01:00.0 путь выглядит так:
lspci -nn | grep -i "3D controller"
01:00.0 3D controller [0302]: Intel Corporation Data Center GPU Flex 170 [8086:56c0]
cat /sys/bus/pci/devices/0000:01:00.0/sriov_totalvfs
7
Файл sriov_totalvfs показывает максимум виртуальных функций, заложенный в возможности SR-IOV устройства; драйвер вправе его понизить. Число включенных функций управляется записью в соседний файл sriov_numvfs. Перед пробросом в виртуальные машины стоит запретить ядру автоматически привязывать VF к хостовому драйверу, для этого ноль пишут в sriov_drivers_autoprobe до включения функций:
echo 0 > /sys/bus/pci/devices/0000:01:00.0/sriov_drivers_autoprobe
echo 4 > /sys/bus/pci/devices/0000:01:00.0/sriov_numvfs
lspci | grep -i "Virtual Function"
Четыре записи создают четыре функции на адресах 0000:01:00.4 и далее. Расширенная диагностика показывает заявленные в прошивке пределы и текущее состояние:
lspci -vvs 0000:01:00.0 | grep -A3 "Single Root I/O Virtualization"
Initial VFs: 8, Total VFs: 8, Number of VFs: 4, Function Dependency Link: 00
Поля Initial VFs и Total VFs задают потолок для sriov_totalvfs, поле Number of VFs меняется после записи в sriov_numvfs. Правила у записи строгие: значение не превышает sriov_totalvfs, а чтобы поменять количество включенных VF, сначала пишут ноль, иначе ядро вернет ошибку EINVAL. Ноль же отключает все функции разом. Связи удобно проверять по ссылкам sysfs: у PF появляются узлы virtfn0, virtfn1 и далее, а у каждой VF есть обратная ссылка physfn на родителя. Если файлов sriov_totalvfs нет вовсе, устройство не поддерживает SR-IOV или драйвер ограничил функциональность; тогда обновляют прошивку и драйвер, а возможности карты смотрят через lspci -vv.
Привязка функций к vfio-pci и проброс в виртуальные машины
Проброс VF в гостевую систему требует работающего IOMMU. Проверка начинается с журнала ядра и каталога групп:
dmesg | grep -i -e DMAR -e IOMMU
ls /sys/kernel/iommu_groups/
На Intel в параметры ядра добавляют intel_iommu=on iommu=pt, на AMD достаточно включить IOMMU в прошивке платы. Строку вписывают в GRUB_CMDLINE_LINUX_DEFAULT файла /etc/default/grub и выполняют update-grub. Функции отделяют от хоста драйвером vfio-pci, закрепив идентификаторы устройств в конфигурации модуля:
options vfio-pci ids=8086:56c0,8086:56c1
softdep i915 pre: vfio-pci
Идентификаторы берут из вывода lspci -nn: у виртуальных функций ID часто отличается от PF на единицу, поэтому в списке указаны оба. После dracut -f или update-initramfs -u модуль vfio-pci захватит устройства раньше основного драйвера. Если устройство уже занято другим драйвером, его переводят вручную:
echo vfio-pci > /sys/bus/pci/devices/0000:01:00.4/driver_override
echo 0000:01:00.4 > /sys/bus/pci/drivers/vfio-pci/bind
Гостевую машину запускают с одним устройством на слот:
qemu-system-x86_64 -enable-kvm -m 16384 -smp 8 \
-device vfio-pci,host=0000:01:00.4 \
-drive file=vm1.qcow2,if=virtio,format=qcow2
В libvirt тот же проброс описывают блоком hostdev:
<hostdev mode="subsystem" type="pci" managed="yes">
<source>
<address domain="0x0000" bus="0x01" slot="0x00" function="0x4"/>
</source>
</hostdev>
Каждая машина получает свою VF, и гостевая система видит ее через lspci как самостоятельный ускоритель. Работоспособность проброса проверяют внутри гостя: в строке Kernel driver in use у функции должен появиться драйвер гостевого стека, а не vfio-pci, который остается только на хосте. Правило простое: одна виртуальная функция уходит в одну машину.
Расчет ресурсов, автозапуск настроек и типичные ошибки
Числа помогают решить, что выгоднее под задачу. На A100 в 40 ГБ семь экземпляров 1g.5gb занимают 7 x 5 = 35 ГБ и все семь слайсов по 14 SM, а оставшиеся 5 ГБ резервирует аппаратура под служебные структуры. Схема 4g.20gb плюс 3g.20gb отдает всю память 20 + 20 = 40 ГБ и все 7 слайсов, но делит карту лишь на две части. Всего на карте помещается не больше семи экземпляров независимо от профиля: вариант из трех карт 2g.10gb занимает 30 ГБ и шесть слайсов, седьмой остается незадействованным. На H100 с 80 ГБ семь экземпляров 1g.10gb дают 70 ГБ, на H200 с 141 ГБ профиль 1g.18gb отводит по 18 ГБ и семь экземпляров дают 126 ГБ, каждый получает по 1/8 L2 кеша. Для SR-IOV считают не память, а очереди: атрибут sriov_vf_total_msix у PF показывает общий пул MSI-X векторов, а sriov_vf_msix_count у каждой VF назначает долю. Пул в 512 векторов на восемь функций означает по 64 вектора при равном делении; функции под нагруженную сеть дают больше, экономя на остальных.
Запись в sriov_numvfs живет до перезагрузки, поэтому ее выносят в разовую службу systemd:
[Unit]
Description=Enable SR-IOV virtual functions on GPU
[Service]
Type=oneshot
ExecStart=/bin/sh -c "echo 4 > /sys/bus/pci/devices/0000:01:00.0/sriov_numvfs"
[Install]
WantedBy=multi-user.target
Файл кладут в /etc/systemd/system/sriov-gpu.service и включают командой systemctl enable sriov-gpu.service. Режим MIG сам переживает перезагрузку, а экземпляры нет: их пересоздают скриптом с -cgi и -CIG или инструментом nvidia-mig-parted, который применяет готовую схему из YAML файла. Порядок внедрения на рабочем сервере удобно зафиксировать заранее:
- посчитать профили и убедиться, что сумма слайсов и памяти не превышает карту;
- включить MIG или VF на стенде и прогнать эталонную задачу в каждом экземпляре;
- закрепить проброс в конфигурации виртуальных машин, по одной функции на машину;
- вынести создание экземпляров и запись в sriov_numvfs в службы автозапуска;
- задокументировать команды удаления, чтобы при сбое вернуть карту в цельное состояние.
Типичные ошибки сводятся к трем сценариям: карта занята в момент включения MIG, значение в sriov_numvfs превышает лимит или меняется без предварительного нуля, драйвер vfio-pci не успел захватить устройство до хостового. Каждый случай ловится по сообщению драйвера и исправляется последовательностью из двух команд, сброса и повторной записи. Разделение одной видеокарты приучает и к аккуратности в квотах: пустой слайс проще отдать под короткую задачу, чем объяснять простой дорогого железа, а жесткие границы памяти снимают классические споры о том, чей процесс занял всю карту.