Sweeps produce dozens of runs; nesting keeps the experiment readable and the best trial easy to find.
A hyperparameter search is naturally a tree: one parent run for the search, one child run per configuration. Create children with nested=True inside an active parent:
with mlflow.start_run(run_name="search"):
for params in grid:
with mlflow.start_run(nested=True):
...Children get the tag mlflow.parentRunId, and the UI shows them collapsed under the parent. Log the search configuration and the best result on the parent, so the parent alone answers "what did this sweep find?".
Patterns that work well:
- Log each child's params and validation metric; log the best params and metric to the parent at the end.
- Query children with
filter_string=f"tags.mlflow.parentRunId = '{parent_id}'". - With tools like Optuna, start a nested run inside the objective function. In parallel workers, pass
parent_run_id=explicitly tostart_runinstead of relying on the active run, because the active-run stack is per thread.
sklearn autolog does this automatically for GridSearchCV and RandomizedSearchCV, creating child runs for the top candidates (limited by max_tuning_runs).
Keep the number of logged metrics per child small; thousands of child runs with many step metrics make the UI slow.
Example
import mlflow
def train(lr, depth): # stand-in for real training
return 0.9 - abs(lr - 0.1) - abs(depth - 4) * 0.01
mlflow.set_experiment("tuning-demo")
with mlflow.start_run(run_name="grid-search") as parent:
best = None
for lr in [0.01, 0.1, 0.3]:
for depth in [2, 4]:
with mlflow.start_run(nested=True):
score = train(lr, depth)
mlflow.log_params({"lr": lr, "depth": depth})
mlflow.log_metric("val_score", score)
if best is None or score > best[0]:
best = (score, lr, depth)
mlflow.log_metric("best_val_score", best[0])
mlflow.log_params({"best_lr": best[1], "best_depth": best[2]})
kids = mlflow.search_runs(filter_string=f"tags.mlflow.parentRunId = '{parent.info.run_id}'")
print(len(kids), best) # 6 (0.9, 0.1, 4)Remember
- Use a parent run per search and
nested=Truechild runs per trial - Summarize the best params and score on the parent
- Children carry the
mlflow.parentRunIdtag for querying
Watch out for
- Starting child runs from worker threads without passing parent_run_id, producing orphaned top-level runs.