Python SDK
Logging, safe-point commands, artifacts, checkpoints and a PyTorch adapter. Requires Python 3.11+ on macOS, Linux, or Windows.
Install
pip install launchhelm
pip install "launchhelm[torch]" # PyTorch adapter
pip install "launchhelm[system]" # host metrics (psutil/NVML)
pip install "launchhelm[hf]" # transformers.Trainer callback
pip install "launchhelm[lightning]" # PyTorch Lightning callback
Pin the version in your requirements; 0.x minor releases can change behavior. Package page: pypi.org/project/launchhelm.
Configure
export LAUNCHHELM_URL=https://launchhelm.com
export LAUNCHHELM_API_KEY=lh_... # from Settings → API keys, shown once
export LAUNCHHELM_ORGANIZATION=... # organization UUID
export LAUNCHHELM_PROJECT=... # only for a key limited to one project
init also reads .env files from the working directory upward; shell variables win. A key for all projects finds or creates the project named in init. A key limited to one project needs LAUNCHHELM_PROJECT, the project UUID.
Log a run
import launchhelm
with launchhelm.init("quickstart", name="first-run", config={"lr": 0.0003}) as run:
for step in range(8):
loss = 2.0 / (step + 1)
run.log({"loss": loss, "lr": 0.0003}, step=step)
print("run finished:", run.run_id)python-log_run.py · verbatim from sdks/python/examples/log_run.py
launchhelm.init(project, ...) opens or creates the named project, starts a run and returns a Run. The with block calls finish() on exit.
Plan
run.plan(step=32_000) declares where the run is meant to end; one other axis keyword (run.plan(tokens=33_500_000_000)) declares it on that axis instead, and run.plan() clears it. The run page shows a step plan as "21,412 of 32,000" and, while the run is open, an estimate of the time left from the run's average rate since it started.
Events
run.event("data mix updated") records a named moment at the current step; step= places it elsewhere, and one other axis keyword (run.event("warmup done", tokens=2_000_000_000)) places it on that axis instead. The run's charts draw events on the step axis as markers, and runs can be lined up on an event they share. Each call sends one request and waits for it, so record moments, not every step.
Delivery
run.log(...)returns as soon as the point is recorded locally.run.flush(delivered=False)persists queued points on the local machine (state dir.launchhelm/by default); they upload on the next start that reuses it.run.flush()waits until the server has stored the points.run.finish()waits for delivery and ends the run.run.close()saves the queue without that wait — preferfinish().
Commands and safe points
@run.command("after_step") publishes a capability; run.sync(...) applies one waiting command per safe point without blocking the training loop. See Control and tunables and Concepts.
Rich values
Beyond scalars, four renderers carry structured values: text, media, table, and histogram:
run.text(name, text, format=..., caption=...)— plain or markdown notes.run.image(name, source, ...)/run.audio(...)/run.video(...)— one media item;run.media(name, items)— a grid.sourceis a path, bytes withmedia_type, a PIL image, or a NumPy array / torch tensor encoded to PNG.run.upload_media(...)andrun.media_url(href, media_type)build grid/table items directly; external https URLs need no upload.run.rows(name, records)infers column types;run.log_table(name, columns, rows)takes explicit columns and allowsmediacells.run.histogram(name, values, bins=64)bins equal-width;run.histogram_counts(name, edges, counts, **stats)takes precomputed bins.run.log_distribution(name, values, quantiles=(0.1, 0.25, 0.5, 0.75, 0.9))logs the spread of one batch (per-example loss, per-token entropy, rollout reward) as scalar streamsname/p10...name/p90,name/min,name/max,name/meanandname/count, all at the same step. Quantiles are exact for the batch (linear interpolation between order statistics). Non-finite values are left out and counted inname/nonfinite; a batch with nothing finite logs only the counts, so the chart shows a gap, not a zero. A dashboard draws the ranges withvalue_range:
panel = line(
streams(name="loss/p50", runs="runs", label="median", id="median"),
streams(name="loss/p10", runs="runs", id="p10"),
streams(name="loss/p90", runs="runs", id="p90"),
streams(name="loss/p25", runs="runs", id="p25"),
streams(name="loss/p75", runs="runs", id="p75"),
ranges=[
value_range("median", "p10", "p90", label="p10-p90"),
value_range("median", "p25", "p75", label="p25-p75", style="band_lines"),
],
id="loss-spread",
title="Loss across the batch",
)
style is band, lines, band_lines or whiskers. On a bar panel the same ranges and bands draw error bars.
log_distribution shows the spread of one batch at one step. A long run drawn as a downsampled line hides a different spread: the variation of one stream over time inside each bucket. bucket_range shows it without logging anything more. It asks the query engine for two quantiles (p1, p5, p10, p25, median, p75, p90, p95 or p99) of the samples inside each bucket of the center stream itself:
over_time = line(
streams(name="loss", runs="runs", id="loss"),
ranges=[bucket_range("loss", "p10", "p90", label="p10-p90 in each bucket")],
id="loss-over-time",
title="Loss, with its spread inside each bucket",
)
It consumes no source, so it needs no sibling streams. It is drawn only on a bucketed line: raw points have no spread inside a bucket, so the chart says the range is not drawn instead of showing a flat band, and a bar panel refuses it. The quantiles are exact while the window's points fit the query budget, and labelled approximate when the engine answers from a sketch.
Media uploads don't block the loop: the bytes persist locally first, so a flush(), finish(), or a resumed run still delivers the point.
launchhelm.Text, Image, Audio, Video, Table and Histogram values are also accepted inside run.log({...}). client.read_samples(stream_id, select=...) reads bounded samples back. See the full example under Examples.
Errors
launchhelm.init raises ValueError for bad arguments and LaunchHelmError for missing credentials, an ambiguous organization, or a missing/ambiguous project. With a key limited to one project, a forbidden response usually means LAUNCHHELM_PROJECT is unset or holds a name instead of the UUID — see Troubleshooting.