プログラミング
Virtio-nvgpu: KVMゲスト内でNVIDIA GPUへのニアネイティブアクセスを実現
Virtio-nvgpu: Near-native Nvidia GPU access inside a KVM guest (github.com)
要約
Virtio-nvgpuは、KVMゲスト環境でNVIDIA GPUへのニアネイティブなアクセスを提供する新しいドライバーです。APIレベルの翻訳を完全にバイパスし、ゲストOSはNVIDIAの公式ユーザーモードドライバーをそのまま使用できます。これにより、GPUレンダリングとエンコードをゲスト内で実行し、ホストへのCPU負荷を最小限に抑えつつ、ヘッドレスストリーミングなどの用途で高いパフォーマンスを発揮します。
全文翻訳
virtio-nvgpu KVMゲスト内でのニアネイティブなNVIDIA GPUアクセス。ゲストは実行対象のマシンと比較して2%以内のオーバーヘッドでレンダリングし、CPUコストも同等です。virtio-nvgpuは、Linuxゲストとホスト間でNVIDIAカーネルドライバーのioctlをドライバーABIレベルで転送し、APIレベルの翻訳を完全にバイパスします。ゲストはNVIDIA自身のユーザーモードドライバーを、変更なしで使用します。同じライブラリ、同じVulkanとNVENCが、同じカードと通信します。ターゲットはヘッドレスストリーミングです。VM内のコンポジターがGPU上でフレームをレンダリング、コンポジット、エンコードし、その後圧縮ビデオを送信します。VMにはモニターがなく、ホストがカードを保持します。
現状
動作しており、測定も行われています。Waylandクライアントがゲスト内で表示され、キャプチャレイヤーがゲーム自身のデバイスでエンコードし、H.264が反対側から出力されます。ffmpegがエラーなしでデコードする618フレームです。RTX 3060(ドライバー595.99.02)で測定され、ゲストとホストは同一で、ベアメタルと同等のヘッドレスVulkan負荷がかかっています。ホストが1フレームにかかる時間に対して、ゲストのフレーム時間は39 msでベアメタルより0.4%高速、ノイズの範囲内です。9.9 msでは-0.7%、2.0 msでは+1.7%、0.5 msでは+7.1%です。ウェイクアップは約0.02 msかかり、フレームは0.05 msの半分です。+40.8%。約2 ms/フレーム(ゲームが描画するすべてのフレーム)を超えると、ゲストはベアメタルから2%以内のパフォーマンスになります。それ以下では、GPUを待つコストが、ほとんど存在しないフレームに対して現れ始めます。
CPUももう半分です。共有GPUは、ゲストのコストが低い場合にのみ共有する価値があるからです。12秒間、ペースなしで約100 fpsで実行した場合、1つのゲストのCPU使用率は、ホストのベアメタルで0.40秒、ゲストで0.37秒です。ゲストのコストはホストのコストと同等です。レンダーループでの転送には何も費やされません。なぜなら、何も転送されないからです。NVIDIAのユーザーモードドライバーは、マッピングされたメモリにサブミットし、そのメモリはホストのものです。813,691フレームにわたって、バックエンドは13,792メッセージを処理しました。これは59フレームあたり1メッセージのクロスオーバーで、ほぼすべてがデバイスセットアップでした。完全な方法、生の実行結果、およびこれらの数値がサポートしないものについては、BENCHMARKS.mdを参照してください。
1枚のカードで複数のゲスト
1枚のRTX 3060で4つのゲスト、それぞれに同じ負荷がかかっています。フレームレートは25.84、26.49、25.57、25.79 fpsです。合計で103.7 fpsとなり、単一ゲストの102.9 fpsと比較して、p50フレーム時間は39.165 ms、39.164 ms、39.168 ms、39.165 msです。ゲストを追加しても合計値は変化せず、分割は小数点以下4桁まで均等です。4つすべてが同時に正しくレンダリングされ、4つすべてが同時にH.264をエンコードし、それぞれが正確に60 Hzでペース設定され、NVENCセッション制限に達することはありませんでした。4は実行された数であり、制限ではありません。
ドライバーバージョン
595.99.02で測定されました。615.71.09のA2000はレンダリングしますが、ベンチマークは行われていません。出荷されたABIプロファイルは、535.129.03、580.178.04、595.71.05で、範囲で一致しており、最初のものより古いものは拒否されます。詳細は以下を参照してください。
動作することがわかっていること
ゲストはカードを列挙します。nvidia-smiは実際の電力とメモリを報告し、deviceUUIDはホストのものです。Vulkanはレンダリングします。vulkaninfoは0で終了し、オフスクリーン描画はピクセルパーフェクトです。Waylandクライアントはゲスト内のコンポジターを通じて表示されます。Vulkan Video経由のNVENCは、クライアント自身のデバイスでエンコードします。インポートされたバッファはホストのメモリであり、共有ウィンドウを通じてマッピングされます。
完了していないこと
4つ以上のゲスト、または720pのvkcubeより重いことを行うゲスト。4つはカードを均等に共有します。8つは試されていません。2枚のカード、2つのドライバーバージョン。RTX 3060 / 595.99.02が数値の出所です。RTX A2000 / 615.71.09はレンダリングしますが、ベンチマークは行われていません。CUDAは転送されますが、列挙以外はテストされていません。Jailer、バージョンごとのドライバー共有、マルチテナントエンベロープはビルドされていません。
リポジトリレイアウト
4つのコンポーネント、3つのライセンスゾーン。この分割は意図的です。ゲスト側はカーネルシンボルに触れるためにGPLである必要があり、ホスト側は他の人がそれを基盤に構築できるように、許可的なライセンスである必要があります。両方が共有する定義は、両方からインクルード可能でなければなりません。
ディレクトリ
ライセンス
内容
driver/
GPL-2.0
ゲストカーネルモジュール。/dev/nvidia*を登録し、virtqueue経由でioctlとmmapを転送します。ABIを意識しないように意図されています。
device/
Apache-2.0
VMMを依存リストに持たないRustクレートとしてのvirtioデバイス。すべてのVMMの懸念はトレイトです。
isolate/
Apache-2.0
まだコードではない設計ノート。実際のデバイスFDを保持するサンドボックス化されたヘルパーです。現在、バックエンドはVMM自身のプロセス内でディスクリプタを保持しています。
gen/
— 生成されたABIテーブル。チェックインされ、再現可能です。
protocol/
BSD-3-Clause OR GPL-2.0+
ワイヤーフォーマットとABI定義。両方の半分で共有されます。GPLドライバーとApacheクレートが同じヘッダーを含めることができるように、デュアルライセンスされています。
レイアウトは、chromeos/virtio-mediaに従っており、同じ問題を解決しています。つまり、1つのリポジトリに、許可的にライセンスされたVMMに依存しないデバイスクレートの隣にGPLゲストドライバーが含まれています。VMMがdevice/から使用するには、仮想マシンモニターに依存しません。VMMは、小さなトレイトセット(Read/Writeとしてのディスクリプタチェーン、イベントキュー、ゲストメモリマッピング、ホストメモリマッピング)を実装することでデバイスを採用し、クレートをパッチすることなくデバイス全体を取得します。オプションの機能は、ビルドに失敗するのではなく、低下するため、VMMはすべての機能をサポートする前に採用できます。バッファとウィンドウのブックキーピングはdevice/にあります。VMMは生のマップとアンマップのみを提供し、それ以上はありません。
トレイトにならないことの1つは、isolateです。意図された設計では、ゲストプロセスごとに1つのサンドボックス化されたヘルパープロセスを実行するため、最終的に採用することは、ライブラリ依存関係だけでなく、プロセスモデルを引き継ぐことを意味します。そのヘルパーはまだ書かれていません。バックエンドは現在デバイスディスクリプタ自体を保持しており、isolate/は設計が生きている場所です。
なぜ?
私たちが望むストリーミングパイプライン
ゲストVM(ヘッドレス、物理ディスプレイなし)
──────────────────────────────────────────
ゲーム/アプリケーション
│ VulkanまたはOpenGL
▼
Waylandコンポジター(ゲスト側)
│ すべてのウィンドウをコンポジット
│ CUDAゼロコピー
│ コンポジットされたフレームのインポート
▼
NVENCハードウェアエンコーダー(ゲスト側)
│ H.264 / H.265ビットストリーム(フレームあたり約100 KB)
▼
リモートクライアントへのストリーム
レンダリング→コンポジット→エンコードパイプライン全体がGPU上で、ゲスト内で実行されます。圧縮されたビットストリームのみが出力されます。これには、ゲストがGPUリソース(バッファハンドル、フェンス、CUDAデバイスポインター、NVENCセッション)への実際のドライバーレベルのアクセスを持つ必要があります。
既存のアプローチが不十分な理由
virtio-gpu + Venus(APIレベル翻訳)。Venusはゲスト内のすべてのVulkanまたはOpenGLコールをシリアライズし、virtio経由で転送し、ホスト側で再生します。このユースケースには3つの問題があります。
レイテンシがドローコールが多いワークロードで累積します。ゲームはフレームあたり1,000〜5,000のドローコールを発行し、バインド、ディスクリプタ更新、レンダリングパス遷移が追加され、それぞれが個別にシリアライズおよび再生されます。60 fpsでは、フレーム予算は16.6 msです。シリアライズに1〜3 msかかると、GPU作業が発生する前に6〜18%が失われます。
CPUオーバーヘッドが大きいです。シリアライズ、転送、再生は、アプリケーションが必要とするホストCPUを消費します。コンピューティングが課金され、有限である場合、その無駄は製品です。
ゲスト側エンコーディングは実行可能ではありません。GPUバッファはホストが所有しています。ゲストコンポジターはそれらを表示またはインポートできないため、Venusが管理するバッファを指すゲスト内のCUdeviceptrへの実用的なパスはありません。これは、完全なCPUリードバックとコピーなしではNVENCを意味しません。
DRMネイティブコンテキスト(Intel / AMD)。ゲストは実際のMesaドライバーを実行し、ローカルでコマンドバッファを構築し、サブミッションのみが境界を越えます。ゲスト側バッファの所有権とエンコーディングは正しく機能します。これはNVIDIAには存在しません。
VFIOパススルー。ゲスト内のネイティブパフォーマンスと完全なドライバースタックですが、GPU全体を1つのVMに専念させます。マルチテナント環境では、これはしばしばオプションではありません。
virtio-nvgpuの独自性
翻訳はグラフィックスAPIレベルではなく、カーネルドライバーレベル(/dev/nvidia*へのioctl)で行われます。ゲストはNVIDIAの実際のユーザーモードライブラリを実行し、ローカルでGPUコマンドバッファを構築します。個々のドローコールは決してシリアライズされません。
Venus
virtio-nvgpu
──────────────
──────────────────────
ドローコールごと:
シリアライズ + ローカル関数呼び出し
転送 + (VM出口なし)
デシリアライズ + 再生
フレームごと
約2,000メッセージ
約5〜20メッセージ
境界(1