Detect and quarantine wedged vGPU VFs - #435
Conversation
d312338 to
7d54fb9
Compare
7d54fb9 to
9a90223
Compare
9a90223 to
0ff16d1
Compare
0ff16d1 to
3db5546
Compare
3db5546 to
b35501a
Compare
b3bf925 to
55a0d6f
Compare
55a0d6f to
ee1b160
Compare
a5dc229 to
fed19c4
Compare
7bd4f52 to
67b2b24
Compare
67b2b24 to
0662ad9
Compare
0662ad9 to
f3bf0cc
Compare
Detection moves from host-side matching of the raw NVRM kernel line to an explicit guest-to-host report: the guest agent watches /dev/kmsg for the driver's RmInitAdapter failure and emits a HYPEMAN-GPU-INIT-FAILED marker over the established sentinel-marker channel, which the controller convicts on. The raw kernel line is no longer matched, so detection now requires the guest agent; images without it do not report. Quarantine hardening in the same pass: - The conviction brake pauses instead of drops: a suppressed conviction leaves the tail open and the agent's re-emission retries it once the window clears. - A match on an already-quarantined VF is not a new conviction: no brake accounting, no metric, no wedge-count inflation on controller restarts. - Tails reset when the instance acquires a new assignment, so a finished tail from a previous boot or VF cannot suppress scanning the next one. - A vf-health state file that fails to load refuses mutations (and retries the load) instead of letting the next conviction clobber every previously persisted quarantine. - Oversized unterminated log lines are skipped instead of re-buffered on every scan. - The controller idles on hosts without the vendor VFIO framework. GPU.md: DCGM quiesce is now an ordered step of the recovery sequence, and clearing vf-health.json documents the immediate-restart requirement.
Review fixes on the quarantine layer: - start now archives the previous boot's serial log before persisting the new vGPU assignment. The sentinel keys its tail on the assignment epoch, so the old ordering let a scan replay the previous boot's wedge report against the freshly assigned VF. A failed archive is fatal for GPU instances instead of a warning. - placement and profile availability refuse to run when the VF health state file exists but cannot be loaded, instead of treating the empty in-memory set as healthy and returning every quarantined VF to rotation. The load is retried on each attempt, so a repaired file self-heals. - a conviction whose persist fails is rolled back from memory: keeping it made the next report look like a repeat conviction, ending retries with nothing on disk. - the marker pattern requires the full shape through the quoted NVRM payload; a truncated or payload-less marker echoed by an exec command no longer matches. - unreadable instance metadata in the sentinel target listing logs a warning instead of silently shrinking detection coverage. - GPU.md recovery runbook unwound a circularity: placement excludes quarantined VFs and there is no VF-pin API, so the entry is cleared before the verification boot; the sentinel re-quarantines automatically if the cycle did not cure the VF.
- Name the unavailable VF health store in the GPU admission error instead of reporting slot exhaustion with used < total. - Load the VF health store in devices.NewManager so a manager built outside wire cannot run placement against an empty, non-persisting quarantine set. - Inline the single-caller placement-lock wrapper and the locked checked-addresses variant; AllocatableVFs no longer reaches into store internals. - Deduplicate the sentinel controller's probe block; distinguish the stat-failure and load-failure warn messages. - Restore the fail-closed rationale comment on listMetadataFilesStrict and document the archive-before-assign detection window in GPU.md.
f3bf0cc to
ff54c20
Compare
ff54c20 to
37bc190
Compare
37bc190 to
44158d3
Compare
…hypeship/vgpu-wedge-quarantine # Conflicts: # lib/instances/storage.go
sjmiller609
left a comment
There was a problem hiding this comment.
Overall looks good, a few small feedbacks and improvements we should consider.
- Versioning: use a versioned envelope for future counters and recovery state:
{"version": 1, "records": [...]}-
Failure threshold: currently 1 detection quarantines a VF. Should this be configurable? e.g. quarantine after failures from N distinct VM assignments, default 2. Also we could have a sucess reset: successful GPU-initialization sentinel that clears the VF’s failure count. I think we should do this if there's any case where this error happens even when the VF is not wedged.
-
Resources API: expose allocatable and quarantined slots so reported capacity matches admission:
"total_slots": 64,
"used_slots": 5,
+"allocatable_slots": 57,
+"quarantined_slots": 2 "profiles": [{
"name": "NVIDIA L40S-12Q",
- "available": 59
+ "available": 57
}]-
GPU-less hosts: exit the controller when no GPU framework exists; continuous five-second discovery is unnecessary.
-
Terminology: replace “convict/convictions” terminology, that seems confusing.
-
Cleanup follow-up: acknowledged that automated GPU reset and cleanup are intentionally deferred; this PR handles detection and quarantine only.
Replace single-report quarantine with a per-assignment failure tally: a VF is quarantined once failures from gpu.vf_quarantine_threshold distinct instance assignments accumulate (default 2), so a one-off transient init failure no longer removes a healthy VF. The guest agent now probes driver init at boot with nvidia-smi when present - forcing detection on wedged VFs without waiting for the workload - and emits a HYPEMAN-GPU-INIT-OK marker on success, which clears the VF's failure tally (never a quarantine). The vf-health.json state file gains a versioned envelope. The sentinel controller exits on hosts without a vGPU framework instead of re-probing forever, and conviction wording is replaced with failure/quarantine terminology, splitting the metric into vgpu_sentinel_init_failures_total and vgpu_sentinel_quarantines_total.
Reported capacity now matches admission control: allocatable_slots is the free-and-healthy count admission uses, quarantined_slots counts VFs currently quarantined on the host.
|
Addressed the review feedback (commits be50050, 7b24855):
Still pending before merge: the live end-to-end run on the dev GPU host, which should now cover the threshold path (two victim boots) and the OK probe. |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 2 potential issues.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 7b24855. Configure here.
-->
✱ stlc build✅ go code · compare
✅ python code · compare
✅ typescript code · compare
Diagnostics: ❗ 0 new / 1 total error, 💡 0 new / 5 total note
Build metadata
This comment is auto-generated by stlc and is kept up to date as you push. |
Removed comment about VFQuarantineThreshold in GPUConfig.
A recorded init failure no longer stops the assignment's scan: the boot probe can emit a failure before a slow-but-healthy init succeeds, and the later HYPEMAN-GPU-INIT-OK must clear the tally or two such boots would quarantine a healthy VF. The store's per-assignment dedup makes the continued rescans free. The OK marker is emitted once per boot and the scan offset moves past it, so a clear whose persist fails is now remembered on the tail and retried on the next scan.
A tally clear now requires a live matching assignment: the clear keys on the VF address alone, so an OK marker scanned after the instance released the VF could erase a failure just recorded by the VF's next holder. Failures still proceed on released assignments - the wedge evidence stands after the victim stops. The nvidia-smi init probe gets a 30s per-attempt timeout (plus a WaitDelay for probes hung in uninterruptible ioctls, the usual hang mode on a wedged VF); a hung invocation previously blocked forever and the 10-minute probe deadline was never reached. gpu.vf_quarantine_threshold below 1 is now a startup validation error instead of silently keeping the default, and the gpu section is documented in config.example.yaml.

Summary
Killing the process holding a vendor VFIO VF during guest driver init (~8s window) silently corrupts the VF: every later boot on it fails while sysfs, NVML, and the vGPU plugin all look healthy. #321 removed hypeman's own kill triggers; this layer detects the wedges external kills (OOM, operator, deploy restarts) still cause, and takes the VF out of placement.
Detection — guest agent report, no lifecycle change. On a wedged VF the guest driver is present, trying, and failing: its kernel loops
NVRM: ... RmInitAdapter failed!in the guest kernel log. The guest agent watches/dev/kmsgfor that line and reports it as aHYPEMAN-GPU-INIT-FAILEDmarker — the same guest→host channel as the existingHYPEMAN-*markers, landing in the instance'slogs/app.log. The agent also probes driver init at boot withnvidia-smi -Lwhen the binary is present: the device open runs RmInitAdapter, so on a wedged VF the probe itself triggers the failure line without waiting for the workload to touch the GPU, and on success it emits aHYPEMAN-GPU-INIT-OKmarker. A newVGPUSentinelControllertailsapp.logfor every instance holding a vendor VFIO assignment and records failures on the marker; the full marker shape is required since a bare token could appear in echoed exec command lines. The agent only watches when an NVIDIA PCI function is present, throttles re-emission to one per 30s, and matches the full kernel-line shape, never the driver-build-specific(0x22:0x65:884)tuple. A no-driver image never produces the kernel line and can never trigger a failure record. An image that skips the guest agent does not report — v1 accepts that coverage bound; the raw kernel line still reachesapp.logfor manual diagnosis. The controller exits on hosts without a vGPU framework instead of re-probing every 5s.Quarantine. Failure reports persist to
<data-dir>/gpu/vf-health.json(versioned envelope{"version": 1, "records": [...]}, keyed by VF address, survives restarts) under the placement lock, tallied per instance assignment. A VF is quarantined once failures accumulate fromgpu.vf_quarantine_thresholddistinct assignments (default 2), so a one-off transient does not remove a healthy VF while a genuinely wedged VF — which fails every boot — is out after its second victim. AHYPEMAN-GPU-INIT-OKmarker clears the VF's failure tally; it never clears a quarantine, which stays a manual recovery. One wedge produces one failure record per assignment no matter how many times the marker repeats or the controller restarts. The store fails closed on an unreadable state file: mutations are refused, and vGPU placement and advertised availability are disabled (with load retried on each attempt) rather than treating the empty in-memory set as healthy and returning quarantined VFs to rotation. A report whose persist fails is rolled back from memory so the next scan retries it. Start archives the previous boot's serial log before persisting a new assignment (fatal on failure for GPU instances), so a scan can never replay the prior boot's report against a freshly assigned VF. The threshold softens but does not prevent host-wide quarantine from a systemic non-wedge init failure (e.g. a driver-mismatch rollout emitting the same line on every VF): such changes are validated on a test host first, and the init-failures counter is the early alerting signal if one gets through.Placement.
Resources API.
GET /resourcesnow reportsallocatable_slots(the free-and-healthy count admission control uses; 0 while VF health state is unavailable) andquarantined_slotsalongsidetotal_slots/used_slots, so reported capacity matches admission.Observability. Below-threshold failures log at warn and count in
hypeman_instances_vgpu_sentinel_init_failures_total; quarantines log at error and count inhypeman_instances_vgpu_sentinel_quarantines_total; thehypeman_instances_vgpu_quarantined_vfsgauge andhypeman_instances_vgpu_vf_health_store_unavailable(1 while the persisted state file cannot be loaded and placement is failing closed) round it out.GPU.md documents the new behavior and the manual recovery flow (DCGM quiesce → SR-IOV cycle → restart services → verification boot → clear the VF's entry in
vf-health.json→ restart hypeman immediately).Out of scope (follow-up layer)
An operator force-cycle endpoint (DCGM quiesce →
sriov-manage -d/-e→ verification boot → clear quarantine). Until then recovery stays the documented manual runbook.Testing
go test -race ./lib/devices ./lib/paths ./lib/providers ./lib/resources ./lib/system/guest_agent ./cmd/api/configand the vGPU-relatedlib/instancestests pass locally;go vetclean. The fulllib/instancessuite fails only on environment-dependent VM/network tests, identically on the unmodified base.app.log) and the recovery sequence were validated on real L40S hardware by deliberately wedging VFs; the guest-agent watcher and the controller have not yet run against a live wedge end-to-end — that run should happen on the dev GPU host before this merges, and should now also exercise the threshold path (two victim boots) and the nvidia-smi OK probe.Note
High Risk
Changes vGPU placement, admission control, and persistent VF health state on GPU hosts; misconfiguration or systemic false positives could quarantine capacity or block all vGPU placement until state is repaired.
Overview
Adds automatic detection and quarantine for vendor VFIO vGPU VFs that look healthy on the host but fail guest driver init (
RmInitAdapter), so one wedged VF no longer absorbs every placement.The guest agent watches
/dev/kmsgand (whennvidia-smiexists) probes at boot, writing strictHYPEMAN-GPU-INIT-FAILED/HYPEMAN-GPU-INIT-OKlines toapp.log. A newVGPUSentinelControllertails those logs for vendor VFIO instances, persists per-VF failure tallies invf-health.json, and quarantines aftergpu.vf_quarantine_thresholddistinct instance assignments (default 2). OK markers clear below-threshold tallies but never lift quarantine.Placement skips quarantined VFs, deprioritizes parent GPUs that have quarantined siblings, and randomizes tie-breaks among free VFs (replacing lowest-address bias). Corrupt or unreadable VF health state fails closed: placement and advertised availability are disabled until the file is fixed. GPU instance start archives the prior boot’s
app.logbefore assigning a VF so stale markers cannot count against a new assignment.GET /resourcesexposesallocatable_slotsandquarantined_slots; admission/reservation now keys off allocatable slots (nottotal_slots - used_slots), with explicit errors when placement is disabled. Metrics cover init failures, quarantines, quarantined VF count, and store-unavailable state. GPU.md and config examples document recovery (DCGM quiesce → SR-IOV cycle → clearvf-health.json+ restart).Reviewed by Cursor Bugbot for commit 1ae633a. Bugbot is set up for automated code reviews on this repo. Configure here.