From 4b739b6cc1b1d4402bd7b4302846de0be5cb73ac Mon Sep 17 00:00:00 2001 From: Stephen Shao Date: Tue, 11 Aug 2026 21:36:02 -0400 Subject: [PATCH 01/16] fix(run_orchestrator): preserve multiple_results in local-image manifest (#166) MAD_CONTAINER_IMAGE (local image) mode built a synthetic manifest that omitted the models.json `multiple_results` field. Without it, ContainerRunner never sets MAD_OUTPUT_CSV, never copies the perf CSV out of the container, and falls back to scraping the run log for a "performance: NUMBER METRIC" line -- reporting FAILURE even when the model produced valid perf-CSV results. Co-authored-by: Claude Sonnet 5 --- .../orchestration/run_orchestrator.py | 1 + tests/unit/test_orchestration.py | 87 +++++++++++++++++++ 2 files changed, 88 insertions(+) diff --git a/src/madengine/orchestration/run_orchestrator.py b/src/madengine/orchestration/run_orchestrator.py index 296af1ee..b0338d8c 100644 --- a/src/madengine/orchestration/run_orchestrator.py +++ b/src/madengine/orchestration/run_orchestrator.py @@ -459,6 +459,7 @@ def _create_manifest_from_local_image( "deprecated": model.get("deprecated", False), "skip_gpu_arch": model.get("skip_gpu_arch", []), "additional_docker_run_options": model.get("additional_docker_run_options", ""), + "multiple_results": model.get("multiple_results", ""), } # Write manifest to file diff --git a/tests/unit/test_orchestration.py b/tests/unit/test_orchestration.py index 54a11bd5..aaf24fcc 100644 --- a/tests/unit/test_orchestration.py +++ b/tests/unit/test_orchestration.py @@ -359,3 +359,90 @@ def test_distributed_warns_on_local_only_flags(self, tmp_path): assert "--keep-alive" in printed assert "--skip-model-run" in printed assert "--keep-model-dir" not in printed # was False, must not appear + + +@pytest.mark.unit +class TestCreateManifestFromLocalImage: + """MAD_CONTAINER_IMAGE (local image) mode must carry every models.json field + that ContainerRunner relies on -- including multiple_results, whose absence + silently drops perf-CSV-based result reporting (falls back to scraping the + log for a 'performance: NUMBER METRIC' line and reports FAILURE).""" + + @patch("madengine.orchestration.run_orchestrator.Context") + def test_multiple_results_field_is_preserved(self, mock_context, tmp_path): + mock_context.return_value.ctx = {} + mock_args = MagicMock() + mock_args.additional_context = None + mock_args.live_output = False + + orchestrator = RunOrchestrator(mock_args) + orchestrator.console = MagicMock() + orchestrator.rich_console = MagicMock() + + fake_model = { + "name": "uber_storefront/v1t1", + "tags": ["inference"], + "scripts": "run.sh", + "n_gpus": "1", + "data": "uber_storefront_models", + "args": "--model-dir v1t1", + "multiple_results": "perf_uber_storefront_v1t1.csv", + } + + manifest_output = str(tmp_path / "build_manifest.json") + + with patch( + "madengine.utils.discover_models.DiscoverModels.run", + return_value=[fake_model], + ): + orchestrator._create_manifest_from_local_image( + image_name="registry.io/org/model:ci-tag", + tags=["inference"], + manifest_output=manifest_output, + ) + + with open(manifest_output) as f: + manifest = json.load(f) + + built_model = next(iter(manifest["built_models"].values())) + assert built_model["multiple_results"] == "perf_uber_storefront_v1t1.csv" + + @patch("madengine.orchestration.run_orchestrator.Context") + def test_multiple_results_defaults_to_empty_string(self, mock_context, tmp_path): + """Models without multiple_results in models.json still get the key (empty), + so ContainerRunner's model_info.get("multiple_results") lookups never KeyError.""" + mock_context.return_value.ctx = {} + mock_args = MagicMock() + mock_args.additional_context = None + mock_args.live_output = False + + orchestrator = RunOrchestrator(mock_args) + orchestrator.console = MagicMock() + orchestrator.rich_console = MagicMock() + + fake_model = { + "name": "dummy/model", + "tags": ["inference"], + "scripts": "run.sh", + "n_gpus": "1", + "data": "", + "args": "", + } + + manifest_output = str(tmp_path / "build_manifest.json") + + with patch( + "madengine.utils.discover_models.DiscoverModels.run", + return_value=[fake_model], + ): + orchestrator._create_manifest_from_local_image( + image_name="registry.io/org/model:ci-tag", + tags=["inference"], + manifest_output=manifest_output, + ) + + with open(manifest_output) as f: + manifest = json.load(f) + + built_model = next(iter(manifest["built_models"].values())) + assert built_model["multiple_results"] == "" From 01733261ec22213c3420f6bad4cd2cde0930640d Mon Sep 17 00:00:00 2001 From: Stephen Shao Date: Tue, 11 Aug 2026 22:30:49 -0400 Subject: [PATCH 02/16] feat(auth): reuse existing docker login (OAT) instead of requiring credential.json (#168) * feat(auth): reuse existing docker login instead of requiring credential.json Reads ${DOCKER_CONFIG:-~/.docker}/config.json the same way the Docker CLI does, so a machine already authenticated via `docker login` (including an org access token) is not forced to duplicate credentials into credential.json, and blank placeholder credentials never override or break a working login. Also distinguishes insufficient_scope (authorization) from unauthorized (authentication) in base-image pull failures so the error message points at the right fix, and logs in before `docker build --pull` only when there's no existing login to reuse. Co-Authored-By: Claude Sonnet 5 * fix(gpu-tools): support non-default ROCm install paths and detect GPUs via PATH Honor $ROCM_PATH (falling back to /opt/rocm) instead of hardcoding /opt/rocm in amd_smi_utils.py, rocm_smi_utils.py, and gpu_info_profiler.py, and detect nvidia-smi/rocm-smi/amd-smi via `command -v` in gpu_info_pre.sh instead of a fixed binary path, so detection works when ROCm is installed elsewhere or GPU tools are only on PATH. Also make the rpd tracer's LD_LIBRARY_PATH ROCm-path-aware, and fall back to saving the raw trace.rpd when rpd2tracing.py fails instead of losing the trace. Co-Authored-By: Claude Sonnet 5 * fix(gpu-tools): guard rocminfo call and detect nvidia-smi/rocm-smi via PATH rocminfo may be absent even when rocm-smi/amd-smi are present; call it only when available instead of failing the pre-script. Also prefer PATH lookups over hardcoded /usr/bin and $ROCM_PATH/bin paths when detecting GPU vendor. Co-Authored-By: Claude Sonnet 5 * fix(auth): name the actual registry in pull-denied hints explain_registry_denial() always suggested Docker Hub credentials even when the failing image referenced another registry (e.g. ghcr.io). Extract the registry host from the image reference and tailor the docker login / credential.json suggestions to it, falling back to the existing Docker Hub guidance when the image has no registry host. Co-Authored-By: Claude Sonnet 5 --------- Co-authored-by: Claude Sonnet 5 --- docs/configuration.md | 36 +++ src/madengine/core/auth.py | 239 ++++++++++++++++-- src/madengine/core/console.py | 6 + src/madengine/execution/docker_builder.py | 106 +++++++- .../orchestration/build_orchestrator.py | 39 ++- .../scripts/common/post_scripts/trace.sh | 8 +- .../common/pre_scripts/gpu_info_pre.sh | 10 +- src/madengine/scripts/common/tools.json | 6 +- .../scripts/common/tools/amd_smi_utils.py | 6 +- .../scripts/common/tools/gpu_info_profiler.py | 28 +- .../scripts/common/tools/rocm_smi_utils.py | 6 +- tests/unit/test_auth.py | 210 ++++++++++++++- 12 files changed, 626 insertions(+), 74 deletions(-) diff --git a/docs/configuration.md b/docs/configuration.md index 4831cc4f..dcdc4e3d 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -697,6 +697,42 @@ export MAD_DOCKERHUB_PASSWORD=mytoken export MAD_DOCKERHUB_REPO=myorg ``` +### Registry Authentication + +madengine reuses an existing `docker login` — including an organization access +token (OAT) — rather than requiring credentials to be duplicated into +`credential.json`. Ambient credentials are read from +`${DOCKER_CONFIG:-~/.docker}/config.json` exactly as the Docker CLI reads them, +covering `auths` entries, `credHelpers`, and `credsStore`. + +Blank values are treated as **not configured**, not as credentials. A placeholder +entry such as `{"username": "", "password": ""}` will never override or break a +working `docker login`. + +| Existing `docker login` | Credentials in `credential.json` / env | Behavior | +|---|---|---| +| no | yes (non-blank) | `docker login` with the configured credentials | +| yes | yes (non-blank) | `docker login` with the configured credentials (explicit wins) | +| yes | absent or blank | Reuse the existing login; no `docker login` is run | +| no | absent or blank | Push fails with an actionable error; pull warns and continues | + +Before `docker build`, madengine logs in to the base image's registry only when +that registry has no existing login and usable credentials are configured, so a +node authenticated with an OAT is never re-authenticated. + +Relevant environment variables: + +- `DOCKER_CONFIG` — directory holding `config.json` (default `~/.docker`) +- `MAD_SKIP_DOCKER_LOGIN=1` — never run `docker login`; always defer to the + credentials the machine already has + +If a base image pull is denied, madengine distinguishes the two causes: +credentials were rejected (authentication — supply credentials or run +`docker login`), versus credentials were accepted but the registry granted no +pull scope for that repository (`insufficient_scope` — an authorization problem, +where the access token needs to be scoped to the repository and re-running +`docker login` will not help). + ## Configuration Priority For Kubernetes/SLURM deployments: diff --git a/src/madengine/core/auth.py b/src/madengine/core/auth.py index 15f0a0a6..021aa6dc 100644 --- a/src/madengine/core/auth.py +++ b/src/madengine/core/auth.py @@ -11,7 +11,8 @@ import json import os import shlex -from typing import Dict, Optional +from pathlib import Path +from typing import Dict, Optional, Tuple from madengine.core.errors import ( ConfigurationError, @@ -19,6 +20,19 @@ handle_error, ) +# Keys under which the Docker CLI stores Docker Hub credentials in config.json. +# Docker has used several spellings over the years and any of them means +# "this machine is authenticated to Docker Hub". +_DOCKERHUB_CONFIG_KEYS: Tuple[str, ...] = ( + "https://index.docker.io/v1/", + "index.docker.io", + "registry-1.docker.io", + "docker.io", +) + +# Registry values that madengine treats as "Docker Hub" rather than a host. +_DOCKERHUB_ALIASES = ("docker.io", "dockerhub") + def load_credentials() -> Optional[Dict]: """Load credentials from credential.json and environment variables. @@ -81,6 +95,174 @@ def load_credentials() -> Optional[Dict]: return credentials +def _registry_config_keys(registry: Optional[str]) -> Tuple[str, ...]: + """Map a madengine registry value to the keys Docker uses in config.json. + + Args: + registry: Registry URL (e.g. ``"localhost:5000"``, ``"docker.io/rocm"``), + or ``None``/empty string for Docker Hub. + + Returns: + The config.json ``auths`` keys that would hold credentials for it. + """ + if not registry or registry.lower() in _DOCKERHUB_ALIASES: + return _DOCKERHUB_CONFIG_KEYS + # Downstream code derives the registry host the same way (docker login ). + host = registry.split("/")[0] + if host.lower() in _DOCKERHUB_ALIASES: + return _DOCKERHUB_CONFIG_KEYS + return (host,) + + +def has_ambient_docker_auth(registry: Optional[str]) -> bool: + """Report whether the local Docker CLI is already authenticated to ``registry``. + + Reads ``${DOCKER_CONFIG:-~/.docker}/config.json`` the same way the Docker CLI + does, so an existing ``docker login`` (e.g. an organisation access token) is + honoured instead of being overridden or reported as "no credentials". + + Args: + registry: Registry URL, or ``None``/empty string for Docker Hub. + + Returns: + ``True`` if a usable credential entry exists for the registry. Any read + or parse problem yields ``False``; this function never raises and never + logs credential material. + """ + config_dir = os.environ.get("DOCKER_CONFIG") or os.path.join( + os.path.expanduser("~"), ".docker" + ) + try: + config = json.loads(Path(config_dir, "config.json").read_text(encoding="utf-8")) + except (OSError, ValueError): + return False + if not isinstance(config, dict): + return False + + keys = _registry_config_keys(registry) + auths = config.get("auths") or {} + cred_helpers = config.get("credHelpers") or {} + creds_store = config.get("credsStore") + + if isinstance(cred_helpers, dict) and any(key in cred_helpers for key in keys): + return True + if not isinstance(auths, dict): + return False + for key in keys: + entry = auths.get(key) + if not isinstance(entry, dict): + continue + if entry.get("auth") or entry.get("identitytoken") or entry.get("username"): + return True + # Credential-store-managed entries are persisted as an empty object; + # the secret itself lives in the external store. + if creds_store: + return True + return False + + +def _usable_credentials(creds: object) -> bool: + """Report whether a credential entry carries a non-blank username and password. + + Placeholder entries such as ``{"username": "", "password": ""}`` are treated + as "not configured" rather than as credentials, matching + :func:`madengine.deployment.k8s_secrets.build_registry_secret_data`. + """ + if not isinstance(creds, dict): + return False + return bool(str(creds.get("username") or "").strip()) and bool( + str(creds.get("password") or "").strip() + ) + + +def _registry_from_image(image: Optional[str]) -> Optional[str]: + """Extract the registry host from an image reference. + + Args: + image: Image reference (e.g. ``"ghcr.io/org/app:tag"``), or ``None``. + + Returns: + The registry host, or ``None`` when the reference targets Docker Hub, + is malformed, or was not supplied. + """ + if not image or not image.strip(): + return None + ref = image.strip() + if "/" not in ref: + return None + # Docker treats the first component as a registry only when it looks like a + # host; otherwise it is a Docker Hub namespace (e.g. "rocm/private"). + host = ref.split("/")[0] + if not ("." in host or ":" in host or host == "localhost"): + return None + if host.lower() in _DOCKERHUB_ALIASES: + return None + return host + + +def explain_registry_denial( + log_text: str, image: Optional[str] = None +) -> Optional[str]: + """Turn a registry denial in Docker output into an actionable explanation. + + Distinguishes "authenticated but not authorized for this repository" from + "not authenticated at all", because the two need completely different fixes. + + Args: + log_text: Docker build/pull output to inspect. + image: Optional image reference the denial refers to, for the message. + + Returns: + A multi-line hint, or ``None`` if the output shows no registry denial. + """ + lowered = (log_text or "").lower() + subject = image.strip() if image and image.strip() else "the base image" + + if "insufficient_scope" in lowered or "authorization failed" in lowered: + return ( + f"Base image pull was denied: {subject}\n" + " The registry ACCEPTED the credentials but granted no pull scope " + "for this repository.\n" + " This is an authorization problem, not a login problem:\n" + " - the access token is not scoped to this repository, or\n" + " - the repository/tag does not exist under that namespace.\n" + " Re-running `docker login` will not fix it; widen the token's " + "repository scope instead." + ) + + denied = ( + "pull access denied" in lowered + or "requested access to the resource is denied" in lowered + or "authentication required" in lowered + or "unauthorized" in lowered + ) + if not denied: + return None + + registry = _registry_from_image(image) + if registry is None: + fixes = ( + " - `docker login` on this machine (madengine reuses an existing " + "login)\n" + ' - add {"dockerhub": {"username": "...", "password": "..."}} to ' + "credential.json\n" + " - export MAD_DOCKERHUB_USER and MAD_DOCKERHUB_PASSWORD" + ) + else: + fixes = ( + f" - `docker login {registry}` on this machine (madengine reuses " + "an existing login)\n" + f' - add {{"{registry}": {{"username": "...", "password": "..."}}}} ' + "to credential.json" + ) + + return ( + f"Base image pull was denied: {subject}\n" + " No usable credentials were presented to the registry.\n" + " Fix with any one of:\n" + fixes + ) + + def login_to_registry( registry: Optional[str], credentials: Optional[Dict], @@ -103,10 +285,17 @@ def login_to_registry( failure (missing key, invalid format, or docker login error). Set to ``False`` to log and return instead, allowing the caller to fall back to pulling public images. + + Precedence: explicit credentials (``credential.json`` / ``MAD_DOCKERHUB_*``) + win when they carry a non-blank username and password. Otherwise an existing + ``docker login`` on this machine is reused and no login is attempted, so a + placeholder credential entry never overrides or breaks working ambient auth. + Set ``MAD_SKIP_DOCKER_LOGIN=1`` to always defer to ambient credentials. """ - if not credentials: + if os.environ.get("MAD_SKIP_DOCKER_LOGIN") == "1": rich_console.print( - "[yellow]No credentials provided for registry login[/yellow]" + "[yellow]MAD_SKIP_DOCKER_LOGIN=1 - using existing docker login for " + f"{registry or 'DockerHub'}[/yellow]" ) return @@ -116,8 +305,35 @@ def login_to_registry( if registry and registry.lower() == "docker.io": registry_key = "dockerhub" - if registry_key not in credentials: - error_msg = f"No credentials found for registry: {registry_key}" + entry = (credentials or {}).get(registry_key) + creds: Dict = entry if isinstance(entry, dict) else {} + + if not _usable_credentials(creds): + # No explicit credentials configured for this registry. If the machine + # is already logged in (e.g. an organisation access token), reuse that + # instead of failing or clobbering it. + if has_ambient_docker_auth(registry): + rich_console.print( + f"[green]Using existing docker login for " + f"{registry or 'DockerHub'} (no explicit credentials " + f"configured)[/green]" + ) + return + + if not credentials: + rich_console.print( + "[yellow]No credentials provided for registry login[/yellow]" + ) + return + + if registry_key not in credentials: + error_msg = f"No credentials found for registry: {registry_key}" + else: + error_msg = ( + f"Invalid credentials format for registry: {registry_key}" + f"\nCredentials must contain non-empty 'username' and " + f"'password' fields" + ) if registry_key == "dockerhub": error_msg += ( f"\nPlease add dockerhub credentials to credential.json:\n" @@ -140,18 +356,7 @@ def login_to_registry( " }\n" "}" ) - rich_console.print(f"[red]{error_msg}[/red]") - if raise_on_failure: - raise RuntimeError(error_msg) - return - - creds = credentials[registry_key] - - if "username" not in creds or "password" not in creds: - error_msg = ( - f"Invalid credentials format for registry: {registry_key}" - f"\nCredentials must contain 'username' and 'password' fields" - ) + error_msg += "\nAlternatively, run `docker login` on this machine." rich_console.print(f"[red]{error_msg}[/red]") if raise_on_failure: raise RuntimeError(error_msg) diff --git a/src/madengine/core/console.py b/src/madengine/core/console.py index 71105c2f..36e1e161 100644 --- a/src/madengine/core/console.py +++ b/src/madengine/core/console.py @@ -229,6 +229,12 @@ def sh( # Check for failure success = proc.returncode == 0 + # When output is captured rather than streamed it is discarded on + # failure, and the RuntimeError below carries only the command and the + # exit code. Echo it so the log records why the command actually failed. + if not success and not canFail and not secret and not self.live_output and outs: + print(redact_secrets(outs), flush=True) + # Show docker operation completion status if not secret: self._show_docker_completion(command, success) diff --git a/src/madengine/execution/docker_builder.py b/src/madengine/execution/docker_builder.py index bc44aa44..5ee83c47 100644 --- a/src/madengine/execution/docker_builder.py +++ b/src/madengine/execution/docker_builder.py @@ -9,6 +9,7 @@ import os import shlex +import sys from pathlib import Path import time import json @@ -16,7 +17,11 @@ import typing from contextlib import redirect_stdout, redirect_stderr from rich.console import Console as RichConsole -from madengine.core.auth import login_to_registry +from madengine.core.auth import ( + explain_registry_denial, + has_ambient_docker_auth, + login_to_registry, +) from madengine.core.console import Console from madengine.core.context import Context from madengine.utils.ops import PythonicTee @@ -98,6 +103,72 @@ def get_build_arg(self, run_build_arg: typing.Optional[typing.Dict] = None) -> s return build_args + def _resolve_base_docker(self, dockerfile: str) -> str: + """Resolve the base image the Dockerfile builds ``FROM``. + + Prefers a ``BASE_DOCKER`` override from ``docker_build_arg`` context, + otherwise reads ``ARG BASE_DOCKER=`` from the Dockerfile. + + Args: + dockerfile: Path to the Dockerfile. + + Returns: + str: The base image reference, or ``""`` if it cannot be determined. + """ + if ( + "docker_build_arg" in self.context.ctx + and "BASE_DOCKER" in self.context.ctx["docker_build_arg"] + ): + return str(self.context.ctx["docker_build_arg"]["BASE_DOCKER"]) + try: + return str( + self.console.sh( + f"grep '^ARG BASE_DOCKER=' {shlex.quote(dockerfile)} | sed -E 's/ARG BASE_DOCKER=//g'" + ) + ) + except Exception: + return "" + + @staticmethod + def _registry_of(image: str) -> str: + """Return the registry an image reference points at. + + Args: + image: An image reference such as ``rocm/pytorch:latest`` or + ``myhost:5000/team/img:tag``. + + Returns: + str: The registry host, or ``"docker.io"`` for Docker Hub references. + """ + first_segment = (image or "").strip().split("/")[0] + if "." in first_segment or ":" in first_segment or first_segment == "localhost": + return first_segment + return "docker.io" + + def _report_registry_denial(self, log_file_path: str, base_docker: str) -> None: + """Print an actionable hint if a build log shows a registry denial. + + Called from inside the build's stdout redirection, so the hint is written + to the real stdout as well to make sure it reaches the terminal and not + only the build log. + + Args: + log_file_path: Path to the build log written by this build. + base_docker: The base image the build was pulling. + """ + try: + with open(log_file_path, encoding="utf-8", errors="replace") as log: + log_text = log.read() + except OSError: + return + hint = explain_registry_denial(log_text, base_docker) + if not hint: + return + message = f"[bold red]❌ {hint}[/bold red]" + self.rich_console.print(f"\n{message}") + if not self.live_output and sys.__stdout__ is not None: + RichConsole(file=sys.__stdout__).print(f"\n{message}") + def build_image( self, model_info: typing.Dict, @@ -195,8 +266,28 @@ def build_image( with redirect_stdout( PythonicTee(outlog, self.live_output) ), redirect_stderr(PythonicTee(outlog, self.live_output)): + # `docker build --pull` resolves the base image itself, so it only + # works if this machine can authenticate to the base image's + # registry. Log in when — and only when — there is no existing + # login to reuse, so an ambient `docker login` (e.g. an + # organisation access token) is left alone. + base_docker = self._resolve_base_docker(dockerfile) + base_registry = self._registry_of(base_docker) + if credentials and not has_ambient_docker_auth(base_registry): + login_to_registry( + base_registry, + credentials, + console=self.console, + rich_console=self.rich_console, + raise_on_failure=False, + ) + print(f"🔨 Executing build command...") - self.console.sh(build_command, timeout=None) + try: + self.console.sh(build_command, timeout=None) + except Exception: + self._report_registry_denial(log_file_path, base_docker) + raise build_duration = time.time() - build_start_time @@ -206,17 +297,6 @@ def build_image( self.rich_console.print(f"[dim]{'='*80}[/dim]") # Get base docker info - base_docker = "" - if ( - "docker_build_arg" in self.context.ctx - and "BASE_DOCKER" in self.context.ctx["docker_build_arg"] - ): - base_docker = self.context.ctx["docker_build_arg"]["BASE_DOCKER"] - else: - base_docker = self.console.sh( - f"grep '^ARG BASE_DOCKER=' {shlex.quote(dockerfile)} | sed -E 's/ARG BASE_DOCKER=//g'" - ) - print(f"BASE DOCKER is {base_docker}") # Get docker SHA diff --git a/src/madengine/orchestration/build_orchestrator.py b/src/madengine/orchestration/build_orchestrator.py index 246701cf..17e836e8 100644 --- a/src/madengine/orchestration/build_orchestrator.py +++ b/src/madengine/orchestration/build_orchestrator.py @@ -20,7 +20,7 @@ from madengine.core.console import Console from madengine.core.context import Context from madengine.core.additional_context_defaults import apply_build_context_defaults -from madengine.core.auth import load_credentials +from madengine.core.auth import has_ambient_docker_auth, load_credentials from madengine.core.errors import ( BuildError, ConfigurationError, @@ -975,16 +975,33 @@ def _execute_build_on_compute( ) if any(pub_reg in registry_lower for pub_reg in public_registries): if not dockerhub_user or not dockerhub_password: - raise ConfigurationError( - f"Registry credentials required for pushing to {registry}", - context=create_error_context( - operation="build_on_compute", - component="BuildOrchestrator", - additional_info={"registry": registry}, - ), - suggestions=_matched_hints, - ) - self.rich_console.print(f" Auth: Will login to registry before push") + # An existing `docker login` (e.g. an organisation access token) + # is a valid substitute for explicit credentials, so do not hard + # fail on it. The generated sbatch script already falls back to + # "assume pre-authenticated" when no credentials are supplied. + if has_ambient_docker_auth(registry.split("/")[0]): + self.rich_console.print( + " [yellow]Auth: No explicit credentials; relying on the " + "existing docker login[/yellow]" + ) + self.rich_console.print( + " [dim]Note: this login was detected on the submit node. " + "The compute node only shares it if $HOME (or $DOCKER_CONFIG) " + "is on shared storage.[/dim]" + ) + else: + raise ConfigurationError( + f"Registry credentials required for pushing to {registry}", + context=create_error_context( + operation="build_on_compute", + component="BuildOrchestrator", + additional_info={"registry": registry}, + ), + suggestions=_matched_hints + + ["Or run `docker login` on the build node"], + ) + else: + self.rich_console.print(f" Auth: Will login to registry before push") else: # Private/internal registry - may not need auth self.rich_console.print(f" Auth: Private registry (auth may not be required)") diff --git a/src/madengine/scripts/common/post_scripts/trace.sh b/src/madengine/scripts/common/post_scripts/trace.sh index 1e489861..1dbdaaf5 100644 --- a/src/madengine/scripts/common/post_scripts/trace.sh +++ b/src/madengine/scripts/common/post_scripts/trace.sh @@ -33,8 +33,12 @@ rpd) if [ -f "./rocmProfileData/tools/rpd2tracing.py" ]; then echo "RPD post-script: rpd2tracing.py found" if [ -f "trace.rpd" ] && [ -s "trace.rpd" ]; then - python3 ./rocmProfileData/tools/rpd2tracing.py trace.rpd trace.json - mv trace.rpd trace.json "$OUTPUT" + if python3 ./rocmProfileData/tools/rpd2tracing.py trace.rpd trace.json; then + mv trace.rpd trace.json "$OUTPUT" + else + echo "RPD post-script: rpd2tracing.py failed (likely no captured trace data); saving raw trace.rpd only" + mv trace.rpd "$OUTPUT" + fi else echo "RPD post-script: Skipping rpd2tracing.py because trace.rpd is missing or empty" # Create empty files so the directory structure exists diff --git a/src/madengine/scripts/common/pre_scripts/gpu_info_pre.sh b/src/madengine/scripts/common/pre_scripts/gpu_info_pre.sh index 60bd60a0..7903e378 100644 --- a/src/madengine/scripts/common/pre_scripts/gpu_info_pre.sh +++ b/src/madengine/scripts/common/pre_scripts/gpu_info_pre.sh @@ -5,15 +5,19 @@ # gpu_vendor="" -if [ -f "/usr/bin/nvidia-smi" ]; then +if command -v nvidia-smi >/dev/null 2>&1; then echo "NVIDIA GPU detected." gpu_vendor="NVIDIA" gpu_architecture=$(nvidia-smi --query-gpu=name --format=csv,noheader | grep -m 1 -E -o ".{0,1}100"| xargs ) python3 -m pip install nvidia-ml-py -elif [ -f "/opt/rocm/bin/rocm-smi" ]; then +elif command -v rocm-smi >/dev/null 2>&1 || command -v amd-smi >/dev/null 2>&1; then echo "AMD GPU detected." gpu_vendor="AMD" - gpu_architecture=$(rocminfo | grep -o -m 1 'gfx.*' | xargs ) + if command -v rocminfo >/dev/null 2>&1; then + gpu_architecture=$(rocminfo | grep -o -m 1 'gfx.*' | xargs ) + else + echo "rocminfo not found; skipping AMD GPU architecture detection." + fi MI200="gfx90a" MI100="gfx908" MI50="gfx906" diff --git a/src/madengine/scripts/common/tools.json b/src/madengine/scripts/common/tools.json index 82869087..b237c63c 100644 --- a/src/madengine/scripts/common/tools.json +++ b/src/madengine/scripts/common/tools.json @@ -7,10 +7,8 @@ "args": "rpd" } ], - "cmd": "./rocmProfileData/rpd_tracer/runTracer.sh", - "env_vars": { - "LD_LIBRARY_PATH": "./rocmProfileData/rpd_tracer:/opt/rocm/lib" - }, + "cmd": "LD_LIBRARY_PATH=\"./rocmProfileData/rpd_tracer:${ROCM_PATH:-/opt/rocm}/lib:${LD_LIBRARY_PATH}\" ./rocmProfileData/rpd_tracer/runTracer.sh", + "env_vars": {}, "post_scripts": [ { "path": "scripts/common/post_scripts/trace.sh", diff --git a/src/madengine/scripts/common/tools/amd_smi_utils.py b/src/madengine/scripts/common/tools/amd_smi_utils.py index e0e48096..ab245c8e 100644 --- a/src/madengine/scripts/common/tools/amd_smi_utils.py +++ b/src/madengine/scripts/common/tools/amd_smi_utils.py @@ -6,16 +6,18 @@ Copyright (c) Advanced Micro Devices, Inc. All rights reserved. """ +import os import sys import logging from typing import List, Optional, Dict, Any -sys.path.append("/opt/rocm/libexec/amdsmi_cli/") +_ROCM_PATH = os.environ.get("ROCM_PATH", "/opt/rocm") +sys.path.append(f"{_ROCM_PATH}/libexec/amdsmi_cli/") try: from amdsmi_init import amdsmi_interface from amdsmi_init import amdsmi_cli_init, amdsmi_cli_shutdown except ImportError: - raise ImportError("Could not import /opt/rocm/libexec/amdsmi_cli/amdsmi_init.py") + raise ImportError(f"Could not import {_ROCM_PATH}/libexec/amdsmi_cli/amdsmi_init.py") class ProfUtils: diff --git a/src/madengine/scripts/common/tools/gpu_info_profiler.py b/src/madengine/scripts/common/tools/gpu_info_profiler.py index 111f655d..14949954 100644 --- a/src/madengine/scripts/common/tools/gpu_info_profiler.py +++ b/src/madengine/scripts/common/tools/gpu_info_profiler.py @@ -14,6 +14,7 @@ import sys import csv import os +import shutil import logging import typing import signal @@ -27,10 +28,11 @@ def check_amd_smi_available() -> bool: bool: True if amd-smi is available, False otherwise. """ # First check for Python bindings (more reliable for programmatic access) + rocm_path = os.environ.get("ROCM_PATH", "/opt/rocm") try: - sys.path.append("/opt/rocm/libexec/amdsmi_cli/") + sys.path.append(f"{rocm_path}/libexec/amdsmi_cli/") from amdsmi_init import amdsmi_interface - logging.debug("amd-smi Python bindings found at /opt/rocm/libexec/amdsmi_cli/") + logging.debug(f"amd-smi Python bindings found at {rocm_path}/libexec/amdsmi_cli/") return True except ImportError: logging.debug("amd-smi Python bindings not found") @@ -74,9 +76,11 @@ def get_rocm_version() -> Optional[float]: logging.debug(f"hipconfig check failed: {e}") try: - # Fallback to /opt/rocm/.info/version - if os.path.exists("/opt/rocm/.info/version"): - result = subprocess.run(['cat', '/opt/rocm/.info/version'], + # Fallback to $ROCM_PATH/.info/version + rocm_path = os.environ.get("ROCM_PATH", "/opt/rocm") + version_file = f"{rocm_path}/.info/version" + if os.path.exists(version_file): + result = subprocess.run(['cat', version_file], capture_output=True, text=True, timeout=10) if result.returncode == 0: version_str = result.stdout.strip().split('-')[0] # Remove build suffix @@ -96,15 +100,21 @@ def detect_gpu_vendor() -> tuple[bool, bool]: Raises: ValueError: If no GPU management tools are found. """ - if os.path.exists("/usr/bin/nvidia-smi"): + rocm_path = os.environ.get("ROCM_PATH", "/opt/rocm") + if shutil.which("nvidia-smi"): return True, False - elif os.path.exists("/opt/rocm/bin/rocm-smi") or check_amd_smi_available(): + elif ( + shutil.which("rocm-smi") + or os.path.exists(f"{rocm_path}/bin/rocm-smi") + or check_amd_smi_available() + ): return False, True else: error_msg = ( "Unable to detect GPU vendor. No GPU management tools found.\n" - "For NVIDIA: /usr/bin/nvidia-smi not found\n" - "For AMD: /opt/rocm/bin/rocm-smi and amd-smi not found\n\n" + "For NVIDIA: nvidia-smi not found on PATH\n" + f"For AMD: rocm-smi not found on PATH or in {rocm_path}/bin, " + "and amd-smi not available\n\n" "Please ensure:\n" " 1. GPU drivers are installed\n" " 2. For AMD GPUs: ROCm is properly installed (https://rocm.docs.amd.com)\n" diff --git a/src/madengine/scripts/common/tools/rocm_smi_utils.py b/src/madengine/scripts/common/tools/rocm_smi_utils.py index dd73219b..0ef5772f 100644 --- a/src/madengine/scripts/common/tools/rocm_smi_utils.py +++ b/src/madengine/scripts/common/tools/rocm_smi_utils.py @@ -6,16 +6,18 @@ Copyright (c) Advanced Micro Devices, Inc. All rights reserved. """ +import os import sys import logging from typing import List -sys.path.append("/opt/rocm/libexec/rocm_smi/") +_ROCM_PATH = os.environ.get("ROCM_PATH", "/opt/rocm") +sys.path.append(f"{_ROCM_PATH}/libexec/rocm_smi/") try: import rocm_smi from rsmiBindings import * except ImportError: - raise ImportError("Could not import /opt/rocm/libexec/rocm_smi/rocm_smi.py") + raise ImportError(f"Could not import {_ROCM_PATH}/libexec/rocm_smi/rocm_smi.py") class ProfUtils: diff --git a/tests/unit/test_auth.py b/tests/unit/test_auth.py index 47d4a6c7..d2bf2fd8 100644 --- a/tests/unit/test_auth.py +++ b/tests/unit/test_auth.py @@ -1,10 +1,16 @@ """Unit tests for madengine.core.auth module.""" +import json import os import pytest from unittest.mock import MagicMock, mock_open, patch -from madengine.core.auth import load_credentials, login_to_registry +from madengine.core.auth import ( + explain_registry_denial, + has_ambient_docker_auth, + load_credentials, + login_to_registry, +) class TestLoadCredentials: @@ -115,21 +121,23 @@ def test_load_credentials_non_dockerhub_registry(self, mock_file, mock_exists): assert result["custom_registry"]["token"] == "abc123" +@patch.dict(os.environ, {"MAD_SKIP_DOCKER_LOGIN": ""}, clear=False) +@patch("madengine.core.auth.has_ambient_docker_auth", return_value=False) class TestLoginToRegistry: - """Tests for login_to_registry().""" + """Tests for login_to_registry() when the machine has no existing docker login.""" def _mocks(self): console = MagicMock() rich_console = MagicMock() return console, rich_console - def test_no_credentials_returns_early(self): + def test_no_credentials_returns_early(self, mock_ambient): """Passing None credentials logs a warning and returns without error.""" console, rich_console = self._mocks() login_to_registry("docker.io", None, console, rich_console) console.sh.assert_not_called() - def test_missing_registry_key_raises_when_raise_on_failure(self): + def test_missing_registry_key_raises_when_raise_on_failure(self, mock_ambient): """RuntimeError raised when registry key absent and raise_on_failure=True.""" console, rich_console = self._mocks() credentials = {"other_registry": {"username": "u", "password": "p"}} @@ -137,14 +145,14 @@ def test_missing_registry_key_raises_when_raise_on_failure(self): login_to_registry("myregistry.io", credentials, console, rich_console, raise_on_failure=True) console.sh.assert_not_called() - def test_missing_registry_key_returns_when_not_raise_on_failure(self): + def test_missing_registry_key_returns_when_not_raise_on_failure(self, mock_ambient): """Returns silently when registry key absent and raise_on_failure=False.""" console, rich_console = self._mocks() credentials = {"other_registry": {"username": "u", "password": "p"}} login_to_registry("myregistry.io", credentials, console, rich_console, raise_on_failure=False) console.sh.assert_not_called() - def test_invalid_credentials_format_raises(self): + def test_invalid_credentials_format_raises(self, mock_ambient): """RuntimeError raised when username/password fields missing.""" console, rich_console = self._mocks() credentials = {"dockerhub": {"token": "abc"}} @@ -152,14 +160,22 @@ def test_invalid_credentials_format_raises(self): login_to_registry("docker.io", credentials, console, rich_console, raise_on_failure=True) console.sh.assert_not_called() - def test_invalid_credentials_format_returns_when_not_raise_on_failure(self): + def test_invalid_credentials_format_returns_when_not_raise_on_failure(self, mock_ambient): """Returns silently when credentials format invalid and raise_on_failure=False.""" console, rich_console = self._mocks() credentials = {"dockerhub": {"token": "abc"}} login_to_registry("docker.io", credentials, console, rich_console, raise_on_failure=False) console.sh.assert_not_called() - def test_docker_io_normalised_to_dockerhub(self): + def test_blank_credentials_raise_without_ambient_auth(self, mock_ambient): + """Placeholder credentials are treated as absent, not as credentials.""" + console, rich_console = self._mocks() + credentials = {"dockerhub": {"repository": "r", "username": "", "password": ""}} + with pytest.raises(RuntimeError, match="username|password"): + login_to_registry("docker.io", credentials, console, rich_console, raise_on_failure=True) + console.sh.assert_not_called() + + def test_docker_io_normalised_to_dockerhub(self, mock_ambient): """docker.io registry is looked up under the 'dockerhub' key.""" console, rich_console = self._mocks() credentials = {"dockerhub": {"username": "user", "password": "pass"}} @@ -169,7 +185,7 @@ def test_docker_io_normalised_to_dockerhub(self): # docker.io should not appear in the login command (uses default DockerHub endpoint) assert "docker.io" not in cmd - def test_custom_registry_included_in_command(self): + def test_custom_registry_included_in_command(self, mock_ambient): """Non-DockerHub registry URL is included in the login command.""" console, rich_console = self._mocks() credentials = {"myregistry.io": {"username": "user", "password": "pass"}} @@ -178,7 +194,7 @@ def test_custom_registry_included_in_command(self): cmd = console.sh.call_args[0][0] assert "myregistry.io" in cmd - def test_login_failure_raises_when_raise_on_failure(self): + def test_login_failure_raises_when_raise_on_failure(self, mock_ambient): """docker login error is re-raised when raise_on_failure=True.""" console, rich_console = self._mocks() console.sh.side_effect = RuntimeError("auth failed") @@ -186,10 +202,182 @@ def test_login_failure_raises_when_raise_on_failure(self): with pytest.raises(RuntimeError, match="auth failed"): login_to_registry(None, credentials, console, rich_console, raise_on_failure=True) - def test_login_failure_suppressed_when_not_raise_on_failure(self): + def test_login_failure_suppressed_when_not_raise_on_failure(self, mock_ambient): """docker login error is suppressed when raise_on_failure=False.""" console, rich_console = self._mocks() console.sh.side_effect = RuntimeError("auth failed") credentials = {"dockerhub": {"username": "user", "password": "pass"}} login_to_registry(None, credentials, console, rich_console, raise_on_failure=False) # Should not propagate the exception + + +@patch.dict(os.environ, {"MAD_SKIP_DOCKER_LOGIN": ""}, clear=False) +class TestLoginToRegistryWithAmbientAuth: + """Tests for login_to_registry() when the machine already has a docker login.""" + + def _mocks(self): + return MagicMock(), MagicMock() + + @patch("madengine.core.auth.has_ambient_docker_auth", return_value=True) + def test_blank_credentials_defer_to_ambient_auth(self, mock_ambient): + """Blank credentials never override or break an existing docker login.""" + console, rich_console = self._mocks() + credentials = {"dockerhub": {"repository": "r", "username": "", "password": ""}} + # No raise even with raise_on_failure=True: the machine is authenticated. + login_to_registry("docker.io", credentials, console, rich_console, raise_on_failure=True) + console.sh.assert_not_called() + + @patch("madengine.core.auth.has_ambient_docker_auth", return_value=True) + def test_missing_registry_key_defers_to_ambient_auth(self, mock_ambient): + """A registry with no credential.json entry falls back to the existing login.""" + console, rich_console = self._mocks() + credentials = {"other_registry": {"username": "u", "password": "p"}} + login_to_registry("myregistry.io", credentials, console, rich_console, raise_on_failure=True) + console.sh.assert_not_called() + + @patch("madengine.core.auth.has_ambient_docker_auth", return_value=True) + def test_explicit_credentials_win_over_ambient_auth(self, mock_ambient): + """Usable explicit credentials still trigger a login (explicit wins).""" + console, rich_console = self._mocks() + credentials = {"dockerhub": {"username": "user", "password": "pass"}} + login_to_registry("docker.io", credentials, console, rich_console) + console.sh.assert_called_once() + assert "--username user" in console.sh.call_args[0][0] + + @patch("madengine.core.auth.has_ambient_docker_auth", return_value=False) + def test_whitespace_only_credentials_are_not_credentials(self, mock_ambient): + """Whitespace-only values are treated as blank.""" + console, rich_console = self._mocks() + credentials = {"dockerhub": {"username": " ", "password": "\t"}} + with pytest.raises(RuntimeError, match="username|password"): + login_to_registry("docker.io", credentials, console, rich_console, raise_on_failure=True) + console.sh.assert_not_called() + + +class TestSkipDockerLogin: + """Tests for the MAD_SKIP_DOCKER_LOGIN escape hatch.""" + + @patch.dict(os.environ, {"MAD_SKIP_DOCKER_LOGIN": "1"}, clear=False) + def test_skip_env_var_bypasses_login(self): + """MAD_SKIP_DOCKER_LOGIN=1 defers to ambient credentials unconditionally.""" + console, rich_console = MagicMock(), MagicMock() + credentials = {"dockerhub": {"username": "user", "password": "pass"}} + login_to_registry("docker.io", credentials, console, rich_console, raise_on_failure=True) + console.sh.assert_not_called() + + +class TestHasAmbientDockerAuth: + """Tests for has_ambient_docker_auth().""" + + def _write_config(self, tmp_path, config): + (tmp_path / "config.json").write_text(json.dumps(config), encoding="utf-8") + return {"DOCKER_CONFIG": str(tmp_path)} + + def test_dockerhub_auth_entry_detected(self, tmp_path): + """A Docker Hub entry with an auth blob counts as authenticated.""" + env = self._write_config( + tmp_path, {"auths": {"https://index.docker.io/v1/": {"auth": "abc123"}}} + ) + with patch.dict(os.environ, env, clear=False): + assert has_ambient_docker_auth(None) is True + assert has_ambient_docker_auth("docker.io") is True + assert has_ambient_docker_auth("docker.io/rocm/mad-private") is True + assert has_ambient_docker_auth("myregistry.io") is False + + def test_identity_token_entry_detected(self, tmp_path): + """An identitytoken-only entry counts as authenticated.""" + env = self._write_config( + tmp_path, {"auths": {"index.docker.io": {"identitytoken": "tok"}}} + ) + with patch.dict(os.environ, env, clear=False): + assert has_ambient_docker_auth("docker.io") is True + + def test_cred_helper_detected(self, tmp_path): + """A credHelpers entry counts as authenticated.""" + env = self._write_config(tmp_path, {"credHelpers": {"myregistry.io": "ecr-login"}}) + with patch.dict(os.environ, env, clear=False): + assert has_ambient_docker_auth("myregistry.io/team/img") is True + assert has_ambient_docker_auth("docker.io") is False + + def test_creds_store_with_empty_auth_entry(self, tmp_path): + """credsStore-managed entries are stored empty but are still credentials.""" + env = self._write_config( + tmp_path, + {"credsStore": "desktop", "auths": {"https://index.docker.io/v1/": {}}}, + ) + with patch.dict(os.environ, env, clear=False): + assert has_ambient_docker_auth("docker.io") is True + assert has_ambient_docker_auth("other.io") is False + + def test_empty_auth_entry_without_creds_store(self, tmp_path): + """An empty entry with no credential store is not usable.""" + env = self._write_config(tmp_path, {"auths": {"https://index.docker.io/v1/": {}}}) + with patch.dict(os.environ, env, clear=False): + assert has_ambient_docker_auth("docker.io") is False + + def test_registry_with_port_matches_host(self, tmp_path): + """host:port registries are matched on the host segment.""" + env = self._write_config(tmp_path, {"auths": {"localhost:5000": {"auth": "x"}}}) + with patch.dict(os.environ, env, clear=False): + assert has_ambient_docker_auth("localhost:5000/team/img") is True + + def test_missing_config_returns_false(self, tmp_path): + """A missing config.json yields False rather than raising.""" + with patch.dict(os.environ, {"DOCKER_CONFIG": str(tmp_path)}, clear=False): + assert has_ambient_docker_auth("docker.io") is False + + def test_corrupt_config_returns_false(self, tmp_path): + """A corrupt config.json yields False rather than raising.""" + (tmp_path / "config.json").write_text("not json{{{", encoding="utf-8") + with patch.dict(os.environ, {"DOCKER_CONFIG": str(tmp_path)}, clear=False): + assert has_ambient_docker_auth("docker.io") is False + + +class TestExplainRegistryDenial: + """Tests for explain_registry_denial().""" + + def test_insufficient_scope_is_an_authorization_problem(self): + """insufficient_scope is reported as authorization, not authentication.""" + log = ( + "#6 ERROR: pull access denied, repository does not exist or may require " + "authorization: server message: insufficient_scope: authorization failed" + ) + hint = explain_registry_denial(log, "rocm/triton-inference-server-dev:x") + assert hint is not None + assert "authorization problem" in hint + assert "rocm/triton-inference-server-dev:x" in hint + assert "will not fix it" in hint + + def test_plain_denial_points_at_login(self): + """A denial without insufficient_scope points at supplying credentials.""" + log = "Error response from daemon: pull access denied for rocm/private" + hint = explain_registry_denial(log, "rocm/private:latest") + assert hint is not None + assert "docker login" in hint + assert "authorization problem" not in hint + + def test_non_dockerhub_denial_names_that_registry(self): + """A denial for another registry does not suggest Docker Hub credentials.""" + log = "Error response from daemon: pull access denied for ghcr.io/org/app" + hint = explain_registry_denial(log, "ghcr.io/org/app:latest") + assert hint is not None + assert "docker login ghcr.io" in hint + assert '"ghcr.io": {"username"' in hint + assert "dockerhub" not in hint + assert "MAD_DOCKERHUB" not in hint + + def test_dockerhub_namespace_still_suggests_dockerhub(self): + """A bare namespace/repo reference is Docker Hub, not a registry host.""" + log = "Error response from daemon: pull access denied for rocm/private" + hint = explain_registry_denial(log, "rocm/private:latest") + assert hint is not None + assert '"dockerhub"' in hint + assert "MAD_DOCKERHUB_USER" in hint + + def test_unrelated_failure_returns_none(self): + """Non-registry build failures produce no hint.""" + assert explain_registry_denial("RUN apt-get install failed: exit code 100") is None + + def test_empty_log_returns_none(self): + """Empty output produces no hint.""" + assert explain_registry_denial("") is None From c5b016f687a58500d5a6209917bbc2c8651e72c0 Mon Sep 17 00:00:00 2001 From: mkuznet1 Date: Tue, 18 Aug 2026 15:37:20 +0200 Subject: [PATCH 03/16] fix(slurm): make the generated job script portable across clusters (#163) * feat(slurm): allow opting out of the --gpus-per-node sbatch directive Clusters that do not advertise GPU GRES reject any job script carrying --gpus-per-node, so the generated sbatch fails before launch. Add slurm.skip_gpus_directive (default false) to omit the directive and rely on exclusive/nproc_per_node instead. Co-authored-by: Cursor * fix(slurm): raise the madengine availability probe timeout The pre-submission check ran `madengine --version` with a 5s timeout, which a cold interpreter start off shared/NFS storage exceeds, aborting submission on a perfectly healthy environment. Raise it so the probe only catches a hang. Co-authored-by: Cursor * fix(run): cap the informational rocm-libs package query The node-info step shelled out to the host package manager with no time limit. On a node where yum wants to import a repo GPG key the command waits on a prompt that never arrives, so the whole multi-node run hangs before the workload starts. Co-authored-by: Cursor * fix(slurm): inherit the submitter's PATH in the sbatch job A batch job is not guaranteed to inherit the submitter's PATH: a site can default sbatch to --export=NONE, and the module loads in the job body can rewrite it. The pre-submission check then passes on the login node while the compute node aborts with "madengine not found in PATH". Render the per-user bin directory and the directory the madengine console script was resolved from at submission time into the generated script, so the job puts the same interpreter back on PATH instead of relying on inheritance. * fix(slurm): match nfs4 in the shared-filesystem probe The single-node workspace probe matched \bnfs\b only, but df -T reports nfs4 on most modern NFS mounts. A shared submission directory was therefore classified as node-local and the job copied the whole project into /tmp instead of using the shared path. Match \bnfs[0-9]*\b so nfs, nfs3 and nfs4 are all recognized. The rendered job script now also has coverage for the --gpus-per-node opt-out it grew earlier in this batch: skip_gpus_directive shipped without tests, so nothing failed if the directive crept back into the template. Both states of the flag are asserted against the rendered script. * fix(slurm): read the filesystem type, not the whole df line The shared-filesystem probe grepped the entire `df -T` output line, which carries the mount point as well as the type. A local disk mounted at a path such as /mnt/nfs-scratch therefore matched, the submission directory was classified as shared, and the single-node job worked out of storage the other side of the run could not see. Read the type column alone via `df --output=fstype` and anchor the pattern to it. The option is GNU coreutils 8.21 and up, so an awk fallback over `df -T` covers older systems. beegfs and panfs join the list of shared types while the pattern is being rewritten; both are common enough on HPC sites to be worth recognizing. --------- Co-authored-by: Mikhail Kuznetsov Co-authored-by: Cursor Co-authored-by: Stephen Shao --- src/madengine/deployment/slurm.py | 30 ++- .../deployment/templates/slurm/job.sh.j2 | 26 ++- .../orchestration/run_orchestrator.py | 11 +- tests/unit/test_slurm_job_template.py | 201 ++++++++++++++++++ 4 files changed, 260 insertions(+), 8 deletions(-) create mode 100644 tests/unit/test_slurm_job_template.py diff --git a/src/madengine/deployment/slurm.py b/src/madengine/deployment/slurm.py index af99c08d..088a3fb2 100644 --- a/src/madengine/deployment/slurm.py +++ b/src/madengine/deployment/slurm.py @@ -13,6 +13,7 @@ import os import shlex +import shutil import subprocess import time from pathlib import Path @@ -77,6 +78,8 @@ def __init__(self, config: DeploymentConfig): self.time_limit = self.slurm_config.get("time", "24:00:00") self.output_dir = Path(self.slurm_config.get("output_dir", "./slurm_results")) self.reservation = self.slurm_config.get("reservation", None) + # Some clusters expose no GPU GRES, so sbatch rejects --gpus-per-node. + self.skip_gpus_directive = self.slurm_config.get("skip_gpus_directive", False) # Setup Jinja2 template engine template_dir = Path(__file__).parent / "templates" / "slurm" @@ -226,6 +229,22 @@ def validate(self) -> bool: self.console.print("[green]✓ SLURM environment validated[/green]") return True + @staticmethod + def _submission_bin_dir() -> Optional[str]: + """ + Directory the madengine console script was resolved from at submission time. + + A batch job is not guaranteed to inherit the submitter's PATH: a site can + default sbatch to --export=NONE, and `module load` can rewrite PATH before + the job body runs. Passing the directory into the job script lets it put + the same madengine back on PATH instead of relying on inheritance. + + Returns: + Optional[str]: absolute directory, or None if madengine is not on PATH + """ + cli_path = shutil.which("madengine") + return str(Path(cli_path).resolve().parent) if cli_path else None + def _validate_cli_availability(self) -> bool: """ Validate madengine is available before job submission. @@ -241,7 +260,9 @@ def _validate_cli_availability(self) -> bool: ["madengine", "--version"], capture_output=True, text=True, - timeout=5, + # A cold import off shared/NFS storage can take far longer than a + # local one, so this only guards against a hung interpreter. + timeout=600, check=False ) if result.returncode == 0: @@ -456,7 +477,10 @@ def _prepare_slurm_multi_script(self, model_info: Dict, docker_image_name: str = f"#SBATCH --partition={self.partition}", f"#SBATCH --nodes={self.nodes}", f"#SBATCH --ntasks={self.nodes}", - f"#SBATCH --gpus-per-node={self.gpus_per_node}", + ] + if not self.skip_gpus_directive: + script_lines.append(f"#SBATCH --gpus-per-node={self.gpus_per_node}") + script_lines += [ f"#SBATCH --time={self.time_limit}", ] # Honour user-configured exclusivity (defaults to True to match the standard SLURM template). @@ -669,6 +693,7 @@ def debug(self, msg): "partition": self.partition, "nodes": self.nodes, "gpus_per_node": resolved_gpus_per_node, # Use resolved GPU count + "skip_gpus_directive": self.skip_gpus_directive, "time_limit": self.time_limit, "output_dir": str(self.output_dir), "master_port": master_port, @@ -682,6 +707,7 @@ def debug(self, msg): "qos": self.slurm_config.get("qos"), "account": self.slurm_config.get("account"), "modules": self.slurm_config.get("modules", []), + "submission_bin_dir": self._submission_bin_dir(), "env_vars": self.config.additional_context.get("env_vars", {}), "shared_workspace": self.slurm_config.get("shared_workspace"), "shared_data": self.config.additional_context.get("shared_data"), diff --git a/src/madengine/deployment/templates/slurm/job.sh.j2 b/src/madengine/deployment/templates/slurm/job.sh.j2 index 3b236d9f..4cdddba0 100644 --- a/src/madengine/deployment/templates/slurm/job.sh.j2 +++ b/src/madengine/deployment/templates/slurm/job.sh.j2 @@ -6,7 +6,8 @@ #SBATCH --nodes={{ nodes }} #SBATCH --ntasks={{ nodes }} #SBATCH --ntasks-per-node=1 -#SBATCH --gpus-per-node={{ gpus_per_node }} +{% if not skip_gpus_directive %}#SBATCH --gpus-per-node={{ gpus_per_node }} +{% endif %} #SBATCH --time={{ time_limit }} {% if reservation %} #SBATCH --reservation={{ reservation }} @@ -41,6 +42,19 @@ module load {{ module }} {% endfor %} +# ============================================================================= +# PATH +# ============================================================================= +# A batch job does not reliably inherit the submitter's PATH: a site can default +# sbatch to --export=NONE, and the module loads above can rewrite it. Without +# this the madengine console script is missing on the compute node even though +# the pre-submission check found it. Re-add the per-user bin directory and the +# directory madengine itself was resolved from at submission time. +export PATH="$HOME/.local/bin:$PATH" +{% if submission_bin_dir %} +export PATH="{{ submission_bin_dir }}:$PATH" +{% endif %} + # ============================================================================= # Environment Setup (Standard ML Environment Variables) # ============================================================================= @@ -141,7 +155,15 @@ echo "Submission directory: {{ manifest_file | dirname }}" # Single-node: Prefer shared storage (submission dir), with local fallback if needed # Check if submission directory is on shared filesystem SUBMIT_DIR={{ manifest_file | dirname }} -if df -T "$SUBMIT_DIR" 2>/dev/null | grep -qE '\bnfs\b|\blustre\b|\bgpfs\b|\bceph\b'; then +# Ask df for the filesystem type alone. Matching the whole df -T line reads the mount +# point too, so a local disk under a path such as /mnt/nfs-scratch answered yes and the +# job then trusted node-local storage to be shared. --output=fstype is GNU coreutils +# 8.21 and up; the awk form is the fallback for anything older. +SUBMIT_FSTYPE=$(df --output=fstype "$SUBMIT_DIR" 2>/dev/null | tail -n 1) +if [ -z "$SUBMIT_FSTYPE" ]; then + SUBMIT_FSTYPE=$(df -T "$SUBMIT_DIR" 2>/dev/null | awk 'NR > 1 { print $2; exit }') +fi +if printf '%s' "$SUBMIT_FSTYPE" | grep -qE '^(nfs[0-9]*|lustre|gpfs|ceph|beegfs|panfs)$'; then # Submission directory is on shared storage - use it directly (best practice) WORKSPACE=$SUBMIT_DIR WORKSPACE_TYPE="shared-nfs" diff --git a/src/madengine/orchestration/run_orchestrator.py b/src/madengine/orchestration/run_orchestrator.py index b0338d8c..498401c4 100644 --- a/src/madengine/orchestration/run_orchestrator.py +++ b/src/madengine/orchestration/run_orchestrator.py @@ -765,14 +765,17 @@ def _show_node_info(self): self.console.sh("echo 'MAD Run Models'") host_os = self.context.ctx.get("host_os", "") + # This is purely informational, but a package manager can block forever on + # an interactive prompt (e.g. yum asking to import a repo GPG key) with no + # tty to answer it, so every query is capped. if "HOST_UBUNTU" in host_os: - print(self.console.sh("apt show rocm-libs -a", canFail=True)) + print(self.console.sh("timeout 10 apt show rocm-libs -a", canFail=True)) elif "HOST_CENTOS" in host_os: - print(self.console.sh("yum info rocm-libs", canFail=True)) + print(self.console.sh("timeout 10 yum info rocm-libs", canFail=True)) elif "HOST_SLES" in host_os: - print(self.console.sh("zypper info rocm-libs", canFail=True)) + print(self.console.sh("timeout 10 zypper info rocm-libs", canFail=True)) elif "HOST_AZURE" in host_os: - print(self.console.sh("tdnf info rocm-libs", canFail=True)) + print(self.console.sh("timeout 10 tdnf info rocm-libs", canFail=True)) else: self.rich_console.print("[yellow]Warning: Unable to detect host OS[/yellow]") diff --git a/tests/unit/test_slurm_job_template.py b/tests/unit/test_slurm_job_template.py new file mode 100644 index 00000000..6ce19628 --- /dev/null +++ b/tests/unit/test_slurm_job_template.py @@ -0,0 +1,201 @@ +#!/usr/bin/env python3 +""" +Unit tests for the generated SLURM job script (`job.sh.j2`). + +Locks in the portability contract points that clusters keep re-discovering +downstream (see ROCm/rocm-systems#9055, which patched madengine's source +rather than filing them): + +1. The job script puts madengine back on PATH itself instead of assuming the + batch environment inherited the submitter's PATH. +2. The shared-filesystem probe recognizes `nfs4`, which is what `df -T` + reports on most modern NFS mounts. +3. `slurm.skip_gpus_directive` removes `#SBATCH --gpus-per-node`, which a + cluster advertising no GPU GRES rejects outright. + +Copyright (c) Advanced Micro Devices, Inc. All rights reserved. +""" + +import json +import re +from pathlib import Path +from unittest.mock import patch + +import pytest + +from madengine.deployment.base import DeploymentConfig +from madengine.deployment.slurm import SlurmDeployment + + +MODEL_ENTRY = { + "name": "dummy_torchrun_multinode", + "url": "", + "dockerfile": "docker/dummy", + "scripts": "scripts/dummy/run.sh", + "n_gpus": "8", + "owner": "mad.support@amd.com", + "training_precision": "", + "tags": ["pyt", "training"], + "timeout": -1, + "args": "", +} + + +def _build_deployment( + tmp_path: Path, + slurm_overrides: dict = None, + distributed_overrides: dict = None, +) -> SlurmDeployment: + """SlurmDeployment over a minimal torchrun manifest, output_dir under tmp_path.""" + manifest = { + "built_images": {"dummy-image": {"docker_image": "dummy:latest"}}, + "built_models": {"dummy-image": MODEL_ENTRY}, + "context": { + "docker_env_vars": {}, + "docker_mounts": {}, + "docker_build_arg": {}, + "gpu_vendor": "AMD", + "guest_os": "UBUNTU", + "docker_gpus": "all", + }, + } + manifest_path = tmp_path / "build_manifest.json" + manifest_path.write_text(json.dumps(manifest)) + + slurm_config = { + "partition": "test-partition", + "nodes": 2, + "gpus_per_node": 8, + "time": "01:00:00", + "output_dir": str(tmp_path / "slurm_output"), + "exclusive": True, + } + slurm_config.update(slurm_overrides or {}) + + distributed_config = { + "launcher": "torchrun", + "nnodes": 2, + "nproc_per_node": 8, + "backend": "nccl", + "port": 29500, + } + distributed_config.update(distributed_overrides or {}) + + cfg = DeploymentConfig( + target="slurm", + manifest_file=str(manifest_path), + additional_context={ + "deploy": "slurm", + "gpu_vendor": "AMD", + "guest_os": "UBUNTU", + "slurm": slurm_config, + "distributed": distributed_config, + }, + ) + return SlurmDeployment(cfg) + + +def _render(deployment: SlurmDeployment) -> str: + """Render job.sh.j2 exactly as prepare() does, without submitting anything.""" + context = deployment._prepare_template_context(MODEL_ENTRY) + return deployment.jinja_env.get_template("job.sh.j2").render(**context) + + +# --------------------------------------------------------------------------- +# 1. PATH is re-established inside the job + +class TestJobScriptPath: + """The job script must not depend on the submitter's PATH being inherited.""" + + def test_user_bin_dir_is_prepended(self, tmp_path): + script = _render(_build_deployment(tmp_path)) + assert 'export PATH="$HOME/.local/bin:$PATH"' in script + + def test_submission_bin_dir_is_prepended(self, tmp_path): + with patch("madengine.deployment.slurm.shutil.which", return_value="/opt/venv/bin/madengine"): + script = _render(_build_deployment(tmp_path)) + assert 'export PATH="/opt/venv/bin:$PATH"' in script + + def test_no_empty_export_when_cli_not_on_path(self, tmp_path): + """madengine missing at submission time must not render an empty PATH entry.""" + with patch("madengine.deployment.slurm.shutil.which", return_value=None): + script = _render(_build_deployment(tmp_path)) + assert 'export PATH=":$PATH"' not in script + assert 'export PATH="$HOME/.local/bin:$PATH"' in script + + def test_path_is_set_before_madengine_is_looked_up(self, tmp_path): + """The export is useless if it lands after `command -v madengine`.""" + with patch("madengine.deployment.slurm.shutil.which", return_value="/opt/venv/bin/madengine"): + script = _render(_build_deployment(tmp_path)) + assert script.index('export PATH="/opt/venv/bin:$PATH"') < script.index("command -v madengine") + + +# --------------------------------------------------------------------------- +# 2. Shared-filesystem probe + +class TestSharedFilesystemProbe: + """`df -T` reports nfs4 on modern mounts; the probe must not miss it. + + And it must read the filesystem type, nothing else: the mount point travels on the + same `df -T` line, so a local disk under a path such as /mnt/nfs-scratch used to answer + yes and the job then trusted node-local storage to be visible from every node. + """ + + @staticmethod + def _probe_pattern(script: str) -> str: + match = re.search(r"SUBMIT_FSTYPE\"?\s*\|\s*grep -qE '([^']+)'", script) + assert match, "shared-filesystem probe not found in rendered script" + return match.group(1) + + @pytest.mark.parametrize("fstype,expected", [ + ("nfs", True), + ("nfs3", True), + ("nfs4", True), + ("lustre", True), + ("gpfs", True), + ("ceph", True), + ("beegfs", True), + ("panfs", True), + ("ext4", False), + ("xfs", False), + ("overlay", False), + ("tmpfs", False), + ]) + def test_probe_matches_shared_filesystems(self, tmp_path, fstype, expected): + # The probe only exists on the single-node branch of the template. + deployment = _build_deployment(tmp_path, {"nodes": 1}, {"nnodes": 1}) + pattern = self._probe_pattern(_render(deployment)) + assert bool(re.search(pattern, fstype)) is expected + + def test_the_probe_reads_the_fstype_column_only(self, tmp_path): + script = _render(_build_deployment(tmp_path, {"nodes": 1}, {"nnodes": 1})) + assert 'df --output=fstype "$SUBMIT_DIR"' in script + assert 'df -T "$SUBMIT_DIR" 2>/dev/null | grep' not in script + + def test_a_mount_point_that_says_nfs_does_not_make_a_disk_shared(self, tmp_path): + """/mnt/nfs-scratch on ext4 is local, whatever its name suggests.""" + script = _render(_build_deployment(tmp_path, {"nodes": 1}, {"nnodes": 1})) + pattern = self._probe_pattern(script) + df_line = "/dev/nvme0n1p2 ext4 104857600 50106368 54751232 48% /mnt/nfs-scratch" + assert re.search(pattern, df_line) is None + assert re.search(pattern, "ext4") is None + + def test_there_is_a_fallback_for_df_without_output(self, tmp_path): + """--output is coreutils 8.21; older df still has to be read correctly.""" + script = _render(_build_deployment(tmp_path, {"nodes": 1}, {"nnodes": 1})) + assert "awk 'NR > 1 { print $2; exit }'" in script + + +# --------------------------------------------------------------------------- +# 3. GPU GRES directive opt-out + +class TestGpusPerNodeDirective: + """A cluster with GresTypes=(null) rejects any job carrying --gpus-per-node.""" + + def test_directive_present_by_default(self, tmp_path): + script = _render(_build_deployment(tmp_path)) + assert "#SBATCH --gpus-per-node=8" in script + + def test_directive_omitted_when_opted_out(self, tmp_path): + script = _render(_build_deployment(tmp_path, {"skip_gpus_directive": True})) + assert "--gpus-per-node" not in script From 4f281efed4738641dc4a79fe32caccd0d90fb72d Mon Sep 17 00:00:00 2001 From: Stephen Shao Date: Wed, 19 Aug 2026 21:29:34 -0400 Subject: [PATCH 04/16] docs: rewrite README as concise landing page and add architecture diagrams (#161) * docs(readme): rewrite README as concise landing page with Mermaid figures Trim root README from 707 to 258 lines by moving deep reference material (profiling tables, extended config/usage recipes, tips) into docs/ and linking out. Replace the ASCII architecture block and the stale, unreferenced docs/img PNGs with accurate inline Mermaid figures for the layered architecture, build->run->report pipeline, and deployment-target inference. Move the parallelism/infrastructure capability matrices into docs/launchers.md and add Mermaid workflow diagrams to docs/deployment.md and docs/README.md. Co-Authored-By: Claude * docs: fix CLI flag names and defaults across README, cli-reference, usage Corrects --csv-file to the real --csv-file-path/--file flags, fills in missing `database` command flags (--unique-key/-k, --batch-size, --no-upsert, --no-index, --dry-run, MONGO_AUTH_SOURCE/MONGO_TIMEOUT_MS), fixes wrong `run --output`/`--tools-config` defaults, fixes broken space-separated --tags syntax, and updates the stale version badge. * docs(configuration): remove fabricated keys, fix defaults and schemas Removes fabricated timeout_multiplier/service_account keys and the vllm.tensor_parallel_size example (never read; real value derives from distributed.nproc_per_node). Fixes SLURM gpus_per_node default (1 -> 8), docker_env_vars.MAD_ROCM_PATH -> ROCM_PATH, the Data Provider schema (fabricated data_sources wrapper -> real flat shape), and credential key names (AMD_GITHUB -> PUBLIC_GITHUB_ROCM_KEY, uppercase USERNAME/PASSWORD). * docs(deployment): fix config priority order and stale example references Corrects the backwards/incomplete K8s "Configuration Priority" list to match config_loader.py's real 5-layer merge order (including the previously-missing AMD multi-GPU vendor layer), and removes nonexistent SLURM fields (mem, mail_user, mail_type). In examples/k8s-configs and examples/slurm-configs READMEs: fixes ~35 stale/renamed file paths, removes dead Primus example references, marks gpu_vendor/guest_os/deploy as non-required with real defaults, removes the invalid "RHEL" guest_os value, documents results_pvc and host_ipc as non-configurable (host_ipc previously told users to set a key that is always silently overridden), adds missing results_storage_size / data_storage_size / allow_privileged_profiling fields, and documents the SLURM node-health preflight keys (enable_node_check, auto_cleanup_nodes, allow_submit_without_clean_nodes, verbose_node_check). * docs(launchers): fix launcher value, dead config keys, and wrong examples Fixes "megatron" -> "megatron-lm" launcher value, removes the nonexistent --config flag in favor of --additional-context-file, clarifies the dead distributed.master_port key (SLURM reads distributed.port; K8s reads a separate top-level launcher.master_port object), corrects the 5-node auto-split table row (1/3, not 2/2), fixes the vLLM SLURM multi-node description (data-parallel, not TP+PP with Ray), and removes broken Primus example links. * docs(profiling): fix tool flags, env var names, and default config example Removes the nonexistent --tools therock_check flag, fixes the "Default Tool Configuration" example (rocprof command, gpu_info_power_profiler env vars using bare names instead of the real POWER_/VRAM_-prefixed names) across all Multi-GPU and sampling-rate examples, and adds the undocumented tool names (rocprof_hip_only, rocprof_sys, rocprofv3, rocprofv3_agent, rocprofv3_agent_counter, hipblaslt_trace, instruction_mix.txt). * docs: fix batch-build manifest example and stale install/contributing refs Notes that deployment_config in build_manifest.json is only written for non-local deployments (per _save_deployment_config in build_orchestrator.py), and adds the always-present context/ credentials_required keys to the example. Removes a duplicated `madengine --version` line in installation.md and fixes a stale test path in contributing.md (tests/test_cli.py -> tests/unit/test_cli.py). * docs(database): rewrite README to describe the shipped mongodb module The README described the module as "Not yet implemented" and documented a fictional future API (mongodb_client.py/MongoDBClient, local_storage.py/LocalStorage, api.py/ingest_results()), even though mongodb.py is fully implemented and wired into the `database` CLI command. Rewrites the README around the real classes (MongoDBConfig, UploadOptions, UploadResult, DocumentLoader/JSONLoader/CSVLoader, DocumentTransformer, MongoDBUploader, upload_file_to_mongodb) and adds a CLI-flag-to-API-param mapping table. * docs: fix execution and reporting README signature and API mismatches execution/README.md: fixes build_all_models's models_list -> models param, run_container's fabricated model_docker/gpu_ids params -> real docker_image string param, wrong status value casing/set (successful/failed/timeout -> SUCCESS/FAILURE/SKIPPED), wrong result key (duration -> test_duration), and documents the previously-missing dockerfile_utils.py and container_runner_helpers.py files. reporting/README.md: removes the fabricated "Legacy Reporting Tools" section claiming csv_to_html.py/csv_to_email.py live in a nonexistent tools/ directory and are unused by the modern CLI (they live in reporting/ and back `report to-html`/`report to-email`), fixes the update_perf_csv()/flatten_tags() example signatures, documents the perf_entry.csv/.json side effect, and adds a missing entry for update_perf_super.py. * docs: correct launcher names, docker-login env vars, and config key references - Fix remaining megatron -> megatron-lm launcher references in README, usage, and configuration docs. - Document DOCKER_CONFIG and MAD_SKIP_DOCKER_LOGIN, and clarify that MAD_CONTAINER_IMAGE is an --additional-context key, not an env var (cli-reference, configuration, usage). - Add missing Kubernetes and SLURM additional_context keys (cluster/scheduling, storage, node health/results) with pointers to the example READMEs for full reference. - Correct SGLang Disaggregated minimum node counts and split formula to reflect the SLURM co-located-proxy layout vs. Kubernetes' dedicated proxy requirement. - Replace stale hardcoded version/date footer with a pointer to `madengine --version` and CHANGELOG.md. --------- Co-authored-by: Claude --- README.md | 736 ++++++----------------------- docs/README.md | 54 ++- docs/batch-build.md | 4 + docs/cli-reference.md | 71 +-- docs/configuration.md | 113 +++-- docs/contributing.md | 2 +- docs/deployment.md | 56 +-- docs/img/architecture_overview.png | Bin 258476 -> 0 bytes docs/img/distributed_workflow.png | Bin 204443 -> 0 bytes docs/installation.md | 1 - docs/launchers.md | 73 ++- docs/profiling.md | 35 +- docs/usage.md | 62 ++- examples/k8s-configs/README.md | 161 +++---- examples/slurm-configs/README.md | 36 +- src/madengine/database/README.md | 192 +++++--- src/madengine/execution/README.md | 17 +- src/madengine/reporting/README.md | 56 +-- 18 files changed, 723 insertions(+), 946 deletions(-) delete mode 100755 docs/img/architecture_overview.png delete mode 100755 docs/img/distributed_workflow.png diff --git a/README.md b/README.md index 9ff81e42..ed763456 100644 --- a/README.md +++ b/README.md @@ -9,45 +9,21 @@ [![Python](https://img.shields.io/badge/python-3.8%2B-blue.svg)](https://python.org) [![CI](https://img.shields.io/badge/CI-GitHub%20Actions-green.svg)](https://github.com/ROCm/madengine/actions) [![Code Style](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) -[![Version](https://img.shields.io/badge/version-2.0-brightgreen.svg)](CHANGELOG.md) +[![Version](https://img.shields.io/badge/version-2.1.3-brightgreen.svg)](CHANGELOG.md) [![License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) > **AI model automation and benchmarking platform for local and distributed execution** -madengine is a modern CLI tool for running Large Language Models (LLMs) and Deep Learning models across local and distributed environments. Built for the [MAD (Model Automation and Dashboarding)](https://github.com/ROCm/MAD) ecosystem, it provides seamless execution from single GPUs to multi-node clusters. - -## 📖 Table of Contents - -- [Key Features](#-key-features) -- [Quick Start](#-quick-start) -- [Commands](#-commands) -- [Documentation](#-documentation) -- [Architecture](#-architecture) -- [Feature Matrix](#-feature-matrix) -- [Usage Examples](#-usage-examples) -- [Model Discovery](#-model-discovery) -- [Performance Profiling](#-performance-profiling) -- [Reporting and Database](#-reporting-and-database) -- [Installation](#-installation) -- [Tips & Best Practices](#-tips--best-practices) - - [Log error pattern scan](#log-error-pattern-scan) - - [Exit codes and CI](#exit-codes-and-ci) -- [Contributing](#-contributing) -- [License](#-license) -- [Links & Resources](#-links--resources) +madengine is a modern CLI tool for running Large Language Models (LLMs) and Deep Learning models across local and distributed environments. Built for the [MAD (Model Automation and Dashboarding)](https://github.com/ROCm/MAD) ecosystem, it provides seamless execution from single GPUs to multi-node clusters — with the same command working locally, on Kubernetes, and on SLURM. ## ✨ Key Features -- **🚀 Modern CLI** - Rich terminal output with Typer and Rich -- **🎯 Simple Deployment** - Run locally or deploy to Kubernetes/SLURM via configuration -- **🔧 Distributed Launchers** - Full support for torchrun, DeepSpeed, Megatron-LM, TorchTitan, Primus, vLLM, SGLang -- **🐳 Container-Native** - Docker-based execution with GPU support (ROCm, CUDA) -- **📂 ROCm Path** - Auto-detect **host** ROCm root (override with top-level `MAD_ROCM_PATH`); in-container `ROCM_PATH` is set independently via `docker_env_vars.MAD_ROCM_PATH` and resolved at Docker run (image OCI env + in-image probe, not host mirroring) — see [Configuration](docs/configuration.md#rocm-path-run-only) -- **📊 Performance Tools** - Integrated profiling with rocprof/rocprofv3, [rocm-trace-lite](https://github.com/sunway513/rocm-trace-lite) (RTL), rocblas, MIOpen, RCCL tracing -- **🎯 ROCprofv3 Profiles** - 8 pre-configured profiles for compute/memory/communication bottleneck analysis -- **🔍 Environment Validation** - TheRock ROCm detection and validation tools -- **⚙️ Intelligent Defaults** - Minimal K8s configs with automatic preset application -- **📋 Configurable log scan** - Optional `--additional-context` keys to disable or tune post-run log substring checks (see [Log error pattern scan](#log-error-pattern-scan)) +- **🚀 Modern CLI** — Rich terminal output with Typer and Rich +- **🎯 Simple Deployment** — Run locally or deploy to Kubernetes/SLURM by adding a config key; no code changes +- **🔧 Distributed Launchers** — torchrun, DeepSpeed, Megatron-LM, TorchTitan, Primus, vLLM, SGLang +- **🐳 Container-Native** — Docker-based execution with GPU support (ROCm, CUDA) +- **📊 Performance Tools** — Integrated profiling with rocprof/rocprofv3, [rocm-trace-lite](https://github.com/sunway513/rocm-trace-lite), rocBLAS/MIOpen/RCCL tracing → see [Profiling](docs/profiling.md) +- **⚙️ Intelligent Defaults** — Minimal configs auto-merged with presets; host/in-container ROCm path auto-detected → see [Configuration](docs/configuration.md#rocm-path-run-only) ## 🚀 Quick Start @@ -55,504 +31,186 @@ madengine is a modern CLI tool for running Large Language Models (LLMs) and Deep # Install madengine pip install git+https://github.com/ROCm/madengine.git -# Clone MAD package (required for models) +# Clone the MAD package (required for models) git clone https://github.com/ROCm/MAD.git && cd MAD # Discover available models madengine discover --tags dummy -# Run locally (full workflow: discover/build/run as configured by the model) +# Run locally (discover → build → run, as configured by the model) madengine run --tags dummy +``` + +> **Note:** For build operations `gpu_vendor` defaults to `AMD` and `guest_os` to `UBUNTU`. For non-AMD/Ubuntu environments, set them explicitly, e.g. `--additional-context '{"gpu_vendor": "NVIDIA", "guest_os": "CENTOS"}'`. + +**Results:** Performance data is written to `perf.csv` (and optionally `perf_entry.csv`), created automatically if missing. Failed runs are recorded with status `FAILURE` so every attempted model appears. See [Exit Codes](docs/cli-reference.md#exit-codes) for CI usage. If ROCm isn't auto-detected, set `MAD_ROCM_PATH` — see [Configuration](docs/configuration.md#rocm-path-run-only). -# Or with explicit configuration -madengine run --tags dummy \ - --additional-context '{"gpu_vendor": "AMD", "guest_os": "UBUNTU"}' +## 🏗️ Architecture + +madengine is organized in layers: the CLI drives orchestrators that discover and build models, then hand off to a local or distributed execution target, which runs the model under the appropriate launcher and emits performance data for reporting. + +```mermaid +flowchart TB + subgraph CLI["CLI Layer — Typer + Rich"] + C1[discover] + C2[build] + C3[run] + C4[report] + C5[database] + end + + subgraph ORC["Orchestration Layer"] + O1[DiscoverModels] + O2[BuildOrchestrator] + O3[RunOrchestrator] + MAN[(build_manifest.json)] + end + + subgraph EXEC["Execution / Deployment Layer"] + E1[ContainerRunner
local Docker] + E2[DeploymentFactory] + K8S[Kubernetes Jobs] + SLURM[SLURM Jobs] + end + + subgraph LAUNCH["Launcher Layer"] + T[Train: torchrun · DeepSpeed
Megatron-LM · TorchTitan · Primus] + I[Infer: vLLM · SGLang · SGLang Disagg] + end + + OUT[(perf.csv / JSON)] + + C1 --> O1 + C2 --> O2 + C3 --> O3 + O2 --> MAN --> O3 + O1 --> O2 + O3 --> E1 + O3 --> E2 + E2 --> K8S + E2 --> SLURM + E1 --> LAUNCH + K8S --> LAUNCH + SLURM --> LAUNCH + LAUNCH --> OUT + OUT --> C4 + OUT --> C5 ``` -> **Note**: For build operations, `gpu_vendor` defaults to `AMD` and `guest_os` defaults to `UBUNTU` if not specified. For production deployments or non-AMD/Ubuntu environments, explicitly specify these values. +1. **CLI Layer** — five commands: `discover`, `build`, `run`, `report`, `database` +2. **Orchestration** — `DiscoverModels` finds models; `BuildOrchestrator` builds images and writes `build_manifest.json`; `RunOrchestrator` reads/triggers the build and infers the target +3. **Execution / Deployment** — local `ContainerRunner`, or `DeploymentFactory` → Kubernetes / SLURM +4. **Launchers** — distributed training and inference frameworks +5. **Output & Post-Processing** — `perf.csv`/JSON results → `report` (HTML/email) and `database` (MongoDB) -If auto-detection does not find your **host** ROCm root, set top-level `MAD_ROCM_PATH` in `--additional-context`. For a different ROCm root **inside the container**, set `docker_env_vars.MAD_ROCM_PATH` in additional context. If you omit it, madengine derives in-container `ROCM_PATH` when running Docker (from the image's baked-in env, then an in-container probe, then `/opt/rocm` — it does **not** copy the host path). You can also set `ROCM_PATH` / `MAD_AUTO_ROCM_PATH=0` for **host** behavior as documented in [docs/configuration.md](docs/configuration.md): +## 🔄 Workflow -```bash -# Override host ROCm root: -madengine run --tags dummy --additional-context '{"MAD_ROCM_PATH": "/path/to/rocm"}' -# or: export ROCM_PATH=/path/to/rocm && madengine run --tags dummy -# Override in-container ROCm root independently: -madengine run --tags dummy --additional-context '{"docker_env_vars": {"MAD_ROCM_PATH": "/path/in/container"}}' +The core pipeline is the same everywhere: discover models, build images once, run them against a target, then report. Build and run can be separated so images are built once (e.g. in CI) and reused across nodes. + +```mermaid +flowchart LR + D[discover
find models by tag] --> B[build
Docker images] + B --> M[(build_manifest.json)] + M --> R[run
infer target + execute] + R --> P[(perf.csv)] + P --> RP[report
HTML / email] + P --> DB[database
MongoDB] ``` -**Results:** Performance data is written to `perf.csv` (and optionally `perf_entry.csv`). The file is created automatically if missing. Failed runs (including pre-run setup failures) are recorded with status `FAILURE` so every attempted model appears in the table. See [Exit Codes](docs/cli-reference.md#exit-codes) for CI/script usage. +**Deployment target is inferred from the config** (Convention over Configuration) — no `deploy` flag needed: -## 📋 Commands +```mermaid +flowchart TD + A[additional_context] --> Q{which key?} + Q -->|k8s / kubernetes| K[Kubernetes deployment] + Q -->|slurm| S[SLURM deployment] + Q -->|neither| L[Local Docker execution] +``` -madengine provides five main commands for model automation and benchmarking: +## 📋 Commands | Command | Description | Use Case | |---------|-------------|----------| -| **[discover](#-model-discovery)** | Find available models | Model exploration and validation | -| **[build](#building-images)** | Build Docker images | Create containerized models | -| **[run](#-usage-examples)** | Execute models | Local and distributed execution | -| **[report](docs/cli-reference.md#report---generate-reports)** | Generate HTML reports | Convert CSV to viewable reports | -| **[database](docs/cli-reference.md#database---upload-to-mongodb)** | Upload to MongoDB | Store results in database | - -**Quick Start:** +| **[discover](docs/usage.md#model-discovery)** | Find available models | Model exploration and validation | +| **[build](docs/usage.md#build-workflow)** | Build Docker images | Create containerized models | +| **[run](docs/usage.md#run-workflow)** | Execute models | Local and distributed execution | +| **[report](docs/cli-reference.md#report---generate-reports)** | Generate HTML/email reports | Convert CSV to viewable reports | +| **[database](docs/cli-reference.md#database---upload-to-mongodb)** | Upload to MongoDB | Store results in a database | ```bash -# Discover models -madengine discover --tags dummy +madengine discover --tags dummy # Find models +madengine build --tags dummy # Build image (AMD/UBUNTU defaults) +madengine run --tags dummy # Run model +madengine report to-html --csv-file-path perf_entry.csv # Report +madengine database --file perf_entry.csv --db mydb --collection results # Upload +``` -# Build image (uses AMD/UBUNTU defaults) -madengine build --tags dummy +For all options and examples, see the **[CLI Reference](docs/cli-reference.md)**. -# Run model -madengine run --tags dummy +## 💻 Usage Examples -# For non-AMD/Ubuntu environments, specify explicitly: -# madengine build --tags dummy --additional-context '{"gpu_vendor": "NVIDIA", "guest_os": "CENTOS"}' +```bash +# Local, multi-GPU with torchrun (DDP/FSDP) +madengine run --tags model \ + --additional-context '{"docker_gpus": "0,1,2,3", + "distributed": {"launcher": "torchrun", "nproc_per_node": 4}}' -# Generate report -madengine report to-html --csv-file perf_entry.csv +# Kubernetes (minimal config, presets auto-applied) +madengine run --tags model \ + --additional-context '{"k8s": {"gpu_count": 2}}' -# Upload results -madengine database --csv-file perf_entry.csv --db mydb --collection results +# SLURM (build once, then deploy) +madengine build --tags model --registry gcr.io/myproject +madengine run --manifest-file build_manifest.json \ + --additional-context '{"slurm": {"partition": "gpu", "nodes": 4, "gpus_per_node": 8}, + "distributed": {"launcher": "torchtitan", "nnodes": 4, "nproc_per_node": 8}}' ``` -For detailed command options, see the **[CLI Command Reference](docs/cli-reference.md)**. +More local/K8s/SLURM/CI recipes: [Usage Guide](docs/usage.md) · [Configuration](docs/configuration.md) · [CLI Reference](docs/cli-reference.md). ## 📚 Documentation | Guide | Description | |-------|-------------| | [Installation](docs/installation.md) | Complete installation instructions | -| [Usage Guide](docs/usage.md) | Commands, workflows, and examples ([`--skip-model-run`](docs/usage.md#skip-model-run-after-build)) | +| [Usage Guide](docs/usage.md) | Commands, workflows, and examples | | **[CLI Reference](docs/cli-reference.md)** | **Detailed command options and examples** | +| [Configuration](docs/configuration.md) | Advanced options, ROCm path, log error scan | | [Deployment](docs/deployment.md) | Kubernetes and SLURM deployment | -| [Configuration](docs/configuration.md) | Advanced options; [run log error pattern scan](docs/configuration.md#run-phase-log-error-pattern-scan) | | [Batch Build](docs/batch-build.md) | Selective builds for CI/CD | -| [Launchers](docs/launchers.md) | Distributed training frameworks | +| [Launchers](docs/launchers.md) | Distributed frameworks + capability matrices | | [Profiling](docs/profiling.md) | Performance analysis tools | | [Contributing](docs/contributing.md) | How to contribute | -## 🏗️ Architecture - -``` - ┌─────────────────────────────────────────────────────────────────────────────────────────┐ - │ madengine CLI v2.0 (Typer + Rich) │ - │ discover │ build │ run │ report │ database │ - └─────────────────────────────────────────────────────────────────────────────────────────┘ - │ │ │ - │ │ ▼ - │ │ ┌─────────────────────── Orchestration Layer ───────────────────────────┐ - │ │ │ Model Discovery (models.json / scripts/ get_models) │ - │ │ │ BuildOrchestrator · RunOrchestrator │ - │ └──│ | - └──────────────┴───────────────────────────────────────────────────────────────────────┘ - │ - ┌─────────────────────────────────┼──────────────────── Infrastructure Layer ─────────────┐ - │ ▼ ▼ ▼ │ - │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ - │ │ Local │ │ Kubernetes │ │ SLURM │ │ - │ │ Docker │ │ Jobs │ │ Jobs │ │ - │ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │ - │ └──────────────────┼──────────────────┘ │ - └─────────────────────────────────┼───────────────────────────────────────────────────────┘ - ▼ - ┌──────────────────────────────────── Launcher Layer (Distribution) ──────────────────────┐ - │ Train: torchrun · DeepSpeed · Megatron-LM · TorchTitan · Primus │ - │ Infer: vLLM · SGLang · SGLang Disagg │ - └─────────────────────────────────┬───────────────────────────────────────────────────────┘ - ▼ - ┌─────────────────────────────┐ - │ Performance (CSV/JSON) │ - └─────────────┬───────────────┘ - │ - ┌───────────────────┴───────────────────┐ - ▼ ▼ - ┌───────────────────┐ ┌──────────────────┐ - │ report │ │ database │ - │ to-html, to-email │ │ MongoDB upload │ - └───────────────────┘ └──────────────────┘ -``` - -**Component Flow:** - -1. **CLI Layer** - User interface with 5 commands (discover, build, run, report, database) -2. **Model Discovery** - Find and validate models from MAD package -3. **Orchestration** - BuildOrchestrator & RunOrchestrator manage workflows -4. **Execution Targets** - Local Docker, Kubernetes Jobs, or SLURM Jobs -5. **Distributed Launchers** - Training (torchrun, DeepSpeed, Megatron-LM, TorchTitan, Primus) and Inference (vLLM, SGLang) -6. **Performance Output** - CSV/JSON results with metrics -7. **Post-Processing** - Report generation (HTML/Email) and database upload (MongoDB) - -## 🎯 Feature Matrix - -### Supported Launchers & Infrastructure +## 🎯 Supported Launchers | Launcher | Local | Kubernetes | SLURM | Type | Key Features | |----------|-------|-----------|-------|------|--------------| | **torchrun** | ✅ | ✅ | ✅ | Training | PyTorch DDP/FSDP, elastic training | | **DeepSpeed** | ✅ | ✅ | ✅ | Training | ZeRO optimization, pipeline parallelism | | **Megatron-LM** | ✅ | ✅ | ✅ | Training | Tensor+Pipeline parallel, large transformers | -| **TorchTitan** | ✅ | ✅ | ✅ | Training | FSDP2+TP+PP+CP, Llama 3.1 (8B-405B) | -| **Primus** | ✅ | ✅ | ✅ | Training | Megatron / TorchTitan / MaxText via Primus YAML; `distributed.primus` | +| **TorchTitan** | ✅ | ✅ | ✅ | Training | FSDP2+TP+PP+CP, Llama 3.1 (8B–405B) | +| **Primus** | ✅ | ✅ | ✅ | Training | Megatron / TorchTitan / MaxText via Primus YAML | | **vLLM** | ✅ | ✅ | ✅ | Inference | v1 engine, PagedAttention, Ray cluster | | **SGLang** | ✅ | ✅ | ✅ | Inference | RadixAttention, structured generation | | **SGLang Disagg** | ❌ | ✅ | ✅ | Inference | Disaggregated prefill/decode, Mooncake, 3+ nodes | -**Note:** All launchers support single-GPU, multi-GPU (single node), and multi-node (where infrastructure allows). See [Launchers Guide](docs/launchers.md) for details. - -### Parallelism Capabilities - -| Launcher | Tensor Parallel (TP) | Pipeline Parallel (PP) | Data Parallel (DP) | Context Parallel (CP) | FSDP/ZeRO | Expert Parallel (EP) | Primary Use Case | -|----------|----------------------|------------------------|--------------------|------------------------|-----------|----------------------|------------------| -| **torchrun** | ❗Manual | ❌No | ❗Manual (DDP) | ❌No | ❗Manual (FSDP) | ❌No | General distributed training | -| **TorchTitan** | ✅Auto | ✅Auto | ✅Auto (FSDP2) | ❗Manual | ✅Auto (FSDP2) | ❌No | Large-scale LLM pre-training | -| **DeepSpeed** | ❗Manual | ❗Manual | ✅Auto (ZeRO) | ❌No | ✅Auto (ZeRO) | ❌No | Memory-efficient training | -| **Megatron-LM** | ✅Auto | ✅Auto | ✅Implicit | ✅Auto | ❌No | ❌No | Large transformer training | -| **Primus** | ❗Manual | ❗Manual | ❗Manual | ❗Manual | ❗Manual | ❌No | Unified pretrain (experiment YAML; backend-specific) | -| **vLLM** | ✅Auto | SLURM: ✅Auto (Multi) / K8s: ❗Disabled | ✅Auto (Replicas) | ❌No | ❌No | ❗Manual | High-throughput inference | -| **SGLang** | ✅Auto | SLURM: ✅Auto (Multi) / K8s: ❗Disabled | ❗Limited | ❌No | ❌No | ❌No | Inference + structured gen | -| **SGLang PD Disagg** | ✅Auto | ❌No | ✅Role-based | ❌No | ❌No | ❌No | Optimized prefill/decode | +All launchers support single-GPU, multi-GPU, and multi-node (where infrastructure allows). See the [Launchers Guide](docs/launchers.md) for the full **parallelism** and **infrastructure** capability matrices. -**Legend:** ✅Auto = supported and configured by madengine; ❗Manual = supported by launcher but requires user configuration; ❗Limited / ❗Disabled = launcher or platform limitation. See [Launchers Guide](docs/launchers.md) and [Configuration](docs/configuration.md) for details. +## 📊 Profiling -### Infrastructure Capabilities - -| Feature | Local | Kubernetes | SLURM | -|---------|-------|-----------|-------| -| **Execution** | Docker containers | K8s Jobs | SLURM jobs | -| **Multi-Node** | ❌ | ✅ Indexed Jobs | ✅ Job arrays | -| **Resource Mgmt** | Manual | Declarative (YAML) | Batch scheduler | -| **Monitoring** | Docker logs | kubectl/dashboard | squeue/scontrol | -| **Auto-scaling** | ❌ | ✅ | ❌ | -| **Network** | Host | CNI plugin | InfiniBand/Ethernet | - -## 💻 Usage Examples - -### Local Execution - -```bash -# Single GPU -madengine run --tags dummy \ - --additional-context '{"gpu_vendor": "AMD", "guest_os": "UBUNTU"}' - -# Multi-GPU with torchrun (DDP/FSDP) -madengine run --tags model \ - --additional-context '{ - "gpu_vendor": "AMD", - "guest_os": "UBUNTU", - "docker_gpus": "0,1,2,3", - "distributed": { - "launcher": "torchrun", - "nproc_per_node": 4 - } - }' - -# With DeepSpeed (ZeRO optimization) -madengine run --tags model \ - --additional-context '{ - "gpu_vendor": "AMD", - "guest_os": "UBUNTU", - "docker_gpus": "all", - "distributed": { - "launcher": "deepspeed", - "nproc_per_node": 8 - } - }' -``` - -### Kubernetes Deployment +madengine ships integrated profiling for AMD ROCm — `rocprof`, eight pre-configured `rocprofv3` profiles (ROCm 7.0+), `rocm-trace-lite`, library tracing (rocBLAS/MIOpen/Tensile/RCCL), and power/VRAM monitors. Tools are stackable via `--additional-context '{"tools": [...]}'`. ```bash -# Minimal config (auto-defaults applied) -madengine run --tags model \ - --additional-context '{"k8s": {"gpu_count": 2}}' - -# Multi-node inference with vLLM -madengine run --tags model \ - --additional-context '{ - "k8s": { - "namespace": "ml-team", - "gpu_count": 8 - }, - "distributed": { - "launcher": "vllm", - "nnodes": 2, - "nproc_per_node": 4 - } - }' - -# SGLang with structured generation -madengine run --tags model \ - --additional-context '{ - "k8s": {"gpu_count": 4}, - "distributed": { - "launcher": "sglang", - "nproc_per_node": 4 - } - }' -``` - -### SLURM Deployment - -```bash -# Build phase (local or CI) -madengine build --tags model \ - --registry gcr.io/myproject \ - --additional-context '{"gpu_vendor": "AMD", "guest_os": "UBUNTU"}' - -# Deploy phase (on SLURM login node) -madengine run --manifest-file build_manifest.json \ - --additional-context '{ - "slurm": { - "partition": "gpu", - "nodes": 4, - "gpus_per_node": 8, - "time": "24:00:00" - }, - "distributed": { - "launcher": "torchtitan", - "nnodes": 4, - "nproc_per_node": 8 - } - }' -``` - -To run on **specific nodes**, set `nodelist` (comma-separated node names). When set, the job is restricted to those nodes and automatic node health preflight is skipped. Example: `"slurm": { "nodelist": "node01,node02", "nodes": 2, ... }`. See [Configuration](docs/configuration.md#slurm-deployment) and [examples/slurm-configs/basic/03-multi-node-basic-nodelist.json](examples/slurm-configs/basic/03-multi-node-basic-nodelist.json). - -### Common Workflows - -**Development → Testing → Production:** - -```bash -# 1. Develop locally with single GPU -madengine run --tags model \ - --additional-context '{"gpu_vendor": "AMD", "guest_os": "UBUNTU"}' - -# 2. Test multi-GPU locally -madengine run --tags model \ - --additional-context '{ - "gpu_vendor": "AMD", - "guest_os": "UBUNTU", - "docker_gpus": "0,1", - "distributed": {"launcher": "torchrun", "nproc_per_node": 2} - }' - -# 3. Build and push to registry -madengine build --tags model \ - --registry docker.io/myorg \ - --additional-context '{"gpu_vendor": "AMD", "guest_os": "UBUNTU"}' - -# 4. Deploy to Kubernetes -madengine run --manifest-file build_manifest.json -``` - -**CI/CD Pipeline:** - -```bash -# Batch build (selective rebuilds) -madengine build --batch-manifest batch.json \ - --registry docker.io/myorg - -# Run tests -madengine run --manifest-file build_manifest.json \ - --additional-context '{"k8s": {"namespace": "ci-test"}}' - -# Generate and email reports -madengine report to-email --directory ./results --output ci_report.html - -# Upload to database -madengine database --csv-file perf_entry.csv \ - --database-name ci_db --collection-name test_results -``` - -See [Usage Guide](docs/usage.md), [Configuration Guide](docs/configuration.md), and [CLI Reference](docs/cli-reference.md) for more examples. - -### Building Images - -```bash -# Build single model -madengine build --tags dummy \ - --additional-context '{"gpu_vendor": "AMD", "guest_os": "UBUNTU"}' - -# Build with registry (for distributed deployment) -madengine build --tags model1 model2 \ - --registry localhost:5000 \ - --additional-context '{"gpu_vendor": "AMD", "guest_os": "UBUNTU"}' - -# Build for multiple GPU architectures -madengine build --tags model \ - --target-archs gfx908 gfx90a gfx942 \ - --registry gcr.io/myproject - -# Batch build mode (selective builds for CI/CD) -madengine build --batch-manifest examples/build-manifest/batch.json \ - --registry docker.io/myorg - -# Clean rebuild (no Docker cache) -madengine build --tags model --clean-docker-cache \ - --additional-context '{"gpu_vendor": "AMD", "guest_os": "UBUNTU"}' -``` - -**Output:** Creates `build_manifest.json` with built image names and configurations. - -See [Batch Build Guide](docs/batch-build.md) and examples in [`examples/build-manifest/`](examples/build-manifest/). - -## 🔍 Model Discovery - -madengine discovers models from the MAD package using three methods: - -```bash -# Root models (models.json) -madengine discover --tags pyt_huggingface_bert - -# Directory-specific (scripts/{dir}/models.json), scoped tag: {dir}/{model_or_tag} -madengine discover --tags dummy2/model1 - -# Dynamic with parameters (scripts/{dir}/get_models_json.py) -madengine discover --tags dummy3/model3:batch_size=512 +madengine run --tags model --additional-context '{"tools": [{"name": "rocprofv3_compute"}]}' ``` -## 📊 Performance Profiling - -madengine includes integrated profiling tools for AMD ROCm: - -```bash -# GPU profiling with rocprof -madengine run --tags model \ - --additional-context '{ - "gpu_vendor": "AMD", - "guest_os": "UBUNTU", - "tools": [{"name": "rocprof"}] - }' - -# ROCprofv3 (ROCm 7.0+) - Advanced profiling with pre-configured profiles -madengine run --tags model \ - --additional-context '{"tools": [{"name": "rocprofv3_compute"}]}' - -# Use configuration files for complex setups -madengine run --tags model \ - --additional-context-file examples/profiling-configs/rocprofv3_multi_gpu.json - -# Library tracing (rocBLAS, MIOpen, Tensile, RCCL) -madengine run --tags model \ - --additional-context '{"tools": [{"name": "rocblas_trace"}]}' - -# rocm-trace-lite — lightweight kernel dispatch trace (SQLite; no rocprofiler-sdk) -# Requires outbound HTTPS to GitHub on first run unless the wheel is baked into the image -# (see docs/profiling.md). Do not combine with rocprof / rocprofv3_* on the same run. -madengine run --tags model \ - --additional-context '{"gpu_vendor": "AMD", "guest_os": "UBUNTU", "tools": [{"name": "rocm_trace_lite"}]}' - -# Power and VRAM monitoring -madengine run --tags model \ - --additional-context '{"tools": [ - {"name": "gpu_info_power_profiler"}, - {"name": "gpu_info_vram_profiler"} - ]}' - -# Multiple tools (stackable) -madengine run --tags model \ - --additional-context '{"tools": [ - {"name": "rocprofv3_memory"}, - {"name": "rocblas_trace"}, - {"name": "gpu_info_power_profiler"} - ]}' -``` - -**Available Tools:** - -| Tool | Purpose | Output | -|------|---------|--------| -| `rocprof` | GPU kernel profiling | Kernel timings, occupancy | -| `rocprofv3_compute` | Compute-bound analysis (ROCm 7.0+) | ALU metrics, wave execution | -| `rocprofv3_memory` | Memory-bound analysis (ROCm 7.0+) | Cache hits, bandwidth | -| `rocprofv3_communication` | Multi-GPU communication (ROCm 7.0+) | RCCL traces, inter-GPU transfers | -| `rocprofv3_lightweight` | Minimal overhead profiling (ROCm 7.0+) | HIP and kernel traces | -| `rocm_trace_lite` | RTL **`lite`** mode — kernel dispatch trace (HSA, SQLite/RPD-style); [`rtl trace --mode lite`](https://sunway513.github.io/rocm-trace-lite/quickstart.html) via `rtl_trace_wrapper.sh` | `rocm_trace_lite_output/trace.db` (and optional `trace.json.gz`, `trace_summary.txt`) | -| `rocm_trace_lite_default` | RTL **`default`** mode — broader dispatch coverage; higher overhead than `lite` (same outputs paths) | Same as `rocm_trace_lite` | -| `rocblas_trace` | rocBLAS library calls | Function calls, arguments | -| `miopen_trace` | MIOpen library calls | Conv/pooling operations | -| `tensile_trace` | Tensile GEMM library | Matrix multiply details | -| `rccl_trace` | RCCL collective ops | Communication patterns | -| `gpu_info_power_profiler` | GPU power consumption | Power usage over time | -| `gpu_info_vram_profiler` | GPU memory usage | VRAM utilization | -| `therock_check` | TheRock ROCm validation | Installation detection | - -**ROCprofv3 Profiles** (ROCm 7.0+): - -madengine provides 8 pre-configured ROCprofv3 profiles for different bottleneck scenarios: - -- `rocprofv3_compute` - Compute-bound workloads (transformers, dense ops) -- `rocprofv3_memory` - Memory-bound workloads (large batches, high-res) -- `rocprofv3_communication` - Multi-GPU distributed training -- `rocprofv3_full` - Comprehensive profiling (all metrics, high overhead) -- `rocprofv3_lightweight` - Minimal overhead (production-friendly) -- `rocprofv3_perfetto` - Perfetto UI compatible traces -- `rocprofv3_api_overhead` - API call timing analysis -- `rocprofv3_pc_sampling` - Kernel hotspot identification - -See [`examples/profiling-configs/`](examples/profiling-configs/) for ready-to-use configuration files. - -**rocm-trace-lite (`rocm_trace_lite` / `rocm_trace_lite_default`):** - -- madengine runs workloads under `scripts/common/tools/rtl_trace_wrapper.sh`, which invokes the `rtl` CLI (or `python3 -m rocm_trace_lite.cli`) with **`RTL_MODE=lite`** or **`RTL_MODE=default`** and writes traces under `rocm_trace_lite_output/`. -- The trace **pre-script** installs the package from a **[GitHub Release wheel](https://github.com/sunway513/rocm-trace-lite/releases)** (not PyPI). By default it uses a **pinned** `linux_x86_64` wheel for reproducible installs. Set **`ROCM_TRACE_LITE_FOLLOW_LATEST=1`** to resolve the latest wheel via the GitHub API, or **`ROCM_TRACE_LITE_WHEEL_URL`** to a direct `.whl` URL for air-gapped installs or non-x86_64 platforms. -- Choose **either** `rocm_trace_lite` **or** rocprof / `rocprofv3_*` for a given run—not both. Details: [Profiling Guide](docs/profiling.md) (section *rocm-trace-lite (RTL)*). - -**TheRock Validation:** - -```bash -# Validate TheRock installation (AMD's pip-based ROCm) -madengine run --tags dummy_therock \ - --additional-context '{"tools": [{"name": "therock_check"}]}' -``` - -See [Profiling Guide](docs/profiling.md) for detailed usage and analysis. - -## 📊 Reporting and Database - -### Generate Reports - -Convert performance CSV files to HTML reports: - -```bash -# Single CSV to HTML -madengine report to-html --csv-file perf_entry.csv - -# Consolidated email report (all CSVs in directory) -madengine report to-email --directory ./results --output summary.html -``` - -### Upload to Database - -Store performance results in MongoDB: - -```bash -# Set MongoDB connection -export MONGO_HOST=mongodb.example.com -export MONGO_PORT=27017 -export MONGO_USER=myuser -export MONGO_PASSWORD=mypassword - -# Upload CSV to MongoDB -madengine database --csv-file perf_entry.csv \ - --database-name performance_db \ - --collection-name model_runs -``` - -**Use Cases:** -- Track performance over time -- Compare results across different configurations -- Build performance dashboards -- Automated CI/CD reporting - -See [CLI Reference](docs/cli-reference.md) for complete options. +See the [Profiling Guide](docs/profiling.md) and ready-to-use configs in [`examples/profiling-configs/`](examples/profiling-configs/). ## 📦 Installation ```bash -# Install madengine (all dependencies, including Kubernetes support, are included) +# Install (all dependencies, including Kubernetes support, included) pip install git+https://github.com/ROCm/madengine.git # Development installation @@ -560,147 +218,41 @@ git clone https://github.com/ROCm/madengine.git cd madengine && pip install -e . ``` -See [Installation Guide](docs/installation.md) for detailed instructions. - -## 💡 Tips & Best Practices - -### General Usage - -- **Use configuration files** for complex setups instead of long command lines -- **Test locally first** with single GPU before scaling to multi-node -- **Enable verbose logging** (`--verbose`) when debugging issues -- **Use `--live-output`** for real-time monitoring of long-running operations - -### Log error pattern scan - -After a local Docker run, madengine can scan the captured **run log** for common failure substrings (for example `RuntimeError:`, `CUDA out of memory`, `Traceback`). That helps catch hard failures when exit codes are ambiguous, but some workloads log benign `RuntimeError:` text while tests still pass. - -- **Disable** the scan when another signal is authoritative (e.g. pytest/JUnit inside the image): set `"log_error_pattern_scan": false` in `--additional-context` or in the model entry in `models.json`. See [Configuration — Run phase: log error pattern scan](docs/configuration.md#run-phase-log-error-pattern-scan). -- **Extend exclusions** with `log_error_benign_patterns` (list of strings), or **replace** the default pattern list with `log_error_patterns` (non-empty list of strings) for advanced cases. - -### CI / Jenkins - -- **Exit codes:** The CLI uses fixed exit codes (`ExitCode` in `madengine.cli.constants`, e.g. `SUCCESS=0`, `RUN_FAILURE=3`, `INVALID_ARGS=4`). Pipelines should treat **non-zero** as failure; no log scraping is required for pass/fail. -- **Streaming:** In Jenkins, avoid redirecting stdout only to a file (`> file`) without `tee` if you want the console to update during the run. Prefer `... 2>&1 | tee madengine.run.log` with `bash -o pipefail` so the step exit code is still from `madengine`. -- **Unbuffered Python:** If output still appears in chunks, set `PYTHONUNBUFFERED=1` (or `python -u`) for the `madengine` process. +See the [Installation Guide](docs/installation.md) for details. -### Build & Deployment +## 💡 Tips & Troubleshooting -- **Separate build and run phases** for distributed deployments -- **Skip model script:** `madengine run --tags … --skip-model-run` starts the container and runs `pre_scripts`, but skips the model script. Combine with `--keep-alive` for a live container ready for manual exec. Ignored with a warning on SLURM/K8s. See [Usage — Skip model run after build](docs/usage.md#skip-model-run-after-build). -- **Use registries** for multi-node execution (K8s/SLURM) -- **Use batch build mode** for CI/CD to optimize build times -- **Specify `--target-archs`** when building for multiple GPU architectures +- **Test locally first** with a single GPU before scaling to multi-node; use config files for complex setups. +- **Debugging:** add `--verbose --live-output`; keep a container for inspection with `--keep-alive`. +- **CI:** the CLI uses fixed [exit codes](docs/cli-reference.md#exit-codes) (`0` success, `2` build failure, `3` run failure, `4` invalid args) — no log scraping needed. +- **Log error scan** can flag benign `RuntimeError:` text; disable or tune it via `log_error_pattern_scan` / `log_error_benign_patterns` — see [Configuration](docs/configuration.md#run-phase-log-error-pattern-scan). -### Performance - -- **Start with small timeouts** and increase as needed -- **Use profiling tools** to identify bottlenecks -- **Monitor GPU utilization** with `gpu_info_power_profiler` -- **Profile library calls** with rocBLAS/MIOpen tracing - -### Exit codes and CI - -madengine uses consistent exit codes for scripts and CI (e.g. Jenkins): `0` = success, `1` = general failure, `2` = build failure, `3` = one or more run failures, `4` = invalid arguments. Failed runs are still written to `perf.csv` with status `FAILURE`. See [CLI Reference — Exit Codes](docs/cli-reference.md#exit-codes) for the full table and examples. - -### Troubleshooting - -```bash -# Check model is available -madengine discover --tags your_model - -# Verbose output for debugging -madengine run --tags model --verbose --live-output - -# Keep container alive for inspection -madengine run --tags model --keep-alive - -# Clean rebuild if build fails -madengine build --tags model --clean-docker-cache --verbose -``` - -**ROCm not in /opt/rocm:** Set top-level `MAD_ROCM_PATH` in `--additional-context` for the **host**; for **in-container** paths, set `docker_env_vars.MAD_ROCM_PATH`, or let madengine resolve `ROCM_PATH` at run from the image and probe (see [Configuration](docs/configuration.md#rocm-path-run-only)). - -**Common Issues:** -- **False failures with profiling**: If models show FAILURE but have performance metrics, see [Profiling Troubleshooting](docs/profiling.md#false-failure-detection-with-rocprof) -- **False failures from `RuntimeError:` in logs**: If the workload logs expected exception text but tests pass, disable or tune the scan with `log_error_pattern_scan` / `log_error_benign_patterns` — see [Configuration](docs/configuration.md#run-phase-log-error-pattern-scan) -- **ROCProf log errors**: Messages like `E20251230` are informational logs, not errors (fixed in v2.0+) -- **Configuration errors**: Validate JSON with `python -m json.tool your-config.json` +More: [Usage — Troubleshooting](docs/usage.md#troubleshooting) · [Profiling — False failures](docs/profiling.md#false-failure-detection-with-rocprof). ## 🤝 Contributing -We welcome contributions! See [Contributing Guide](docs/contributing.md) for details. +Contributions are welcome! See the [Contributing Guide](docs/contributing.md). ```bash git clone https://github.com/ROCm/madengine.git -cd madengine -python3 -m venv venv && source venv/bin/activate +cd madengine && python3 -m venv venv && source venv/bin/activate pip install -e . - -# Run all tests pytest - -# Run specific test module -pytest tests/unit/test_error_handling.py -v - -# Run error pattern tests -pytest tests/unit/test_error_handling.py::TestErrorPatternMatching -v ``` ## 📄 License -MIT License - see [LICENSE](LICENSE) file for details. +MIT License — see [LICENSE](LICENSE). ## 🔗 Links & Resources -### Documentation -- **[CLI Reference](docs/cli-reference.md)** - Complete command options -- **[Usage Guide](docs/usage.md)** - Workflows and examples -- **[Deployment Guide](docs/deployment.md)** - Kubernetes/SLURM deployment -- **[Configuration Guide](docs/configuration.md)** - Advanced configuration -- **[All Docs](docs/)** - Complete documentation index - -### External Resources -- **MAD Package**: https://github.com/ROCm/MAD -- **Issues & Support**: https://github.com/ROCm/madengine/issues -- **ROCm Documentation**: https://rocm.docs.amd.com/ - -### Getting Help - -**Command Help:** -```bash -madengine --help # Main help -madengine --help # Command-specific help -madengine report --help # Sub-app help -madengine report to-html --help # Sub-command help -``` - -**Quick Checks:** -```bash -# Verify installation -madengine --version - -# Discover available models -madengine discover - -# Check specific model -madengine discover --tags your_model --verbose -``` - -**Troubleshooting:** -- Check [CLI Reference](docs/cli-reference.md) for all command options -- Enable `--verbose` flag for detailed error messages -- See [Usage Guide](docs/usage.md) troubleshooting section -- Report issues: https://github.com/ROCm/madengine/issues +- **MAD Package:** https://github.com/ROCm/MAD +- **Issues & Support:** https://github.com/ROCm/madengine/issues +- **ROCm Documentation:** https://rocm.docs.amd.com/ +- **Command help:** `madengine --help` · `madengine --help` --- ## ⚠️ Migration Notice (v2.0.0+) -The CLI has been unified! Starting from v2.0.0: -- ✅ Use `madengine` (unified modern CLI with K8s, SLURM, distributed support) -- ❌ Legacy v1.x CLI has been removed - ---- - -**Code Quality**: Clean codebase with no dead code, comprehensive test coverage, and following Python best practices. +The CLI has been unified. Starting from v2.0.0, use `madengine` (with K8s, SLURM, and distributed support); the legacy v1.x CLI has been removed. diff --git a/docs/README.md b/docs/README.md index db31f762..1dd64548 100644 --- a/docs/README.md +++ b/docs/README.md @@ -35,7 +35,55 @@ Complete documentation for madengine - AI model automation and distributed bench ## 🏗️ Architecture -The architecture diagram (Orchestration, Infrastructure, and Launcher layers) is in the [main README](../README.md#-architecture). Summary: +The CLI drives orchestrators that discover and build models, then hand off to a local or distributed execution target, which runs the model under the appropriate launcher and emits performance data for reporting. (Same diagram as the [main README](../README.md#-architecture).) + +```mermaid +flowchart TB + subgraph CLI["CLI Layer — Typer + Rich"] + C1[discover] + C2[build] + C3[run] + C4[report] + C5[database] + end + + subgraph ORC["Orchestration Layer"] + O1[DiscoverModels] + O2[BuildOrchestrator] + O3[RunOrchestrator] + MAN[(build_manifest.json)] + end + + subgraph EXEC["Execution / Deployment Layer"] + E1[ContainerRunner
local Docker] + E2[DeploymentFactory] + K8S[Kubernetes Jobs] + SLURM[SLURM Jobs] + end + + subgraph LAUNCH["Launcher Layer"] + T[Train: torchrun · DeepSpeed
Megatron-LM · TorchTitan · Primus] + I[Infer: vLLM · SGLang · SGLang Disagg] + end + + OUT[(perf.csv / JSON)] + + C1 --> O1 + C2 --> O2 + C3 --> O3 + O2 --> MAN --> O3 + O1 --> O2 + O3 --> E1 + O3 --> E2 + E2 --> K8S + E2 --> SLURM + E1 --> LAUNCH + K8S --> LAUNCH + SLURM --> LAUNCH + LAUNCH --> OUT + OUT --> C4 + OUT --> C5 +``` 1. **CLI Layer** - User interface with 5 commands (discover, build, run, report, database) 2. **Model Discovery** - Find and validate models from MAD package @@ -106,7 +154,7 @@ madengine operates within the MAD (Model Automation and Dashboarding) ecosystem. - **torchrun** - PyTorch DDP/FSDP - **deepspeed** - ZeRO optimization -- **megatron** - Large transformers (K8s + SLURM) +- **megatron-lm** - Large transformers (K8s + SLURM) - **torchtitan** - LLM pre-training - **vllm** - LLM inference - **sglang** - Structured generation @@ -119,7 +167,7 @@ This documentation follows these principles: 2. **Progressive disclosure** - Start simple, add complexity as needed 3. **Examples first** - Show working examples before explaining details 4. **Consistent naming** - Files follow simple naming pattern (no prefixes) -5. **Up-to-date** - Reflects current implementation (v2.0) +5. **Up-to-date** - Tracks the current implementation; see [CHANGELOG.md](../CHANGELOG.md) for release history ## 🤝 Contributing to Documentation diff --git a/docs/batch-build.md b/docs/batch-build.md index 24983d98..7bac329f 100644 --- a/docs/batch-build.md +++ b/docs/batch-build.md @@ -224,11 +224,15 @@ Creates `build_manifest.json` with: } }, "built_models": {...}, + "context": {...}, + "credentials_required": {...}, "deployment_config": {...}, "summary": {...} } ``` +> `deployment_config` is only written when `--additional-context` resolves to a non-local deployment (e.g. `slurm`, `k8s`/`kubernetes`, `distributed`, `vllm`, or non-empty `env_vars`). Plain local builds omit this key entirely. + ## Best Practices 1. **Version Control**: Keep batch manifests in version control for reproducibility diff --git a/docs/cli-reference.md b/docs/cli-reference.md index 842e0fdf..0e9c869b 100644 --- a/docs/cli-reference.md +++ b/docs/cli-reference.md @@ -233,10 +233,10 @@ madengine run [OPTIONS] | `--manifest-output` | | TEXT | `build_manifest.json` | Output file for build manifest (full workflow) | | `--summary-output` | `-s` | TEXT | `None` | Output file for summary JSON | | `--live-output` | `-l` | FLAG | `False` | Print output in real-time | -| `--output` | `-o` | TEXT | `perf_entry.csv` | Performance output file | +| `--output` | `-o` | TEXT | `perf.csv` | Performance output file | | `--ignore-deprecated` | | FLAG | `False` | Force run deprecated models | | `--data-config` | | TEXT | `data.json` | Custom data configuration file | -| `--tools-config` | | TEXT | `tools.json` | Custom tools JSON configuration | +| `--tools-config` | | TEXT | `./scripts/common/tools.json` | Custom tools JSON configuration | | `--sys-env-details` | | FLAG | `True` | Generate system config env details | | `--force-mirror-local` | | TEXT | `None` | Path to force local data mirroring | | `--disable-skip-gpu-arch` | | FLAG | `False` | Disable skipping models based on GPU architecture | @@ -356,7 +356,7 @@ madengine run --tags model \ **Performance Output:** -Results are saved to CSV file (default: `perf_entry.csv`) with metrics including: +Results are saved to CSV file (default: `perf.csv`) with metrics including: - Execution time - GPU utilization - Memory usage @@ -384,20 +384,20 @@ madengine report to-html [OPTIONS] | Option | Short | Type | Required | Description | |--------|-------|------|----------|-------------| -| `--csv-file` | | TEXT | **Yes** | Path to the CSV file to convert | +| `--csv-file-path` | | TEXT | **Yes** | Path to the CSV file to convert | | `--verbose` | `-v` | FLAG | No | Enable verbose logging | **Examples:** ```bash # Convert CSV to HTML -madengine report to-html --csv-file perf_entry.csv +madengine report to-html --csv-file-path perf_entry.csv # With custom CSV file -madengine report to-html --csv-file results/perf_mi300.csv +madengine report to-html --csv-file-path results/perf_mi300.csv # Verbose output -madengine report to-html --csv-file perf.csv --verbose +madengine report to-html --csv-file-path perf.csv --verbose ``` **Output:** Creates `{filename}.html` in the same directory as the CSV file. @@ -444,7 +444,7 @@ madengine report to-email --directory ./results --verbose ### `database` - Upload to MongoDB -Upload CSV performance data to MongoDB database. +Upload CSV or JSON performance data to MongoDB (format is auto-detected). **Usage:** @@ -456,32 +456,30 @@ madengine database [OPTIONS] | Option | Short | Type | Default | Required | Description | |--------|-------|------|---------|----------|-------------| -| `--csv-file` | | TEXT | `perf_entry.csv` | No | Path to the CSV file to upload | -| `--database-name` | `--db` | TEXT | `None` | **Yes** | Name of the MongoDB database | -| `--collection-name` | `--collection` | TEXT | `None` | **Yes** | Name of the MongoDB collection | -| `--verbose` | `-v` | FLAG | `False` | No | Enable verbose logging | +| `--file` | `-f` | TEXT | `None` | **Yes** | Path to file (CSV or JSON, auto-detected) | +| `--database` | `--db` | TEXT | `None` | **Yes** | MongoDB database name | +| `--collection` | `-c` | TEXT | `None` | **Yes** | MongoDB collection name | +| `--unique-key` | `-k` | TEXT | `None` | No | Unique field(s) for deduplication (comma-separated, auto-detected if not specified) | +| `--batch-size` | | INT | `1000` | No | Batch size for bulk operations | +| `--no-upsert` | | FLAG | `False` | No | Insert only (don't update existing documents) | +| `--no-index` | | FLAG | `False` | No | Skip automatic index creation | +| `--dry-run` | | FLAG | `False` | No | Validate without uploading | +| `--verbose` | `-v` | FLAG | `False` | No | Verbose output | **Examples:** ```bash -# Upload to MongoDB -madengine database \ - --csv-file perf_entry.csv \ - --database-name mydb \ - --collection-name results +# Upload JSON with auto-detection +madengine database -f perf_entry_super.json --db mydb -c perf_super -# Short option names -madengine database \ - --csv-file perf.csv \ - --db test \ - --collection perf_data +# Upload CSV with custom unique key +madengine database -f perf.csv --db test -c results -k model,timestamp + +# Dry run to validate +madengine database -f data.json --db test -c data --dry-run # With verbose output -madengine database \ - --csv-file perf.csv \ - --db mydb \ - --collection results \ - --verbose +madengine database -f perf.csv --db mydb -c results --verbose ``` **Environment Variables:** @@ -490,10 +488,12 @@ MongoDB connection details are read from environment variables: | Variable | Description | Example | |----------|-------------|---------| -| `MONGO_HOST` | MongoDB host address | `localhost` or `mongodb.example.com` | -| `MONGO_PORT` | MongoDB port | `27017` | +| `MONGO_HOST` | MongoDB host address (default: `localhost`) | `localhost` or `mongodb.example.com` | +| `MONGO_PORT` | MongoDB port (default: `27017`) | `27017` | | `MONGO_USER` | MongoDB username | `admin` | | `MONGO_PASSWORD` | MongoDB password | `secretpassword` | +| `MONGO_AUTH_SOURCE` | MongoDB authentication database (default: `admin`) | `admin` | +| `MONGO_TIMEOUT_MS` | Server selection timeout in milliseconds (default: `5000`) | `5000` | **Example Setup:** @@ -504,7 +504,7 @@ export MONGO_USER=myuser export MONGO_PASSWORD=mypassword madengine database \ - --csv-file perf_entry.csv \ + --file perf_entry.csv \ --db performance_db \ --collection model_runs ``` @@ -637,12 +637,17 @@ madengine recognizes these environment variables: | `MAD_DOCKERHUB_USER` | Docker Hub username | None | | `MAD_DOCKERHUB_PASSWORD` | Docker Hub password/token | None | | `MAD_DOCKERHUB_REPO` | Docker Hub repository | None | -| `MAD_CONTAINER_IMAGE` | Pre-built container image to use | None | +| `DOCKER_CONFIG` | Directory holding the Docker `config.json` whose existing login madengine reuses | `~/.docker` | +| `MAD_SKIP_DOCKER_LOGIN` | Set to `1` to never run `docker login`; always defer to the machine's existing credentials | Unset | | `MONGO_HOST` | MongoDB host for database command | `localhost` | | `MONGO_PORT` | MongoDB port for database command | `27017` | | `MONGO_USER` | MongoDB username | None | | `MONGO_PASSWORD` | MongoDB password | None | +> `MAD_CONTAINER_IMAGE` is **not** an environment variable. It is an +> `--additional-context` key that selects a pre-built image and skips the build +> phase — see [Configuration](configuration.md#pre-built-container-images). + --- ## Best Practices @@ -669,6 +674,6 @@ madengine recognizes these environment variables: --- -**Version:** 2.1.0 -**Last Updated:** May 2026 +Run `madengine --version` for the installed version (derived from git tags via +versioningit). Release history is in [CHANGELOG.md](../CHANGELOG.md). diff --git a/docs/configuration.md b/docs/configuration.md index dcdc4e3d..f3f46d29 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -21,8 +21,7 @@ madengine run --tags model --additional-context-file config.json ```json { "gpu_vendor": "AMD", - "guest_os": "UBUNTU", - "timeout_multiplier": 2.0 + "guest_os": "UBUNTU" } ``` @@ -179,13 +178,13 @@ Unknown `rocenv_mode` values fall back to `lite` with a warning. **Overrides** (recommended for CI): - **Additional context (host):** top-level `"MAD_ROCM_PATH": "/path/to/host/rocm"` — controls where madengine looks for host GPU tools (`rocminfo`, `amd-smi`, etc.). -- **Additional context (container):** `"docker_env_vars": { "MAD_ROCM_PATH": "/path/inside/image" }` — sets the in-container `ROCM_PATH` for Docker runs. If omitted, at `run` time madengine uses the image OCI `Env` (`ROCM_PATH` / `ROCM_HOME`) if present, then an in-container probe, then defaults to `/opt/rocm`. The host-resolved path is **not** mirrored into the container. +- **Additional context (container):** `"docker_env_vars": { "ROCM_PATH": "/path/inside/image" }` — sets the in-container `ROCM_PATH` for Docker runs. If omitted, at `run` time madengine uses the image OCI `Env` (`ROCM_PATH` / `ROCM_HOME`) if present, then an in-container probe, then defaults to `/opt/rocm`. The host-resolved path is **not** mirrored into the container. These two keys are independent, allowing host and container to use different ROCm installations without confusion. Precedence (host): top-level `MAD_ROCM_PATH` → auto-detect (unless disabled) → `ROCM_PATH` → `/opt/rocm`. -Precedence (container, **local Docker `run`**, **AMD**): `docker_env_vars.MAD_ROCM_PATH` (maps to `ROCM_PATH` for the workload) or explicit `ROCM_PATH` in `docker_env_vars` → image OCI `Env` (`ROCM_PATH` / `ROCM_HOME`) → in-image probe → default `/opt/rocm` with a warning. Implemented in `ContainerRunner.run_container` after the run image is resolved. +Precedence (container, **local Docker `run`**, **AMD**): explicit `ROCM_PATH` in `docker_env_vars` → image OCI `Env` (`ROCM_PATH` / `ROCM_HOME`) → in-image probe → default `/opt/rocm` with a warning. Implemented in `ContainerRunner.run_container` after the run image is resolved. This applies to the run phase; build uses build-only context (no GPU detection) but still honors `MAD_ROCM_PATH` in context when set. @@ -264,17 +263,25 @@ Pass environment variables to containers: } ``` -### Custom Base Image +### Pre-Built Container Images -Override Docker base image: +`MAD_CONTAINER_IMAGE` runs the model in an image you already have, **skipping the +build phase entirely**. madengine validates the image exists locally (pulling it +if not) and writes a synthetic `build_manifest.json` for it: -```json -{ - "MAD_CONTAINER_IMAGE": "rocm/pytorch:custom-tag" -} +```bash +madengine run --tags my_model \ + --additional-context "{'MAD_CONTAINER_IMAGE': 'rocm/pytorch:custom-tag'}" ``` -Or override BASE_DOCKER in FROM line: +`--tags` is required in this mode — without it madengine has no models to map +onto the image and fails with a configuration error. Note this is an +`--additional-context` key, not an environment variable. + +### Custom Base Image + +To keep the normal build but change the image the Dockerfile's `FROM` line +resolves to, override `BASE_DOCKER` as a build argument: ```json { @@ -328,13 +335,15 @@ Format: Comma-separated list with hyphen ranges. ### Timeout Settings +Set a per-model timeout (seconds) in `models.json`: + ```json { - "timeout_multiplier": 2.0 + "timeout": 7200 } ``` -Or use command-line option: +Or use the command-line option, which overrides the model's timeout: ```bash madengine run --tags model --timeout 7200 @@ -388,7 +397,6 @@ Automatically applies (see presets under `src/madengine/deployment/presets/k8s/` "memory_limit": "64Gi", "cpu": "16", "cpu_limit": "32", - "service_account": "madengine-sa", "image_pull_policy": "Always", "ttl_seconds_after_finished": null, "allow_privileged_profiling": null, @@ -409,7 +417,6 @@ Automatically applies (see presets under `src/madengine/deployment/presets/k8s/` - `memory_limit` - Memory limit (default: 2× memory request) - `cpu` - CPU cores request (default: auto-scaled by GPU count) - `cpu_limit` - CPU cores limit (default: 2× CPU request) -- `service_account` - Service account name - `image_pull_policy` - `Always`, `IfNotPresent`, or `Never` - `ttl_seconds_after_finished` - Optional Job TTL in seconds (auto-delete finished Job); `null` to omit - `allow_privileged_profiling` - `null` means enable elevated `securityContext` when tools/profiling are configured; `true`/`false` to force @@ -417,6 +424,26 @@ Automatically applies (see presets under `src/madengine/deployment/presets/k8s/` - `secrets.image_pull_secret_names` - Extra pull secret names (strings) merged with any created from `credential.json` when using `from_local_credentials` - `secrets.runtime_secret_name` - Required for `existing` (pre-created opaque Secret with key `credential.json`); optional for `omit` if you still mount a runtime Secret +**Cluster and scheduling keys:** +- `kubeconfig` - Path to kubeconfig (default: `~/.kube/config`) +- `gpu_resource_name` - GPU resource name (default: `amd.com/gpu`; use `nvidia.com/gpu` for NVIDIA) +- `node_selector` - Label selectors for pod placement (default: `{}`) +- `tolerations` - Tolerations for tainted nodes (default: `[]`) +- `backoff_limit` - Job retry attempts before marking failed (default: `3`) +- `output_dir` - Directory for generated manifests (default: `./k8s_manifests`) + +**Storage keys:** +- `data_pvc` - Name of an existing PVC to use for data, skipping auto-creation +- `storage_class` - Broad fallback StorageClass for both the shared-data PVC and the single-node results PVC +- `nfs_storage_class` / `data_storage_class` - RWX class for shared data and multi-node results +- `single_node_results_storage_class` / `multi_node_results_storage_class` - Fine-grained results-PVC overrides (`local_path_storage_class` is the legacy single-node fallback) +- `results_storage_size` / `data_storage_size` - PVC sizes (defaults: `10Gi` / `100Gi`) +- `recreate_shared_data_pvc` - Delete and recreate `madengine-shared-data` before use. **Destroys existing data** — back up first; intended for migrating an RWO PVC to RWX + +Multi-node jobs require an RWX results StorageClass; madengine warns when one is +not set. The full key reference, PVC access-mode matrix, and preset defaults are +in [examples/k8s-configs/README.md](../examples/k8s-configs/README.md). + ### Multi-Node Kubernetes ```json @@ -458,9 +485,12 @@ Automatically applies (see presets under `src/madengine/deployment/presets/k8s/` "nodes": 2, "nodelist": "node01,node02", "time": "24:00:00", - "mem": "64G", - "mail_user": "user@example.com", - "mail_type": "ALL" + "exclusive": true, + "constraint": "mi300x", + "exclude": "node07,node09", + "modules": ["rocm/6.2"], + "network_interface": "ib0", + "output_dir": "./slurm_results" } } ``` @@ -471,15 +501,23 @@ Automatically applies (see presets under `src/madengine/deployment/presets/k8s/` - `partition` - SLURM partition name (required) - `account` - Billing account - `qos` - Quality of Service -- `gpus_per_node` - GPUs per node (default: 1) +- `gpus_per_node` - GPUs per node (default: 8) - `nodes` - Number of nodes (default: 1) - `nodelist` - Comma-separated node names to run on (e.g. `"node01,node02"`); when set, job is restricted to these nodes and automatic node health preflight is skipped - `reservation` - SLURM reservation name; forwarded to srun health/cleanup commands and SBATCH directives - `exclusive` - Exclusive node access (default: `true`) -- `time` - Wall time limit HH:MM:SS (required) -- `mem` - Memory per node (e.g., "64G") -- `mail_user` - Email for notifications -- `mail_type` - Notification types (BEGIN, END, FAIL, ALL) +- `time` - Wall time limit HH:MM:SS (default: `24:00:00`) +- `constraint` - SBATCH `--constraint` feature expression +- `exclude` - Comma-separated nodes to exclude; node health preflight appends to this list +- `modules` - Array of environment modules to `module load` in the job (default: `[]`) +- `network_interface` - Interface exported as `NCCL_SOCKET_IFNAME` / `GLOO_SOCKET_IFNAME` (e.g. `ib0`) +- `output_dir` - Directory for SLURM `.out`/`.err` files (default: `./slurm_results`) +- `skip_gpus_directive` - Omit the `#SBATCH --gpus-per-node` directive (default: `false`). Set `true` on clusters that expose no GPU GRES and reject any job script carrying it; allocation then relies on `exclusive` / `nproc_per_node`. + +Node health preflight, shared-storage, and results-collection keys (`enable_node_check`, +`auto_cleanup_nodes`, `allow_submit_without_clean_nodes`, `verbose_node_check`, +`shared_workspace`, `results_dir`) are documented in +[examples/slurm-configs/README.md](../examples/slurm-configs/README.md). ### Multi-Node SLURM @@ -523,7 +561,7 @@ Automatically applies (see presets under `src/madengine/deployment/presets/k8s/` **Supported Launchers:** - `torchrun` - PyTorch DDP/FSDP - `deepspeed` - ZeRO optimization -- `megatron` - Large transformers (K8s + SLURM) +- `megatron-lm` - Large transformers (K8s + SLURM) - `torchtitan` - LLM pre-training - `primus` - Primus unified pretrain - `vllm` - LLM inference @@ -558,14 +596,12 @@ See [Launchers Guide](launchers.md) for details. "launcher": "vllm", "nnodes": 2, "nproc_per_node": 4 - }, - "vllm": { - "tensor_parallel_size": 4, - "pipeline_parallel_size": 1 } } ``` +`nproc_per_node` is exported into the container as `VLLM_TENSOR_PARALLEL_SIZE`; there is no separate `vllm.*` config block for tensor/pipeline parallel sizing. + ## Profiling Configuration ### Basic Profiling @@ -656,14 +692,11 @@ Configure in `data.json` (MAD package root): ```json { - "data_sources": { - "model_data": { - "nas": {"path": "/home/datum"}, - "minio": {"path": "s3://datasets/datum"}, - "aws": {"path": "s3://datasets/datum"} - } - }, - "mirrorlocal": "/tmp/local_mirror" + "model_data": { + "nas": {"path": "/home/datum", "mirrorlocal": "/tmp/local_mirror"}, + "minio": {"path": "s3://datasets/datum"}, + "aws": {"path": "s3://datasets/datum"} + } } ``` @@ -678,13 +711,13 @@ Configure in `credential.json` (MAD package root): "password": "your_token", "repository": "myorg" }, - "AMD_GITHUB": { + "PUBLIC_GITHUB_ROCM_KEY": { "username": "github_username", - "password": "github_token" + "token": "github_token" }, "MAD_AWS_S3": { - "username": "aws_access_key", - "password": "aws_secret_key" + "USERNAME": "aws_access_key", + "PASSWORD": "aws_secret_key" } } ``` diff --git a/docs/contributing.md b/docs/contributing.md index 5e53a300..078b23bc 100644 --- a/docs/contributing.md +++ b/docs/contributing.md @@ -61,7 +61,7 @@ pytest pytest --cov=src/madengine --cov-report=html # Run specific test file -pytest tests/test_cli.py +pytest tests/unit/test_cli.py # Run tests matching pattern pytest -k "test_build" diff --git a/docs/deployment.md b/docs/deployment.md index fa03e7f5..c913b117 100644 --- a/docs/deployment.md +++ b/docs/deployment.md @@ -13,24 +13,28 @@ Deployment is configured via `--additional-context` and happens automatically du ## Deployment Workflow +Build once, then deploy the resulting manifest to any target: + +```mermaid +flowchart LR + B["1. Build Phase
(local or CI/CD)
madengine build --tags model"] --> M[(build_manifest.json
+ image in registry)] + M --> D["2. Deploy Phase
madengine run --manifest-file build_manifest.json
--additional-context '{...}'"] + D --> T{detect target} + T -->|k8s / kubernetes| K[K8s Job] + T -->|slurm| S[SLURM script] + T -->|neither| L[Local Docker] ``` -┌─────────────────────────────────────────────┐ -│ 1. Build Phase (Local or CI/CD) │ -│ madengine build --tags model │ -│ → Creates Docker image │ -│ → Pushes to registry │ -│ → Generates build_manifest.json │ -└─────────────────────────────────────────────┘ - ↓ -┌─────────────────────────────────────────────┐ -│ 2. Deploy Phase (Run with Context) │ -│ madengine run │ -│ --manifest-file build_manifest.json │ -│ --additional-context '{"deploy":...}' │ -│ → Detects deployment target │ -│ → Creates K8s Job or SLURM script │ -│ → Submits and monitors execution │ -└─────────────────────────────────────────────┘ + +### Deployment Target Inference + +No explicit `deploy` field is required — the target is inferred from the config structure (Convention over Configuration): + +```mermaid +flowchart TD + A[additional_context] --> Q{which key?} + Q -->|k8s / kubernetes| K[Kubernetes deployment] + Q -->|slurm| S[SLURM deployment] + Q -->|neither| L[Local Docker execution] ``` ## Kubernetes Deployment @@ -90,11 +94,12 @@ The deployment target is automatically detected from the `k8s` key in the config } ``` -**Configuration Priority:** -1. User config (`--additional-context-file`) -2. Profile presets (single-gpu/multi-gpu) -3. GPU vendor presets (AMD/NVIDIA) -4. Base defaults +**Configuration Priority (lowest to highest precedence):** +1. Base defaults +2. GPU vendor preset (AMD/NVIDIA) +3. GPU vendor multi-GPU preset (AMD only, for multi-GPU/multi-node) +4. Profile preset (single-gpu/multi-gpu/multi-node) +5. User config (`--additional-context-file`) — highest precedence, applied last See [examples/k8s-configs/](../examples/k8s-configs/) for complete examples. @@ -229,9 +234,7 @@ The deployment target is automatically detected from the `slurm` key in the conf "qos": "normal", "gpus_per_node": 8, "nodes": 1, - "time": "24:00:00", - "mail_user": "user@example.com", - "mail_type": "ALL" + "time": "24:00:00" } } ``` @@ -245,10 +248,7 @@ The deployment target is automatically detected from the `slurm` key in the conf - `nodelist`: Comma-separated node names to run on (e.g. `"node01,node02"`); when set, job runs only on these nodes and node health preflight is skipped - `reservation`: SLURM reservation name; forwarded to srun health/cleanup commands - `time`: Wall time limit (HH:MM:SS) -- `mem`: Memory per node (e.g., "64G") - `exclusive`: Exclusive node access (default: `true`) -- `mail_user`: Email for job notifications -- `mail_type`: Notification types (BEGIN, END, FAIL, ALL) See [examples/slurm-configs/](../examples/slurm-configs/) for complete examples. diff --git a/docs/img/architecture_overview.png b/docs/img/architecture_overview.png deleted file mode 100755 index 7bf972b391a4d61945b38698e81cc1de0330530d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 258476 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

TxeDH_?DyQhE7E8_a9eqBo_832aym&< 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

  • 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�f*|HBY&~v2l6%@q$$6+_du4uME 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%0A3SIK32JR^m6fK?52xONgjmqIzv=AR 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⋘>N+s#79z7Q)*Mw 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 diff --git a/docs/installation.md b/docs/installation.md index c8c9eb4c..e4cbd658 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -91,7 +91,6 @@ madengine run --tags dummy \ ```bash # Check installation madengine --version -madengine --version # Test basic functionality (requires MAD package) cd /path/to/MAD diff --git a/docs/launchers.md b/docs/launchers.md index 227557aa..658b252a 100644 --- a/docs/launchers.md +++ b/docs/launchers.md @@ -19,7 +19,7 @@ madengine provides unified support for multiple distributed frameworks, enabling | **Primus** | Training | Megatron/TorchTitan/Jax via Primus config | ✅ | ✅ | ✅ | | **vLLM** | Inference | High-throughput LLM serving | ✅ | ✅ | ✅ | | **SGLang** | Inference | Fast LLM inference | ✅ | ✅ | ✅ | -| **SGLang Disaggregated** | Inference | Large-scale disaggregated inference | ✅ | ✅ | ✅ (min 3) | +| **SGLang Disaggregated** | Inference | Large-scale disaggregated inference | ✅ | ✅ | ✅ (min 2 on SLURM, 3 on K8s) | | **slurm_multi** | Escape hatch | Self-managed multi-container topologies | ❌ | ✅ | ✅ | --- @@ -69,11 +69,13 @@ madengine run --manifest-file build_manifest.json "launcher": "torchrun", "nnodes": 2, "nproc_per_node": 8, - "master_port": 29500 + "port": 29500 } } ``` +Note: `distributed.port` sets the master port on **SLURM** (`distributed.get("port", 29500)`). On **Kubernetes**, the master port is instead read from a separate top-level `"launcher"` object (`{"launcher": {"master_port": 29500}}`), not from `distributed`. + **Features**: - Automatic rank assignment - NCCL backend for GPU communication @@ -136,7 +138,7 @@ madengine run --manifest-file build_manifest.json ```json { "distributed": { - "launcher": "megatron", + "launcher": "megatron-lm", "nnodes": 4, "nproc_per_node": 8 } @@ -276,8 +278,6 @@ Optional **`primus.backend`** (e.g. `MaxText`, `megatron`) emits `export BACKEND **Container image**: Prefer `docker/primus.ubuntu.amd.Dockerfile` with `COPY scripts/Primus/ /workspace/Primus/` and `PRIMUS_ROOT=/workspace/Primus`. On **Kubernetes**, the Job’s emptyDir hides image files under `/workspace`; madengine bundles `scripts/Primus/examples/...` into the ConfigMap as `Primus/examples/...` so the init container recreates `/workspace/Primus`. `run.sh` resolves `PRIMUS_ROOT` in that order (see script comments). **Examples**: -- SLURM: `examples/slurm-configs/minimal/primus-minimal.json` -- K8s: `examples/k8s-configs/minimal/primus-minimal.json` - K8s (Primus vs upstream workload API, MaxText caveats, TorchTitan/Megatron/MaxText sample JSON): `examples/k8s-configs/README.md` section **Primus on Kubernetes** --- @@ -314,13 +314,13 @@ Optional **`primus.backend`** (e.g. `MaxText`, `megatron`) emits `export BACKEND **Architecture**: - Single-node: TP across GPUs, no Ray - Multi-node (K8s): Data Parallelism with independent replicas per pod -- Multi-node (SLURM): TP + PP with Ray cluster +- Multi-node (SLURM): Data Parallelism (one vLLM serve per node, TP only on that node, no shared Ray cluster) **Environment Variables**: ```bash VLLM_TENSOR_PARALLEL_SIZE=4 VLLM_PIPELINE_PARALLEL_SIZE=1 -VLLM_DISTRIBUTED_BACKEND="auto" # or "ray" for multi-node +VLLM_DISTRIBUTED_BACKEND="auto" # or "none" for multi-node SLURM (data parallel) ``` **Examples**: @@ -419,25 +419,31 @@ SGLang Disaggregated separates inference into specialized node pools: ``` **Minimum Requirements**: -- **Nodes**: Minimum 3 nodes (1 proxy + 1 prefill + 1 decode) +- **Nodes**: **SLURM** — minimum 2 nodes (co-located proxy on the first prefill node + 1 prefill + 1 decode). **Kubernetes** — minimum 3 nodes (dedicated proxy + 1 prefill + 1 decode); a 2-node co-located topology is rejected. - **GPUs**: Minimum 1 GPU per node (for tensor parallelism) - **Network**: High-speed interconnect (InfiniBand recommended for production) +The proxy/router is started by the model's `run.sh`, not by the launcher, so on +SLURM both layouts are valid: a dedicated proxy node (`1 + xP + yD == nnodes`) or +a proxy co-located on the first prefill node (`xP + yD == nnodes`). + **Node Roles**: 1. **Proxy Node (Rank 0)**: Load balancer, request router (mini_lb) 2. **Prefill Nodes**: Process input prompts, generate KV cache 3. **Decode Nodes**: Receive KV cache, generate output tokens **Automatic Split (Default)**: -- Uses 40/60 golden ratio for prefill/decode -- Formula: `prefill = max(1, (nnodes - 1) * 2 // 5)` +- Uses 40/60 golden ratio for prefill/decode across the worker nodes +- Formula: `prefill = max(1, (nnodes - 1) * 2 // 5)`, `decode = nnodes - 1 - prefill` +- `nnodes == 2` is special-cased on SLURM to 1 prefill + 1 decode with a co-located proxy (the general formula would yield 0 decode nodes) | Total Nodes | Proxy | Prefill | Decode | |-------------|-------|---------|--------| -| 3 | 1 | 1 (33%) | 1 (33%) | -| 5 | 1 | 2 (40%) | 2 (40%) | -| 7 | 1 | 2 (29%) | 4 (57%) | -| 11 | 1 | 4 (40%) | 6 (60%) | +| 2 (SLURM only) | co-located | 1 | 1 | +| 3 | 1 | 1 | 1 | +| 5 | 1 | 1 | 3 | +| 7 | 1 | 2 | 4 | +| 11 | 1 | 4 | 6 | **Custom Split (NEW Feature!)**: @@ -469,7 +475,8 @@ Override automatic split based on workload characteristics: **Validation Rules**: - `prefill_nodes >= 1` - `decode_nodes >= 1` -- `prefill_nodes + decode_nodes + 1 == nnodes` +- `prefill_nodes + decode_nodes + 1 == nnodes` (dedicated proxy node), **or** + `prefill_nodes + decode_nodes == nnodes` (proxy co-located on the first prefill node, SLURM) **Features**: - Disaggregated prefill/decode architecture @@ -529,14 +536,14 @@ SGLANG_NODE_IPS="10.0.0.1,10.0.0.2,..." **Performance Tuning**: ```bash # Start with automatic split -madengine run --tags model --config minimal-config.json +madengine run --tags model --additional-context-file minimal-config.json # Monitor bottleneck (prefill latency vs decode throughput) # If prefill is bottleneck → increase prefill nodes # If decode is bottleneck → increase decode nodes # Apply custom split -madengine run --tags model --config custom-split-config.json +madengine run --tags model --additional-context-file custom-split-config.json ``` **Troubleshooting**: @@ -694,6 +701,34 @@ madengine run --manifest-file build_manifest.json --- +## Parallelism Capabilities + +How each launcher handles the various parallelism strategies. `✅Auto` = supported and configured by madengine; `❗Manual` = supported by the launcher but requires user configuration; `❗Limited` / `❗Disabled` = launcher or platform limitation. + +| Launcher | Tensor Parallel (TP) | Pipeline Parallel (PP) | Data Parallel (DP) | Context Parallel (CP) | FSDP/ZeRO | Expert Parallel (EP) | Primary Use Case | +|----------|----------------------|------------------------|--------------------|------------------------|-----------|----------------------|------------------| +| **torchrun** | ❗Manual | ❌No | ❗Manual (DDP) | ❌No | ❗Manual (FSDP) | ❌No | General distributed training | +| **TorchTitan** | ✅Auto | ✅Auto | ✅Auto (FSDP2) | ❗Manual | ✅Auto (FSDP2) | ❌No | Large-scale LLM pre-training | +| **DeepSpeed** | ❗Manual | ❗Manual | ✅Auto (ZeRO) | ❌No | ✅Auto (ZeRO) | ❌No | Memory-efficient training | +| **Megatron-LM** | ✅Auto | ✅Auto | ✅Implicit | ✅Auto | ❌No | ❌No | Large transformer training | +| **Primus** | ❗Manual | ❗Manual | ❗Manual | ❗Manual | ❗Manual | ❌No | Unified pretrain (experiment YAML; backend-specific) | +| **vLLM** | ✅Auto | SLURM: ✅Auto (Multi) / K8s: ❗Disabled | ✅Auto (Replicas) | ❌No | ❌No | ❗Manual | High-throughput inference | +| **SGLang** | ✅Auto | SLURM: ✅Auto (Multi) / K8s: ❗Disabled | ❗Limited | ❌No | ❌No | ❌No | Inference + structured gen | +| **SGLang PD Disagg** | ✅Auto | ❌No | ✅Role-based | ❌No | ❌No | ❌No | Optimized prefill/decode | + +## Infrastructure Capabilities + +| Feature | Local | Kubernetes | SLURM | +|---------|-------|-----------|-------| +| **Execution** | Docker containers | K8s Jobs | SLURM jobs | +| **Multi-Node** | ❌ | ✅ Indexed Jobs | ✅ Job arrays | +| **Resource Mgmt** | Manual | Declarative (YAML) | Batch scheduler | +| **Monitoring** | Docker logs | kubectl/dashboard | squeue/scontrol | +| **Auto-scaling** | ❌ | ✅ | ❌ | +| **Network** | Host | CNI plugin | InfiniBand/Ethernet | + +--- + ## Configuration Best Practices ### 1. Launcher Selection @@ -835,7 +870,7 @@ SGLANG_NODE_RANK=${SLURM_PROCID} ```bash Error: Unknown launcher type 'xyz' ``` -Solution: Use one of: `torchrun`, `deepspeed`, `megatron`, `torchtitan`, `primus`, `vllm`, `sglang`, `sglang-disagg`, `slurm_multi` (or `slurm-multi`) +Solution: Use one of: `torchrun`, `deepspeed`, `megatron-lm`, `torchtitan`, `primus`, `vllm`, `sglang`, `sglang-disagg`, `slurm_multi` (or `slurm-multi`) **2. Multi-Node Communication Fails** ```bash @@ -878,7 +913,7 @@ madengine provides `$MAD_MULTI_NODE_RUNNER` for frameworks that use torchrun: #!/bin/bash # Your model script -# For torchrun/deepspeed/megatron/torchtitan +# For torchrun/deepspeed/megatron-lm/torchtitan $MAD_MULTI_NODE_RUNNER your_training_script.py --args # For primus (no MAD_MULTI_NODE_RUNNER; use run.sh → run_pretrain.sh; PRIMUS_* / BACKEND set by madengine) diff --git a/docs/profiling.md b/docs/profiling.md index 5e421ec8..367313ef 100644 --- a/docs/profiling.md +++ b/docs/profiling.md @@ -93,6 +93,8 @@ madengine uses `rocprof_wrapper.sh` to automatically handle the transition betwe ``` 3. **Backward Compatibility:** The `--` works with both rocprof and rocprofv3, ensuring your configurations work across ROCm versions +**Related presets:** `rocprof_hip_only` (HIP trace only) and `rocprof_sys` (system trace only) wrap the same `rocprof_wrapper.sh` with fixed flags, for when you don't need a custom `cmd`. + **Example - Custom Command with Wrapper:** ```json { @@ -258,6 +260,8 @@ ROCprofv3 is the next-generation profiler for ROCm 7.0+ with enhanced features a - **Metrics**: Program counter sampling at 1000 Hz - **Output Format**: Perfetto trace with PC samples +**Other presets:** `rocprofv3` (bare `--runtime-trace` invocation, no wrapper), `rocprofv3_agent` (CSV stats/kernel trace via `rocprof_wrapper.sh`), and `rocprofv3_agent_counter` (same as `rocprofv3_agent` plus hardware counters from `counters/instruction_mix.txt`) are also available as `{"name": "..."}` tool entries. + #### Using Pre-Configured Profiles madengine provides ready-to-use configuration files in `examples/profiling-configs/`: @@ -321,6 +325,7 @@ Custom counter files are in `scripts/common/tools/counters/`: - `memory_bound.txt` - Cache and memory metrics - `communication_bound.txt` - PCIe and synchronization metrics - `full_profile.txt` - Comprehensive metrics +- `instruction_mix.txt` - Instruction mix metrics (used by `rocprofv3_agent_counter`) Create your own counter file: ```text @@ -360,6 +365,8 @@ Trace rocBLAS API calls and configurations: **Use Case:** Analyze BLAS operations, identify optimization opportunities +**Related:** `hipblaslt_trace` traces hipBLASLt calls the same way (sets `HIPBLASLT_TRACE=1`). + ### miopen_trace - MIOpen Library Tracing Trace MIOpen API calls for deep learning operations: @@ -509,9 +516,6 @@ Profile real-time GPU memory consumption: } ``` This will generate both `gpu_info_power_profiler_output.csv` and `gpu_info_vram_profiler_output.csv`. -- `SAMPLING_RATE` - Sampling interval in seconds -- `MODE` - Must be `"vram"` for this tool -- `DUAL-GCD` - Enable dual-GCD mode **Supported Platforms:** ROCm and CUDA @@ -666,8 +670,8 @@ madengine run --tags model \ { "name": "gpu_info_power_profiler", "env_vars": { - "DEVICE": "all", - "SAMPLING_RATE": "0.1" + "POWER_DEVICE": "all", + "POWER_SAMPLING_RATE": "0.1" } }, {"name": "rccl_trace"} @@ -777,7 +781,7 @@ Balance detail vs. overhead: { "name": "gpu_info_power_profiler", "env_vars": { - "SAMPLING_RATE": "1.0" // Less overhead, less detail + "POWER_SAMPLING_RATE": "1.0" // Less overhead, less detail } } ] @@ -923,15 +927,19 @@ Tool defaults are defined in `scripts/common/tools.json`: ```json { "rocprof": { - "cmd": "rocprof --hip-trace", - "env_vars": {} + "cmd": "bash ../scripts/common/tools/rocprof_wrapper.sh --runtime-trace --", + "env_vars": {}, + "post_scripts": [ + {"path": "scripts/common/post_scripts/trace.sh", "args": "rocprof"} + ] }, "gpu_info_power_profiler": { "env_vars": { - "DEVICE": "0", - "SAMPLING_RATE": "0.1", - "MODE": "power", - "DUAL-GCD": "false" + "POWER_DEVICE": "all", + "POWER_SAMPLING_RATE": "0.1", + "POWER_MODE": "power", + "POWER_DUAL_GCD": "false", + "POWER_OUTPUT_FILE": "gpu_info_power_profiler_output.csv" } } } @@ -956,8 +964,7 @@ Validate [TheRock](https://github.com/ROCm/TheRock) ROCm installations before ru ```bash madengine run --tags dummy_therock \ - --tools therock_check \ - --additional-context '{"gpu_vendor": "AMD", "guest_os": "UBUNTU"}' + --additional-context '{"gpu_vendor": "AMD", "guest_os": "UBUNTU", "tools": [{"name": "therock_check"}]}' ``` **Standalone detection:** diff --git a/docs/usage.md b/docs/usage.md index f06557bb..a79d495b 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -46,7 +46,7 @@ madengine provides five main commands: | `build` | Build Docker images | `--tags`, `--registry`, `--batch-manifest` | | `run` | Execute models | `--tags`, `--manifest-file`, `--timeout` | | `report` | Generate HTML reports | `to-html`, `to-email` | -| `database` | Upload to MongoDB | `--csv-file`, `--database-name` | +| `database` | Upload to MongoDB | `--file`, `--db` | For complete command options and detailed examples, see **[CLI Command Reference](cli-reference.md)**. @@ -66,11 +66,11 @@ madengine run --tags model # madengine build --tags model --additional-context '{"gpu_vendor": "NVIDIA", "guest_os": "CENTOS"}' # Generate HTML report -madengine report to-html --csv-file perf_entry.csv +madengine report to-html --csv-file-path perf_entry.csv # Upload to MongoDB -madengine database --csv-file perf_entry.csv \ - --database-name mydb --collection-name results +madengine database --file perf_entry.csv \ + --database mydb --collection results ``` ## Model Discovery @@ -82,7 +82,7 @@ madengine supports three discovery methods: Central model definitions in MAD package root: ```bash -madengine discover --tags dummy pyt_huggingface_bert +madengine discover --tags dummy --tags pyt_huggingface_bert ``` ### 2. Directory-Specific Models @@ -117,18 +117,32 @@ Creates `build_manifest.json`: ```json { - "models": [ - { - "model_name": "my_model", - "image": "localhost:5000/my_model:20240115_123456", - "tag": "my_model" + "built_images": { + "ci-my_model_ubuntu": { + "model": "my_model", + "docker_image": "ci-my_model_ubuntu", + "dockerfile": "docker/my_model.ubuntu.amd.Dockerfile", + "build_duration": 42.3, + "registry": "localhost:5000" } - ], - "registry": "localhost:5000", - "build_timestamp": "2024-01-15T12:34:56Z" + }, + "built_models": { + "ci-my_model_ubuntu": { + "name": "my_model", + "dockerfile": "my_model", + "n_gpus": "1" + } + }, + "context": { + "gpu_vendor": "AMD", + "guest_os": "UBUNTU" + }, + "credentials_required": [] } ``` +`built_images` and `built_models` are both keyed by the built Docker image name. Depending on the build, the manifest may also include `deployment_config` and `summary` keys. + ### Build with Deployment Config Include deployment configuration: @@ -493,7 +507,7 @@ Convert performance CSV files to viewable HTML reports: ```bash # Single CSV to HTML -madengine report to-html --csv-file perf_entry.csv +madengine report to-html --csv-file-path perf_entry.csv # Result: Creates perf_entry.html in same directory ``` @@ -532,13 +546,13 @@ export MONGO_PASSWORD=secretpassword # Upload results madengine database \ - --csv-file perf_entry.csv \ - --database-name performance_tracking \ - --collection-name model_runs + --file perf_entry.csv \ + --database performance_tracking \ + --collection model_runs # Upload specific results madengine database \ - --csv-file results/perf_mi300.csv \ + --file results/perf_mi300.csv \ --db benchmarks \ --collection mi300_results ``` @@ -547,15 +561,15 @@ madengine database \ ```bash # 1. Run benchmarks -madengine run --tags model1 model2 model3 \ +madengine run --tags model1 --tags model2 --tags model3 \ --output perf_entry.csv # 2. Generate HTML report -madengine report to-html --csv-file perf_entry.csv +madengine report to-html --csv-file-path perf_entry.csv # 3. Upload to database madengine database \ - --csv-file perf_entry.csv \ + --file perf_entry.csv \ --db benchmarks \ --collection daily_runs @@ -586,7 +600,7 @@ Configure distributed training: **Supported Launchers:** - `torchrun` - PyTorch DDP/FSDP - `deepspeed` - ZeRO optimization -- `megatron` - Large transformers (K8s + SLURM) +- `megatron-lm` - Large transformers (K8s + SLURM) - `torchtitan` - LLM pre-training - `vllm` - LLM inference - `sglang` - Structured generation @@ -671,6 +685,8 @@ madengine build --tags model --clean-docker-cache --verbose | `MAD_DOCKERHUB_USER` | Docker Hub username | `"myusername"` | | `MAD_DOCKERHUB_PASSWORD` | Docker Hub password | `"mytoken"` | | `MAD_DOCKERHUB_REPO` | Docker Hub repository | `"myorg"` | +| `DOCKER_CONFIG` | Directory holding the Docker `config.json` whose existing login is reused | `/etc/docker-oat` | +| `MAD_SKIP_DOCKER_LOGIN` | Set to `1` to never run `docker login` and always defer to the machine's existing credentials | `"1"` | ## Best Practices @@ -764,7 +780,7 @@ if [ $? -eq 0 ]; then # Generate and upload results madengine report to-email --output ci_results.html madengine database \ - --csv-file perf.csv \ + --file perf.csv \ --db ci_results \ --collection ${CI_BUILD_ID} else diff --git a/examples/k8s-configs/README.md b/examples/k8s-configs/README.md index 40843ab3..fbd327dd 100644 --- a/examples/k8s-configs/README.md +++ b/examples/k8s-configs/README.md @@ -103,16 +103,16 @@ MODEL_DIR=tests/fixtures/dummy madengine run \ ```bash # For single GPU testing -cp examples/k8s-configs/01-single-node-single-gpu.json my-config.json +cp examples/k8s-configs/basic/01-native-single-node-single-gpu.json my-config.json # For multi-GPU (2 GPUs) -cp examples/k8s-configs/02-single-node-multi-gpu.json my-config.json +cp examples/k8s-configs/basic/02-torchrun-single-node-multi-gpu.json my-config.json # For multi-node distributed (2 nodes × 2 GPUs) -cp examples/k8s-configs/03-multi-node-basic.json my-config.json +cp examples/k8s-configs/basic/03-torchrun-multi-node-basic.json my-config.json # For data provider with auto-PVC -cp examples/k8s-configs/06-data-provider-with-pvc.json my-config.json +cp examples/k8s-configs/basic/06-data-provider-with-pvc.json my-config.json ``` #### 2. Customize for Your Cluster (Optional) @@ -158,10 +158,10 @@ Located in [`minimal/`](minimal/) directory: | File | Description | GPU Count | |------|-------------|-----------| -| [`minimal/single-gpu-minimal.json`](minimal/single-gpu-minimal.json) | Single GPU with auto-defaults | 1 | -| [`minimal/multi-gpu-minimal.json`](minimal/multi-gpu-minimal.json) | Multi-GPU with auto-defaults | 2 | -| [`minimal/multi-node-minimal.json`](minimal/multi-node-minimal.json) | Multi-node with auto-defaults | 2×2 | -| [`minimal/nvidia-gpu-minimal.json`](minimal/nvidia-gpu-minimal.json) | NVIDIA GPUs with auto-defaults | 4 | +| [`minimal/torchrun-single-gpu-minimal.json`](minimal/torchrun-single-gpu-minimal.json) | Single GPU with auto-defaults | 1 | +| [`minimal/torchrun-multi-gpu-minimal.json`](minimal/torchrun-multi-gpu-minimal.json) | Multi-GPU with auto-defaults | 2 | +| [`minimal/torchrun-multi-node-minimal.json`](minimal/torchrun-multi-node-minimal.json) | Multi-node with auto-defaults | 2×2 | +| [`minimal/torchrun-nvidia-gpu-minimal.json`](minimal/torchrun-nvidia-gpu-minimal.json) | NVIDIA GPUs with auto-defaults | 4 | | [`minimal/custom-namespace-minimal.json`](minimal/custom-namespace-minimal.json) | Shows override examples | 1 | **Distributed Launchers:** @@ -169,7 +169,6 @@ Located in [`minimal/`](minimal/) directory: | File | Launcher | Description | GPUs | |------|----------|-------------|------| | [`minimal/torchtitan-single-node-minimal.json`](minimal/torchtitan-single-node-minimal.json) | TorchTitan | LLM pre-training (single-node) | 8 | -| [`minimal/primus-minimal.json`](minimal/primus-minimal.json) | primus | Primus pretrain (edit `distributed.primus.config_path`) | 2 | | [`minimal/vllm-single-node-minimal.json`](minimal/vllm-single-node-minimal.json) | vLLM | LLM inference (single-node) | 4 | | [`minimal/sglang-single-node-minimal.json`](minimal/sglang-single-node-minimal.json) | SGLang | LLM inference (single-node) | 4 | @@ -211,24 +210,24 @@ Set `distributed.primus.config_path` to your YAML under the Primus repo layout. **MaxText caveat:** For **multi-node** MaxText, Primus `run_pretrain.sh` may run **in-container `apt` installs** (InfiniBand-related packages). Many clusters disallow that unless the image is pre-baked or policy allows it. madengine logs a **warning** when MaxText is detected (`backend` or path) and `nnodes > 1`. -Primus examples under [`basic/`](basic/): [`primus-single-node-multi-gpu.json`](basic/primus-single-node-multi-gpu.json) (one pod, multi-GPU) and [`primus-multi-node.json`](basic/primus-multi-node.json) (Indexed Job). The same files work for TorchTitan, Megatron, and MaxText: set `distributed.primus.config_path` to your experiment YAML, and rely on madengine’s `BACKEND` inference from the model name (`primus_pretrain/__...`) or set `distributed.primus.backend` only when you need an explicit override. Use `docker_env_vars.HF_TOKEN` as a placeholder or runtime secrets — do not commit real tokens. +To use Primus, start from any `distributed.launcher: "torchrun"` example under [`basic/`](basic/) or [`minimal/`](minimal/) and change `distributed.launcher` to `"primus"`, adding a nested `distributed.primus.config_path` pointing at your experiment YAML (see [Distributed Execution Fields](#distributed-execution-fields) below). This works for TorchTitan, Megatron, and MaxText: set `distributed.primus.config_path` to your experiment YAML, and rely on madengine's `BACKEND` inference from the model name (`primus_pretrain/__...`) or set `distributed.primus.backend` only when you need an explicit override. Use `docker_env_vars.HF_TOKEN` as a placeholder or runtime secrets — do not commit real tokens. ### Full Configs (Reference Examples) Complete configurations showing all available fields: -**Training Configs:** +**Training Configs (basic/):** | File | GPUs | Nodes | Launcher | Use Case | |------|------|-------|----------|----------| -| [`01-single-node-single-gpu.json`](01-single-node-single-gpu.json) | 1 | 1 | None | Basic testing, small models | -| [`01-single-node-single-gpu-tools.json`](01-single-node-single-gpu-tools.json) | 1 | 1 | None | Single GPU + monitoring | -| [`02-single-node-multi-gpu.json`](02-single-node-multi-gpu.json) | 2 | 1 | torchrun | Multi-GPU training | -| [`02-single-node-multi-gpu-tools.json`](02-single-node-multi-gpu-tools.json) | 2 | 1 | torchrun | Multi-GPU + monitoring | -| [`03-multi-node-basic.json`](03-multi-node-basic.json) | 2/node | 2 | torchrun | Multi-node basics (4 GPUs total) | -| [`04-multi-node-advanced.json`](04-multi-node-advanced.json) | 2/node | 4 | torchrun | Production multi-node (8 GPUs) | -| [`05-nvidia-gpu-example.json`](05-nvidia-gpu-example.json) | 4 | 1 | torchrun | NVIDIA GPUs (A100, H100) | -| [`06-data-provider-with-pvc.json`](06-data-provider-with-pvc.json) | 2 | 1+ | torchrun | **Data provider with auto-PVC** | +| [`basic/01-native-single-node-single-gpu.json`](basic/01-native-single-node-single-gpu.json) | 1 | 1 | None | Basic testing, small models | +| [`basic/01-native-single-node-single-gpu-tools.json`](basic/01-native-single-node-single-gpu-tools.json) | 1 | 1 | None | Single GPU + monitoring | +| [`basic/02-torchrun-single-node-multi-gpu.json`](basic/02-torchrun-single-node-multi-gpu.json) | 2 | 1 | torchrun | Multi-GPU training | +| [`basic/02-torchrun-single-node-multi-gpu-tools.json`](basic/02-torchrun-single-node-multi-gpu-tools.json) | 2 | 1 | torchrun | Multi-GPU + monitoring | +| [`basic/03-torchrun-multi-node-basic.json`](basic/03-torchrun-multi-node-basic.json) | 2/node | 2 | torchrun | Multi-node basics (4 GPUs total) | +| [`basic/04-torchrun-multi-node-advanced.json`](basic/04-torchrun-multi-node-advanced.json) | 2/node | 4 | torchrun | Production multi-node (8 GPUs) | +| [`basic/05-torchrun-nvidia-gpu-example.json`](basic/05-torchrun-nvidia-gpu-example.json) | 4 | 1 | torchrun | NVIDIA GPUs (A100, H100) | +| [`basic/06-data-provider-with-pvc.json`](basic/06-data-provider-with-pvc.json) | 2 | 1+ | torchrun | **Data provider with auto-PVC** | **Distributed Launcher Configs (basic/):** @@ -237,8 +236,6 @@ Complete configurations showing all available fields: | [`basic/torchtitan-multi-node-basic.json`](basic/torchtitan-multi-node-basic.json) | 8/node | 4 | TorchTitan | Llama 3.1 70B+ training | | [`basic/vllm-multi-node-basic.json`](basic/vllm-multi-node-basic.json) | 4/node | 2 | vLLM | High-throughput inference | | [`basic/sglang-multi-node-basic.json`](basic/sglang-multi-node-basic.json) | 4/node | 2 | SGLang | Distributed inference | -| [`basic/primus-single-node-multi-gpu.json`](basic/primus-single-node-multi-gpu.json) | 8 | 1 | primus | Primus pretrain (single pod; edit `primus.config_path`) | -| [`basic/primus-multi-node.json`](basic/primus-multi-node.json) | 8/node | 2+ | primus | Primus pretrain (multi-pod; edit `nnodes`, `primus.config_path`) | --- @@ -248,26 +245,26 @@ Complete configurations showing all available fields: | Scenario | Config File | GPUs | Nodes | |----------|-------------|------|-------| -| **Quick test** | `01-single-node-single-gpu.json` | 1 | 1 | -| **Single GPU benchmark** | `01-single-node-single-gpu-tools.json` | 1 | 1 | -| **Multi-GPU (2 GPUs)** | `02-single-node-multi-gpu.json` | 2 | 1 | -| **Multi-GPU + monitoring** | `02-single-node-multi-gpu-tools.json` | 2 | 1 | -| **Multi-node (4 GPUs)** | `03-multi-node-basic.json` | 2×2 | 2 | -| **Multi-node (8 GPUs)** | `04-multi-node-advanced.json` | 2×4 | 4 | -| **NVIDIA GPUs** | `05-nvidia-gpu-example.json` | 4 | 1 | -| **With data download** | `06-data-provider-with-pvc.json` | 2 | 1+ | +| **Quick test** | `basic/01-native-single-node-single-gpu.json` | 1 | 1 | +| **Single GPU benchmark** | `basic/01-native-single-node-single-gpu-tools.json` | 1 | 1 | +| **Multi-GPU (2 GPUs)** | `basic/02-torchrun-single-node-multi-gpu.json` | 2 | 1 | +| **Multi-GPU + monitoring** | `basic/02-torchrun-single-node-multi-gpu-tools.json` | 2 | 1 | +| **Multi-node (4 GPUs)** | `basic/03-torchrun-multi-node-basic.json` | 2×2 | 2 | +| **Multi-node (8 GPUs)** | `basic/04-torchrun-multi-node-advanced.json` | 2×4 | 4 | +| **NVIDIA GPUs** | `basic/05-torchrun-nvidia-gpu-example.json` | 4 | 1 | +| **With data download** | `basic/06-data-provider-with-pvc.json` | 2 | 1+ | ### By Use Case | Use Case | Recommended Config | |----------|-------------------| -| **Development/Testing** | `01-single-node-single-gpu.json` | -| **Small models (BERT, ResNet)** | `01-single-node-single-gpu.json` | -| **Medium models (GPT-2, Stable Diffusion)** | `02-single-node-multi-gpu.json` | -| **Large models (LLaMA-13B)** | `03-multi-node-basic.json` | -| **Very large models (LLaMA-70B+)** | `04-multi-node-advanced.json` | -| **Models requiring datasets** | `06-data-provider-with-pvc.json` | -| **Busy/shared clusters** | `02-single-node-multi-gpu.json` (2 GPUs) | +| **Development/Testing** | `basic/01-native-single-node-single-gpu.json` | +| **Small models (BERT, ResNet)** | `basic/01-native-single-node-single-gpu.json` | +| **Medium models (GPT-2, Stable Diffusion)** | `basic/02-torchrun-single-node-multi-gpu.json` | +| **Large models (LLaMA-13B)** | `basic/03-torchrun-multi-node-basic.json` | +| **Very large models (LLaMA-70B+)** | `basic/04-torchrun-multi-node-advanced.json` | +| **Models requiring datasets** | `basic/06-data-provider-with-pvc.json` | +| **Busy/shared clusters** | `basic/02-torchrun-single-node-multi-gpu.json` (2 GPUs) | --- @@ -278,7 +275,7 @@ Complete configurations showing all available fields: ```bash MODEL_DIR=tests/fixtures/dummy madengine build \ --tags dummy \ - --additional-context-file examples/k8s-configs/01-single-node-single-gpu.json \ + --additional-context-file examples/k8s-configs/basic/01-native-single-node-single-gpu.json \ --registry dockerhub MODEL_DIR=tests/fixtures/dummy madengine run \ @@ -291,7 +288,7 @@ MODEL_DIR=tests/fixtures/dummy madengine run \ ```bash MODEL_DIR=tests/fixtures/dummy madengine build \ --tags dummy_torchrun \ - --additional-context-file examples/k8s-configs/02-single-node-multi-gpu.json \ + --additional-context-file examples/k8s-configs/basic/02-torchrun-single-node-multi-gpu.json \ --registry dockerhub MODEL_DIR=tests/fixtures/dummy madengine run \ @@ -304,7 +301,7 @@ MODEL_DIR=tests/fixtures/dummy madengine run \ ```bash MODEL_DIR=tests/fixtures/dummy madengine build \ --tags dummy_torchrun \ - --additional-context-file examples/k8s-configs/03-multi-node-basic.json \ + --additional-context-file examples/k8s-configs/basic/03-torchrun-multi-node-basic.json \ --registry dockerhub MODEL_DIR=tests/fixtures/dummy madengine run \ @@ -317,7 +314,7 @@ MODEL_DIR=tests/fixtures/dummy madengine run \ ```bash MODEL_DIR=tests/fixtures/dummy madengine build \ --tags dummy_torchrun_data_minio \ - --additional-context-file examples/k8s-configs/06-data-provider-with-pvc.json \ + --additional-context-file examples/k8s-configs/basic/06-data-provider-with-pvc.json \ --registry dockerhub MODEL_DIR=tests/fixtures/dummy madengine run \ @@ -347,7 +344,7 @@ kubectl get pvc madengine-shared-data **Step 1: Use data provider config** ```bash madengine build --tags dummy_torchrun_data_minio \ - --additional-context-file examples/k8s-configs/06-data-provider-with-pvc.json \ + --additional-context-file examples/k8s-configs/basic/06-data-provider-with-pvc.json \ --registry dockerhub ``` @@ -465,7 +462,6 @@ To use an existing PVC instead of auto-creation: "_comment": "Description of this configuration", "gpu_vendor": "AMD|NVIDIA", "guest_os": "UBUNTU", - "deploy": "k8s", "k8s": { "kubeconfig": "~/.kube/config", @@ -483,8 +479,7 @@ To use an existing PVC instead of auto-creation: "node_selector": {}, "tolerations": [], - "data_pvc": null, // Optional: for data providers - "results_pvc": null // Optional: custom results storage + "data_pvc": null // Optional: for data providers }, "distributed": { @@ -512,10 +507,10 @@ To use an existing PVC instead of auto-creation: | Field | Type | Required | Description | |-------|------|----------|-------------| -| `gpu_vendor` | string | **Yes** | `"AMD"` or `"NVIDIA"` | -| `guest_os` | string | **Yes** | `"UBUNTU"`, `"RHEL"`, etc. | -| `deploy` | string | **Yes** | Must be `"k8s"` | -| `k8s` | object | **Yes** | Kubernetes configuration | +| `gpu_vendor` | string | No (default `"AMD"`) | `"AMD"` or `"NVIDIA"` | +| `guest_os` | string | No (default `"UBUNTU"`) | `"UBUNTU"` or `"CENTOS"` | +| `deploy` | string | No | Not needed — deploy target is auto-inferred from presence of the `k8s`/`kubernetes` key (or `slurm`); if set, it must agree with the inferred target | +| `k8s` | object | **Yes** | Kubernetes configuration (presence of this key is what triggers K8s deployment) | | `distributed` | object | No | Distributed training (for torchrun) | | `env_vars` | object | No | Custom environment variables | | `debug` | boolean | No | Enable debug mode (saves manifests) | @@ -551,7 +546,9 @@ To use an existing PVC instead of auto-creation: |-------|------|---------|-------------| | `image_pull_policy` | string | `"Always"` | `"Always"`, `"IfNotPresent"`, or `"Never"` | | `backoff_limit` | integer | `3` | Retry attempts before marking failed | -| `host_ipc` | boolean | `false` | Enable shared memory (required for multi-node) | +| `allow_privileged_profiling` | boolean | `null` | Whether the pod runs with the elevated privileges profiling tools need. If unset (`null`), it's auto-enabled when a `tools` config is present; set explicitly to `true`/`false` to override | + +**Note:** `host_ipc` is **not** user-configurable — madengine always sets it automatically to `nnodes > 1` (enabled for multi-node jobs, disabled otherwise). Setting `k8s.host_ipc` in your config has no effect; it is not read anywhere. **Optional - Node Selection:** @@ -565,15 +562,18 @@ To use an existing PVC instead of auto-creation: | Field | Type | Default | Description | |-------|------|---------|-------------| | `data_pvc` | string | `null` | Data PVC name (auto-created if using data provider) | -| `results_pvc` | string | `null` | Results PVC name (auto-created by default) | | `storage_class` | string | **`nfs-banff`** (preset, since 2.0.3) | Generic broad fallback for both the data PVC and the single-node results PVC when no more-specific key is set | | `nfs_storage_class` | string | **`nfs-banff`** (preset) | RWX class for shared-data / multi-node results | | `local_path_storage_class` | string | `null` (not in preset since 2.0.3; was **`local-path`** in ≤ 2.0.2) | Optional RWO class for single-node `{job}-results`. Still honoured for backward compatibility | | `data_storage_class` | string | **`nfs-banff`** (preset) | Overrides SC for shared-data only | | `single_node_results_storage_class` | string | `null` | Overrides single-node results SC (falls back to `local_path_storage_class`, then `storage_class`) | | `multi_node_results_storage_class` | string | `null` | Overrides multi-node results SC (`nfs_storage_class` if unset) | +| `results_storage_size` | string | `"10Gi"` | Size of the per-job `{job}-results` PVC | +| `data_storage_size` | string | `"100Gi"` | Size of the shared `madengine-shared-data` PVC | | `recreate_shared_data_pvc` | boolean | **`false`** (preset) | If `true`, delete `madengine-shared-data` before create (data loss) | +**Note:** `results_pvc` (the per-job results PVC name) is **not** user-configurable — madengine always creates and uses `{job_name}-results`. There is no config key that overrides this name. + #### Distributed Execution Fields Configuration for distributed workloads (training and inference): @@ -650,7 +650,7 @@ CPU: 16 (request), 32 (limit) GPUs: 2 per node (4 total) Memory: 64Gi per node CPU: 16 per node -host_ipc: true (required!) +host_ipc: enabled automatically (nnodes > 1) ``` **Multi-Node Advanced (4 nodes × 2 GPUs):** @@ -658,7 +658,7 @@ host_ipc: true (required!) GPUs: 2 per node (8 total) Memory: 128Gi per node CPU: 24 per node -host_ipc: true +host_ipc: enabled automatically (nnodes > 1) PVCs: Recommended for data and results ``` @@ -714,7 +714,7 @@ Write durable outputs under `/results//` in the container so each re - `HSA_ENABLE_SDMA=0` - Disable SDMA for better P2P **For multi-node:** -- `host_ipc: true` - Required for shared memory +- Shared memory (`host_ipc`) is enabled automatically when `distributed.nnodes > 1` — no config needed - `HSA_FORCE_FINE_GRAIN_PCIE=1` - Cross-node communication - `TORCH_NCCL_ASYNC_ERROR_HANDLING=1` - Better error handling @@ -833,14 +833,7 @@ NCCL WARN ... Unable to find NCCL communicator **Solutions:** -1. **Enable host_ipc:** -```json -{ - "k8s": { - "host_ipc": true // Required for multi-node! - } -} -``` +1. **Host IPC is automatic — no config needed:** madengine always enables shared memory (`hostIPC`) for the pod whenever `distributed.nnodes > 1`; there is no `host_ipc` config key to set, and setting one has no effect. If communication is still failing, confirm `nnodes` in your `distributed` config is actually `> 1` (a value of `1` disables host IPC). 2. **Verify headless service:** ```bash @@ -1008,7 +1001,7 @@ Use Case: Multi-GPU training, testing on busy clusters Configuration: 2 nodes × 2 GPUs per node Memory: 64Gi per node CPU: 16 per node -host_ipc: true (required!) +host_ipc: enabled automatically (nnodes > 1) Use Case: Distributed training development ``` @@ -1017,7 +1010,7 @@ Use Case: Distributed training development Configuration: 4 nodes × 2 GPUs per node Memory: 128Gi per node CPU: 24 per node -host_ipc: true +host_ipc: enabled automatically (nnodes > 1) PVCs: Recommended Use Case: Large-scale production training ``` @@ -1031,7 +1024,7 @@ Use Case: Large-scale production training ```bash # Use minimal config (defaults for everything) madengine build --tags dummy \ - --additional-context-file examples/k8s-configs/01-single-node-single-gpu.json \ + --additional-context-file examples/k8s-configs/basic/01-native-single-node-single-gpu.json \ --registry dockerhub madengine run --manifest-file build_manifest.json @@ -1042,7 +1035,7 @@ madengine run --manifest-file build_manifest.json ```bash # Use 2 GPUs to avoid scheduling conflicts madengine build --tags resnet50 \ - --additional-context-file examples/k8s-configs/02-single-node-multi-gpu.json \ + --additional-context-file examples/k8s-configs/basic/02-torchrun-single-node-multi-gpu.json \ --registry dockerhub madengine run --manifest-file build_manifest.json --live-output @@ -1053,7 +1046,7 @@ madengine run --manifest-file build_manifest.json --live-output ```bash # Multi-node for large models madengine build --tags llama_13b \ - --additional-context-file examples/k8s-configs/03-multi-node-basic.json \ + --additional-context-file examples/k8s-configs/basic/03-torchrun-multi-node-basic.json \ --registry dockerhub madengine run --manifest-file build_manifest.json --live-output @@ -1064,7 +1057,7 @@ madengine run --manifest-file build_manifest.json --live-output ```bash # Data provider with auto-PVC madengine build --tags bert_large \ - --additional-context-file examples/k8s-configs/06-data-provider-with-pvc.json \ + --additional-context-file examples/k8s-configs/basic/06-data-provider-with-pvc.json \ --registry dockerhub madengine run --manifest-file build_manifest.json --live-output @@ -1079,7 +1072,7 @@ kubectl exec -- ls -lh /data/ ```bash # Use *-tools.json variant for monitoring madengine build --tags model \ - --additional-context-file examples/k8s-configs/02-single-node-multi-gpu-tools.json \ + --additional-context-file examples/k8s-configs/basic/02-torchrun-single-node-multi-gpu-tools.json \ --registry dockerhub madengine run --manifest-file build_manifest.json --live-output @@ -1096,7 +1089,7 @@ kubectl cp :/results/gpu_info_*.csv ./ ```bash # Copy closest match -cp examples/k8s-configs/02-single-node-multi-gpu.json my-custom-config.json +cp examples/k8s-configs/basic/02-torchrun-single-node-multi-gpu.json my-custom-config.json # Edit vim my-custom-config.json @@ -1216,25 +1209,25 @@ kubectl logs | grep NCCL ## 🎓 Learning Path ### Level 1: Beginner -1. Start with `01-single-node-single-gpu.json` +1. Start with `basic/01-native-single-node-single-gpu.json` 2. Test on single GPU 3. Understand basic K8s concepts 4. Monitor logs and results ### Level 2: Intermediate -1. Try `02-single-node-multi-gpu.json` +1. Try `basic/02-torchrun-single-node-multi-gpu.json` 2. Learn distributed execution with torchrun (training workloads) 3. Understand NCCL configuration 4. Profile GPU utilization ### Level 3: Advanced -1. Deploy `03-multi-node-basic.json` +1. Deploy `basic/03-torchrun-multi-node-basic.json` 2. Master multi-node networking 3. Optimize NCCL parameters 4. Use PVCs for data and results ### Level 4: Expert -1. Customize `04-multi-node-advanced.json` +1. Customize `basic/04-torchrun-multi-node-advanced.json` 2. Fine-tune for your cluster 3. Implement node affinity and tolerations 4. Scale to 8+ nodes @@ -1250,7 +1243,7 @@ Before deploying to production: - [ ] Set appropriate memory and CPU limits - [ ] Configured node selectors (if needed) - [ ] Set NCCL environment variables -- [ ] Enabled `host_ipc` for multi-node +- [ ] Confirmed `distributed.nnodes > 1` for multi-node (host IPC is enabled automatically) - [ ] Tested with small batch size first - [ ] Configured PVCs for data (if using data providers) - [ ] Set up monitoring and logging @@ -1271,15 +1264,17 @@ Before deploying to production: ``` examples/k8s-configs/ -├── README.md # This file -├── 01-single-node-single-gpu.json # 1 GPU, basic -├── 01-single-node-single-gpu-tools.json # 1 GPU + monitoring -├── 02-single-node-multi-gpu.json # 2 GPUs, distributed -├── 02-single-node-multi-gpu-tools.json # 2 GPUs + monitoring -├── 03-multi-node-basic.json # 2 nodes × 2 GPUs -├── 04-multi-node-advanced.json # 4 nodes × 2 GPUs -├── 05-nvidia-gpu-example.json # NVIDIA GPUs -└── 06-data-provider-with-pvc.json # Data provider + auto-PVC +├── README.md # This file +├── basic/ +│ ├── 01-native-single-node-single-gpu.json # 1 GPU, basic +│ ├── 01-native-single-node-single-gpu-tools.json # 1 GPU + monitoring +│ ├── 02-torchrun-single-node-multi-gpu.json # 2 GPUs, distributed +│ ├── 02-torchrun-single-node-multi-gpu-tools.json # 2 GPUs + monitoring +│ ├── 03-torchrun-multi-node-basic.json # 2 nodes × 2 GPUs +│ ├── 04-torchrun-multi-node-advanced.json # 4 nodes × 2 GPUs +│ ├── 05-torchrun-nvidia-gpu-example.json # NVIDIA GPUs +│ └── 06-data-provider-with-pvc.json # Data provider + auto-PVC +└── minimal/ # Minimal configs (see minimal/README.md) ``` --- diff --git a/examples/slurm-configs/README.md b/examples/slurm-configs/README.md index e89f016f..0100fe54 100644 --- a/examples/slurm-configs/README.md +++ b/examples/slurm-configs/README.md @@ -42,7 +42,7 @@ The deployment type is **inferred** from the configuration structure: | File | Description | Nodes | GPUs | Use Case | |------|-------------|-------|------|----------| -| `01-torchrun-single-node-single-gpu.json` | Single GPU training | 1 | 1 | Quick tests, small models | +| `01-single-node-single-gpu.json` | Single GPU training | 1 | 1 | Quick tests, small models | | `02-single-node-multi-gpu.json` | Single node, 8 GPUs | 1 | 8 | Single-node distributed workload | | `03-multi-node-basic.json` | 2 nodes, 8 GPUs each | 2 | 16 | Multi-node distributed workload | | `03-multi-node-basic-nodelist.json` | Same as 03 with `nodelist` | 2 | 16 | Pin job to specific nodes (e.g. node01,node02) | @@ -58,15 +58,13 @@ The deployment type is **inferred** from the configuration structure: ### Minimal Examples (`minimal/`) Stripped-down configurations showing only essential fields: -- `single-gpu-minimal.json` - Minimal single GPU config -- `multi-gpu-minimal.json` - Minimal 8 GPU config -- `multi-node-minimal.json` - Minimal 2-node config -- `primus-minimal.json` - Minimal Primus pretrain (`distributed.launcher: "primus"`; edit `primus.config_path`) +- `torchrun-single-gpu-minimal.json` - Minimal single GPU config +- `torchrun-multi-gpu-minimal.json` - Minimal 8 GPU config +- `torchrun-multi-node-minimal.json` - Minimal 2-node config - `vllm-single-node-minimal.json` - Minimal vLLM single-node - `vllm-multi-node-minimal.json` - Minimal vLLM multi-node - `slurm-multi-minimal.json` - Minimal slurm_multi self-managed launcher (3 nodes) -For Primus options and environment variables, see [Launchers Guide](../../docs/launchers.md#5-primus). For slurm_multi escape-hatch launcher, see [Launchers Guide](../../docs/launchers.md#9-slurm_multi-self-managed-escape-hatch). ## 🔄 Configuration Workflow @@ -127,7 +125,7 @@ ssh user@hpc-cluster.example.com # Phase 1: Build with configuration MODEL_DIR=models/my-model madengine build \ --tags model_tag \ - --additional-context-file examples/slurm-configs/03-multi-node-basic.json \ + --additional-context-file examples/slurm-configs/basic/03-multi-node-basic.json \ --manifest-output build_manifest.json # Phase 2: Run from manifest @@ -146,7 +144,7 @@ For quick tests without custom `env_vars`: ```bash madengine run --tags model_tag \ - --additional-context-file examples/slurm-configs/minimal/single-gpu-minimal.json + --additional-context-file examples/slurm-configs/minimal/torchrun-single-gpu-minimal.json ``` ### 3. CLI Override @@ -168,7 +166,7 @@ madengine run --tags model_tag \ ```bash # Use base config, override specific fields madengine run --tags model_tag \ - --additional-context-file examples/slurm-configs/03-multi-node-basic.json \ + --additional-context-file examples/slurm-configs/basic/03-multi-node-basic.json \ --additional-context '{"slurm": {"nodes": 4, "time": "48:00:00"}}' ``` @@ -392,13 +390,23 @@ madengine uses intelligent multi-layer configuration merging: "qos": "high", // Quality of Service "account": "project-name", // SLURM account "network_interface": "ib0", // Network interface (ib0/eth0) - "modules": ["rocm/5.7.0"] // Environment modules to load + "modules": ["rocm/5.7.0"], // Environment modules to load + "enable_node_check": true, // Run node health preflight before submission (default: true) + "auto_cleanup_nodes": false, // Automatically clean up unhealthy nodes found during preflight (default: false) + "allow_submit_without_clean_nodes": false, // Submit even if fewer clean nodes than requested are found (default: false) + "verbose_node_check": false // Print detailed node health-check output (default: false) } } ``` **nodelist**: When set to a comma-separated list of node names (e.g. `"node01,node02"`), the job runs only on those nodes. Automatic node health preflight is skipped when `nodelist` is set. +**Node Health Preflight**: Before submitting multi-node jobs, madengine runs a health check across candidate nodes and pins the job to the healthy ones (skipped if `nodelist` is set). +- **`enable_node_check`** (default `true`): Enables/disables the preflight health check. +- **`auto_cleanup_nodes`** (default `false`): Automatically attempts to clean up unhealthy nodes found during preflight. +- **`allow_submit_without_clean_nodes`** (default `false`): If fewer clean nodes are found than requested, submit anyway instead of failing. +- **`verbose_node_check`** (default `false`): Print detailed health-check output during preflight. + ### Distributed Execution Section ```json @@ -452,7 +460,7 @@ For slurm_multi, the model's `.slurm` script runs on baremetal and manages Docke ```bash madengine run --tags my_model \ - --additional-context-file examples/slurm-configs/minimal/single-gpu-minimal.json + --additional-context-file examples/slurm-configs/minimal/torchrun-single-gpu-minimal.json ``` ### Multi-Node Training @@ -461,7 +469,7 @@ madengine run --tags my_model \ # Build with config MODEL_DIR=models/my-model madengine build \ --tags training \ - --additional-context-file examples/slurm-configs/03-multi-node-basic.json + --additional-context-file examples/slurm-configs/basic/03-multi-node-basic.json # Run from manifest MODEL_DIR=models/my-model madengine run \ @@ -498,7 +506,7 @@ MODEL_DIR=models/llama2-70b madengine run \ ```bash madengine build --tags my_model \ - --additional-context-file examples/slurm-configs/04-multi-node-advanced.json + --additional-context-file examples/slurm-configs/basic/04-multi-node-advanced.json madengine run --manifest-file build_manifest.json ``` @@ -759,7 +767,7 @@ module load python/3.9 # 3. Build with configuration MODEL_DIR=models/my-model madengine build \ --tags llama2_training \ - --additional-context-file examples/slurm-configs/03-multi-node-basic.json \ + --additional-context-file examples/slurm-configs/basic/03-multi-node-basic.json \ --manifest-output build_manifest.json # 4. Run from manifest diff --git a/src/madengine/database/README.md b/src/madengine/database/README.md index 2c8e5f9f..eec76ccb 100644 --- a/src/madengine/database/README.md +++ b/src/madengine/database/README.md @@ -1,81 +1,152 @@ -# Database Layer (Future MongoDB Ingestion) +# Database Layer -**Status**: Planned for future development -**Purpose**: Modern data ingestion API for local and distributed deployments +**Status**: Active +**Purpose**: Upload CSV/JSON performance results to MongoDB --- -## 🎯 Objective +## 🎯 Responsibility -This directory is reserved for a future unified database ingestion layer that will support: -- MongoDB data persistence -- Local result storage -- Distributed data collection from build and run phases -- Unified API for performance metrics ingestion +This module implements MongoDB ingestion for madengine results (e.g. `perf.csv`, +`perf_entry.csv`, or arbitrary JSON documents). It is a single, self-contained +file — `mongodb.py` — with no sub-packages. It handles: ---- +- Auto-detecting file format (CSV vs JSON) +- Loading files with native type preservation (numbers, bools, nested + JSON-in-CSV-cell strings) +- Transforming/normalizing documents (metadata stamping, numpy/pandas type + cleanup) +- Auto-detecting unique fields for deduplication when not specified +- Bulk upload to MongoDB with batching, upsert, and automatic indexing -## 📋 Current State +It is wired directly into the CLI via `madengine database` (see +`src/madengine/cli/commands/database.py`). -⚠️ **Not yet implemented**. This directory is a placeholder for future development. +--- -For current database operations, use the existing `db/` package which handles MySQL operations via SSH. +## 📦 Components (`mongodb.py`) + +### Configuration + +- **`MongoDBConfig`** — dataclass holding `host`, `port`, `username`, + `password`, `auth_source`, `timeout_ms`. `MongoDBConfig.from_env()` builds + one from `MONGO_HOST`, `MONGO_PORT`, `MONGO_USER`, `MONGO_PASSWORD`, + `MONGO_AUTH_SOURCE`, `MONGO_TIMEOUT_MS`. The `.uri` property builds the + `mongodb://` connection string. +- **`UploadOptions`** — dataclass controlling upload behavior: + `unique_fields`, `upsert`, `batch_size`, `ordered`, `create_indexes`, + `index_fields`, `add_metadata`, `metadata_prefix`, `validate_schema` + (reserved field, currently unused by the implementation), `dry_run`. +- **`UploadResult`** — dataclass returned by every upload: `status` + (`"success"` / `"partial"` / `"failed"`), `documents_read`, + `documents_processed`, `documents_inserted`, `documents_updated`, + `documents_failed`, `errors`, `duration_seconds`. Has a + `print_summary()` method that renders a formatted Rich summary. + +### File loading (Strategy pattern) + +- **`DocumentLoader`** (ABC) — defines `load(file_path)` and + `infer_schema(documents)`. +- **`JSONLoader`** — loads a JSON object or array of objects, preserving + native types. +- **`CSVLoader`** — loads via `pandas.read_csv`, preserving native types and + attempting to parse cell values that look like JSON (`{...}` / `[...]`) + back into dicts/lists. +- **`detect_file_format(file_path)`** — picks `FileFormat.CSV` or + `FileFormat.JSON` from the extension, falling back to content sniffing. +- **`get_loader(file_format)`** — returns the right loader instance. + +### Transformation + +- **`DocumentTransformer`** — takes `UploadOptions` and: + - `transform(documents)` adds metadata (`_meta_uploaded_at`, + `created_date`) and normalizes types (numpy scalars → Python, pandas + `Timestamp` → `datetime`, `NaN` → `None`). + - `infer_unique_fields(documents)` guesses a dedup key by checking + candidate fields (`model`, `name`, `id`, `timestamp`, `date`, + `pipeline`) for uniqueness across a sample of documents. + +### Upload + +- **`MongoDBUploader`** — connection + bulk-write class, usable as a context + manager (`with MongoDBUploader(config) as uploader:`). + - `connect()` / `disconnect()` + - `upload(documents, database_name, collection_name, options)` → + `UploadResult`. Creates indexes (if `options.create_indexes`) then does + either a plain `insert_many` (when no `unique_fields`/`upsert`) or a + batched `bulk_write` of `UpdateOne(..., upsert=True)` operations keyed + on `unique_fields`. + +### Entry points + +- **`upload_file_to_mongodb(file_path, database_name, collection_name, config=None, options=None) -> UploadResult`** + — the main entry point. Detects format, loads, auto-infers unique fields + if not given, transforms, honors `dry_run` (returns without connecting to + MongoDB), then uploads. +- **`upload_csv_to_mongodb(csv_file_path, database_name, collection_name, mongo_config=None) -> Dict[str, Any]`** + — deprecated wrapper around `upload_file_to_mongodb` that returns a legacy + dict shape instead of `UploadResult`. +- **`MongoDBHandler`** — deprecated class-based wrapper (`MongoDBHandler(args).run() -> bool`) + kept for backward compatibility with old argparse-style call sites. --- -## 🗂️ Legacy MySQL Tools (Removed) +## 🔗 CLI mapping (`madengine database`) -**MySQL support has been removed from madengine**. The following tools are no longer available: +`src/madengine/cli/commands/database.py` is a thin Typer wrapper around +`upload_file_to_mongodb`: -| File | Purpose | Status | -|------|---------|--------| -| ~~`tools/create_table_db.py`~~ | MySQL table creation | **REMOVED** | -| ~~`tools/update_table_db.py`~~ | MySQL table updates | **REMOVED** | -| ~~`db/` package~~ | MySQL operations via SSH | **REMOVED** | +| CLI flag | Maps to | +|---|---| +| `--file` / `-f` | `file_path` | +| `--database` / `--db` | `database_name` | +| `--collection` / `-c` | `collection_name` | +| `--unique-key` / `-k` (comma-separated) | `UploadOptions.unique_fields` | +| `--batch-size` | `UploadOptions.batch_size` | +| `--no-upsert` | `UploadOptions.upsert = False` | +| `--no-index` | `UploadOptions.create_indexes = False` | +| `--dry-run` | `UploadOptions.dry_run` | -For database operations, use MongoDB via the `database` command in the new CLI or legacy `mad.py`. +Connection config always comes from `MongoDBConfig.from_env()` (the CLI does +not expose host/port/credential flags — set `MONGO_HOST`, `MONGO_PORT`, +`MONGO_USER`, `MONGO_PASSWORD` instead). See `madengine database --help` or +`docs/cli-reference.md` for the full flag reference. --- -## 🚀 Future Implementation Plan - -When implemented, this layer will provide: - -### **1. MongoDB Client** (`mongodb_client.py`) -```python -from madengine.database.mongodb_client import MongoDBClient +## 🚀 Usage -# Connect to local or remote MongoDB -client = MongoDBClient(connection_string="mongodb://localhost:27017") +**Via the CLI:** -# Ingest build results -client.ingest_build_results(build_manifest) +```bash +export MONGO_HOST=localhost +export MONGO_USER=admin +export MONGO_PASSWORD=secret -# Ingest run results -client.ingest_run_results(run_summary) +madengine database -f perf.csv --db madengine --collection results -k model,timestamp +madengine database -f perf_entry.json --db madengine --collection results --dry-run ``` -### **2. Local Storage** (`local_storage.py`) -```python -from madengine.database.local_storage import LocalStorage - -# Store results locally (JSON, Parquet, etc.) -storage = LocalStorage(base_path="./madengine_results") -storage.save_results(results_dict) -``` +**Via the Python API:** -### **3. Unified API** (`api.py`) ```python -from madengine.database import ingest_results - -# Works with both local and distributed deployments -ingest_results( - results=run_summary, - target="mongodb", # or "local", "mysql" - config={"connection": "mongodb://..."} +from madengine.database import upload_file_to_mongodb, MongoDBConfig, UploadOptions + +result = upload_file_to_mongodb( + file_path="perf.csv", + database_name="madengine", + collection_name="results", + config=MongoDBConfig.from_env(), + options=UploadOptions(unique_fields=["model", "timestamp"], batch_size=500), ) + +result.print_summary() +print(result.status, result.documents_inserted, result.documents_updated) ``` +`config` and `options` are both optional — omit them to use +`MongoDBConfig.from_env()` and default `UploadOptions()`. + --- ## 📦 Difference from `db/` Package (Removed) @@ -87,28 +158,23 @@ ingest_results( | **Transport** | SSH tunnel | Direct connection | | **Status** | **REMOVED** | Active | ---- - -## 🔄 Migration Status - MySQL support has been fully removed from madengine: -1. ✅ **Phase 1**: Removed `db/` package (MySQL operations) -2. ✅ **Phase 2**: Removed `tools/create_table_db.py` and `tools/update_table_db.py` -3. ✅ **Phase 3**: Removed `utils/ssh_to_db.py` (SSH to MySQL host) -4. ✅ **Phase 4**: Removed MySQL dependencies (`mysql-connector-python`, `pymysql`) +1. ✅ Removed `db/` package (MySQL operations) +2. ✅ Removed `tools/create_table_db.py` and `tools/update_table_db.py` +3. ✅ Removed `utils/ssh_to_db.py` (SSH to MySQL host) +4. ✅ Removed MySQL dependencies (`mysql-connector-python`, `pymysql`) -**Current state**: Only MongoDB support remains via the `database/` package. +**Current state**: Only MongoDB support remains, via this `database/` package. --- ## 📚 References -- **MongoDB package**: `src/madengine/database/mongodb.py` -- **CLI database command**: `madengine database --help` +- **Implementation**: `src/madengine/database/mongodb.py` +- **CLI command**: `src/madengine/cli/commands/database.py`, `madengine database --help` --- -**Last Updated**: November 30, 2025 +**Last Updated**: 2026-08-05 **Maintainer**: madengine Team - diff --git a/src/madengine/execution/README.md b/src/madengine/execution/README.md index 1277cfa3..6a51f274 100644 --- a/src/madengine/execution/README.md +++ b/src/madengine/execution/README.md @@ -44,7 +44,7 @@ result = builder.build_image( # Build all models results = builder.build_all_models( - models_list=[model1, model2, model3], + models=[model1, model2, model3], target_archs=["gfx90a", "gfx942"] ) @@ -52,6 +52,10 @@ results = builder.build_all_models( builder.export_build_manifest(output_file="build_manifest.json") ``` +### **`dockerfile_utils.py`** + +Helper functions for multi-architecture Dockerfile parsing (e.g., `parse_dockerfile_gpu_variables`, `normalize_architecture_name`, `is_target_arch_compatible_with_variable`, `is_compilation_arch_compatible`) used by `docker_builder.py`. + ### **`container_runner.py`** Runs Docker containers locally for model execution. @@ -73,16 +77,19 @@ runner = ContainerRunner(context, data, console) # Run model in container result = runner.run_container( model_info=model_dict, - model_docker=docker_client, - gpu_ids="0,1", + docker_image="model1:latest", timeout=3600 ) # Result includes status, metrics, logs -print(result["status"]) # "successful", "failed", "timeout" -print(result["duration"]) +print(result["status"]) # "SUCCESS", "FAILURE", "SKIPPED" +print(result["test_duration"]) ``` +### **`container_runner_helpers.py`** + +Backs `container_runner.py`'s timeout management and error-detection features (e.g., `resolve_log_error_scan_config`, `log_text_has_error_pattern`, `resolve_run_status`, `resolve_run_timeout`, `make_run_log_file_path`). + --- ## 🏗️ Architecture diff --git a/src/madengine/reporting/README.md b/src/madengine/reporting/README.md index 33d8e5a4..ac3ec49e 100644 --- a/src/madengine/reporting/README.md +++ b/src/madengine/reporting/README.md @@ -26,43 +26,41 @@ from madengine.reporting.update_perf_csv import update_perf_csv, flatten_tags # Update CSV with new results update_perf_csv( - perf_json_path="results.json", - output_csv="performance.csv" + perf_csv="performance.csv", + single_result="results.json", ) -# Flatten nested tags for CSV export -flattened = flatten_tags(perf_entry) +# Flatten nested tags in place (no return value) +flatten_tags(perf_entry) # mutates perf_entry in place ``` ---- - -## 🗂️ Legacy Reporting Tools +### **`update_perf_super.py`** -The following legacy-only reporting tools remain in `tools/`: +Maintains `perf_super.json`, a cumulative superset performance record that also +captures matched config data, and provides conversion to `perf_super.csv` / +`perf_entry_super.json` / `perf_entry_super.csv`. -| File | Purpose | Used By | Status | -|------|---------|---------|--------| -| `tools/csv_to_html.py` | Convert CSV to HTML | `mad.py`, `run_models.py` | Legacy only | -| `tools/csv_to_email.py` | Email CSV reports | `mad.py` | Legacy only | +**Used by:** +- ✅ `execution/container_runner.py` (modern madengine CLI) -These tools are **NOT** used by the modern `madengine` CLI. +### **`csv_to_html.py`** ---- +Converts a single CSV file to an HTML table. Provides the `ConvertCsvToHtml` +handler class (`ConvertCsvToHtml.__init__(self, args: argparse.Namespace)`, +`.run(self) -> bool`) used by the CLI. -## 📋 Architecture Decision - -**Why is `update_perf_csv.py` in `reporting/` instead of `tools/`?** +**Used by:** +- ✅ `cli/commands/report.py` (backs `madengine report to-html`) -1. ✅ **Shared across architectures**: Used by both legacy and new CLI -2. ✅ **Active development**: Not deprecated, actively maintained -3. ✅ **Clear responsibility**: Performance data processing -4. ✅ **Semantic clarity**: Reporting is a distinct concern +### **`csv_to_email.py`** -**Why are other CSV tools still in `tools/`?** +Converts all CSV files in a directory into a single consolidated HTML report +suitable for emailing. Provides the `ConvertCsvToEmail` handler class +(`ConvertCsvToEmail.__init__(self, args: argparse.Namespace)`, `.run(self) -> bool`) +used by the CLI. -- They are **not used** by the modern `madengine` CLI -- Kept for backward compatibility only -- Will be deprecated when legacy CLI is retired +**Used by:** +- ✅ `cli/commands/report.py` (backs `madengine report to-email`) --- @@ -74,10 +72,10 @@ These tools are **NOT** used by the modern `madengine` CLI. from madengine.reporting.update_perf_csv import update_perf_csv # After model execution completes +perf_csv = "/path/to/performance.csv" results_json = "/path/to/results.json" -output_csv = "/path/to/performance.csv" -update_perf_csv(results_json, output_csv) +update_perf_csv(perf_csv, single_result=results_json) ``` ### **Legacy madengine** (via `run_models.py` or `mad.py`) @@ -107,6 +105,10 @@ Performance CSV (Optional) CSV → Email (legacy only) ``` +**Note:** As a side effect, `update_perf_csv()` (and the `handle_*_result()` helpers it +calls) also always write/append `perf_entry.csv` and `perf_entry.json` with the +latest result, regardless of the output file passed in. + --- ## 🧪 Testing From 438618ad17a9788b00dfdb296741d6581c6c3b9d Mon Sep 17 00:00:00 2001 From: Stephen Shao Date: Thu, 27 Aug 2026 15:52:17 -0500 Subject: [PATCH 05/16] docs: add design spec for pinned image digest enforcement Proposes capturing the pushed image digest at build time (always on) and gating enforcement of digest-pinned pulls behind an opt-in --require-pinned-image flag, addressing a run that pulled a different image than the one the build pushed due to a mutable-tag race. --- .../2026-08-27-pinned-image-digest-design.md | 156 ++++++++++++++++++ 1 file changed, 156 insertions(+) create mode 100644 docs/superpowers/specs/2026-08-27-pinned-image-digest-design.md diff --git a/docs/superpowers/specs/2026-08-27-pinned-image-digest-design.md b/docs/superpowers/specs/2026-08-27-pinned-image-digest-design.md new file mode 100644 index 00000000..08d69aea --- /dev/null +++ b/docs/superpowers/specs/2026-08-27-pinned-image-digest-design.md @@ -0,0 +1,156 @@ +# Design: pin registry pulls to the digest recorded at build time + +## Problem + +A client-perf-hub accuracy run failed because the image pulled at run time +did not match the image pushed at build time: + +- build pushed `sha256:df36ef7e...` +- the CI runner later pulled `sha256:cbb7e5ed...` + +Both shared the same registry tag. A concurrent build of the same model +pushed a newer image to that tag between the two events, so the run phase +silently executed a different image than the one the build phase produced. + +It was assumed madengine already guards against this ("we use it to confirm +if the pulled image is identical to the one in the build manifest"). It does +not. Tracing the code: + +- `build_info["docker_sha"]` (`docker_builder.py:303-308`) is the digest of + the Dockerfile's `FROM` (base) image, not the image madengine builds and + pushes. It is consumed only as a reporting column (`update_perf_csv.py`, + `k8s_results.py`, `slurm.py`). +- `push_image()` (`docker_builder.py:395`) discards `docker push` stdout, + which is where the pushed digest (`digest: sha256:...`) is printed. It is + never captured anywhere. +- The run phase pulls by tag only (`container_runner.py:2854` → + `pull_image()` at `container_runner.py:572`, a plain `docker pull `) + and performs no comparison against anything in the manifest. The only + image-identity checks in the codebase (`_local_image_id`, + `BUILD_FINGERPRINT_LABEL`, `container_runner.py:2464-2503`) exist solely + for cross-node consistency in multi-node SLURM *local-image* mode; they + never touch registry digests. + +This design adds the missing capability: capture the real pushed digest at +build time, and optionally enforce it at run time. + +## Non-goals + +- This does not prevent the tag race itself. Two builds pushing the same + mutable tag (`f"{registry}:{model_name}"`, `build_orchestrator.py:1055`) + will still clobber each other; the loser under the new flag fails fast + with a clear error instead of silently running the wrong image. Actually + eliminating the race (e.g. unique tags per build) is a client-perf-hub / + upstream CI change, out of scope here. +- This does not rename or repurpose `build_info["docker_sha"]`. It is wired + into four existing reporting sinks and column orders; changing its + meaning is a separate, riskier change and is called out only as a + possible future cleanup. +- This does not change default pull behavior for any existing user. See + "Opt-in enforcement" below. + +## Design + +### 1. Capture the pushed digest at build time (always on, default mode included) + +In `DockerBuilder.push_image()` (`docker_builder.py`), after `docker push` +succeeds, parse the digest from its output (`digest: sha256:...`, the same +line format already parsed for base-image SHA in `docker_builder.py:305`). + +If the push output doesn't contain a parseable digest line (registry output +format variance, e.g. some mirrors), fall back to: +``` +docker image inspect --format '{{index .RepoDigests 0}}' +``` +and extract the digest from that. + +If both fail to produce a digest, log a **debug/dim-level** note (not a +user-facing warning) and continue — this is a manifest-completeness gap to +leave a trail for later, not a failure. Push already succeeded; we don't +block on this. + +Store the result as a new field: `build_info["image_digest"]` (e.g. +`"sha256:df36ef7e..."`), separate from `docker_sha`. This flows into +`build_manifest.json` through the existing `built_images` / +`export_build_manifest()` path with no other changes needed. + +This capture step runs unconditionally — no flag gates it. It's the +recording half of the fix, and it's inert (never read by pull logic) unless +strict mode is on. + +### 2. Opt-in enforcement at run time + +New flag: `--require-pinned-image` on the `run` command, mirrored as an +`additional_context` key (e.g. `"require_pinned_image": true`) so CI +pipelines that drive madengine through `additional_context` rather than raw +CLI flags can set it the same way as other behavior-affecting keys +(`k8s`, `slurm`, `tools`, ...). + +**Default (flag absent):** no behavior change whatsoever. Pull by tag, +exactly as today. `image_digest` rides along in the manifest unused. + +**With the flag set**, for every `build_info` entry with a +`registry_image`: +- If `image_digest` is present, construct a pinned reference + `repo@sha256:...` (stripping any existing tag) and pull that image + instead of the tag. The registry itself now enforces identity: a moved + tag surfaces as a normal "manifest unknown" pull failure rather than a + silent wrong-image success. +- If `image_digest` is absent (older manifest, or capture failed at build + time), fail immediately with a clear error naming the model/image and + explaining that the manifest has no recorded digest — do not fall back to + pulling by tag. Silent degradation defeats the purpose of asking for the + guarantee. + +One shared helper (e.g. `build_pinned_reference(registry_image, digest)`) +builds the `repo@sha256:...` string, used identically by all three +enforcement call sites so they can't drift: + +| Path | Location | Change under the flag | +|---|---|---| +| Local Docker | `container_runner.py:2854` | `pull_image(pinned_ref)` | +| Kubernetes | `k8s_template_context.py:518` (`"image": image_info["registry_image"]`) | `"image": pinned_ref` | +| SLURM | `slurm.py:544` (parallel `srun` pull) | pinned ref substituted into the generated pull command | + +### 3. Compatibility check: log filenames + +`container_runner_helpers.py:256` already strips `@sha256:...` before +deriving log/tar filenames from an image reference (`ref_without_digest = +s.split("@", 1)[0]`). Pinned references flow through this unchanged — no +new filename collisions. Confirmed by reading the function; will be +covered by a test regardless. + +## Testing plan + +TDD; each behavior below is a separate test: + +1. `docker push` output containing a `digest: sha256:...` line → + `build_info["image_digest"]` set to that value. +2. `docker push` output without a parseable digest line → falls back to + `docker image inspect --format '{{index .RepoDigests 0}}'`. +3. Both parse paths fail → `image_digest` absent, a debug-level log line is + emitted, and the build/push otherwise succeeds unaffected. +4. Flag absent (default) → run pulls by tag regardless of whether + `image_digest` is present in the manifest; no new log output. +5. Flag present, `image_digest` present → the pull command (or k8s image + field / SLURM pull line) contains `repo@sha256:...`. +6. Flag present, `image_digest` absent → run fails immediately with a clear + error, before any pull is attempted. +7. K8s pod spec and SLURM generated script both carry the pinned reference + when the flag is set, and the untouched tag reference when it isn't. +8. Log/tar filename derivation given a pinned (`@sha256:...`) reference + produces the same filename as today's digest-free reference. +9. Existing tests touching `docker_sha` / `build_manifest.json` structure + continue to pass unmodified (the new field is additive). + +## Rollout note (for the reply to Tej/Rahul) + +- The verification described in the thread ("we use it to confirm...") + does not currently exist in the code; this design builds it, gated + behind `--require-pinned-image` / `require_pinned_image` context key. +- Turning the flag on stops the *symptom* (silently running the wrong + image) by failing fast instead. It does not stop the *cause* (two builds + racing to push the same mutable tag) — that needs a change on the + client-perf-hub / CI side (e.g. unique tags per build). +- client-perf-hub must explicitly opt in for this guarantee to apply to its + runs; it is not automatic on upgrade. From d9c4ad376fbf4f41d935583b0be7b3a31e86f74d Mon Sep 17 00:00:00 2001 From: Stephen Shao Date: Thu, 27 Aug 2026 17:37:01 -0500 Subject: [PATCH 06/16] docs: add implementation plan for pinned image digest enforcement Co-Authored-By: Claude Opus 5 --- .../plans/2026-08-27-pinned-image-digest.md | 1623 +++++++++++++++++ 1 file changed, 1623 insertions(+) create mode 100644 docs/superpowers/plans/2026-08-27-pinned-image-digest.md diff --git a/docs/superpowers/plans/2026-08-27-pinned-image-digest.md b/docs/superpowers/plans/2026-08-27-pinned-image-digest.md new file mode 100644 index 00000000..dad4b9d3 --- /dev/null +++ b/docs/superpowers/plans/2026-08-27-pinned-image-digest.md @@ -0,0 +1,1623 @@ +# Pinned Image Digest Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Record the real digest of every image madengine pushes, and let users opt into pinning run-time pulls to that digest so a moved registry tag fails loudly instead of silently running the wrong image. + +**Architecture:** A new leaf module `madengine/core/image_digest.py` holds four small pure functions (two parsers, one reference builder, one policy resolver that raises when enforcement is on but a digest is missing). `DockerBuilder.push_image()` records digests into a `self.pushed_digests` dict — mirroring the existing `self.built_images` / `self.built_models` pattern — and the two push call sites copy the digest into `build_info["image_digest"]`, which rides into `build_manifest.json` unchanged. At run time a `--require-pinned-image` flag (mirrored as the `require_pinned_image` additional-context key) flows through `RunOrchestrator.additional_context` into all three execution paths, each calling the same `resolve_pinned_image()` helper. + +**Tech Stack:** Python 3, Typer (CLI), pytest + `unittest.mock`, Jinja2 (SLURM/K8s templates), Docker CLI. + +--- + +## Background you need before starting + +Read the design spec first: `docs/superpowers/specs/2026-08-27-pinned-image-digest-design.md`. + +Facts about this codebase that the tasks below depend on: + +- `Console.sh(command)` (`src/madengine/core/console.py:138`) runs a shell command and **returns its stdout as a stripped string**, raising `RuntimeError` on non-zero exit unless `canFail=True`. This is how we capture `docker push` output. +- `build_info` dicts are created in `DockerBuilder.build_image()` (`src/madengine/execution/docker_builder.py:311-322`) and serialized wholesale into `build_manifest.json` under `built_images` by `export_build_manifest()`. **Any new key added to `build_info` appears in the manifest automatically** — no serializer changes needed. +- There are exactly two places that call `push_image` and set `build_info["registry_image"]`: `docker_builder.py:769` (single-arch) and `docker_builder.py:977` (per-GPU-arch). Both must record the digest. +- `ContainerRunner.run_models_from_manifest()` merges the manifest's `context` dict over its own `additional_context` (`src/madengine/execution/container_runner.py:2799-2800`). `RunOrchestrator._load_and_merge_manifest()` writes selected runtime keys into `manifest["context"]` (`src/madengine/orchestration/run_orchestrator.py:499-503`). Adding our key to that merge list is what makes the flag survive into the nested `madengine run` that the standard SLURM job script executes on each compute node. +- `_docker_image_ref_for_log_naming()` (`src/madengine/execution/container_runner_helpers.py:232`) already strips `@sha256:...`, so pinned references produce the same log filenames as tags. Task 9 locks that in with a test. +- Test style in this repo: `pytest` classes named `TestX` with plain `assert`, `MagicMock` for `Context`/`Console`, `tmp_path` for manifests. Follow the surrounding file's style in each test file you touch. + +### Deliberate deviations from the spec (do not "fix" these) + +1. **SLURM enforcement point.** The spec's table says the pinned reference goes into the generated `srun docker pull` line in `slurm.py:544`. Implementing only that would pull the pinned image and then `docker run` the *tag* — the exact race the feature exists to close. Instead we set `env_vars["DOCKER_IMAGE_NAME"]` to the pinned reference (Task 8); the pull line interpolates that same variable, so both pull and run are pinned by one change. +2. **Digest capture is not added to the build-on-compute-node path** (`build_orchestrator._execute_build_on_compute` at `build_orchestrator.py:748`, which pushes from a generated bash script inside an sbatch job and writes `built_images` entries at `:1224`/`:1239` with no `registry_image` and no digest). Manifests from that path have no `image_digest`, so `--require-pinned-image` runs against them fail fast with the Task 2 error. This is documented in Task 10, not implemented. + +--- + +## File Structure + +**Create:** + +| File | Responsibility | +|---|---| +| `src/madengine/core/image_digest.py` | Pure helpers: parse a digest out of `docker push` / `docker image inspect` output, build a `repo@sha256:...` reference, and apply the enforcement policy (return-as-is / pin / raise). No I/O, no Docker calls. | +| `tests/unit/test_image_digest.py` | Unit tests for all four functions in the module above. | + +**Modify:** + +| File | Change | +|---|---| +| `src/madengine/execution/docker_builder.py` | Capture the pushed digest in `push_image()`; copy it into `build_info["image_digest"]` at both push call sites. | +| `src/madengine/cli/commands/run.py` | Add the `--require-pinned-image` flag; pass it into both `create_args_namespace(...)` calls. | +| `src/madengine/orchestration/run_orchestrator.py` | Fold the flag into `additional_context`; persist it into `manifest["context"]`. | +| `src/madengine/execution/container_runner.py` | Resolve the pinned reference before the registry pull. | +| `src/madengine/deployment/k8s_template_context.py` | Resolve the pinned reference for the pod spec `image` field. | +| `src/madengine/deployment/slurm.py` | Resolve the pinned reference for `DOCKER_IMAGE_NAME` in the slurm_multi wrapper. | +| `docs/cli-reference.md`, `docs/configuration.md` | Document the flag and the context key. | + +**Test files touched:** `tests/unit/test_image_digest.py` (new), `tests/unit/test_docker_builder.py`, `tests/unit/test_orchestration.py`, `tests/unit/test_container_runner.py`, `tests/unit/test_k8s.py`, `tests/unit/test_slurm_multi.py`, `tests/unit/test_execution.py`. + +--- + +## Task 1: Digest parsing and pinned-reference construction + +**Files:** +- Create: `src/madengine/core/image_digest.py` +- Test: `tests/unit/test_image_digest.py` + +- [ ] **Step 1: Write the failing tests** + +Create `tests/unit/test_image_digest.py`: + +```python +"""Unit tests for madengine.core.image_digest. + +Covers digest extraction from `docker push` / `docker image inspect` output and +construction of pinned `repo@sha256:...` references. + +Copyright (c) Advanced Micro Devices, Inc. All rights reserved. +""" + +import pytest + +from madengine.core.image_digest import ( + build_pinned_reference, + parse_push_digest, + parse_repo_digest, +) + + +DIGEST = "sha256:" + "df36ef7e" * 8 # 64 hex chars +OTHER_DIGEST = "sha256:" + "cbb7e5ed" * 8 + + +class TestParsePushDigest: + """parse_push_digest extracts the digest line emitted by `docker push`.""" + + def test_typical_push_output(self): + output = ( + "The push refers to repository [docker.io/myorg/ci]\n" + "a1b2c3d4e5f6: Pushed\n" + "9f8e7d6c5b4a: Layer already exists\n" + f"mymodel: digest: {DIGEST} size: 4738\n" + ) + assert parse_push_digest(output) == DIGEST + + def test_no_space_after_colon(self): + assert parse_push_digest(f"mymodel: digest:{DIGEST} size: 12") == DIGEST + + def test_last_digest_wins_when_multiple(self): + output = ( + f"tag-a: digest: {OTHER_DIGEST} size: 10\n" + f"tag-b: digest: {DIGEST} size: 10\n" + ) + assert parse_push_digest(output) == DIGEST + + def test_uppercase_hex_is_not_matched(self): + assert parse_push_digest("mymodel: digest: sha256:ABC size: 1") is None + + def test_output_without_digest_line(self): + assert parse_push_digest("The push refers to repository [x]\nlayer: Pushed\n") is None + + def test_empty_output(self): + assert parse_push_digest("") is None + + def test_none_output(self): + assert parse_push_digest(None) is None + + +class TestParseRepoDigest: + """parse_repo_digest extracts the digest from a `repo@sha256:...` reference.""" + + def test_repo_digest_reference(self): + assert parse_repo_digest(f"myorg/ci@{DIGEST}") == DIGEST + + def test_registry_with_port(self): + assert parse_repo_digest(f"localhost:5000/myorg/ci@{DIGEST}") == DIGEST + + def test_surrounding_whitespace_and_quotes(self): + assert parse_repo_digest(f" 'myorg/ci@{DIGEST}' \n") == DIGEST + + def test_empty_repodigests_placeholder(self): + # `docker image inspect` prints this when RepoDigests is empty. + assert parse_repo_digest("") is None + + def test_none_output(self): + assert parse_repo_digest(None) is None + + +class TestBuildPinnedReference: + """build_pinned_reference produces repo@sha256:... with any tag stripped.""" + + def test_strips_tag(self): + assert build_pinned_reference(f"myorg/ci:mymodel", DIGEST) == f"myorg/ci@{DIGEST}" + + def test_no_tag(self): + assert build_pinned_reference("myorg/ci", DIGEST) == f"myorg/ci@{DIGEST}" + + def test_registry_port_is_not_mistaken_for_a_tag(self): + out = build_pinned_reference("localhost:5000/myorg/ci:latest", DIGEST) + assert out == f"localhost:5000/myorg/ci@{DIGEST}" + + def test_registry_port_without_tag(self): + out = build_pinned_reference("localhost:5000/myorg/ci", DIGEST) + assert out == f"localhost:5000/myorg/ci@{DIGEST}" + + def test_bare_name_with_tag(self): + assert build_pinned_reference("ci-dummy:latest", DIGEST) == f"ci-dummy@{DIGEST}" + + def test_existing_digest_is_replaced(self): + out = build_pinned_reference(f"myorg/ci@{OTHER_DIGEST}", DIGEST) + assert out == f"myorg/ci@{DIGEST}" +``` + +- [ ] **Step 2: Run tests to verify they fail** + +Run: `pytest tests/unit/test_image_digest.py -v` +Expected: FAIL — `ModuleNotFoundError: No module named 'madengine.core.image_digest'` + +- [ ] **Step 3: Write the implementation** + +Create `src/madengine/core/image_digest.py`: + +```python +#!/usr/bin/env python3 +""" +Registry image digest helpers for madengine. + +Build pushes record the digest of the image they push; runs can optionally be +pinned to that digest so a registry tag that moved between build and run fails +loudly instead of silently resolving to a different image. + +Copyright (c) Advanced Micro Devices, Inc. All rights reserved. +""" + +import re +import typing + + +# `docker push` prints e.g. "mytag: digest: sha256:<64 hex> size: 4738". +_PUSH_DIGEST_RE = re.compile(r"digest:\s*(sha256:[0-9a-f]{64})") + +# `docker image inspect --format '{{index .RepoDigests 0}}'` prints "repo@sha256:<64 hex>". +_REPO_DIGEST_RE = re.compile(r"@(sha256:[0-9a-f]{64})") + + +def parse_push_digest(push_output: typing.Optional[str]) -> typing.Optional[str]: + """Extract the pushed image digest from `docker push` output. + + Args: + push_output: Combined stdout/stderr of the push command. + + Returns: + The digest (``sha256:...``), or None if no digest line was present. + When several digest lines appear the last one is returned, which is the + digest of the reference the push command was invoked with. + """ + if not push_output: + return None + matches = _PUSH_DIGEST_RE.findall(push_output) + return matches[-1] if matches else None + + +def parse_repo_digest(inspect_output: typing.Optional[str]) -> typing.Optional[str]: + """Extract the digest from a ``repo@sha256:...`` reference. + + Args: + inspect_output: Output of + ``docker image inspect --format '{{index .RepoDigests 0}}' ``. + + Returns: + The digest (``sha256:...``), or None if the output held no digest + (e.g. ```` when the image has no RepoDigests entry). + """ + if not inspect_output: + return None + match = _REPO_DIGEST_RE.search(inspect_output) + return match.group(1) if match else None + + +def build_pinned_reference(registry_image: str, digest: str) -> str: + """Build a digest-pinned image reference. + + Any existing tag or digest on ``registry_image`` is dropped. A port in the + registry host (``localhost:5000/org/img``) is not mistaken for a tag because + only the final path segment is inspected for ``:``. + + Args: + registry_image: Image reference, with or without tag/digest. + digest: Digest to pin to (``sha256:...``). + + Returns: + A reference of the form ``repo@sha256:...``. + """ + repo = registry_image.split("@", 1)[0] + last_slash = repo.rfind("/") + tail = repo[last_slash + 1 :] + if ":" in tail: + repo = repo[: last_slash + 1] + tail.split(":", 1)[0] + return f"{repo}@{digest}" +``` + +- [ ] **Step 4: Run tests to verify they pass** + +Run: `pytest tests/unit/test_image_digest.py -v` +Expected: PASS (18 tests) + +- [ ] **Step 5: Commit** + +```bash +git add src/madengine/core/image_digest.py tests/unit/test_image_digest.py +git commit -m "feat(image-digest): add digest parsing and pinned reference helpers" +``` + +--- + +## Task 2: Enforcement policy resolver + +**Files:** +- Modify: `src/madengine/core/image_digest.py` +- Test: `tests/unit/test_image_digest.py` + +`resolve_pinned_image()` is the single place the "pin, pass through, or fail" decision is made. All three execution paths (local Docker, K8s, SLURM) call it so they cannot drift. + +- [ ] **Step 1: Write the failing tests** + +Append to `tests/unit/test_image_digest.py`: + +```python +class TestResolvePinnedImage: + """resolve_pinned_image applies the --require-pinned-image policy.""" + + def test_disabled_returns_image_unchanged_without_digest(self): + assert resolve_pinned_image("myorg/ci:mymodel", None, False) == "myorg/ci:mymodel" + + def test_disabled_returns_image_unchanged_even_with_digest(self): + # Default behaviour must not change for existing users: the digest rides + # along in the manifest but is never used unless enforcement is on. + assert resolve_pinned_image("myorg/ci:mymodel", DIGEST, False) == "myorg/ci:mymodel" + + def test_enabled_with_digest_returns_pinned_reference(self): + out = resolve_pinned_image("myorg/ci:mymodel", DIGEST, True) + assert out == f"myorg/ci@{DIGEST}" + + def test_enabled_without_digest_raises(self): + with pytest.raises(ConfigurationError): + resolve_pinned_image("myorg/ci:mymodel", None, True, model_name="my_model") + + def test_enabled_with_empty_digest_raises(self): + with pytest.raises(ConfigurationError): + resolve_pinned_image("myorg/ci:mymodel", "", True, model_name="my_model") + + def test_error_message_names_model_and_image(self): + with pytest.raises(ConfigurationError) as excinfo: + resolve_pinned_image("myorg/ci:mymodel", None, True, model_name="my_model") + message = str(excinfo.value) + assert "my_model" in message + assert "myorg/ci:mymodel" in message + + def test_error_carries_actionable_suggestions(self): + with pytest.raises(ConfigurationError) as excinfo: + resolve_pinned_image("myorg/ci:mymodel", None, True, model_name="my_model") + assert excinfo.value.suggestions +``` + +And extend the imports at the top of the file: + +```python +from madengine.core.errors import ConfigurationError +from madengine.core.image_digest import ( + build_pinned_reference, + parse_push_digest, + parse_repo_digest, + resolve_pinned_image, +) +``` + +- [ ] **Step 2: Run tests to verify they fail** + +Run: `pytest tests/unit/test_image_digest.py -k ResolvePinned -v` +Expected: FAIL — `ImportError: cannot import name 'resolve_pinned_image'` + +- [ ] **Step 3: Write the implementation** + +Add the import near the top of `src/madengine/core/image_digest.py`, below `import typing`: + +```python +from madengine.core.errors import ConfigurationError, create_error_context +``` + +Append to the same file: + +```python +def resolve_pinned_image( + registry_image: str, + image_digest: typing.Optional[str], + require_pinned: bool, + model_name: str = "", +) -> str: + """Resolve the image reference to use for a run. + + Args: + registry_image: Tagged registry reference from the build manifest. + image_digest: Digest recorded at build time, if any. + require_pinned: True when --require-pinned-image / require_pinned_image is set. + model_name: Model name, used only to make the error message actionable. + + Returns: + ``registry_image`` unchanged when enforcement is off, otherwise a + digest-pinned reference. + + Raises: + ConfigurationError: When enforcement is on but the manifest recorded no + digest. Falling back to the tag is deliberately not done: silent + degradation would defeat the guarantee the caller asked for. + """ + if not require_pinned: + return registry_image + + if not image_digest: + raise ConfigurationError( + f"--require-pinned-image is set but the build manifest records no " + f"image digest for model '{model_name}' (image: {registry_image}). " + f"Refusing to pull by tag.", + context=create_error_context( + operation="resolve_pinned_image", + component="image_digest", + additional_info={"model": model_name, "image": registry_image}, + ), + suggestions=[ + "Rebuild with this version of madengine so the push digest is recorded", + "Drop --require-pinned-image / require_pinned_image to pull by tag", + ], + ) + + return build_pinned_reference(registry_image, image_digest) +``` + +- [ ] **Step 4: Run tests to verify they pass** + +Run: `pytest tests/unit/test_image_digest.py -v` +Expected: PASS (25 tests) + +- [ ] **Step 5: Commit** + +```bash +git add src/madengine/core/image_digest.py tests/unit/test_image_digest.py +git commit -m "feat(image-digest): add resolve_pinned_image enforcement policy" +``` + +--- + +## Task 3: Capture the pushed digest in `DockerBuilder.push_image()` + +**Files:** +- Modify: `src/madengine/execution/docker_builder.py:51`, `:394-403` +- Test: `tests/unit/test_docker_builder.py` + +Digest capture is **always on** and never fails a build. If neither the push output nor `docker image inspect` yields a digest, we log a dim note and move on — the push already succeeded. + +- [ ] **Step 1: Write the failing tests** + +Append to `tests/unit/test_docker_builder.py`: + +```python +DIGEST = "sha256:" + "df36ef7e" * 8 + + +class TestPushImageRecordsDigest: + """push_image records the pushed image digest in builder.pushed_digests.""" + + def _builder(self, sh_side_effect): + ctx = MagicMock() + ctx.ctx = {} + console = MagicMock() + console.sh = MagicMock(side_effect=sh_side_effect) + builder = DockerBuilder(ctx, console) + builder.rich_console = MagicMock() + return builder + + def test_digest_parsed_from_push_output(self): + def sh(command, *args, **kwargs): + if "docker push" in command: + return f"mymodel: digest: {DIGEST} size: 4738" + return "" + + builder = self._builder(sh) + result = builder.push_image("ci-dummy", "localhost:5000", None, "localhost:5000/ci-dummy") + + assert result == "localhost:5000/ci-dummy" + assert builder.pushed_digests["localhost:5000/ci-dummy"] == DIGEST + + def test_falls_back_to_image_inspect_when_push_output_has_no_digest(self): + def sh(command, *args, **kwargs): + if "docker push" in command: + return "The push refers to repository [localhost:5000/ci-dummy]\nlayer: Pushed" + if "docker image inspect" in command: + return f"localhost:5000/ci-dummy@{DIGEST}" + return "" + + builder = self._builder(sh) + builder.push_image("ci-dummy", "localhost:5000", None, "localhost:5000/ci-dummy") + + assert builder.pushed_digests["localhost:5000/ci-dummy"] == DIGEST + inspect_calls = [ + c for c in builder.console.sh.call_args_list if "docker image inspect" in c.args[0] + ] + assert len(inspect_calls) == 1 + assert "RepoDigests" in inspect_calls[0].args[0] + + def test_no_digest_anywhere_leaves_entry_absent_and_push_still_succeeds(self): + def sh(command, *args, **kwargs): + if "docker push" in command: + return "layer: Pushed" + if "docker image inspect" in command: + return "" + return "" + + builder = self._builder(sh) + result = builder.push_image("ci-dummy", "localhost:5000", None, "localhost:5000/ci-dummy") + + assert result == "localhost:5000/ci-dummy" + assert "localhost:5000/ci-dummy" not in builder.pushed_digests + # The gap is noted at dim level, not as a user-facing warning. + printed = " ".join(str(c) for c in builder.rich_console.print.call_args_list) + assert "[dim]" in printed + assert "no pushed digest" in printed.lower() + + def test_inspect_failure_is_swallowed(self): + def sh(command, *args, **kwargs): + if "docker push" in command: + return "layer: Pushed" + if "docker image inspect" in command: + raise RuntimeError("no such image") + return "" + + builder = self._builder(sh) + result = builder.push_image("ci-dummy", "localhost:5000", None, "localhost:5000/ci-dummy") + + assert result == "localhost:5000/ci-dummy" + assert builder.pushed_digests == {} + + def test_no_registry_records_nothing(self): + builder = self._builder(lambda command, *a, **k: "") + result = builder.push_image("ci-dummy") + + assert result == "ci-dummy" + assert builder.pushed_digests == {} +``` + +- [ ] **Step 2: Run tests to verify they fail** + +Run: `pytest tests/unit/test_docker_builder.py -k PushImageRecordsDigest -v` +Expected: FAIL — `AttributeError: 'DockerBuilder' object has no attribute 'pushed_digests'` + +- [ ] **Step 3: Write the implementation** + +Add the import to `src/madengine/execution/docker_builder.py`, after the `from madengine.core.context import Context` line: + +```python +from madengine.core.image_digest import parse_push_digest, parse_repo_digest +``` + +In `DockerBuilder.__init__`, immediately after `self.built_images = {} # Track built images` (line 51), add: + +```python + self.pushed_digests = {} # registry_image -> digest recorded at push time +``` + +Replace the push block in `push_image()` (lines 394-399) — from `# Push the image` through `self.console.sh(push_command)` — with: + +```python + # Push the image + push_command = f"docker push {shlex.quote(registry_image)}" + self.rich_console.print(f"\n[bold blue]🚀 Starting docker push to registry...[/bold blue]") + print(f"📤 Registry: {registry}") + print(f"🏷️ Image: {registry_image}") + push_output = self.console.sh(push_command) + + self._record_pushed_digest(registry_image, push_output) +``` + +Add the new method immediately after `push_image()` (i.e. after its `raise` at line 408, before `def export_build_manifest`): + +```python + def _record_pushed_digest(self, registry_image: str, push_output: str) -> None: + """Record the digest of the image just pushed, for digest-pinned runs. + + Best-effort by design: the push has already succeeded by the time this + runs, so a missing digest is a manifest-completeness gap (noted at dim + level), never a build failure. Runs only consult the recorded digest + when --require-pinned-image is set. + + Args: + registry_image: The reference that was pushed. + push_output: stdout/stderr captured from the push command. + """ + digest = parse_push_digest(push_output) + + if not digest: + # Some registries/mirrors do not print the digest line; ask the + # daemon for the RepoDigests entry it recorded for this push. + try: + inspect_output = self.console.sh( + "docker image inspect --format '{{index .RepoDigests 0}}' " + + shlex.quote(registry_image) + ) + digest = parse_repo_digest(inspect_output) + except Exception: + digest = None + + if not digest: + self.rich_console.print( + f"[dim]No pushed digest recorded for {registry_image}; " + f"--require-pinned-image runs will reject this manifest entry[/dim]" + ) + return + + self.pushed_digests[registry_image] = digest + self.rich_console.print(f"[dim]Pushed digest: {registry_image} -> {digest}[/dim]") +``` + +- [ ] **Step 4: Run tests to verify they pass** + +Run: `pytest tests/unit/test_docker_builder.py -v` +Expected: PASS (all, including the 2 pre-existing naming tests) + +- [ ] **Step 5: Verify no existing push tests regressed** + +Run: `pytest tests/integration/test_docker_integration.py -k push_image -v` +Expected: PASS (6 tests). These mock `Console.sh` with `return_value="Success"`, so the digest parse returns None, the inspect fallback also returns `"Success"` (no digest), and `push_image` still returns the tag unchanged. + +- [ ] **Step 6: Commit** + +```bash +git add src/madengine/execution/docker_builder.py tests/unit/test_docker_builder.py +git commit -m "feat(build): capture pushed image digest during docker push" +``` + +--- + +## Task 4: Write `image_digest` into `build_info` at both push sites + +**Files:** +- Modify: `src/madengine/execution/docker_builder.py:762-772`, `:971-980` +- Test: `tests/unit/test_docker_builder.py` + +`build_info` is serialized wholesale into `build_manifest.json`, so setting the key here is all that is needed to get it into the manifest. + +- [ ] **Step 1: Write the failing tests** + +Append to `tests/unit/test_docker_builder.py`: + +```python +class TestBuildInfoCarriesImageDigest: + """Both push call sites copy the recorded digest into build_info.""" + + def _builder(self): + ctx = MagicMock() + ctx.ctx = {} + builder = DockerBuilder(ctx, MagicMock()) + builder.rich_console = MagicMock() + return builder + + def _run_single_arch(self, builder): + """Drive _build_model_single_arch with everything below push_image stubbed.""" + return builder._build_model_single_arch( + model_info={"name": "dummy", "dockerfile": "docker/dummy"}, + credentials={}, + clean_cache=False, + registry="localhost:5000", + phase_suffix="", + batch_build_metadata=None, + ) + + def test_single_arch_push_sets_image_digest(self): + builder = self._builder() + + def fake_push(docker_image, registry, credentials, explicit_registry_image): + builder.pushed_digests[explicit_registry_image] = DIGEST + return explicit_registry_image + + with patch.object( + builder, "_get_dockerfiles_for_model", return_value=["docker/dummy.ubuntu"] + ), patch.object( + builder, "build_image", return_value={"docker_image": "ci-dummy", "model": "dummy"} + ), patch.object( + builder, "_get_effective_gpu_architecture", return_value="" + ), patch.object( + builder, "_create_registry_image_name", return_value="localhost:5000/ci-dummy" + ), patch.object( + builder, "push_image", side_effect=fake_push + ): + results = self._run_single_arch(builder) + + assert results[0]["registry_image"] == "localhost:5000/ci-dummy" + assert results[0]["image_digest"] == DIGEST + # A push failure must still be recorded the way it is today. + assert "push_error" not in results[0] + + def test_single_arch_push_without_digest_omits_key(self): + builder = self._builder() + + with patch.object( + builder, "_get_dockerfiles_for_model", return_value=["docker/dummy.ubuntu"] + ), patch.object( + builder, "build_image", return_value={"docker_image": "ci-dummy", "model": "dummy"} + ), patch.object( + builder, "_get_effective_gpu_architecture", return_value="" + ), patch.object( + builder, "_create_registry_image_name", return_value="localhost:5000/ci-dummy" + ), patch.object( + builder, "push_image", return_value="localhost:5000/ci-dummy" + ): + results = self._run_single_arch(builder) + + assert results[0]["registry_image"] == "localhost:5000/ci-dummy" + assert "image_digest" not in results[0] +``` + +Extend the import at the top of `tests/unit/test_docker_builder.py`: + +```python +from unittest.mock import MagicMock, patch +``` + +> `_build_model_single_arch(self, model_info, credentials, clean_cache, registry, phase_suffix, batch_build_metadata)` is at `docker_builder.py:729`; the per-arch sibling is `_build_model_for_arch`, which takes an extra `arch` argument. Both are reached from `build_all_models` (`docker_builder.py:581`, `:617`). + +- [ ] **Step 2: Run tests to verify they fail** + +Run: `pytest tests/unit/test_docker_builder.py -k BuildInfoCarriesImageDigest -v` +Expected: FAIL — `KeyError: 'image_digest'` + +- [ ] **Step 3: Write the implementation** + +At the single-arch site (`docker_builder.py:769-770`), replace: + +```python + self.push_image(build_info["docker_image"], registry, credentials, registry_image) + build_info["registry_image"] = registry_image +``` + +with: + +```python + self.push_image(build_info["docker_image"], registry, credentials, registry_image) + build_info["registry_image"] = registry_image + # Recorded at push time; consumed only by --require-pinned-image runs. + pushed_digest = self.pushed_digests.get(registry_image) + if pushed_digest: + build_info["image_digest"] = pushed_digest +``` + +At the per-arch site (`docker_builder.py:977-978`), replace: + +```python + self.push_image(arch_image_name, registry, credentials, registry_image) + build_info["registry_image"] = registry_image +``` + +with: + +```python + self.push_image(arch_image_name, registry, credentials, registry_image) + build_info["registry_image"] = registry_image + # Recorded at push time; consumed only by --require-pinned-image runs. + pushed_digest = self.pushed_digests.get(registry_image) + if pushed_digest: + build_info["image_digest"] = pushed_digest +``` + +- [ ] **Step 4: Run tests to verify they pass** + +Run: `pytest tests/unit/test_docker_builder.py -v` +Expected: PASS + +- [ ] **Step 5: Confirm the manifest structure is unchanged for existing consumers** + +Run: `pytest tests/unit/test_orchestration.py tests/unit/test_slurm_multi.py -v` +Expected: PASS — `image_digest` is purely additive. + +- [ ] **Step 6: Commit** + +```bash +git add src/madengine/execution/docker_builder.py tests/unit/test_docker_builder.py +git commit -m "feat(build): record image_digest in build manifest entries" +``` + +--- + +## Task 5: `--require-pinned-image` flag and context propagation + +**Files:** +- Modify: `src/madengine/cli/commands/run.py:162-168` (flag), `:230-247` and `:318-341` (both `create_args_namespace` calls) +- Modify: `src/madengine/orchestration/run_orchestrator.py:85` (context merge), `:502` (manifest merge keys) +- Test: `tests/unit/test_orchestration.py` + +Two entry points must both work: the CLI flag, and the `require_pinned_image` key inside `--additional-context` (which is how CI pipelines drive madengine). Persisting the key into `manifest["context"]` is what carries the setting to the nested `madengine run` that the SLURM job script executes on each compute node. + +- [ ] **Step 1: Write the failing tests** + +Append to `tests/unit/test_orchestration.py`: + +```python +class TestRequirePinnedImageContext: + """--require-pinned-image and require_pinned_image both reach additional_context.""" + + @patch("madengine.orchestration.run_orchestrator.Context") + def test_cli_flag_sets_context_key(self, mock_context): + args = create_args_namespace( + additional_context=None, + require_pinned_image=True, + live_output=False, + ) + orch = RunOrchestrator(args) + assert orch.additional_context["require_pinned_image"] is True + + @patch("madengine.orchestration.run_orchestrator.Context") + def test_flag_absent_leaves_key_unset(self, mock_context): + args = create_args_namespace( + additional_context=None, + require_pinned_image=False, + live_output=False, + ) + orch = RunOrchestrator(args) + assert "require_pinned_image" not in orch.additional_context + + @patch("madengine.orchestration.run_orchestrator.Context") + def test_additional_context_key_alone_is_honoured(self, mock_context): + args = create_args_namespace( + additional_context="{'require_pinned_image': True}", + live_output=False, + ) + orch = RunOrchestrator(args) + assert orch.additional_context["require_pinned_image"] is True + + @patch("madengine.orchestration.run_orchestrator.Context") + def test_key_is_persisted_into_manifest_context(self, mock_context, tmp_path): + manifest_path = tmp_path / "build_manifest.json" + manifest_path.write_text(json.dumps({ + "built_images": {"img1": {"registry_image": "myorg/ci:m"}}, + "built_models": {"img1": {"name": "m"}}, + "context": {}, + "deployment_config": {}, + })) + + args = create_args_namespace( + additional_context=None, + require_pinned_image=True, + live_output=False, + ) + orch = RunOrchestrator(args) + orch._load_and_merge_manifest(str(manifest_path)) + + written = json.loads(manifest_path.read_text()) + assert written["context"]["require_pinned_image"] is True +``` + +Make sure `json`, `patch`, `create_args_namespace` and `RunOrchestrator` are imported at the top of `tests/unit/test_orchestration.py` — the existing `TestRunOrchestratorInit` and `TestCreateManifestFromLocalImage` classes already import most of these; add only what is missing: + +```python +from madengine.cli.utils import create_args_namespace +``` + +- [ ] **Step 2: Run tests to verify they fail** + +Run: `pytest tests/unit/test_orchestration.py -k RequirePinnedImage -v` +Expected: FAIL — `KeyError: 'require_pinned_image'` + +- [ ] **Step 3: Write the implementation — orchestrator** + +In `src/madengine/orchestration/run_orchestrator.py`, immediately after `self.additional_context = merged_context` (line 85), add: + +```python + # The CLI flag and the require_pinned_image context key are equivalent; + # the key lets CI pipelines that drive madengine through + # --additional-context opt in the same way as for k8s/slurm/tools. + if getattr(args, "require_pinned_image", False): + self.additional_context["require_pinned_image"] = True +``` + +In `_load_and_merge_manifest`, extend the merge key list (line 502) from: + +```python + merge_keys = ["tools", "pre_scripts", "post_scripts", "encapsulate_script"] +``` + +to: + +```python + merge_keys = [ + "tools", + "pre_scripts", + "post_scripts", + "encapsulate_script", + # Persisted so nested runs on SLURM compute nodes (which re-enter + # `madengine run --manifest-file`) inherit the enforcement setting. + "require_pinned_image", + ] +``` + +- [ ] **Step 4: Write the implementation — CLI** + +In `src/madengine/cli/commands/run.py`, add a new option immediately after the `skip_model_run` option block (which ends at line 108 with `] = False,`): + +```python + require_pinned_image: Annotated[ + bool, + typer.Option( + "--require-pinned-image", + help=( + "Pull registry images by the digest recorded in the build manifest " + "instead of by tag. Fails immediately if the manifest has no digest " + "for an image. Equivalent to the 'require_pinned_image' " + "additional-context key." + ), + ), + ] = False, +``` + +Then add `require_pinned_image=require_pinned_image,` to **both** `create_args_namespace(...)` calls — in the manifest-exists branch (next to `skip_model_run=skip_model_run,` around line 245) and in the full-workflow branch (next to `skip_model_run=skip_model_run,` around line 339). + +- [ ] **Step 5: Run tests to verify they pass** + +Run: `pytest tests/unit/test_orchestration.py -v` +Expected: PASS + +- [ ] **Step 6: Verify the flag is wired into the CLI** + +Run: `madengine run --help | grep -A 3 require-pinned-image` +Expected: the option and its help text are listed. + +- [ ] **Step 7: Commit** + +```bash +git add src/madengine/cli/commands/run.py src/madengine/orchestration/run_orchestrator.py tests/unit/test_orchestration.py +git commit -m "feat(run): add --require-pinned-image flag and context propagation" +``` + +--- + +## Task 6: Enforce pinned pulls in local Docker execution + +**Files:** +- Modify: `src/madengine/execution/container_runner.py:2851-2859` +- Test: `tests/unit/test_container_runner.py` + +- [ ] **Step 1: Write the failing tests** + +Append to `tests/unit/test_container_runner.py`: + +```python +DIGEST = "sha256:" + "df36ef7e" * 8 + + +class TestRequirePinnedImageLocalRun: + """run_models_from_manifest honours require_pinned_image for registry pulls.""" + + def _manifest(self, tmpdir, build_info): + manifest_path = os.path.join(tmpdir, "build_manifest.json") + with open(manifest_path, "w") as f: + json.dump( + { + "built_images": {"img1": build_info}, + "built_models": { + "img1": {"name": "m", "tags": "t", "n_gpus": "1", "args": ""} + }, + }, + f, + ) + return manifest_path + + def _runner(self): + ctx = MagicMock() + ctx.ctx = {"docker_env_vars": {"MAD_SYSTEM_GPU_ARCHITECTURE": "gfx90a"}} + ctx.ensure_runtime_context = MagicMock() + console = MagicMock() + console.sh.return_value = "testhost" + runner = ContainerRunner(context=ctx, console=console) + runner.set_credentials({}) + return runner + + @patch("madengine.execution.container_runner.update_perf_csv") + def test_default_pulls_by_tag_even_when_digest_present(self, _mock_csv): + with tempfile.TemporaryDirectory() as tmpdir: + manifest_path = self._manifest( + tmpdir, + {"registry_image": "myorg/ci:m", "image_digest": DIGEST}, + ) + runner = self._runner() + runner.perf_csv_path = os.path.join(tmpdir, "perf.csv") + + with patch.object(runner, "pull_image") as mock_pull, patch.object( + runner, "run_container", return_value={"status": "SUCCESS"} + ): + runner.run_models_from_manifest(manifest_file=manifest_path, timeout=60) + + mock_pull.assert_called_once_with("myorg/ci:m") + + @patch("madengine.execution.container_runner.update_perf_csv") + def test_enabled_pulls_pinned_reference(self, _mock_csv): + with tempfile.TemporaryDirectory() as tmpdir: + manifest_path = self._manifest( + tmpdir, + {"registry_image": "myorg/ci:m", "image_digest": DIGEST}, + ) + runner = self._runner() + runner.perf_csv_path = os.path.join(tmpdir, "perf.csv") + runner.additional_context = {"require_pinned_image": True} + + with patch.object(runner, "pull_image") as mock_pull, patch.object( + runner, "run_container", return_value={"status": "SUCCESS"} + ) as mock_run: + runner.run_models_from_manifest(manifest_file=manifest_path, timeout=60) + + mock_pull.assert_called_once_with(f"myorg/ci@{DIGEST}") + # The container must run the same pinned reference that was pulled. + assert mock_run.call_args[1]["docker_image"] == f"myorg/ci@{DIGEST}" + + @patch("madengine.execution.container_runner.update_perf_csv") + def test_enabled_without_digest_fails_before_pulling(self, _mock_csv): + with tempfile.TemporaryDirectory() as tmpdir: + manifest_path = self._manifest(tmpdir, {"registry_image": "myorg/ci:m"}) + runner = self._runner() + runner.perf_csv_path = os.path.join(tmpdir, "perf.csv") + runner.additional_context = {"require_pinned_image": True} + + with patch.object(runner, "pull_image") as mock_pull, patch.object( + runner, "run_container" + ) as mock_run: + result = runner.run_models_from_manifest( + manifest_file=manifest_path, timeout=60 + ) + + mock_pull.assert_not_called() + mock_run.assert_not_called() + assert len(result["failed_runs"]) == 1 + assert "require-pinned-image" in result["failed_runs"][0]["error"] + + @patch("madengine.execution.container_runner.update_perf_csv") + def test_manifest_context_key_enables_enforcement(self, _mock_csv): + """A nested run on a SLURM compute node inherits the setting via manifest context.""" + with tempfile.TemporaryDirectory() as tmpdir: + manifest_path = os.path.join(tmpdir, "build_manifest.json") + with open(manifest_path, "w") as f: + json.dump( + { + "built_images": { + "img1": {"registry_image": "myorg/ci:m", "image_digest": DIGEST} + }, + "built_models": { + "img1": {"name": "m", "tags": "t", "n_gpus": "1", "args": ""} + }, + "context": {"require_pinned_image": True}, + }, + f, + ) + runner = self._runner() + runner.perf_csv_path = os.path.join(tmpdir, "perf.csv") + + with patch.object(runner, "pull_image") as mock_pull, patch.object( + runner, "run_container", return_value={"status": "SUCCESS"} + ): + runner.run_models_from_manifest(manifest_file=manifest_path, timeout=60) + + mock_pull.assert_called_once_with(f"myorg/ci@{DIGEST}") +``` + +- [ ] **Step 2: Run tests to verify they fail** + +Run: `pytest tests/unit/test_container_runner.py -k RequirePinnedImageLocalRun -v` +Expected: FAIL — `test_enabled_pulls_pinned_reference` asserts the pinned ref but the tag is pulled. + +- [ ] **Step 3: Write the implementation** + +Add the import to `src/madengine/execution/container_runner.py`, after `from madengine.core.docker import Docker`: + +```python +from madengine.core.image_digest import resolve_pinned_image +``` + +Replace the registry branch at `container_runner.py:2851-2859`: + +```python + elif build_info.get("registry_image"): + # Registry image: Pull from registry + try: + self.pull_image(build_info["registry_image"]) + # Update docker_image to use registry image + run_image = build_info["registry_image"] + except Exception as pull_error: + self.rich_console.print(f"[yellow]Warning: Could not pull from registry, using local image[/yellow]") + run_image = image_name +``` + +with: + +```python + elif build_info.get("registry_image"): + # Registry image: Pull from registry. Under + # require_pinned_image this resolves to repo@sha256:... and + # raises (outside the pull try/except, so there is no tag + # fallback) when the manifest recorded no digest. + pull_target = resolve_pinned_image( + build_info["registry_image"], + build_info.get("image_digest"), + bool((self.additional_context or {}).get("require_pinned_image")), + model_name=model_info.get("name", ""), + ) + try: + self.pull_image(pull_target) + # Update docker_image to use registry image + run_image = pull_target + except Exception as pull_error: + self.rich_console.print(f"[yellow]Warning: Could not pull from registry, using local image[/yellow]") + run_image = image_name +``` + +- [ ] **Step 4: Run tests to verify they pass** + +Run: `pytest tests/unit/test_container_runner.py -v` +Expected: PASS + +- [ ] **Step 5: Commit** + +```bash +git add src/madengine/execution/container_runner.py tests/unit/test_container_runner.py +git commit -m "feat(run): pin local docker pulls to manifest digest when required" +``` + +--- + +## Task 7: Enforce pinned images in the Kubernetes pod spec + +**Files:** +- Modify: `src/madengine/deployment/k8s_template_context.py:518` +- Test: `tests/unit/test_k8s.py` + +- [ ] **Step 1: Write the failing tests** + +Append to `tests/unit/test_k8s.py`: + +```python +class TestK8sRequirePinnedImage: + """The generated pod spec image field honours require_pinned_image.""" + + DIGEST = "sha256:" + "df36ef7e" * 8 + + def _template_context(self, tmp_path, monkeypatch, require_pinned, image_digest): + """Build a real template context, the way prepare() does. + + _prepare_template_context reads the manifest and the model's scripts + directory from the current working directory, so the test runs inside + tmp_path with a minimal model tree. + """ + monkeypatch.chdir(tmp_path) + (tmp_path / "scripts" / "dummy").mkdir(parents=True) + (tmp_path / "scripts" / "dummy" / "run.sh").write_text("#!/bin/bash\necho hi\n") + + image_info = {"registry_image": "myorg/ci:m"} + if image_digest: + image_info["image_digest"] = image_digest + model_info = { + "name": "m", + "tags": ["t"], + "n_gpus": "1", + "args": "", + "scripts": "scripts/dummy/run.sh", + "dockerfile": "docker/dummy", + } + manifest = { + "built_images": {"img1": image_info}, + "built_models": {"img1": model_info}, + "context": {}, + } + (tmp_path / "build_manifest.json").write_text(json.dumps(manifest)) + + additional_context = { + "k8s": {"namespace": "default"}, + "gpu_vendor": "AMD", + "guest_os": "UBUNTU", + } + if require_pinned: + additional_context["require_pinned_image"] = True + + cfg = DeploymentConfig( + target="k8s", + manifest_file="build_manifest.json", + additional_context=additional_context, + ) + deployment = KubernetesDeployment(cfg) + return deployment._prepare_template_context(model_info, image_info) + + def test_default_uses_tag(self, tmp_path, monkeypatch): + ctx = self._template_context( + tmp_path, monkeypatch, require_pinned=False, image_digest=self.DIGEST + ) + assert ctx["image"] == "myorg/ci:m" + + def test_enabled_uses_pinned_reference(self, tmp_path, monkeypatch): + ctx = self._template_context( + tmp_path, monkeypatch, require_pinned=True, image_digest=self.DIGEST + ) + assert ctx["image"] == f"myorg/ci@{self.DIGEST}" + + def test_enabled_without_digest_raises(self, tmp_path, monkeypatch): + with pytest.raises(ConfigurationError): + self._template_context( + tmp_path, monkeypatch, require_pinned=True, image_digest=None + ) +``` + +Add whatever of these imports `tests/unit/test_k8s.py` is missing at the top (`pytest` is already there): + +```python +import json + +from madengine.core.errors import ConfigurationError +from madengine.deployment.base import DeploymentConfig +from madengine.deployment.kubernetes import KubernetesDeployment +``` + +- [ ] **Step 2: Run tests to verify they fail** + +Run: `pytest tests/unit/test_k8s.py -k K8sRequirePinnedImage -v` +Expected: FAIL — `test_template_context_wires_the_resolver` fails; the resolver-behaviour tests pass already (they exercise Task 2 code directly, which is intentional: they document the K8s-facing contract). + +- [ ] **Step 3: Write the implementation** + +Add the import to `src/madengine/deployment/k8s_template_context.py`, next to the existing `from madengine.core.errors import ConfigurationError` (line 33): + +```python +from madengine.core.image_digest import resolve_pinned_image +``` + +In `_prepare_template_context`, immediately before the `return {` statement that begins the context dict, add: + +```python + # Under require_pinned_image the pod pulls repo@sha256:... so a moved tag + # surfaces as an ImagePullBackOff rather than a silent wrong-image run. + resolved_image = resolve_pinned_image( + image_info["registry_image"], + image_info.get("image_digest"), + bool(additional_context.get("require_pinned_image")), + model_name=model_name, + ) +``` + +Then change the image entry (line 518) from: + +```python + "image": image_info["registry_image"], +``` + +to: + +```python + "image": resolved_image, +``` + +> `additional_context` and `model_name` are both already local variables in this method (`additional_context = self.config.additional_context.copy()` and `model_name = model_info["name"]` near the top). + +- [ ] **Step 4: Run tests to verify they pass** + +Run: `pytest tests/unit/test_k8s.py -v` +Expected: PASS + +- [ ] **Step 5: Commit** + +```bash +git add src/madengine/deployment/k8s_template_context.py tests/unit/test_k8s.py +git commit -m "feat(k8s): pin pod image to manifest digest when required" +``` + +--- + +## Task 8: Enforce pinned images in the SLURM slurm_multi wrapper + +**Files:** +- Modify: `src/madengine/deployment/slurm.py:440-457` +- Test: `tests/unit/test_slurm_multi.py` + +The standard SLURM template path needs no change — it re-enters `madengine run --manifest-file` on each compute node, which goes through Task 6's local-Docker enforcement using the `require_pinned_image` key that Task 5 persisted into `manifest["context"]`. Only the self-managed `slurm_multi` wrapper, which bypasses that nested run, needs its own resolution. + +Setting `DOCKER_IMAGE_NAME` to the pinned reference pins both the parallel `srun docker pull` (which interpolates this variable) and the `docker run` inside the model's own script. See "Deliberate deviations" above. + +- [ ] **Step 1: Write the failing tests** + +Append to `tests/unit/test_slurm_multi.py`: + +```python +DIGEST = "sha256:" + "df36ef7e" * 8 + + +class TestSlurmMultiRequirePinnedImage: + """slurm_multi wrapper pins DOCKER_IMAGE_NAME (and thus the pull) when required.""" + + IMAGE_KEY = "rocm/pytorch-private:sglang_disagg_mori_20260502" + + def _deployment(self, tmp_path, require_pinned, image_digest): + script_rel = PR186_MODEL_ENTRY["scripts"] + script_abs = tmp_path / script_rel + script_abs.parent.mkdir(parents=True, exist_ok=True) + script_abs.write_text("#!/bin/bash\n# placeholder\n") + + image_entry = { + "image_name": self.IMAGE_KEY, + "docker_image": self.IMAGE_KEY, + "registry_image": self.IMAGE_KEY, + } + if image_digest: + image_entry["image_digest"] = image_digest + + manifest = { + "built_images": {self.IMAGE_KEY: image_entry}, + "built_models": {self.IMAGE_KEY: PR186_MODEL_ENTRY}, + "context": { + "docker_env_vars": {}, + "docker_mounts": {}, + "docker_build_arg": {}, + "gpu_vendor": "AMD", + "guest_os": "UBUNTU", + "docker_gpus": "all", + }, + } + manifest_path = tmp_path / "build_manifest.json" + manifest_path.write_text(json.dumps(manifest)) + + additional_context = { + "deploy": "slurm", + "gpu_vendor": "AMD", + "guest_os": "UBUNTU", + "slurm": dict( + PR186_MODEL_ENTRY["slurm"], output_dir=str(tmp_path / "slurm_results") + ), + "distributed": PR186_MODEL_ENTRY["distributed"], + } + if require_pinned: + additional_context["require_pinned_image"] = True + + cfg = DeploymentConfig( + target="slurm", + manifest_file=str(manifest_path), + additional_context=additional_context, + ) + return SlurmDeployment(cfg) + + def test_default_exports_tag(self, tmp_path): + dep = self._deployment(tmp_path, require_pinned=False, image_digest=DIGEST) + assert dep.prepare() is True + script_text = Path(dep.script_path).read_text() + assert f"export DOCKER_IMAGE_NAME={shlex.quote(self.IMAGE_KEY)}" in script_text + assert DIGEST not in script_text + + def test_enabled_exports_pinned_reference(self, tmp_path): + dep = self._deployment(tmp_path, require_pinned=True, image_digest=DIGEST) + assert dep.prepare() is True + script_text = Path(dep.script_path).read_text() + + pinned = f"rocm/pytorch-private@{DIGEST}" + assert f"export DOCKER_IMAGE_NAME={shlex.quote(pinned)}" in script_text + # The parallel pull interpolates the same value, so it is pinned too. + assert f"docker pull {pinned}" in script_text + + def test_enabled_without_digest_does_not_silently_fall_through(self, tmp_path): + """A missing digest must abort, not quietly take the standard template path. + + prepare()'s launcher peek wraps the slurm_multi dispatch in a bare + `except Exception: pass`. Without the re-raise added in Step 3b, a + ConfigurationError here would be swallowed and prepare() would generate + an ordinary (unpinned) sbatch script instead — the exact silent + degradation the flag exists to prevent. + """ + dep = self._deployment(tmp_path, require_pinned=True, image_digest=None) + with pytest.raises(ConfigurationError): + dep.prepare() +``` + +Add whatever of these imports `tests/unit/test_slurm_multi.py` is missing at the top: + +```python +import pytest + +from madengine.core.errors import ConfigurationError +``` + +- [ ] **Step 2: Run tests to verify they fail** + +Run: `pytest tests/unit/test_slurm_multi.py -k RequirePinnedImage -v` +Expected: FAIL — `test_enabled_exports_pinned_reference` finds the tag, not the pinned reference. + +- [ ] **Step 3: Write the implementation** + +Add the import to `src/madengine/deployment/slurm.py`, next to the other `madengine.*` imports (after `from madengine.utils.gpu_config import resolve_runtime_gpus`): + +```python +from madengine.core.image_digest import resolve_pinned_image +``` + +In `_prepare_slurm_multi_script`, replace the `DOCKER_IMAGE_NAME` resolution block (lines 440-457): + +```python + # Override DOCKER_IMAGE_NAME with the built image from manifest + # This ensures the run uses the freshly built image, not the base image + # Priority: docker_image_name param > model_info.docker_image > env_vars.DOCKER_IMAGE_NAME + if docker_image_name and docker_image_name.startswith("ci-"): + # The manifest key IS the built image name for madengine-built images + self.console.print(f"[cyan]Using built Docker image: {docker_image_name}[/cyan]") + env_vars["DOCKER_IMAGE_NAME"] = docker_image_name + elif "docker_image" in model_info: + built_image = model_info["docker_image"] + self.console.print(f"[cyan]Using Docker image: {built_image}[/cyan]") + env_vars["DOCKER_IMAGE_NAME"] = built_image + elif "image" in model_info: + # Fallback to 'image' field + built_image = model_info["image"] + self.console.print(f"[cyan]Using Docker image: {built_image}[/cyan]") + env_vars["DOCKER_IMAGE_NAME"] = built_image +``` + +with: + +```python + # Override DOCKER_IMAGE_NAME with the built image from manifest + # This ensures the run uses the freshly built image, not the base image + # Priority: docker_image_name param > model_info.docker_image > env_vars.DOCKER_IMAGE_NAME + if docker_image_name and docker_image_name.startswith("ci-"): + # The manifest key IS the built image name for madengine-built images + self.console.print(f"[cyan]Using built Docker image: {docker_image_name}[/cyan]") + env_vars["DOCKER_IMAGE_NAME"] = docker_image_name + elif "docker_image" in model_info: + built_image = model_info["docker_image"] + self.console.print(f"[cyan]Using Docker image: {built_image}[/cyan]") + env_vars["DOCKER_IMAGE_NAME"] = built_image + elif "image" in model_info: + # Fallback to 'image' field + built_image = model_info["image"] + self.console.print(f"[cyan]Using Docker image: {built_image}[/cyan]") + env_vars["DOCKER_IMAGE_NAME"] = built_image + + # Under require_pinned_image, pin DOCKER_IMAGE_NAME to the digest recorded + # at build time. slurm_multi runs the model's own script (no nested + # `madengine run` on the compute nodes), so enforcement has to happen here. + # Pinning the variable covers both the parallel `srun docker pull` below, + # which interpolates it, and the `docker run` inside the model script. + require_pinned = bool( + self.config.additional_context.get("require_pinned_image") + ) + if require_pinned and env_vars.get("DOCKER_IMAGE_NAME"): + image_entry = (self.manifest.get("built_images") or {}).get( + docker_image_name, {} + ) + env_vars["DOCKER_IMAGE_NAME"] = resolve_pinned_image( + env_vars["DOCKER_IMAGE_NAME"], + image_entry.get("image_digest"), + True, + model_name=model_info.get("name", ""), + ) + self.console.print( + f"[cyan]Pinned Docker image: {env_vars['DOCKER_IMAGE_NAME']}[/cyan]" + ) +``` + +- [ ] **Step 3b: Stop `prepare()` from swallowing the enforcement error** + +`prepare()` (`slurm.py:315-341`) wraps the whole slurm_multi dispatch — including the `_prepare_slurm_multi_script` call itself — in `except Exception: pass`, then falls through to the standard template path. Left as-is, a `ConfigurationError` from Step 3 would be silently discarded and an ordinary *unpinned* sbatch script would be generated instead. + +Narrow the handler so enforcement errors propagate. Replace the `except` clause at `slurm.py:339-341`: + +```python + except Exception: + # Fall through to develop's standard flow on any peek error + pass +``` + +with: + +```python + except ConfigurationError: + # Enforcement failures (e.g. --require-pinned-image with no recorded + # digest) are deliberate aborts, not peek errors. Falling through to + # the standard path here would silently generate an unpinned script. + raise + except Exception: + # Fall through to develop's standard flow on any peek error + pass +``` + +Add the import alongside the other `madengine.*` imports in `slurm.py`: + +```python +from madengine.core.errors import ConfigurationError +``` + +- [ ] **Step 4: Run tests to verify they pass** + +Run: `pytest tests/unit/test_slurm_multi.py -v` +Expected: PASS + +- [ ] **Step 5: Commit** + +```bash +git add src/madengine/deployment/slurm.py tests/unit/test_slurm_multi.py +git commit -m "feat(slurm): pin slurm_multi image to manifest digest when required" +``` + +--- + +## Task 9: Lock in log-filename compatibility for pinned references + +**Files:** +- Test: `tests/unit/test_execution.py` + +`_docker_image_ref_for_log_naming()` already strips `@sha256:...`; this test prevents a future refactor from breaking it and silently changing log/tar filenames when pinning is on. + +- [ ] **Step 1: Write the test** + +Append to the existing `_docker_image_ref_for_log_naming` test class in `tests/unit/test_execution.py`: + +```python + def test_pinned_reference_names_same_as_untagged_reference(self): + digest = "sha256:" + "df36ef7e" * 8 + assert ( + _docker_image_ref_for_log_naming(f"registry/ns/myimg@{digest}") + == _docker_image_ref_for_log_naming("registry/ns/myimg") + ) + + def test_pinned_ci_reference_still_yields_tag(self): + digest = "sha256:" + "df36ef7e" * 8 + assert ( + _docker_image_ref_for_log_naming(f"rocm/ns/img:ci-m_model_df@{digest}") + == "ci-m_model_df" + ) +``` + +- [ ] **Step 2: Run the test** + +Run: `pytest tests/unit/test_execution.py -k log_naming -v` +Expected: PASS immediately — this is a characterization test of behaviour that already exists. If either assertion fails, stop and report it; that would mean pinned references change log filenames and the spec's compatibility claim is wrong. + +- [ ] **Step 3: Commit** + +```bash +git add tests/unit/test_execution.py +git commit -m "test(execution): cover log naming for digest-pinned image references" +``` + +--- + +## Task 10: Documentation and full-suite verification + +**Files:** +- Modify: `docs/cli-reference.md:232` (run options table) +- Modify: `docs/configuration.md` + +- [ ] **Step 1: Add the CLI reference row** + +In `docs/cli-reference.md`, insert a new row into the `run` options table immediately after the `--skip-model-run` row (line 232): + +```markdown +| `--require-pinned-image` | | FLAG | `False` | Pull registry images by the `sha256` digest recorded in the build manifest (`repo@sha256:...`) instead of by tag, so a tag that moved between build and run fails loudly instead of silently running a different image. Fails immediately — with no tag fallback — if the manifest has no digest for an image. Equivalent to the `require_pinned_image` additional-context key. See [Configuration — Pinned image digests](configuration.md#pinned-image-digests). | +``` + +- [ ] **Step 2: Add the configuration section** + +In `docs/configuration.md`, add a new section after the "Run phase: log error pattern scan" section (which ends before "## System environment collection (rocEnvTool)"): + +```markdown +## Pinned image digests + +Every build records the digest of the image it pushes as `image_digest` on each +`built_images` entry in `build_manifest.json`. This capture is always on and +costs nothing: by default the digest is carried along and never used. + +Pass `--require-pinned-image` (or set `"require_pinned_image": true` in +`--additional-context`) to make the run phase pull `repo@sha256:...` instead of +the tag: + +```bash +madengine run --manifest-file build_manifest.json --require-pinned-image + +# Equivalent, for pipelines that drive madengine through additional context +madengine run --manifest-file build_manifest.json \ + --additional-context "{'require_pinned_image': True}" +``` + +| Behaviour | Flag absent (default) | Flag set | +|---|---|---| +| Registry pull | By tag | By digest (`repo@sha256:...`) | +| Manifest has no `image_digest` | Pull by tag | **Fails immediately**, no tag fallback | +| Tag moved since the build | Silently runs the newer image | Registry rejects the pull (`manifest unknown`) | + +Applies to all three execution paths: local Docker, Kubernetes (the pod spec +`image` field), and SLURM. On SLURM the setting is written into the manifest's +`context` block so the nested `madengine run` on each compute node inherits it. + +**Limitations** + +- This does not prevent two concurrent builds from racing to push the same + mutable tag. It converts the resulting silent wrong-image run into a fast, + clear failure. Eliminating the race requires unique tags per build in the + calling CI pipeline. +- Manifests produced by the build-on-compute-node path (SLURM batch builds, + which push from inside a generated sbatch script) carry no `image_digest`. + Runs against those manifests fail fast when the flag is set. +``` + +- [ ] **Step 3: Run the full unit suite** + +Run: `pytest tests/unit -v` +Expected: PASS, no regressions. + +- [ ] **Step 4: Run the integration suite** + +Run: `pytest tests/integration -v -m "not slow"` +Expected: PASS. Pay particular attention to `tests/integration/test_docker_integration.py -k push_image`, which asserts the exact `docker tag` / `docker push` call shapes. + +- [ ] **Step 5: Format and lint the changed files** + +```bash +black src/madengine/core/image_digest.py src/madengine/execution/docker_builder.py \ + src/madengine/execution/container_runner.py src/madengine/deployment/slurm.py \ + src/madengine/deployment/k8s_template_context.py \ + src/madengine/orchestration/run_orchestrator.py src/madengine/cli/commands/run.py \ + tests/unit/test_image_digest.py +isort src/madengine/core/image_digest.py tests/unit/test_image_digest.py +mypy src/madengine/core/image_digest.py +``` + +Expected: `black`/`isort` reformat or report no changes; `mypy` reports no errors in the new module. + +> Only run `black`/`isort` on the files you actually touched. Reformatting untouched files would bloat the diff. + +- [ ] **Step 6: Commit** + +```bash +git add docs/cli-reference.md docs/configuration.md +git commit -m "docs: document --require-pinned-image and image digest capture" +``` + +- [ ] **Step 7: Manual end-to-end smoke check (optional, requires a registry)** + +```bash +# Build and push, then confirm the digest landed in the manifest +madengine build --tags dummy --registry localhost:5000 +python -c "import json; m=json.load(open('build_manifest.json')); print({k: v.get('image_digest') for k, v in m['built_images'].items()})" + +# Run with enforcement and confirm the pull is by digest +madengine run --manifest-file build_manifest.json --require-pinned-image --live-output 2>&1 | grep "docker pull" +``` + +Expected: the manifest prints a `sha256:...` per image, and the pull line contains `@sha256:`. + +--- + +## Verification checklist against the spec's testing plan + +| Spec test | Covered by | +|---|---| +| 1. Push output digest → `image_digest` | Task 3 `test_digest_parsed_from_push_output` + Task 4 `test_single_arch_push_sets_image_digest` | +| 2. Fallback to `docker image inspect` | Task 3 `test_falls_back_to_image_inspect_when_push_output_has_no_digest` | +| 3. Both paths fail → absent key, debug log, build unaffected | Task 3 `test_no_digest_anywhere_leaves_entry_absent_and_push_still_succeeds`, `test_inspect_failure_is_swallowed` | +| 4. Flag absent → pull by tag, no new output | Task 6 `test_default_pulls_by_tag_even_when_digest_present`, Task 7 `test_default_uses_tag`, Task 8 `test_default_exports_tag` | +| 5. Flag present + digest → pinned reference | Task 6 `test_enabled_pulls_pinned_reference`, Task 7 `test_enabled_uses_pinned_reference`, Task 8 `test_enabled_exports_pinned_reference` | +| 6. Flag present, no digest → fail before pull | Task 2 `test_enabled_without_digest_raises`, Task 6 `test_enabled_without_digest_fails_before_pulling` | +| 7. K8s pod spec and SLURM script carry pinned/tag reference | Task 7 + Task 8 | +| 8. Log filename derivation unchanged | Task 9 | +| 9. Existing `docker_sha` / manifest tests unmodified | Task 4 Step 5, Task 10 Steps 3-4 | From 46f2edd391e17eeab9e69d80badca04ce4dff7ea Mon Sep 17 00:00:00 2001 From: Stephen Shao Date: Thu, 27 Aug 2026 21:02:50 -0500 Subject: [PATCH 07/16] feat(image-digest): add digest parsing, pinned reference, and enforcement helpers Add madengine/core/image_digest.py with four pure helpers: - parse_push_digest: extract sha256 digest from `docker push` output - parse_repo_digest: extract digest from a repo@sha256:... reference - build_pinned_reference: build repo@sha256:... , stripping any tag/digest - resolve_pinned_image: pass through, pin, or raise ConfigurationError Co-Authored-By: Claude Opus 5 --- src/madengine/core/image_digest.py | 122 ++++++++++++++++++++++++ tests/unit/test_image_digest.py | 146 +++++++++++++++++++++++++++++ 2 files changed, 268 insertions(+) create mode 100644 src/madengine/core/image_digest.py create mode 100644 tests/unit/test_image_digest.py diff --git a/src/madengine/core/image_digest.py b/src/madengine/core/image_digest.py new file mode 100644 index 00000000..bd0a8294 --- /dev/null +++ b/src/madengine/core/image_digest.py @@ -0,0 +1,122 @@ +#!/usr/bin/env python3 +""" +Registry image digest helpers for madengine. + +Build pushes record the digest of the image they push; runs can optionally be +pinned to that digest so a registry tag that moved between build and run fails +loudly instead of silently resolving to a different image. + +Copyright (c) Advanced Micro Devices, Inc. All rights reserved. +""" + +import re +import typing + +from madengine.core.errors import ConfigurationError, create_error_context + +# `docker push` prints e.g. "mytag: digest: sha256:<64 hex> size: 4738". +_PUSH_DIGEST_RE = re.compile(r"digest:\s*(sha256:[0-9a-f]{64})") + +# `docker image inspect --format '{{index .RepoDigests 0}}'` prints "repo@sha256:<64 hex>". +_REPO_DIGEST_RE = re.compile(r"@(sha256:[0-9a-f]{64})") + + +def parse_push_digest(push_output: typing.Optional[str]) -> typing.Optional[str]: + """Extract the pushed image digest from `docker push` output. + + Args: + push_output: Combined stdout/stderr of the push command. + + Returns: + The digest (``sha256:...``), or None if no digest line was present. + When several digest lines appear the last one is returned, which is the + digest of the reference the push command was invoked with. + """ + if not push_output: + return None + matches = _PUSH_DIGEST_RE.findall(push_output) + return matches[-1] if matches else None + + +def parse_repo_digest(inspect_output: typing.Optional[str]) -> typing.Optional[str]: + """Extract the digest from a ``repo@sha256:...`` reference. + + Args: + inspect_output: Output of + ``docker image inspect --format '{{index .RepoDigests 0}}' ``. + + Returns: + The digest (``sha256:...``), or None if the output held no digest + (e.g. ```` when the image has no RepoDigests entry). + """ + if not inspect_output: + return None + match = _REPO_DIGEST_RE.search(inspect_output) + return match.group(1) if match else None + + +def build_pinned_reference(registry_image: str, digest: str) -> str: + """Build a digest-pinned image reference. + + Any existing tag or digest on ``registry_image`` is dropped. A port in the + registry host (``localhost:5000/org/img``) is not mistaken for a tag because + only the final path segment is inspected for ``:``. + + Args: + registry_image: Image reference, with or without tag/digest. + digest: Digest to pin to (``sha256:...``). + + Returns: + A reference of the form ``repo@sha256:...``. + """ + repo = registry_image.split("@", 1)[0] + last_slash = repo.rfind("/") + tail = repo[last_slash + 1 :] + if ":" in tail: + repo = repo[: last_slash + 1] + tail.split(":", 1)[0] + return f"{repo}@{digest}" + + +def resolve_pinned_image( + registry_image: str, + image_digest: typing.Optional[str], + require_pinned: bool, + model_name: str = "", +) -> str: + """Resolve the image reference to use for a run. + + Args: + registry_image: Tagged registry reference from the build manifest. + image_digest: Digest recorded at build time, if any. + require_pinned: True when --require-pinned-image / require_pinned_image is set. + model_name: Model name, used only to make the error message actionable. + + Returns: + ``registry_image`` unchanged when enforcement is off, otherwise a + digest-pinned reference. + + Raises: + ConfigurationError: When enforcement is on but the manifest recorded no + digest. Falling back to the tag is deliberately not done: silent + degradation would defeat the guarantee the caller asked for. + """ + if not require_pinned: + return registry_image + + if not image_digest: + raise ConfigurationError( + f"--require-pinned-image is set but the build manifest records no " + f"image digest for model '{model_name}' (image: {registry_image}). " + f"Refusing to pull by tag.", + context=create_error_context( + operation="resolve_pinned_image", + component="image_digest", + additional_info={"model": model_name, "image": registry_image}, + ), + suggestions=[ + "Rebuild with this version of madengine so the push digest is recorded", + "Drop --require-pinned-image / require_pinned_image to pull by tag", + ], + ) + + return build_pinned_reference(registry_image, image_digest) diff --git a/tests/unit/test_image_digest.py b/tests/unit/test_image_digest.py new file mode 100644 index 00000000..6179e2b5 --- /dev/null +++ b/tests/unit/test_image_digest.py @@ -0,0 +1,146 @@ +"""Unit tests for madengine.core.image_digest. + +Covers digest extraction from `docker push` / `docker image inspect` output and +construction of pinned `repo@sha256:...` references. + +Copyright (c) Advanced Micro Devices, Inc. All rights reserved. +""" + +import pytest + +from madengine.core.errors import ConfigurationError +from madengine.core.image_digest import ( + build_pinned_reference, + parse_push_digest, + parse_repo_digest, + resolve_pinned_image, +) + +DIGEST = "sha256:" + "df36ef7e" * 8 # 64 hex chars +OTHER_DIGEST = "sha256:" + "cbb7e5ed" * 8 + + +class TestParsePushDigest: + """parse_push_digest extracts the digest line emitted by `docker push`.""" + + def test_typical_push_output(self): + output = ( + "The push refers to repository [docker.io/myorg/ci]\n" + "a1b2c3d4e5f6: Pushed\n" + "9f8e7d6c5b4a: Layer already exists\n" + f"mymodel: digest: {DIGEST} size: 4738\n" + ) + assert parse_push_digest(output) == DIGEST + + def test_no_space_after_colon(self): + assert parse_push_digest(f"mymodel: digest:{DIGEST} size: 12") == DIGEST + + def test_last_digest_wins_when_multiple(self): + output = ( + f"tag-a: digest: {OTHER_DIGEST} size: 10\n" + f"tag-b: digest: {DIGEST} size: 10\n" + ) + assert parse_push_digest(output) == DIGEST + + def test_uppercase_hex_is_not_matched(self): + assert parse_push_digest("mymodel: digest: sha256:ABC size: 1") is None + + def test_output_without_digest_line(self): + assert ( + parse_push_digest("The push refers to repository [x]\nlayer: Pushed\n") + is None + ) + + def test_empty_output(self): + assert parse_push_digest("") is None + + def test_none_output(self): + assert parse_push_digest(None) is None + + +class TestParseRepoDigest: + """parse_repo_digest extracts the digest from a `repo@sha256:...` reference.""" + + def test_repo_digest_reference(self): + assert parse_repo_digest(f"myorg/ci@{DIGEST}") == DIGEST + + def test_registry_with_port(self): + assert parse_repo_digest(f"localhost:5000/myorg/ci@{DIGEST}") == DIGEST + + def test_surrounding_whitespace_and_quotes(self): + assert parse_repo_digest(f" 'myorg/ci@{DIGEST}' \n") == DIGEST + + def test_empty_repodigests_placeholder(self): + # `docker image inspect` prints this when RepoDigests is empty. + assert parse_repo_digest("") is None + + def test_none_output(self): + assert parse_repo_digest(None) is None + + +class TestBuildPinnedReference: + """build_pinned_reference produces repo@sha256:... with any tag stripped.""" + + def test_strips_tag(self): + assert ( + build_pinned_reference(f"myorg/ci:mymodel", DIGEST) == f"myorg/ci@{DIGEST}" + ) + + def test_no_tag(self): + assert build_pinned_reference("myorg/ci", DIGEST) == f"myorg/ci@{DIGEST}" + + def test_registry_port_is_not_mistaken_for_a_tag(self): + out = build_pinned_reference("localhost:5000/myorg/ci:latest", DIGEST) + assert out == f"localhost:5000/myorg/ci@{DIGEST}" + + def test_registry_port_without_tag(self): + out = build_pinned_reference("localhost:5000/myorg/ci", DIGEST) + assert out == f"localhost:5000/myorg/ci@{DIGEST}" + + def test_bare_name_with_tag(self): + assert build_pinned_reference("ci-dummy:latest", DIGEST) == f"ci-dummy@{DIGEST}" + + def test_existing_digest_is_replaced(self): + out = build_pinned_reference(f"myorg/ci@{OTHER_DIGEST}", DIGEST) + assert out == f"myorg/ci@{DIGEST}" + + +class TestResolvePinnedImage: + """resolve_pinned_image applies the --require-pinned-image policy.""" + + def test_disabled_returns_image_unchanged_without_digest(self): + assert ( + resolve_pinned_image("myorg/ci:mymodel", None, False) == "myorg/ci:mymodel" + ) + + def test_disabled_returns_image_unchanged_even_with_digest(self): + # Default behaviour must not change for existing users: the digest rides + # along in the manifest but is never used unless enforcement is on. + assert ( + resolve_pinned_image("myorg/ci:mymodel", DIGEST, False) + == "myorg/ci:mymodel" + ) + + def test_enabled_with_digest_returns_pinned_reference(self): + out = resolve_pinned_image("myorg/ci:mymodel", DIGEST, True) + assert out == f"myorg/ci@{DIGEST}" + + def test_enabled_without_digest_raises(self): + with pytest.raises(ConfigurationError): + resolve_pinned_image("myorg/ci:mymodel", None, True, model_name="my_model") + + def test_enabled_with_empty_digest_raises(self): + with pytest.raises(ConfigurationError): + resolve_pinned_image("myorg/ci:mymodel", "", True, model_name="my_model") + + def test_error_message_names_model_and_image(self): + with pytest.raises(ConfigurationError) as excinfo: + resolve_pinned_image("myorg/ci:mymodel", None, True, model_name="my_model") + message = str(excinfo.value) + assert "my_model" in message + assert "myorg/ci:mymodel" in message + + def test_error_carries_actionable_suggestions(self): + with pytest.raises(ConfigurationError) as excinfo: + resolve_pinned_image("myorg/ci:mymodel", None, True, model_name="my_model") + assert excinfo.value.suggestions From fc789f504a8e3b13ec90ef48bbb8b88f60ff9dd9 Mon Sep 17 00:00:00 2001 From: Stephen Shao Date: Thu, 27 Aug 2026 21:03:57 -0500 Subject: [PATCH 08/16] feat(build): capture pushed image digest during docker push push_image() now captures the push output and records the resulting sha256 digest in self.pushed_digests, falling back to `docker image inspect --format '{{index .RepoDigests 0}}'` when the registry does not print a digest line. Best-effort: a missing digest is noted at dim level and never fails the build. Co-Authored-By: Claude Opus 5 --- src/madengine/execution/docker_builder.py | 44 ++++++++++- tests/unit/test_docker_builder.py | 96 ++++++++++++++++++++++- 2 files changed, 138 insertions(+), 2 deletions(-) diff --git a/src/madengine/execution/docker_builder.py b/src/madengine/execution/docker_builder.py index 5ee83c47..ec5a1b85 100644 --- a/src/madengine/execution/docker_builder.py +++ b/src/madengine/execution/docker_builder.py @@ -24,6 +24,7 @@ ) from madengine.core.console import Console from madengine.core.context import Context +from madengine.core.image_digest import parse_push_digest, parse_repo_digest from madengine.utils.ops import PythonicTee from madengine.execution.dockerfile_utils import ( is_target_arch_compatible_with_variable, @@ -49,6 +50,7 @@ def __init__( self.live_output = live_output self.rich_console = RichConsole() self.built_images = {} # Track built images + self.pushed_digests = {} # registry_image -> digest recorded at push time self.built_models = {} # Track built models def get_context_path(self, info: typing.Dict) -> str: @@ -396,7 +398,9 @@ def push_image( self.rich_console.print(f"\n[bold blue]🚀 Starting docker push to registry...[/bold blue]") print(f"📤 Registry: {registry}") print(f"🏷️ Image: {registry_image}") - self.console.sh(push_command) + push_output = self.console.sh(push_command) + + self._record_pushed_digest(registry_image, push_output) self.rich_console.print(f"[bold green]✅ Successfully pushed image to registry:[/bold green] [cyan]{registry_image}[/cyan]") self.rich_console.print(f"[dim]{'='*80}[/dim]") @@ -406,6 +410,44 @@ def push_image( self.rich_console.print(f"[red]❌ Failed to push image {docker_image} to registry {registry}: {e}[/red]") raise + def _record_pushed_digest(self, registry_image: str, push_output: str) -> None: + """Record the digest of the image just pushed, for digest-pinned runs. + + Best-effort by design: the push has already succeeded by the time this + runs, so a missing digest is a manifest-completeness gap (noted at dim + level), never a build failure. Runs only consult the recorded digest + when --require-pinned-image is set. + + Args: + registry_image: The reference that was pushed. + push_output: stdout/stderr captured from the push command. + """ + digest = parse_push_digest(push_output) + + if not digest: + # Some registries/mirrors do not print the digest line; ask the + # daemon for the RepoDigests entry it recorded for this push. + try: + inspect_output = self.console.sh( + "docker image inspect --format '{{index .RepoDigests 0}}' " + + shlex.quote(registry_image) + ) + digest = parse_repo_digest(inspect_output) + except Exception: + digest = None + + if not digest: + self.rich_console.print( + f"[dim]No pushed digest recorded for {registry_image}; " + f"--require-pinned-image runs will reject this manifest entry[/dim]" + ) + return + + self.pushed_digests[registry_image] = digest + self.rich_console.print( + f"[dim]Pushed digest: {registry_image} -> {digest}[/dim]" + ) + def export_build_manifest( self, output_file: str = "build_manifest.json", diff --git a/tests/unit/test_docker_builder.py b/tests/unit/test_docker_builder.py index 3fe97f9b..5827400b 100644 --- a/tests/unit/test_docker_builder.py +++ b/tests/unit/test_docker_builder.py @@ -3,12 +3,14 @@ Currently covers registry image naming for single-arch builds (credentials → repository:tag). """ -from unittest.mock import MagicMock +from unittest.mock import MagicMock, patch import pytest from madengine.execution.docker_builder import DockerBuilder +DIGEST = "sha256:" + "df36ef7e" * 8 + @pytest.fixture def docker_builder(): @@ -44,3 +46,95 @@ def test_create_registry_image_name_without_credentials_matches_local_tag(docker None, ) assert out == "ci-dummy_dummy.ubuntu.amd" + + +class TestPushImageRecordsDigest: + """push_image records the pushed image digest in builder.pushed_digests.""" + + def _builder(self, sh_side_effect): + ctx = MagicMock() + ctx.ctx = {} + console = MagicMock() + console.sh = MagicMock(side_effect=sh_side_effect) + builder = DockerBuilder(ctx, console) + builder.rich_console = MagicMock() + return builder + + def test_digest_parsed_from_push_output(self): + def sh(command, *args, **kwargs): + if "docker push" in command: + return f"mymodel: digest: {DIGEST} size: 4738" + return "" + + builder = self._builder(sh) + result = builder.push_image( + "ci-dummy", "localhost:5000", None, "localhost:5000/ci-dummy" + ) + + assert result == "localhost:5000/ci-dummy" + assert builder.pushed_digests["localhost:5000/ci-dummy"] == DIGEST + + def test_falls_back_to_image_inspect_when_push_output_has_no_digest(self): + def sh(command, *args, **kwargs): + if "docker push" in command: + return "The push refers to repository [localhost:5000/ci-dummy]\nlayer: Pushed" + if "docker image inspect" in command: + return f"localhost:5000/ci-dummy@{DIGEST}" + return "" + + builder = self._builder(sh) + builder.push_image( + "ci-dummy", "localhost:5000", None, "localhost:5000/ci-dummy" + ) + + assert builder.pushed_digests["localhost:5000/ci-dummy"] == DIGEST + inspect_calls = [ + c + for c in builder.console.sh.call_args_list + if "docker image inspect" in c.args[0] + ] + assert len(inspect_calls) == 1 + assert "RepoDigests" in inspect_calls[0].args[0] + + def test_no_digest_anywhere_leaves_entry_absent_and_push_still_succeeds(self): + def sh(command, *args, **kwargs): + if "docker push" in command: + return "layer: Pushed" + if "docker image inspect" in command: + return "" + return "" + + builder = self._builder(sh) + result = builder.push_image( + "ci-dummy", "localhost:5000", None, "localhost:5000/ci-dummy" + ) + + assert result == "localhost:5000/ci-dummy" + assert "localhost:5000/ci-dummy" not in builder.pushed_digests + # The gap is noted at dim level, not as a user-facing warning. + printed = " ".join(str(c) for c in builder.rich_console.print.call_args_list) + assert "[dim]" in printed + assert "no pushed digest" in printed.lower() + + def test_inspect_failure_is_swallowed(self): + def sh(command, *args, **kwargs): + if "docker push" in command: + return "layer: Pushed" + if "docker image inspect" in command: + raise RuntimeError("no such image") + return "" + + builder = self._builder(sh) + result = builder.push_image( + "ci-dummy", "localhost:5000", None, "localhost:5000/ci-dummy" + ) + + assert result == "localhost:5000/ci-dummy" + assert builder.pushed_digests == {} + + def test_no_registry_records_nothing(self): + builder = self._builder(lambda command, *a, **k: "") + result = builder.push_image("ci-dummy") + + assert result == "ci-dummy" + assert builder.pushed_digests == {} From 7be8aaf05903df88202a1c8bdb58de6ea3895342 Mon Sep 17 00:00:00 2001 From: Stephen Shao Date: Thu, 27 Aug 2026 21:04:50 -0500 Subject: [PATCH 09/16] feat(build): record image_digest in build manifest entries Both push call sites (single-arch and per-GPU-arch) copy the digest recorded by push_image into build_info["image_digest"]. build_info is serialized wholesale into build_manifest.json, so the key is purely additive for existing manifest consumers. Co-Authored-By: Claude Opus 5 --- src/madengine/execution/docker_builder.py | 8 +++ tests/unit/test_docker_builder.py | 70 +++++++++++++++++++++++ 2 files changed, 78 insertions(+) diff --git a/src/madengine/execution/docker_builder.py b/src/madengine/execution/docker_builder.py index ec5a1b85..0e0d608c 100644 --- a/src/madengine/execution/docker_builder.py +++ b/src/madengine/execution/docker_builder.py @@ -810,6 +810,10 @@ def _build_model_single_arch( ) self.push_image(build_info["docker_image"], registry, credentials, registry_image) build_info["registry_image"] = registry_image + # Recorded at push time; consumed only by --require-pinned-image runs. + pushed_digest = self.pushed_digests.get(registry_image) + if pushed_digest: + build_info["image_digest"] = pushed_digest except Exception as e: build_info["push_error"] = str(e) @@ -1018,6 +1022,10 @@ def _build_model_for_arch( try: self.push_image(arch_image_name, registry, credentials, registry_image) build_info["registry_image"] = registry_image + # Recorded at push time; consumed only by --require-pinned-image runs. + pushed_digest = self.pushed_digests.get(registry_image) + if pushed_digest: + build_info["image_digest"] = pushed_digest except Exception as e: build_info["push_error"] = str(e) diff --git a/tests/unit/test_docker_builder.py b/tests/unit/test_docker_builder.py index 5827400b..c9d63171 100644 --- a/tests/unit/test_docker_builder.py +++ b/tests/unit/test_docker_builder.py @@ -138,3 +138,73 @@ def test_no_registry_records_nothing(self): assert result == "ci-dummy" assert builder.pushed_digests == {} + + +class TestBuildInfoCarriesImageDigest: + """Both push call sites copy the recorded digest into build_info.""" + + def _builder(self): + ctx = MagicMock() + ctx.ctx = {} + builder = DockerBuilder(ctx, MagicMock()) + builder.rich_console = MagicMock() + return builder + + def _run_single_arch(self, builder): + """Drive _build_model_single_arch with everything below push_image stubbed.""" + return builder._build_model_single_arch( + model_info={"name": "dummy", "dockerfile": "docker/dummy"}, + credentials={}, + clean_cache=False, + registry="localhost:5000", + phase_suffix="", + batch_build_metadata=None, + ) + + def test_single_arch_push_sets_image_digest(self): + builder = self._builder() + + def fake_push(docker_image, registry, credentials, explicit_registry_image): + builder.pushed_digests[explicit_registry_image] = DIGEST + return explicit_registry_image + + with patch.object( + builder, "_get_dockerfiles_for_model", return_value=["docker/dummy.ubuntu"] + ), patch.object( + builder, + "build_image", + return_value={"docker_image": "ci-dummy", "model": "dummy"}, + ), patch.object( + builder, "_get_effective_gpu_architecture", return_value="" + ), patch.object( + builder, "_create_registry_image_name", return_value="localhost:5000/ci-dummy" + ), patch.object( + builder, "push_image", side_effect=fake_push + ): + results = self._run_single_arch(builder) + + assert results[0]["registry_image"] == "localhost:5000/ci-dummy" + assert results[0]["image_digest"] == DIGEST + # A push failure must still be recorded the way it is today. + assert "push_error" not in results[0] + + def test_single_arch_push_without_digest_omits_key(self): + builder = self._builder() + + with patch.object( + builder, "_get_dockerfiles_for_model", return_value=["docker/dummy.ubuntu"] + ), patch.object( + builder, + "build_image", + return_value={"docker_image": "ci-dummy", "model": "dummy"}, + ), patch.object( + builder, "_get_effective_gpu_architecture", return_value="" + ), patch.object( + builder, "_create_registry_image_name", return_value="localhost:5000/ci-dummy" + ), patch.object( + builder, "push_image", return_value="localhost:5000/ci-dummy" + ): + results = self._run_single_arch(builder) + + assert results[0]["registry_image"] == "localhost:5000/ci-dummy" + assert "image_digest" not in results[0] From b424b05aa01b27ffe6ca3362ca5143bb55911ae9 Mon Sep 17 00:00:00 2001 From: Stephen Shao Date: Thu, 27 Aug 2026 21:09:06 -0500 Subject: [PATCH 10/16] feat(run): add --require-pinned-image flag and context propagation The CLI flag and the require_pinned_image additional-context key are equivalent entry points. The key is also persisted into manifest["context"] so the nested `madengine run` that SLURM job scripts execute on each compute node inherits the setting. Two pre-existing tests built args as a bare MagicMock and asserted exact-equality on additional_context; auto-vivified attributes are truthy, so the new flag leaked in. Pin the attribute in those mocks. Co-Authored-By: Claude Opus 5 --- src/madengine/cli/commands/run.py | 14 +++++ .../orchestration/run_orchestrator.py | 17 ++++- .../test_orchestrator_workflows.py | 4 ++ tests/unit/test_orchestration.py | 62 +++++++++++++++++++ 4 files changed, 96 insertions(+), 1 deletion(-) diff --git a/src/madengine/cli/commands/run.py b/src/madengine/cli/commands/run.py index c961cbe5..83266ff8 100644 --- a/src/madengine/cli/commands/run.py +++ b/src/madengine/cli/commands/run.py @@ -106,6 +106,18 @@ def run( ), ), ] = False, + require_pinned_image: Annotated[ + bool, + typer.Option( + "--require-pinned-image", + help=( + "Pull registry images by the digest recorded in the build manifest " + "instead of by tag. Fails immediately if the manifest has no digest " + "for an image. Equivalent to the 'require_pinned_image' " + "additional-context key." + ), + ), + ] = False, manifest_output: Annotated[ str, typer.Option( @@ -234,6 +246,7 @@ def run( verbose=verbose, cleanup_perf=cleanup_perf, skip_model_run=skip_model_run, + require_pinned_image=require_pinned_image, _separate_phases=True, ) @@ -338,6 +351,7 @@ def run( verbose=verbose, cleanup_perf=cleanup_perf, skip_model_run=skip_model_run, + require_pinned_image=require_pinned_image, _separate_phases=False, # Full workflow uses .live.log (not .run.live.log) ) diff --git a/src/madengine/orchestration/run_orchestrator.py b/src/madengine/orchestration/run_orchestrator.py index 498401c4..aa8fa652 100644 --- a/src/madengine/orchestration/run_orchestrator.py +++ b/src/madengine/orchestration/run_orchestrator.py @@ -83,6 +83,13 @@ def __init__(self, args, additional_context: Optional[Dict] = None): merged_context.update(additional_context) self.additional_context = merged_context + + # The CLI flag and the require_pinned_image context key are equivalent; + # the key lets CI pipelines that drive madengine through + # --additional-context opt in the same way as for k8s/slurm/tools. + if getattr(args, "require_pinned_image", False): + self.additional_context["require_pinned_image"] = True + keys_str = ", ".join(sorted(self.additional_context.keys())) if self.additional_context else "(none)" self.rich_console.print(f"[dim]Run additional context (CLI):[/dim] [cyan]{keys_str}[/cyan]") @@ -496,7 +503,15 @@ def _load_and_merge_manifest(self, manifest_file: str) -> str: if "context" not in manifest: manifest["context"] = {} - merge_keys = ["tools", "pre_scripts", "post_scripts", "encapsulate_script"] + merge_keys = [ + "tools", + "pre_scripts", + "post_scripts", + "encapsulate_script", + # Persisted so nested runs on SLURM compute nodes (which re-enter + # `madengine run --manifest-file`) inherit the enforcement setting. + "require_pinned_image", + ] context_updated = False for key in merge_keys: if key in self.additional_context: diff --git a/tests/integration/test_orchestrator_workflows.py b/tests/integration/test_orchestrator_workflows.py index 143e56e5..5e51cc7d 100644 --- a/tests/integration/test_orchestrator_workflows.py +++ b/tests/integration/test_orchestrator_workflows.py @@ -323,6 +323,9 @@ def test_run_orchestrator_initialization(self, mock_context): mock_args = MagicMock() mock_args.additional_context = None mock_args.live_output = True + # A bare MagicMock auto-vivifies truthy attributes; pin the flags this + # assertion depends on so they cannot leak into additional_context. + mock_args.require_pinned_image = False orchestrator = RunOrchestrator(mock_args) @@ -335,6 +338,7 @@ def test_run_orchestrator_additional_context_parsing(self): mock_args = MagicMock() mock_args.additional_context = '{"deploy": "slurm", "slurm": {"nodes": 4}}' mock_args.live_output = True + mock_args.require_pinned_image = False orchestrator = RunOrchestrator(mock_args) diff --git a/tests/unit/test_orchestration.py b/tests/unit/test_orchestration.py index aaf24fcc..f198b9ed 100644 --- a/tests/unit/test_orchestration.py +++ b/tests/unit/test_orchestration.py @@ -16,6 +16,7 @@ from madengine.orchestration.build_orchestrator import BuildOrchestrator from madengine.orchestration.run_orchestrator import RunOrchestrator from madengine.core.errors import ConfigurationError +from madengine.cli.utils import create_args_namespace # ---- image_filtering ---- @@ -160,6 +161,9 @@ def test_initializes_with_args(self, mock_context): mock_args = MagicMock() mock_args.additional_context = None mock_args.live_output = True + # A bare MagicMock auto-vivifies truthy attributes; pin the flags this + # assertion depends on so they cannot leak into additional_context. + mock_args.require_pinned_image = False orchestrator = RunOrchestrator(mock_args) @@ -446,3 +450,61 @@ def test_multiple_results_defaults_to_empty_string(self, mock_context, tmp_path) built_model = next(iter(manifest["built_models"].values())) assert built_model["multiple_results"] == "" + + +class TestRequirePinnedImageContext: + """--require-pinned-image and require_pinned_image both reach additional_context.""" + + @patch("madengine.orchestration.run_orchestrator.Context") + def test_cli_flag_sets_context_key(self, mock_context): + args = create_args_namespace( + additional_context=None, + require_pinned_image=True, + live_output=False, + ) + orch = RunOrchestrator(args) + assert orch.additional_context["require_pinned_image"] is True + + @patch("madengine.orchestration.run_orchestrator.Context") + def test_flag_absent_leaves_key_unset(self, mock_context): + args = create_args_namespace( + additional_context=None, + require_pinned_image=False, + live_output=False, + ) + orch = RunOrchestrator(args) + assert "require_pinned_image" not in orch.additional_context + + @patch("madengine.orchestration.run_orchestrator.Context") + def test_additional_context_key_alone_is_honoured(self, mock_context): + args = create_args_namespace( + additional_context="{'require_pinned_image': True}", + live_output=False, + ) + orch = RunOrchestrator(args) + assert orch.additional_context["require_pinned_image"] is True + + @patch("madengine.orchestration.run_orchestrator.Context") + def test_key_is_persisted_into_manifest_context(self, mock_context, tmp_path): + manifest_path = tmp_path / "build_manifest.json" + manifest_path.write_text( + json.dumps( + { + "built_images": {"img1": {"registry_image": "myorg/ci:m"}}, + "built_models": {"img1": {"name": "m"}}, + "context": {}, + "deployment_config": {}, + } + ) + ) + + args = create_args_namespace( + additional_context=None, + require_pinned_image=True, + live_output=False, + ) + orch = RunOrchestrator(args) + orch._load_and_merge_manifest(str(manifest_path)) + + written = json.loads(manifest_path.read_text()) + assert written["context"]["require_pinned_image"] is True From f7bb2a6913dc4281500baabf2b4a4b1667499e17 Mon Sep 17 00:00:00 2001 From: Stephen Shao Date: Thu, 27 Aug 2026 21:09:56 -0500 Subject: [PATCH 11/16] feat(run): pin local docker pulls to manifest digest when required The resolve_pinned_image call sits outside the pull try/except so a missing digest aborts rather than falling back to the local image tag. The container runs the same pinned reference that was pulled. Co-Authored-By: Claude Opus 5 --- src/madengine/execution/container_runner.py | 16 ++- tests/unit/test_container_runner.py | 119 ++++++++++++++++++++ 2 files changed, 132 insertions(+), 3 deletions(-) diff --git a/src/madengine/execution/container_runner.py b/src/madengine/execution/container_runner.py index eab4af7d..c38261a6 100644 --- a/src/madengine/execution/container_runner.py +++ b/src/madengine/execution/container_runner.py @@ -23,6 +23,7 @@ from madengine.core.console import Console, redact_secrets from madengine.core.context import Context from madengine.core.docker import Docker +from madengine.core.image_digest import resolve_pinned_image from madengine.core.timeout import Timeout from madengine.core.dataprovider import Data from madengine.utils.ops import PythonicTee, file_print @@ -2849,11 +2850,20 @@ def run_models_from_manifest( ) elif build_info.get("registry_image"): - # Registry image: Pull from registry + # Registry image: Pull from registry. Under + # require_pinned_image this resolves to repo@sha256:... and + # raises (outside the pull try/except, so there is no tag + # fallback) when the manifest recorded no digest. + pull_target = resolve_pinned_image( + build_info["registry_image"], + build_info.get("image_digest"), + bool((self.additional_context or {}).get("require_pinned_image")), + model_name=model_info.get("name", ""), + ) try: - self.pull_image(build_info["registry_image"]) + self.pull_image(pull_target) # Update docker_image to use registry image - run_image = build_info["registry_image"] + run_image = pull_target except Exception as pull_error: self.rich_console.print(f"[yellow]Warning: Could not pull from registry, using local image[/yellow]") run_image = image_name diff --git a/tests/unit/test_container_runner.py b/tests/unit/test_container_runner.py index aae79321..3e80d88c 100644 --- a/tests/unit/test_container_runner.py +++ b/tests/unit/test_container_runner.py @@ -523,3 +523,122 @@ def test_skip_model_run_false_does_exec_script(self): assert any( "run.sh" in c and "cd " in c for c in docker_sh_calls ), f"Model script was not executed: {docker_sh_calls}" + + +DIGEST = "sha256:" + "df36ef7e" * 8 + + +class TestRequirePinnedImageLocalRun: + """run_models_from_manifest honours require_pinned_image for registry pulls.""" + + def _manifest(self, tmpdir, build_info): + manifest_path = os.path.join(tmpdir, "build_manifest.json") + with open(manifest_path, "w") as f: + json.dump( + { + "built_images": {"img1": build_info}, + "built_models": { + "img1": {"name": "m", "tags": "t", "n_gpus": "1", "args": ""} + }, + }, + f, + ) + return manifest_path + + def _runner(self): + ctx = MagicMock() + ctx.ctx = {"docker_env_vars": {"MAD_SYSTEM_GPU_ARCHITECTURE": "gfx90a"}} + ctx.ensure_runtime_context = MagicMock() + console = MagicMock() + console.sh.return_value = "testhost" + runner = ContainerRunner(context=ctx, console=console) + runner.set_credentials({}) + return runner + + @patch("madengine.execution.container_runner.update_perf_csv") + def test_default_pulls_by_tag_even_when_digest_present(self, _mock_csv): + with tempfile.TemporaryDirectory() as tmpdir: + manifest_path = self._manifest( + tmpdir, + {"registry_image": "myorg/ci:m", "image_digest": DIGEST}, + ) + runner = self._runner() + runner.perf_csv_path = os.path.join(tmpdir, "perf.csv") + + with patch.object(runner, "pull_image") as mock_pull, patch.object( + runner, "run_container", return_value={"status": "SUCCESS"} + ): + runner.run_models_from_manifest(manifest_file=manifest_path, timeout=60) + + mock_pull.assert_called_once_with("myorg/ci:m") + + @patch("madengine.execution.container_runner.update_perf_csv") + def test_enabled_pulls_pinned_reference(self, _mock_csv): + with tempfile.TemporaryDirectory() as tmpdir: + manifest_path = self._manifest( + tmpdir, + {"registry_image": "myorg/ci:m", "image_digest": DIGEST}, + ) + runner = self._runner() + runner.perf_csv_path = os.path.join(tmpdir, "perf.csv") + runner.additional_context = {"require_pinned_image": True} + + with patch.object(runner, "pull_image") as mock_pull, patch.object( + runner, "run_container", return_value={"status": "SUCCESS"} + ) as mock_run: + runner.run_models_from_manifest(manifest_file=manifest_path, timeout=60) + + mock_pull.assert_called_once_with(f"myorg/ci@{DIGEST}") + # The container must run the same pinned reference that was pulled. + assert mock_run.call_args[1]["docker_image"] == f"myorg/ci@{DIGEST}" + + @patch("madengine.execution.container_runner.update_perf_csv") + def test_enabled_without_digest_fails_before_pulling(self, _mock_csv): + with tempfile.TemporaryDirectory() as tmpdir: + manifest_path = self._manifest(tmpdir, {"registry_image": "myorg/ci:m"}) + runner = self._runner() + runner.perf_csv_path = os.path.join(tmpdir, "perf.csv") + runner.additional_context = {"require_pinned_image": True} + + with patch.object(runner, "pull_image") as mock_pull, patch.object( + runner, "run_container" + ) as mock_run: + result = runner.run_models_from_manifest( + manifest_file=manifest_path, timeout=60 + ) + + mock_pull.assert_not_called() + mock_run.assert_not_called() + assert len(result["failed_runs"]) == 1 + assert "require-pinned-image" in result["failed_runs"][0]["error"] + + @patch("madengine.execution.container_runner.update_perf_csv") + def test_manifest_context_key_enables_enforcement(self, _mock_csv): + """A nested run on a SLURM compute node inherits the setting via manifest context.""" + with tempfile.TemporaryDirectory() as tmpdir: + manifest_path = os.path.join(tmpdir, "build_manifest.json") + with open(manifest_path, "w") as f: + json.dump( + { + "built_images": { + "img1": { + "registry_image": "myorg/ci:m", + "image_digest": DIGEST, + } + }, + "built_models": { + "img1": {"name": "m", "tags": "t", "n_gpus": "1", "args": ""} + }, + "context": {"require_pinned_image": True}, + }, + f, + ) + runner = self._runner() + runner.perf_csv_path = os.path.join(tmpdir, "perf.csv") + + with patch.object(runner, "pull_image") as mock_pull, patch.object( + runner, "run_container", return_value={"status": "SUCCESS"} + ): + runner.run_models_from_manifest(manifest_file=manifest_path, timeout=60) + + mock_pull.assert_called_once_with(f"myorg/ci@{DIGEST}") From 3e8f1a56774f99c477937d3f163d91ba80483e1d Mon Sep 17 00:00:00 2001 From: Stephen Shao Date: Thu, 27 Aug 2026 21:15:10 -0500 Subject: [PATCH 12/16] feat(k8s): pin pod image to manifest digest when required The pod spec image field resolves through resolve_pinned_image, so a moved tag surfaces as an ImagePullBackOff rather than a silent wrong-image run. Co-Authored-By: Claude Opus 5 --- .../deployment/k8s_template_context.py | 12 ++- tests/unit/test_k8s.py | 74 +++++++++++++++++++ 2 files changed, 85 insertions(+), 1 deletion(-) diff --git a/src/madengine/deployment/k8s_template_context.py b/src/madengine/deployment/k8s_template_context.py index e38b251a..498e2659 100644 --- a/src/madengine/deployment/k8s_template_context.py +++ b/src/madengine/deployment/k8s_template_context.py @@ -31,6 +31,7 @@ from madengine.core.additional_context_defaults import DEFAULT_GUEST_OS from madengine.core.dataprovider import Data from madengine.core.errors import ConfigurationError +from madengine.core.image_digest import resolve_pinned_image from madengine.utils.gpu_config import resolve_runtime_gpus from madengine.utils.path_utils import get_madengine_root @@ -483,6 +484,15 @@ def _prepare_template_context( else None ) + # Under require_pinned_image the pod pulls repo@sha256:... so a moved tag + # surfaces as an ImagePullBackOff rather than a silent wrong-image run. + resolved_image = resolve_pinned_image( + image_info["registry_image"], + image_info.get("image_digest"), + bool(additional_context.get("require_pinned_image")), + model_name=model_name, + ) + # Build complete context context = { # Job metadata @@ -515,7 +525,7 @@ def _prepare_template_context( "data_provider_script": data_provider_script, "data_provider_script_content": data_provider_script_content, # Image - "image": image_info["registry_image"], + "image": resolved_image, "image_pull_policy": self.k8s_config.get("image_pull_policy", "Always"), # Resources "gpu_resource_name": self.gpu_resource_name, diff --git a/tests/unit/test_k8s.py b/tests/unit/test_k8s.py index 4a1f84a4..b4674b77 100644 --- a/tests/unit/test_k8s.py +++ b/tests/unit/test_k8s.py @@ -5,6 +5,8 @@ Integration/e2e tests stay in their own modules. """ +import json + import pytest from madengine.deployment.k8s_names import ( @@ -24,10 +26,13 @@ build_registry_secret_data, ) from madengine.deployment.kubernetes import ( + KubernetesDeployment, _pod_job_name_label_selector, assign_pvc_subdirs_to_pods, match_pvc_subdir_to_k8s_pod, ) +from madengine.core.errors import ConfigurationError +from madengine.deployment.base import DeploymentConfig def test_merge_secrets_config_defaults(): @@ -274,3 +279,72 @@ def test_invalid_mode_falls_back_to_lite(self): pre_scripts = [] mixin.gather_system_env_details(pre_scripts, "my_model", rocenv_mode="bogus") assert pre_scripts[0]["args"] == "my_model_env lite UBUNTU" + + +class TestK8sRequirePinnedImage: + """The generated pod spec image field honours require_pinned_image.""" + + DIGEST = "sha256:" + "df36ef7e" * 8 + + def _template_context(self, tmp_path, monkeypatch, require_pinned, image_digest): + """Build a real template context, the way prepare() does. + + _prepare_template_context reads the manifest and the model's scripts + directory from the current working directory, so the test runs inside + tmp_path with a minimal model tree. + """ + monkeypatch.chdir(tmp_path) + (tmp_path / "scripts" / "dummy").mkdir(parents=True) + (tmp_path / "scripts" / "dummy" / "run.sh").write_text("#!/bin/bash\necho hi\n") + + image_info = {"registry_image": "myorg/ci:m"} + if image_digest: + image_info["image_digest"] = image_digest + model_info = { + "name": "m", + "tags": ["t"], + "n_gpus": "1", + "args": "", + "scripts": "scripts/dummy/run.sh", + "dockerfile": "docker/dummy", + } + manifest = { + "built_images": {"img1": image_info}, + "built_models": {"img1": model_info}, + "context": {}, + } + (tmp_path / "build_manifest.json").write_text(json.dumps(manifest)) + + additional_context = { + "k8s": {"namespace": "default"}, + "gpu_vendor": "AMD", + "guest_os": "UBUNTU", + } + if require_pinned: + additional_context["require_pinned_image"] = True + + cfg = DeploymentConfig( + target="k8s", + manifest_file="build_manifest.json", + additional_context=additional_context, + ) + deployment = KubernetesDeployment(cfg) + return deployment._prepare_template_context(model_info, image_info) + + def test_default_uses_tag(self, tmp_path, monkeypatch): + ctx = self._template_context( + tmp_path, monkeypatch, require_pinned=False, image_digest=self.DIGEST + ) + assert ctx["image"] == "myorg/ci:m" + + def test_enabled_uses_pinned_reference(self, tmp_path, monkeypatch): + ctx = self._template_context( + tmp_path, monkeypatch, require_pinned=True, image_digest=self.DIGEST + ) + assert ctx["image"] == f"myorg/ci@{self.DIGEST}" + + def test_enabled_without_digest_raises(self, tmp_path, monkeypatch): + with pytest.raises(ConfigurationError): + self._template_context( + tmp_path, monkeypatch, require_pinned=True, image_digest=None + ) From dc0ad91f89bff425fc46ae1e3c7c8bd41aaaf361 Mon Sep 17 00:00:00 2001 From: Stephen Shao Date: Thu, 27 Aug 2026 21:19:02 -0500 Subject: [PATCH 13/16] feat(slurm): pin slurm_multi image to manifest digest when required slurm_multi runs the model's own script on the head node with no nested `madengine run` on the compute nodes, so enforcement happens here. Pinning DOCKER_IMAGE_NAME covers both the parallel `srun docker pull` (which interpolates it) and the `docker run` inside the model script. prepare()'s launcher peek wrapped the whole slurm_multi dispatch in a bare `except Exception: pass`, which would have swallowed the enforcement error and generated an unpinned script instead. Re-raise ConfigurationError so deliberate aborts propagate. Co-Authored-By: Claude Opus 5 --- src/madengine/deployment/slurm.py | 31 ++++++++++- tests/unit/test_slurm_multi.py | 88 +++++++++++++++++++++++++++++++ 2 files changed, 118 insertions(+), 1 deletion(-) diff --git a/src/madengine/deployment/slurm.py b/src/madengine/deployment/slurm.py index 088a3fb2..c9cdd21b 100644 --- a/src/madengine/deployment/slurm.py +++ b/src/madengine/deployment/slurm.py @@ -29,6 +29,8 @@ ) from .config_loader import ConfigLoader, apply_deployment_config from .slurm_node_selector import SlurmNodeSelector +from madengine.core.errors import ConfigurationError +from madengine.core.image_digest import resolve_pinned_image from madengine.utils.gpu_config import resolve_runtime_gpus from madengine.utils.run_details import get_build_number, get_pipeline from madengine.utils.path_utils import scripts_base_dir_from @@ -336,6 +338,11 @@ def prepare(self) -> bool: return self._prepare_slurm_multi_script( model_info_peek, docker_image_name=model_keys_peek[0] ) + except ConfigurationError: + # Enforcement failures (e.g. --require-pinned-image with no recorded + # digest) are deliberate aborts, not peek errors. Falling through to + # the standard path here would silently generate an unpinned script. + raise except Exception: # Fall through to develop's standard flow on any peek error pass @@ -453,7 +460,29 @@ def _prepare_slurm_multi_script(self, model_info: Dict, docker_image_name: str = built_image = model_info["image"] self.console.print(f"[cyan]Using Docker image: {built_image}[/cyan]") env_vars["DOCKER_IMAGE_NAME"] = built_image - + + # Under require_pinned_image, pin DOCKER_IMAGE_NAME to the digest recorded + # at build time. slurm_multi runs the model's own script (no nested + # `madengine run` on the compute nodes), so enforcement has to happen here. + # Pinning the variable covers both the parallel `srun docker pull` below, + # which interpolates it, and the `docker run` inside the model script. + require_pinned = bool( + self.config.additional_context.get("require_pinned_image") + ) + if require_pinned and env_vars.get("DOCKER_IMAGE_NAME"): + image_entry = (self.manifest.get("built_images") or {}).get( + docker_image_name, {} + ) + env_vars["DOCKER_IMAGE_NAME"] = resolve_pinned_image( + env_vars["DOCKER_IMAGE_NAME"], + image_entry.get("image_digest"), + True, + model_name=model_info.get("name", ""), + ) + self.console.print( + f"[cyan]Pinned Docker image: {env_vars['DOCKER_IMAGE_NAME']}[/cyan]" + ) + # Get model args. The wrapper script below is executed by bash, so the # script name and free-form args string must be shell-quoted to prevent # embedded metacharacters ($(), backticks, ;, etc.) from being evaluated diff --git a/tests/unit/test_slurm_multi.py b/tests/unit/test_slurm_multi.py index de5c2eae..ae1fac97 100644 --- a/tests/unit/test_slurm_multi.py +++ b/tests/unit/test_slurm_multi.py @@ -28,6 +28,7 @@ from madengine.deployment.common import VALID_LAUNCHERS, is_self_managed_launcher, normalize_launcher from madengine.deployment.base import DeploymentConfig from madengine.deployment.slurm import SlurmDeployment +from madengine.core.errors import ConfigurationError # --------------------------------------------------------------------------- @@ -595,3 +596,90 @@ def test_below_minimum_nodes_raises(self, deployment_factory): deployment_factory._generate_sglang_disagg_command( nnodes=1, nproc_per_node=8, master_port=12345 ) + + +DIGEST = "sha256:" + "df36ef7e" * 8 + + +class TestSlurmMultiRequirePinnedImage: + """slurm_multi wrapper pins DOCKER_IMAGE_NAME (and thus the pull) when required.""" + + IMAGE_KEY = "rocm/pytorch-private:sglang_disagg_mori_20260502" + + def _deployment(self, tmp_path, require_pinned, image_digest): + script_rel = PR186_MODEL_ENTRY["scripts"] + script_abs = tmp_path / script_rel + script_abs.parent.mkdir(parents=True, exist_ok=True) + script_abs.write_text("#!/bin/bash\n# placeholder\n") + + image_entry = { + "image_name": self.IMAGE_KEY, + "docker_image": self.IMAGE_KEY, + "registry_image": self.IMAGE_KEY, + } + if image_digest: + image_entry["image_digest"] = image_digest + + manifest = { + "built_images": {self.IMAGE_KEY: image_entry}, + "built_models": {self.IMAGE_KEY: PR186_MODEL_ENTRY}, + "context": { + "docker_env_vars": {}, + "docker_mounts": {}, + "docker_build_arg": {}, + "gpu_vendor": "AMD", + "guest_os": "UBUNTU", + "docker_gpus": "all", + }, + } + manifest_path = tmp_path / "build_manifest.json" + manifest_path.write_text(json.dumps(manifest)) + + additional_context = { + "deploy": "slurm", + "gpu_vendor": "AMD", + "guest_os": "UBUNTU", + "slurm": dict( + PR186_MODEL_ENTRY["slurm"], output_dir=str(tmp_path / "slurm_results") + ), + "distributed": PR186_MODEL_ENTRY["distributed"], + } + if require_pinned: + additional_context["require_pinned_image"] = True + + cfg = DeploymentConfig( + target="slurm", + manifest_file=str(manifest_path), + additional_context=additional_context, + ) + return SlurmDeployment(cfg) + + def test_default_exports_tag(self, tmp_path): + dep = self._deployment(tmp_path, require_pinned=False, image_digest=DIGEST) + assert dep.prepare() is True + script_text = Path(dep.script_path).read_text() + assert f"export DOCKER_IMAGE_NAME={shlex.quote(self.IMAGE_KEY)}" in script_text + assert DIGEST not in script_text + + def test_enabled_exports_pinned_reference(self, tmp_path): + dep = self._deployment(tmp_path, require_pinned=True, image_digest=DIGEST) + assert dep.prepare() is True + script_text = Path(dep.script_path).read_text() + + pinned = f"rocm/pytorch-private@{DIGEST}" + assert f"export DOCKER_IMAGE_NAME={shlex.quote(pinned)}" in script_text + # The parallel pull interpolates the same value, so it is pinned too. + assert f"docker pull {pinned}" in script_text + + def test_enabled_without_digest_does_not_silently_fall_through(self, tmp_path): + """A missing digest must abort, not quietly take the standard template path. + + prepare()'s launcher peek wraps the slurm_multi dispatch in a bare + `except Exception: pass`. Without the re-raise, a ConfigurationError + here would be swallowed and prepare() would generate an ordinary + (unpinned) sbatch script instead — the exact silent degradation the + flag exists to prevent. + """ + dep = self._deployment(tmp_path, require_pinned=True, image_digest=None) + with pytest.raises(ConfigurationError): + dep.prepare() From be6a2d401a361a7ab13989cae555beb3aa4ec2ee Mon Sep 17 00:00:00 2001 From: Stephen Shao Date: Thu, 27 Aug 2026 21:19:19 -0500 Subject: [PATCH 14/16] test(execution): cover log naming for digest-pinned image references Characterization tests: _docker_image_ref_for_log_naming already strips @sha256:..., so pinned references produce the same log/tar filenames as tags. Locks that in against future refactors. Co-Authored-By: Claude Opus 5 --- tests/unit/test_execution.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/tests/unit/test_execution.py b/tests/unit/test_execution.py index dc18121e..b6c844a3 100644 --- a/tests/unit/test_execution.py +++ b/tests/unit/test_execution.py @@ -88,6 +88,19 @@ def test_non_ci_tag_sanitizes_full_ref(self): def test_short_ci_tag_unchanged(self): assert _docker_image_ref_for_log_naming("ci-model_ubuntu") == "ci-model_ubuntu" + def test_pinned_reference_names_same_as_untagged_reference(self): + digest = "sha256:" + "df36ef7e" * 8 + assert _docker_image_ref_for_log_naming( + f"registry/ns/myimg@{digest}" + ) == _docker_image_ref_for_log_naming("registry/ns/myimg") + + def test_pinned_ci_reference_still_yields_tag(self): + digest = "sha256:" + "df36ef7e" * 8 + assert ( + _docker_image_ref_for_log_naming(f"rocm/ns/img:ci-m_model_df@{digest}") + == "ci-m_model_df" + ) + class TestMakeRunLogFilePath: """make_run_log_file_path behavior.""" From 102c2166a9946a78b6fafbb188d908192b1dec40 Mon Sep 17 00:00:00 2001 From: Stephen Shao Date: Thu, 27 Aug 2026 21:20:48 -0500 Subject: [PATCH 15/16] docs: document --require-pinned-image and image digest capture Co-Authored-By: Claude Opus 5 --- docs/cli-reference.md | 1 + docs/configuration.md | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/docs/cli-reference.md b/docs/cli-reference.md index 0e9c869b..adffab03 100644 --- a/docs/cli-reference.md +++ b/docs/cli-reference.md @@ -230,6 +230,7 @@ madengine run [OPTIONS] | `--keep-model-dir` | | FLAG | `False` | Keep model directory after run (local Docker only; ignored with a warning on SLURM/K8s) | | `--clean-docker-cache` | | FLAG | `False` | Rebuild images without using cache (full workflow) | | `--skip-model-run` | | FLAG | `False` | Skip the model script inside each container. The container still starts and `pre_scripts` still run; only the model script invocation is skipped (status reported as `SKIPPED`, exit code `0`). Combine with `--keep-alive` to leave a live container for manual exec. Ignored with a warning on SLURM/K8s targets. See [Usage — Skip model run](usage.md#skip-model-run-after-build). | +| `--require-pinned-image` | | FLAG | `False` | Pull registry images by the `sha256` digest recorded in the build manifest (`repo@sha256:...`) instead of by tag, so a tag that moved between build and run fails loudly instead of silently running a different image. Fails immediately — with no tag fallback — if the manifest has no digest for an image. Equivalent to the `require_pinned_image` additional-context key. See [Configuration — Pinned image digests](configuration.md#pinned-image-digests). | | `--manifest-output` | | TEXT | `build_manifest.json` | Output file for build manifest (full workflow) | | `--summary-output` | `-s` | TEXT | `None` | Output file for summary JSON | | `--live-output` | `-l` | FLAG | `False` | Print output in real-time | diff --git a/docs/configuration.md b/docs/configuration.md index f3f46d29..38f49e89 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -120,6 +120,44 @@ madengine run --tags my_unit_test_suite \ Disabling the scan does **not** change performance metric extraction from the log; it only affects the post-hoc grep used to set `has_errors` for status. +## Pinned image digests + +Every build records the digest of the image it pushes as `image_digest` on each +`built_images` entry in `build_manifest.json`. This capture is always on and +costs nothing: by default the digest is carried along and never used. + +Pass `--require-pinned-image` (or set `"require_pinned_image": true` in +`--additional-context`) to make the run phase pull `repo@sha256:...` instead of +the tag: + +```bash +madengine run --manifest-file build_manifest.json --require-pinned-image + +# Equivalent, for pipelines that drive madengine through additional context +madengine run --manifest-file build_manifest.json \ + --additional-context "{'require_pinned_image': True}" +``` + +| Behaviour | Flag absent (default) | Flag set | +|---|---|---| +| Registry pull | By tag | By digest (`repo@sha256:...`) | +| Manifest has no `image_digest` | Pull by tag | **Fails immediately**, no tag fallback | +| Tag moved since the build | Silently runs the newer image | Registry rejects the pull (`manifest unknown`) | + +Applies to all three execution paths: local Docker, Kubernetes (the pod spec +`image` field), and SLURM. On SLURM the setting is written into the manifest's +`context` block so the nested `madengine run` on each compute node inherits it. + +**Limitations** + +- This does not prevent two concurrent builds from racing to push the same + mutable tag. It converts the resulting silent wrong-image run into a fast, + clear failure. Eliminating the race requires unique tags per build in the + calling CI pipeline. +- Manifests produced by the build-on-compute-node path (SLURM batch builds, + which push from inside a generated sbatch script) carry no `image_digest`. + Runs against those manifests fail fast when the flag is set. + ## System environment collection (rocEnvTool) Before each container run, madengine appends `scripts/common/pre_scripts/run_rocenv_tool.sh` to the model's pre-scripts. It captures a CSV snapshot of the container's environment (OS, CPU, GPU, ROCm/CUDA, packages, env vars, NUMA). The output ends up at `_env.csv` in the workspace root. From 0455477327f89ac6eaa191aa0eaa919883d12f72 Mon Sep 17 00:00:00 2001 From: Stephen Shao Date: Thu, 27 Aug 2026 21:45:01 -0500 Subject: [PATCH 16/16] fix(run): enforce pinned digest on build-on-compute-node manifests The build-on-compute-node path writes built_images entries carrying both a truthy local_image and a registry reference in docker_image. That branch runs before the registry branch in run_models_from_manifest, so --require-pinned-image was silently a no-op for those manifests -- the exact bypass the flag exists to prevent, and contrary to the documented fail-fast behaviour. Resolve the pin in the local_image branch too. resolve_pinned_image now passes through references that are already digest-pinned, so an explicitly pinned MAD_CONTAINER_IMAGE is accepted rather than rejected for lacking a manifest digest. Co-Authored-By: Claude Opus 5 --- docs/configuration.md | 1 + src/madengine/core/image_digest.py | 6 +++ src/madengine/execution/container_runner.py | 16 ++++++- tests/unit/test_container_runner.py | 53 +++++++++++++++++++++ tests/unit/test_image_digest.py | 6 +++ 5 files changed, 81 insertions(+), 1 deletion(-) diff --git a/docs/configuration.md b/docs/configuration.md index 38f49e89..6147c85c 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -142,6 +142,7 @@ madengine run --manifest-file build_manifest.json \ |---|---|---| | Registry pull | By tag | By digest (`repo@sha256:...`) | | Manifest has no `image_digest` | Pull by tag | **Fails immediately**, no tag fallback | +| Image reference is already `repo@sha256:...` | Used as-is | Used as-is (already pinned) | | Tag moved since the build | Silently runs the newer image | Registry rejects the pull (`manifest unknown`) | Applies to all three execution paths: local Docker, Kubernetes (the pod spec diff --git a/src/madengine/core/image_digest.py b/src/madengine/core/image_digest.py index bd0a8294..53c21c00 100644 --- a/src/madengine/core/image_digest.py +++ b/src/madengine/core/image_digest.py @@ -104,6 +104,12 @@ def resolve_pinned_image( return registry_image if not image_digest: + # An already-pinned reference (e.g. MAD_CONTAINER_IMAGE given as + # repo@sha256:...) satisfies the guarantee on its own; re-pinning it is + # a no-op and rejecting it would be wrong. + if parse_repo_digest(registry_image): + return registry_image + raise ConfigurationError( f"--require-pinned-image is set but the build manifest records no " f"image digest for model '{model_name}' (image: {registry_image}). " diff --git a/src/madengine/execution/container_runner.py b/src/madengine/execution/container_runner.py index c38261a6..4fe29816 100644 --- a/src/madengine/execution/container_runner.py +++ b/src/madengine/execution/container_runner.py @@ -2837,7 +2837,21 @@ def run_models_from_manifest( # Local image mode (MAD_CONTAINER_IMAGE): Use the provided image directly run_image = build_info.get("docker_image") self.rich_console.print(f"[yellow]🏠 Using local image: {run_image}[/yellow]") - + + # This branch also covers build-on-compute-node manifests, + # whose docker_image is a registry reference. Enforce here + # too, otherwise those manifests would silently bypass the + # flag by never reaching the registry branch below. + run_image = resolve_pinned_image( + run_image, + build_info.get("image_digest"), + bool( + (self.additional_context or {}).get("require_pinned_image") + ), + model_name=model_info.get("name", ""), + ) + + # Ensure the local image is available on this node. In a # multi-node SLURM run only the primary may have the # locally-built image; the shared-tar cache diff --git a/tests/unit/test_container_runner.py b/tests/unit/test_container_runner.py index 3e80d88c..1899cf5c 100644 --- a/tests/unit/test_container_runner.py +++ b/tests/unit/test_container_runner.py @@ -612,6 +612,59 @@ def test_enabled_without_digest_fails_before_pulling(self, _mock_csv): assert len(result["failed_runs"]) == 1 assert "require-pinned-image" in result["failed_runs"][0]["error"] + @patch("madengine.execution.container_runner.update_perf_csv") + def test_local_image_entry_with_registry_ref_is_not_a_bypass(self, _mock_csv): + """build_info["local_image"] must not smuggle an unpinned registry tag through. + + The build-on-compute-node path writes entries carrying BOTH a truthy + local_image and a registry reference in docker_image. That branch runs + before the registry branch, so without enforcement here the flag is + silently a no-op for those manifests. + """ + with tempfile.TemporaryDirectory() as tmpdir: + manifest_path = self._manifest( + tmpdir, + { + "local_image": "ci-m_ubuntu", + "docker_image": "myorg/ci:m", + "built_on_compute": True, + }, + ) + runner = self._runner() + runner.perf_csv_path = os.path.join(tmpdir, "perf.csv") + runner.additional_context = {"require_pinned_image": True} + + with patch.object( + runner, "_ensure_local_image_available" + ) as mock_ensure, patch.object(runner, "run_container") as mock_run: + result = runner.run_models_from_manifest( + manifest_file=manifest_path, timeout=60 + ) + + mock_ensure.assert_not_called() + mock_run.assert_not_called() + assert len(result["failed_runs"]) == 1 + assert "require-pinned-image" in result["failed_runs"][0]["error"] + + @patch("madengine.execution.container_runner.update_perf_csv") + def test_local_image_already_pinned_is_accepted(self, _mock_csv): + """An explicitly digest-pinned MAD_CONTAINER_IMAGE already meets the guarantee.""" + pinned = f"myorg/ci@{DIGEST}" + with tempfile.TemporaryDirectory() as tmpdir: + manifest_path = self._manifest( + tmpdir, {"local_image": True, "docker_image": pinned} + ) + runner = self._runner() + runner.perf_csv_path = os.path.join(tmpdir, "perf.csv") + runner.additional_context = {"require_pinned_image": True} + + with patch.object(runner, "_ensure_local_image_available"), patch.object( + runner, "run_container", return_value={"status": "SUCCESS"} + ) as mock_run: + runner.run_models_from_manifest(manifest_file=manifest_path, timeout=60) + + assert mock_run.call_args[1]["docker_image"] == pinned + @patch("madengine.execution.container_runner.update_perf_csv") def test_manifest_context_key_enables_enforcement(self, _mock_csv): """A nested run on a SLURM compute node inherits the setting via manifest context.""" diff --git a/tests/unit/test_image_digest.py b/tests/unit/test_image_digest.py index 6179e2b5..58e24f18 100644 --- a/tests/unit/test_image_digest.py +++ b/tests/unit/test_image_digest.py @@ -133,6 +133,12 @@ def test_enabled_with_empty_digest_raises(self): with pytest.raises(ConfigurationError): resolve_pinned_image("myorg/ci:mymodel", "", True, model_name="my_model") + def test_enabled_accepts_already_pinned_reference_without_manifest_digest(self): + # A reference the user pinned themselves already provides the guarantee, + # so there is nothing to look up and nothing to reject. + pinned = f"myorg/ci@{DIGEST}" + assert resolve_pinned_image(pinned, None, True) == pinned + def test_error_message_names_model_and_image(self): with pytest.raises(ConfigurationError) as excinfo: resolve_pinned_image("myorg/ci:mymodel", None, True, model_name="my_model")