Skip to content

Support vendor VFIO vGPU devices - #321

Open
yummybomb wants to merge 12 commits into
hypeship/generalize-vgpu-devicefrom
hypeship/vendor-vfio-vgpu
Open

Support vendor VFIO vGPU devices#321
yummybomb wants to merge 12 commits into
hypeship/generalize-vgpu-devicefrom
hypeship/vendor-vfio-vgpu

Conversation

@yummybomb

@yummybomb yummybomb commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

background

The current migration target is NVIDIA vGPU Manager R19 (R580) on Ubuntu 24.04 / kernel 6.8. For SR-IOV vGPUs, that host uses NVIDIA's vendor-specific VFIO framework: each VF is assigned a profile directly through its nvidia/ sysfs attributes and the VF itself is passed to the VM (no mdev UUIDs). Existing GPU hosts continue to use mdev, so both frameworks must coexist while hosts migrate one at a time: hypeman detects the host's framework at runtime and leaves the mdev path intact.

Note that /sys/class/mdev_bus/ itself may still be present on a vendor VFIO host, because nvidia_vgpu_vfio pulls in the mdev module. It just never gains parent entries, so detection keys off discovered mdev VFs rather than the directory existing.

summary

  • detect NVIDIA's mdev and vendor-specific VFIO frameworks at runtime
  • discover vendor VFIO virtual functions and parse creatable_vgpu_types
  • assign and release profiles through current_vgpu_type with least-loaded-GPU placement
  • protect newly created assignments with per-instance ownership until QEMU opens the VFIO device, then preserve active assignments and clear orphans by checking both iommufd cdevs (/dev/vfio/devices/*) and the legacy /dev/vfio/<group> node
  • document both frameworks and verify that vendor VFIO vGPUs retain the existing standby rejection

Stacked on #322, which contains the framework-neutral lifecycle refactor.

creatable_vgpu_types format

The driver emits a table with a header row and the type ID first:

ID    : vGPU Name
1145  : NVIDIA L40S-1B
1147  : NVIDIA L40S-1Q

Parsing splits on the : separator and skips the header row. An earlier revision expected the name first and the type ID last, which failed on every line (parse vGPU type ID "Name"), and because getVGPUStatus discards profile errors the host advertised vGPU mode with all its slots and no profiles at all — no placement could succeed.

Once a profile consumes a GPU's entire framebuffer, the remaining VFs on that GPU print the header and nothing else. That is a legitimately empty list rather than a malformed file, so it yields zero profiles instead of an error; otherwise one 48Q allocation would remove the whole GPU from the status response.

host validation

Validated on an L40S host running vGPU Manager 580.159.01 on Ubuntu 24.04 / kernel 6.8.0-136, where the driver reports Host VGPU Mode: SR-IOV and /sys/class/mdev_bus/ has no parents:

  • framework detection returns vendor VFIO; 64 VFs discovered across two L40S
  • 30 profiles enumerated with correct framebuffer sizes
  • availability drops from 64 to 32 slots after assigning NVIDIA L40S-48Q on one GPU, and returns to 64 after release. Assigning a smaller profile instead clamps that GPU's remaining VFs to the same framebuffer size class (2B/2Q/2A after a 2Q), matching how the mdev driver reports available_instances

Availability counts free VFs that can currently accept a profile, not how many of that profile fit — the driver enforces the framebuffer limit at assignment time. That is the pre-existing mdev behavior and this PR keeps it; tightening the count is separate work.

  • assigning current_vgpu_type and booting QEMU with -device vfio-pci,sysfsdev=<VF> produces a live NVIDIA L40S-1Q in nvidia-smi vgpu, released by writing 0
  • that QEMU process held the legacy /dev/vfio/<group> node, not an iommufd cdev, even with /dev/iommu present — so the legacy group fd check is the one that keeps assignments alive on this host
  • ran this branch as the systemd service and booted both existing and Ubuntu 24.04 kernel-browser GPU images with NVIDIA L40S-2Q; guest GRID loaded, CDP and kernel-images-api started, hardware OpenGL reported the L40S through ANGLE, and NVENC completed an encode
  • stop released the VF, start reacquired it, and standby returned the expected 409
  • restarting hypeman with a live VM preserved its assignment; killing QEMU and restarting hypeman cleared the orphaned current_vgpu_type
  • after the host provisioning role installed persistent SR-IOV ordering, a reboot restored 64 VFs and hypeman rediscovered all 30 profiles

tests

  • go test -race ./lib/devices ./lib/instances -run 'VendorVFIO|CreatableVGPU|StoredVGPU|StandbyRejectsVendorVFIO' -count=1
  • go test ./lib/devices ./lib/hypervisor/qemu ./lib/resources -count=1
  • go test ./cmd/api ./cmd/api/api ./lib/instances ./integration -run '^$' -count=1
  • go vet ./cmd/api ./lib/devices ./lib/hypervisor/qemu ./lib/instances ./lib/resources

review follow-ups

  • discover the vGPU framework and its VFs in one sysfs scan (DiscoverVGPU); ListGPUProfilesWithVFs takes the framework instead of re-detecting, removing the repeated full VF scans per GPU status query
  • GPU resource status now logs discovery and profile-listing failures instead of silently reporting a vGPU host with slots but no profiles (the failure mode the parser fix in this PR originally surfaced)
  • reconcile shares one /proc scan across all VF in-use checks instead of walking /proc per allocated VF
  • document why DestroyVGPU infers the framework for metadata written before GPUFramework existed
  • merged the base branch so this PR carries its current head
  • fail closed when VFIO usage probes are inconclusive, so reconciliation preserves active assignments
  • propagate framework discovery errors through create and reconciliation instead of treating them as no vGPU support
  • surface rollback failures after vendor VFIO assignment verification errors
  • report capacity instead of “profile not found” when the creatable catalog is empty at full GPU capacity, with an all-GPUs-full test
  • reconcile passes its shared /proc scan into each orphan release instead of rescanning per VF
  • reconciliation skips allocated VFs whose device path belongs to an instance with a live hypervisor process, so a hypeman restart cannot clear an assignment from under a VM that has not opened the VFIO device yet (the interval the in-process owner map cannot cover across restarts); orphans with no live instance are still cleared. If the instance inventory cannot be listed, reconciliation is skipped entirely rather than proceeding unprotected

scope decisions

The implementation stays scoped to the validated R19 / Ubuntu 24.04 target:

  • dropped the non-QEMU hypervisor rejection — the caller selects the hypervisor for vGPU instances, and hypeman keeps that selection with callers (mdev vGPU on Cloud Hypervisor was never gated either)
  • dropped the name-first creatable_vgpu_types format; the parser accepts only the ID : Name table the target driver emits
  • keep an in-process assignment ownership map for the interval between writing current_vgpu_type and QEMU opening the VFIO device; open-handle probing cannot cover that interval. After a restart, fail-closed VFIO handle probing protects recovered assignments
  • dropped dead framework inference for vendor VFIO device paths in DestroyVGPU
  • at full GPU capacity /resources reports an empty profile list (slot counts still show exhaustion); a last-known-catalog cache was considered and rejected — it would be stale across restarts and no caller makes placement decisions from that list — metadata with a device path always carries GPUFramework; only mdev-UUID-only legacy metadata needs inference
  • release identifiers travel in a VGPUAssignment struct instead of positional string parameters
  • listProfiles reads each VF's creatable_vgpu_types once per listing instead of twice (catalog + availability in one pass)

Note

High Risk
Changes GPU assignment, sysfs writes, and startup reconciliation on security-sensitive multi-tenant infrastructure; incorrect cleanup could disturb live VMs, though protection and fail-closed VFIO probes mitigate that.

Overview
Adds NVIDIA vendor VFIO as a second SR-IOV vGPU path next to mdev: hosts are classified at runtime via DiscoverVGPU() (mdev VFs first, else VFs with nvidia/creatable_vgpu_types). Vendor VFIO assigns profiles by writing current_vgpu_type, picks VFs with least-loaded-GPU placement, and releases by writing 0, with per-instance ownership until QEMU opens VFIO and reconciliation that scans open iommufd and legacy /dev/vfio/<group> handles.

API startup now runs ReconcileVGPUs with device paths protected for instances whose hypervisor PID still owns the instance socket (ListInstancesForReconcile + HypervisorProcessExists). If the instance list fails, vendor VFIO reconcile is skipped rather than running unprotected.

Instance lifecycle passes InstanceID into vGPU destroy, clears stale assignments when another live instance claims the same path, retains metadata when create-time vGPU teardown fails, and extends integration tests for sysfs assignment, stop/release, and start/reacquire on both frameworks. GPU resource reporting uses discovery instead of removed DetectHostGPUMode, with warnings on discovery/profile errors.

Reviewed by Cursor Bugbot for commit d92201d. Bugbot is set up for automated code reviews on this repo. Configure here.

@yummybomb
yummybomb force-pushed the hypeship/vendor-vfio-vgpu branch from 199b1d1 to a93d009 Compare July 28, 2026 21:32
@yummybomb
yummybomb changed the base branch from main to hypeship/generalize-vgpu-device July 28, 2026 21:32
@yummybomb
yummybomb force-pushed the hypeship/vendor-vfio-vgpu branch 4 times, most recently from d893fce to de4742a Compare July 29, 2026 15:15
@yummybomb
yummybomb force-pushed the hypeship/vendor-vfio-vgpu branch from de4742a to 18e047f Compare July 29, 2026 16:09
@yummybomb

yummybomb commented Aug 4, 2026

Copy link
Copy Markdown
Contributor Author

Trimmed over-engineering after an architectural review against the parent task (host kernel 6.8 / Ubuntu 24.04 support). Most of the removed weight came from earlier review-round additions rather than the original design:

@yummybomb
yummybomb marked this pull request as ready for review August 5, 2026 19:47
@yummybomb
yummybomb force-pushed the hypeship/vendor-vfio-vgpu branch from 7ef1f8d to bf21162 Compare August 5, 2026 19:57
Comment thread lib/instances/start.go
Comment thread integration/vgpu_test.go
@yummybomb
yummybomb force-pushed the hypeship/vendor-vfio-vgpu branch from bf21162 to 83c53ea Compare August 5, 2026 20:24
…ion test by framework

A vGPU assignment goes stale when its release succeeds but the metadata
save does not (or start fails between the release and its first save).
The in-process owner map only covers assignments created since the last
restart and the VFIO handle scan only covers VMs that have opened the
device, so after a restart a stale release could still write 0 to a VF
during another live instance's pre-open boot window. Consult live
instance metadata on every release: when another instance with a live
hypervisor process claims the same device path, drop the stale metadata
without touching the device.

The integration test asserted mdev specifics (UUID, /sys/bus/mdev path)
and failed before exercising the lifecycle on a vendor VFIO host.
Branch the assertions by framework and extend the test to cover release
on stop and reacquisition on start.

Also report an absent-but-possibly-valid profile as ambiguous instead
of 'not found': the vendor VFIO creatable catalog is capacity-dependent,
so a valid larger profile disappears while smaller ones remain.
@yummybomb
yummybomb force-pushed the hypeship/vendor-vfio-vgpu branch from 83c53ea to 79eef07 Compare August 5, 2026 20:39
Comment thread cmd/api/main.go
kill(pid, 0) returning EPERM means the process exists but cannot be
signaled. Both the reconcile protection set and the stale-claim check
treated that as dead, which could release a VF under a live VM. Reuse
processExists (EPERM-aware, zombie-filtering) in the claim check and
handle EPERM in the reconcile scan.
target, err := os.Readlink(filepath.Join(s.pciDevicesPath, vfAddress, "iommu_group"))
if os.IsNotExist(err) {
return false, nil
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

VFIO probe skips sibling check

High Severity

The vfioDeviceInUse function prematurely returns false if either the vfio-dev or iommu_group sysfs path is missing. This short-circuits the check, incorrectly marking a VF as unused even when a running QEMU VM holds an active device handle. This can lead to current_vgpu_type being cleared for an active VM during destroy or reconcile.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit ff9462f. Configure here.

Comment thread lib/instances/query.go Outdated
Comment thread lib/instances/create.go
@yummybomb
yummybomb force-pushed the hypeship/vendor-vfio-vgpu branch from ab2b0e8 to 19b9602 Compare August 6, 2026 17:08

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

There are 2 total unresolved issues (including 1 from previous review).

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 19b9602. Configure here.

Comment thread lib/instances/start.go
A bare kill(0) check treats any process that reused a stored hypervisor
PID as the owning VMM, so a reused PID could keep an orphaned VF in the
startup protected set or make a stale record look like a live claim
during release. Require the PID to own the instance's hypervisor socket
on Linux in both paths.

Also retain assignment metadata when start's vGPU rollback fails,
matching create and stop, so later release paths can still find the
device path instead of leaking the slot until the next restart.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant