Detect wedged vGPU VFs from the guest and report to the health store - #435
Detect wedged vGPU VFs from the guest and report to the health store#435yummybomb wants to merge 1 commit into
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
f3bf0cc to
ff54c20
Compare
ff54c20 to
37bc190
Compare
37bc190 to
44158d3
Compare
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.
|
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. |
-->
✱ 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. |
yummybomb
left a comment
There was a problem hiding this comment.
found three issues that should be addressed before merge: two false-positive quarantine paths and the unbounded nvidia-smi wait. the targeted vGPU/store/sentinel tests pass; the current linux CI failure is the unrelated TestWaitForState_ChannelClosedOnDelete timing failure.
There was a problem hiding this comment.
revising severity: these are technically reproducible edge cases, but neither should block this PR.
-
lib/instances/vgpu_sentinel.go:244-248— non-blocking follow-up: a confirmedOKretry can be deleted only if the VF-health write fails and the same instance is reassigned between adjacent repeatedOKlines. reusing an existing pending entry would close that resilience edge, but this is a compounded failure rather than a normal-path correctness issue. -
lib/instances/vgpu_sentinel.go:343-388,lib/instances/logs.go:137-190— non-blocking follow-up: copytruncate can move a marker toapp.log.1between sentinel scans. for this to affect the one-shotOK, the active log must already exceed the 50MB rotation threshold and the successful probe must land in the narrow pre-rotation window. worth making rotation-aware, but not a merge blocker for the initial quarantine path.
both repros prove the edge cases exist; they do not justify the blocker severity I originally assigned. the core failure detection, assignment revalidation, success ordering/rescission, and bounded probe paths look sound.
6582c0e to
4d174d2
Compare
yummybomb
left a comment
There was a problem hiding this comment.
one blocking test-quality issue. the sentinel/store boundaries, fail-closed placement behavior, assignment revalidation, and persistence rollback paths otherwise look coherent in context. targeted race tests and go vet pass locally; the current linux CI also exposes the changed-test failure below.
| require.NoError(t, os.WriteFile(nvidiaSMI, []byte(script), 0o755)) | ||
| t.Setenv("COUNT_PATH", countPath) | ||
|
|
||
| probeGPUInitUntil(&gpuInitReporter{}, nvidiaSMI, time.Now().Add(time.Second), 10*time.Millisecond, 0) |
There was a problem hiding this comment.
blocker: this 10ms deadline is shorter than normal shell startup under race/loaded CI, so the nominally successful second attempt can time out too. The current Linux job failed here with actual: 3, and go test -race ... -count=100 reproduces it locally. Please make the attempt runner/clock injectable and use a deterministic fake, or otherwise give the success attempt independent scheduling margin; asserting exactly two real process launches with this deadline is flaky.
There was a problem hiding this comment.
fixed in 88f3c3d by injecting the probe attempt into the retry loop and testing the timeout/success sequence directly. go test -race ./lib/system/guest_agent -run TestProbeGPUInitRetriesAfterAttemptTimeout -count=100, the package race suite, and go vet pass.
yummybomb
left a comment
There was a problem hiding this comment.
two code-quality findings. the store/placement boundaries, persistence rollback, assignment revalidation, and tests otherwise look coherent in repo context. the new functions peak at cyclomatic complexity 16 (loadLocked / selectLeastLoadedVF); those paths remain cohesive and well-covered, so I don't see mechanical decomposition as a merge requirement.
| func (c *VGPUSentinelController) scanTarget(ctx context.Context, target vgpuSentinelTarget) { | ||
| tail := c.tails[target.instanceID] | ||
| if tail == nil || tail.vfAddress != target.vfAddress || tail.assignedAt != target.assignedAt { | ||
| tail = &vgpuSentinelTail{vfAddress: target.vfAddress, assignedAt: target.assignedAt} |
There was a problem hiding this comment.
The tail offset is process-local, but a successful report removes the only persistent dedup record. After FAILED -> OK, restarting hypeman starts at offset 0, records the historical failure again, increments init_failures_total, then clears it again on the historical OK. With vf_quarantine_threshold: 1, replay also emits a fresh quarantine/rescind and increments both counters on every restart; if that replayed clear fails, a healthy VF stays quarantined. Please retain a bounded last-resolved assignment/watermark per VF (or coalesce a replay before mutating state) and add a restart regression test.
| } | ||
|
|
||
| reader := bufio.NewReaderSize(f, vgpuSentinelMaxLineBytes) | ||
| for { |
There was a problem hiding this comment.
non-blocking: please thread ctx into scanSentinelLog and check cancellation between reads (and target scans). On the first pass after controller/process start every assignment begins at byte 0, so a host with many logs near the 50 MB rotation limit can keep Run inside this loop and hold the process-level errgroup.Wait well after shutdown was requested.
yummybomb
left a comment
There was a problem hiding this comment.
reviewed the change against hypeship/vendor-vfio-vgpu and traced the sentinel through guest output, host log scanning, durable health state, placement, and admission. the persistence rollback and assignment-identity handling are coherent, but the serial marker is not actually source-authenticated, and timed-out probes can overlap in the exact uninterruptible-ioctl case this code targets.
leaving the first finding as a blocker; github does not allow this account to request changes on its own PR.
|
|
||
| // Require a standalone guest-agent line so echoed marker text cannot count against a VF. | ||
| var ( | ||
| vgpuSentinelFailedPattern = regexp.MustCompile(`^\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2} \[guest-agent\] (HYPEMAN-GPU-INIT-FAILED ts=\S+ nvrm="NVRM: [^"\r\n]*RmInitAdapter failed![^"\r\n]*")\r?\n?$`) |
There was a problem hiding this comment.
blocker: a standalone line does not establish that the guest agent wrote it. In exec mode the customer entrypoint's stdout/stderr is wired directly to the same serial console (lib/system/init/mode_exec.go:132-133), so an ordinary workload can print a complete line matching this regex without root or /dev/kmsg access. Repeating that across assignments can quarantine healthy VFs, and the runbook's statement that only a root guest can forge this is therefore incorrect. Please carry the report over a channel whose source the host can distinguish (for example a dedicated guest-agent RPC/transport), or otherwise authenticate the source; matching the log shape is not sufficient here.
| case err := <-done: | ||
| return err | ||
| case <-timer.C: | ||
| _ = cmd.Process.Kill() |
There was a problem hiding this comment.
this bounds how long the caller waits, but not the attempt's lifetime. If nvidia-smi is stuck in the uninterruptible ioctl described above, Kill does not make cmd.Wait return; the process and waiter goroutine remain live while probeGPUInitUntil launches another attempt after 15s. The 10-minute loop can therefore accumulate many concurrent stuck probes on the VF. Can we retain the in-flight attempt and avoid launching another until it is reaped (or stop retrying once an attempt cannot be killed), so there is at most one outstanding nvidia-smi?
88f3c3d to
b901282
Compare
The guest agent watches /dev/kmsg for kernel-facility NVRM RmInitAdapter-failed records and emits a standalone HYPEMAN-GPU-INIT-FAILED marker (repeated so printk splits cannot lose it), and probes driver init at boot with nvidia-smi -L when present, emitting a terminal HYPEMAN-GPU-INIT-OK on success. A hung probe attempt is bounded to 30 seconds. A host sentinel controller tails each vendor VFIO instance's app.log, matches only complete standalone markers, revalidates the assignment before reporting, and feeds failures and successes into the VF health store. Start archives the previous boot's app log before persisting a new assignment so stale markers cannot count against the new VF, and create allocates the vGPU immediately before metadata persistence to minimize the unpersisted window. vGPU reconciliation preserves assignments when hypervisor liveness is uncertain and records the condition in logs and a metric. GPU.md gains the detection and sentinel documentation.
b901282 to
1fd824a
Compare
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 3 potential issues.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Want higher recall? High effort reviews run extra passes and find more bugs. A team admin can switch effort levels in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 1fd824a. Configure here.
| // Require a standalone guest-agent line so echoed marker text cannot count against a VF. | ||
| var ( | ||
| vgpuSentinelFailedPattern = regexp.MustCompile(`^\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2} \[guest-agent\] (HYPEMAN-GPU-INIT-FAILED ts=\S+ nvrm="NVRM: [^"\r\n]*RmInitAdapter failed![^"\r\n]*")\r?\n?$`) | ||
| vgpuSentinelOKPattern = regexp.MustCompile(`^\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2} \[guest-agent\] (HYPEMAN-GPU-INIT-OK ts=\S+)\r?\n?$`) |
There was a problem hiding this comment.
Workloads can forge GPU sentinel markers
High Severity
The host matcher accepts any complete standalone line in app.log. In exec mode the workload's stdout and stderr share that serial console, so an unprivileged guest can print a matching HYPEMAN-GPU-INIT-FAILED or HYPEMAN-GPU-INIT-OK line. Repeating the failure marker across assignments can quarantine healthy VFs; printing OK after a real failure can keep a wedged VF in rotation. The docs still describe this as root-only, which is not true for exec mode.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit 1fd824a. Configure here.
| assignedAt string | ||
| offset int64 | ||
| skippingLongLine bool | ||
| } |
There was a problem hiding this comment.
Restart replays cleared failure markers
Medium Severity
Tail offsets live only in process memory. After FAILED then OK, a successful clear deletes the assignment's health-store dedup record. On hypeman restart the log is read from byte 0, so the historical failure is recorded again and init_failures_total increments. With vf_quarantine_threshold of 1 this also re-quarantines; if the replayed clear then fails, a healthy VF stays quarantined.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit 1fd824a. Configure here.
| case <-timer.C: | ||
| _ = cmd.Process.Kill() | ||
| return context.DeadlineExceeded | ||
| } |
There was a problem hiding this comment.
Hung GPU probes accumulate concurrently
Medium Severity
runGPUProbeAttempt returns after Kill without waiting for cmd.Wait. On a wedged VF, nvidia-smi can sit in an uninterruptible ioctl where Kill does not reap it, and probeGPUInitUntil starts another attempt every 15s for up to 10 minutes. That can leave many concurrent stuck probes on the same VF.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit 1fd824a. Configure here.


Summary
Top half of the wedged-VF work, stacked on #462 (the VF health store, placement exclusion, admission, and
/resourcesfields). This PR adds the detection path that feeds that store./dev/kmsgfor kernel-facilityNVRM: ... RmInitAdapter failed!records and emits a standaloneHYPEMAN-GPU-INIT-FAILEDmarker. It also probes initialization withnvidia-smi -Lwhen available and emitsHYPEMAN-GPU-INIT-OKon success.app.logand reports failures and successes into the VF health store from Quarantine unhealthy vGPU VFs via a persisted health store #462.Safety and failure handling
HYPEMAN-GPU-INIT-OK.nvidia-smiattempt is bounded to 30 seconds without waiting indefinitely for a process stuck in an uninterruptible ioctl.Observability
hypeman_instances_vgpu_sentinel_init_failures_totalhypeman_instances_vgpu_sentinel_quarantines_totalhypeman_instances_vgpu_quarantined_vfshypeman_instances_vgpu_vf_health_store_unavailablehypeman_instances_vgpu_reconcile_liveness_uncertain_totallib/devices/GPU.mdgains the detection and sentinel documentation.Out of scope
An operator force-cycle endpoint. Recovery remains the documented manual DCGM quiesce, SR-IOV cycle, state edit, restart, and verification flow.
Testing
Passed:
This branch's tree is byte-identical to the previously reviewed head (
88f3c3d) — the split into #462 + this PR rewrites history only, not content. The underlying wedge signal and manual recovery sequence were previously validated on L40S hardware. A live end-to-end run of the guest watcher and host controller is still required before merge, including two failed assignments and the success path.Note
Medium Risk
Changes GPU placement safety (quarantine inputs), instance create/start ordering for vGPUs, and background controllers on production API hosts; incorrect marker matching or assignment races could mis-quarantine VFs or miss wedges, though tests and fail-closed reconcile mitigate this.
Overview
Adds an end-to-end path to detect guest NVIDIA driver init failures on vendor VFIO vGPUs and feed the existing VF health / quarantine store.
The guest agent (when an NVIDIA PCI device is present) tails
/dev/kmsgfor kernelRmInitAdapter failed!lines and optionally probes with boundednvidia-smi -L, emitting strict standaloneHYPEMAN-GPU-INIT-FAILED/HYPEMAN-GPU-INIT-OKlines intoapp.log. Success is terminal so later failure noise is suppressed.A new
VGPUSentinelControllerruns in the API process on vendor VFIO hosts: every few seconds it tails each assigned instance’sapp.log, matches only full guest-agent marker lines, confirms the VF assignment, and callsReportVFInitFailure/ReportVFInitSuccess. It exposes sentinel and quarantine metrics and exits cleanly on non–vendor-VFIO hosts.Lifecycle hardening supports trustworthy attribution: vGPU allocation on create moves to just before metadata save (after disks/network/config); create and start archive the prior boot’s app log before a new assignment (start fails closed for GPU instances if archive fails); GPU init markers are filtered from streamed app logs. vGPU reconcile now surfaces ambiguous hypervisor liveness (warn +
hypeman_instances_vgpu_reconcile_liveness_uncertain_total) instead of silently treating uncertainty as live. Copy and error text for failed-create retention stubs are clarified.lib/devices/GPU.mddocuments the detection flow, metrics, and sentinel behavior.Reviewed by Cursor Bugbot for commit 1fd824a. Bugbot is set up for automated code reviews on this repo. Configure here.