Massively-parallel hyperparameter sweeps
"# Massively-parallel hyperparameter sweeps\n\nWhen tuning RL runs, finding the optimal set of hyperparameters is time-consuming\nand error-prone if not properly guided or documented. This is made a first-class\noperation in the Gym so you can move faster and spend less.\n\n```python\nfrom modal_training_gym import (\n HuggingFaceDataset,\n Qwen3_5_4B,\n Qwen3_5_4B_Recipe,\n TrainConfig,\n TrainingGroup,\n)\n```\n\n## Define the training base\n\nWe'll start by creating the shared model, dataset, and base training recipe\nall sweep runs will share. To stay focused on how to do sweeps, we'll keep\nthis code minimal and only tune a few parameters, but the sky's the limit!\n\n```python\nmodel = Qwen3_5_4B()\n\nclass MathDataset(HuggingFaceDataset):\n hf_repo = \"zhuzilin/dapo-math-17k\"\n input_key = \"prompt\"\n label_key = \"label\"\n output_format = \"jsonl\"\n apply_chat_template = True\n always_prepare = True\n\ntrain_dataset = MathDataset(hf_split=\"train[:2000]\")\n\nbase = TrainConfig(\n model=model,\n dataset=train_dataset,\n recipe=Qwen3_5_4B_Recipe(\n eval_interval=None,\n rollout_num_gpus=8,\n num_rollout=15,\n rollout_max_response_len=8192,\n global_batch_size=32,\n rm_type=\"dapo\",\n ),\n)\n```\n\n## Create the sweep\n\nWe'll pass in the parameters we wish to test, then preview the set of\nruns the grid search will kick off once we're ready.\n\n```python\ngroup = TrainingGroup(\n base=base,\n grid={\n \"recipe.lr\": [5e-7, 5e-6],\n \"recipe.rollout_temperature\": [0.8, 1.0],\n },\n)\nconfigs = group.get_train_configs()\nprint(f\"{len(configs)} runs in group {group.group_id}:\")\nfor cfg in configs:\n print(\n f\"- lr={cfg.recipe.lr:<8}, temp={cfg.recipe.rollout_temperature}\"\n )\n```\n\n## Launch it!\n\nOnce it all looks good, `.launch()` it!\n\n```python\nlaunches = group.launch(prepare_inputs=True)\nprint(f\"group {group.group_id}: {len(launches)} runs launched\")\nfor launch in launches:\n print(\n f\"- {launch.training_run_id}, app={launch.modal_app_id}, group_id={launch.group_id}\"\n )\nif group.failures:\n for overrides, err in group.failures:\n print(f\"- FAILED {overrides}: {err}\")\n\nresults = []\nfor launch in launches:\n result = launch.result()\n results.append(result)\n print(f\"completed {result.training_run_id} (group_id={result.group_id})\")\n\nprint(f\"group {group.group_id}: {len(results)} runs completed\")\n```\n"
When tuning RL runs, finding the optimal set of hyperparameters is time-consuming and error-prone if not properly guided or documented. This is made a first-class operation in the Gym so you can move faster and spend less.
from modal_training_gym import ( HuggingFaceDataset, Qwen3_5_4B, Qwen3_5_4B_Recipe, TrainConfig, TrainingGroup,)Define the training base
We’ll start by creating the shared model, dataset, and base training recipe all sweep runs will share. To stay focused on how to do sweeps, we’ll keep this code minimal and only tune a few parameters, but the sky’s the limit!
model = Qwen3_5_4B()
class MathDataset(HuggingFaceDataset): hf_repo = "zhuzilin/dapo-math-17k" input_key = "prompt" label_key = "label" output_format = "jsonl" apply_chat_template = True always_prepare = True
train_dataset = MathDataset(hf_split="train[:2000]")
base = TrainConfig( model=model, dataset=train_dataset, recipe=Qwen3_5_4B_Recipe( eval_interval=None, rollout_num_gpus=8, num_rollout=15, rollout_max_response_len=8192, global_batch_size=32, rm_type="dapo", ),)Create the sweep
We’ll pass in the parameters we wish to test, then preview the set of runs the grid search will kick off once we’re ready.
group = TrainingGroup( base=base, grid={ "recipe.lr": [5e-7, 5e-6], "recipe.rollout_temperature": [0.8, 1.0], },)configs = group.get_train_configs()print(f"{len(configs)} runs in group {group.group_id}:")for cfg in configs: print( f"- lr={cfg.recipe.lr:<8}, temp={cfg.recipe.rollout_temperature}" )Launch it!
Once it all looks good, .launch() it!
launches = group.launch(prepare_inputs=True)print(f"group {group.group_id}: {len(launches)} runs launched")for launch in launches: print( f"- {launch.training_run_id}, app={launch.modal_app_id}, group_id={launch.group_id}" )if group.failures: for overrides, err in group.failures: print(f"- FAILED {overrides}: {err}")
results = []for launch in launches: result = launch.result() results.append(result) print(f"completed {result.training_run_id} (group_id={result.group_id})")
print(f"group {group.group_id}: {len(results)} runs completed")