Trackio documentation

Артефакты

Hugging Face's logo
Join the Hugging Face community

and get access to the augmented documentation experience

to get started

Артефакты

Введение

Артефакт — это именованный пакет файлов с версионированием, привязанный к проекту: обученная модель, набор данных или результаты оценки. Логирование файлов в качестве артефактов (вместо их хранения на диске) позволяет:

  • Версионирование — каждый раз, когда вы фиксируете данные под одним и тем же именем, Trackio записывает новую версию (v0, v1, …). Идентичное содержимое дедуплицируется, поэтому повторная фиксация неизменённых файлов использует существующую версию.
  • Псевдонимы — указатели, такие как latest (назначается автоматически) или собственные (prod, best), которые всегда указывают на одну конкретную версию.
  • Происхождение — артефакты связаны с запусками, которые их создали и использовали, поэтому вы можете отследить, какой запуск создал модель и какие запуски её использовали.

Artifacts полностью работают в автономном режиме с локальным хранилищем и синхронизируются с Hugging Face Space (или self-hosted сервером), если он настроен, точно так же, как метрики.

Логирование артефакта

Самый быстрый способ — это напрямую записать файл или каталог. Это создаёт новый артефакт, добавляет путь и фиксирует его как выход текущего запуска:

import trackio

trackio.init(project="my_project")

# ... training ...

trackio.log_artifact("checkpoints/model.safetensors", type="model")

При передаче пути имя артефакта name по умолчанию принимает значение run-<run_id>-<basename>, а type — значение "unspecified". Передайте name и type, чтобы задать их явно:

trackio.log_artifact("checkpoints/", name="my-model", type="model")

Типы артефактов

Параметр type артефакта — это строка произвольной категории. Trackio не требует использования фиксированного списка типов, но распространены следующие соглашения:

ТипИспользуйте для
"model"Чекпоинты моделей, веса адаптеров, токенизаторы, конфигурации моделей
"dataset"Данные для обучения, валидации, тестирования или сгенерированные
"evaluation"Результаты оценки, предсказания, результаты бенчмарков, файлы с оценками
"report"Рисунки, графики, таблицы, ноутбуки или пакеты анализа, пригодные для чтения человеком
"unspecified"Значение по умолчанию, если тип не указан

Используйте стабильные имена типов в рамках проекта, чтобы последовательно фильтровать артефакты и использовать trackio.use_artifact(..., type="...") для проверки того, что запуск потребляет ожидаемый тип артефакта.

Явное создание артефакта

Для более точного управления — несколькими файлами, пользовательскими логическими путями, описанием или метаданными — создайте объект Artifact, добавьте в него файлы, а затем зафиксируйте его:

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)

После регистрации артефакт фиксируется, и его version, aliases, size и manifest заполняются:

print(logged.version)   # "v0"
print(logged.aliases)   # ("latest",)

Версии и псевдонимы

Каждый лог, созданный под существующим именем, формирует следующую целочисленную версию и переносит на неё псевдоним latest. Назначьте собственные псевдонимы с помощью aliases=:

trackio.log_artifact(artifact, aliases=["prod", "best"])

Алиасы — это перемещаемые указатели: при записи новой версии с тем же алиасом указатель перенаправляется на неё. Спецификаторы версий (v0, v1, …) зарезервированы и не могут использоваться в качестве алиасов.

Если вы записываете контент, идентичный существующей версии, Trackio переиспользует эту версию вместо создания новой. Любые явно переданные вами aliases по-прежнему будут переназначены на неё, но latest остаётся на месте — повторная запись идентичного (или более старого) контента никогда не сдвигает его назад.

Использование артефакта

use_artifact() загружает артефакт и регистрирует его как input для текущего запуска, что и формирует связь между запусками, использующими артефакт, и самим артефактом:

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

Передайте type, чтобы проверить тип артефакта и вызвать исключение, если он не совпадает:

artifact = trackio.use_artifact("my-model", type="model")

Загрузка файлов

use_artifact возвращает Artifact, файлы которого вы материализуете с помощью download():

artifact = trackio.use_artifact("my-model:latest")
path = artifact.download()
# files are now under ./.trackio/artifact-downloads/my_project/my-model_v2/

По умолчанию файлы записываются в ./.trackio/artifact-downloads/<project>/<name>_v<version>/, с ключом по проекту, чтобы артефакты с одинаковыми именами из разных проектов не конфликтовали; передайте root, чтобы выбрать другую директорию. download() идемпотентен — уже существующие файлы пропускаются — а если запуск поддерживается Space, любой отсутствующий локально файл загружается из удалённого хранилища.

Ссылки на внешние данные

Некоторые данные слишком велики для копирования или уже хранятся в постоянном хранилище, дублирование которого нежелательно. add_reference регистрирует такие данные в манифесте по URI без размещения каких-либо байтов в хранилище Trackio, поэтому вы по-прежнему получаете версионирование, дедупликацию, алиасы и прослеживаемость происхождения для данных, которые никуда не перемещаются:

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)

Встроенные схемы — file://, http(s):// и hf:// — поддерживаются существующими зависимостями Trackio. Любая другая схема (s3://, gs://, URL Azure blob, …) поддерживается путем регистрации пользовательского обработчика; полные примеры для S3, GCS и Azure приведены в разделе Ссылки на другие хранилища ниже. При checksum=True (значение по умолчанию) Trackio опрашивает источник, чтобы заполнить size и контрольную сумму:

  • file:// хешируется потоковым способом в sha256; его размер считывается с диска.
  • hf:// использует LFS sha256 (или git blob id); http(s):// использует серверный ETag.

Каждая контрольная сумма хранится в едином поле digest — это sha256 для локальных/LFS-ссылок или ETag провайдера для HTTP- или object-store-ссылок.

URI, указывающий на каталог (file://) или путь к репозиторию (hf://), раскрывается в одну запись ссылки на каждый объект, каждая из которых именуется своим путём относительно префикса (вложенной под name=, если он задан). max_objects (по умолчанию 10 000) ограничивает раскрытие и вызывает ошибку при превышении.

Если контрольную сумму получить невозможно — например, на сервере нет ETag — ссылка всё равно записывается, при этом в качестве digest используется сам URI, и выводится предупреждение. Исключение составляет локальный путь file://, который не существует: в этом случае возникает ошибка, а запись не создаётся, поскольку отсутствие локального файла почти всегда является ошибкой — для подготовки локального файла используйте add_file. Передайте checksum=False, чтобы пропустить проверку контрольной суммы для каждого объекта — тогда для одного объекта будет записан только его URI — и name=, чтобы задать логический путь.

Идентичность версии ссылки определяется её URI, размером и digest, поэтому повторное логирование идентичных ссылок приводит к дедупликации до одной версии, тогда как изменение URI, размера или контрольной суммы создаёт новую версию — точно так же, как при изменении содержимого файла. Если URI используется в качестве digest, идентичность отслеживает именно URI, а не содержимое.

URI записываются дословно, а манифесты можно синхронизировать с набором данных Hugging Face. URI, содержащий учётные данные — предподписанный URL https:// или токен Azure SAS, — раскроет эти учётные данные любому, кто может читать набор данных, поэтому add_reference предупреждает, если строка запроса URI выглядит как подписанная; предпочтительнее использовать канонический неподписанный URI объекта (например, s3://bucket/key).

Справочные байты никогда не загружаются в Space или бакет, но их можно получить по запросу. download() скачивает каждый объект, на который есть ссылка, в каталог (через httpx2, huggingface_hub или ваш зарегистрированный обработчик), что может привести к передаче большого объёма данных. Перед получением источник каждого объекта повторно проверяется, а его текущая контрольная сумма сравнивается с записанной — в случае несовпадения выводится предупреждение о том, что источник мог измениться, но загрузка продолжается. Каждый объект записывается атомарно, а тот, который уже присутствует на диске, пропускается без повторной проверки источника, поэтому повторные вызовы дёшевы и идемпотентны:

artifact = trackio.use_artifact("training-set:latest")
path = artifact.download()   # downloads staged files AND referenced objects

Чтобы работать с указателями без загрузки, считывайте их из артефакта:

print(artifact.references)                  # [{'path': ..., 'ref': 's3://...', 'size': ..., 'digest': ...}, ...]
print(artifact.get_entry_uri("eval.json"))  # "s3://my-bucket/eval.json"

Недоступная ссылка (отсутствующий локальный файл, недостижимый URL или схема без зарегистрированного обработчика) вызывает исключение, когда download() доходит до неё.

Ссылки на другие хранилища (S3, GCS, Azure)

Trackio намеренно не включает SDK облачных провайдеров. Чтобы ссылаться на объекты в хранилище, с которым он не работает нативно, создайте подкласс trackio.ReferenceHandler и зарегистрируйте его экземпляр с помощью trackio.register_reference_handler. Обработчик реализует четыре метода:

  • matches(scheme, uri) — определяет, принадлежит ли данный обработчик указанному URI. Зарегистрированные обработчики опрашиваются до встроенных, поэтому обработчик может перехватывать конкретные хосты https:// (как в примере с Azure ниже) раньше универсального HTTP-обработчика.
  • resolve(uri, checksum, max_objects) — исследует источник и возвращает список записей trackio.ResolvedReference: одна запись с relkey=None для одиночного объекта или по одной записи на каждый объект (с relkey, установленным в путь объекта относительно префикса), если URI обозначает префикс. Устанавливайте size и digest, если источник можно исследовать (ETag провайдера отлично подходит в качестве дайджеста — Trackio рассматривает дайджесты ссылок как непрозрачные токены); возвращайте их как None, чтобы зафиксировать ссылку без проверки контрольной суммы, в этом случае Trackio использует URI в качестве дайджеста и выводит предупреждение.
  • fetch(uri, dest) — загрузка одного объекта по адресу uri в локальный путь dest (вызывается из Artifact.download()).
  • hint() — однострочное сообщение с рекомендацией по действию, отображаемое в предупреждении, если resolve не сформировал контрольную сумму.

Ключи, обнаруженные при расширении префикса, должны быть закодированы с помощью percent-encoding при синтезе uri каждого элемента (и декодированы в fetch), поскольку URI манифеста не могут содержать пробелы.

S3 (s3://, через boto3)

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://, через google-cloud-storage)

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 (через azure-storage-blob + azure-identity)

Объекты Azure blobs адресуются обычными URL-адресами https:// (https://<account>.blob.core.windows.net/<container>/<blob>), поэтому этот обработчик заявляет их по суффиксу хоста — его регистрация обеспечивает приоритет над универсальным HTTP-обработчиком Trackio:

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())

Эти обработчики вызывают исключение, если источник недоступен для проверки. Если в таком случае вы предпочитаете записать ссылку без контрольной суммы (например, при каталогизации данных, защищенных аутентификацией, к которой нет доступа во время логирования), перехватите ошибку провайдера внутри resolve и верните [trackio.ResolvedReference(relkey=None, uri=uri)] — Trackio использует URI в качестве дайджеста и выводит предупреждение, используя ваш hint().

Регистрируйте обработчики в каждом процессе, который добавляет или загружает такие ссылки: Artifact.download() требует обработчик для получения байтов, а URI со схемой, для которой не зарегистрирован обработчик, вызывает ошибку.

Просмотр артефактов в панели управления

Вкладка Artifacts на панели управления (появляется, когда в проекте есть артефакты) содержит список артефактов, сгруппированных по типу, с отображением для каждой версии её псевдонимов, размера и количества файлов. При выборе версии отображается её манифест файлов — нажмите на файл, чтобы скачать его, — а также её метаданные и происхождение: запуск, который создал артефакт, и запуски, которые его использовали, каждый из которых ведёт на страницу с деталями соответствующего запуска.

Каждая версия также содержит раздел Lineage, в котором отображается интерактивный граф происхождения версии: версии артефактов и запуски представлены в виде узлов, со стрелками от запуска к записанным им артефактам и от артефакта к запускам, которые его использовали. Выбранная версия подсвечивается. Чтобы крупные графы оставались читаемыми, пять и более похожих узлов с идентичными связями (например, множество контрольных точек, записанных одним запуском) группируются в единый узел cluster с отображением количества — щелкните по кластеру, чтобы найти его элементы и вернуть отдельные узлы обратно в граф. Перетаскивайте для перемещения, используйте Ctrl/Cmd + прокрутку (или кнопки +/−) для масштабирования, а щелчок по любому узлу откроет сводку со ссылкой на соответствующий запуск или версию артефакта.

Страницы с деталями запуска отображают ту же линейную историю, но в обратном направлении, с разделами Output artifacts и Input artifacts, которые ведут обратно на вкладку Artifacts.

Удаленное хранилище

Если ваш запуск нацелен на Hugging Face Space или самохостинговый сервер (см. Track), файлы артефактов адресуются по содержимому и загружаются один раз: Trackio пропускает блобы, которые уже есть на сервере, поэтому повторная запись общих файлов обходится дёшево. Метаданные артефактов и блобы сохраняются вместе с остальными данными запуска в настроенный HF Dataset или бакет.

Update on GitHub