Commit d28f2f5
Aicomnet dev slurm multi (#142)
* feat(preparation): amd-smi JSON tolerance, manifest runtime-context restore, secrets precedence, lightweight-CLI isolation
Stage A of porting the aicomnet multi-node local-image work onto the
slurm_multi pipeline (develop). Preparation-phase changes only:
- core/context: parse amd-smi JSON via JSONDecoder.raw_decode so trailing
non-JSON text (deprecation banners, double-emitted blocks under
concurrent slurm tasks) no longer aborts run-phase init.
- orchestration/run_orchestrator: restore host-level runtime context
(docker_mounts, docker_build_arg, docker_gpus, gpu_vendor, guest_os)
from the manifest on execution, keeping runtime-detected values as
priority; docker_env_vars from the manifest no longer overwrite runtime
values already populated by Context (e.g. MAD_SECRETS_* read from
os.environ), so unexpanded "${VAR}" manifest placeholders cannot clobber
the resolved secret.
- core/constants: --version/--help lightweight CLI invocations skip
MODEL_DIR setup and keep output clean, avoiding global-state pollution
of subsequent runs in the same shell.
* feat(build): multi-node local-image build-on-primary + shared-tar distribution
Stage B of porting the aicomnet multi-node local-image work onto the
slurm_multi pipeline. Adds local-image preparation to the per-node run path
(run_models_from_manifest, which slurm_multi invokes on every node in docker
target mode):
- _ensure_local_image_available: primary node (rank 0) ensures the local image
is present (building it from the manifest dockerfile if missing), and when
MAD_DOCKER_BUILDS points at a shared dir, docker-saves it into a shared tar;
worker nodes load that tar instead of pulling. Local images are not in any
registry, so upstreams registry-only pull path cannot reach them on workers.
- Atomic tar write (sibling .tmp + os.replace) so peers never load a
half-written tar.
- _build_local_image_from_manifest / _get_build_args / _build_or_pull_local_image
with shlex.quote shell-escaping for all path/image/build-arg components
(Console.sh runs with shell=True).
- Strict NODE_RANK/RANK and NNODES/WORLD_SIZE parsing (malformed values raise
instead of silently defaulting to 0/1 and diverging).
- _sync_after_local_image_ready: Stage B uses a shared-filesystem readiness wait
(workers without the image poll for the primary tar); Stage C replaces this
with a deterministic TCP barrier.
Replaces the previous verify-then-pull block, which could only pull registry
images and left local multi-node images unreachable on worker nodes.
* feat(running): deterministic TCP image-ready barrier + docker mount de-duplication
Stage C of porting the aicomnet multi-node local-image work onto the slurm_multi
pipeline. Replaces the Stage B shared-FS readiness poll with a robust TCP
rendezvous and adds docker mount de-duplication.
- _tcp_image_ready_barrier / _recv_line: NODE_RANK=0 listens on a port range
derived from MASTER_PORT+SLURM_JOB_ID; workers send "READY <token> <rank>" and
wait for "GO". No shared-FS visibility required (avoids NFS/Weka metadata lag).
Hardening: MAD_BARRIER_TOKEN secret (defaults JOB<id>); bind to MASTER_ADDR
resolved IP, skipping loopback (127/8, ::1) so an /etc/hosts loopback mapping
cannot leave the listener on lo while workers connect over the routable IP;
line-based recv (partial-read safe); worker-rank normalization so NODE_RANK="01"
matches the int ACK; strict rank / nnodes parsing; on master timeout the ACKed
worker sockets are closed (EOF, not half-open); accepted-peer list logged for
deadlock diagnosis.
- _sync_after_local_image_ready now drives the TCP barrier instead of polling the
shared filesystem.
- get_mount_arg de-duplicates against -v/--volume targets already present in
additional_docker_run_options (via _extract_additional_mount_targets) so docker
does not reject "Duplicate mount point".
SLURM->container env pass-through and --version/--help MODEL_DIR isolation were
already covered (develop slurm_env_vars list; Stage A constants).
* feat(postprocessing): best-non-empty multi-node results CSV selection + on-failure container diagnostics
Stage D of porting the aicomnet multi-node local-image work onto the slurm_multi
pipeline. Covers result aggregation and failure triage across nodes:
- slurm.py collect_results: instead of taking the first node_<rank> directory
that happens to hold the multiple_results CSV, gather every per-node candidate
(plus the job-dir copy) and pass them to _select_best_multiple_results_csv,
which ranks by the count of non-empty "performance" rows and breaks ties on
total row count. In multi-node runs the master CSV is frequently present but
empty while a worker holds the real throughput numbers, so first-match could
aggregate empty data; this defers empty-perf handling to the richest file.
Header/row keys are stripped so a leading-space CSV header still matches.
- container_runner.py run path: wrap the model-script exec in a try/except that,
on RuntimeError, parses the failed container id from the error and snapshots
bounded diagnostics inside it (process table, listening ports, and tails of
/run_logs and the model dir logs) via Console.sh so they land in the run log
next to the failure. All diagnostic calls are time-bounded and non-fatal; the
original error is re-raised unchanged.
* fix(running): defer empty-perf verdict to login-node aggregation in SLURM in-job runs
In multi-node SLURM runs the per-node in-job status check marked the job FAILED
when the designated collector node (MAD_COLLECT_METRICS=true) had an empty
multiple_results CSV, even though Primus emits throughput only on the last global
rank, which frequently lives on a different node than the collector. With
skip_perf_collection set, the login-node collect_results already selects the
richest per-node CSV (_select_best_multiple_results_csv) and writes the
authoritative perf/status record, so an empty local perf is not authoritative and
must not hard-fail the node (exit 3 -> whole job fails). Add a skip_perf_collection
guard to the status determination (and the except fallback). Real failures remain
caught first by the error-pattern scan; genuinely-empty multi-node runs still get a
FAILURE row from the login-node aggregation.
* fix(running): shell-escape image_name in local-image validate/pull (Copilot review)
Resolves Copilot review (High): in _create_manifest_from_local_image, image_name
was interpolated into "docker image inspect" / "docker pull" commands passed to
Console.sh (shell=True) without quoting -- a command-injection risk that also
breaks valid image names containing shell-special characters. Escape it once via
shlex.quote and use the quoted form in both shell calls (display strings keep the
raw name). Matches the shlex.quote escaping already used on the Stage B/C
container_runner shell paths.
* fix(slurm): reconcile local image across nodes and rebuild stale images
Multi-node local-image runs trusted the Docker tag alone, allowing two
failure modes that silently ran mismatched software across ranks:
- Cross-node identity: a worker holding an old image under the same tag
kept it while rank 0 (re)built a different image, because workers only
acted when the tag was absent. Rank 0 now broadcasts the ensured image
ID over the rendezvous barrier (extended GO line) and workers force-
reload rank 0's tar from MAD_DOCKER_BUILDS whenever their local image
ID differs, failing hard if the mismatch persists.
- Content staleness: reusing a tag for a different build (e.g. a new RCCL
commit) was never rebuilt. Local builds now bake a mad.build_fingerprint
label (hash of Dockerfile + docker_build_arg); a present-but-differing
label triggers a rebuild and tar refresh. Images without the label
(pulled or pre-existing) are left untouched to avoid spurious rebuilds.
When no shared tar is configured, workers fall back to fingerprint-based
content correctness instead of byte-identical ID reconciliation.
Adds unit tests for GO-line parsing, build fingerprinting, staleness
detection, and primary/worker reconciliation paths.
* fix(tcp-barrier): catch _recv_line errors on master side
On the master side of the TCP barrier, _recv_line(conn) could raise
socket.timeout or other socket errors after accept(); the outer
except socket.timeout only covered server.accept() itself, so a slow
or misbehaving peer could abort the entire barrier.
Wrap _recv_line in its own try/except Exception: close the connection
and continue accepting remaining peers instead of propagating.
* fix(gpu-mapping): restore kfd_renderDs None guard for ROCm pre-6.4.1
The legacy unique_id path has no amd-smi fallback for renderD discovery;
only the 6.4.1+ branch can recover when kfd_renderDs is empty. Without
this guard an inaccessible KFD topology surfaces as an obscure len(None)
TypeError instead of a clear, actionable error. Restores the guard
dropped in 3ee0500.
---------
Co-authored-by: Stephen Shao <yu.shao@amd.com>1 parent ef87d05 commit d28f2f5
6 files changed
Lines changed: 1045 additions & 34 deletions
File tree
- src/madengine
- core
- deployment
- execution
- orchestration
- tests/unit
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
26 | 26 | | |
27 | 27 | | |
28 | 28 | | |
| 29 | + | |
29 | 30 | | |
30 | 31 | | |
31 | 32 | | |
32 | 33 | | |
33 | 34 | | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
34 | 39 | | |
35 | 40 | | |
36 | 41 | | |
37 | 42 | | |
38 | 43 | | |
39 | 44 | | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
40 | 51 | | |
41 | 52 | | |
42 | 53 | | |
| |||
65 | 76 | | |
66 | 77 | | |
67 | 78 | | |
68 | | - | |
| 79 | + | |
69 | 80 | | |
70 | | - | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
71 | 85 | | |
72 | 86 | | |
73 | 87 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
816 | 816 | | |
817 | 817 | | |
818 | 818 | | |
819 | | - | |
| 819 | + | |
| 820 | + | |
| 821 | + | |
| 822 | + | |
820 | 823 | | |
821 | 824 | | |
822 | 825 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1632 | 1632 | | |
1633 | 1633 | | |
1634 | 1634 | | |
| 1635 | + | |
| 1636 | + | |
| 1637 | + | |
| 1638 | + | |
| 1639 | + | |
| 1640 | + | |
| 1641 | + | |
| 1642 | + | |
| 1643 | + | |
| 1644 | + | |
| 1645 | + | |
| 1646 | + | |
| 1647 | + | |
| 1648 | + | |
| 1649 | + | |
| 1650 | + | |
| 1651 | + | |
| 1652 | + | |
| 1653 | + | |
| 1654 | + | |
| 1655 | + | |
| 1656 | + | |
| 1657 | + | |
| 1658 | + | |
| 1659 | + | |
| 1660 | + | |
| 1661 | + | |
| 1662 | + | |
| 1663 | + | |
| 1664 | + | |
| 1665 | + | |
| 1666 | + | |
| 1667 | + | |
| 1668 | + | |
| 1669 | + | |
| 1670 | + | |
| 1671 | + | |
| 1672 | + | |
| 1673 | + | |
| 1674 | + | |
| 1675 | + | |
| 1676 | + | |
| 1677 | + | |
| 1678 | + | |
| 1679 | + | |
| 1680 | + | |
| 1681 | + | |
| 1682 | + | |
| 1683 | + | |
| 1684 | + | |
| 1685 | + | |
| 1686 | + | |
| 1687 | + | |
| 1688 | + | |
1635 | 1689 | | |
1636 | 1690 | | |
1637 | 1691 | | |
| |||
1775 | 1829 | | |
1776 | 1830 | | |
1777 | 1831 | | |
| 1832 | + | |
| 1833 | + | |
| 1834 | + | |
| 1835 | + | |
| 1836 | + | |
1778 | 1837 | | |
1779 | | - | |
1780 | | - | |
1781 | | - | |
1782 | | - | |
1783 | | - | |
1784 | | - | |
1785 | | - | |
| 1838 | + | |
| 1839 | + | |
| 1840 | + | |
| 1841 | + | |
| 1842 | + | |
| 1843 | + | |
| 1844 | + | |
1786 | 1845 | | |
1787 | 1846 | | |
1788 | 1847 | | |
| |||
0 commit comments