From e3a987c2a74c4ab55e0fbef3b0d8f993cc91c198 Mon Sep 17 00:00:00 2001 From: Glenn Jocher Date: Tue, 30 Dec 2025 03:34:03 +0100 Subject: [PATCH] `ultralytics 8.3.243` Deduplicate ConsoleLogger progress bars (#23082) Signed-off-by: Glenn Jocher Co-authored-by: UltralyticsAssistant --- .gitignore | 4 ++ docs/en/reference/utils/callbacks/platform.md | 4 ++ ultralytics/__init__.py | 2 +- ultralytics/utils/callbacks/platform.py | 69 ++++++++++++++++++- ultralytics/utils/logger.py | 32 +++++++-- 5 files changed, 103 insertions(+), 8 deletions(-) diff --git a/.gitignore b/.gitignore index c900f6b2f1..94755fdf8f 100644 --- a/.gitignore +++ b/.gitignore @@ -199,3 +199,7 @@ calibration_*.npy *.heic *.ico *.raw + +# Training logs +args.yaml +results.csv diff --git a/docs/en/reference/utils/callbacks/platform.md b/docs/en/reference/utils/callbacks/platform.md index 1f77f85d53..f3877a6e26 100644 --- a/docs/en/reference/utils/callbacks/platform.md +++ b/docs/en/reference/utils/callbacks/platform.md @@ -27,6 +27,10 @@ keywords: platform callbacks, training callbacks, console logging, YOLO11 traini



+## ::: ultralytics.utils.callbacks.platform._get_environment_info + +



+ ## ::: ultralytics.utils.callbacks.platform.on_pretrain_routine_start



diff --git a/ultralytics/__init__.py b/ultralytics/__init__.py index 8cd87756ab..ffcc54e5c0 100644 --- a/ultralytics/__init__.py +++ b/ultralytics/__init__.py @@ -1,6 +1,6 @@ # Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license -__version__ = "8.3.242" +__version__ = "8.3.243" import importlib import os diff --git a/ultralytics/utils/callbacks/platform.py b/ultralytics/utils/callbacks/platform.py index 4d62b05170..56cfcfb22b 100644 --- a/ultralytics/utils/callbacks/platform.py +++ b/ultralytics/utils/callbacks/platform.py @@ -1,11 +1,14 @@ # Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license import os +import platform +import socket +import sys from concurrent.futures import ThreadPoolExecutor from pathlib import Path from time import time -from ultralytics.utils import LOGGER, RANK, SETTINGS, TESTS_RUNNING +from ultralytics.utils import ENVIRONMENT, GIT, LOGGER, PYTHON_VERSION, RANK, SETTINGS, TESTS_RUNNING _last_upload = 0 # Rate limit model uploads _console_logger = None # Global console logger instance @@ -85,13 +88,58 @@ def _upload_model_async(model_path, project, name): _executor.submit(_upload_model, model_path, project, name) +def _get_environment_info(): + """Collect comprehensive environment info using existing ultralytics utilities.""" + import torch + + from ultralytics import __version__ + from ultralytics.utils.torch_utils import get_cpu_info, get_gpu_info + + env = { + "ultralyticsVersion": __version__, + "hostname": socket.gethostname(), + "os": platform.platform(), + "environment": ENVIRONMENT, + "pythonVersion": PYTHON_VERSION, + "pythonExecutable": sys.executable, + "cpuCount": os.cpu_count() or 0, + "cpu": get_cpu_info(), + "command": " ".join(sys.argv), + } + + # Git info using cached GIT singleton (no subprocess calls) + try: + if GIT.is_repo: + if GIT.origin: + env["gitRepository"] = GIT.origin + if GIT.branch: + env["gitBranch"] = GIT.branch + if GIT.commit: + env["gitCommit"] = GIT.commit[:12] # Short hash + except Exception: + pass + + # GPU info + try: + if torch.cuda.is_available(): + env["gpuCount"] = torch.cuda.device_count() + env["gpuType"] = get_gpu_info(0) if torch.cuda.device_count() > 0 else None + except Exception: + pass + + return env + + def on_pretrain_routine_start(trainer): """Initialize Platform logging at training start.""" - global _console_logger + global _console_logger, _last_upload if RANK not in {-1, 0} or not trainer.args.project: return + # Initialize upload timer to now so first checkpoint waits 15 min from training start + _last_upload = time() + project, name = str(trainer.args.project), str(trainer.args.name or "train") LOGGER.info(f"Platform: Streaming to project '{project}' as '{name}'") @@ -104,12 +152,29 @@ def on_pretrain_routine_start(trainer): _console_logger = ConsoleLogger(batch_size=5, flush_interval=5.0, on_flush=send_console_output) _console_logger.start_capture() + # Gather model info for richer metadata + model_info = {} + try: + info = model_info_for_loggers(trainer) + model_info = { + "parameters": info.get("model/parameters", 0), + "gflops": info.get("model/GFLOPs", 0), + "classes": getattr(trainer.model, "yaml", {}).get("nc", 0), # number of classes + } + except Exception: + pass + + # Collect environment info (W&B-style metadata) + environment = _get_environment_info() + _send_async( "training_started", { "trainArgs": {k: str(v) for k, v in vars(trainer.args).items()}, "epochs": trainer.epochs, "device": str(trainer.device), + "modelInfo": model_info, + "environment": environment, }, project, name, diff --git a/ultralytics/utils/logger.py b/ultralytics/utils/logger.py index b676b77734..362e6c6003 100644 --- a/ultralytics/utils/logger.py +++ b/ultralytics/utils/logger.py @@ -76,7 +76,7 @@ class ConsoleLogger: # Deduplication state self.last_line = "" self.last_time = 0.0 - self.last_progress_line = "" # Track last progress line for deduplication + self.last_progress_line = "" # Track progress sequence key for deduplication self.last_was_progress = False # Track if last line was a progress bar def start_capture(self): @@ -146,12 +146,34 @@ class ConsoleLogger: if "─" in line: # Has thin lines but no thick lines continue - # Deduplicate completed progress bars only if they match the previous progress line + # Only show 100% completion lines for progress bars if " ━━" in line: - progress_core = line.split(" ━━")[0].strip() - if progress_core == self.last_progress_line and self.last_was_progress: + is_complete = "100%" in line + + # Skip ALL non-complete progress lines + if not is_complete: continue - self.last_progress_line = progress_core + + # Extract sequence key to deduplicate multiple 100% lines for same sequence + parts = line.split() + seq_key = "" + if parts: + # Check for epoch pattern (X/Y at start) + if "/" in parts[0] and parts[0].replace("/", "").isdigit(): + seq_key = parts[0] # e.g., "1/3" + elif parts[0] == "Class" and len(parts) > 1: + seq_key = f"{parts[0]}_{parts[1]}" # e.g., "Class_train:" or "Class_val:" + elif parts[0] in ("train:", "val:"): + seq_key = parts[0] # Phase identifier + + # Skip if we already showed 100% for this sequence + if seq_key and self.last_progress_line == f"{seq_key}:done": + continue + + # Mark this sequence as done + if seq_key: + self.last_progress_line = f"{seq_key}:done" + self.last_was_progress = True else: # Skip empty line after progress bar