クイックスタート:5分で完了する初期設定
午前2時、障害チケットのアラートが鳴り響く。Blenderレンダリング用のWindows仮想マシンで深刻なボトルネックが発生し、仮想GPU(VirtIO)のビューポートはわずか4 FPSしか出ていません。この状況を打破する唯一の解決策が、VFIOを利用して物理GPUをVMに直接パススルーすることです。
以下は、Ubuntu 22.04またはDebian 12環境で直接実行できる手順です。
ステップ1:ブートローダーでIOMMUを有効化する
# GRUB設定を開く
sudo nano /etc/default/grub
# CPUに応じたパラメータをGRUB_CMDLINE_LINUX_DEFAULT変数に追加:
# Intel CPU: intel_iommu=on iommu=pt
# AMD CPU: amd_iommu=on iommu=pt
GRUB_CMDLINE_LINUX_DEFAULT="quiet splash intel_iommu=on iommu=pt"
# GRUBを更新して再起動
sudo update-grub
sudo reboot
ステップ2:GPUのハードウェアIDを確認する
lspci -nn | grep -E "VGA|Audio"
# RTX 3060の出力例:
# 01:00.0 VGA compatible controller [0300]: NVIDIA Corporation GA106 [GeForce RTX 3060] [10de:2504] (rev a1)
# 01:00.1 Audio device [0403]: NVIDIA Corporation GA106 High Definition Audio Controller [10de:228e] (rev a1)
# 記録しておくVendor:Device IDのペア: 10de:2504, 10de:228e
ステップ3:vfio-pciドライバーでGPUをホストから分離する
仮想マシンが起動する前に、ホストLinux側で対象GPUの制御を手放す必要があります。上記で確認したIDペアにvfio-pciドライバーを割り当てる設定ファイルを作成します。
echo "options vfio-pci ids=10de:2504,10de:228e" | sudo tee /etc/modprobe.d/vfio.conf
# 起動時に必要なモジュールをロード
echo -e "vfio\nvfio_iommu_type1\nvfio_pci" | sudo tee -a /etc/modules
# デフォルトのグラフィックドライバーによるデバイスの占有を防止
echo -e "blacklist nouveau\nblacklist nvidia\nblacklist snd_hda_intel" | sudo tee /etc/modprobe.d/blacklist.conf
# initramfsを更新して再起動
sudo update-initramfs -u -k all
sudo reboot
ホストサーバーの再起動後、以下のコマンドで状態を確認します。
lspci -nnk -d 10de:2504
# 「Kernel driver in use」の行が vfio-pci と表示されていれば正常
技術的な仕組み:IOMMUとIOMMUグループの課題
一般的な仮想マシンは、QXLやVirtIO GPUなどの仮想化グラフィックデバイスを経由して描画を行います。そのため、ホストCPUがグラフィック命令のトランスレーション処理をすべて負担することになります。この方式は文書作成やWebブラウジング程度であれば十分ですが、3DレンダリングやAAAタイトルゲームではパフォーマンスが70%以上低下してしまいます。
VFIO(Virtual Function I/O)はこのボトルネックを根本から解消します。物理GPUのPCIeレジスタを仮想マシンが直接制御できるようにすることで、ベアメタル環境と比較して97〜99%の性能を引き出すことが可能になります。
IOMMUグループの正確な理解
IOMMUは、I/Oデバイス専用のMMU(メモリ管理ユニット)として機能します。VMの仮想メモリアドレス空間をハードウェアデバイスのDMAバスに直接マッピングする仕組みです。ただし、マザーボードのハードウェア設計上、各PCIeスロットが個別に完全分離されているわけではなく、複数のデバイスが「IOMMUグループ」としてまとめられています。
KVMにおける絶対的な鉄則は明白です。IOMMUグループはハードウェア分離の最小単位であるため、同じグループに属するすべてのデバイスをまとめて仮想マシンにパススルーするか、あるいは一切パススルーしないかのいずれかを選択しなければなりません。例外は存在しません。
以下のスクリプトを実行して、デバイスのグループ構成を確認します。
#!/bin/bash
for d in /sys/kernel/iommu_groups/*/devices/*; do
n=${d#*/iommu_groups/*}; n=${n%%/*}
printf 'IOMMU Group %s ' "$n"
lspci -nns "${d##*/}"
done | grep -i nvidia
グラフィックカードとHDMIオーディオコントローラーが同一のGroup 13にある場合、両方をvfio-pciに割り当てる必要があります。逆に、GPUがオンボードのUSBコントローラーやNICと同じグループに含まれている場合、VMを単独で起動することはできません。
高度なチューニング:Code 43の解消とフレームタイム遅延の削減
ホスト層でGPUを正常に認識させても、作業はまだ半分です。Windowsを起動すると、デバイスマネージャーにおなじみの黄色い感嘆符とともにCode 43エラーが表示されることがあります。
1. Code 43の根本的な解消
バージョン465(2021年4月)以降のドライバーで、NVIDIAはGeForceにおける仮想マシンの制限を解除しました。しかし、一部のゲームやグラフィックアプリケーションは依然としてハイパーバイザーの存在を検知すると動作を停止します。確実な解決策は、仮想マシンのXML定義ファイル(virsh edit win10-gpu)でKVMの痕跡を隠蔽することです。
<features>
<acpi/>
<apic/>
<hyperv mode='custom'>
<relaxed state='on'/>
<vapic state='on'/>
<spinlocks state='on' threshold='8191'/>
<vendor_id state='on' value='GenuineIntel'/>
</hyperv>
<kvm>
<hidden state='on'/>
</kvm>
<ioapic driver='kvm'/>
</features>
2. PCIeの割り当てとクリーンなVBIOSのロード
パススルー対象のGPUがホスト起動時のプライマリGPUである場合、ホストのUEFIによってカードのROMメモリが上書きされてしまいます。その結果、仮想マシンがドライバーを読み込む際に画面が真っ暗になる現象が発生します。TechPowerUpからオリジナルのVBIOSファイルをダウンロードするか、事前にダンプしておいたROMファイルを<hostdev>タグ内で明示的に指定します。
<hostdev mode='subsystem' type='pci' managed='yes'>
<source>
<address domain='0x0000' bus='0x01' slot='0x00' function='0x0'/>
</source>
<rom file='/var/lib/libvirt/vbios/rtx3060.rom'/>
<address type='pci' domain='0x0000' bus='0x06' slot='0x00' function='0x0' multifunction='on'/>
</hostdev>
<hostdev mode='subsystem' type='pci' managed='yes'>
<source>
<address domain='0x0000' bus='0x01' slot='0x00' function='0x1'/>
</source>
<address type='pci' domain='0x0000' bus='0x06' slot='0x00' function='0x1'/>
</hostdev>
3. CPU PinningとHugePagesによるマイクロスタッターの排除
不意に発生する画面のカクつき(マイクロスタッター)は、ホストCPUの頻繁なコア間移動(コンテキストスイッチング)やメモリの断片化が原因です。物理コア/スレッドのペアを各vCPUに固定(Pinning)することで、フレームタイムのブレを2ms未満に抑制できます。
<vcpu placement='static'>8</vcpu>
<cputune>
<vcpupin vcpu='0' cpuset='4'/>
<vcpupin vcpu='1' cpuset='12'/>
<vcpupin vcpu='2' cpuset='5'/>
<vcpupin vcpu='3' cpuset='13'/>
<vcpupin vcpu='4' cpuset='6'/>
<vcpupin vcpu='5' cpuset='14'/>
<vcpupin vcpu='6' cpuset='7'/>
<vcpupin vcpu='7' cpuset='15'/>
<emulatorpin cpuset='0,8'/>
</cputune>
<memoryBacking>
<hugepages/>
</memoryBacking>
さらに2MBのHugePagesを設定することで、TLBページテーブルのキャッシュミス率を12%から0.8%未満へと大幅に低減し、極めて安定したフレームレートを維持できます。
トラブルシューティングの実践ノウハウ
- マザーボードのIOMMUグループ分割が不適切な場合:GPUがSATAコントローラーなどと同じグループにまとめられている場合、暫定的な対策としてカーネルパラメータに
pcie_acs_override=downstream,multifunctionを追加します。これによりグループを強制的に細分化できますが、VM間のDMAセキュリティ境界が損なわれるため、ホームラボや開発環境でのみ使用してください。 - 仮想マシン起動時のブラックアウト:マザーボードのBIOSでCSM(Compatibility Support Module)を無効化してください。また、仮想マシンのファームウェアにはレガシーなSeaBIOSやi440fxチップセットを避け、必ず
OVMF (UEFI)とQ35チップセットを組み合わせて作成してください。 - ヘッドレス(モニター未接続)運用:グラフィックカードのポートにHDMIダミープラグ(安価なダミーコネクタ)を接続します。Windows VM上にSunshineサーバーを導入し、クライアント端末からMoonlight経由でストリーミングします。1GbpsのLAN環境があれば、デコード遅延3ms未満で2K 120Hzの快適なストリーミング環境を実現できます。
- BAR 0のリソース競合解除:
dmesg | grep -i vfioを実行してBAR 0: cannot reserveエラーが出ている場合、LinuxのフレームバッファがGPUを占有しています。GRUBパラメータにvideo=efifb:offまたはinitcall_blacklist=sysfb_initを追加して解決します。

