Skip to content

CLI Guide

srtctl is the main command-line interface for submitting benchmark jobs to SLURM. This page covers workflows and behavior; every subcommand's arguments, with their defaults, are generated from the parser into the CLI Reference.

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

# Pre-generate SA-Bench datasets so the benchmark job starts measuring right away
srtctl cache-inputs -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]

Every argument: CLI Reference.

--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]

Every argument: CLI Reference.

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 cache-inputs

Pre-generate the datasets a recipe's benchmark run would build, so the benchmark job spends its GPU time measuring instead of tokenizing.

srtctl cache-inputs -f <config.yaml> [options]

The command asks the recipe's benchmark runner what it can build ahead of time; a benchmark that has nothing to pre-generate says so and nothing runs. SA-Bench is the one that does today, and it requires benchmark.dataset_cache_dir in the recipe — that is where the datasets are written, and where the benchmark looks for them. See Dataset caching.

Options:

Flag Description
-f, --file Recipe YAML file (required)
--dry-run Print the datasets and the srun command, then exit
--account SLURM account (default: recipe slurm.account)
--partition SLURM partition (default: recipe slurm.partition)
--time SLURM time limit (default: recipe slurm.time_limit)
--num-workers Worker processes for prompt generation (default: min(cpu_count, 8))

Examples:

# Build every dataset the recipe needs
srtctl cache-inputs -f disagg.yaml

# See what it would build first
srtctl cache-inputs -f disagg.yaml --dry-run

# Long sweep on a wide node
srtctl cache-inputs -f disagg.yaml --time 4:00:00 --num-workers 32

One dataset is built per prompt count the run asks for. Warmup and measured runs each get their own file because num_prompts changes how many draws come out of the seeded RNG, so a recipe with concurrencies: "64x128", num_prompts_mult: 8 and num_warmup_mult: 1 produces four datasets: n=64, n=512, n=128, n=1024. Counts that repeat across concurrency levels are built once.

The command allocates a single node with no GPUs and runs only the prompt build in the recipe's container, so it can run while the cluster is busy with the jobs it is preparing for. Run from inside an existing allocation it attaches to it instead of queueing again.

Re-running is cheap and safe: datasets already in the cache are verified and skipped, and only missing ones are generated. That makes this the fastest way to resume after a time limit cut a long prewarm short.

srtctl render

Write the exact sbatch script srtctl apply would submit, without submitting it.

srtctl render -f <config.yaml> --to <dir> [--serve-only] [--setup-script NAME] [--no-preflight]

For launchers that must own the sbatch call themselves — for example a harness whose contract is that its launch script ends in exec sbatch --parsable ... and reads the job id from that one line. render gives such a launcher srtctl's orchestration without srtctl's submit.

The script is self-contained: apply copies the recipe into outputs/<job_id>/ after sbatch hands back the id, which no one does for a rendered script, so the rendered script copies its recipe from --to <dir> into its own output directory at job start. --to <dir> receives sbatch_script.sh, config.yaml (the recipe as given), config_<variant>.yaml for an override variant, the git-state snapshot of any mounted checkouts, and render.json: what the launcher needs to know about the job before it exists — total_nodes, frontend_node_index and client_node_index (positions in the allocation's scontrol show hostnames order, computed with the orchestrator's own node-carving rules; null for heterogeneous jobs), frontend_port, served_model_name, benchmark_type. The directory must stay in place until the job has started.

Once every configured worker has passed the health gate, the job writes <log_dir>/server_ready.json (ready_at_unix). A launcher driving a manual job with its own client should wait for that file rather than for the frontend alone: a Dynamo frontend lists the model as soon as its first worker registers.

Prose goes to stderr; the last line of stdout is the script path, so the whole thing composes into one submit line:

exec sbatch --parsable --output=/path/serve.log "$(srtctl render -f recipe.yaml --to /path/render)"

Only a single recipe (or one override variant via file:selector) can be rendered; sweeps, directories and unselected override files are refused. Cluster defaults (srtslurm.yaml: account, partition, container and model aliases, --segment) apply exactly as for apply, and are found the same way: srtslurm.yaml in the working directory (or its two parents), or the file SRTSLURM_CONFIG points at. A launcher that runs render from somewhere else should set SRTSLURM_CONFIG.

Every argument: CLI Reference.

srtctl resolve-override

Expand an override config and write the specialised YAML file(s) without submitting.

srtctl resolve-override -f <config.yaml> [options]

Every argument: CLI Reference.

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 pre-2.0 (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. Such a recipe no longer loads: srtctl apply and srtctl dry-run reject it with a pointer to this command. 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

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: 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). dynamo.top_of_tree is kept as is; it has no immutable equivalent under dynamo.source, so pin a commit in source.rev when you can. Finish with 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 bake-image

Preinstall dynamo, the sa-bench Python packages, a recipe setup_script, and/or a vLLM unified diff into a new container image, so jobs stop paying for the same work on every run.

srtctl bake-image --image my-model-fp8 --dynamo 1.2.0.dev20260526 --sa-bench
srtctl bake-image --image my-model-fp8 --script install-nixl-dsv4.sh
srtctl bake-image --image my-model-fp8 --patch mine.diff

No allocation needed: srun requests a node itself, taking default_account and default_partition from srtslurm.yaml. Run it inside an existing salloc and it joins that allocation instead, skipping the queue.

Flag Description
--image Source .sqsh path, or a container alias from srtslurm.yaml
-o, --output Output path (default: source name plus what was installed)
--dynamo VERSION ai-dynamo version to pip-install
--sa-bench Install the packages sa-bench needs at runtime
--script, --setup-script Run a configs/ setup script inside the image (same as recipe setup_script). Name under configs/ or configs/patches/, or a path
--patch FILE.diff Apply a unified diff to the image's vLLM tree (repeatable). Name under configs/patches/ or a path. A conflict aborts the bake; no output image is kept
--force Overwrite an existing output image
--time Time limit when srun allocates a node itself (default: 0:30:00)
--account, --partition Override the srtslurm.yaml defaults
--dry-run Print the srun command and install script without running them

Inside the container the steps run in this order: --script, then each --patch, then dynamo, then sa-bench. --script mounts the repo configs/ tree at /configs, the same path jobs use. --patch dry-runs patch(1) against the installed vLLM package first; if a hunk does not apply, the step exits non-zero and a newly written .sqsh is removed so a conflicted tree is not baked.

The default output name records its contents, so a directory of images stays readable:

my-model-fp8-dynamo1.2.0.dev20260526-sa-bench.sqsh
my-model-fp8-install-nixl-dsv4.sqsh
my-model-fp8-mine.sqsh
my-model-fp8-dynamo1.2.0.dev20260526-sa-bench.manifest.json

The manifest records the source image, what was installed (including the setup script and patch filenames) and when.

Nothing understands the squashfs format here: pyxis' --container-save exports the container filesystem to a new image when the step ends. Bind mounts are not part of that export; only overlay writes (patched site-packages, pip installs) persist. The install runs as root inside the container via ENROOT_REMAP_ROOT, and pip caches plus /tmp are cleared before the save so they are not baked in.

Once an image is baked, drop the matching work from your recipes:

dynamo:
  install: false        # already in the image
# setup_script: "install-nixl-dsv4.sh"   # already baked; omit

SA-Bench dependencies are installed into /opt/srtctl/venvs/sa-bench, isolated from the model server's Python environment. bench.sh activates that venv automatically; recipes do not need to modify PATH. Both sides read the package list and venv path from src/srtctl/benchmarks/scripts/sa-bench/deps.sh, which keeps a baked image aligned with what the benchmark expects.

srtctl status-server

Run the native status collector. Point reporting.status.endpoint in srtslurm.yaml or a recipe at it and every srtctl apply shows up as a job row with an ordered event feed (submitted, starting, workers, frontend, benchmark, then completed or failed, each with its stage and message). Jobs and events persist in one SQLite file and the process prints one line per transition. The endpoints are in Status API.

srtctl status-server                                        # 127.0.0.1:8080, ~/.local/state/srtctl/status.db, no token needed on loopback
srtctl status-server --host 0.0.0.0 --allow-unauthenticated # Open on a trusted network such as a login node
SRTCTL_STATUS_TOKEN=... SRTCTL_STATUS_READ_TOKEN=... \
  srtctl status-server --host 0.0.0.0                       # Bearer tokens required (write token; optional read-only token)
srtctl status-server --port 9000 --db /lustre/shared/status.db
srtctl status-server --host 0.0.0.0 --cors-origin https://ui.example  # UI hosted elsewhere may call the API (read-only)
curl http://login-node:8080/api/jobs                        # Newest jobs first
curl "http://login-node:8080/api/events?after=0"            # Global event feed; pass next_cursor back as after

Open http://<host>:8080/ in a browser for the built-in UI (jobs table, per-job event timeline, live event feed); paste the read token once and the page keeps it in localStorage. Run the server where both the submitting host (the POST at apply time) and the allocation's head node (the PUTs during the run) can reach it, typically a login node. Listening beyond loopback without a token is refused unless --allow-unauthenticated is passed. With a token set on the server, export the same SRTCTL_STATUS_TOKEN in the shell that runs srtctl apply; the reporter sends it as a bearer token and never puts it in a recipe. See Status API.

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 schema and schema-docs

Both read the recipe and srtslurm.yaml dataclasses, so their keys, types, defaults, allowed values, and descriptions always match what srtctl apply loads.

srtctl schema                           # JSON Schema (draft 2020-12) for recipes and override files, to stdout
srtctl schema --cluster                 # JSON Schema for srtslurm.yaml
srtctl schema --output recipe.schema.json
srtctl schema-docs                      # regenerate docs/schema-reference.md, docs/schema/*.schema.json, docs/cli-reference.md
srtctl schema-docs --check              # exit 1 when a checked-in generated file is stale (CI)

The JSON Schema checks shape only (unknown keys, types, enums, required keys). Cross-field rules such as the role topology, placement, and the benchmark keys each benchmark.type accepts are checked by srtctl dry-run. Point an editor's YAML language server at the published copy with # yaml-language-server: $schema=https://nvidia.github.io/srt-slurm/schema/recipe.schema.json as a recipe's first line, or validate with any JSON Schema library.

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 -f for scripting and CI pipelines
  • Always dry-run first for sweeps to check job count
  • Check outputs/<job_id>/ for submitted configs and metadata

srtctl dsight

Explicitly build or query the offline inference trace explorer. Generation is independent of the benchmark job workflow. Run manually in a Bash shell on a cluster login node with uv on PATH, Python 3.10+, a writable checkout that includes DSight, readable run artifacts, and a writable report parent directory. No Slurm allocation, GPU, running deployment, or container is required.

Replace the quoted placeholders with your paths; relative paths resolve from the current working directory.

cd "<path_to_srt_slurm_checkout>"
uv run --no-dev srtctl dsight build "<path_to_run_directory>" \
  --output "<path_to_report_directory>"
# Optional: skip OTel processing and lifecycle breakdowns.
uv run --no-dev srtctl dsight build "<path_to_run_directory>" \
  --output "<path_to_report_directory>" --no-otel
uv run --no-dev srtctl dsight query "<path_to_report_directory>" summary
uv run --no-dev srtctl dsight query "<path_to_report_directory>" requests \
  --from 10 --to 20 --limit 10

Open the generated <path_to_report_directory>/index.html in a browser after copying or publishing it. The read-only MCP query_trace tool uses the generated dataset. See DSight for inputs, environment setup, lifecycle semantics, Nsight imports, and the browser/Python APIs.