e93507a09c
Lockfile supply-chain audit / lockfile supply-chain audit (push) Has been cancelled
Windows Studio GGUF CI / GPU prebuilt resolves without Visual Studio (push) Has been cancelled
Windows Studio GGUF CI / setup.ps1 unit tests (VS 2026 / CMake guard) (push) Has been cancelled
Windows Studio GGUF CI / real-VS detection (VS 2022) (push) Has been cancelled
Windows Studio GGUF CI / real-VS detection (VS 2026) (push) Has been cancelled
Windows Studio GGUF CI / VC++ runtime detect + install round-trip (windows-2025-vs2026) (push) Has been cancelled
Windows Studio GGUF CI / VC++ runtime detect + install round-trip (windows-latest) (push) Has been cancelled
Windows Studio Update CI / Studio Updating Tests (push) Has been cancelled
Wheel CI / Wheel build + content sanity + import smoke (push) Has been cancelled
Lint CI / Source lint (Python + shell + YAML + JSON + safety nets) (push) Has been cancelled
MLX CI on Mac M1 / dispatch (push) Has been cancelled
Security audit / advisory audit (pip + npm + cargo) (push) Has been cancelled
Security audit / pip scan-packages :: extras (push) Has been cancelled
Security audit / pip scan-packages :: studio (push) Has been cancelled
Security audit / pip scan-packages :: hf-stack (push) Has been cancelled
Security audit / npm scan-packages (Studio frontend tarballs) (push) Has been cancelled
Security audit / workflow-trigger lint (pull_request_target / cache-poisoning) (push) Has been cancelled
Security audit / pytest tests/security (push) Has been cancelled
Security audit / npm provenance + new install-script diff (push) Has been cancelled
Studio API CI / Studio API & Auth Tests (push) Has been cancelled
Backend CI / (Python 3.10) (push) Has been cancelled
Backend CI / (Python 3.11) (push) Has been cancelled
Backend CI / (Python 3.12) (push) Has been cancelled
Backend CI / (Python 3.13) (push) Has been cancelled
Backend CI / Repo tests (CPU) (push) Has been cancelled
Frontend CI / Frontend build + bundle sanity (push) Has been cancelled
Studio GGUF CI / OpenAI, Anthropic API tests (push) Has been cancelled
Studio GGUF CI / Tool calling Tests (push) Has been cancelled
Studio GGUF CI / JSON, images (push) Has been cancelled
Mac Studio GGUF CI / OpenAI, Anthropic API tests (push) Has been cancelled
Mac Studio GGUF CI / Tool calling Tests (push) Has been cancelled
Mac Studio GGUF CI / JSON, images (push) Has been cancelled
Mac Studio Install Matrix CI / Install + load (macos-14) (push) Has been cancelled
Mac Studio Install Matrix CI / Install + load (macos-15) (push) Has been cancelled
Mac Studio Install Matrix CI / Install + load (macos-26) (push) Has been cancelled
Mac Studio Install Matrix CI / Install + load (macos-15-intel) (push) Has been cancelled
Mac Studio API CI / Studio API & Auth Tests (push) Has been cancelled
Mac Studio Install Matrix CI / Install + load (macos-26-intel) (push) Has been cancelled
Mac Studio UI CI / Chat UI Tests (push) Has been cancelled
Studio Tauri CI / Tauri Linux debug build (no codesign) (push) Has been cancelled
Mac Studio Update CI / Studio Updating Tests (push) Has been cancelled
Studio UI CI / Chat UI Tests (push) Has been cancelled
Windows Studio API CI / Studio API & Auth Tests (push) Has been cancelled
Windows Studio UI CI / Chat UI Tests (push) Has been cancelled
Studio Update CI / Studio Updating Tests (push) Has been cancelled
Core / Core (HF=default + TRL=default) (push) Has been cancelled
Core / Core (HF=4.57.6 + TRL<1) (push) Has been cancelled
Core / Core (HF=latest + TRL=latest) (push) Has been cancelled
Core / llama.cpp build + smoke (push) Has been cancelled
Windows Studio GGUF CI / OpenAI, Anthropic API tests (push) Has been cancelled
Windows Studio GGUF CI / Tool calling Tests (push) Has been cancelled
Windows Studio GGUF CI / JSON, images (push) Has been cancelled
Windows Studio GGUF CI / Studio install + inference without Visual Studio (push) Has been cancelled
Studio export capability / capability (macos-latest) (push) Has been cancelled
Studio export capability / capability (ubuntu-latest) (push) Has been cancelled
Studio export capability / capability (windows-latest) (push) Has been cancelled
Cross-platform parity / parity (macos-latest) (push) Has been cancelled
Cross-platform parity / parity (windows-latest) (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
Studio load-orchestrator CI / test (push) Has been cancelled
105 lines
3.7 KiB
Python
105 lines
3.7 KiB
Python
# SPDX-License-Identifier: AGPL-3.0-only
|
|
# Copyright 2026-present the Unsloth AI Inc. team. All rights reserved. See /studio/LICENSE.AGPL-3.0
|
|
|
|
"""Helpers for naming and describing Studio training runs."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import re
|
|
import time
|
|
from typing import Any, Optional
|
|
|
|
_INVALID_SEGMENT_CHARS = re.compile(r"[^A-Za-z0-9._-]+")
|
|
_MAX_RUN_DIR_NAME_CHARS = 255
|
|
_PROJECT_MARKER = "__project-"
|
|
_PROJECT_MARKER_ESCAPE = f"{_PROJECT_MARKER}-"
|
|
|
|
|
|
def _trim_segment(segment: str, max_chars: int) -> str:
|
|
if max_chars <= 0:
|
|
return ""
|
|
return segment[:max_chars].strip("._-")
|
|
|
|
|
|
def _escape_project_marker(segment: str) -> str:
|
|
return segment.replace(_PROJECT_MARKER, _PROJECT_MARKER_ESCAPE)
|
|
|
|
|
|
def _unescape_project_marker(segment: str) -> str:
|
|
return segment.replace(_PROJECT_MARKER_ESCAPE, _PROJECT_MARKER)
|
|
|
|
|
|
def _appended_project_marker_index(segment: str) -> int:
|
|
marker_index = segment.rfind(_PROJECT_MARKER)
|
|
while marker_index >= 0 and segment.startswith(_PROJECT_MARKER_ESCAPE, marker_index):
|
|
marker_index = segment.rfind(_PROJECT_MARKER, 0, marker_index)
|
|
return marker_index
|
|
|
|
|
|
def normalize_project_name(project_name: Any) -> Optional[str]:
|
|
"""Return a trimmed project name, or None when empty/invalid."""
|
|
if not isinstance(project_name, str):
|
|
return None
|
|
normalized = " ".join(project_name.strip().split())
|
|
return normalized or None
|
|
|
|
|
|
def slugify_project_name(project_name: Any) -> Optional[str]:
|
|
"""Convert a project name into a filesystem-safe suffix."""
|
|
normalized = normalize_project_name(project_name)
|
|
if normalized is None:
|
|
return None
|
|
|
|
slug = _INVALID_SEGMENT_CHARS.sub("-", normalized).strip("-._")
|
|
if not slug:
|
|
return None
|
|
return slug.lower()
|
|
|
|
|
|
def build_default_output_dir_name(
|
|
model_name: str,
|
|
project_name: Any = None,
|
|
*,
|
|
timestamp: Optional[int] = None,
|
|
) -> str:
|
|
"""Build the default training output folder name."""
|
|
from utils.paths import default_run_dir_name
|
|
|
|
timestamp_part = str(int(time.time() if timestamp is None else timestamp))
|
|
timestamp_suffix = f"_{timestamp_part}"
|
|
model_segment = _escape_project_marker(default_run_dir_name(model_name))
|
|
project_slug = slugify_project_name(project_name)
|
|
if not project_slug:
|
|
max_model_chars = _MAX_RUN_DIR_NAME_CHARS - len(timestamp_suffix)
|
|
model_segment = _trim_segment(model_segment, max_model_chars) or "model"
|
|
return f"{model_segment}{timestamp_suffix}"
|
|
|
|
max_project_chars = (
|
|
_MAX_RUN_DIR_NAME_CHARS - len("model") - len(_PROJECT_MARKER) - len(timestamp_suffix)
|
|
)
|
|
project_slug = _trim_segment(project_slug, max_project_chars) or "project"
|
|
project_suffix = f"{_PROJECT_MARKER}{project_slug}{timestamp_suffix}"
|
|
max_model_chars = _MAX_RUN_DIR_NAME_CHARS - len(project_suffix)
|
|
model_segment = _trim_segment(model_segment, max_model_chars) or "model"
|
|
return f"{model_segment}{project_suffix}"
|
|
|
|
|
|
def model_segment_from_default_output_dir_name(output_dir_name: str) -> Optional[str]:
|
|
"""Return the encoded model segment from a default run folder name."""
|
|
parts = str(output_dir_name or "").rsplit("_", 1)
|
|
if len(parts) != 2 or not parts[1].isdigit():
|
|
return None
|
|
model_segment = parts[0]
|
|
marker_index = _appended_project_marker_index(model_segment)
|
|
if marker_index >= 0:
|
|
model_segment = model_segment[:marker_index]
|
|
model_segment = _unescape_project_marker(model_segment)
|
|
return model_segment or None
|
|
|
|
|
|
def extract_project_name(config: Any) -> Optional[str]:
|
|
"""Read and normalize a project name from a stored config dict."""
|
|
if not isinstance(config, dict):
|
|
return None
|
|
return normalize_project_name(config.get("project_name"))
|