feat(detector): align the detect path with the operator, split information from usage, and report workload exit status - #17
Conversation
There was a problem hiding this comment.
Pull request overview
Aligns gpustack_runtime.detector behavior with the gpustack-operator detector path, separating inventory (“info”) from metrics (“usage”), removing virtual/physical-index switching, and improving workload termination diagnostics across deployers (Docker/Podman/Kubernetes).
Changes:
- Split device detection into
detect_info()(inventory) anddetect_usage()(metrics), with UUID-based merging and MIG instances kept underappendix["mig_devices"]. - Retire vGPU/virtual-card classification and physical-index priority; move driver-physical numbering into
appendix(e.g.,minor_number,physical_id). - Add workload exit reporting (
WorkloadStatus.exits), injectCUDA_DEVICE_ORDER=PCI_BUS_IDwhen containers can see all NVIDIA devices, and improve Kubernetes failure reporting by reading Pod Events (plus RBAC).
Reviewed changes
Copilot reviewed 67 out of 67 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| tests/gpustack_runtime/detector/test_samples.py | Guards sample JSON against Device schema and retired appendix keys. |
| tests/gpustack_runtime/detector/test_mthreads.py | Adds fake driver + tests for MThreads info/usage split and UUID merge. |
| tests/gpustack_runtime/detector/test_mig_devices.py | Updates MIG indexing expectations and removes vGPU appendix usage. |
| tests/gpustack_runtime/detector/test_metax.py | Adds fake driver + tests for MetaX info/usage split and UUID merge. |
| tests/gpustack_runtime/detector/test_iluvatar.py | Adds fake binding tests for Iluvatar v2→v1 memory fallback + CDI minor-number addressing. |
| tests/gpustack_runtime/detector/test_detector_types.py | Adds tests for Detector ABC split + merge_devices_usage semantics and PCI name lookup. |
| tests/gpustack_runtime/detector/test_detector_cli.py | Adds CLI tests for --no-usage and JSON/table rendering rules. |
| tests/gpustack_runtime/detector/samples/topology_output_thead_ppu.json | Refreshes T-Head topology sample output. |
| tests/gpustack_runtime/detector/samples/topology_output_nvidia_rtx4090_48g.json | Adds NVIDIA RTX 4090 (48G) topology sample. |
| tests/gpustack_runtime/detector/samples/topology_output_amd_rx7800xt.json | Updates AMD RX7800XT topology sample for multi-device output. |
| tests/gpustack_runtime/detector/samples/README.md | Documents which samples are captured vs hand-aligned and device-node-relevant facts. |
| tests/gpustack_runtime/detector/samples/detect_output_nvidia_rtx5090d.json | Refreshes RTX 5090D detect output; replaces vgpu with mig and adds minor. |
| tests/gpustack_runtime/detector/samples/detect_output_nvidia_rtx4090d.json | Replaces vgpu appendix key with mig. |
| tests/gpustack_runtime/detector/samples/detect_output_nvidia_rtx4090_48g.json | Adds RTX 4090 (48G) detect output sample. |
| tests/gpustack_runtime/detector/samples/detect_output_nvidia_rtx4080super.json | Replaces vgpu appendix key with mig. |
| tests/gpustack_runtime/detector/samples/detect_output_nvidia_h200.json | Replaces vgpu appendix key with mig. |
| tests/gpustack_runtime/detector/samples/detect_output_nvidia_h100.json | Replaces vgpu appendix key with mig. |
| tests/gpustack_runtime/detector/samples/detect_output_nvidia_h100_mig.json | Converts MIG instances into appendix["mig_devices"] under their parent cards. |
| tests/gpustack_runtime/detector/samples/detect_output_nvidia_gb10.json | Replaces vgpu appendix key with mig. |
| tests/gpustack_runtime/detector/samples/detect_output_metax_c500.json | Removes vgpu appendix key from MetaX sample. |
| tests/gpustack_runtime/detector/samples/detect_output_hygon_k100ai.json | Removes vgpu appendix key from Hygon sample. |
| tests/gpustack_runtime/detector/samples/detect_output_ascend_910b2.json | Refreshes Ascend sample and adds physical_id appendix key. |
| tests/gpustack_runtime/detector/samples/detect_output_ascend_310p3.json | Removes vgpu appendix key from Ascend 310P3 sample. |
| tests/gpustack_runtime/detector/samples/detect_output_amd_rx7800xt.json | Refreshes AMD RX7800XT sample; updates names/versions and adds second device. |
| tests/gpustack_runtime/detector/samples/detect_output_amd_mi308x.json | Removes vgpu appendix key from AMD MI308X sample. |
| tests/gpustack_runtime/detector/samples/detect_output_amd_mi300x.json | Removes vgpu appendix key from AMD MI300X sample. |
| tests/gpustack_runtime/deployer/test_workload_status.py | Adds serialization/back-compat tests for WorkloadStatus.exits. |
| tests/gpustack_runtime/deployer/test_visible_devices_ordering.py | Adds tests for CUDA device ordering injection behavior across deployers. |
| tests/gpustack_runtime/deployer/test_podman_status.py | Adds Podman exit parsing/state_message tests using shared exit parser behavior. |
| tests/gpustack_runtime/deployer/test_docker_status.py | Adds Docker exit parsing/state_message tests including timestamp normalization. |
| ruff.toml | Adds pycndev binding directory to Ruff ignore list. |
| mkdocs.yml | Adds Detector Parity doc to MkDocs navigation. |
| gpustack_runtime/envs.py | Removes GPUSTACK_RUNTIME_DETECT_PHYSICAL_INDEX_PRIORITY. |
| gpustack_runtime/detector/pydcmi/init.py | Adds Ascend DCMI v1/v2 struct/call fallbacks used for older drivers. |
| gpustack_runtime/detector/mthreads.py | Implements detect_info/detect_usage split and removes virtRole/vGPU filtering. |
| gpustack_runtime/detector/metax.py | Implements detect_info/detect_usage split and removes virtualization-mode filtering. |
| gpustack_runtime/detector/iluvatar.py | Implements split, adds v2→v1 memory fallback, moves minor number into appendix. |
| gpustack_runtime/detector/hygon.py | Implements split; prefers PCI-ID-derived marketing name over driver chip name. |
| gpustack_runtime/detector/cambricon.py | Replaces cnmon shell-out with pycndev binding; implements split + Neuware version parsing. |
| gpustack_runtime/detector/amd.py | Implements split; adds PCI ID name lookup and factors UUID derivation helper. |
| gpustack_runtime/detector/utils.py | Adds pci.ids parsing + get_pci_device_name; removes physfn-based vGPU classification helper. |
| gpustack_runtime/detector/types.py | Makes info/usage abstract, adds merge_devices_usage, updates Device.index semantics. |
| gpustack_runtime/detector/init.py | Threads usage= through detect_devices; avoids usage query for topology. |
| gpustack_runtime/deployer/podman.py | Adds exit collection + CUDA device ordering injection for all-device/privileged requests. |
| gpustack_runtime/deployer/kuberentes.py | Adds exit collection, image-pull failure → Failed, and Pod Event message enrichment + ordering injection. |
| gpustack_runtime/deployer/docker.py | Adds exit collection + CUDA device ordering injection for all-device/privileged requests. |
| gpustack_runtime/deployer/cdi/thead.py | Clarifies T-Head device node naming is by enumeration index, not minor number. |
| gpustack_runtime/deployer/cdi/iluvatar.py | Uses appendix["minor_number"] for device node path, falling back to Device.index. |
| gpustack_runtime/deployer/cdi/ascend.py | Uses appendix["physical_id"] for /dev/davinciN, skipping devices without it. |
| gpustack_runtime/deployer/types.py | Adds WorkloadStatusExit + shared Docker/Podman exit parser + ordering mapping. |
| gpustack_runtime/cmds/detector.py | Adds --no-usage and omits/unrenders usage fields when metrics weren’t queried. |
| deploy/manifests/kubernetes.yaml | Adds RBAC get/list permission for core events to support Pod Event diagnostics. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
There was a problem hiding this comment.
Code Review
This pull request aligns the runtime detector with the operator's device manager and introduces structured workload exit status reporting. Key changes include splitting the device detection process into inventory (detect_info) and metrics (detect_usage) queries, allowing callers to opt out of metrics queries via a new --no-usage CLI flag. It refactors all nine vendor detectors to implement this split, resolving various parity gaps (such as NVIDIA GDDR ECC capacity restore, a new pycndev binding for Cambricon, and pci.ids name resolution for AMD/Hygon). Additionally, the deployers (Docker, Podman, and Kubernetes) are updated to report structured container exit information (WorkloadStatusExit), handle Kubernetes image-pull failures as failed states enriched with Pod Event messages, and automatically pin CUDA_DEVICE_ORDER=PCI_BUS_ID for NVIDIA workloads seeing all host devices. Comprehensive unit tests and documentation have been added to support these changes. I have no feedback to provide as there are no review comments.
- Drop the GDDR ECC capacity restore. The operator adds back the ~1/16 that ECC parity carves out, but that figure is display-only there and takes no part in allocation, while `memory` here does: restoring it made `memory - memory_used` over-report free space and would over-commit every GDDR card with ECC enabled. - Keep the divergence deliberate and stated at the call site. - The test now pins the opposite contract, including the GDDR-with-ECC case where the operator would restore. Addresses PR review on #17. Signed-off-by: thxCode <thxcode0824@gmail.com>
- The operator asks libdrm for the board's marketing name between the HSA product name and the ASIC name. That step was left unreachable because pyamdgpu bound no such call; bind it and reach for it, on AMD and Hygon alike, so the name precedence matches the operator's. - The call answers with a pointer into libdrm's own table, so declare the return type, and report an unnamed board as empty rather than an error: libdrm answers NULL for a device id its table does not carry, and the caller simply falls through to its next source. - The device is opened only when pci.ids and HSA both found nothing, which is the rare path, so the common one costs no extra open. - Verified against real libdrm on the AMD host: both cards answer "AMD Radeon RX 7800 XT". Addresses PR review on #17. Signed-off-by: thxCode <thxcode0824@gmail.com>
- Rename the MIG merge's loop variable so the copy reads as what it is, usage entry into appendix entry, which a reviewer read as a self-copy. - Record the two decisions PR review changed: NVIDIA memory stays what the driver reports, and the libdrm marketing-name step is implemented rather than deferred. Addresses PR review on #17. Signed-off-by: thxCode <thxcode0824@gmail.com>
…ndex switch
- add `Detector.detect_info` / `detect_usage` / `detect(usage=True)`, both
halves abstract, so a vendor cannot half implement the split and have
`detect` look like it measured something
- add `merge_devices_usage`, joining usage onto information by UUID, MIG
entries in `appendix["mig_devices"]` included, mirroring the operator's
`MonitorAccelerator`, which returns a UUID-keyed metrics list consumers join
by identity, never by index
- drop an ambiguous UUID from that join instead of keying a dict by it, which
collapsed last-wins and wrote one card's utilization, memory, temperature
and power onto every card answering the same id
- keep the information query's `memory_status` when the usage entry left it
UNKNOWN: no vendor's health helper returns UNKNOWN, so such an entry never
read health, and writing it erased a verdict the CLI renders as ERR
- thread `usage` through `detect_devices`, detecting topologies without it, and
keep the inventory when only the usage query fails -- a raising detector is
logged and skipped, so one failed metric call reported a host of healthy
cards as none
- drop `GPUSTACK_RUNTIME_DETECT_PHYSICAL_INDEX_PRIORITY`, recording the
driver's physical number in the appendix instead, as the operator does, and
keep `/dev/davinci{N}` and `/dev/iluvatar{N}` on that number so no device
node path changes meaning
- add `get_pci_device_name`, mirroring the operator's pci.ids lookup, and
delete `get_physical_function_by_bdf`, whose last caller goes with the vGPU
classification
- add `detect --no-usage`, rendering the unmeasured table columns as N/A and
omitting the five usage-only JSON keys, MIG instances included, rather than
emitting a zero that reads like a measurement
Signed-off-by: thxCode <thxcode0824@gmail.com>
…it its usage query - prefer the v2 memory structure through its packed version constant, probed so an older binding stays on v1 without raising the dependency floor - report `memory` as the driver reports it, diverging from the operator's GDDR ECC capacity restore on purpose: the operator's corrected figure is display only and takes no part in allocation, while this one does, so restoring the ~1/16 that ECC parity carves out of a narrow-bus card would make `memory - memory_used` over-report free space and over-commit the card - move GPM, utilization, temperature and used power into `detect_usage`, MIG instance entries included - suppress a failing MIG read per instance rather than per card, so one instance refusing a read no longer aborts the loop and erases every later instance from the inventory, or leaves it reading as idle - delete the vGPU sniff and the `vgpu` appendix key: whole cards only - record `appendix["minor_number"]`, keep the host-memory fallback for a zero total as a deliberate divergence for WSL and iGPU tolerance, and say at the PCI call site why it stays unversioned Signed-off-by: thxCode <thxcode0824@gmail.com>
- skip a unit whose DCMI type is not NPU, keeping one whose type cannot be read, as the operator does - bind the V1 die, PCIe and memory entry points and fall back to them, so a driver exposing only those still yields a device instead of none, pinning both new structs' size and field offsets against the driver's header - derive used memory from the utilization percent on the V2 memory path, rather than reporting every card as fully used the way the operator does there - drop the vNPU branch and the `vgpu` appendix key, and with them the `/dev/vdavinci` device node path - move utilization, temperature and power into `detect_usage` - drop a device whose physical id DCMI will not answer, as the operator's ascend/device.go does on a failed GetPhysicalID: /dev/davinciN is numbered by that id, so such a device cannot be addressed at all - stop the CDI generator standing `Device.index` in for a missing physical id -- the index is the logic id, a different number, so the fallback could hand a container another NPU's node. The physical id is now an invariant of a detected Ascend device. Signed-off-by: thxCode <thxcode0824@gmail.com>
…g cnmon - add `pycndev`, a hand-written ctypes binding over libcndev following the pydcmi pattern: 15 structs and the calls a Cambricon detector needs, plus the operator's NEUWARE_HOME search order and its two synthetic error codes - pin every struct's size and field offsets, the API version, the error codes and the device types against the driver's own header, since no hardware in CI can catch a layout or constant mistake later, and stamp the temperature structure's IN version field, which the header requires and the operator's binding omits - keep the module inert at import: a missing library raises the binding's own error with the loader's OSError chained onto it, never a bare OSError, so a present but unloadable library stops reading as an absent one - hold the load lock across the whole check-then-initialize sequence, as the operator's binding does with sync.Once: checking `_libInitialized` outside it let two concurrent first callers both reach cndevInit, and the loser's already-initialized error was cached as the library's permanent state. The lock is reentrant because `_cndevGetFunctionPointer` takes it too, and the init flags are cleared only after the driver has let go of what cndevInit allocated. - report no identity rather than manufacturing a bare "MLU-" from an empty field, which is exactly the ambiguous uuid the usage join has to drop - derive overallHealth in the V1 health fallback instead of leaving it at zero, which is CNDEV_HEALTH_RESULT_PASS - replace the `cnmon info -e -m -u -j` shell-out with the binding, so a host with a working driver no longer needs the command-line tool present, and report the driver and Neuware versions, the PCIe bus id, the NUMA node and a real health state, none of which the shell-out surfaced - move core utilization, used memory, temperature and power into `detect_usage`, and drop the `vgpu` appendix key, the last one in the package - skip a card whose required reads fail, as the operator does, without renumbering the cards that answered -- but fail the pass when every card is skipped, which is systemic rather than one faulty card, and used to report a host full of MLUs as having none - keep the binding out of the linter's naming and star-import rules, as every other hand-written binding here is Signed-off-by: thxCode <thxcode0824@gmail.com>
… usage - resolve the name from the local PCI ID database first, then the HSA product name, then libdrm's marketing name, then the ASIC name, which is the operator's full precedence, so a board reads as the board - bind `amdgpu_get_marketing_name` in `pyamdgpu`, which bound no such call and so left that step unreachable; declare its return type, since it answers with a pointer into libdrm's own table, and report an unnamed board as empty rather than as an error -- libdrm answers NULL for a device id its table does not carry, and the caller simply falls through to its next source - open the device only when pci.ids and HSA both found nothing, which is the rare path, so the common one costs no extra open. Verified against real libdrm on the AMD host: both cards answer "AMD Radeon RX 7800 XT". - move utilization, temperature, used memory and used power into `detect_usage`, leaving the power limit in the inventory query - drop the `vgpu` appendix key and the SR-IOV physical-function comparison - cover the name precedence, the split and the unchanged CDI device paths Signed-off-by: thxCode <thxcode0824@gmail.com>
…ir usage - adopt the same name precedence AMD now has -- the local PCI ID database, then the HSA product name, then libdrm's marketing name, then the driver-reported name -- since a Hygon DCU runs on the same ROCm binding stack - move utilization, temperature, used memory and used power into `detect_usage`, leaving the power limit in the inventory query - drop the `vgpu` appendix key and the SR-IOV physical-function comparison - cover the name precedence, the split and the unchanged CDI device paths Signed-off-by: thxCode <thxcode0824@gmail.com>
…e query
- read memory through the v2 structure with a v1 fallback, as the operator
does, so a driver exposing only v1 still reports a device
- move utilization, temperature and used power into `detect_usage`
- drop the `vgpu` appendix key and the SR-IOV physical-function comparison
- cover the fallback, the split and the unchanged `/dev/iluvatar{N}` path, which
now reads the minor number from the appendix
Signed-off-by: thxCode <thxcode0824@gmail.com>
…bled host - delete the skip on MXSML_VIRTUALIZATION_MODE_PF, which dropped the physical function -- the whole card -- so such a host reported no devices at all - apply no virtualization-mode filter at all, reporting whatever the driver enumerates, and drop the `vgpu` appendix key - move core utilization, used memory, temperature and board power into `detect_usage`, keeping the power limit in the inventory query - cover all three virtualization modes, so a reintroduced filter fails the suite Signed-off-by: thxCode <thxcode0824@gmail.com>
…apable card - delete the virtRole skip: it read `mpcCap`, which `c_mtmlDeviceProperty_t` does not have, so a card reporting HOST_VIRTDEVICE raised AttributeError and failed the whole detect pass -- the worker saw no MThreads devices at all - apply no virtRole filter, reporting whatever the driver enumerates, and drop the `vgpu` appendix key - move utilization, temperature, used memory and used power into `detect_usage` - cover every virtRole, so a reintroduced filter fails the suite loudly Signed-off-by: thxCode <thxcode0824@gmail.com>
…eaches the card - move utilization, temperature and used power into `detect_usage`, instance entries included, keeping the memory total and the power limit as inventory - suppress a failing MIG read per instance rather than per card, so one instance refusing a read no longer aborts the loop and erases every later instance from the inventory, or leaves it reading as idle - drop the `vgpu` appendix key from the card and its instances, and the SR-IOV physical-function comparison - keep the GPU/compute-instance entries in `appendix["mig_devices"]`, `sliced` intact, since the operator has no equivalent and the topology path reads it - record `appendix["minor_number"]` and make the CDI generator compare it: where the detector read a minor, the node /dev/alixpu_ppu<ordinal> must carry it, or the device is refused. The failure that refuses is the operator's measured one -- a path built from the minor lands on the neighbouring accelerator silently, and on the last card of a 16-card host names a ppu16 that does not exist. Signed-off-by: thxCode <thxcode0824@gmail.com>
…s not running - add `Deployer.map_visible_devices_ordering`, resolving the runtime visible devices env names to `CUDA_DEVICE_ORDER=PCI_BUS_ID` when NVIDIA is among them, and inject it from all three deployers when a container requests all devices or runs privileged -- the deployers' own resolution of "this container sees every card" -- as a default the container's own declaration overrides - add `WorkloadStatusExit`, holding a container's name and operation token, its exit code, reason, message, timestamps and restart count, held on `WorkloadStatus.exits` and defaulted so a payload serialized before this change still deserializes. The exit code stays optional: a container blocked from starting reports a reason and no code. - add one `parse_container_exit` beside it rather than a copy per runtime: podman-py is Docker-API compatible, both call sites inspect a reloaded container, and the two parsers were 58 of 60 lines identical, so every fix would have had to land twice and could drift - derive reason "Error" from a non-zero exit code, since `State.Error` is filled for a container that failed to start and not for one that ran and exited non-zero -- so the commonest crash of all produced an exit code with no reason, and the caller only sets `state_message` when there is one. Kubernetes already reported "Error" for the same event. - report an unset timestamp as empty rather than Go's zero time, which a UI renders as year 1, and truncate Docker's 9-digit nanoseconds to the 6 the Kubernetes deployer formats, which is also all strptime's %f accepts - read every `State` key defensively, so a container missing them degrades to the code alone, and cover a "restarting" container: a crash-looping one sits there with the last attempt's exit code - leave `parse_state`'s verdicts alone on Docker and Podman: this adds information, it does not re-decide the state - report Failed instead of Pending for ErrImageNeverPull, ErrImagePull, ImagePullBackOff, InvalidImageName and RegistryUnavailable, so a model that can never start stops looking like one that is still starting, and build an exit entry per Kubernetes container from its terminated or waiting state, init containers included - append the Pod's Warning event, which carries the registry error the bare waiting reason omits, reading events only for a Pod blocked that way, selecting by `involvedObject.uid` as well as name so a recreated Pod does not match its predecessor's, sorting by timestamp because the API guarantees no order and an Event's random name suffix breaks list order, and preferring the kubelet's "Failed" Event so a stale FailedScheduling cannot stand in as the diagnosis - degrade a failed events read to a debug log, so a cluster that has not applied the new RBAC rule keeps reporting the state and the reason, and grant get/list on core events as its own rule rather than widening the wildcard one Signed-off-by: thxCode <thxcode0824@gmail.com>
…d the design - add `test_detector_types.py` and `test_detector_cli.py`, covering the ABC composition, the usage merge and `detect --no-usage`: a `Detector` subclass missing either half fails to instantiate, no detector in the shipped package can emit a `vgpu` key, and the omitted columns render as N/A - give the merge tests distinct per-card values and switch health checks on: with identical payloads a positional join, a reversed mapping and a broadcast-to-all all satisfied them, and with health checks off both queries answer HEALTHY without a driver call, so neither test could fail - align every captured sample with the Device contract -- the retired `vgpu` key gone, the NVIDIA and THead samples carrying the `mig` flag, the MIG sample rebuilt around its cards with the instances inside `appendix.mig_devices` -- and add the guard that deserializes each one and compares its keys against `Device`'s fields, since `from_dict` alone swallows an unknown key silently - refresh the samples from the five hardware environments: 2x AMD RX 7800 XT, 1x RTX 5090 D, 2x RTX 4090 48G, 8x Ascend 910B2 and 16x T-Head PPU-ZW810E, replacing the hand-alignments where a real reading differs, and add the 48G-modded 4090 as its own sample since the existing rtx4090d one is a different 24G card - say in the README which samples are measurements and which are not, and why the H100 MIG and Hygon K100 AI ones could not be refreshed - record the design: the goals, the per-vendor parity audit, the query split, the retired physical-index switch, the exit-status surface, the hardware validation and the items deliberately deferred to their own tasks Signed-off-by: thxCode <thxcode0824@gmail.com>
0852c93 to
fd866ac
Compare
Aligns
gpustack_runtime/detectorwithgpustack-operator'spkg/devicemanager/detector, splits thedetect path into an information query and a usage query, retires the physical-index switch, and makes
workloads report why they stopped.
Spec:
specs/2026-08-14-detector-alignment-and-workload-exit-status.md(Status: Built).Closes the runtime side of:
CUDA_DEVICE_ORDERvs. the physical-index design.and Kubernetes.
What changed
Detect-path parity (nine vendors). Every vendor's binding calls were audited against the operator's
and the gaps closed. The defects that cost devices or reported them wrong:
reported zero devices.
mpcCapattribute, which raised and failed the whole detect pass.*_v2/*_v3calls with no V1 fallback) and did notfilter by unit type.
precedence —
pci.ids→ HSAProductName→ libdrmamdgpu_get_marketing_name→ ASIC / ROCm-SMIname.
pyamdgpugained that libdrm binding in this PR.cnmonshell-out with aTODOwhere the sample outputshould be. It is now a hand-written
pycndevctypes binding derived from the operator'scndev.h,with its struct layouts pinned against an independently compiled probe.
Information / usage split.
detect_info()anddetect_usage()are both abstract,detect(usage=True)composes them, and
detect_devices(usage=False)skips every metric call. Usage joins by UUID, never byindex.
get_topologyno longer pays for metrics it never reads.Whole-card reporting. The vGPU/virtual-card classification is gone for all nine vendors — the filters
dropped physical cards on MetaX and crashed the pass on MThreads. MIG-style instances stay in
appendix["mig_devices"], and no sample may resurrect the retired appendix keys.Device numbering.
Device.indexis the detector's enumeration index; driver-physical numbering(
minor_number,card_id/physical_id) moved to the appendix, and thePHYSICAL_INDEX_PRIORITYswitchis retired.
CUDA_DEVICE_ORDER=PCI_BUS_IDis injected by the deployer for NVIDIA workloads given alldevices or run privileged, never overriding a value the container already declares.
Workload exit status.
WorkloadStatus.exitsis populated by all three deployers, and a Kubernetesworkload whose image cannot be pulled reports
Failedinstead of sitting atPending, with the PodEvent appended. One new RBAC rule grants
get/liston coreevents— downstream charts need thesame rule.
One deliberate divergence worth calling out. NVIDIA
memoryis what the driver reports, i.e. what thecard can allocate. The operator adds back the ~1/16 that ECC parity carves out of a GDDR part, but that
figure is display-only there and takes no part in allocation, while this one does: restoring it would make
memory - memory_usedover-report free space and over-commit the card.Review
Three axes ran over the whole diff — spec, standards, and two external cross-checks — plus this PR's own
review. The findings worth naming:
/dev/davinciNis numbered by the physical id; the path wasbuilt from the logic id when the physical id was unreadable. A device that cannot be addressed is now
dropped, as the operator's
ascend/device.godoes. Pre-existing on the default path, not a regressionof this branch.
is the card's identity; the generator now compares the two and refuses the device when they disagree,
which is the proof the operator's allocator performs.
cndevGetUUIDno longer manufactures a bare"MLU-"that is one.reported none.
state_message—while Kubernetes reported
Errorfor the same event. Docker's and Podman's exit parsers were 58 of 60lines identical and are now one function.
join, a reversed mapping and a broadcast-to-all all satisfied it; the memory-status test ran with health
checks off, where both queries answer
healthywithout a driver call.Verification
471 passed, 20 skipped,make lintclean,make docsbuilds.Hardware validation ran on five environments — 2 × AMD RX 7800 XT, 1 × RTX 5090 D, 2 × RTX 4090 48G,
8 × Ascend 910B2, 16 × T-Head PPU-ZW810E — plus Hygon by proxy, every pass exiting 0. What no fixture
could establish:
the operator's). The node is named by the ordinal, so anything reading the minor instead addresses the
neighbouring card.
physical_idfor all 8 NPUs, so the invariant the device-node fix relies on holds.happening in practice.
AMD Radeon RX 7800 XTfor both cards, which is the new marketing-name step.Failure reporting was exercised end to end rather than only in fixtures:
Failed,ImagePullBackOff: ...; Error: ErrImagePull— waiting reason plus the Pod Eventexit_code=7,reason=Error,state_message=Errorexit_code=137,reason=OOMKilled,state_message=OOMKilledexit_code=137,reason=Error,state_message=ErrorSamples are refreshed from those hosts. Iluvatar, MetaX, MThreads and Cambricon have no environment, and
no MIG-enabled host was available, so those samples stay hand-aligned — stated in the samples README
rather than guessed at.