From 9f5ca7cc7b4f07fe93682523e377ecb1cd2f29bf Mon Sep 17 00:00:00 2001 From: thad0ctor Date: Sat, 23 May 2026 23:08:09 -0700 Subject: [PATCH] feat(cli): refresh launch banner with axolotl head + repo/build metadata MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replaces the @-symbol AXOLOTL logo with the new axolotl-head + AXOLOTL letters art. Renders the GitHub URL and `vVERSION · · ` centered under the letters; sha/date are best-effort from `git`, falling back to just the version on non-git installs. Also wires `print_axolotl_text_art()` into `axolotl.cli.train:do_cli` so the banner shows when invoked via `python -m axolotl.cli.train` (e.g. under torchrun in custom launch scripts), and adds an `AXOLOTL_BANNER_PRINTED` env-var guard so the Click parent + spawned subprocess (`accelerate launch` / `torchrun -m`) print it only once. --- src/axolotl/cli/art.py | 93 +++++++++++++++++++++++++++++++++------- src/axolotl/cli/train.py | 2 + 2 files changed, 80 insertions(+), 15 deletions(-) diff --git a/src/axolotl/cli/art.py b/src/axolotl/cli/art.py index 81dbb98313..52606bbc4f 100644 --- a/src/axolotl/cli/art.py +++ b/src/axolotl/cli/art.py @@ -1,30 +1,93 @@ """Axolotl ASCII logo utils.""" +import os +import subprocess # nosec B404 +from pathlib import Path + +import axolotl from axolotl.utils.distributed import is_main_process -AXOLOTL_LOGO = """ - #@@ #@@ @@# @@# - @@ @@ @@ @@ =@@# @@ #@ =@@#. - @@ #@@@@@@@@@ @@ #@#@= @@ #@ .=@@ - #@@@@@@@@@@@@@@@@@ =@# @# ##= ## =####=+ @@ =#####+ =#@@###. @@ - @@@@@@@@@@/ +@@/ +@@ #@ =@= #@= @@ =@#+ +#@# @@ =@#+ +#@# #@. @@ - @@@@@@@@@@ ##@@ ##@@ =@# @# =@# @# @@ @@ @@ @@ #@ #@ @@ - @@@@@@@@@@@@@@@@@@@@ #@=+++#@= =@@# @@ @@ @@ @@ #@ #@ @@ - =@#=====@@ =@# @# @@ @@ @@ @@ #@ #@ @@ - @@@@@@@@@@@@@@@@ @@@@ #@ #@= #@= +@@ #@# =@# @@. =@# =@# #@. @@ - =@# @# #@= #@ =#@@@@#= +#@@= +#@@@@#= .##@@+ @@ - @@@@ @@@@@@@@@@@@@@@@ -""" +GITHUB_URL = "https://github.com/axolotl-ai-cloud/axolotl" + +_AXOLOTL_LOGO_HEAD = "\n".join( + [ + " ┌─┐┌─┐ ┌─┐┌─┐", + " │ ││ │ │ ││ │ █████ ██ ██ █████ ██ █████ ███████ ██", + " │ └┘ └────┘ └┘ │ ██ ██ ██ ██ ██ ██ ██ ██ ██ ███ ██", + " │ │ ███████ ███ ██ ██ ██ ██ ██ ███ ██", + " │ ◉ ◉ │ ██ ██ ██ ██ ██ ██ ██ ██ ██ ███ ██", + " └──────────────┘ ██ ██ ██ ██ █████ ███████ █████ ███ ███████", + ] +) + +_BARS_TOP = " ▄▄▄▄▄▄▄▄▄▄ ▄▄▄" +_BARS_BOTTOM = " ▄▄▄ ▄▄▄▄▄▄▄▄▄▄" + +_LETTERS_START_COL = len(" └──────────────┘ ") +_LETTERS_END_COL = len( + " └──────────────┘ ██ ██ ██ ██ █████ ███████ █████ ███ ███████" +) HAS_PRINTED_LOGO = False +def _get_git_info() -> tuple[str | None, str | None]: + """Return (short_sha, commit_date) or (None, None) when not a git checkout.""" + repo_root = Path(axolotl.__file__).resolve().parents[2] + if not (repo_root / ".git").exists(): + return None, None + try: + sha = subprocess.check_output( # nosec B603 B607 + ["git", "-C", str(repo_root), "rev-parse", "--short", "HEAD"], + stderr=subprocess.DEVNULL, + text=True, + ).strip() + date = subprocess.check_output( # nosec B603 B607 + ["git", "-C", str(repo_root), "log", "-1", "--format=%cs"], + stderr=subprocess.DEVNULL, + text=True, + ).strip() + return sha or None, date or None + except (subprocess.CalledProcessError, FileNotFoundError, OSError): + return None, None + + +def _centered_under_letters(text: str, *, bars: str = "") -> str: + """Pad `text` so it sits centered under the AXOLOTL letters, optionally after bars.""" + available = _LETTERS_END_COL - _LETTERS_START_COL + pad = max(0, (available - len(text)) // 2) + text_col = _LETTERS_START_COL + pad + gap = max(1, text_col - len(bars)) + return bars + " " * gap + text + + +def _build_logo() -> str: + sha, date = _get_git_info() + parts = [f"v{axolotl.__version__}"] + if sha: + parts.append(sha) + if date: + parts.append(date) + build = " · ".join(parts) + return "\n".join( + [ + "", + _AXOLOTL_LOGO_HEAD, + _BARS_TOP, + _centered_under_letters(GITHUB_URL, bars=_BARS_BOTTOM), + _centered_under_letters(build), + "", + ] + ) + + def print_axolotl_text_art(): """Prints axolotl ASCII art.""" global HAS_PRINTED_LOGO - if HAS_PRINTED_LOGO: + if HAS_PRINTED_LOGO or os.environ.get("AXOLOTL_BANNER_PRINTED"): return if is_main_process(): HAS_PRINTED_LOGO = True - print(AXOLOTL_LOGO) + os.environ["AXOLOTL_BANNER_PRINTED"] = "1" + print(_build_logo()) diff --git a/src/axolotl/cli/train.py b/src/axolotl/cli/train.py index 4528577624..42331ed44e 100644 --- a/src/axolotl/cli/train.py +++ b/src/axolotl/cli/train.py @@ -10,6 +10,7 @@ from transformers.hf_argparser import HfArgumentParser from axolotl.cli.args import TrainerCliArgs +from axolotl.cli.art import print_axolotl_text_art from axolotl.cli.checks import check_accelerate_default_config, check_user_token from axolotl.cli.config import ( gpu_capabilities, @@ -65,6 +66,7 @@ def do_cli(config: Union[Path, str] = Path("examples/"), **kwargs): config: Path to `axolotl` config YAML file. kwargs: Additional keyword arguments to override config file values. """ + print_axolotl_text_art() parsed_cfg = load_cfg(config, **kwargs) parser = HfArgumentParser(TrainerCliArgs) parsed_cli_args, _ = parser.parse_args_into_dataclasses(