Source code for tlc_plugin_sdk.shared.model_storage
# Copyright 2026 3LC Inc.
# SPDX-License-Identifier: Apache-2.0
"""Shared utility for saving and copying model checkpoints to Run folders.
Handles both local filesystem and cloud storage (S3, GCS, Azure) via the
``tlc.Url`` abstraction. The compute service is assumed to have write access
to the Run folder location.
"""
from __future__ import annotations
import logging
import shutil
import tempfile
from pathlib import Path
from typing import Any
logger = logging.getLogger(__name__)
def _is_local_url(url_str: str) -> bool:
"""Check if a URL points to the local filesystem (not cloud storage)."""
if "://" not in url_str:
return True
scheme = url_str.split("://", 1)[0].lower()
return scheme in ("file", "")
def _resolve_to_local_path(url_str: str) -> Path | None:
"""Resolve a 3LC URL to a local filesystem path, or None if cloud."""
import tlc
try:
abs_url = tlc.Url(url_str).to_absolute()
url_s = str(abs_url)
if _is_local_url(url_s):
# Strip file:// scheme if present
path = url_s.replace("file://", "")
return Path(path)
except Exception:
logger.debug("Could not resolve model URL via tlc.Url: %s", url_str)
# Try direct path interpretation
if _is_local_url(url_str):
return Path(url_str)
return None
[docs]
def save_model_to_run(
run_url: str,
model_data: Any,
filename: str = "best_model.pt",
source_file: str | Path | None = None,
on_status: Any = None,
) -> str:
"""Save a model checkpoint to a Run's ``model/`` subdirectory.
Supports both local and cloud (S3/GCS/Azure) run folders. For cloud storage,
the file is first written to a temp directory, then uploaded via ``tlc.Url``.
Args:
run_url: The 3LC Run URL (local path or cloud URL).
model_data: PyTorch state_dict to save (ignored if ``source_file`` is set).
filename: Name for the model file in the run folder.
source_file: If set, copy this existing file instead of saving ``model_data``.
on_status: Optional callback for status messages.
Returns:
The relative path to the saved model file (e.g. ``model/best.pt``).
This is relative to the run folder so it survives run renames.
Raises:
RuntimeError: If the model could not be saved.
"""
import tlc
if on_status is None:
on_status = lambda m: None # noqa: E731
relative_path = f"model/{filename}"
local_path = _resolve_to_local_path(run_url)
if local_path is not None:
# ── Local filesystem ──
model_dir = local_path / "model"
model_dir.mkdir(parents=True, exist_ok=True)
dest = model_dir / filename
if source_file is not None:
shutil.copy2(str(source_file), str(dest))
on_status(f"Copied model to run: {dest}")
else:
import torch
torch.save(model_data, str(dest))
on_status(f"Saved model to run: {dest}")
return relative_path
# ── Cloud storage (S3/GCS/Azure) ──
on_status("Run is on cloud storage, uploading model...")
run_abs = tlc.Url(run_url).to_absolute()
model_url = run_abs / "model" / filename
with tempfile.TemporaryDirectory() as tmp_dir:
tmp_path = Path(tmp_dir) / filename
if source_file is not None:
shutil.copy2(str(source_file), str(tmp_path))
else:
import torch
torch.save(model_data, str(tmp_path))
# Upload using tlc.Url write facilities
try:
model_url.write_file(str(tmp_path))
on_status(f"Uploaded model to: {model_url}")
except AttributeError:
# Fallback: use the Url's native copy mechanism
src_url = tlc.Url(str(tmp_path))
src_url.copy_to(model_url)
on_status(f"Copied model to cloud: {model_url}")
return relative_path
[docs]
def store_model_info_in_run(
run: Any,
model_name: str,
model_path: str,
source_url: str = "",
on_status: Any = None,
) -> None:
"""Store model metadata in a Run's parameters.
Args:
run: The ``tlc.Run`` object.
model_name: Model architecture name (e.g. ``yolov8n.pt``, ``resnet50``).
model_path: Path/URL to the saved model checkpoint.
source_url: Original pretrained model URL (if fine-tuning).
on_status: Optional callback for status messages.
"""
if on_status is None:
on_status = lambda m: None # noqa: E731
try:
model_info: dict[str, str] = {
"model_name": model_name,
"model_path": model_path,
}
if source_url:
model_info["model_source"] = source_url
run.set_parameters(model_info)
on_status(f"Stored model info in Run: {model_name} → {model_path}")
except Exception as e:
on_status(f"Warning: could not store model info in Run: {e}")
logger.warning("Failed to store model info in Run: %s", e)