Buckets:

qgallouedec's picture
|
download
raw
19.6 kB
# Artifacts
## Introduction
An **artifact** is a versioned, named bundle of files attached to a project — a trained model, a dataset, a set of evaluation outputs. Logging files as artifacts (rather than leaving them on disk) gives you:
- **Versioning** — each time you log under the same name, Trackio records a new version (`v0`, `v1`, ...). Identical content is de-duplicated, so re-logging unchanged files reuses the existing version.
- **Aliases** — moving pointers such as `latest` (assigned automatically) or your own (`prod`, `best`) that always resolve to a single version.
- **Lineage** — artifacts are linked to the runs that produced and consumed them, so you can trace which run created a model and which runs used it.
Artifacts work fully offline against local storage, and sync to a Hugging Face Space (or self-hosted server) when one is configured, exactly like metrics.
## Logging an artifact
The quickest way is to log a file or directory directly. This creates a new artifact, adds the path, and records it as an output of the current run:
```python
import trackio
trackio.init(project="my_project")
# ... training ...
trackio.log_artifact("checkpoints/model.safetensors", type="model")
```
When you pass a path, the artifact `name` defaults to `run-<run_id>-<basename>` and `type` defaults to `"unspecified"`. Pass `name` and `type` to set them explicitly:
```python
trackio.log_artifact("checkpoints/", name="my-model", type="model")
```
### Artifact types
Artifact `type` is a free-form category string. Trackio does not enforce a fixed list of types, but common conventions are:
| Type | Use it for |
| --- | --- |
| `"model"` | Model checkpoints, adapter weights, tokenizers, model configs |
| `"dataset"` | Training, validation, test, or generated datasets |
| `"evaluation"` | Evaluation outputs, predictions, benchmark results, score files |
| `"report"` | Figures, plots, tables, notebooks, or human-readable analysis bundles |
| `"unspecified"` | The default when no type is provided |
Use stable type names within a project so you can filter artifacts consistently and use `trackio.use_artifact(..., type="...")` to assert that a run is consuming the expected kind of artifact.
### Building an artifact explicitly
For finer control — multiple files, custom logical paths, a description, or metadata — construct an [Artifact](/docs/trackio/pr_658/en/api#trackio.Artifact), add files to it, then log it:
```python
artifact = trackio.Artifact(
name="my-model",
type="model",
description="Fine-tuned on the v2 dataset",
metadata={"base_model": "bert-base", "epochs": 3},
)
artifact.add_file("checkpoints/model.safetensors")
artifact.add_file("checkpoints/config.json")
artifact.add_dir("tokenizer/")
logged = trackio.log_artifact(artifact)
```
After logging, the artifact is frozen and its `version`, `aliases`, `size`, and `manifest` are populated:
```python
print(logged.version) # "v0"
print(logged.aliases) # ("latest",)
```
### Versions and aliases
Each log under an existing name creates the next integer version and moves the `latest` alias onto it. Assign your own aliases with `aliases=`:
```python
trackio.log_artifact(artifact, aliases=["prod", "best"])
```
Aliases are moving pointers: logging a new version with the same alias re-points it. Version specifiers (`v0`, `v1`, ...) are reserved and cannot be used as aliases.
If you log content identical to an existing version, Trackio reuses that version instead of creating a new one. Any explicit `aliases` you pass still rotate onto it, but `latest` is left where it is — re-logging identical (or older) content never moves it backward.
## Using an artifact
[use_artifact()](/docs/trackio/pr_658/en/api#trackio.use_artifact) fetches an artifact and records it as an **input** to the current run, which is what builds lineage between a consuming run and the artifact it used:
```python
trackio.init(project="my_project")
artifact = trackio.use_artifact("my-model") # resolves to :latest
artifact = trackio.use_artifact("my-model:v2") # pin a version
artifact = trackio.use_artifact("my-model:prod") # resolve an alias
```
Pass `type` to assert the artifact's type, raising if it doesn't match:
```python
artifact = trackio.use_artifact("my-model", type="model")
```
### Downloading files
`use_artifact` returns an [Artifact](/docs/trackio/pr_658/en/api#trackio.Artifact) whose files you materialize with `download()`:
```python
artifact = trackio.use_artifact("my-model:latest")
path = artifact.download()
# files are now under ./.trackio/artifact-downloads/my_project/my-model_v2/
```
By default, files are written to `./.trackio/artifact-downloads/<project>/<name>_v<version>/`, keyed by project so same-named artifacts from different projects never collide; pass `root` to choose another directory. `download()` is idempotent — files already present are skipped — and when the run is backed by a Space, any file missing locally is fetched from the remote.
## Referencing external data
Some data is too large to copy, or already lives in durable storage you don't want duplicated. `add_reference` records such data by **URI** in the manifest *without staging any bytes* into Trackio's storage, so you still get versioning, de-duplication, aliases, and lineage over data that never moves:
```python
artifact = trackio.Artifact(name="training-set", type="dataset")
artifact.add_reference("file:///mnt/nvme/datasets/corpus.parquet")
artifact.add_reference("file:///mnt/nvme/datasets/shards/") # a directory — expands to one entry per file
artifact.add_reference("https://example.com/data/eval.json")
artifact.add_reference("hf://datasets/username/my-dataset/train.parquet")
trackio.log_artifact(artifact)
```
Built-in schemes are `file://`, `http(s)://`, and `hf://` — all served by Trackio's existing dependencies. Any other scheme (`s3://`, `gs://`, Azure blob URLs, ...) is supported by registering a custom handler; see [Referencing other stores](#referencing-other-stores-s3-gcs-azure) below for complete S3, GCS, and Azure examples. With `checksum=True` (the default), Trackio probes the source to fill in `size` and a checksum:
- `file://` is stream-hashed to a sha256; its size is read from disk.
- `hf://` uses the LFS sha256 (or the git blob id); `http(s)://` uses the server `ETag`.
Every checksum is stored in a single `digest` field — the sha256 for local/LFS references, or the provider's ETag for HTTP or object-store references.
A URI that denotes a directory (`file://`) or a repo path (`hf://`) is **expanded** into one reference entry per object, each named by its path relative to the prefix (nested under `name=` when given). `max_objects` (default 10,000) caps the expansion and raises if exceeded.
When no checksum can be obtained — for example a server with no `ETag` — the reference is still recorded, using **the URI itself as the `digest`**, and a warning is emitted. A local `file://` path that doesn't exist is the exception: it raises rather than being recorded, since a missing local file is almost always a mistake — use `add_file` to stage a local file. Pass `checksum=False` to skip the per-object checksum — a single object then records just its URI — and `name=` to set the logical path.
A reference's version identity comes from its URI, size, and `digest`, so re-logging identical references de-duplicates to one version, while a changed URI, size, or checksum creates a new one — exactly like file content. When the URI is used as the `digest`, identity therefore tracks the URI and not the content.
URIs are recorded verbatim, and manifests can be synced to a Hugging Face Dataset. A URI that embeds a credential — a presigned `https://` URL or an Azure SAS token — would expose that credential to anyone who can read the Dataset, so `add_reference` warns when a URI's query string looks signed; prefer the object's canonical unsigned URI (e.g. `s3://bucket/key`).
Reference bytes are never uploaded to a Space or bucket, but they can be fetched on demand. `download()` downloads each referenced object into the directory (via `httpx`, `huggingface_hub`, or your registered handler), which can transfer a large amount of data. Before fetching, each object's source is re-probed and its current checksum compared against the recorded one — a mismatch warns that the source may have changed but the download proceeds. Each object is written atomically, and one already present on disk is skipped without re-checking the source, so repeated calls are cheap and idempotent:
```python
artifact = trackio.use_artifact("training-set:latest")
path = artifact.download() # downloads staged files AND referenced objects
```
To work with the pointers *without* downloading, read them from the artifact instead:
```python
print(artifact.references) # [{'path': ..., 'ref': 's3://...', 'size': ..., 'digest': ...}, ...]
print(artifact.get_entry_uri("eval.json")) # "s3://my-bucket/eval.json"
```
An unfetchable reference (a missing local file, an unreachable URL, or a scheme with no registered handler) raises when `download()` reaches it.
## Referencing other stores (S3, GCS, Azure)
Trackio deliberately ships no cloud-vendor SDKs. To reference objects in a store it doesn't handle natively, subclass `trackio.ReferenceHandler` and register an instance with `trackio.register_reference_handler`. A handler implements four methods:
- `matches(scheme, uri)` — whether this handler owns the URI. Registered handlers are consulted *before* the built-ins, so a handler may also claim specific `https://` hosts (as the Azure example below does) ahead of the generic HTTP handler.
- `resolve(uri, checksum, max_objects)` — probe the source and return a list of `trackio.ResolvedReference` entries: one entry with `relkey=None` for a single object, or one entry per object (with `relkey` set to the object's path relative to the prefix) when the URI denotes a prefix. Set `size` and `digest` when the source can be probed (the provider's ETag works fine as a digest — Trackio treats reference digests as opaque tokens); return them as `None` to record the reference un-checksummed, in which case Trackio falls back to the URI as the digest and warns.
- `fetch(uri, dest)` — download the single object at `uri` to the local path `dest` (called by `Artifact.download()`).
- `hint()` — a one-line actionable message shown in the warning when `resolve` produced no checksum.
Keys discovered during prefix expansion should be percent-encoded when synthesizing each entry's `uri` (and decoded in `fetch`), since manifest URIs may not contain spaces.
### S3 (`s3://`, via `boto3`)
```python
from urllib.parse import quote, unquote, urlsplit
import boto3
import trackio
class S3Handler(trackio.ReferenceHandler):
def matches(self, scheme, uri):
return scheme == "s3"
def resolve(self, uri, checksum, max_objects):
parts = urlsplit(uri)
bucket, key = parts.netloc, unquote(parts.path.lstrip("/"))
client = boto3.client("s3")
if key and not key.endswith("/"):
head = client.head_object(Bucket=bucket, Key=key)
return [
trackio.ResolvedReference(
relkey=None,
uri=uri,
size=int(head["ContentLength"]),
digest=head["ETag"].strip('"') if checksum else None,
)
]
base = key[: key.rfind("/") + 1]
entries = []
pages = client.get_paginator("list_objects_v2").paginate(
Bucket=bucket, Prefix=key
)
for page in pages:
for obj in page.get("Contents", []):
if obj["Key"].endswith("/"):
continue
if len(entries) >= max_objects:
raise ValueError(
f"{uri} expands to more than {max_objects} objects; "
"pass max_objects= to raise the limit."
)
entries.append(
trackio.ResolvedReference(
relkey=obj["Key"][len(base):],
uri=f"s3://{bucket}/{quote(obj['Key'], safe='/')}",
size=int(obj["Size"]),
digest=obj["ETag"].strip('"') if checksum else None,
)
)
return entries
def fetch(self, uri, dest):
parts = urlsplit(uri)
with open(dest, "wb") as f:
boto3.client("s3").download_fileobj(
parts.netloc, unquote(parts.path.lstrip("/")), f
)
def hint(self):
return "Configure AWS credentials so the object can be probed for an ETag."
trackio.register_reference_handler(S3Handler())
artifact = trackio.Artifact(name="training-set", type="dataset")
artifact.add_reference("s3://my-bucket/shards/") # expands to one entry per object
trackio.log_artifact(artifact)
```
### Google Cloud Storage (`gs://`, via `google-cloud-storage`)
```python
from urllib.parse import quote, unquote, urlsplit
from google.cloud import storage
import trackio
class GcsHandler(trackio.ReferenceHandler):
def matches(self, scheme, uri):
return scheme == "gs"
def resolve(self, uri, checksum, max_objects):
parts = urlsplit(uri)
bucket, key = parts.netloc, unquote(parts.path.lstrip("/"))
client = storage.Client()
if key and not key.endswith("/"):
blob = client.bucket(bucket).get_blob(key)
if blob is None:
raise ValueError(f"No such GCS object: {uri}")
return [
trackio.ResolvedReference(
relkey=None,
uri=uri,
size=blob.size,
digest=blob.etag.strip('"') if checksum else None,
)
]
base = key[: key.rfind("/") + 1]
entries = []
for blob in client.list_blobs(bucket, prefix=key):
if blob.name.endswith("/"):
continue
if len(entries) >= max_objects:
raise ValueError(
f"{uri} expands to more than {max_objects} objects; "
"pass max_objects= to raise the limit."
)
entries.append(
trackio.ResolvedReference(
relkey=blob.name[len(base):],
uri=f"gs://{bucket}/{quote(blob.name, safe='/')}",
size=blob.size,
digest=blob.etag.strip('"') if checksum else None,
)
)
return entries
def fetch(self, uri, dest):
parts = urlsplit(uri)
key = unquote(parts.path.lstrip("/"))
storage.Client().bucket(parts.netloc).blob(key).download_to_filename(
str(dest)
)
def hint(self):
return "Configure GCP credentials so the object can be probed for an ETag."
trackio.register_reference_handler(GcsHandler())
```
### Azure Blob Storage (via `azure-storage-blob` + `azure-identity`)
Azure blobs are addressed by plain `https://` URLs (`https://<account>.blob.core.windows.net/<container>/<blob>`), so this handler claims them by host suffix — registering it makes it win over Trackio's generic HTTP handler:
```python
from urllib.parse import quote, unquote, urlsplit
from azure.identity import DefaultAzureCredential
from azure.storage.blob import BlobServiceClient
import trackio
class AzureHandler(trackio.ReferenceHandler):
def matches(self, scheme, uri):
return scheme in ("http", "https") and urlsplit(
uri
).netloc.lower().endswith(".blob.core.windows.net")
def _split(self, uri):
parts = urlsplit(uri)
container, _, blob_name = parts.path.lstrip("/").partition("/")
return f"{parts.scheme}://{parts.netloc}", container, unquote(blob_name)
def _service(self, account_url):
return BlobServiceClient(account_url, credential=DefaultAzureCredential())
def resolve(self, uri, checksum, max_objects):
account_url, container, blob_name = self._split(uri)
service = self._service(account_url)
if blob_name and not blob_name.endswith("/"):
props = service.get_blob_client(container, blob_name).get_blob_properties()
return [
trackio.ResolvedReference(
relkey=None,
uri=uri,
size=props.size,
digest=props.etag.strip('"') if checksum else None,
)
]
base = blob_name[: blob_name.rfind("/") + 1]
entries = []
container_client = service.get_container_client(container)
for blob in container_client.list_blobs(name_starts_with=blob_name):
if blob.name.endswith("/"):
continue
if len(entries) >= max_objects:
raise ValueError(
f"{uri} expands to more than {max_objects} objects; "
"pass max_objects= to raise the limit."
)
entries.append(
trackio.ResolvedReference(
relkey=blob.name[len(base):],
uri=f"{account_url}/{container}/{quote(blob.name, safe='/')}",
size=blob.size,
digest=blob.etag.strip('"') if checksum else None,
)
)
return entries
def fetch(self, uri, dest):
account_url, container, blob_name = self._split(uri)
downloader = (
self._service(account_url)
.get_blob_client(container, blob_name)
.download_blob()
)
with open(dest, "wb") as f:
downloader.readinto(f)
def hint(self):
return "Configure Azure credentials so the blob can be probed for an ETag."
trackio.register_reference_handler(AzureHandler())
```
These handlers raise when the source can't be probed; if you'd rather record an un-checksummed reference in that case (for example, when cataloging data behind auth you can't reach at log time), catch the provider error inside `resolve` and return `[trackio.ResolvedReference(relkey=None, uri=uri)]` — Trackio will fall back to the URI as the digest and warn, using your `hint()`.
Register handlers in every process that adds *or downloads* such references: `Artifact.download()` needs the handler to fetch the bytes, and a URI whose scheme has no registered handler raises.
## Browsing artifacts in the dashboard
The dashboard's **Artifacts** tab (shown once a project has artifacts) lists artifacts grouped by type, with each version's aliases, size, and file count. Selecting a version shows its file manifest — click a file to download it — plus its metadata and lineage: the run that produced it and the runs that used it, each linking to the run's detail page.
Run detail pages show the same lineage from the other direction, with **Output artifacts** and **Input artifacts** sections linking back to the Artifacts tab.
## Remote storage
When your run targets a Hugging Face Space or self-hosted server (see [Track](track)), artifact files are content-addressed and uploaded once: Trackio skips blobs the server already has, so re-logging shared files is cheap. Artifact metadata and blobs are persisted alongside your other run data to the configured HF Dataset or bucket.

Xet Storage Details

Size:
19.6 kB
·
Xet hash:
82be993452acf7025e1001e155004426d39ef8893a2327b89c29cd6838dbe0f0

Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.