Skip to content

feat(detector): align the detect path with the operator, split information from usage, and report workload exit status - #17

Merged
thxCode merged 12 commits into
mainfrom
refactor-binding
Aug 15, 2026
Merged

feat(detector): align the detect path with the operator, split information from usage, and report workload exit status#17
thxCode merged 12 commits into
mainfrom
refactor-binding

Conversation

@thxCode

@thxCode thxCode commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

Aligns gpustack_runtime/detector with gpustack-operator's pkg/devicemanager/detector, splits the
detect 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:

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:

  • MetaX skipped physical functions with an inverted comparison, so a virtualization-enabled host
    reported zero devices.
  • MThreads read a nonexistent mpcCap attribute, which raised and failed the whole detect pass.
  • Ascend was undetectable on older drivers (four *_v2/*_v3 calls with no V1 fallback) and did not
    filter by unit type.
  • Iluvatar had no v2→v1 memory fallback.
  • AMD / Hygon resolved the device name from the driver only; they now follow the operator's full
    precedence — pci.ids → HSA ProductName → libdrm amdgpu_get_marketing_name → ASIC / ROCm-SMI
    name. pyamdgpu gained that libdrm binding in this PR.
  • Cambricon was not a binding at all — a cnmon shell-out with a TODO where the sample output
    should be. It is now a hand-written pycndev ctypes binding derived from the operator's cndev.h,
    with its struct layouts pinned against an independently compiled probe.

Information / usage split. detect_info() and detect_usage() are both abstract, detect(usage=True)
composes them, and detect_devices(usage=False) skips every metric call. Usage joins by UUID, never by
index. get_topology no 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.index is the detector's enumeration index; driver-physical numbering
(minor_number, card_id/physical_id) moved to the appendix, and the PHYSICAL_INDEX_PRIORITY switch
is retired. CUDA_DEVICE_ORDER=PCI_BUS_ID is injected by the deployer for NVIDIA workloads given all
devices or run privileged, never overriding a value the container already declares.

Workload exit status. WorkloadStatus.exits is populated by all three deployers, and a Kubernetes
workload whose image cannot be pulled reports Failed instead of sitting at Pending, with the Pod
Event appended. One new RBAC rule grants get/list on core eventsdownstream charts need the
same rule.

One deliberate divergence worth calling out. NVIDIA memory is what the driver reports, i.e. what the
card 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_used over-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:

  • Ascend could mount the wrong NPU. /dev/davinciN is numbered by the physical id; the path was
    built 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.go does. Pre-existing on the default path, not a regression
    of this branch.
  • T-Head could address its neighbour. The node is named by the card ordinal while the recorded minor
    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.
  • An ambiguous UUID broadcast one card's metrics onto every card. The merge now drops such an id, and
    cndevGetUUID no longer manufactures a bare "MLU-" that is one.
  • A usage-query failure discarded an inventory already in hand, so a host with eight healthy GPUs
    reported none.
  • A container that merely exited non-zero carried no reason, so nothing reached state_message
    while Kubernetes reported Error for the same event. Docker's and Podman's exit parsers were 58 of 60
    lines identical and are now one function.
  • Two tests could not fail. The usage-join test gave every card identical values, so a positional
    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 healthy without a driver call.

Verification

471 passed, 20 skipped, make lint clean, make docs builds.

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:

  1. T-Head's minor number is not its enumeration index (they run one apart on that host, as they do on
    the operator's). The node is named by the ordinal, so anything reading the minor instead addresses the
    neighbouring card.
  2. Ascend reports physical_id for all 8 NPUs, so the invariant the device-node fix relies on holds.
  3. AMD's per-card UUIDs are distinct, so the constant-id collision the merge now guards against is not
    happening in practice.
  4. libdrm answers AMD Radeon RX 7800 XT for both cards, which is the new marketing-name step.

Failure reporting was exercised end to end rather than only in fixtures:

Scenario Backend Result
Unpullable image k3s Failed, ImagePullBackOff: ...; Error: ErrImagePull — waiting reason plus the Pod Event
Exit non-zero Docker 29.6.1 exit_code=7, reason=Error, state_message=Error
Out of memory Docker 29.6.1 exit_code=137, reason=OOMKilled, state_message=OOMKilled
Killed (SIGKILL) Docker 29.6.1 exit_code=137, reason=Error, state_message=Error

Samples 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.

Copilot AI lite review requested due to automatic review settings August 14, 2026 18:02

Copilot AI 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.

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) and detect_usage() (metrics), with UUID-based merging and MIG instances kept under appendix["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), inject CUDA_DEVICE_ORDER=PCI_BUS_ID when 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.

Comment thread gpustack_runtime/deployer/__types__.py

@gemini-code-assist gemini-code-assist 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.

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.

Comment thread gpustack_runtime/detector/__types__.py Outdated
Comment thread gpustack_runtime/detector/amd.py
Comment thread gpustack_runtime/detector/hygon.py
Comment thread gpustack_runtime/detector/nvidia.py Outdated
Comment thread gpustack_runtime/deployer/kuberentes.py
thxCode added a commit that referenced this pull request Aug 15, 2026
- 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>
thxCode added a commit that referenced this pull request Aug 15, 2026
- 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>
thxCode added a commit that referenced this pull request Aug 15, 2026
- 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>
thxCode added 12 commits August 15, 2026 11:50
…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>
@thxCode
thxCode merged commit 518db70 into main Aug 15, 2026
7 checks passed
@thxCode
thxCode deleted the refactor-binding branch August 15, 2026 14:31
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.

2 participants