Examples#

Hyperparameter sweeps#

This example shows how you can run a hyperparameter sweep in Beaker using beaker-py.

Note

You can find the source code for this example on GitHub.

Setup#

Add the following files to a directory of your choosing:

# This Dockerfile defines the image that we'll use for all of the sweep experiments
# that we submit to Beaker.

FROM python:3.9

COPY entrypoint.py .

ENTRYPOINT ["python", "entrypoint.py"]
"""
This is the script that will run on Beaker as the Docker image's "entrypoint".

All it does is write out a simple JSON file with a random number in it to
the experiment's result directory. This is just meant to simulate the results
of a training/evaluation pipeline.
"""

import json
import random
import sys

# NOTE: it's important that this file is called 'metrics.json'. That tells Beaker
# to collect metrics for the task from this file.
OUTPUT_PATH = "/output/metrics.json"


def main(x: int):
    random.seed(x)
    with open(OUTPUT_PATH, "w") as out_file:
        json.dump({"result": random.random()}, out_file)


if __name__ == "__main__":
    main(int(sys.argv[1]))
"""
This script will upload an image to Beaker and then submit a bunch
of experiments with different inputs. It will wait for all experiments to finish
and then collect the results.

See the output of 'python run.py --help' for usage.
"""

import argparse
import uuid

import petname
from rich import print, progress, table, traceback

from beaker import *


def unique_name() -> str:
    """Helper function to generate a unique name for the image, group, and each experiment."""
    return petname.generate() + "-" + str(uuid.uuid4())[:8]  # type: ignore


def main(image: str, workspace: str):
    beaker = Beaker.from_env(default_workspace=workspace)
    sweep_name = unique_name()
    print(f"Starting sweep '{sweep_name}'...\n")

    # Using the `beaker.session()` context manager is not necessary, but it does
    # speed things up since it allows the Beaker client to reuse the same TCP connection
    # for all requests made within-context.
    with beaker.session():
        # Upload image to Beaker.
        print(f"Uploading image '{image}' to Beaker...")
        beaker_image = beaker.image.create(unique_name(), image)
        print(
            f"Image uploaded as '{beaker_image.full_name}', view at {beaker.image.url(beaker_image)}\n"
        )

        # Launch experiments.
        experiments = []
        for x in progress.track(range(5), description="Launching experiments..."):
            spec = ExperimentSpec.new(
                "ai2/allennlp",
                description=f"Run {x+1} of sweep {sweep_name}",
                beaker_image=beaker_image.full_name,
                result_path="/output",
                priority=Priority.preemptible,
                arguments=[str(x)],
            )
            experiment = beaker.experiment.create(f"{sweep_name}-{x+1}", spec)
            experiments.append(experiment)
        print()

        # Create group.
        print("Creating group for sweep...")
        group = beaker.group.create(
            sweep_name, *experiments, description="Group for sweep {sweep_name}"
        )
        print(f"Group '{group.full_name}' created, view at {beaker.group.url(group)}\n")

        # Wait for experiments to finish.
        print("Waiting for experiments to finalize...\n")
        experiments = beaker.experiment.wait_for(*experiments)
        print()

        # Display results as a table.
        results_table = table.Table(title="Results for sweep")
        results_table.add_column("Input")
        results_table.add_column("Output")
        for x, experiment in enumerate(
            progress.track(experiments, description="Gathering results...")
        ):
            metrics = beaker.experiment.metrics(experiment)
            assert metrics is not None
            results_table.add_row(f"x = {x}", f"{metrics['result']:.4f}")
        print()
        print(results_table)


if __name__ == "__main__":
    traceback.install()

    parser = argparse.ArgumentParser(description="Run a hyperparameter sweep in Beaker")
    parser.add_argument(
        "image", type=str, help="""The tag of the local Docker image built from the Dockerfile."""
    )
    parser.add_argument("workspace", type=str, help="""The Beaker workspace to use.""")
    opts = parser.parse_args()

    main(image=opts.image, workspace=opts.workspace)

Running it#

To run it, first build the Docker image:

image=sweep
docker build -t $image .

Then launch the sweep with:

workspace=ai2/my-sweep  # change this to the workspace of your choosing
cluster=ai2/petew-cpu  # change this to the cluster of your choosing
python run.py $image $workspace $cluster