runboard 0.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
runboard-0.2.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Mohammad Zbeeb
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,362 @@
1
+ Metadata-Version: 2.4
2
+ Name: runboard
3
+ Version: 0.2.0
4
+ Summary: Self-hosted, W&B-style live training dashboards for any cluster or VM, viewable in your browser without SSH.
5
+ Author-email: Mohammad Zbeeb <mhmdzbyb829@gmail.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/Moe-Zbeeb/runboard
8
+ Project-URL: Documentation, https://github.com/Moe-Zbeeb/runboard#readme
9
+ Project-URL: Issues, https://github.com/Moe-Zbeeb/runboard/issues
10
+ Project-URL: Changelog, https://github.com/Moe-Zbeeb/runboard/blob/main/CHANGELOG.md
11
+ Keywords: machine-learning,experiment-tracking,training,dashboard,metrics,slurm,cluster,wandb
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Intended Audience :: Science/Research
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Operating System :: POSIX :: Linux
16
+ Classifier: Operating System :: MacOS
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3 :: Only
19
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
20
+ Classifier: Topic :: System :: Monitoring
21
+ Requires-Python: >=3.8
22
+ Description-Content-Type: text/markdown
23
+ License-File: LICENSE
24
+ Provides-Extra: dev
25
+ Requires-Dist: pytest; extra == "dev"
26
+ Dynamic: license-file
27
+
28
+ <h1 align="center">runboard</h1>
29
+
30
+ <p align="center">
31
+ <b>Your own live experiment dashboard on Cloudflare, from any cluster or VM.</b>
32
+ </p>
33
+
34
+ <p align="center">
35
+ <a href="https://github.com/Moe-Zbeeb/runboard/actions/workflows/ci.yml"><img src="https://github.com/Moe-Zbeeb/runboard/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
36
+ <a href="https://pypi.org/project/runboard/"><img src="https://img.shields.io/pypi/v/runboard" alt="PyPI"></a>
37
+ <a href="https://pypi.org/project/runboard/"><img src="https://img.shields.io/pypi/pyversions/runboard" alt="Python versions"></a>
38
+ <img src="https://img.shields.io/badge/dependencies-0-brightgreen" alt="Zero dependencies">
39
+ <a href="https://github.com/Moe-Zbeeb/runboard/blob/main/LICENSE"><img src="https://img.shields.io/github/license/Moe-Zbeeb/runboard" alt="License"></a>
40
+ </p>
41
+
42
+ <p align="center">
43
+ <a href="https://deploy.workers.cloudflare.com/?url=https://github.com/Moe-Zbeeb/runboard"><img src="https://deploy.workers.cloudflare.com/button" alt="Deploy to Cloudflare"></a>
44
+ </p>
45
+
46
+ <picture>
47
+ <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/Moe-Zbeeb/runboard/main/docs/assets/dashboard-dark.png">
48
+ <img alt="runboard dashboard comparing three training runs" src="https://raw.githubusercontent.com/Moe-Zbeeb/runboard/main/docs/assets/dashboard-light.png">
49
+ </picture>
50
+
51
+ runboard is a personal, W&B-style experiment dashboard. Each researcher deploys one small backend to
52
+ their own Cloudflare account. Training jobs send metrics directly over HTTPS, so the cluster does not
53
+ need to host a server and the dashboard keeps the same URL when jobs, login nodes, or laptops restart.
54
+
55
+ - **Two lines in your training code.** `runboard.init(...)` and `runboard.log({...})`.
56
+ - **Bring your own Cloudflare.** One click creates a Worker, D1 database, and static dashboard
57
+ in your account. No domain or always-on cluster process is required.
58
+ - **One stable URL.** Jobs and browsers connect to the same `workers.dev` HTTPS endpoint from anywhere.
59
+ - **Zero dependencies.** The client, server, and dashboard use only the Python standard library.
60
+ `pip install runboard` works on any cluster, even behind a restrictive proxy.
61
+ - **Keeps network and disk work out of training.** `log()` appends metrics in memory; a background
62
+ thread sends batches, retries outages, and spills excess backlog to disk. Remaining metrics are
63
+ saved when the run finishes.
64
+ - **A local mode when you need it.** The same package can store plain JSONL files and serve them from a
65
+ cluster or laptop without Cloudflare storage.
66
+
67
+ ## Contents
68
+
69
+ - [Quickstart](#quickstart)
70
+ - [How it works](#how-it-works)
71
+ - [Logging API](#logging-api)
72
+ - [Cloudflare deployment](#cloudflare-deployment)
73
+ - [Running on a cluster](#running-on-a-cluster)
74
+ - [The dashboard](#the-dashboard)
75
+ - [Configuration](#configuration)
76
+ - [CLI reference](#cli-reference)
77
+ - [Security](#security)
78
+ - [FAQ](#faq)
79
+ - [Development](#development)
80
+
81
+ ## Quickstart
82
+
83
+ **1. Deploy your personal backend.** Click the button, sign in to Cloudflare, and choose a long random
84
+ `RUNBOARD_TOKEN` when prompted. Cloudflare creates the Worker, database, and dashboard.
85
+
86
+ [![Deploy to Cloudflare](https://deploy.workers.cloudflare.com/button)](https://deploy.workers.cloudflare.com/?url=https://github.com/Moe-Zbeeb/runboard)
87
+
88
+ Cloudflare gives you a URL such as `https://runboard.<account>.workers.dev`.
89
+
90
+ **2. Install and connect the Python client** on each machine or shared cluster home:
91
+
92
+ ```bash
93
+ pip install runboard
94
+ runboard configure https://runboard.<account>.workers.dev
95
+ ```
96
+
97
+ The command asks for the same token, verifies the deployment, and saves both values in
98
+ `~/.runboard/server.json` with mode `600`.
99
+
100
+ **3. Log from your training script:**
101
+
102
+ ```python
103
+ import runboard
104
+
105
+ runboard.init(project="resnet", config={"lr": 3e-4, "batch_size": 256})
106
+
107
+ for step in range(num_steps):
108
+ loss = train_step()
109
+ runboard.log({"train/loss": loss}, step=step)
110
+
111
+ if step % 500 == 0:
112
+ runboard.log({"eval/accuracy": evaluate()}, step=step)
113
+
114
+ runboard.finish()
115
+ ```
116
+
117
+ **4. Open the URL printed by `runboard configure`.** Charts update live every two seconds.
118
+
119
+ `runboard.init()` reads the saved endpoint automatically. On clusters with a shared home directory,
120
+ configure it once and every compute node uses it. For containers or separate machines, set
121
+ `RUNBOARD_SERVER` and `RUNBOARD_TOKEN` as environment variables.
122
+
123
+ ## How it works
124
+
125
+ ```text
126
+ cluster / VM your Cloudflare account anywhere
127
+ ┌──────────────────┐ HTTPS ┌───────────────────────────────┐ ┌───────────┐
128
+ │ train.py │ ──────────► │ Worker: auth + REST API │ ◄────► │ browser │
129
+ │ runboard.log() │ │ D1: runs + metric batches │ HTTPS │ dashboard │
130
+ │ retry + spool │ │ Assets: dashboard │ └───────────┘
131
+ └──────────────────┘ │ │
132
+ └───────────────────────────────┘
133
+ ```
134
+
135
+ 1. Your script appends metrics to an in-memory buffer. A background thread sends them to the
136
+ server once per second, in chunks of up to 5,000 rows.
137
+ 2. The Cloudflare Worker authenticates the request and stores run metadata and metric batches in D1.
138
+ Retried batches have deterministic IDs and are stored once.
139
+ 3. The dashboard is served by the same Worker. It polls every two seconds and fetches only batches it
140
+ has not seen.
141
+ 4. If Cloudflare or the network is unavailable, the client retries with backoff and writes remaining
142
+ data to `~/.runboard/spool/`. `runboard sync` sends it later.
143
+
144
+ More detail is in [docs/architecture.md](docs/architecture.md).
145
+
146
+ ## Logging API
147
+
148
+ ```python
149
+ import runboard
150
+
151
+ run = runboard.init(
152
+ project="my-project", # groups runs in the dashboard
153
+ name="baseline-lr3e-4", # display name (defaults to the run id)
154
+ config={"lr": 3e-4}, # hyperparameters, shown in run details
155
+ tags=["baseline"], # searchable in the sidebar
156
+ run_id=None, # pass an existing id to resume a run
157
+ server=None, token=None, # override server discovery
158
+ dir=None, # write to a shared directory instead of HTTP
159
+ )
160
+
161
+ runboard.log({"train/loss": 0.42, "train/lr": 1e-4}, step=100) # step is optional; auto-increments
162
+ runboard.finish() # also runs automatically at exit
163
+ ```
164
+
165
+ | Behavior | Details |
166
+ |---|---|
167
+ | Values | Python numbers, NumPy scalars, and 0-d torch tensors all work. NaN and inf become gaps; non-numeric values are ignored. |
168
+ | Sections | Metrics named `group/name` are grouped into dashboard sections (`train/…`, `eval/…`). |
169
+ | Status | Runs are marked `running`, `finished`, `crashed` (uncaught exception), or `killed` (Ctrl-C). A run that stops logging for 10 minutes is shown as `stale`. |
170
+ | Metadata | Hostname, command line, and `SLURM_JOB_ID` are recorded automatically. |
171
+ | Multi-GPU | Under `torchrun` or DDP, only rank 0 logs. `init()` and `log()` are no-ops where `RANK` or `LOCAL_RANK` is non-zero, so you can call them unconditionally. Pass `all_ranks=True` to log from every rank. |
172
+ | Context manager | `with runboard.Run(project="p") as run: run.log(...)` marks the run `crashed` if the block raises. |
173
+
174
+ ### When the server is unreachable
175
+
176
+ Network failures never run inside `log()`. If the server is down, rows are kept in memory and retried
177
+ with backoff; excess backlog is saved in the background. Rows still unsent when the job exits are written to
178
+ `~/.runboard/spool/`. Upload them later with:
179
+
180
+ ```bash
181
+ runboard sync
182
+ ```
183
+
184
+ ## Cloudflare deployment
185
+
186
+ The deploy button is the normal path. It uses [Cloudflare's automatic resource provisioning](https://developers.cloudflare.com/workers/platform/deploy-buttons/),
187
+ so every user receives isolated resources in their own account. A custom domain is optional; the
188
+ generated `workers.dev` address is stable and sufficient.
189
+
190
+ To deploy from a terminal instead:
191
+
192
+ ```bash
193
+ git clone https://github.com/Moe-Zbeeb/runboard && cd runboard
194
+ npm install
195
+ npx wrangler login
196
+ npm run deploy
197
+ ```
198
+
199
+ Generate a token, then store it as a Worker secret when Wrangler prompts for its value:
200
+
201
+ ```bash
202
+ python -c 'import secrets; print(secrets.token_urlsafe(32))'
203
+ npx wrangler secret put RUNBOARD_TOKEN
204
+ ```
205
+
206
+ The Worker creates its schema on the first authenticated request. The SQL migration is also kept in
207
+ `cloudflare/migrations/` for inspection and future upgrades.
208
+
209
+ For local Worker development, copy `.dev.vars.example` to `.dev.vars`, replace its value, and run
210
+ `npm run dev`. Wrangler keeps local D1 data under `.wrangler/`.
211
+
212
+ Cloudflare's free plan is enough for personal use and normal research runs, subject to its current
213
+ [Workers](https://developers.cloudflare.com/workers/platform/pricing/),
214
+ [D1](https://developers.cloudflare.com/d1/platform/pricing/) quotas. Large sweeps or very frequent logging can
215
+ exceed those quotas. Runboard batches writes, but log at a useful interval instead of every inner-loop
216
+ operation.
217
+
218
+ ## Running on a cluster
219
+
220
+ ### Recommended: send directly to Cloudflare
221
+
222
+ If the cluster home is shared, run `runboard configure` once on a login node. Otherwise inject the
223
+ settings into the job:
224
+
225
+ ```bash
226
+ export RUNBOARD_SERVER="https://runboard.<account>.workers.dev"
227
+ export RUNBOARD_TOKEN="your-token"
228
+ python train.py
229
+ ```
230
+
231
+ Only outbound HTTPS is required. There is no Runboard daemon on the login node and no tunnel to keep
232
+ alive.
233
+
234
+ ### Local server mode
235
+
236
+ When experiments must remain inside the cluster, run the included local server:
237
+
238
+ Run it in `tmux` or with `nohup` on the login node:
239
+
240
+ ```bash
241
+ nohup runboard serve --tunnel > ~/runboard.log 2>&1 &
242
+ grep public: ~/runboard.log
243
+ ```
244
+
245
+ If your cluster kills long processes on login nodes, run the server as a low-resource Slurm job
246
+ ([examples/slurm/serve.sbatch](examples/slurm/serve.sbatch)):
247
+
248
+ ```bash
249
+ sbatch examples/slurm/serve.sbatch
250
+ grep public: runboard-*.out
251
+ ```
252
+
253
+ ### Quick-tunnel URL notifications
254
+
255
+ The quick-tunnel URL changes whenever the server restarts. Use `--notify` to have each new URL pushed
256
+ to your phone through [ntfy](https://ntfy.sh). Install the app and subscribe to a private,
257
+ hard-to-guess topic:
258
+
259
+ ```bash
260
+ runboard serve --tunnel --notify https://ntfy.sh/my-private-topic-8f3k2
261
+ ```
262
+
263
+ ### Local server discovery
264
+
265
+ Nothing extra is needed. Jobs discover the server through `~/.runboard/server.json`. See
266
+ [examples/slurm/train.sbatch](examples/slurm/train.sbatch) and
267
+ [examples/slurm/sweep.sbatch](examples/slurm/sweep.sbatch) (a job-array learning-rate sweep).
268
+
269
+ ### Compute nodes can't reach the login node?
270
+
271
+ Some clusters block traffic between nodes. If they share a filesystem, skip HTTP and write straight
272
+ into the server's run directory:
273
+
274
+ ```python
275
+ runboard.init(project="resnet", dir="~/runboard-runs")
276
+ ```
277
+
278
+ or set `export RUNBOARD_DIR=~/runboard-runs` in your job script. The server picks the files up.
279
+
280
+ ## The dashboard
281
+
282
+ - **Runs sidebar.** Search, project filter, and status badges. Click a run's name to see its config and metadata.
283
+ - **Compare runs.** Select up to 8 runs. Each run keeps its color while it stays selected.
284
+ - **One chart per metric.** Hover for exact values, drag to zoom, double-click to reset. Legends show each run's latest value.
285
+ - **Controls.** EMA smoothing, log-scale y, x axis as step, relative time, or wall clock, a regex metric filter, and system/light/dark themes.
286
+ - **Long runs.** Charts retain peaks and dips while reducing very large series to a display-sized sample. Original metrics remain in the JSONL files.
287
+ - **Summary table.** Latest value of every metric for the selected runs.
288
+ - A responsive layout for phones and desktops.
289
+
290
+ ## Configuration
291
+
292
+ | Environment variable | Default | Purpose |
293
+ |---|---|---|
294
+ | `RUNBOARD_SERVER` | from `~/.runboard/server.json` | Server URL used by `init()` |
295
+ | `RUNBOARD_TOKEN` | from `~/.runboard/server.json` | Access token used by `init()` |
296
+ | `RUNBOARD_DIR` | unset | If set, `init()` writes files to this directory instead of using HTTP |
297
+ | `RUNBOARD_HOME` | `~/.runboard` | Where the token, server info, and spool files live |
298
+
299
+ ## CLI reference
300
+
301
+ | Command | Description |
302
+ |---|---|
303
+ | `runboard configure URL` | Verify and save a personal Cloudflare or other hosted endpoint. Prompts securely for its token; `--token` is available for automation. |
304
+ | `runboard serve` | Start the server. Options: `--dir` (default `./runboard-runs`), `--port` (default 8080, falls back to a free port if taken), `--host`, `--tunnel`, `--notify URL`, `--advertise URL` (address jobs should use) |
305
+ | `runboard ls [--dir D]` | List runs with status and last update |
306
+ | `runboard sync [files…]` | Upload spooled offline metrics |
307
+ | `runboard url` | Print the current dashboard URL |
308
+ | `runboard --version` | Print the version |
309
+
310
+ ## Security
311
+
312
+ - Every request, including the dashboard itself, needs the access token. For Cloudflare, each user
313
+ chooses it during deployment and stores it as a Worker secret. The local server generates one.
314
+ - Opening `/?token=…` stores the token in an `HttpOnly` cookie and removes it from the address bar.
315
+ - The hosted endpoint uses HTTPS. Run metadata and metric batches are stored in D1 inside the user's
316
+ Cloudflare account.
317
+ - **Anyone with the URL and the token can read your metrics.** Treat the full URL like a password. To
318
+ rotate a Cloudflare token, run `npx wrangler secret put RUNBOARD_TOKEN` from the repository and
319
+ re-run `runboard configure`. For a local server, delete `~/.runboard/token` and restart it.
320
+
321
+ ## FAQ
322
+
323
+ **How is this different from W&B or TensorBoard?**
324
+ W&B is a hosted service, so your metrics leave your infrastructure and you need an account.
325
+ TensorBoard needs port forwarding or a tunnel you set up yourself, and it reads event files.
326
+ runboard gives every user their own small Cloudflare backend and keeps the Python client dependency-free.
327
+
328
+ **Does it work on clusters with no internet access?**
329
+ Cloudflare mode needs outbound HTTPS from compute nodes. If that is blocked, use local file mode with
330
+ `RUNBOARD_DIR` and `runboard serve`, then access it over the internal network or an SSH tunnel.
331
+
332
+ **Can I log from any experiment?**
333
+ Yes. Log numeric metrics with any names, group them with `/`, and include any JSON-compatible
334
+ hyperparameters in the run config. The tracker has no framework dependency, so it can live in a plain
335
+ Python loop or a PyTorch, JAX, or other training script. Each backend retains the run's full metric
336
+ history.
337
+
338
+ **Can I analyze the data myself?**
339
+ Local mode stores every run as `<dir>/<project>/<run_id>/metrics.jsonl`:
340
+
341
+ ```python
342
+ import pandas as pd
343
+ df = pd.read_json("runboard-runs/resnet/20260924-101500-a1b2c3/metrics.jsonl", lines=True)
344
+ ```
345
+
346
+ ## Development
347
+
348
+ ```bash
349
+ git clone https://github.com/Moe-Zbeeb/runboard && cd runboard
350
+ python -m venv .venv && source .venv/bin/activate
351
+ pip install -e ".[dev]"
352
+ pytest
353
+ npm install
354
+ npm run check:cloud
355
+ ```
356
+
357
+ Try it end to end with `runboard serve` in one terminal and `python examples/quickstart.py` in another.
358
+ See [CONTRIBUTING.md](CONTRIBUTING.md) for details.
359
+
360
+ ## License
361
+
362
+ [MIT](LICENSE). Bundles [uPlot](https://github.com/leeoniya/uPlot) (MIT).