ultralytics 8.3.243 Deduplicate ConsoleLogger progress bars (#23082)

Signed-off-by: Glenn Jocher <glenn.jocher@ultralytics.com>
Co-authored-by: UltralyticsAssistant <web@ultralytics.com>
This commit is contained in:
Glenn Jocher 2025-12-30 03:34:03 +01:00 committed by GitHub
parent 7b1ba7cab8
commit e3a987c2a7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 103 additions and 8 deletions

4
.gitignore vendored
View file

@ -199,3 +199,7 @@ calibration_*.npy
*.heic
*.ico
*.raw
# Training logs
args.yaml
results.csv

View file

@ -27,6 +27,10 @@ keywords: platform callbacks, training callbacks, console logging, YOLO11 traini
<br><br><hr><br>
## ::: ultralytics.utils.callbacks.platform._get_environment_info
<br><br><hr><br>
## ::: ultralytics.utils.callbacks.platform.on_pretrain_routine_start
<br><br><hr><br>

View file

@ -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

View file

@ -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,

View file

@ -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