Clusydocs
Guides

Branching experiments

A worked example. Fork a notebook, develop two approaches, compare them, and converge.

This walks one branching workflow end to end, by hand, with code you can paste. For the ideas behind it (checkpoints, the branch graph, convergence), read Branches first.

The setup: you load a dataset, split it, and fit a logistic-regression baseline. Then you want to try a gradient-boosted model against it without redoing any of that.

NOTEBOOKARM 1ARM 2ARM 3own kernelown branchCOMPARISONOne notebook, forked at the last shared cell: three arms run side by side on their own kernels, and come back as a single comparison.

When to fork by hand, and when not to

If your experiment is a parameter sweep ("try these three learning rates"), skip the manual forking and ask the agent for a batch experiment: it forks a branch per variant from the same checkpoint, runs them, and reports back. Same shape of work, one instruction.

Fork by hand when the branches differ structurally rather than parametrically: different model families, different feature sets, different framings of the problem. That's this example. A sweep varies a number; a fork lets each arm be a different piece of code.

1. Get to the fork point

Work the notebook up to the cell where the two paths split. Here that's the train/test split, and it's the last thing both approaches share:

from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler

X, y = load_breast_cancer(return_X_y=True, as_frame=True)
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, stratify=y, random_state=0
)
scaler = StandardScaler().fit(X_train)

The Breast Cancer Wisconsin dataset ships with scikit-learn, so there's nothing to download. Then the baseline, on the branch you're already on:

from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score, roc_auc_score

logreg = LogisticRegression(max_iter=5000).fit(scaler.transform(X_train), y_train)
p = logreg.predict_proba(scaler.transform(X_test))[:, 1]
print(accuracy_score(y_test, p > 0.5), roc_auc_score(y_test, p))
# 0.9825 0.9957

2. Fork

Hover the gap under the train/test-split cell and click Fork. Clusy checkpoints the kernel there, so the new branch opens with X_train, y_test, scaler and the rest already in memory. Name it something you'll recognize: "gbm" beats "branch 2".

You now have two branches sharing the setup and diverging from that cell. The baseline branch is untouched, and its logreg is still sitting in its own kernel.

3. Build the new branch

On the new branch, write the gradient-boosted approach. The first cell can use X_train immediately, because it came along in the checkpoint:

from sklearn.ensemble import HistGradientBoostingClassifier
from sklearn.metrics import accuracy_score, roc_auc_score

gbm = HistGradientBoostingClassifier(random_state=0).fit(X_train, y_train)
p = gbm.predict_proba(X_test)[:, 1]
print(accuracy_score(y_test, p > 0.5), roc_auc_score(y_test, p))
# 0.9386 0.9868

Nothing above the fork ran again. Note also what the fork did not have to carry: logreg was fitted after the fork point, on the other branch, so it isn't here — which is exactly what you want, because it keeps the two arms clean.

4. Compare

Switch between the branches from the branch control at the top right of the canvas, or click the fork pills that appear inline at the split cell. Both branches ran from the same checkpoint, so they saw the same split of the same data, and the comparison is honest:

AccuracyROC AUCFit time
Logistic regression (baseline)0.9820.996~0.01 s
Gradient boosting0.9390.987~0.4 s

The boosted model loses, on both metrics, and takes longer. That's a real result on this dataset: 30 well-behaved numeric features and 569 rows is a linear model's home turf, and boosting has nothing to exploit. Worth remembering the next time a comparison card puts a Fastest badge on the quickest run and it happens to be the simplest one too. The badge is a stopwatch, not a verdict; the reading is yours.

5. Converge

Once you've decided, ask the agent to converge the branches. The cell they rejoin shows a bar across the top: how many variants feed it, a chip per variant, and the mode, which you can change there.

  • Winner carries that branch's kernel state past the join, so the cells below run against it. Here you'd take the baseline forward.
  • Comparison keeps both at the join and carries nothing on. Use it when the comparison is the deliverable, which for a result like this one it may well be.
  • Manual lets you take pieces of each.

The point where the branches rejoin is a convergence point, a diamond in the graph that says these split back at the train/test cell and came together here.

Tips

  • Fork early. It snapshots state instead of recomputing, so a branch you throw away costs almost nothing. Don't agonize over whether an idea deserves one.
  • Watch what a fork can't carry. Open handles, database connections and device contexts don't survive the checkpoint, and a namespace over 256 MB isn't carried at all. If the fork degrades, the branch starts on a fresh kernel and Clusy says so. See what a fork doesn't carry.
  • Mind the meter on GPU sweeps. Auto on CPU is free. N variants on a GPU is roughly N times the compute, and the sandbox meter runs the whole time. See Usage.
  • Name branches. A tree of "branch 1, 2, 3" is its own puzzle.
  • Set dead ends to dormant instead of deleting them. You keep the record of what you tried without the clutter.
  • Branching isn't committing. When you've got a result worth versioning, commit the notebook to GitHub.

On this page

Ask docs