Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions docs/config-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,8 @@ The `srtslurm.yaml` file can contain the following fields:
| `default_account` | string | Default SLURM account |
| `default_partition` | string | Default SLURM partition |
| `default_time_limit` | string | Default job time limit |
| `gpus_per_node` | int | Default GPUs per node |
| `gpus_per_node` | int | Default GPUs per node (applied to recipes that omit `resources.gpus_per_node`) |
| `default_gpu_type` | string | Default `resources.gpu_type` for recipes that omit it |
| `network_interface` | string | Network interface for NCCL |
| `srtctl_root` | string | Root directory for srtctl |
| `output_dir` | string | Custom output directory (overrides srtctl_root/outputs) |
Expand Down Expand Up @@ -226,8 +227,8 @@ resources:

| Field | Type | Default | Description |
| ----------------- | ------ | ------------------ | ------------------------------------- |
| `gpu_type` | string | - | GPU type: "gb200", "gb300", or "h100" |
| `gpus_per_node` | int | 4 | GPUs per node |
| `gpu_type` | string | `default_gpu_type` | GPU type, e.g. "gb200", "gb300", "h100". Optional; inherits `default_gpu_type` from `srtslurm.yaml` when omitted |
| `gpus_per_node` | int | cluster / 4 | GPUs per node; inherits the cluster `gpus_per_node` when omitted, else 4 |
| `prefill_nodes` | int | null | Nodes dedicated to prefill |
| `decode_nodes` | int | null | Nodes dedicated to decode |
| `prefill_workers` | int | null | Number of prefill workers |
Expand Down
3 changes: 2 additions & 1 deletion docs/schema-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ Resource allocation configuration.

| Key | Type | Default | Description |
|---|---|---|---|
| `gpu_type` | str | required | |
| `gpu_type` | str \| None | `None` | GPU type (h100, gb200, ...). Cluster fact, not a topology choice. Optional: a recipe that omits it inherits `default_gpu_type` from srtslurm.yaml, and `gpus_per_node` inherits the cluster `gpus_per_node`. Both are still worth setting in a recipe so it is self-describing for result rollups. |
| `gpus_per_node` | int | `4` | |
| `prefill_nodes` | int \| None | `None` | Disaggregated mode |
| `decode_nodes` | int \| None | `None` | |
Expand Down Expand Up @@ -543,6 +543,7 @@ Top-level keys of `srtslurm.yaml`. Recipes inherit these defaults and resolve al
| `default_partition` | str \| None | `None` | |
| `default_time_limit` | str \| None | `None` | |
| `gpus_per_node` | int \| None | `None` | |
| `default_gpu_type` | str \| None | `None` | Default for ``ResourceConfig.gpu_type`` when the recipe omits it. Lets one recipe move between clusters of different GPU types without an edit. |
| `network_interface` | str \| None | `None` | |
| `use_gpus_per_node_directive` | bool | `True` | |
| `use_segment_sbatch_directive` | bool | `True` | |
Expand Down
12 changes: 12 additions & 0 deletions src/srtctl/core/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,18 @@ def resolve_config_with_defaults(user_config: dict[str, Any], cluster_config: di
slurm["time_limit"] = cluster_config["default_time_limit"]
logger.debug(f"Applied default time_limit: {slurm['time_limit']}")

# GPU-topology facts inherited from the cluster when the recipe omits them.
# gpu_type and gpus_per_node describe the machine, not the deployment, so a
# recipe can move between clusters by leaving them to srtslurm.yaml.
resources_defaults = config.get("resources")
if isinstance(resources_defaults, dict):
if not resources_defaults.get("gpu_type") and cluster_config.get("default_gpu_type"):
resources_defaults["gpu_type"] = cluster_config["default_gpu_type"]
logger.debug("Applied default gpu_type: %s", resources_defaults["gpu_type"])
if "gpus_per_node" not in resources_defaults and cluster_config.get("gpus_per_node") is not None:
resources_defaults["gpus_per_node"] = cluster_config["gpus_per_node"]
logger.debug("Applied cluster gpus_per_node: %s", resources_defaults["gpus_per_node"])

default_sbatch_directives = cluster_config.get("default_sbatch_directives")
if isinstance(default_sbatch_directives, dict):
sbatch_directives = config.setdefault("sbatch_directives", {})
Expand Down
9 changes: 8 additions & 1 deletion src/srtctl/core/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,9 @@ class ClusterConfig:
default_partition: str | None = None
default_time_limit: str | None = None
gpus_per_node: int | None = None
# Default for ``ResourceConfig.gpu_type`` when the recipe omits it. Lets one
# recipe move between clusters of different GPU types without an edit.
default_gpu_type: str | None = None
network_interface: str | None = None
use_gpus_per_node_directive: bool = True
use_segment_sbatch_directive: bool = True
Expand Down Expand Up @@ -548,7 +551,11 @@ class HetComponent:
class ResourceConfig:
"""Resource allocation configuration."""

gpu_type: str
# GPU type (h100, gb200, ...). Cluster fact, not a topology choice. Optional:
# a recipe that omits it inherits `default_gpu_type` from srtslurm.yaml, and
# `gpus_per_node` inherits the cluster `gpus_per_node`. Both are still worth
# setting in a recipe so it is self-describing for result rollups.
gpu_type: str | None = None
gpus_per_node: int = 4

# Disaggregated mode
Expand Down
38 changes: 38 additions & 0 deletions tests/test_configs.py
Original file line number Diff line number Diff line change
Expand Up @@ -5035,3 +5035,41 @@ def fake_start_endpoint(ep_procs):

# Each node has only 1 worker — no wait should be triggered
assert wait_called == []


class TestClusterGpuDefaults:
"""resources.gpu_type / gpus_per_node inherit from srtslurm.yaml when omitted."""

def _recipe(self, resources: dict) -> dict:
return {
"name": "gpu-defaults",
"model": {"path": "/m", "container": "/c.sqsh", "precision": "fp8"},
"resources": resources,
}

def test_recipe_without_gpu_type_inherits_default_gpu_type(self):
from srtctl.core.config import resolve_config_with_defaults

resolved = resolve_config_with_defaults(
self._recipe({"agg_nodes": 1, "agg_workers": 1}),
{"default_gpu_type": "gb200", "gpus_per_node": 4},
)
assert resolved["resources"]["gpu_type"] == "gb200"
assert resolved["resources"]["gpus_per_node"] == 4

def test_recipe_gpu_fields_win_over_cluster_defaults(self):
from srtctl.core.config import resolve_config_with_defaults

resolved = resolve_config_with_defaults(
self._recipe({"gpu_type": "h100", "gpus_per_node": 8, "agg_nodes": 1}),
{"default_gpu_type": "gb200", "gpus_per_node": 4},
)
assert resolved["resources"]["gpu_type"] == "h100"
assert resolved["resources"]["gpus_per_node"] == 8

def test_recipe_without_gpu_type_and_no_cluster_default_loads(self):
from srtctl.core.schema import SrtConfig

config = SrtConfig.Schema().load(self._recipe({"agg_nodes": 1, "agg_workers": 1}))
assert config.resources.gpu_type is None
assert config.resources.gpus_per_node == 4
7 changes: 6 additions & 1 deletion tests/test_schema_docs.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,12 @@ def test_marshmallow_data_key_wins_over_private_attribute_name() -> None:
assert "gpus_per_decode" in rows
assert "_explicit_gpus_per_prefill" not in rows
assert rows["gpus_per_node"].default == "`4`"
assert rows["gpu_type"].default == "required"
assert rows["gpu_type"].default == "`None`"


def test_required_field_renders_as_required() -> None:
rows = {row.key: row for row in field_docs(SrtConfig)}
assert rows["name"].default == "required"


def test_docstring_attributes_become_descriptions() -> None:
Expand Down
Loading