Skip to content
36 changes: 28 additions & 8 deletions examples/puzzletron/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,14 +57,14 @@ overrides:
python examples/puzzletron/puzzletron_setup.py --detailed
```

Every answer is saved atomically. A new invocation starts fresh; resume is always
explicit:
Every answer is saved atomically. Invocations without `--resume` start a campaign;
resuming an existing campaign requires its path:

```bash
python examples/puzzletron/puzzletron_setup.py --resume /path/to/campaign
```

The initial profiles support Nemotron 3 and Qwen 3.5/3.6 dense, MoE, text, and
The supported profiles cover Nemotron 3 and Qwen 3.5/3.6 dense, MoE, text, and
multimodal configurations. Unsupported configs exit with detected metadata and
point to `.agents/skills/running-puzzletron/SKILL.md` for descriptor onboarding.

Expand All @@ -75,8 +75,20 @@ with `localhost` for a single local host.

### Setup wizard v2

The new schema-driven wizard keeps the existing entry point unchanged and adds
local defaults-versus-customize decisions at every section:
The setup wizard v2 offers three guided profiles:

- **Quick smoke** is the fastest way to verify that the campaign shape is valid.
- **Balanced pruning** is recommended for a first real campaign.
- **High-confidence search** spends more runtime on scoring and sanity checks.

The selected profile supplies nested pruning and MIP defaults from the detected
model family's `setup_v2_defaults.yaml`. A family file can refine those values
for an exact model geometry, so a small and large model in the same family do
not need to share scoring budgets. Unspecified model values inherit the family
profile, while an explicitly selected defaults file has the highest
default precedence. Setup then asks for the model and dataset, and requires
explicit acceptance or customization of infrastructure-specific worker and
cluster defaults:

```bash
python examples/puzzletron/puzzletron_setup_v2.py \
Expand All @@ -85,9 +97,17 @@ python examples/puzzletron/puzzletron_setup_v2.py \

The example defaults use only repository-relative values. Copy the file and add
site-specific data, scheduler, and container settings before selecting it.
Defaults are loaded only when passed explicitly. Selection prompts have
a visible **← Back** action; text and numeric prompts accept `:back`. Every
accepted answer and the exact navigation frame are saved in
The defaults file is loaded only when passed explicitly and takes precedence
over the selected profile. To expose every per-section and nested setting, use
the advanced flow explicitly:

```bash
python examples/puzzletron/puzzletron_setup_v2.py --full
```

Press **Esc** to go back from any prompt. Selection prompts show a visible
**← Back** action, and text or numeric prompts accept `:back`.
Every accepted answer and the exact navigation frame are saved in
`answers_v2.yaml`, so an interrupted session can resume with:

```bash
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
schema_version: 2

profiles:
smoke:
pruning:
depth_remove: 1
depth_importance_samples: 32
width_importance_samples: 512
sort_sanity: false
width_sanity: false
slicing_sanity: false
replacement_samples: 32
bypass:
enabled: false
samples: 64
mip:
goal_value: 90%
num_solutions: 2

balanced:
pruning:
depth_remove: 4
depth_importance_samples: 128
width_importance_samples: 32768
sort_sanity: false
width_sanity: false
slicing_sanity: false
replacement_samples: 128
bypass:
enabled: true
samples: 4096
mip:
goal_value: 75%
num_solutions: 8

high-confidence:
pruning:
depth_remove: 6
depth_importance_samples: 512
width_importance_samples: 65536
sort_sanity: true
sort_sanity_samples: 512
width_sanity: true
width_sanity_samples: 512
slicing_sanity: true
replacement_samples: 512
bypass:
enabled: true
samples: 8192
mip:
goal_value: 70%
num_solutions: 16

# Model-specific values are seeded from historical campaign configurations.
# Unspecified fields continue to inherit the family profile above.
model_overrides:
nano_30b_a3b_bf16:
match:
num_layers: 52
facts:
hidden_size: 2688
intermediate_size: 1856
num_attention_heads: 32
num_key_value_heads: 2
num_experts: 128
profiles:
smoke:
pruning:
depth_importance_samples: 2
width_importance_samples: 2
replacement_samples: 2
bypass:
enabled: true
mip:
num_solutions: 1
high-confidence:
pruning:
depth_remove: 5
depth_importance_samples: 128
width_importance_samples: 8192
replacement_samples: 128
mip:
goal_value: 75%
num_solutions: 5
105 changes: 105 additions & 0 deletions examples/puzzletron/configs/families/qwen3_5/setup_v2_defaults.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
schema_version: 2

profiles:
smoke:
pruning:
depth_remove: 1
depth_importance_samples: 32
width_importance_samples: 512
sort_sanity: false
width_sanity: false
slicing_sanity: false
replacement_samples: 32
bypass:
enabled: false
samples: 64
mip:
goal_value: 90%
num_solutions: 2

balanced:
pruning:
depth_remove: 4
depth_importance_samples: 128
width_importance_samples: 32768
sort_sanity: false
width_sanity: false
slicing_sanity: false
replacement_samples: 128
bypass:
enabled: true
samples: 4096
mip:
goal_value: 75%
num_solutions: 8

high-confidence:
pruning:
depth_remove: 6
depth_importance_samples: 512
width_importance_samples: 65536
sort_sanity: true
sort_sanity_samples: 512
width_sanity: true
width_sanity_samples: 512
slicing_sanity: true
replacement_samples: 512
bypass:
enabled: true
samples: 8192
mip:
goal_value: 70%
num_solutions: 16

# Model-specific values are seeded from historical campaign configurations.
# Unspecified fields continue to inherit the family profile above.
model_overrides:
qwen3p5_0p8b:
match:
num_layers: 24
facts:
hidden_size: 1024
intermediate_size: 3584
num_attention_heads: 8
num_key_value_heads: 2
profiles:
smoke:
pruning:
width_importance_samples: 8
replacement_samples: 4
mip:
goal_value: 85%

qwen3p5_9b:
match:
num_layers: 32
facts:
hidden_size: 4096
intermediate_size: 12288
num_attention_heads: 16
num_key_value_heads: 4
profiles:
high-confidence:
pruning:
depth_importance_samples: 128
replacement_samples: 128
bypass:
enabled: false
mip:
goal_value: 75%

qwen3p6_27b:
match:
num_layers: 64
facts:
hidden_size: 5120
intermediate_size: 17408
num_attention_heads: 24
num_key_value_heads: 4
profiles:
balanced:
pruning:
width_importance_samples: 16384
replacement_samples: 16
mip:
goal_value: 85%
2 changes: 1 addition & 1 deletion examples/puzzletron/puzzletron_setup_v2.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0

"""Create a fully configurable Puzzletron campaign without launching it."""
"""Create a guided or fully configurable Puzzletron campaign without launching it."""

from __future__ import annotations

Expand Down
3 changes: 2 additions & 1 deletion puzzletron_setup/v2/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

"""Schema-driven Puzzletron campaign setup."""

from .defaults import DefaultsResolver, ResolvedDefault, load_defaults
from .defaults import DefaultsResolver, ResolvedDefault, load_defaults, validate_defaults
from .state import FieldRecord, PromptFrame, WizardState

__all__ = [
Expand All @@ -13,4 +13,5 @@
"ResolvedDefault",
"WizardState",
"load_defaults",
"validate_defaults",
]
25 changes: 14 additions & 11 deletions puzzletron_setup/v2/bundle.py
Original file line number Diff line number Diff line change
Expand Up @@ -257,10 +257,7 @@ def _bundle_readme(
acquisition_command.extend(
[
"--subset-rows",
*[
f"{name}={rows}"
for name, rows in subset_rows.items()
],
*[f"{name}={rows}" for name, rows in subset_rows.items()],
]
)
acquisition_command.extend(
Expand Down Expand Up @@ -342,14 +339,20 @@ def build_bundles_v2(campaign_dir: Path, state: WizardState) -> BundleResult:
(bundle / "dry-run-plan.txt").write_text(dry_run_bundle(bundle))

resolved = {
path: {
"value": record.value,
"requested": record.requested,
"effective": record.effective,
"source": record.source,
}
for path, record in state.records().items()
str(path): dict(record)
for path, record in dict(state.collection("default_resolutions") or {}).items()
}
resolved.update(
{
path: {
"value": record.value,
"requested": record.requested,
"effective": record.effective,
"source": record.source,
}
for path, record in state.records().items()
}
)
_write_yaml(temp_root / "resolved_defaults.yaml", resolved)
repository = str(
state.get_field(
Expand Down
17 changes: 15 additions & 2 deletions puzzletron_setup/v2/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,13 @@

import argparse
from pathlib import Path
from typing import Optional, Sequence
from typing import TYPE_CHECKING

from puzzletron_setup import SetupError

if TYPE_CHECKING:
from collections.abc import Sequence

__all__ = ["main"]


Expand All @@ -28,17 +31,27 @@ def _parser() -> argparse.ArgumentParser:
type=Path,
help="Explicit versioned defaults YAML; never discovered automatically.",
)
parser.add_argument(
"--full",
action="store_true",
help=(
"Expose every advanced section and nested setting. "
"Without this flag, setup uses a guided profile."
),
)
return parser


def main(argv: Optional[Sequence[str]] = None) -> int:
def main(argv: Sequence[str] | None = None) -> int:
"""Run the setup-v2 command-line interface."""
args = _parser().parse_args(argv)
from .wizard import run_wizard_v2

try:
campaign = run_wizard_v2(
resume=args.resume,
defaults_path=args.defaults,
full=args.full,
)
except KeyboardInterrupt:
target = args.resume or "<campaign>"
Expand Down
Loading
Loading