feat(device_plugin): discover vGPU VFs via vendor-specific VFIO framework#192
feat(device_plugin): discover vGPU VFs via vendor-specific VFIO framework#192lexfrei wants to merge 5 commits into
Conversation
…work Ada/Hopper+ GPUs (vGPU 17+) no longer expose vGPU instances through mdev; instead, SR-IOV Virtual Functions stay bound to the "nvidia" host driver and are configured directly on the PCI function via sysfs. The existing vGPU discovery only walks /sys/bus/mdev/devices, so it finds nothing on these GPUs and every vGPU VF ends up indistinguishable from a whole-card passthrough device, since both share the same PCI vendor:device id. Add a second discovery path that walks /sys/bus/pci/devices looking for Nvidia functions bound to the "nvidia" driver, skips Physical Functions (identified by sriov_totalvfs), and treats a Virtual Function as a configured vGPU when its current_vgpu_type is non-zero. The profile name is resolved by matching that type id against the creatable_vgpu_types catalog, merged per physical card (scoped by each VF's physfn parent) since a configured VF's own list is frequently empty or reduced to the active entry, and since vGPU type ids are only unique within a single physical card. Because these VFs are ordinary PCI/IOMMU-group devices, discovered functions are added to the same iommu/BDF maps used by GPU passthrough and dispatched through the existing GenericDevicePlugin, grouped by vGPU profile name instead of PCI device id. No changes were needed to the allocate path or device contract; the mdev-backed vGPU discovery is untouched. Document the new hardware path in the README alongside the existing mdev instructions, since it applies to a different GPU generation and neither replaces the other. Signed-off-by: Aleksei Sviridkin <f@lex.la>
A fully consumed card reduces the creatable_vgpu_types list of every one of its functions down to the header, so the per-card catalog cannot resolve the types of already-configured VFs and the plugin advertised nothing exactly when all capacity was allocated. Fall back to a host-wide catalog merged across all cards; numeric ids that different cards map to different names stay ambiguous and are still skipped rather than guessed. Signed-off-by: Aleksei Sviridkin <f@lex.la>
On a host where every card is fully consumed, no creatable_vgpu_types catalog lists anything at all — sysfs alone cannot resolve configured type ids to profile names no matter how catalogs are merged. Resolve through NVML as the last resort: the supported-vGPU list of a card does not shrink as capacity is allocated. Scoped per physical card like the sysfs catalogs, cached per scan, and requires the host driver library to be loadable in the container. Signed-off-by: Aleksei Sviridkin <f@lex.la>
|
Thanks for this PR and hardware validation. The overall approach makes sense to me
A couple of smaller follow-ups:
The discovery structure, per-PF grouping, IOMMU-map integration, and test coverage otherwise look solid to me. |
…other cards When a configured VF's card catalog is reduced and cannot resolve its type id, resolution fell back to a host-wide catalog merged across all cards. Numeric vGPU type ids are only unique within one physical card, so if the target card contributed no entry for the id and another card mapped the same id to a different profile, that entry looked unambiguous and the VF could be advertised under the wrong extended resource. Drop the host-wide catalog entirely. Resolve strictly card-local sysfs, then the parent Physical Function's own NVML supported-type list, then skip the VF with a log. NVML is authoritative per card and its supported list does not shrink as capacity is allocated, so it covers the fully consumed card without ever borrowing a sibling card's mapping. Assisted-By: Claude <noreply@anthropic.com> Signed-off-by: Aleksei Sviridkin <f@lex.la>
…ments The NVML name-resolution fallback needs libnvidia-ml.so.1 and the NVIDIA device nodes. The default DaemonSet mounts only /dev/vfio and runs unprivileged, so on a fully consumed node a restart could resolve no profile names and advertise no VFs. Add a dedicated manifest that provides them via a single-file bind of libnvidia-ml.so.1 plus the NVIDIA device nodes under privileged: true, leaving the default manifest minimal and unprivileged. Document in the README both the minimally privileged runtimeClassName: nvidia path and the hostPath variant, why the library must be bound as a single file rather than the whole host lib directory, and which device nodes the plugin's NVML queries actually require. Also document that vGPU profiles must be configured before the plugin starts, since it scans the Virtual Functions once at startup. Assisted-By: Claude <noreply@anthropic.com> Signed-off-by: Aleksei Sviridkin <f@lex.la>
|
Thanks for the careful review — all four points are addressed. 1. Wrong-profile risk from the host-wide catalog. Removed. The host-wide catalog and its ambiguity tracking are gone. A configured VF's profile name is now resolved strictly by (1) its own card's sysfs 2. NVML fallback usable from the manifest. Added 3. Profile timing. Documented: the plugin scans the vendor-VFIO VFs once at startup and does not re-scan, so all profiles must be configured before it starts; the README also suggests ordering the plugin after whatever recreates the VFs on reboot. Dynamic rediscovery of profiles created or changed while the plugin runs is a separate follow-up PR — I'll link it here once it's open. 4. Stale comment. The |
feat(device_plugin): discover vGPU VFs via vendor-specific VFIO framework
Problem
This plugin discovers vGPU instances by walking
/sys/bus/mdev/devices. On Ada Lovelace and newer GPUs (vGPU release 17+, e.g. an H200 SXM system), the host driver no longer exposes vGPU through mdev at all — vGPU profiles are assigned per SR-IOV Virtual Function through a vendor-specific VFIO sysfs interface instead. On these GPUs the mdev walk finds nothing, so no vGPU resources are ever advertised, and every VF (regardless of the profile configured on it) shares the same PCI vendor:device id as the whole card, making per-profile scheduling impossible through the existing GPU-passthrough path either.KubeVirt's own built-in PCI device plugin already recognizes this pattern (kubevirt/kubevirt#16890, merged in v1.9.0-beta.0): a PCI function bound to the
nvidiadriver with a non-zerocurrent_vgpu_typeis treated as a permitted vGPU VF. This plugin exists specifically to implement the same per-profile external-device-plugin pattern KubeVirt expects (externalResourceProvider: true), so it should recognize these VFs the same way.Related upstream context: NVIDIA/gpu-operator#2594 tracks the separate gap of creating vGPU profiles on these VFs (writing
current_vgpu_type) via the GPU Operator's vgpu-device-manager. This PR does not touch creation — it only discovers and advertises VFs that already have a profile configured (created manually, or by whatever eventually addresses that issue).What changed
pkg/device_plugin/vfio_vgpu.go, a second vGPU discovery path alongside the existing mdev-based one (createVgpuIDMap, untouched)./sys/bus/pci/devicesfor Nvidia (10de) functions bound to thenvidiadriver, skips Physical Functions (identified by thesriov_totalvfsattribute, consistent with how KubeVirt itself distinguishes PFs), and treats a Virtual Function as a configured vGPU whencurrent_vgpu_type != 0.creatable_vgpu_types("<id> : <name>"lines), sanitized the same way the mdev path already sanitizes profile names (whitespace replaced with_) so both frameworks produce identically formatted resource names for a profile of the same name.vfioVGpuMap, wired intoInitiateDevicePlugin/createDevicePluginsinpkg/device_plugin/device_plugin.goalongside the existingdeviceMap(GPU passthrough) andvGpuMap(mdev vGPU) loops.Design notes
Profile name resolution. On a configured VF,
creatable_vgpu_typesis reduced (down to just the header once the card's capacity is fully allocated), and on at least one H200 SXM system the Physical Function exposes nonvidia/sysfs directory at all — so the unconfigured sibling VFs are the only sysfs catalog source. Resolution is a three-step chain, all scoped per physical card (VFs keyed by theirphysfnparent) where possible:creatable_vgpu_typescatalog;nvmlDeviceGetSupportedVgpus+nvmlVgpuTypeGetNameon the parent PF) as the last resort — unlike the creatable list, the supported list does not shrink as capacity is allocated, so it keeps working on a fully tiled-out host where every sysfs catalog on every card is reduced simultaneously. That state is not hypothetical: it is the steady state of a fully allocated node, exactly when the plugin must keep advertising. NVML requireslibnvidia-ml.so.1to be loadable in the container — bind-mount the single library file from the host driver installation (mounting the whole host library directory would drag the host glibc into the container) and give the pod access to the NVIDIA device nodes (e.g.privileged: truewith/devmounted).Allocate contract. No new allocation logic was needed. Discovered VFs are added to the same
iommuMap/bdfToIommuMapused by GPU passthrough, and dispatched through the existingGenericDevicePlugin(grouped by profile name instead of PCI device id) rather thanGenericVGpuDevicePlugin, which implements the mdev-specific env-var contract. This was verified against KubeVirt's own built-in plugin: itsPCIDevicePlugin.Allocate/formatVFIODeviceSpecsmount/dev/vfio/vfioplus/dev/vfio/<iommu_group>unconditionally, regardless of whether the underlying driver isvfio-pciornvidia— the classic group-based VFIO device node is what KubeVirt itself expects for these VFs, not the per-devicevfio-devcdev. This plugin'sGenericDevicePlugin.Allocatealready mounts that same group node, and additionally mounts the per-device/dev/vfio/devices/vfioNcdev plus/dev/iommuwhen iommufd is supported host-wide — a strict superset of KubeVirt's own contract, reused here unmodified.mdev path.
createVgpuIDMap,GenericVGpuDevicePlugin, and theMDEV_PCI_RESOURCE_*env contract are untouched. The two discovery paths populate disjoint maps (vGpuMapvsvfioVGpuMap) and are expected to be mutually exclusive in practice — mdev and the vendor-specific VFIO framework apply to different GPU generations, so no de-duplication between them was implemented.Testing
Added
pkg/device_plugin/vfio_vgpu_test.gocovering: profile name parsing/sanitization fromcreatable_vgpu_types(including the sibling-VF and conflicting-entry cases),current_vgpu_type/sriov_totalvfsfile reads, full discovery against a faked sysfs tree (PF, multiple VFs, unconfigured VF, unresolvable type id, an unrelated vfio-pci passthrough device, and a non-Nvidia device all present at once), the host-wide catalog fallback for a fully consumed card (and its refusal to guess when cards disagree on an id), and dispatch throughcreateDevicePlugins.Hardware validation
Run on an 8x H200 SXM system (driver 580.159.01 vGPU host manager, KubeVirt v1.9.0-beta.0,
externalResourceProvider: true): 36 MIG-backed VFs (nvidia.com/NVIDIA_H200X-1-18C) and 2 whole-card VFs (nvidia.com/NVIDIA_H200X-141C) discovered, resolved and registered with kubelet as separate per-profile extended resources, with running VMs consuming them through KubeVirt. Empirical findings folded back into the design above: the PF exposes nonvidia/sysfs directory on this system (the sibling-VF catalog source is load-bearing, not a bonus path); a fully consumed card reducescreatable_vgpu_typesto the header on every function; and on the fully allocated node NO sysfs catalog anywhere lists the configured type ids, making the NVML fallback the only working resolution source in that state.Open questions