Skip to content

Architecture

The one idea

mlc-scripts collects. mlperf-sysinfo composes.

Probing a machine is hard, platform-specific work that should stay shared and unchanged across MLCommons. Deciding what a working group must supply, and what the resulting file should look like, is policy — and policy differs per group, so it belongs in a package groups can configure.

flowchart LR
    A["<b>mlperf-sysinfo</b><br/>composition"] -->|one call| B["<b>mlc-scripts</b><br/>collection"]
    B --> C["SSH to each node<br/>CPU / memory / accelerators<br/>network / OS<br/>Redfish, serving log"]
    C -->|probed field set| A
    A --> D["system_desc.json<br/>metadata and order<br/>from the profile"]

The split runs along one line: the automation owns what the hardware is, this package owns everything a person had to type. The profile's benchmark names which field set to gather — the Endpoints and Inference field sets are not the same shape and are not interchangeable — and what comes back is treated as the probed truth. What each submitter-supplied field says is decided here.

Why the metadata is not left to the automation

The automation's defaults for submitter-supplied fields are placeholder strings: "Insert system category here". A placeholder in a submission file is worse than an empty one, because it reads as filled in. So every such field is written from the config or left genuinely empty, and check is what refuses to run when one that matters is empty.

A profile still costs no automation change

Requirements, recommendations, field order, which optional probes run and what the file is called are all profile data. A new group that can use an existing field set is one YAML file.

Module map

Module Responsibility
config.py The config schema, extends merging, ${VAR} interpolation, placeholder detection
profiles/ What each working group requires, collects, and writes. YAML, no Python
preflight.py Validation and reachability — the check command's engine
collector.py Invokes the automation, verifies what came back, orchestrates the run
output.py Orders the collected field set, overlays config metadata, stamps provenance
report.py Reads a captured file back — show and validate
cli.py / ui.py The command line and its rendering
errors.py Every deliberate failure is one of these

The mlc-scripts dependency is pinned to an exact pre-release, 1.2.0a2, which is the version this package is tested against. pip installs it without any flag because the specifier names the pre-release explicitly; uv needs prerelease = "allow", which pyproject.toml already sets.

What a capture actually does

flowchart TD
    A[load config] --> B[apply extends, interpolate VAR]
    B --> C[load profile]
    C --> D{pre-flight}
    D -->|config problem| E[stop, no override]
    D -->|node unreachable| F{--allow-partial?}
    F -->|no| E
    F -->|yes| G
    D -->|all good| G[call mlc-scripts]
    G --> H[read the collected field set]
    H --> I{how many nodes<br/>came back?}
    I -->|zero| J[error, nothing written]
    I -->|fewer than asked| F
    I -->|all of them| K[shape by profile]
    K --> L[stamp provenance]
    L --> M[write system_desc.json]

Two kinds of problem, handled differently

This distinction is the core of the design.

Kind Examples Behaviour
Config problem Missing required field, CHANGEME left in, unset ${VAR}, node group counts that exceed the nodes configured Hard stop. No override. Cheap to fix, and they produce bad submissions
Reachability problem A node that will not answer, fewer nodes returning hardware than were asked for Stops the run, but --allow-partial proceeds and marks the output partial

Verifying what came back

A node can be perfectly reachable and still return nothing — an unsupported OS, a probe that needs sudo. So the node count is taken from the collected data, never from the config:

  • zero nodes → always an error, even with --allow-partial. No file written.
  • fewer than asked for → partial. Error unless --allow-partial.
  • all of them → complete.

This was a real bug

The first cut trusted the request over the result and wrote complete: true for a capture that collected nothing. Believing the request is exactly how a capture silently ships half a system.

Where files land

The deliverable is the only thing written to output.dir. Everything the automation produces goes into a scratch directory beside it:

results/my_run/
├── system_desc.json                 ← the deliverable
└── .mlperf-sysinfo/
    ├── capture_20260819_202120.log  ← the run log
    ├── raw-system-info.json         ← the field set mlc-scripts returned
    └── mlperf-system-info-single-node-0.json

Redfish captures (redfish_nameplate_power.yaml, redfish_capture.yaml) are deliverables in their own right and are lifted back out into output.dir.

The run log

Every capture writes one, named after the time it started so a retry leaves the log of the failure that prompted it in place. It opens with what produced the run and closes with how it ended:

mlperf-sysinfo capture — 20260819_202120
Started   : 2026-08-19T20:21:20+05:30
Versions  : mlperf-sysinfo 1.0.0a3, mlc-scripts 1.2.0a2
Profile   : endpoints (round 6.0, benchmark endpoints)
Config    : /home/user/sysinfo.yaml
Output    : /home/user/results/my_run/system_desc.json
Nodes     : 2 -- this machine, root@node1:22
Invoked   : mlperf-sysinfo capture -c sysinfo.yaml

[2026-08-19 20:21:20] INFO     config: loaded config /home/user/sysinfo.yaml (profile endpoints)
[2026-08-19 20:21:20] INFO     preflight: 5 of 5 required field(s) set for profile endpoints
[2026-08-19 20:21:20] INFO     collector: collecting with tags: get-mlperf-multi-node-system-info,_cuda,_endpoints
[2026-08-19 20:21:22] Skipping password prompt - non-interactive terminal detected!
[2026-08-19 20:21:33,496 deprecation.py :  66 WARN ] - Your mlcflow version is deprecated
                        Please upgrade to mlcflow >= 1.3.0
[2026-08-19 20:21:27] INFO     collector: 1 of 1 node(s) returned hardware
[2026-08-19 20:21:27] INFO     output: 3 field(s) blank in the written file (unset in config, or not detected): hw_notes, link_config, sw_notes
[2026-08-19 20:21:27] INFO     collector: wrote /home/user/results/my_run/system_desc.json

------------------------------------------------------------------------
Finished  : 2026-08-19T20:21:27+05:30
Duration  : 6.6s
Outcome   : complete -- 1 of 1 node(s)

Four things about the body are deliberate:

  • This package's own actions are in there, at a level and with the module that took them, not just the automation's output. What the automation returned and what was concluded from it are different things, and the second is usually what is being reconstructed. Every level reaches the file regardless of what the terminal is set to — see Logging.
  • Lines mlcflow already stamped keep their own stamp. A second one in front would only push the real timestamp out of the reader's eye line. The two styles are close enough on purpose, so a mixed log still reads down the page.
  • Indented lines are indented, not stamped. They are the tail of the message above -- the wrapped rest of a warning, or the frames of a traceback. A stamp on each claims they were logged separately and pulls the block apart.
  • Records from before the file existed are replayed into it. Loading the config and the whole pre-flight check happen before an output directory is touched, so they are buffered and written in once the file opens. Those early records are usually the interesting ones.

The round belongs in the header for the same reason it is stamped into every output file and printed nowhere on the terminal: a file read weeks later has to say which rules produced it.

Pass --verbose to watch the automation on the terminal as well. The log is still written either way.

Known limitations

Credentials can reach the run log

Keeping secrets in ${VAR} keeps them out of your config file and out of git, but the underlying automation prints its own command lines. A Redfish password may appear in the log. Treat .mlperf-sysinfo/ as sensitive.

Remote scratch files are not confined

When collecting over SSH, the automation writes temporary files on each remote node, under $HOME and /tmp there. This package cannot redirect those, because its environment does not propagate over the automation's own SSH leg.