Skip to content

fix(deployment): validate launcher names instead of silently defaulting - #174

Open
coketaste wants to merge 6 commits into
developfrom
fix/launcher-validation
Open

fix(deployment): validate launcher names instead of silently defaulting#174
coketaste wants to merge 6 commits into
developfrom
fix/launcher-validation

Conversation

@coketaste

Copy link
Copy Markdown
Collaborator

Summary

  • normalize_launcher() silently mapped any unrecognized launcher spelling to the docker/native sentinel, so a typo'd launcher ran as a single-process job and reported success instead of failing. This masked a real bug: the documented megatron-lm spelling was broken on both SLURM and Kubernetes because their dispatch arms compared against the literal "megatron".
  • Replaces normalization with validate_launcher(), which accepts exactly one canonical spelling per launcher (plus the documented slurm-multi hyphen alias) and raises ConfigurationError with did-you-mean suggestions for anything else. Enforced at two chokepoints — cli/validators.py (CLI additional_context) and BaseDeployment.__init__ (manifest and model-card sources) — covering all three config sources on both backends.
  • Fixes the megatron-lm dispatch arms in slurm.py and k8s_template_context.py, and adds sglang-disagg to the Ray GPU-visibility guard in both job templates (it was missing and would otherwise hit Ray's "Inconsistent values found" error).
  • Consolidates three divergent launcher-read implementations in container_runner.py into one _resolve_launcher() helper.
  • Corrects the megatronmegatron-lm launcher value and drops the dead distributed.master_port key across affected example configs (examples/k8s-configs/, examples/slurm-configs/).

Split out of coketaste/docs-rewrite — this is a behavior change (new validation error path) and belongs in its own review rather than riding along with a docs rewrite. The docs branch depends on this landing first (it documents the megatron-lm spelling and the validation error text).

Test plan

  • pytest tests/unit — 646 passed
  • New tests/unit/test_launcher_dispatch.py, parametrized over every valid launcher on both backends, asserts each reaches a real dispatch arm rather than falling through silently
  • tests/unit/test_validators.py, tests/unit/test_deployment.py, tests/unit/test_slurm_multi.py, tests/unit/test_container_runner.py updated/passing

🤖 Generated with Claude Code

coketaste and others added 2 commits August 19, 2026 20:00
…t key

The launcher field must be "megatron-lm" per VALID_LAUNCHERS in
deployment/common.py — "megatron" silently falls through to the Docker
fallback on SLURM. Also removes distributed.master_port, which is never
read (SLURM reads distributed.port; K8s reads a separate top-level
launcher.master_port object), across all affected example configs.
normalize_launcher() mapped any unrecognized launcher spelling to the
docker/native sentinel with no error, so a typo'd launcher silently ran
as a single-process job and reported success. This hid a real bug: the
documented megatron-lm spelling was broken on both SLURM and Kubernetes
because their dispatch arms compared against the literal "megatron".

Replace normalization with validate_launcher(), which accepts exactly
one canonical spelling per launcher (plus the documented slurm-multi
hyphen alias) and raises ConfigurationError with did-you-mean
suggestions for anything else. Validation is enforced at two
chokepoints - cli/validators.py (CLI additional_context) and
BaseDeployment.__init__ (manifest and model-card sources) - so all
three config sources are covered on both backends.

Also fixes the megatron-lm dispatch arms in slurm.py and
k8s_template_context.py, and adds sglang-disagg to the Ray
GPU-visibility guard in both job templates, which it had been missing
from and would otherwise hit Ray's "Inconsistent values found" error.

Consolidates three divergent launcher-read implementations in
container_runner.py into one _resolve_launcher() helper.

Adds tests/unit/test_launcher_dispatch.py, parametrized over every
valid launcher on both backends, asserting each reaches a real
dispatch arm rather than falling through silently.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Copilot AI lite review requested due to automatic review settings August 20, 2026 01:17

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR tightens distributed launcher handling across madengine’s deployment paths by replacing permissive launcher normalization with explicit validation, fixing megatron-lm dispatch on SLURM/Kubernetes, and aligning templates/tests so invalid launcher spellings fail loudly instead of silently running single-process jobs.

Changes:

  • Replace silent launcher defaulting with validate_launcher() + enforce validation at CLI (cli/validators.py) and deployment initialization (BaseDeployment.__init__).
  • Fix backend dispatch/template logic for megatron-lm (previously compared against "megatron"), and extend Ray GPU-visibility guards to include sglang-disagg.
  • Consolidate container-runner launcher resolution and update/add unit tests and example configs to use canonical launcher/port keys.

Reviewed changes

Copilot reviewed 34 out of 34 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
tests/unit/test_validators.py Adds CLI-boundary launcher validation tests (accept/reject/canonicalize).
tests/unit/test_slurm_multi.py Updates tests to validate_launcher() and asserts unknown launchers now error.
tests/unit/test_launcher_dispatch.py New parity/dispatch tests to ensure each valid launcher reaches a real dispatch arm.
tests/unit/test_deployment.py Replaces normalize_launcher tests with launcher_for_reporting + validate_launcher tests.
tests/unit/test_container_runner.py Updates self-managed launcher tests to match canonical spellings only.
src/madengine/execution/container_runner.py Consolidates launcher lookup into _resolve_launcher() and drops alias canonicalization.
src/madengine/deployment/templates/slurm/job.sh.j2 Adds sglang-disagg to Ray GPU guard; switches megatronmegatron-lm in templated checks.
src/madengine/deployment/templates/kubernetes/job.yaml.j2 Adds sglang-disagg to Ray GPU guard; updates distributed-env block to megatron-lm.
src/madengine/deployment/slurm.py Removes normalization/canonicalization; fixes megatron-lm dispatch; uses launcher_for_reporting.
src/madengine/deployment/k8s_template_context.py Removes alias canonicalization; fixes megatron-lm dispatch arms; treats sglang-disagg as canonical.
src/madengine/deployment/k8s_results.py Uses launcher_for_reporting in results collection paths.
src/madengine/deployment/common.py Introduces validate_launcher() and launcher_for_reporting(); updates self-managed detection.
src/madengine/deployment/base.py Validates/canonicalizes launchers in __init__ across additional_context, manifest, and model cards.
src/madengine/cli/validators.py Adds _validate_launcher_after_defaults() to fail fast on invalid launcher values.
examples/slurm-configs/README.md Updates documented launcher list and examples to megatron-lm.
examples/slurm-configs/minimal/megatron-lm-minimal.json Updates launcher to megatron-lm.
examples/slurm-configs/basic/09-megatron-lm-multi-node.json Updates launcher to megatron-lm; switches master_portport.
examples/k8s-configs/README.md Updates docs to megatron-lm in launcher lists/descriptions.
examples/k8s-configs/minimal/megatron-lm-optimized.json Updates launcher to megatron-lm; switches master_portport.
examples/k8s-configs/minimal/megatron-lm-minimal.json Updates launcher to megatron-lm.
examples/k8s-configs/minimal/megatron-lm-exclude-node.json Updates launcher to megatron-lm.
examples/k8s-configs/basic/vllm-multi-node-basic.json Switches master_portport.
examples/k8s-configs/basic/torchtitan-multi-node-basic.json Switches master_portport.
examples/k8s-configs/basic/sglang-multi-node-basic.json Switches master_portport.
examples/k8s-configs/basic/sglang-disagg-multi-node-basic.json Switches master_portport.
examples/k8s-configs/basic/sglang-disagg-custom-split.json Switches master_portport.
examples/k8s-configs/basic/megatron-lm-multi-node-basic.json Updates launcher to megatron-lm; switches master_portport.
examples/k8s-configs/basic/06-data-provider-with-pvc.json Switches master_portport.
examples/k8s-configs/basic/05-torchrun-nvidia-gpu-example.json Switches master_portport.
examples/k8s-configs/basic/04-torchrun-multi-node-advanced.json Switches master_portport.
examples/k8s-configs/basic/03-torchrun-multi-node-basic.json Switches master_portport.
examples/k8s-configs/basic/02-torchrun-single-node-multi-gpu.json Switches master_portport.
examples/k8s-configs/basic/02-torchrun-single-node-multi-gpu-tools.json Switches master_portport.
docs/deployment.md Updates supported launcher spelling to megatron-lm.
Suppressed comments (1)

src/madengine/deployment/common.py:83

  • validate_launcher() trims whitespace into normalized, but whitespace-only values (e.g. " ") still fall through to the "unknown launcher" error. This contradicts the function docstring (“Empty values mean 'no launcher configured'”) because after stripping, the value is empty and should be treated as unset.
    normalized = launcher.strip().lower()
    if normalized in _LAUNCHER_SENTINELS:
        return normalized
    normalized = _DOCUMENTED_ALIASES.get(normalized, normalized)
    if normalized in VALID_LAUNCHERS:
        return normalized


💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/madengine/deployment/k8s_results.py
Comment thread src/madengine/deployment/common.py
@coketaste coketaste self-assigned this Aug 20, 2026
Copilot AI review requested due to automatic review settings September 1, 2026 20:18

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 34 out of 34 changed files in this pull request and generated 2 comments.

Suppressed comments (1)

src/madengine/deployment/k8s_results.py:135

  • k8s_results treats only vllm/sglang as Ray-based (is_ray_launcher), but the templates and tests now treat sglang-disagg as Ray-based too. Leaving it out will skip the Ray multi-node handling branches for sglang-disagg jobs.
            launcher_type = launcher_for_reporting(launcher_type, "kubernetes")

            is_ray_launcher = launcher_type in ["vllm", "sglang"]

Comment thread src/madengine/deployment/common.py Outdated
Comment thread tests/unit/test_slurm_multi.py Outdated
Address review on #174:

- validate_launcher() no longer accepts the reporting sentinels
  docker/native. They are perf.csv output values with no dispatch arm, so
  accepting one at a config boundary let it fall through to the
  unknown-launcher default — the silent single-process run this validation
  exists to prevent. The error names the sentinel and says to omit the key.
- validate_launcher() treats only None and blank strings as "no launcher
  configured". Other falsy values (0, False, []) are misconfiguration and
  now raise instead of passing silently via `if not launcher`.
- k8s result collection classifies per-replica launchers through
  is_per_replica_launcher() in common.py rather than an inline list.
  sglang-disagg is deliberately excluded: it is Ray-based (hence the
  template GPU-visibility guard) but its nodes are proxy/prefill/decode
  roles serving one endpoint, so scaling its throughput by nnodes would
  multiply a whole-cluster number.
- Drop duplicate ConfigurationError import in tests/unit/test_slurm_multi.py.

pytest tests/unit — 722 passed

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings September 1, 2026 21:50

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

Several Kubernetes example configs now set distributed.port, but the Kubernetes backend reads the master port from launcher.master_port, making the updated examples misleading as written.

Review details

Suppressed comments (13)

Previously missed (13) — in code that hasn't changed since the last review.

examples/k8s-configs/basic/02-torchrun-single-node-multi-gpu-tools.json:31

  • On Kubernetes, the rendezvous/master port is read from the top-level launcher.master_port object (see k8s_template_context.py), not from distributed.port. Keeping distributed.port in a K8s example suggests it’s configurable there when it’s currently ignored.
    examples/k8s-configs/basic/02-torchrun-single-node-multi-gpu.json:30
  • On Kubernetes, the rendezvous/master port is read from the top-level launcher.master_port object (see k8s_template_context.py), not from distributed.port. Keeping distributed.port in a K8s example suggests it’s configurable there when it’s currently ignored.
    examples/k8s-configs/basic/03-torchrun-multi-node-basic.json:31
  • The distributed.port field in this Kubernetes example is not used by the Kubernetes backend; k8s_template_context.py reads the master port from the top-level launcher.master_port object instead (see docs/launchers.md note). As written, this example implies distributed.port is honored on Kubernetes when it is effectively dead config, which can mislead users trying to change the rendezvous port.
    examples/k8s-configs/basic/04-torchrun-multi-node-advanced.json:57
  • On Kubernetes, the rendezvous/master port is read from the top-level launcher.master_port object (see k8s_template_context.py), not from distributed.port. Keeping distributed.port in a K8s example suggests it’s configurable there when it’s currently ignored.
    examples/k8s-configs/basic/05-torchrun-nvidia-gpu-example.json:34
  • On Kubernetes, the rendezvous/master port is read from the top-level launcher.master_port object (see k8s_template_context.py), not from distributed.port. Keeping distributed.port in a K8s example suggests it’s configurable there when it’s currently ignored.
    examples/k8s-configs/basic/06-data-provider-with-pvc.json:39
  • On Kubernetes, the rendezvous/master port is read from the top-level launcher.master_port object (see k8s_template_context.py), not from distributed.port. Keeping distributed.port in a K8s example suggests it’s configurable there when it’s currently ignored.
    examples/k8s-configs/basic/megatron-lm-multi-node-basic.json:24
  • On Kubernetes, the rendezvous/master port is read from the top-level launcher.master_port object (see k8s_template_context.py), not from distributed.port. Keeping distributed.port in a K8s example suggests it’s configurable there when it’s currently ignored.
    examples/k8s-configs/basic/sglang-disagg-custom-split.json:33
  • On Kubernetes, the rendezvous/master port is read from the top-level launcher.master_port object (see k8s_template_context.py), not from distributed.port. Keeping distributed.port in a K8s example suggests it’s configurable there when it’s currently ignored.
    examples/k8s-configs/basic/sglang-disagg-multi-node-basic.json:31
  • On Kubernetes, the rendezvous/master port is read from the top-level launcher.master_port object (see k8s_template_context.py), not from distributed.port. Keeping distributed.port in a K8s example suggests it’s configurable there when it’s currently ignored.
    examples/k8s-configs/basic/sglang-multi-node-basic.json:25
  • On Kubernetes, the rendezvous/master port is read from the top-level launcher.master_port object (see k8s_template_context.py), not from distributed.port. Keeping distributed.port in a K8s example suggests it’s configurable there when it’s currently ignored.
    examples/k8s-configs/basic/torchtitan-multi-node-basic.json:25
  • On Kubernetes, the rendezvous/master port is read from the top-level launcher.master_port object (see k8s_template_context.py), not from distributed.port. Keeping distributed.port in a K8s example suggests it’s configurable there when it’s currently ignored.
    examples/k8s-configs/basic/vllm-multi-node-basic.json:25
  • On Kubernetes, the rendezvous/master port is read from the top-level launcher.master_port object (see k8s_template_context.py), not from distributed.port. Keeping distributed.port in a K8s example suggests it’s configurable there when it’s currently ignored.
    examples/k8s-configs/minimal/megatron-lm-optimized.json:35
  • On Kubernetes, the rendezvous/master port is read from the top-level launcher.master_port object (see k8s_template_context.py), not from distributed.port. Keeping distributed.port in a K8s example suggests it’s configurable there when it’s currently ignored.
  • Files reviewed: 34/34 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants