Artifacts
Artifacts are versioned, content-addressed files or directories with cross-run lineage tracking. Use them for datasets, model checkpoints, and any other outputs you want to version alongside your runs.
Logging an Artifact
Session.log_artifact copies a file or directory into the blob store,
assigns a version, and records it as an output of the current run.
open Munin
let () =
Session.with_run ~experiment:"pipeline" ~name:"prepare-data"
~tags:[ "data" ]
@@ fun session ->
(* ... produce a dataset file ... *)
let _artifact =
Session.log_artifact session
~name:"measurements"
~kind:`dataset
~path:"data/measurements.csv"
~metadata:[ ("rows", `Int 10000); ("format", `String "csv") ]
~aliases:[ "latest" ]
()
in
()
Parameters:
~name-- logical name for the artifact (e.g."measurements","mnist-cnn").~kind-- one of`checkpoint,`model,`dataset,`file,`dir,`other.~path-- path to the file or directory to store.~metadata-- optional key-value pairs attached to the version.~aliases-- optional alias list (e.g.["latest"; "best"]).
Artifact Kinds
The kind field is a semantic label. It does not affect storage; all
artifacts are stored the same way.
| Kind | Use for |
|---|---|
`checkpoint |
Training checkpoints (model + optimizer state) |
`model |
Final model weights |
`dataset |
Datasets and data splits |
`file |
Single files (configs, logs, reports) |
`dir |
Directory trees |
`other |
Anything else |
Versioning
Each call to log_artifact with the same ~name creates a new
version: v1, v2, v3, and so on. Versions are immutable once
created.
(* First call creates v1. *)
let v1 =
Session.log_artifact session ~name:"model" ~kind:`model
~path:"model_epoch1.safetensors" ()
in
(* Second call creates v2. *)
let v2 =
Session.log_artifact session ~name:"model" ~kind:`model
~path:"model_epoch2.safetensors" ()
in
Printf.printf "%s %s\n" (Artifact.version v1) (Artifact.version v2)
(* prints: v1 v2 *)
Aliases
Aliases are mutable pointers to a specific version. Common aliases:
"latest"-- the most recent version"best"-- the best-performing version
Pass ~aliases when logging to attach them. When a new version gets
the same alias, it moves from the old version to the new one.
Resolve an alias through the store:
let store = Store.open_ () in
match Store.find_artifact store ~name:"model" ~version:"latest" with
| Some artifact ->
Printf.printf "resolved to %s\n" (Artifact.version artifact)
| None ->
Printf.printf "not found\n"
Store.find_artifact accepts both explicit versions ("v2") and
aliases ("latest").
Content-Addressed Deduplication
Artifact payloads are stored in a blob directory keyed by their SHA-256 digest. If two versions have identical content, only one copy is stored on disk.
(* These share the same blob if the file content is identical. *)
let a = Session.log_artifact session ~name:"config" ~kind:`file
~path:"config.yaml" () in
let b = Session.log_artifact session ~name:"config" ~kind:`file
~path:"config.yaml" () in
assert (Artifact.digest a = Artifact.digest b)
Store.gc removes blobs that are no longer referenced by any artifact
version.
Lineage
Producer
When you call Session.log_artifact, the current run is automatically
recorded as the producer:
let artifact =
Session.log_artifact session ~name:"features" ~kind:`dataset
~path:"features.csv" ()
in
Artifact.producer_run_id artifact (* Some "<run_id>" *)
Consumer
Session.use_artifact records the current run as a consumer of an
existing artifact:
(* Run 2 consumes the artifact produced by Run 1. *)
let store = Store.open_ () in
match Store.find_artifact store ~name:"features" ~version:"latest" with
| Some artifact ->
Session.use_artifact session artifact;
let path = Artifact.path artifact in
Printf.printf "loading from: %s\n" path
| None ->
failwith "artifact not found"
After this, the lineage is recorded in both directions:
Artifact.producer_run_id artifact (* run that created it *)
Artifact.consumer_run_ids artifact (* runs that consumed it *)
Run.output_artifacts run1 (* artifacts produced by run1 *)
Run.input_artifacts run2 (* artifacts consumed by run2 *)
Loading Artifacts
From a Store
Store.find_artifact resolves by name and version (or alias):
let store = Store.open_ () in
match Store.find_artifact store ~name:"mnist-cnn" ~version:"best" with
| Some artifact ->
let path = Artifact.path artifact in
Printf.printf "path: %s (%d bytes)\n" path (Artifact.size_bytes artifact)
| None ->
Printf.printf "not found\n"
Listing Artifacts
Store.list_artifacts supports filtering by name, kind, alias, and
lineage:
let store = Store.open_ () in
(* All artifacts. *)
let all = Store.list_artifacts store () in
(* Only checkpoints. *)
let checkpoints = Store.list_artifacts store ~kind:`checkpoint () in
(* Only artifacts produced by a specific run. *)
let from_run = Store.list_artifacts store ~producer_run:"<RUN_ID>" () in
Printf.printf "total: %d, checkpoints: %d, from run: %d\n"
(List.length all) (List.length checkpoints) (List.length from_run)
From the CLI
# List all artifacts.
munin artifacts
# Filter by name.
munin artifacts --name mnist-cnn
Complete Example: Cross-Run Lineage
A data-preparation run produces a dataset. A training run consumes it and produces a model checkpoint.
open Munin
let write_file path text =
let oc = open_out path in
Fun.protect ~finally:(fun () -> close_out oc)
(fun () -> output_string oc text)
let () =
(* Run 1: produce a dataset. *)
Session.with_run ~experiment:"pipeline" ~name:"prepare-data"
~tags:[ "data" ]
@@ fun session ->
write_file "/tmp/data.csv" "x,y\n1.0,2.0\n3.0,4.0\n";
ignore
(Session.log_artifact session ~name:"training-data" ~kind:`dataset
~path:"/tmp/data.csv"
~metadata:[ ("rows", `Int 2) ]
~aliases:[ "latest" ] ())
let () =
(* Run 2: consume the dataset, produce a model. *)
let store = Store.open_ () in
Session.with_run ~experiment:"pipeline" ~name:"train"
~tags:[ "training" ]
@@ fun session ->
let dataset =
match Store.find_artifact store ~name:"training-data" ~version:"latest" with
| Some a -> a
| None -> failwith "dataset not found"
in
Session.use_artifact session dataset;
Printf.printf "training on: %s\n" (Artifact.path dataset);
(* ... train model ... *)
write_file "/tmp/model.bin" "model weights";
ignore
(Session.log_artifact session ~name:"my-model" ~kind:`model
~path:"/tmp/model.bin"
~aliases:[ "latest" ] ())
Garbage Collection
Store.gc removes blobs not referenced by any artifact version:
let store = Store.open_ () in
let removed = Store.gc store in
Printf.printf "removed %d unreferenced blobs\n" removed