Виртуальная машина QEMU очень медленная, если на хосте запущены ресурсоемкие процессы CUDA.

Вопрос или проблема

(Я изначально задал этот вопрос на SuperUser, но думаю, что он там не к месту, поэтому переспрашиваю его здесь с дополнительной информацией; в любом случае извините за кросс-постинг)

Кратко: мой хост используется как для долгосрочных (дни и недели) задач машинного обучения (с использованием CUDA), так и для запуска короткоживущих (минуты) виртуальных машин. Если задачи ML нагружают хост, виртуальные машины становятся невыносимо медленными. Как я могу приоритизировать виртуальные машины, чтобы они оставались отзывчивыми?

Долгая история

У меня есть хост Debian/bookworm, который используется для машинного обучения:

  • Материнская плата: ASUS Pro WS WRX80E-SAGE SE WIFI
  • ЦПУ: AMD Ryzen Threadripper PRO 5965WX 24-Cores
  • ОЗУ: 128GB
  • ГПУ: 3* nVidia GeForce RTX 4090

Пока система в основном занята машинным обучением, я подумал об также использовании ее для редких CI-сборок (учитывая, что это довольно мощная машина и обычно в день не более нескольких CI-задач).

Задачи CI должны быть изолированы от остальной системы с помощью виртуализации (libvirt/qemu/kvm). Временные виртуальные машины используются для компиляции C/C++, так что они не имеют дела с CUDA и подобными вещами (особенно графические карты намеренно не передаются в виртуальную машину)

Образы дисков виртуальных машин (используя формат qcow2) клонируются из “золотого мастера”, который находится на NVMe диске (который также используется задачами CUDA), с использованием функции основного образа qcow2, с фактическими данными COW, хранящимися на RAM-диске (/dev/shm). фактически процесс клонирования (очень быстрый) делает следующее:

qemu-img create -f qcow2 -b /media/VMs/base.qcow2 -F qcow2 /dev/shm/clone.qcow2

После этого виртуальная машина создается через libvirt и запускается. Фактический вызов qemu выглядит следующим образом:

/usr/bin/qemu-system-x86_64 \
    -name guest=test1736346033,debug-threads=on \
    -S \
    -object {"qom-type":"secret","id":"masterKey0","format":"raw","file":"/var/lib/libvirt/qemu/domain-9-test173634603/master-key.aes"} \
    -machine pc-q35-6.0,usb=off,vmport=off,dump-guest-core=off,memory-backend=pc.ram \
    -accel kvm \
    -cpu host,migratable=on,hv-time=on,hv-relaxed=on,hv-vapic=on,hv-spinlocks=0x1fff \
    -m 8192 \
    -object {"qom-type":"memory-backend-ram","id":"pc.ram","size":8589934592} \
    -overcommit mem-lock=off \
    -smp 4,sockets=2,dies=1,cores=2,threads=1 \
    -uuid 26957946-1da2-4b84-bee3-c53a14168e2a \
    -no-user-config \
    -nodefaults \
    -chardev socket,id=charmonitor,fd=35,server=on,wait=off \
    -mon chardev=charmonitor,id=monitor,mode=control \
    -rtc base=localtime,driftfix=slew \
    -global kvm-pit.lost_tick_policy=delay \
    -no-hpet \
    -no-shutdown \
    -global ICH9-LPC.disable_s3=1 \
    -global ICH9-LPC.disable_s4=1 \
    -boot strict=on \
    -device {"driver":"pcie-root-port","port":16,"chassis":1,"id":"pci.1","bus":"pcie.0","multifunction":true,"addr":"0x2"} \
    -device {"driver":"pcie-root-port","port":17,"chassis":2,"id":"pci.2","bus":"pcie.0","addr":"0x2.0x1"} \
    -device {"driver":"pcie-root-port","port":18,"chassis":3,"id":"pci.3","bus":"pcie.0","addr":"0x2.0x2"} \
    -device {"driver":"pcie-root-port","port":19,"chassis":4,"id":"pci.4","bus":"pcie.0","addr":"0x2.0x3"} \
    -device {"driver":"pcie-root-port","port":20,"chassis":5,"id":"pci.5","bus":"pcie.0","addr":"0x2.0x4"} \
    -device {"driver":"pcie-root-port","port":21,"chassis":6,"id":"pci.6","bus":"pcie.0","addr":"0x2.0x5"} \
    -device {"driver":"pcie-root-port","port":22,"chassis":7,"id":"pci.7","bus":"pcie.0","addr":"0x2.0x6"} \
    -device {"driver":"pcie-root-port","port":23,"chassis":8,"id":"pci.8","bus":"pcie.0","addr":"0x2.0x7"} \
    -device {"driver":"pcie-root-port","port":24,"chassis":9,"id":"pci.9","bus":"pcie.0","multifunction":true,"addr":"0x3"} \
    -device {"driver":"pcie-root-port","port":25,"chassis":10,"id":"pci.10","bus":"pcie.0","addr":"0x3.0x1"} \
    -device {"driver":"pcie-root-port","port":26,"chassis":11,"id":"pci.11","bus":"pcie.0","addr":"0x3.0x2"} \
    -device {"driver":"pcie-root-port","port":27,"chassis":12,"id":"pci.12","bus":"pcie.0","addr":"0x3.0x3"} \
    -device {"driver":"pcie-root-port","port":28,"chassis":13,"id":"pci.13","bus":"pcie.0","addr":"0x3.0x4"} \
    -device {"driver":"pcie-root-port","port":29,"chassis":14,"id":"pci.14","bus":"pcie.0","addr":"0x3.0x5"} \
    -device {"driver":"pcie-root-port","port":30,"chassis":15,"id":"pci.15","bus":"pcie.0","addr":"0x3.0x6"} \
    -device {"driver":"pcie-pci-bridge","id":"pci.16","bus":"pci.6","addr":"0x0"} \
    -device {"driver":"qemu-xhci","p2":15,"p3":15,"id":"usb","bus":"pci.2","addr":"0x0"} \
    -device {"driver":"virtio-serial-pci","id":"virtio-serial0","bus":"pci.3","addr":"0x0"} \
    -blockdev {"driver":"file","filename":"/media/VMs/base.qcow2","node-name":"libvirt-2-storage","cache":{"direct":false,"no-flush":true},"auto-read-only":true,"discard":"unmap"} \
    -blockdev {"node-name":"libvirt-2-format","read-only":true,"cache":{"direct":false,"no-flush":true},"driver":"qcow2","file":"libvirt-2-storage","backing":null} \
    -blockdev {"driver":"file","filename":"/dev/shm/clone.qcow2","node-name":"libvirt-1-storage","cache":{"direct":false,"no-flush":true},"auto-read-only":true,"discard":"unmap"} \
    -blockdev {"node-name":"libvirt-1-format","read-only":false,"cache":{"direct":false,"no-flush":true},"driver":"qcow2","file":"libvirt-1-storage","backing":"libvirt-2-format"} \
    -device {"driver":"virtio-blk-pci","bus":"pci.5","addr":"0x0","drive":"libvirt-1-format","id":"virtio-disk0","bootindex":1,"write-cache":"on"} \
    -netdev {"type":"tap","fd":"36","id":"hostnet0"} \
    -device {"driver":"e1000e","netdev":"hostnet0","id":"net0","mac":"52:54:00:c5:99:0e","bus":"pci.1","addr":"0x0"} \
    -chardev pty,id=charserial0 \
    -device {"driver":"isa-serial","chardev":"charserial0","id":"serial0","index":0} \
    -chardev spicevmc,id=charchannel0,name=vdagent \
    -device {"driver":"virtserialport","bus":"virtio-serial0.0","nr":1,"chardev":"charchannel0","id":"channel0","name":"com.redhat.spice.0"} \
    -chardev socket,id=charchannel1,fd=34,server=on,wait=off \
    -device {"driver":"virtserialport","bus":"virtio-serial0.0","nr":2,"chardev":"charchannel1","id":"channel1","name":"org.qemu.guest_agent.0"} \
    -audiodev {"id":"audio1","driver":"spice"} \
    -spice port=5900,addr=127.0.0.1,disable-ticketing=on,image-compression=off,seamless-migration=on \
    -device {"driver":"virtio-balloon-pci","id":"balloon0","bus":"pci.4","addr":"0x0"} \
    -sandbox on,obsolete=deny,elevateprivileges=deny,spawn=deny,resourcecontrol=deny \
    -msg timestamp=on

Теперь, когда на хосте не выполняются CUDA задачи, клонирование и загрузка виртуальной машины занимают около 10-30 секунд, что отлично. К сожалению, когда хост занят одной (или несколькими) задачами машинного обучения, загрузка виртуальной машины занимает от 10 до 20 минут (клонирование по-прежнему очень быстро). 10 минутное время загрузки неприемлемо для CI (в конце концов: это только загрузка системы; фактические CI задачи еще не начались).

Тем временем хост сам по себе остается достаточно отзывчивым, так что я уверен, что это не проблема с подкачкой (единственный процесс, который немного медлителен, это сам htop; это может быть потому, что ему нужно обновлять красочные столбцы для 48 ЦП или что-то в этом роде)

Когда хост занят задачами CUDA, он имеет довольно высокую нагрузку (около 36, учитывая его 48 “ЦП”)

# cat /proc/loadavg 
35.56 36.61 37.36 57/1753 4059018

# nvidia-smi 
Ср Янв 8 15:55:05 2025       
+---------------------------------------------------------------------------------------+
| NVIDIA-SMI 535.183.01             Версия драйвера: 535.183.01   Версия CUDA: 12.2     |
|-----------------------------------------+----------------------+----------------------+
| GPU  Имя                 Устойчив. режим | Bus-Id        Disp.A | Летучая отказ. ECC   |
| Вент. Темп.   Производительность Pwr:Потребление/Потолок | Память-Пользование       | GPU-Утил. Режим вычис.   |
|                                         |                      |               MIG Режим |
|=========================================+======================+======================|
|   0  NVIDIA GeForce RTX 4090        Вкл | 00000000:2C:00.0 Выкл |                  Выкл |
| 51%   54C    P2             118W / 450W |  20477MiB / 24564MiB |     18%      По умолчанию |
|                                         |                      |                  N/A |
+-----------------------------------------+----------------------+----------------------+
|   1  NVIDIA GeForce RTX 4090        Вкл | 00000000:41:00.0 Выкл |                  Выкл |
| 56%   57C    P2             113W / 450W |  19637MiB / 24564MiB |     49%      По умолчанию |
|                                         |                      |                  N/A |
+-----------------------------------------+----------------------+----------------------+
|   2  NVIDIA GeForce RTX 4090        Вкл | 00000000:42:00.0 Выкл |                  Выкл |
| 46%   56C    P2             161W / 450W |  14293MiB / 24564MiB |     61%      По умолчанию |
|                                         |                      |                  N/A |
+-----------------------------------------+----------------------+----------------------+
                                                                                         
+---------------------------------------------------------------------------------------+
| Процессы:                                                                            |
|  GPU   GI   CI        PID   Тип   Имя процесса                            Память GPU |
|        ID   ID                                                             Использование      |
|=======================================================================================|
|    0   N/A  N/A   3971087      C   python                                     4038MiB |
|    0   N/A  N/A   4007510      C   python3                                   16428MiB |
|    1   N/A  N/A   3285843      C   python                                     9988MiB |
|    1   N/A  N/A   4112624      C   python                                     9620MiB |
|    2   N/A  N/A   4116418      C   python                                     5270MiB |
|    2   N/A  N/A   4133228      C   python                                     8980MiB |
+---------------------------------------------------------------------------------------+

htop также показывает высокий уровень загрузки диска:

Диск IO: 52.6% чтение: 405MiB/s запись: 11.0MiB/s                                                                      

Теперь базовый образ виртуальной машины хранится на том самом NVMe диске, который задачи CUDA используют для хранения своих обучающих данных, так что может быть загруженность на этой шине. Однако копирование образов дисков на/с этого диска, в то время как обе задачи CUDA и (медленно загружающиеся) виртуальные машины запускаются, происходит достаточно быстро. Также я уверен, что пробовал полностью поместить весь образ диска виртуальной машины на RAM-диск, без заметного ускорения.

Я пытался renice процессы qemu-system-x86_64 на высокий приоритет (-20), что уменьшило время загрузки до 4 минут. Но это все еще слишком много. (Также: когда я пытался воспроизвести это, время загрузки вновь поднялось до 10 минут, так что, полагаю, 4 минуты были просто удачным стечением обстоятельств, когда задачи CUDA делали синхронизацию данных для чего-либо).

Я немного потерялся, где именно узкое место у хоста:

  • очевидно, что ЦПУ довольно загружен задачами CUDA, и один процесс qemu также удерживает 4 ЦПУ на уровне 100%), но разве не должно renice -n -20 ${PID_of_qemu_processes} справляться с этим?
  • нагрузка на диск также довольно высокая, и я не совсем понимаю, как я могу приоритизировать мои процессы для этого. htop не показывает значительной диск-загрузки процессов qemu (не уверен, хорошо ли это (так как много IO не требуется) или плохо (так как процесс голодает)); в любом случае htop показывает 0.0% для IO_DELAY
  • догадываюсь, что шина PCIe загружена с помощью GPU, но не должны ли виртуальные машины быть не затронутыми этим? (так как они не используют GPU)

Итак: как я мог бы выяснить, почему мои виртуальные машины работают так чрезвычайно медленно? (и что я мог бы сделать, чтобы они работали быстрее?)

Я, вероятно, не возражал бы, если бы задачи CUDA можно было бы заставить работать с очень низким приоритетом (учитывая, что хост не делает ничего, кроме CUDA и виртуальных машин, и виртуальные машины запускаются очень редко)

.

Ответ или решение

Ситуация, когда виртуальная машина QEMU становится чрезвычайно медленной на хосте, который также выполняет интенсивные CUDA процессы, может быть вызвана несколькими факторами, связанными с распределением ресурсов. В вашем случае, когда CUDA задачи значительно загружают систему, необходимо продумать способы оптимизации и распределения ресурсов, чтобы виртуальные машины оставались отзывчивыми и могли эффективно обрабатывать CI-задания.

Определение проблемы

Ваш Debian/bookworm хост, обладая мощным железом (материнская плата ASUS Pro WS WRX80E-SAGE SE WIFI, процессор AMD Ryzen Threadripper PRO 5965WX с 24 ядрами и 128G ОЗУ, а также три NVIDIA GeForce RTX 4090), в основном используется для машинного обучения, которое сильно нагружает систему. Однако те же ресурсы используются и для быстро устанавливаемых виртуальных машин для CI-заданий без графического ускорения.

Возможные узкие места

  1. ЦПУ нагрузка: Ваши CUDA задачи могут потреблять значительное количество времени процессора. Даже при использовании renice, чтобы повысить приоритет задач QEMU, это может не быть достаточно в условиях высокой загрузки системы.

  2. Дисковое I/O: С хранением основной и клонов образов на одном NVMe диске, высокое I/O может стать узким местом, особенно в условиях параллельных записей и чтений для CUDA задач.

  3. Память: Хотя память не упоминается как ограничение, высокие требования CUDA задач могут влиять на доступную оперативную память и задержки затрат на свопинг.

Рекомендации по оптимизации

  1. Распределение приоритета ЦПУ и IO: Используйте cgroups и ionice для определения приоритета процессов. Cgroups помогут установить ограничения CPU для каждого процесса, а ionice выполнит аналогичное для дисковых операций.

  2. Разделение ресурсов: Перенесите VM образы на отдельный диск, избегая конкуренции с CUDA задачами. Рассмотрите возможность использования SSD-памяти для swap и высокоприоритетного хранения.

  3. Изучение узких мест: Используйте инструменты мониторинга, такие как atop, iostat и другие, чтобы выявлять основные источники задержек. Это поможет вам настроить распределение ресурсов более осознанно.

  4. Настройки QEMU/KVM: Убедитесь, что ваша конфигурация QEMU максимально оптимизирована. Это может включать корректировку параметров загрузки, использования КВМ и выбранных настроек процессора.

  5. CUDA задачи низкого приоритета: В тех случаях, когда CI процессы критически важны, переводите CUDA задачи на более низкий приоритет исполнения, чтобы освободить ресурсы.

  6. Расставьте приоритеты: Рассмотрите использование специальных скриптов или программ управления задачами, которые могут динамически изменять приоритеты, основываясь на текущих нагрузках.

Вывод

Понимание ваших текущих конфигураций и ограничений позволит более детально подходить к редизайну процессов управления ресурсами. Необходимо провести тестирование предложенных решений, чтобы оценить их эффективность на практике. Используя подход FOREST (F – Focus on customer needs, O – Original findings, R – Relatable solutions, E – Engaging content, S – Structured process, T – Timely execution), можно добиться значительной оптимизации ресурсов в высоко-нагруженной системе.

Оцените материал
Добавить комментарий

Капча загружается...