CLI Reference¶
srtctl is the main command-line interface for submitting benchmark jobs to SLURM.
Table of Contents¶
- Quick Start
- Interactive Mode
- Example Browser
- Configuration Summary
- Interactive Actions Menu
- sbatch Preview
- Parameter Modification
- Sweep Preview
- Submission Confirmation
- Workflow Examples
- Commands
- srtctl apply
- srtctl dry-run
- srtctl resolve-override
- srtctl migrate
- srtctl monitor
- srtctl skill
- Output
- Sweep Support
- Config Override Support
- Tips
Quick Start¶
# Interactive mode - browse examples, preview, and submit
srtctl
# Submit a job directly
srtctl apply -f examples/sglang/sglang-router-disagg.yaml
# Deploy the recipe and keep its inference endpoint available until cancellation
srtctl apply -f examples/sglang/sglang-router-disagg.yaml --serve-only
# Preview without submitting
srtctl dry-run -f config.yaml
# Live dashboard - monitor all jobs in one place
srtctl monitor
Interactive Mode¶
Running srtctl with no arguments launches an interactive TUI (Text User Interface) powered by Rich and Questionary:
srtctl
# or explicitly:
srtctl -i
Interactive mode is ideal for: - Exploring curated examples without memorizing paths - Previewing and tweaking configurations before submission - Understanding what a sweep will expand to - Quick experimentation and validation
Example Browser¶
On launch, interactive mode scans the examples/ directory and presents curated configurations organized by subdirectory:
? Select an example:
── examples/sglang ──
dynamo-agg.yaml
dynamo-disagg.yaml
sglang-router-agg.yaml
sglang-router-disagg.yaml
── examples/vllm ──
dynamo-agg.yaml
...
──────────────
📁 Browse for file...
Features:
- Examples grouped by parent directory for easy navigation
- Arrow keys to navigate, Enter to select
- "Browse for file..." option for configs outside examples/
- If no examples are found, prompts for manual path entry
Configuration Summary¶
After selecting an example, you'll see a tree-style summary:
📋 Configuration
┌─────────────────────────────────────────────────────────────┐
│ deepseek-r1-1p4d │
└─────────────────────────────────────────────────────────────┘
deepseek-r1-1p4d
├── 📦 Model
│ ├── path: deepseek-r1
│ ├── container: latest
│ └── precision: fp8
├── 🖥️ Resources
│ ├── gpu_type: gb200
│ ├── prefill: 1 workers
│ ├── decode: 4 workers
│ └── gpus_per_node: 4
├── 📊 Benchmark
│ ├── type: sa-bench
│ ├── isl: 1024, osl: 1024
│ └── concurrencies: [128, 256, 512]
└── 🔄 Sweep Parameters (if present)
├── chunked_prefill_size: [4096, 8192]
└── max_total_tokens: [8192, 16384]
Interactive Actions Menu¶
After viewing the config summary, you'll see an action menu:
? What would you like to do?
🚀 Submit job(s) - Submit to SLURM cluster
👁️ Preview sbatch script - View generated SLURM script with syntax highlighting
✏️ Modify parameters - Interactively change values before submission
🔍 Dry-run - Full dry-run preview without submission
📁 Select different config - Choose a different example
❌ Exit - Exit interactive mode
sbatch Preview¶
The "Preview sbatch script" option shows the exact SLURM script that will be submitted:
┌─ Generated sbatch Script ────────────────────────────────────────────────────┐
│ 1 │ #!/bin/bash │
│ 2 │ #SBATCH --job-name=deepseek-r1-1p4d │
│ 3 │ #SBATCH --nodes=5 │
│ 4 │ #SBATCH --gpus-per-node=4 │
│ 5 │ #SBATCH --time=04:00:00 │
│ 6 │ #SBATCH --partition=batch │
│ 7 │ ... │
└──────────────────────────────────────────────────────────────────────────────┘
The script is syntax-highlighted with line numbers for easy reading.
Parameter Modification¶
The "Modify parameters" option lets you interactively change key settings:
Modify Configuration
Press Enter to keep current value, or type new value
? Job name [deepseek-r1-1p4d]: my-experiment
? Prefill workers [1]:
? Decode workers [4]: 8
? Input sequence length [1024]: 2048
? Output sequence length [1024]: 2048
Modifiable fields:
- name - Job name
- Prefill workers - Number of prefill workers (roles.prefill.workers in the recipe)
- Decode workers - Number of decode workers (roles.decode.workers in the recipe)
- benchmark.isl - Input sequence length
- benchmark.osl - Output sequence length
Modified configs are saved to a temporary file and used for submission.
Sweep Preview¶
For configs with a sweep: section, interactive mode shows an expansion table:
┌─ Sweep Jobs ────────────────────────────────────────────────────────────────┐
│ # │ Job Name │ Parameters │
├────┼────────────────────────────────────┼───────────────────────────────────┤
│ 1 │ deepseek-r1-1p4d_cps4096_mtt8192 │ chunked_prefill_size=4096, │
│ │ │ max_total_tokens=8192 │
│ 2 │ deepseek-r1-1p4d_cps4096_mtt16384 │ chunked_prefill_size=4096, │
│ │ │ max_total_tokens=16384 │
│ 3 │ deepseek-r1-1p4d_cps8192_mtt8192 │ chunked_prefill_size=8192, │
│ │ │ max_total_tokens=8192 │
│ 4 │ deepseek-r1-1p4d_cps8192_mtt16384 │ chunked_prefill_size=8192, │
│ │ │ max_total_tokens=16384 │
└─────────────────────────────────────────────────────────────────────────────┘
Total jobs: 4
Submission Confirmation¶
Before submitting, you'll be asked to confirm:
? Submit to SLURM? (y/N)
For sweeps, the confirmation shows: - Full configuration summary - Sweep expansion table - Total job count
Workflow Examples¶
Exploring a curated example:
$ srtctl
> Select: examples/sglang/sglang-router-disagg.yaml
> Action: 👁️ Preview sbatch script (review generated script)
> Action: 🔍 Dry-run (full dry-run)
> Action: 📁 Select different config (try another)
Quick experiment with modifications:
$ srtctl
> Select: examples/vllm/dynamo-agg.yaml
> Action: ✏️ Modify parameters
> Change decode workers: 8
> Change isl: 2048
> Action: 🚀 Submit job(s)
> Confirm: y
Sweep validation:
$ srtctl
> Select: configs/my-sweep.yaml
> View: Sweep table showing 16 jobs
> Action: 🔍 Dry-run (saves all expanded configs to dry-runs/)
> Review generated configs
> Action: 🚀 Submit job(s)
Commands¶
srtctl apply¶
Submit a job or sweep to SLURM.
srtctl apply -f <config.yaml> [options]
Options:
| Flag | Description |
|---|---|
-f, --file |
Path to YAML config file, directory, or file:selector for overrides (required) |
--sweep |
Force sweep mode (usually auto-detected) |
--setup-script |
Custom setup script from configs/ |
--tags |
Comma-separated tags for the run |
--serve-only |
Deploy the endpoint without running a benchmark; serve until cancellation |
--set KEY=VALUE |
Override one recipe value by dotted path before validation (repeatable). Also on dry-run, preflight, resolve-override |
--unset KEY |
Remove one recipe key by dotted path before validation (repeatable) |
-y, --yes |
Skip confirmation prompts |
--no-preflight |
Skip the pre-submit model.path / model.container / telemetry filesystem checks for this run. preflight: false in srtslurm.yaml does the same for every run on a cluster whose paths exist only on compute nodes |
--set and --unset are the supported way to tweak a recipe from a script instead of editing the YAML. Paths are dotted, [N] indexes a list, and quotes protect a segment that contains dots (container_mounts."/a/b.c"). Values parse as YAML: 720 is an int, "720" a string, [4, 8] a list; a mapping such as {"rope_type": "yarn"} stays a literal string because that is how engine flags take JSON. Overrides are applied to the raw document before cluster defaults, sweep expansion, and validation, so an explicit --set always wins and {placeholder} values still expand. On an override file the value is written into base and every override_* / zip_override_* variant, so no variant can shadow it. The applied overrides are listed in each --json record as applied_overrides, and the config.yaml copied into the job directory reflects them. The source file is never modified.
Examples:
# Submit single job
srtctl apply -f examples/sglang/sglang-router-disagg.yaml
# Tweak a recipe from a script without editing it
srtctl apply -f config.yaml --set health_check.max_attempts=720 --unset sbatch_directives.exclude
srtctl apply -f config.yaml --set 'roles.decode.args.speculative-config={"method": "eagle"}'
srtctl dry-run -f config.yaml --set benchmark.concurrencies=[4,8]
# Serve the same recipe without running its configured benchmark
srtctl apply -f examples/sglang/sglang-router-disagg.yaml --serve-only
# Submit sweep (auto-detected from sweep: section)
srtctl apply -f configs/my-sweep.yaml
# Submit all override variants (base + overrides)
srtctl apply -f config.yaml
# Submit only a specific override variant
srtctl apply -f config.yaml:override_tp64
# Submit only the base config (ignore overrides)
srtctl apply -f config.yaml:base
# With tags
srtctl apply -f config.yaml --tags "experiment-1,baseline"
--serve-only submits the recipe normally, waits until the configured workers and frontend are healthy, prints
the frontend URL in the sweep log, and keeps the service running until the job is cancelled or reaches its Slurm
time limit. It ignores the recipe's configured benchmark for that submission. Use scancel <job-id> to stop the
service; srtctl then cleans up the processes it started.
srtctl dry-run¶
Preview what would be submitted without actually submitting.
srtctl dry-run -f <config.yaml> [options]
Options:
| Flag | Description |
|---|---|
-f, --file |
Path to YAML config file, directory, or file:selector for overrides (required) |
--sweep |
Force sweep mode |
Examples:
# Preview single job - shows sbatch script
srtctl dry-run -f config.yaml
# Preview sweep - shows job table and saves configs
srtctl dry-run -f sweep-config.yaml
# Preview all override variants
srtctl dry-run -f override-config.yaml
# Preview a specific override variant
srtctl dry-run -f override-config.yaml:override_tp64
Dry-run output includes:
- Syntax-highlighted sbatch script
- Container mounts table (labeled by source: built-in, srtslurm.yaml, configuration)
- Environment variables table (grouped by scope: global, prefill, decode, aggregated)
- srun options (if configured)
- For sweeps: table of all jobs with parameters
- Generated configs saved to dry-runs/ folder
srtctl resolve-override¶
Expand an override config and write the specialised YAML file(s) without submitting.
srtctl resolve-override -f <config.yaml> [options]
Options:
| Flag | Description |
|---|---|
-f, --file |
Override YAML file, or file:selector to resolve a specific variant (required) |
--stdout |
Print resolved YAML to stdout instead of writing files |
Examples:
# Write all variants next to the source file
srtctl resolve-override -f config.yaml
# Write a single override variant
srtctl resolve-override -f config.yaml:override_lowmem
# Print to stdout
srtctl resolve-override -f config.yaml:override_lowmem --stdout
# Inspect a single zip variant
srtctl resolve-override -f config.yaml:zip_override_tp_sweep[0] --stdout
The resolved YAML preserves the field order and comments from the source file. Base fields appear first in their original order; override-only fields are appended at the end. Output files follow the same {stem}_{suffix}.yaml naming convention used by apply.
See Config Overrides — Resolving Without Submitting for details.
srtctl migrate¶
Rewrites a v1 recipe (no schema: 2; backend:, backend.<mode>_environment, infra:, resources.<role>_nodes / _workers / gpus_per_<role>, dynamo.version / hash / wheel) into the 2.0 layout. The rewrite is deterministic and keeps comments and key order; do not translate by hand.
srtctl migrate -f old.yaml # print the schema-2 document, file untouched
srtctl migrate -f old.yaml --in-place # rewrite it; a directory is walked recursively
srtctl migrate -f old.yaml --output new.yaml
srtctl migrate -f old.yaml --verify # migrate in memory and prove v1 and v2 resolve identically
The key-by-key mapping is in legacy-v1.md. Notable rewrites: decode_nodes: 0 becomes roles.decode.nodes: colocate with an explicit gpus on both roles; v1 frontend.type: sglang (the router) becomes sglang-router; infra becomes services: entries; benchmark fields the recipe's type never reads are removed because schema 2 rejects them. The migrator prints a note for each change and for what it deliberately leaves to you: dynamo.top_of_tree (pin a commit in source.rev), a dedicated etcd node under a frontend that runs no etcd, and a v1 recipe that never named a Dynamo to install (v1 pip-installed PyPI 0.8.0 implicitly; choose dynamo.source or dynamo.install: false). Finish with --verify and a dry-run.
srtctl monitor¶
Live terminal dashboard for all your jobs. See Monitoring for full documentation.
srtctl monitor # Active + recently completed jobs
srtctl monitor --all # Include older jobs from outputs/
srtctl monitor --interval 10 # Refresh every 10s (default: 5)
srtctl monitor --once # Snapshot and exit
srtctl monitor --resume KEY # Resume a previous session
srtctl skill¶
Install the in-package agent skill, one document that teaches a coding agent how to drive srtctl (the 2.0 recipe shape, dry-run before apply, where a run's logs and artifacts live, the MCP tools):
srtctl skill --target claude # .claude/skills/srtctl/SKILL.md
srtctl skill --target codex # .codex/skills/srtctl/SKILL.md
srtctl skill --target cursor # .cursor/rules/srtctl.mdc
srtctl skill --target claude --root /path/to/project
srtctl skill --target claude --print # to stdout
srtctl-mcp¶
The MCP server (srtctl-mcp, stdio by default, SRTCTL_MCP_TRANSPORT=streamable-http with SRTCTL_MCP_HOST / SRTCTL_MCP_PORT for HTTP) exposes the schema tools anywhere and the job lifecycle tools (submit_job, dry_run, job_status, job_logs, list_jobs, cancel_job) when it runs on a Slurm login node in a checkout with srtslurm.yaml. job_status returns the Slurm accounting row, the job metadata, the orchestrator's current stage, any [ERROR] lines, the benchmark rollup, and the sweep-log tail; job_logs lists outputs/<job_id>/logs or tails one file.
Output¶
When you submit a job, srtctl creates an output directory:
outputs/<job_id>/
├── config.yaml # Copy of submitted config
├── sbatch_script.sh # Generated SLURM script
└── <job_id>.json # Job metadata
Sweep Support¶
Configs with a sweep: section are automatically detected and expanded:
sweep:
chunked_prefill_size: [4096, 8192]
max_total_tokens: [8192, 16384]
This creates 4 jobs (2 × 2 Cartesian product). See Parameter Sweeps for details.
Config Override Support¶
Configs with a base top-level key are automatically detected as override configs. Each override_<suffix> section is deep-merged with base and submitted as a separate job.
# Submit all variants (base + all overrides)
srtctl apply -f override-config.yaml
# Submit only the tp64 override variant
srtctl apply -f override-config.yaml:override_tp64
# Submit only the base (ignoring overrides)
srtctl apply -f override-config.yaml:base
The :selector syntax works with apply, dry-run, and resolve-override. If the selector is used on a non-override config, a warning is logged and the config is processed normally.
Override configs also work with directory submission — override files in the directory are auto-detected and expanded.
To inspect the resolved YAML before submitting (preserving field order and comments), use resolve-override:
srtctl resolve-override -f override-config.yaml --stdout
See Config Overrides for full YAML syntax, merge semantics, and field-order / comment-preservation behaviour.
Debugging Running Jobs¶
The full srun command (with all container mounts, environment variables, and flags) is logged at INFO level in the sweep log:
# Find the full srun commands for a running job
grep "srun command" outputs/<job_id>/logs/sweep_<job_id>.log
# Per-worker env vars and inner commands are also logged
grep -E "Env:|Command:" outputs/<job_id>/logs/sweep_<job_id>.log
Tips¶
- Use
srtctl(no args) for exploring curated examples interactively - Use
srtctl apply -ffor scripting and CI pipelines - Always
dry-runfirst for sweeps to check job count - Check
outputs/<job_id>/for submitted configs and metadata