feat(mocknvml): decode GPU fabric health for nvidia-smi -q - #685
Conversation
a224f82 to
0bb66a9
Compare
Every row of the nvidia-smi -q Fabric.Health block read N/A, so a consumer could not tell a healthy fabric from an unknown one and degraded-fabric handling could not be exercised at all: FabricInfo carried a HealthSummary the bridge already copied into the v3 struct, but nothing ever set it, and a zero summary makes nvidia-smi treat the whole block as unreported — which also made the existing fabric.health_mask key silently ineffective. Make fabric health a configured, decoded state. A fabric: block with no health keys means a healthy fabric, so the shipped Grace-Blackwell profiles now report Summary: Healthy and Bandwidth: Full; fabric.health faults one named condition at a time, the summary is derived from the conditions unless pinned with fabric.health_summary, and a raw fabric.health_mask stays available as an escape hatch (an explicit 0 still means "no health reported"). nvml-mock-ctl fabric-health degrades and restores it while a workload runs, authoritatively like throttle, so one condition flips its own row and only its own row. v1/v2 nvmlDeviceGetGpuFabricInfo callers are unaffected now that the summary is non-zero: the field starts exactly where a v2 buffer ends, pinned by a unit test on the struct-tail boundary alongside the shared-field offsets the v2 branch's reinterpret cast depends on. The C ABI harness exercises all three struct versions against a healthy and a faulted device. The e2e assertions read the -q -x document (NVIDIA#658) rather than the human table: the fabric health elements are decoded in schema.go, and the checks compare a whole block per GPU, so one observation covers both the degraded device and its untouched neighbours. A captured document with GPU 1 route-unhealthy is the fixture, and the pre-NVIDIA#677 capture already in testdata is the defect the check must reject. Partition Assigned has no element or row to assert: the label does not exist in the nvidia-smi the mock image bundles, which omits it for every mask value, while the 580.173.02 reference tray prints it as N/A. The mock therefore reports the field as NOT_SUPPORTED, which is what that tray reports. Signed-off-by: Giulio Calzolari <gcalzolari@nvidia.com>
a62e803 to
1e36db1
Compare
ArangoGutierrez
left a comment
There was a problem hiding this comment.
Nothing here blocks merge.
The mask encoding is the part I looked at hardest and it holds up: each condition gets its own shift/width slot, deriveFabricHealthSummary keeps degraded bandwidth at limited-capacity instead of promoting it to unhealthy, and mask == 0 still reports not-supported so the raw escape hatch keeps its old meaning. fabric_tail_test.go is the test I would have asked for - pinning healthSummary at exactly the end of a v2 struct, plus a v2 tag never classifying as v3, is what stops the now non-zero summary from writing past a v2 caller's buffer, and the shared-offset table covers the reinterpret cast the v2 branch depends on. On the e2e side, comparing a whole block per GPU out of one -q -x document with the neighbours pinned to healthy means a fault applied to the wrong device fails instead of quietly passing. One thing on the runtime injection path is worth picking up, inline - it does not change the shape of the change, so I am not holding the review for it.
| } | ||
| return map[string]any{"fabric": map[string]any{ | ||
| "health": health, | ||
| "health_summary": "auto", |
There was a problem hiding this comment.
The patch releases a pinned health_summary to "auto" but leaves health_mask alone, and resolveFabricHealthMask returns a non-nil mask outright before ever looking at the health block. On a config that still pins health_mask the injection merges cleanly and is then discarded, so the command exits 0 having done nothing; with the health_mask: 0 that every shipped profile carried until this PR removed it, the derived summary comes back NotSupported and the whole block renders N/A, which is the pre-#677 behaviour this change is fixing. Worth writing health_mask as null alongside health_summary so the same reasoning that releases the pinned summary also releases the field that outranks it.
Fixes #677
Summary
Every row of the
nvidia-smi -qFabric→Healthblock readN/A, so a consumer could not tell a healthy fabric from an unknown one, and degraded-fabric handling could not be exercised at all.FabricInfoalready carried aHealthSummarythat the bridge copied into the v3 struct, but nothing set it — and a zero summary makesnvidia-smitreat the whole block as unreported, which also made the existingfabric.health_maskkey silently ineffective.fabric:block with no health keys means a healthy fabric, so the shipped Grace-Blackwell profiles reportSummary : Healthy/Bandwidth : Full.fabric.healthfaults one named condition at a time (degraded_bandwidth,route_recovery,route_unhealthy,access_timeout_recovery,incorrect_configuration: <name>), theSummaryrow is derived from the conditions unless pinned withfabric.health_summary, and a rawfabric.health_maskstays available as an escape hatch — an explicit0still means "the driver reported no health".nvml-mock-ctl fabric-health --gpu <idx> route_unhealthydegrades a fabric under a running workload and... fabric-health --gpu <idx> healthyrestores it without areset, so the device's other overrides survive. It is authoritative likethrottle: the named conditions are set and the rest cleared, so one condition flips its own row and only its own row. Injecting a fault also releases a profile-pinned summary, since aHealthysummary next to a faulted condition is not a state hardware can be in.healthSummarystarts exactly where a v2 buffer ends, pinned by a unit test alongside the shared-field offsets the v2 branch's reinterpret cast depends on, plus a check that a v2 tag never takes the v3 path.-q -xdocument (following test(e2e): assert nvidia-smi state from -q -x instead of scraping text #658) rather than the human table. The fabric health elements are decoded inschema.go, and the checks compare a whole block per GPU, so one observation covers both the degraded device and its untouched neighbours — a fault applied to the wrong device fails rather than passes.Verification against a real
nvidia-smiBuilt the mock library from this branch, layered it onto the mock image (which bundles the real
nvidia-smi580.65.06) and ran the binary against thegb200profile config.Before:
After, with
nvml-mock-ctl fabric-health --gpu 1 route_unhealthyapplied — GPU 0 untouched, GPU 1 degraded, from the same-q -xdocument (this capture is the newtestdata/qx-gb200-fabric-degraded.xmlfixture):fabric-health healthyrestored it;no_partitionrendered<incorrect_configuration>No Partition</incorrect_configuration>withSummary : Unhealthyon the target GPU only;degraded_bandwidthrenderedBandwidth : DegradedwithSummary : Limited Capacity.Note on the
Partition Assignedacceptance criterionThere is no row or element to assert. The string
Partition Assigneddoes not exist in the 580.65.06nvidia-smithe image bundles, and neitherPARTITION_ASSIGNED_TRUEnor_FALSEin the health mask makes it appear; the reference tray prints it because it runs 580.173.02. The mock reports the field asNOT_SUPPORTED, which is what that healthy tray reports and what a newernvidia-smirenders asN/A, andschema.godeliberately decodes no element whose name this package cannot pin against a captured document. Documented indocs/configuration.md.Test plan
make test— engine health resolution written TDD; mask field shifts/widths and summary constants pinned againstgo-nvml;mockctl.FabricHealthPatchand thefabric-healthCLI covered; the XML check covered against both the new degraded fixture and the pre-[Feature]: decode GPU fabric health so nvidia-smi -q reports Summary, Bandwidth, and route state instead of N/A #677 capture already intestdata, which it must rejectpkg/gpu/mocknvml/bridge/fabric_tail_test.go— struct-tail boundary, shared-field offsets, version dispatchmake test-mocknvml-bridge— C ABI harness exercises v1/v2/v3 against a healthy and a faulted device (51 passed)make lint-fix— 0 lint issues (govulncheckreports pre-existing Go stdlib advisories; unmodifiedmainfails identically)make helm-tests— 169 tests, 15 snapshots (profile checksums refreshed)nvidia-smi580.65.06 against the built library, as abovenvidiasmi.FabricHealth), thenroute_unhealthyinjected on one GPU with its neighbours asserted unchanged, cleared, and a named misconfiguration. Gated onp.HasFabric(); selected by--label-filter="nvidia-smi"or"failure-injection".Docs
docs/configuration.mdgains a Fabric Health section (conditions table, summary derivation, raw mask, thePartition Assignedcaveat);docs/nvml-mock-ctl.mddocumentsfabric-healthwith a worked example and its reset semantics;CHANGELOG.mdentry under[Unreleased].