Skip to content

Artifacts And Results

Training, distillation, import, and evaluation runs write tracked outputs under results/. Each run stores artifacts, configs, logs, and performance files.

Result Layout

results/
  teacher/
  student/
  recdistill/

Each run follows this structure:

results/<kind>/<run>/
  artifacts/
  config/
  logs/
  perf/

Run names contain the framework, model, dataset, timestamp, and experiment id. The current layout no longer creates nested result directories for individual backbones such as bprmf/, lgcn/, or nmf/; model names are encoded in the run and artifact filenames instead.

Artifact Types

.teacher            trained or imported teacher
.student            plain student baseline
.distilled_student  distilled student model

Checkpointing

STUDENT_CHECKPOINT_FORMAT = 'recdistill.student.v1' module-attribute

config_hash(config: dict[str, Any] | None) -> str | None

Source code in recdistill/checkpointing.py
def config_hash(config: dict[str, Any] | None) -> str | None:
    if config is None:
        return None
    encoded = json.dumps(config, sort_keys=True, default=str, separators=(",", ":")).encode("utf-8")
    return hashlib.sha256(encoded).hexdigest()

current_git_commit(repo_root: Path | None = None) -> str | None

Source code in recdistill/checkpointing.py
def current_git_commit(repo_root: Path | None = None) -> str | None:
    repo_root = repo_root or Path(__file__).resolve().parents[1]
    try:
        result = subprocess.run(
            ["git", "rev-parse", "HEAD"],
            cwd=repo_root,
            check=True,
            capture_output=True,
            text=True,
        )
    except Exception:
        return None
    commit = result.stdout.strip()
    return commit or None

enrich_student_checkpoint_payload(payload: dict[str, Any]) -> dict[str, Any]

Source code in recdistill/checkpointing.py
def enrich_student_checkpoint_payload(payload: dict[str, Any]) -> dict[str, Any]:
    config = payload.get("config") if isinstance(payload.get("config"), dict) else {}
    enriched = dict(payload)
    enriched.setdefault("format_version", STUDENT_CHECKPOINT_FORMAT)
    enriched.setdefault("created_at_utc", utc_now_iso())
    enriched.setdefault("config_hash", config_hash(config))
    enriched.setdefault("git_commit", current_git_commit())
    enriched.setdefault("dataset", config.get("dataset"))
    enriched.setdefault("teacher", config.get("teacher_model"))
    enriched.setdefault("student", config.get("student_backbone"))
    enriched.setdefault("distiller", _distiller_from_config(config))
    metadata = dict(enriched.get("metadata") or {})
    metadata.setdefault("format_version", enriched["format_version"])
    metadata.setdefault("created_at_utc", enriched["created_at_utc"])
    metadata.setdefault("config_hash", enriched["config_hash"])
    metadata.setdefault("git_commit", enriched["git_commit"])
    metadata.setdefault("dataset", enriched.get("dataset"))
    metadata.setdefault("teacher", enriched.get("teacher"))
    metadata.setdefault("student", enriched.get("student"))
    metadata.setdefault("distiller", enriched.get("distiller"))
    enriched["metadata"] = metadata
    return enriched

save_student_checkpoint(path: str | Path, payload: dict[str, Any]) -> dict[str, Any]

Source code in recdistill/checkpointing.py
def save_student_checkpoint(path: str | Path, payload: dict[str, Any]) -> dict[str, Any]:
    checkpoint_path = Path(path)
    checkpoint_path.parent.mkdir(parents=True, exist_ok=True)
    enriched = enrich_student_checkpoint_payload(payload)
    torch.save(enriched, checkpoint_path)
    return enriched

load_student_checkpoint(path: str | Path, map_location: str | torch.device = 'cpu') -> dict[str, Any]

Source code in recdistill/checkpointing.py
def load_student_checkpoint(path: str | Path, map_location: str | torch.device = "cpu") -> dict[str, Any]:
    payload = torch.load(Path(path), map_location=map_location, weights_only=False)
    if not isinstance(payload, dict):
        raise TypeError(f"Unsupported checkpoint payload type: {type(payload)!r}")
    if "student_state_dict" not in payload:
        raise KeyError(f"`student_state_dict` missing in checkpoint: {path}")
    return payload

Runtime Paths

recdistill.paths resolves datasets, run directories, artifacts, histories, and performance files for the flat run-based result layout.

Helper Purpose
experiment_run_dir Builds results/<kind>/<run>/.
experiment_artifact_path Builds paths under a run's artifacts/ directory.
teacher_artifact_path Resolves a native teacher .teacher artifact.
student_artifact_path Resolves a plain student .student artifact.
distilled_student_artifact_path Resolves a .distilled_student artifact.
resolve_teacher_checkpoint Finds an explicit or latest matching teacher checkpoint.
resolve_student_checkpoint Resolves plain or distilled student checkpoints.

REPO_ROOT = Path(__file__).resolve().parents[1] module-attribute

PROJECT_PATH = str(REPO_ROOT) module-attribute

DATA_ROOT = REPO_ROOT / 'data' module-attribute

RESULTS_ROOT = REPO_ROOT / 'results' module-attribute

CONFIG_ROOT = REPO_ROOT / 'config' module-attribute

EXPERIMENTS_CONFIG_ROOT = CONFIG_ROOT / 'experiments' module-attribute

PRESETS_ROOT = EXPERIMENTS_CONFIG_ROOT module-attribute

TEACHER_EXT = '.teacher' module-attribute

STUDENT_EXT = '.student' module-attribute

DISTILLED_STUDENT_EXT = '.distilled_student' module-attribute

CITEULIKE = 'citeulike' module-attribute

BOOKCROSSING = 'bookcrossing' module-attribute

AMAZONDM = 'amazon_dm' module-attribute

AMAZONCD = 'amazon_cd' module-attribute

_DATASET_FILENAME_BY_TYPE = {'raw': Path('data') / 'dataset.tsv', 'processed': Path('dataset.tsv'), 'train': Path('train.tsv'), 'val': Path('val.tsv'), 'test': Path('test.tsv')} module-attribute

PathManager

Compatibility namespace for common runtime path labels.

The current result layout no longer groups artifacts by backbone-specific nested directories. Artifact paths are resolved through run directories under results/<kind>/<run>/artifacts/, while supported model names are maintained by recdistill.registry.

Source code in recdistill/paths.py
class PathManager:
    """Compatibility namespace for common runtime path labels.

    The current result layout no longer groups artifacts by backbone-specific
    nested directories. Artifact paths are resolved through run directories
    under `results/<kind>/<run>/artifacts/`, while supported model names are
    maintained by `recdistill.registry`.
    """

    DISTILLERS = ["de", "rrd", "unkd", "htd", "ftd", "de_rrd", "de_unkd", "rrd_unkd", "de_rrd_unkd"]
    BACKBONES = ["bprmf", "lgcn", "nmf"]
    SUPPORTED_MODELS = sorted(_SUPPORTED_BACKBONES)
    DATASETS = ["amazon_cd", "bookcrossing", "citeulike"]

DISTILLERS = ['de', 'rrd', 'unkd', 'htd', 'ftd', 'de_rrd', 'de_unkd', 'rrd_unkd', 'de_rrd_unkd'] class-attribute instance-attribute

SUPPORTED_MODELS = sorted(_SUPPORTED_BACKBONES) class-attribute instance-attribute

DATASETS = ['amazon_cd', 'bookcrossing', 'citeulike'] class-attribute instance-attribute

_create_directory(path: Path) -> None

Source code in recdistill/paths.py
def _create_directory(path: Path) -> None:
    path.mkdir(parents=True, exist_ok=True)

new_experiment_id() -> str

Source code in recdistill/paths.py
def new_experiment_id() -> str:
    return uuid.uuid4().hex[:8]

timestamp_slug() -> str

Source code in recdistill/paths.py
def timestamp_slug() -> str:
    return datetime.now().strftime("%Y%m%d_%H%M%S")

experiment_kind_slug(kind: str) -> str

Source code in recdistill/paths.py
def experiment_kind_slug(kind: str) -> str:
    kind_slug = str(kind).strip().lower()
    if kind_slug in {"teachers", "train_teacher"}:
        return "teacher"
    if kind_slug in {"students", "train_student"}:
        return "student"
    if kind_slug in {"distill_student", "distillation"}:
        return "recdistill"
    return kind_slug

experiment_run_name(*, kind: str, experiment_id: str, framework: str | None = None, model: str | None = None, dataset: str | None = None, timestamp: str | None = None) -> str

Source code in recdistill/paths.py
def experiment_run_name(
    *,
    kind: str,
    experiment_id: str,
    framework: str | None = None,
    model: str | None = None,
    dataset: str | None = None,
    timestamp: str | None = None,
) -> str:
    stamp = timestamp or timestamp_slug()
    if framework and model and dataset:
        return f"{stamp}_{_path_slug(framework)}_{_path_slug(model)}_{_path_slug(dataset)}_{experiment_id}"
    return f"{stamp}_{experiment_kind_slug(kind)}_{experiment_id}"

experiment_run_dir(kind: str, experiment_id: str, *, framework: str | None = None, model: str | None = None, dataset: str | None = None, timestamp: str | None = None) -> Path

Source code in recdistill/paths.py
def experiment_run_dir(
    kind: str,
    experiment_id: str,
    *,
    framework: str | None = None,
    model: str | None = None,
    dataset: str | None = None,
    timestamp: str | None = None,
) -> Path:
    kind_slug = experiment_kind_slug(kind)
    return RESULTS_ROOT / kind_slug / experiment_run_name(
        kind=kind_slug,
        experiment_id=experiment_id,
        framework=framework,
        model=model,
        dataset=dataset,
        timestamp=timestamp,
    )

experiment_artifact_path(*, kind: str, experiment_id: str, filename: str, framework: str | None = None, model: str | None = None, dataset: str | None = None, timestamp: str | None = None) -> Path

Source code in recdistill/paths.py
def experiment_artifact_path(
    *,
    kind: str,
    experiment_id: str,
    filename: str,
    framework: str | None = None,
    model: str | None = None,
    dataset: str | None = None,
    timestamp: str | None = None,
) -> Path:
    return experiment_run_dir(
        kind,
        experiment_id,
        framework=framework,
        model=model,
        dataset=dataset,
        timestamp=timestamp,
    ) / "artifacts" / filename

experiment_artifact_filename(*, kind: str, experiment_id: str, framework: str, model: str, dataset: str, best: bool = False) -> str

Source code in recdistill/paths.py
def experiment_artifact_filename(
    *,
    kind: str,
    experiment_id: str,
    framework: str,
    model: str,
    dataset: str,
    best: bool = False,
) -> str:
    stem = f"{_path_slug(framework)}_{_path_slug(model)}_{_path_slug(dataset)}_{experiment_id}"
    if best:
        stem = f"{stem}_best"
    return f"{stem}{experiment_kind_ext(kind)}"

experiment_kind_ext(kind: str) -> str

Source code in recdistill/paths.py
def experiment_kind_ext(kind: str) -> str:
    kind_slug = experiment_kind_slug(kind)
    if kind_slug == "teacher":
        return TEACHER_EXT
    if kind_slug == "student":
        return STUDENT_EXT
    if kind_slug == "recdistill":
        return DISTILLED_STUDENT_EXT
    return ".pt"

experiment_id_from_config_path(path: str | Path) -> str

Source code in recdistill/paths.py
def experiment_id_from_config_path(path: str | Path) -> str:
    stem = Path(path).stem
    tail = stem.rsplit("_", 1)[-1]
    is_short_hex = 6 <= len(tail) <= 16 and all(ch in "0123456789abcdefABCDEF" for ch in tail)
    if tail.isdigit():
        return tail.zfill(3)
    return tail if is_short_hex else stem

normalize_experiment_id(value: Any = None, *, config_path: str | Path | None = None) -> str

Source code in recdistill/paths.py
def normalize_experiment_id(value: Any = None, *, config_path: str | Path | None = None) -> str:
    if value is None or value == "":
        if config_path is not None:
            return experiment_id_from_config_path(config_path)
        return new_experiment_id()
    if isinstance(value, int):
        return str(value).zfill(3)
    text = str(value).strip()
    return text.zfill(3) if text.isdigit() else text

_path_slug(value: Any) -> str

Source code in recdistill/paths.py
def _path_slug(value: Any) -> str:
    return str(value).strip().replace(" ", "_").replace("-", "_").replace("+", "_").replace("/", "_").replace("\\", "_")

dataset_directory(dataset_name: str, create_if_not_exists: bool = True) -> str

Source code in recdistill/paths.py
def dataset_directory(dataset_name: str, create_if_not_exists: bool = True) -> str:
    dataset_dir = DATA_ROOT / dataset_name
    if not dataset_dir.exists():
        if not create_if_not_exists:
            raise FileNotFoundError(
                f"Directory at {dataset_dir} not found. Please, check that dataset directory exists"
            )
        dataset_dir.mkdir(parents=True, exist_ok=True)
        print(f"Created directory at '{dataset_dir}'")
    return str(dataset_dir.resolve())

dataset_filepath(dataset_name: str, type: str = 'raw', exists: bool = True) -> str

Source code in recdistill/paths.py
def dataset_filepath(dataset_name: str, type: str = "raw", exists: bool = True) -> str:
    if type not in _DATASET_FILENAME_BY_TYPE:
        raise AssertionError(f"Incorrect dataset type. Dataset type found {type}.")
    path = Path(dataset_directory(dataset_name)) / _DATASET_FILENAME_BY_TYPE[type]
    if exists and not path.exists():
        raise FileNotFoundError(f"File at {path} not found. Please, check your files")
    return str(path.resolve())

_relative_or_absolute(path: str | Path) -> Path

Source code in recdistill/paths.py
def _relative_or_absolute(path: str | Path) -> Path:
    path = Path(path)
    return path if path.is_absolute() else REPO_ROOT / path

_framework_slug(framework: str | None, default: str = 'recbole') -> str

Source code in recdistill/paths.py
def _framework_slug(framework: str | None, default: str = "recbole") -> str:
    raw = str(framework or default).strip().lower()
    return default if raw in {"", "auto"} else raw

_dataset_slug(dataset: str) -> str

Source code in recdistill/paths.py
def _dataset_slug(dataset: str) -> str:
    return str(dataset).strip().lower()

_model_label(model: str) -> str

Source code in recdistill/paths.py
def _model_label(model: str) -> str:
    raw = str(model).strip()
    try:
        return canonical_model_name(raw)
    except ValueError:
        cleaned = raw.replace(" ", "_").replace("/", "_").replace("\\", "_")
        if not cleaned:
            raise ValueError("Model name cannot be empty.")
        return cleaned

teacher_artifact_path(*, framework: str | None, model: str, dataset: str, embedding_dim: int, strategy: str = 'fixed') -> Path

Source code in recdistill/paths.py
def teacher_artifact_path(
    *,
    framework: str | None,
    model: str,
    dataset: str,
    embedding_dim: int,
    strategy: str = "fixed",
) -> Path:
    framework_slug = _framework_slug(framework)
    model_name = _model_label(model)
    dataset_slug = _dataset_slug(dataset)
    experiment_id = f"{framework_slug}_{model_name.lower()}_{dataset_slug}_{int(embedding_dim)}"
    file_name = experiment_artifact_filename(
        kind="teacher",
        experiment_id=experiment_id,
        framework=framework_slug,
        model=model_name,
        dataset=dataset_slug,
    )
    return experiment_artifact_path(
        kind="teacher",
        experiment_id=experiment_id,
        framework=framework_slug,
        model=model_name,
        dataset=dataset_slug,
        filename=file_name,
    )

imported_teacher_artifact_path(*, framework: str | None, model: str, dataset: str, embedding_dim: int) -> Path

Source code in recdistill/paths.py
def imported_teacher_artifact_path(
    *,
    framework: str | None,
    model: str,
    dataset: str,
    embedding_dim: int,
) -> Path:
    framework_slug = _framework_slug(framework)
    model_name = _model_label(model)
    dataset_slug = _dataset_slug(dataset)
    experiment_id = f"imported_{framework_slug}_{model_name.lower()}_{dataset_slug}_{int(embedding_dim)}"
    file_name = experiment_artifact_filename(
        kind="teacher",
        experiment_id=experiment_id,
        framework=framework_slug,
        model=model_name,
        dataset=dataset_slug,
    )
    return experiment_artifact_path(
        kind="teacher",
        experiment_id=experiment_id,
        framework=framework_slug,
        model=model_name,
        dataset=dataset_slug,
        filename=file_name,
    )

student_artifact_path(*, framework: str | None, model: str, dataset: str, embedding_dim: int, strategy: str = 'fixed') -> Path

Source code in recdistill/paths.py
def student_artifact_path(
    *,
    framework: str | None,
    model: str,
    dataset: str,
    embedding_dim: int,
    strategy: str = "fixed",
) -> Path:
    framework_slug = _framework_slug(framework)
    model_name = _model_label(model)
    dataset_slug = _dataset_slug(dataset)
    experiment_id = f"{framework_slug}_{model_name.lower()}_{dataset_slug}_{int(embedding_dim)}"
    file_name = experiment_artifact_filename(
        kind="student",
        experiment_id=experiment_id,
        framework=framework_slug,
        model=model_name,
        dataset=dataset_slug,
    )
    return experiment_artifact_path(
        kind="student",
        experiment_id=experiment_id,
        framework=framework_slug,
        model=model_name,
        dataset=dataset_slug,
        filename=file_name,
    )

distilled_student_artifact_path(*, distiller: str, teacher_framework: str | None, teacher_model: str, student_framework: str | None, student_model: str, dataset: str, embedding_dim: int, strategy: str = 'fixed') -> Path

Source code in recdistill/paths.py
def distilled_student_artifact_path(
    *,
    distiller: str,
    teacher_framework: str | None,
    teacher_model: str,
    student_framework: str | None,
    student_model: str,
    dataset: str,
    embedding_dim: int,
    strategy: str = "fixed",
) -> Path:
    distiller_name = distiller_slug(distiller)
    teacher_framework_slug = _framework_slug(teacher_framework)
    student_framework_slug = _framework_slug(student_framework)
    teacher_model_name = _model_label(teacher_model)
    student_model_name = _model_label(student_model)
    dataset_slug = _dataset_slug(dataset)
    experiment_id = (
        f"{distiller_name}_{teacher_framework_slug}_{teacher_model_name.lower()}_"
        f"to_{student_framework_slug}_{student_model_name.lower()}_{dataset_slug}"
    )
    model_label = f"{distiller_name}_{student_model_name}"
    file_name = experiment_artifact_filename(
        kind="recdistill",
        experiment_id=experiment_id,
        framework=student_framework_slug,
        model=model_label,
        dataset=dataset_slug,
    )
    return experiment_artifact_path(
        kind="recdistill",
        experiment_id=experiment_id,
        framework=student_framework_slug,
        model=model_label,
        dataset=dataset_slug,
        filename=file_name,
    )

resolve_teacher_checkpoint(*, dataset: str, teacher_model: str | None, teacher_embedding_dim: int | None, teacher_framework: str | None = None, teacher_path: str | Path | None = None) -> Path

Source code in recdistill/paths.py
def resolve_teacher_checkpoint(
    *,
    dataset: str,
    teacher_model: str | None,
    teacher_embedding_dim: int | None,
    teacher_framework: str | None = None,
    teacher_path: str | Path | None = None,
) -> Path:
    if teacher_path is not None:
        return Path(teacher_path)

    if teacher_model is None or teacher_embedding_dim is None:
        raise ValueError(
            "When teacher_path is not set, both teacher_model and teacher_embedding_dim are required."
        )
    existing = _find_latest_teacher_artifact(
        framework=teacher_framework,
        model=teacher_model,
        dataset=dataset,
        embedding_dim=teacher_embedding_dim,
        prefer_best=True,
    )
    if existing is not None:
        return existing

    fixed_path = teacher_artifact_path(
        framework=teacher_framework,
        model=teacher_model,
        dataset=dataset,
        embedding_dim=teacher_embedding_dim,
        strategy="fixed",
    )
    best_path = teacher_artifact_path(
        framework=teacher_framework,
        model=teacher_model,
        dataset=dataset,
        embedding_dim=teacher_embedding_dim,
        strategy="best",
    )
    imported_path = imported_teacher_artifact_path(
        framework=teacher_framework,
        model=teacher_model,
        dataset=dataset,
        embedding_dim=teacher_embedding_dim,
    )
    if imported_path.exists():
        return imported_path
    if fixed_path.exists():
        return fixed_path
    if best_path.exists():
        return best_path
    raise FileNotFoundError(
        "No teacher artifact found for "
        f"framework={teacher_framework or 'auto'}, model={teacher_model}, "
        f"dataset={dataset}, embedding_dim={teacher_embedding_dim}. "
        "Set teacher.path in the distillation config or train/import the teacher first."
    )

_find_latest_teacher_artifact(*, framework: str | None, model: str, dataset: str, embedding_dim: int, prefer_best: bool = True) -> Path | None

Source code in recdistill/paths.py
def _find_latest_teacher_artifact(
    *,
    framework: str | None,
    model: str,
    dataset: str,
    embedding_dim: int,
    prefer_best: bool = True,
) -> Path | None:
    root = RESULTS_ROOT / "teacher"
    if not root.exists():
        return None

    framework_slug = _framework_slug(framework)
    model_name = _model_label(model)
    dataset_slug = _dataset_slug(dataset)
    candidates: list[Path] = []
    for path in root.glob("*/artifacts/*.teacher"):
        name = path.name.lower()
        if framework_slug not in name:
            continue
        if model_name.lower() not in name:
            continue
        if dataset_slug not in name:
            continue
        candidates.append(path)

    if not candidates:
        return None
    if prefer_best:
        best_candidates = [path for path in candidates if path.stem.endswith("_best")]
        if best_candidates:
            candidates = best_candidates
    return max(candidates, key=lambda path: path.stat().st_mtime)

resolve_student_checkpoint(*, dataset: str, distiller: str, teacher_model: str | None, student_backbone: str | None, student_embedding_dim: int, teacher_framework: str | None = None, student_framework: str | None = None, output_path: str | Path | None = None, strategy: str = 'fixed') -> Path

Source code in recdistill/paths.py
def resolve_student_checkpoint(
    *,
    dataset: str,
    distiller: str,
    teacher_model: str | None,
    student_backbone: str | None,
    student_embedding_dim: int,
    teacher_framework: str | None = None,
    student_framework: str | None = None,
    output_path: str | Path | None = None,
    strategy: str = "fixed",
) -> Path:
    if output_path is not None:
        return Path(output_path)

    raw_distiller = str(distiller).strip().lower()
    is_plain = raw_distiller in {"none", "plain", "no", "false", "0"}
    if is_plain:
        if student_backbone is None:
            raise ValueError("student_backbone is required for plain student path resolution.")
        return student_artifact_path(
            framework=student_framework,
            model=student_backbone,
            dataset=dataset,
            embedding_dim=student_embedding_dim,
            strategy=strategy,
        )

    if teacher_model is None or student_backbone is None:
        raise ValueError("teacher_model and student_backbone are required for distilled student path resolution.")
    return distilled_student_artifact_path(
        distiller=distiller,
        teacher_framework=teacher_framework,
        teacher_model=teacher_model,
        student_framework=student_framework,
        student_model=student_backbone,
        dataset=dataset,
        embedding_dim=student_embedding_dim,
        strategy=strategy,
    )

history_path(checkpoint_path: str | Path) -> Path

Source code in recdistill/paths.py
def history_path(checkpoint_path: str | Path) -> Path:
    return Path(checkpoint_path).with_suffix(".history.json")

best_checkpoint_path(checkpoint_path: str | Path) -> Path

Source code in recdistill/paths.py
def best_checkpoint_path(checkpoint_path: str | Path) -> Path:
    path = Path(checkpoint_path)
    if path.suffix in {STUDENT_EXT, DISTILLED_STUDENT_EXT, TEACHER_EXT}:
        return path.with_name(f"{path.stem}_best{path.suffix}")
    return path.with_name(f"{path.stem}_best{path.suffix or '.pt'}")

early_stop_checkpoint_path(checkpoint_path: str | Path) -> Path

Source code in recdistill/paths.py
def early_stop_checkpoint_path(checkpoint_path: str | Path) -> Path:
    path = Path(checkpoint_path)
    if path.suffix in {STUDENT_EXT, DISTILLED_STUDENT_EXT, TEACHER_EXT}:
        return path.with_name(f"{path.stem}.earlystop_best{path.suffix}")
    return path.with_suffix(".earlystop_best.pt")

recommendation_path(*, dataset: str, distiller: str, teacher_model: str | None, student_embedding_dim: int, student_backbone: str | None = None, filename: str | None = None) -> Path

Source code in recdistill/paths.py
def recommendation_path(
    *,
    dataset: str,
    distiller: str,
    teacher_model: str | None,
    student_embedding_dim: int,
    student_backbone: str | None = None,
    filename: str | None = None,
) -> Path:
    teacher_slug = model_slug(teacher_model) if teacher_model is not None else "teacher"
    student_slug = model_slug(student_backbone) if student_backbone is not None else "student"
    base = (
        RESULTS_ROOT
        / "recdistill"
        / distiller_slug(distiller)
        / teacher_slug
        / student_slug
        / str(dataset).lower()
        / "recs"
    )
    return base / (filename or f"student_{int(student_embedding_dim)}.tsv")

performance_path(*, dataset: str, distiller: str, teacher_model: str | None, student_backbone: str | None = None, phase: str = 'fixed', filename: str = 'metrics.json') -> Path

Source code in recdistill/paths.py
def performance_path(
    *,
    dataset: str,
    distiller: str,
    teacher_model: str | None,
    student_backbone: str | None = None,
    phase: str = "fixed",
    filename: str = "metrics.json",
) -> Path:
    teacher_slug = model_slug(teacher_model) if teacher_model is not None else "teacher"
    student_slug = model_slug(student_backbone) if student_backbone is not None else "student"
    return (
        RESULTS_ROOT
        / "recdistill"
        / distiller_slug(distiller)
        / teacher_slug
        / student_slug
        / str(dataset).lower()
        / phase
        / "perf"
        / filename
    )

teacher_weights_path(model: str, dataset: str, embedding_dim: int, phase: str = 'best', framework: str | None = None) -> str

Compatibility helper for legacy scripts, backed by the framework path module.

Source code in recdistill/paths.py
def teacher_weights_path(
    model: str,
    dataset: str,
    embedding_dim: int,
    phase: str = "best",
    framework: str | None = None,
) -> str:
    """Compatibility helper for legacy scripts, backed by the framework path module."""
    path = teacher_artifact_path(
        framework=framework,
        model=model,
        dataset=dataset,
        embedding_dim=embedding_dim,
        strategy=phase,
    )
    path.parent.mkdir(parents=True, exist_ok=True)
    return str(path)

student_weights_path(distiller: str, teacher: str, dataset: str, embedding_dim: int, phase: str = 'fixed', student: str | None = None) -> str

Compatibility helper for exported distilled student payloads.

Source code in recdistill/paths.py
def student_weights_path(
    distiller: str,
    teacher: str,
    dataset: str,
    embedding_dim: int,
    phase: str = "fixed",
    student: str | None = None,
) -> str:
    """Compatibility helper for exported distilled student payloads."""
    path = distilled_student_artifact_path(
        distiller=distiller,
        teacher_framework=None,
        teacher_model=teacher,
        student_framework=None,
        student_model=student or teacher,
        dataset=dataset,
        embedding_dim=embedding_dim,
        strategy=phase,
    )
    path.parent.mkdir(parents=True, exist_ok=True)
    return str(path)

resolve_student_checkpoint_from_args(args: Any, distiller_name: str) -> Path

Source code in recdistill/paths.py
def resolve_student_checkpoint_from_args(args: Any, distiller_name: str) -> Path:
    return resolve_student_checkpoint(
        dataset=args.dataset,
        distiller=distiller_name,
        teacher_framework=getattr(args, "teacher_framework", None),
        teacher_model=args.teacher_model,
        student_backbone=args.student_backbone,
        student_framework=getattr(args, "student_framework", None),
        student_embedding_dim=args.student_embedding_dim,
        output_path=args.output_path,
        strategy=getattr(args, "output_strategy", "fixed"),
    )

resolve_teacher_checkpoint_from_args(args: Any) -> Path

Source code in recdistill/paths.py
def resolve_teacher_checkpoint_from_args(args: Any) -> Path:
    return resolve_teacher_checkpoint(
        dataset=args.dataset,
        teacher_model=args.teacher_model,
        teacher_embedding_dim=args.teacher_embedding_dim,
        teacher_framework=getattr(args, "teacher_framework", None),
        teacher_path=args.teacher_path,
    )

Tracking

WandBRunLogger

Source code in recdistill/tracking.py
class WandBRunLogger:
    def __init__(
        self,
        project: str,
        run_name: str | None = None,
        entity: str | None = None,
        tags: list[str] | None = None,
        group: str | None = None,
        notes: str | None = None,
        config: dict | None = None,
    ):
        try:
            import wandb
        except ModuleNotFoundError as exc:
            raise ModuleNotFoundError(
                "wandb is required for W&B logging. Install it with `pip install wandb`."
            ) from exc

        self._wandb = wandb
        self.run = self._wandb.init(
            project=project,
            name=run_name,
            entity=entity,
            tags=tags,
            group=group,
            notes=notes,
            config=config or {},
        )

    def log_start(self, payload: dict) -> None:
        if self.run is None:
            return
        self.run.summary["status"] = payload.get("status", "running")
        self.run.summary["started_at_utc"] = payload.get("started_at_utc", utc_now_iso())
        for key, value in payload.items():
            if key in {"status", "started_at_utc"}:
                continue
            self.run.config[key] = value

    def log_epoch(self, epoch_payload: dict) -> None:
        if self.run is None:
            return
        self._wandb.log(epoch_payload, step=int(epoch_payload.get("epoch", 0)))

    def log_end(self, payload: dict) -> None:
        if self.run is None:
            return
        self.run.summary["status"] = payload.get("status", "completed")
        self.run.summary["ended_at_utc"] = payload.get("ended_at_utc", utc_now_iso())
        for key, value in payload.items():
            if key in {"status", "ended_at_utc"}:
                continue
            self.run.summary[key] = value
        exit_code = 1 if payload.get("status") == "failed" else 0
        self._wandb.finish(exit_code=exit_code)

run = self._wandb.init(project=project, name=run_name, entity=entity, tags=tags, group=group, notes=notes, config=(config or {})) instance-attribute

__init__(project: str, run_name: str | None = None, entity: str | None = None, tags: list[str] | None = None, group: str | None = None, notes: str | None = None, config: dict | None = None)

Source code in recdistill/tracking.py
def __init__(
    self,
    project: str,
    run_name: str | None = None,
    entity: str | None = None,
    tags: list[str] | None = None,
    group: str | None = None,
    notes: str | None = None,
    config: dict | None = None,
):
    try:
        import wandb
    except ModuleNotFoundError as exc:
        raise ModuleNotFoundError(
            "wandb is required for W&B logging. Install it with `pip install wandb`."
        ) from exc

    self._wandb = wandb
    self.run = self._wandb.init(
        project=project,
        name=run_name,
        entity=entity,
        tags=tags,
        group=group,
        notes=notes,
        config=config or {},
    )

log_start(payload: dict) -> None

Source code in recdistill/tracking.py
def log_start(self, payload: dict) -> None:
    if self.run is None:
        return
    self.run.summary["status"] = payload.get("status", "running")
    self.run.summary["started_at_utc"] = payload.get("started_at_utc", utc_now_iso())
    for key, value in payload.items():
        if key in {"status", "started_at_utc"}:
            continue
        self.run.config[key] = value

log_epoch(epoch_payload: dict) -> None

Source code in recdistill/tracking.py
def log_epoch(self, epoch_payload: dict) -> None:
    if self.run is None:
        return
    self._wandb.log(epoch_payload, step=int(epoch_payload.get("epoch", 0)))

log_end(payload: dict) -> None

Source code in recdistill/tracking.py
def log_end(self, payload: dict) -> None:
    if self.run is None:
        return
    self.run.summary["status"] = payload.get("status", "completed")
    self.run.summary["ended_at_utc"] = payload.get("ended_at_utc", utc_now_iso())
    for key, value in payload.items():
        if key in {"status", "ended_at_utc"}:
            continue
        self.run.summary[key] = value
    exit_code = 1 if payload.get("status") == "failed" else 0
    self._wandb.finish(exit_code=exit_code)

utc_now_iso() -> str

Source code in recdistill/tracking.py
def utc_now_iso() -> str:
    return datetime.now(timezone.utc).isoformat()

parse_csv_list(value: str | None) -> list[str] | None

Source code in recdistill/tracking.py
def parse_csv_list(value: str | None) -> list[str] | None:
    if value is None:
        return None
    parsed = [item.strip() for item in value.split(",") if item.strip()]
    return parsed or None

resolve_wandb_logger(args: Any, base_config: dict) -> WandBRunLogger | None

Source code in recdistill/tracking.py
def resolve_wandb_logger(args: Any, base_config: dict) -> WandBRunLogger | None:
    if not args.wandb_log:
        return None
    if not args.wandb_project:
        raise ValueError("--wandb-project is required when --wandb-log is enabled.")

    return WandBRunLogger(
        project=args.wandb_project,
        run_name=args.wandb_run_name,
        entity=args.wandb_entity,
        tags=parse_csv_list(args.wandb_tags),
        group=args.wandb_group,
        notes=args.wandb_notes,
        config=base_config,
    )