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 *.heic
*.ico *.ico
*.raw *.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> <br><br><hr><br>
## ::: ultralytics.utils.callbacks.platform._get_environment_info
<br><br><hr><br>
## ::: ultralytics.utils.callbacks.platform.on_pretrain_routine_start ## ::: ultralytics.utils.callbacks.platform.on_pretrain_routine_start
<br><br><hr><br> <br><br><hr><br>

View file

@ -1,6 +1,6 @@
# Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license # Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license
__version__ = "8.3.242" __version__ = "8.3.243"
import importlib import importlib
import os import os

View file

@ -1,11 +1,14 @@
# Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license # Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license
import os import os
import platform
import socket
import sys
from concurrent.futures import ThreadPoolExecutor from concurrent.futures import ThreadPoolExecutor
from pathlib import Path from pathlib import Path
from time import time 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 _last_upload = 0 # Rate limit model uploads
_console_logger = None # Global console logger instance _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) _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): def on_pretrain_routine_start(trainer):
"""Initialize Platform logging at training start.""" """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: if RANK not in {-1, 0} or not trainer.args.project:
return 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") project, name = str(trainer.args.project), str(trainer.args.name or "train")
LOGGER.info(f"Platform: Streaming to project '{project}' as '{name}'") 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 = ConsoleLogger(batch_size=5, flush_interval=5.0, on_flush=send_console_output)
_console_logger.start_capture() _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( _send_async(
"training_started", "training_started",
{ {
"trainArgs": {k: str(v) for k, v in vars(trainer.args).items()}, "trainArgs": {k: str(v) for k, v in vars(trainer.args).items()},
"epochs": trainer.epochs, "epochs": trainer.epochs,
"device": str(trainer.device), "device": str(trainer.device),
"modelInfo": model_info,
"environment": environment,
}, },
project, project,
name, name,

View file

@ -76,7 +76,7 @@ class ConsoleLogger:
# Deduplication state # Deduplication state
self.last_line = "" self.last_line = ""
self.last_time = 0.0 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 self.last_was_progress = False # Track if last line was a progress bar
def start_capture(self): def start_capture(self):
@ -146,12 +146,34 @@ class ConsoleLogger:
if "" in line: # Has thin lines but no thick lines if "" in line: # Has thin lines but no thick lines
continue continue
# Deduplicate completed progress bars only if they match the previous progress line # Only show 100% completion lines for progress bars
if " ━━" in line: if " ━━" in line:
progress_core = line.split(" ━━")[0].strip() is_complete = "100%" in line
if progress_core == self.last_progress_line and self.last_was_progress:
# Skip ALL non-complete progress lines
if not is_complete:
continue 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 self.last_was_progress = True
else: else:
# Skip empty line after progress bar # Skip empty line after progress bar