Cursus
Logging metrics & artifacts
Cursus logs everything through a module-level singleton — call cursus.log() from anywhere in your training code.
Recording metrics
- Keys are free-form dotted paths (
"train/loss","eval/acc") - Values must be numeric (ints, floats — anything
float()accepts) - Always pass an explicit
step— omitting it warns and records step0, collapsing your curve onto one x-position
Background flushing
Points are queued and flushed in a background thread every 5 seconds or every 50 points, whichever comes first, as one bulk insert.
log()never blocks traininglog()never raises — network failures retry 3 times, then warn and drop the batch- A dead server costs you points, never a crashed training job
Calling log() before init() (or after finish()) warns and drops.
Logging images
imageis a file path, raw PNG/JPEG/WEBP bytes (sniffed by magic bytes), a PIL Image, or a numpy array- The last two need Pillow installed — it stays an optional, lazily-imported extra
- Images upload direct to object storage over a short-lived presigned URL (plain
requestsPUT, no boto) — the server only signs - Caps: 5 MB per image, 500 images per run; re-logging a
(key, step)overwrites the previous bytes - Same never-raises contract: bad inputs, oversize files, and network failures warn and drop
View images on the run's Charts tab: one image at a time with a step scrubber, per key. URLs expire after 15 minutes — reload the page for fresh ones.
Attaching artifacts
- Each call creates a new version of the named artifact in the run's project (artifacts belong to projects, not runs)
- Accepts one path or a list; only file names (not directories) are stored; at most 1000 files per call
typeis a free string ("model","dataset", …);descriptionis shown on the version- Uploads allow up to ~100 MB per file with a longer (120s) timeout
- Call before
finish()— versions record the producing run
Config sync
- The
initconfig is the frozen snapshot.run.config/cursus.configare the same mutable dict update()syncs back debounced (one PATCH per 5s burst) andfinish()flushes, so the final config always lands- Server merge is deep (nested dicts recurse, arrays/scalars replace), last-write-wins, capped at 100 top-level keys / 32 KB
- Finished runs are immutable: updates after
finish()stay local-only (the server answers 409, surfaced as a warning, never an exception)
Return values
| Call | Success | Skip/failure |
|---|---|---|
init() | Run (.id, .name, .url, .config) | raises |
log() | None | warns, drops |
finish() | None | warns (no active run: silent) |
config.update() | None (debounced sync) | warns on sync failure |
log_artifact() | version payload dict | None + warning |
log_image() | media payload dict | None + warning |
Design rule: init() and sweep control calls may raise; everything else degrades to warnings. Training jobs must survive Cursus outages.
What's next
- Sweep Guide — grid and random hyperparameter search
- Troubleshooting
- Track a Training Run (cookbook)
