dgenerate-ultralytics-headless 8.3.214__py3-none-any.whl → 8.4.7__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (249) hide show
  1. {dgenerate_ultralytics_headless-8.3.214.dist-info → dgenerate_ultralytics_headless-8.4.7.dist-info}/METADATA +64 -74
  2. dgenerate_ultralytics_headless-8.4.7.dist-info/RECORD +311 -0
  3. {dgenerate_ultralytics_headless-8.3.214.dist-info → dgenerate_ultralytics_headless-8.4.7.dist-info}/WHEEL +1 -1
  4. tests/__init__.py +7 -9
  5. tests/conftest.py +8 -15
  6. tests/test_cli.py +1 -1
  7. tests/test_cuda.py +13 -10
  8. tests/test_engine.py +9 -9
  9. tests/test_exports.py +65 -13
  10. tests/test_integrations.py +13 -13
  11. tests/test_python.py +125 -69
  12. tests/test_solutions.py +161 -152
  13. ultralytics/__init__.py +1 -1
  14. ultralytics/cfg/__init__.py +86 -92
  15. ultralytics/cfg/datasets/Argoverse.yaml +7 -6
  16. ultralytics/cfg/datasets/DOTAv1.5.yaml +1 -1
  17. ultralytics/cfg/datasets/DOTAv1.yaml +1 -1
  18. ultralytics/cfg/datasets/ImageNet.yaml +1 -1
  19. ultralytics/cfg/datasets/TT100K.yaml +346 -0
  20. ultralytics/cfg/datasets/VOC.yaml +15 -16
  21. ultralytics/cfg/datasets/african-wildlife.yaml +1 -1
  22. ultralytics/cfg/datasets/coco-pose.yaml +21 -0
  23. ultralytics/cfg/datasets/coco12-formats.yaml +101 -0
  24. ultralytics/cfg/datasets/coco128-seg.yaml +1 -1
  25. ultralytics/cfg/datasets/coco8-pose.yaml +21 -0
  26. ultralytics/cfg/datasets/dog-pose.yaml +28 -0
  27. ultralytics/cfg/datasets/dota8-multispectral.yaml +1 -1
  28. ultralytics/cfg/datasets/dota8.yaml +2 -2
  29. ultralytics/cfg/datasets/hand-keypoints.yaml +26 -2
  30. ultralytics/cfg/datasets/kitti.yaml +27 -0
  31. ultralytics/cfg/datasets/lvis.yaml +5 -5
  32. ultralytics/cfg/datasets/open-images-v7.yaml +1 -1
  33. ultralytics/cfg/datasets/tiger-pose.yaml +16 -0
  34. ultralytics/cfg/datasets/xView.yaml +16 -16
  35. ultralytics/cfg/default.yaml +4 -2
  36. ultralytics/cfg/models/11/yolo11-pose.yaml +1 -1
  37. ultralytics/cfg/models/11/yoloe-11-seg.yaml +2 -2
  38. ultralytics/cfg/models/11/yoloe-11.yaml +2 -2
  39. ultralytics/cfg/models/26/yolo26-cls.yaml +33 -0
  40. ultralytics/cfg/models/26/yolo26-obb.yaml +52 -0
  41. ultralytics/cfg/models/26/yolo26-p2.yaml +60 -0
  42. ultralytics/cfg/models/26/yolo26-p6.yaml +62 -0
  43. ultralytics/cfg/models/26/yolo26-pose.yaml +53 -0
  44. ultralytics/cfg/models/26/yolo26-seg.yaml +52 -0
  45. ultralytics/cfg/models/26/yolo26.yaml +52 -0
  46. ultralytics/cfg/models/26/yoloe-26-seg.yaml +53 -0
  47. ultralytics/cfg/models/26/yoloe-26.yaml +53 -0
  48. ultralytics/cfg/models/rt-detr/rtdetr-l.yaml +1 -1
  49. ultralytics/cfg/models/rt-detr/rtdetr-resnet101.yaml +1 -1
  50. ultralytics/cfg/models/rt-detr/rtdetr-resnet50.yaml +1 -1
  51. ultralytics/cfg/models/rt-detr/rtdetr-x.yaml +1 -1
  52. ultralytics/cfg/models/v10/yolov10b.yaml +2 -2
  53. ultralytics/cfg/models/v10/yolov10l.yaml +2 -2
  54. ultralytics/cfg/models/v10/yolov10m.yaml +2 -2
  55. ultralytics/cfg/models/v10/yolov10n.yaml +2 -2
  56. ultralytics/cfg/models/v10/yolov10s.yaml +2 -2
  57. ultralytics/cfg/models/v10/yolov10x.yaml +2 -2
  58. ultralytics/cfg/models/v3/yolov3-tiny.yaml +1 -1
  59. ultralytics/cfg/models/v6/yolov6.yaml +1 -1
  60. ultralytics/cfg/models/v8/yoloe-v8-seg.yaml +9 -6
  61. ultralytics/cfg/models/v8/yoloe-v8.yaml +9 -6
  62. ultralytics/cfg/models/v8/yolov8-cls-resnet101.yaml +1 -1
  63. ultralytics/cfg/models/v8/yolov8-cls-resnet50.yaml +1 -1
  64. ultralytics/cfg/models/v8/yolov8-ghost-p2.yaml +2 -2
  65. ultralytics/cfg/models/v8/yolov8-ghost-p6.yaml +2 -2
  66. ultralytics/cfg/models/v8/yolov8-ghost.yaml +2 -2
  67. ultralytics/cfg/models/v8/yolov8-obb.yaml +1 -1
  68. ultralytics/cfg/models/v8/yolov8-p2.yaml +1 -1
  69. ultralytics/cfg/models/v8/yolov8-pose-p6.yaml +1 -1
  70. ultralytics/cfg/models/v8/yolov8-rtdetr.yaml +1 -1
  71. ultralytics/cfg/models/v8/yolov8-seg-p6.yaml +1 -1
  72. ultralytics/cfg/models/v8/yolov8-world.yaml +1 -1
  73. ultralytics/cfg/models/v8/yolov8-worldv2.yaml +6 -6
  74. ultralytics/cfg/models/v9/yolov9s.yaml +1 -1
  75. ultralytics/data/__init__.py +4 -4
  76. ultralytics/data/annotator.py +5 -6
  77. ultralytics/data/augment.py +300 -475
  78. ultralytics/data/base.py +18 -26
  79. ultralytics/data/build.py +147 -25
  80. ultralytics/data/converter.py +108 -87
  81. ultralytics/data/dataset.py +47 -75
  82. ultralytics/data/loaders.py +42 -49
  83. ultralytics/data/split.py +5 -6
  84. ultralytics/data/split_dota.py +8 -15
  85. ultralytics/data/utils.py +36 -45
  86. ultralytics/engine/exporter.py +351 -263
  87. ultralytics/engine/model.py +186 -225
  88. ultralytics/engine/predictor.py +45 -54
  89. ultralytics/engine/results.py +198 -325
  90. ultralytics/engine/trainer.py +165 -106
  91. ultralytics/engine/tuner.py +41 -43
  92. ultralytics/engine/validator.py +55 -38
  93. ultralytics/hub/__init__.py +16 -19
  94. ultralytics/hub/auth.py +6 -12
  95. ultralytics/hub/google/__init__.py +7 -10
  96. ultralytics/hub/session.py +15 -25
  97. ultralytics/hub/utils.py +5 -8
  98. ultralytics/models/__init__.py +1 -1
  99. ultralytics/models/fastsam/__init__.py +1 -1
  100. ultralytics/models/fastsam/model.py +8 -10
  101. ultralytics/models/fastsam/predict.py +18 -30
  102. ultralytics/models/fastsam/utils.py +1 -2
  103. ultralytics/models/fastsam/val.py +5 -7
  104. ultralytics/models/nas/__init__.py +1 -1
  105. ultralytics/models/nas/model.py +5 -8
  106. ultralytics/models/nas/predict.py +7 -9
  107. ultralytics/models/nas/val.py +1 -2
  108. ultralytics/models/rtdetr/__init__.py +1 -1
  109. ultralytics/models/rtdetr/model.py +5 -8
  110. ultralytics/models/rtdetr/predict.py +15 -19
  111. ultralytics/models/rtdetr/train.py +10 -13
  112. ultralytics/models/rtdetr/val.py +21 -23
  113. ultralytics/models/sam/__init__.py +15 -2
  114. ultralytics/models/sam/amg.py +14 -20
  115. ultralytics/models/sam/build.py +26 -19
  116. ultralytics/models/sam/build_sam3.py +377 -0
  117. ultralytics/models/sam/model.py +29 -32
  118. ultralytics/models/sam/modules/blocks.py +83 -144
  119. ultralytics/models/sam/modules/decoders.py +19 -37
  120. ultralytics/models/sam/modules/encoders.py +44 -101
  121. ultralytics/models/sam/modules/memory_attention.py +16 -30
  122. ultralytics/models/sam/modules/sam.py +200 -73
  123. ultralytics/models/sam/modules/tiny_encoder.py +64 -83
  124. ultralytics/models/sam/modules/transformer.py +18 -28
  125. ultralytics/models/sam/modules/utils.py +174 -50
  126. ultralytics/models/sam/predict.py +2248 -350
  127. ultralytics/models/sam/sam3/__init__.py +3 -0
  128. ultralytics/models/sam/sam3/decoder.py +546 -0
  129. ultralytics/models/sam/sam3/encoder.py +529 -0
  130. ultralytics/models/sam/sam3/geometry_encoders.py +415 -0
  131. ultralytics/models/sam/sam3/maskformer_segmentation.py +286 -0
  132. ultralytics/models/sam/sam3/model_misc.py +199 -0
  133. ultralytics/models/sam/sam3/necks.py +129 -0
  134. ultralytics/models/sam/sam3/sam3_image.py +339 -0
  135. ultralytics/models/sam/sam3/text_encoder_ve.py +307 -0
  136. ultralytics/models/sam/sam3/vitdet.py +547 -0
  137. ultralytics/models/sam/sam3/vl_combiner.py +160 -0
  138. ultralytics/models/utils/loss.py +14 -26
  139. ultralytics/models/utils/ops.py +13 -17
  140. ultralytics/models/yolo/__init__.py +1 -1
  141. ultralytics/models/yolo/classify/predict.py +10 -13
  142. ultralytics/models/yolo/classify/train.py +12 -33
  143. ultralytics/models/yolo/classify/val.py +30 -29
  144. ultralytics/models/yolo/detect/predict.py +9 -12
  145. ultralytics/models/yolo/detect/train.py +17 -23
  146. ultralytics/models/yolo/detect/val.py +77 -59
  147. ultralytics/models/yolo/model.py +43 -60
  148. ultralytics/models/yolo/obb/predict.py +7 -16
  149. ultralytics/models/yolo/obb/train.py +14 -17
  150. ultralytics/models/yolo/obb/val.py +40 -37
  151. ultralytics/models/yolo/pose/__init__.py +1 -1
  152. ultralytics/models/yolo/pose/predict.py +7 -22
  153. ultralytics/models/yolo/pose/train.py +13 -16
  154. ultralytics/models/yolo/pose/val.py +39 -58
  155. ultralytics/models/yolo/segment/predict.py +17 -21
  156. ultralytics/models/yolo/segment/train.py +7 -10
  157. ultralytics/models/yolo/segment/val.py +95 -47
  158. ultralytics/models/yolo/world/train.py +8 -14
  159. ultralytics/models/yolo/world/train_world.py +11 -34
  160. ultralytics/models/yolo/yoloe/__init__.py +7 -7
  161. ultralytics/models/yolo/yoloe/predict.py +16 -23
  162. ultralytics/models/yolo/yoloe/train.py +36 -44
  163. ultralytics/models/yolo/yoloe/train_seg.py +11 -11
  164. ultralytics/models/yolo/yoloe/val.py +15 -20
  165. ultralytics/nn/__init__.py +7 -7
  166. ultralytics/nn/autobackend.py +159 -85
  167. ultralytics/nn/modules/__init__.py +68 -60
  168. ultralytics/nn/modules/activation.py +4 -6
  169. ultralytics/nn/modules/block.py +260 -224
  170. ultralytics/nn/modules/conv.py +52 -97
  171. ultralytics/nn/modules/head.py +831 -299
  172. ultralytics/nn/modules/transformer.py +76 -88
  173. ultralytics/nn/modules/utils.py +16 -21
  174. ultralytics/nn/tasks.py +180 -195
  175. ultralytics/nn/text_model.py +45 -69
  176. ultralytics/optim/__init__.py +5 -0
  177. ultralytics/optim/muon.py +338 -0
  178. ultralytics/solutions/__init__.py +12 -12
  179. ultralytics/solutions/ai_gym.py +13 -19
  180. ultralytics/solutions/analytics.py +15 -16
  181. ultralytics/solutions/config.py +6 -7
  182. ultralytics/solutions/distance_calculation.py +10 -13
  183. ultralytics/solutions/heatmap.py +8 -14
  184. ultralytics/solutions/instance_segmentation.py +6 -9
  185. ultralytics/solutions/object_blurrer.py +7 -10
  186. ultralytics/solutions/object_counter.py +12 -19
  187. ultralytics/solutions/object_cropper.py +8 -14
  188. ultralytics/solutions/parking_management.py +34 -32
  189. ultralytics/solutions/queue_management.py +10 -12
  190. ultralytics/solutions/region_counter.py +9 -12
  191. ultralytics/solutions/security_alarm.py +15 -20
  192. ultralytics/solutions/similarity_search.py +10 -15
  193. ultralytics/solutions/solutions.py +77 -76
  194. ultralytics/solutions/speed_estimation.py +7 -10
  195. ultralytics/solutions/streamlit_inference.py +2 -4
  196. ultralytics/solutions/templates/similarity-search.html +7 -18
  197. ultralytics/solutions/trackzone.py +7 -10
  198. ultralytics/solutions/vision_eye.py +5 -8
  199. ultralytics/trackers/__init__.py +1 -1
  200. ultralytics/trackers/basetrack.py +3 -5
  201. ultralytics/trackers/bot_sort.py +10 -27
  202. ultralytics/trackers/byte_tracker.py +21 -37
  203. ultralytics/trackers/track.py +4 -7
  204. ultralytics/trackers/utils/gmc.py +11 -22
  205. ultralytics/trackers/utils/kalman_filter.py +37 -48
  206. ultralytics/trackers/utils/matching.py +12 -15
  207. ultralytics/utils/__init__.py +124 -124
  208. ultralytics/utils/autobatch.py +2 -4
  209. ultralytics/utils/autodevice.py +17 -18
  210. ultralytics/utils/benchmarks.py +57 -71
  211. ultralytics/utils/callbacks/base.py +8 -10
  212. ultralytics/utils/callbacks/clearml.py +5 -13
  213. ultralytics/utils/callbacks/comet.py +32 -46
  214. ultralytics/utils/callbacks/dvc.py +13 -18
  215. ultralytics/utils/callbacks/mlflow.py +4 -5
  216. ultralytics/utils/callbacks/neptune.py +7 -15
  217. ultralytics/utils/callbacks/platform.py +423 -38
  218. ultralytics/utils/callbacks/raytune.py +3 -4
  219. ultralytics/utils/callbacks/tensorboard.py +25 -31
  220. ultralytics/utils/callbacks/wb.py +16 -14
  221. ultralytics/utils/checks.py +127 -85
  222. ultralytics/utils/cpu.py +3 -8
  223. ultralytics/utils/dist.py +9 -12
  224. ultralytics/utils/downloads.py +25 -33
  225. ultralytics/utils/errors.py +6 -14
  226. ultralytics/utils/events.py +2 -4
  227. ultralytics/utils/export/__init__.py +4 -236
  228. ultralytics/utils/export/engine.py +246 -0
  229. ultralytics/utils/export/imx.py +117 -63
  230. ultralytics/utils/export/tensorflow.py +231 -0
  231. ultralytics/utils/files.py +26 -30
  232. ultralytics/utils/git.py +9 -11
  233. ultralytics/utils/instance.py +30 -51
  234. ultralytics/utils/logger.py +212 -114
  235. ultralytics/utils/loss.py +601 -215
  236. ultralytics/utils/metrics.py +128 -156
  237. ultralytics/utils/nms.py +13 -16
  238. ultralytics/utils/ops.py +117 -166
  239. ultralytics/utils/patches.py +75 -21
  240. ultralytics/utils/plotting.py +75 -80
  241. ultralytics/utils/tal.py +125 -59
  242. ultralytics/utils/torch_utils.py +53 -79
  243. ultralytics/utils/tqdm.py +24 -21
  244. ultralytics/utils/triton.py +13 -19
  245. ultralytics/utils/tuner.py +19 -10
  246. dgenerate_ultralytics_headless-8.3.214.dist-info/RECORD +0 -283
  247. {dgenerate_ultralytics_headless-8.3.214.dist-info → dgenerate_ultralytics_headless-8.4.7.dist-info}/entry_points.txt +0 -0
  248. {dgenerate_ultralytics_headless-8.3.214.dist-info → dgenerate_ultralytics_headless-8.4.7.dist-info}/licenses/LICENSE +0 -0
  249. {dgenerate_ultralytics_headless-8.3.214.dist-info → dgenerate_ultralytics_headless-8.4.7.dist-info}/top_level.txt +0 -0
@@ -1,7 +1,6 @@
1
1
  # Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license
2
2
 
3
3
  import logging
4
- import queue
5
4
  import shutil
6
5
  import sys
7
6
  import threading
@@ -9,77 +8,84 @@ import time
9
8
  from datetime import datetime
10
9
  from pathlib import Path
11
10
 
12
- from ultralytics.utils import MACOS, RANK
11
+ from ultralytics.utils import LOGGER, MACOS, RANK
13
12
  from ultralytics.utils.checks import check_requirements
14
13
 
15
- # Initialize default log file
16
- DEFAULT_LOG_PATH = Path("train.log")
17
- if RANK in {-1, 0} and DEFAULT_LOG_PATH.exists():
18
- DEFAULT_LOG_PATH.unlink(missing_ok=True)
19
-
20
14
 
21
15
  class ConsoleLogger:
22
- """
23
- Console output capture with API/file streaming and deduplication.
16
+ """Console output capture with batched streaming to file, API, or custom callback.
24
17
 
25
- Captures stdout/stderr output and streams it to either an API endpoint or local file, with intelligent
26
- deduplication to reduce noise from repetitive console output.
18
+ Captures stdout/stderr output and streams it with intelligent deduplication and configurable batching.
27
19
 
28
20
  Attributes:
29
- destination (str | Path): Target destination for streaming (URL or Path object).
30
- is_api (bool): Whether destination is an API endpoint (True) or local file (False).
31
- original_stdout: Reference to original sys.stdout for restoration.
32
- original_stderr: Reference to original sys.stderr for restoration.
33
- log_queue (queue.Queue): Thread-safe queue for buffering log messages.
21
+ destination (str | Path | None): Target destination for streaming (URL, Path, or None for callback-only).
22
+ batch_size (int): Number of lines to batch before flushing (default: 1 for immediate).
23
+ flush_interval (float): Seconds between automatic flushes (default: 5.0).
24
+ on_flush (callable | None): Optional callback function called with batched content on flush.
34
25
  active (bool): Whether console capture is currently active.
35
- worker_thread (threading.Thread): Background thread for processing log queue.
36
- last_line (str): Last processed line for deduplication.
37
- last_time (float): Timestamp of last processed line.
38
- last_progress_line (str): Last progress bar line for progress deduplication.
39
- last_was_progress (bool): Whether the last line was a progress bar.
40
26
 
41
27
  Examples:
42
- Basic file logging:
28
+ File logging (immediate):
43
29
  >>> logger = ConsoleLogger("training.log")
44
30
  >>> logger.start_capture()
45
31
  >>> print("This will be logged")
46
32
  >>> logger.stop_capture()
47
33
 
48
- API streaming:
49
- >>> logger = ConsoleLogger("https://api.example.com/logs")
34
+ API streaming with batching:
35
+ >>> logger = ConsoleLogger("https://api.example.com/logs", batch_size=10)
36
+ >>> logger.start_capture()
37
+
38
+ Custom callback with batching:
39
+ >>> def my_handler(content, line_count, chunk_id):
40
+ ... print(f"Received {line_count} lines")
41
+ >>> logger = ConsoleLogger(on_flush=my_handler, batch_size=5)
50
42
  >>> logger.start_capture()
51
- >>> # All output streams to API
52
- >>> logger.stop_capture()
53
43
  """
54
44
 
55
- def __init__(self, destination):
56
- """
57
- Initialize with API endpoint or local file path.
45
+ def __init__(self, destination=None, batch_size=1, flush_interval=5.0, on_flush=None):
46
+ """Initialize console logger with optional batching.
58
47
 
59
48
  Args:
60
- destination (str | Path): API endpoint URL (http/https) or local file path for streaming output.
49
+ destination (str | Path | None): API endpoint URL (http/https), local file path, or None.
50
+ batch_size (int): Lines to accumulate before flush (1 = immediate, higher = batched).
51
+ flush_interval (float): Max seconds between flushes when batching.
52
+ on_flush (callable | None): Callback(content: str, line_count: int, chunk_id: int) for custom handling.
61
53
  """
62
54
  self.destination = destination
63
55
  self.is_api = isinstance(destination, str) and destination.startswith(("http://", "https://"))
64
- if not self.is_api:
56
+ if destination is not None and not self.is_api:
65
57
  self.destination = Path(destination)
66
58
 
67
- # Console capture
59
+ # Batching configuration
60
+ self.batch_size = max(1, batch_size)
61
+ self.flush_interval = flush_interval
62
+ self.on_flush = on_flush
63
+
64
+ # Console capture state
68
65
  self.original_stdout = sys.stdout
69
66
  self.original_stderr = sys.stderr
70
- self.log_queue = queue.Queue(maxsize=1000)
71
67
  self.active = False
72
- self.worker_thread = None
68
+ self._log_handler = None # Track handler for cleanup
73
69
 
74
- # State tracking
70
+ # Buffer for batching
71
+ self.buffer = []
72
+ self.buffer_lock = threading.Lock()
73
+ self.flush_thread = None
74
+ self.chunk_id = 0
75
+
76
+ # Deduplication state
75
77
  self.last_line = ""
76
78
  self.last_time = 0.0
77
- self.last_progress_line = "" # Track last progress line for deduplication
79
+ self.last_progress_line = "" # Track progress sequence key for deduplication
78
80
  self.last_was_progress = False # Track if last line was a progress bar
79
81
 
80
82
  def start_capture(self):
81
- """Start capturing console output and redirect stdout/stderr to custom capture objects."""
82
- if self.active:
83
+ """Start capturing console output and redirect stdout/stderr.
84
+
85
+ Notes:
86
+ In DDP training, only activates on rank 0/-1 to prevent duplicate logging.
87
+ """
88
+ if self.active or RANK not in {-1, 0}:
83
89
  return
84
90
 
85
91
  self.active = True
@@ -88,23 +94,35 @@ class ConsoleLogger:
88
94
 
89
95
  # Hook Ultralytics logger
90
96
  try:
91
- handler = self._LogHandler(self._queue_log)
92
- logging.getLogger("ultralytics").addHandler(handler)
97
+ self._log_handler = self._LogHandler(self._queue_log)
98
+ logging.getLogger("ultralytics").addHandler(self._log_handler)
93
99
  except Exception:
94
100
  pass
95
101
 
96
- self.worker_thread = threading.Thread(target=self._stream_worker, daemon=True)
97
- self.worker_thread.start()
102
+ # Start background flush thread for batched mode
103
+ if self.batch_size > 1:
104
+ self.flush_thread = threading.Thread(target=self._flush_worker, daemon=True)
105
+ self.flush_thread.start()
98
106
 
99
107
  def stop_capture(self):
100
- """Stop capturing console output and restore original stdout/stderr."""
108
+ """Stop capturing console output and flush remaining buffer."""
101
109
  if not self.active:
102
110
  return
103
111
 
104
112
  self.active = False
105
113
  sys.stdout = self.original_stdout
106
114
  sys.stderr = self.original_stderr
107
- self.log_queue.put(None)
115
+
116
+ # Remove logging handler to prevent memory leak
117
+ if self._log_handler:
118
+ try:
119
+ logging.getLogger("ultralytics").removeHandler(self._log_handler)
120
+ except Exception:
121
+ pass
122
+ self._log_handler = None
123
+
124
+ # Final flush
125
+ self._flush_buffer()
108
126
 
109
127
  def _queue_log(self, text):
110
128
  """Queue console text with deduplication and timestamp processing."""
@@ -128,12 +146,34 @@ class ConsoleLogger:
128
146
  if "─" in line: # Has thin lines but no thick lines
129
147
  continue
130
148
 
131
- # Deduplicate completed progress bars only if they match the previous progress line
149
+ # Only show 100% completion lines for progress bars
132
150
  if " ━━" in line:
133
- progress_core = line.split(" ━━")[0].strip()
134
- if progress_core == self.last_progress_line and self.last_was_progress:
151
+ is_complete = "100%" in line
152
+
153
+ # Skip ALL non-complete progress lines
154
+ if not is_complete:
135
155
  continue
136
- self.last_progress_line = progress_core
156
+
157
+ # Extract sequence key to deduplicate multiple 100% lines for same sequence
158
+ parts = line.split()
159
+ seq_key = ""
160
+ if parts:
161
+ # Check for epoch pattern (X/Y at start)
162
+ if "/" in parts[0] and parts[0].replace("/", "").isdigit():
163
+ seq_key = parts[0] # e.g., "1/3"
164
+ elif parts[0] == "Class" and len(parts) > 1:
165
+ seq_key = f"{parts[0]}_{parts[1]}" # e.g., "Class_train:" or "Class_val:"
166
+ elif parts[0] in ("train:", "val:"):
167
+ seq_key = parts[0] # Phase identifier
168
+
169
+ # Skip if we already showed 100% for this sequence
170
+ if seq_key and self.last_progress_line == f"{seq_key}:done":
171
+ continue
172
+
173
+ # Mark this sequence as done
174
+ if seq_key:
175
+ self.last_progress_line = f"{seq_key}:done"
176
+
137
177
  self.last_was_progress = True
138
178
  else:
139
179
  # Skip empty line after progress bar
@@ -154,63 +194,80 @@ class ConsoleLogger:
154
194
  timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
155
195
  line = f"[{timestamp}] {line}"
156
196
 
157
- # Queue with overflow protection
158
- if not self._safe_put(f"{line}\n"):
159
- continue # Skip if queue handling fails
197
+ # Add to buffer and check if flush needed
198
+ should_flush = False
199
+ with self.buffer_lock:
200
+ self.buffer.append(line)
201
+ if len(self.buffer) >= self.batch_size:
202
+ should_flush = True
160
203
 
161
- def _safe_put(self, item):
162
- """Safely put item in queue with overflow handling."""
163
- try:
164
- self.log_queue.put_nowait(item)
165
- return True
166
- except queue.Full:
167
- try:
168
- self.log_queue.get_nowait() # Drop oldest
169
- self.log_queue.put_nowait(item)
170
- return True
171
- except queue.Empty:
172
- return False
173
-
174
- def _stream_worker(self):
175
- """Background worker for streaming logs to destination."""
204
+ # Flush outside lock to avoid deadlock
205
+ if should_flush:
206
+ self._flush_buffer()
207
+
208
+ def _flush_worker(self):
209
+ """Background worker that flushes buffer periodically."""
176
210
  while self.active:
211
+ time.sleep(self.flush_interval)
212
+ if self.active:
213
+ self._flush_buffer()
214
+
215
+ def _flush_buffer(self):
216
+ """Flush buffered lines to destination and/or callback."""
217
+ with self.buffer_lock:
218
+ if not self.buffer:
219
+ return
220
+ lines = self.buffer.copy()
221
+ self.buffer.clear()
222
+ self.chunk_id += 1
223
+ chunk_id = self.chunk_id # Capture under lock to avoid race
224
+
225
+ content = "\n".join(lines)
226
+ line_count = len(lines)
227
+
228
+ # Call custom callback if provided
229
+ if self.on_flush:
177
230
  try:
178
- log_text = self.log_queue.get(timeout=1)
179
- if log_text is None:
180
- break
181
- self._write_log(log_text)
182
- except queue.Empty:
183
- continue
231
+ self.on_flush(content, line_count, chunk_id)
232
+ except Exception:
233
+ pass # Silently ignore callback errors to avoid flooding stderr
234
+
235
+ # Write to destination (file or API)
236
+ if self.destination is not None:
237
+ self._write_destination(content)
184
238
 
185
- def _write_log(self, text):
186
- """Write log to API endpoint or local file destination."""
239
+ def _write_destination(self, content):
240
+ """Write content to file or API destination."""
187
241
  try:
188
242
  if self.is_api:
189
- import requests # scoped as slow import
243
+ import requests
190
244
 
191
- payload = {"timestamp": datetime.now().isoformat(), "message": text.strip()}
245
+ payload = {"timestamp": datetime.now().isoformat(), "message": content}
192
246
  requests.post(str(self.destination), json=payload, timeout=5)
193
247
  else:
194
248
  self.destination.parent.mkdir(parents=True, exist_ok=True)
195
249
  with self.destination.open("a", encoding="utf-8") as f:
196
- f.write(text)
250
+ f.write(content + "\n")
197
251
  except Exception as e:
198
- print(f"Platform logging error: {e}", file=self.original_stderr)
252
+ print(f"Console logger write error: {e}", file=self.original_stderr)
199
253
 
200
254
  class _ConsoleCapture:
201
255
  """Lightweight stdout/stderr capture."""
202
256
 
203
- __slots__ = ("original", "callback")
257
+ __slots__ = ("callback", "original")
204
258
 
205
259
  def __init__(self, original, callback):
260
+ """Initialize a stream wrapper that redirects writes to a callback while preserving the original."""
206
261
  self.original = original
207
262
  self.callback = callback
208
263
 
209
264
  def write(self, text):
265
+ """Forward text to the wrapped original stream, preserving default stdout/stderr semantics."""
210
266
  self.original.write(text)
211
267
  self.callback(text)
212
268
 
213
269
  def flush(self):
270
+ """Flush the wrapped stream to propagate buffered output promptly during console capture."""
214
271
  self.original.flush()
215
272
 
216
273
  class _LogHandler(logging.Handler):
@@ -219,19 +276,20 @@ class ConsoleLogger:
219
276
  __slots__ = ("callback",)
220
277
 
221
278
  def __init__(self, callback):
279
+ """Initialize a lightweight logging.Handler that forwards log records to the provided callback."""
222
280
  super().__init__()
223
281
  self.callback = callback
224
282
 
225
283
  def emit(self, record):
284
+ """Format and forward LogRecord messages to the capture callback for unified log streaming."""
226
285
  self.callback(self.format(record) + "\n")
227
286
 
228
287
 
229
288
  class SystemLogger:
230
- """
231
- Log dynamic system metrics for training monitoring.
289
+ """Log dynamic system metrics for training monitoring.
232
290
 
233
- Captures real-time system metrics including CPU, RAM, disk I/O, network I/O, and NVIDIA GPU statistics for
234
- training performance monitoring and analysis.
291
+ Captures real-time system metrics including CPU, RAM, disk I/O, network I/O, and NVIDIA GPU statistics for training
292
+ performance monitoring and analysis.
235
293
 
236
294
  Attributes:
237
295
  pynvml: NVIDIA pynvml module instance if successfully imported, None otherwise.
@@ -265,54 +323,71 @@ class SystemLogger:
265
323
  self.net_start = psutil.net_io_counters()
266
324
  self.disk_start = psutil.disk_io_counters()
267
325
 
326
+ # For rate calculation
327
+ self._prev_net = self.net_start
328
+ self._prev_disk = self.disk_start
329
+ self._prev_time = time.time()
330
+
268
331
  def _init_nvidia(self):
269
332
  """Initialize NVIDIA GPU monitoring with pynvml."""
333
+ if MACOS:
334
+ return False
335
+
270
336
  try:
271
- assert not MACOS
272
337
  check_requirements("nvidia-ml-py>=12.0.0")
273
338
  self.pynvml = __import__("pynvml")
274
339
  self.pynvml.nvmlInit()
275
340
  return True
276
- except Exception:
341
+ except Exception as e:
342
+ import torch
343
+
344
+ if torch.cuda.is_available():
345
+ LOGGER.warning(f"SystemLogger NVML init failed: {e}")
277
346
  return False
278
347
 
279
- def get_metrics(self):
280
- """
281
- Get current system metrics.
348
+ def get_metrics(self, rates=False):
349
+ """Get current system metrics including CPU, RAM, disk, network, and GPU usage.
282
350
 
283
- Collects comprehensive system metrics including CPU usage, RAM usage, disk I/O statistics,
284
- network I/O statistics, and GPU metrics (if available). Example output:
351
+ Collects comprehensive system metrics including CPU usage, RAM usage, disk I/O statistics, network I/O
352
+ statistics, and GPU metrics (if available).
285
353
 
354
+ Example output (rates=False, default):
286
355
  ```python
287
- metrics = {
356
+ {
288
357
  "cpu": 45.2,
289
358
  "ram": 78.9,
290
359
  "disk": {"read_mb": 156.7, "write_mb": 89.3, "used_gb": 256.8},
291
360
  "network": {"recv_mb": 157.2, "sent_mb": 89.1},
292
361
  "gpus": {
293
- 0: {"usage": 95.6, "memory": 85.4, "temp": 72, "power": 285},
294
- 1: {"usage": 94.1, "memory": 82.7, "temp": 70, "power": 278},
362
+ "0": {"usage": 95.6, "memory": 85.4, "temp": 72, "power": 285},
363
+ "1": {"usage": 94.1, "memory": 82.7, "temp": 70, "power": 278},
364
+ },
365
+ }
366
+ ```
367
+
368
+ Example output (rates=True):
369
+ ```python
370
+ {
371
+ "cpu": 45.2,
372
+ "ram": 78.9,
373
+ "disk": {"read_mbs": 12.5, "write_mbs": 8.3, "used_gb": 256.8},
374
+ "network": {"recv_mbs": 5.2, "sent_mbs": 1.1},
375
+ "gpus": {
376
+ "0": {"usage": 95.6, "memory": 85.4, "temp": 72, "power": 285},
295
377
  },
296
378
  }
297
379
  ```
298
380
 
299
- - cpu (float): CPU usage percentage (0-100%)
300
- - ram (float): RAM usage percentage (0-100%)
301
- - disk (dict):
302
- - read_mb (float): Cumulative disk read in MB since initialization
303
- - write_mb (float): Cumulative disk write in MB since initialization
304
- - used_gb (float): Total disk space used in GB
305
- - network (dict):
306
- - recv_mb (float): Cumulative network received in MB since initialization
307
- - sent_mb (float): Cumulative network sent in MB since initialization
308
- - gpus (dict): GPU metrics by device index (e.g., 0, 1) containing:
309
- - usage (int): GPU utilization percentage (0-100%)
310
- - memory (float): CUDA memory usage percentage (0-100%)
311
- - temp (int): GPU temperature in degrees Celsius
312
- - power (int): GPU power consumption in watts
381
+ Args:
382
+ rates (bool): If True, return disk/network as MB/s rates instead of cumulative MB.
313
383
 
314
384
  Returns:
315
- metrics (dict): System metrics containing 'cpu', 'ram', 'disk', 'network', 'gpus' with respective usage data.
385
+ (dict): Metrics dictionary with cpu, ram, disk, network, and gpus keys.
386
+
387
+ Examples:
388
+ >>> logger = SystemLogger()
389
+ >>> logger.get_metrics()["cpu"] # CPU percentage
390
+ >>> logger.get_metrics(rates=True)["network"]["recv_mbs"] # MB/s download rate
316
391
  """
317
392
  import psutil # scoped as slow import
318
393
 
@@ -320,21 +395,44 @@ class SystemLogger:
320
395
  disk = psutil.disk_io_counters()
321
396
  memory = psutil.virtual_memory()
322
397
  disk_usage = shutil.disk_usage("/")
398
+ now = time.time()
323
399
 
324
400
  metrics = {
325
401
  "cpu": round(psutil.cpu_percent(), 3),
326
402
  "ram": round(memory.percent, 3),
327
- "disk": {
403
+ "gpus": {},
404
+ }
405
+
406
+ # Calculate elapsed time since last call
407
+ elapsed = max(0.1, now - self._prev_time) # Avoid division by zero
408
+
409
+ if rates:
410
+ # Calculate MB/s rates from delta since last call
411
+ metrics["disk"] = {
412
+ "read_mbs": round(max(0, (disk.read_bytes - self._prev_disk.read_bytes) / (1 << 20) / elapsed), 3),
413
+ "write_mbs": round(max(0, (disk.write_bytes - self._prev_disk.write_bytes) / (1 << 20) / elapsed), 3),
414
+ "used_gb": round(disk_usage.used / (1 << 30), 3),
415
+ }
416
+ metrics["network"] = {
417
+ "recv_mbs": round(max(0, (net.bytes_recv - self._prev_net.bytes_recv) / (1 << 20) / elapsed), 3),
418
+ "sent_mbs": round(max(0, (net.bytes_sent - self._prev_net.bytes_sent) / (1 << 20) / elapsed), 3),
419
+ }
420
+ else:
421
+ # Cumulative MB since initialization (original behavior)
422
+ metrics["disk"] = {
328
423
  "read_mb": round((disk.read_bytes - self.disk_start.read_bytes) / (1 << 20), 3),
329
424
  "write_mb": round((disk.write_bytes - self.disk_start.write_bytes) / (1 << 20), 3),
330
425
  "used_gb": round(disk_usage.used / (1 << 30), 3),
331
- },
332
- "network": {
426
+ }
427
+ metrics["network"] = {
333
428
  "recv_mb": round((net.bytes_recv - self.net_start.bytes_recv) / (1 << 20), 3),
334
429
  "sent_mb": round((net.bytes_sent - self.net_start.bytes_sent) / (1 << 20), 3),
335
- },
336
- "gpus": {},
337
- }
430
+ }
431
+
432
+ # Always update previous values for accurate rate calculation on next call
433
+ self._prev_net = net
434
+ self._prev_disk = disk
435
+ self._prev_time = now
338
436
 
339
437
  # Add GPU metrics (NVIDIA only)
340
438
  if self.nvidia_initialized: