dgenerate-ultralytics-headless 8.3.248__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.
- {dgenerate_ultralytics_headless-8.3.248.dist-info → dgenerate_ultralytics_headless-8.4.7.dist-info}/METADATA +52 -61
- {dgenerate_ultralytics_headless-8.3.248.dist-info → dgenerate_ultralytics_headless-8.4.7.dist-info}/RECORD +97 -84
- {dgenerate_ultralytics_headless-8.3.248.dist-info → dgenerate_ultralytics_headless-8.4.7.dist-info}/WHEEL +1 -1
- tests/__init__.py +2 -2
- tests/conftest.py +1 -1
- tests/test_cuda.py +8 -2
- tests/test_engine.py +8 -8
- tests/test_exports.py +11 -4
- tests/test_integrations.py +9 -9
- tests/test_python.py +41 -16
- tests/test_solutions.py +3 -3
- ultralytics/__init__.py +1 -1
- ultralytics/cfg/__init__.py +31 -31
- ultralytics/cfg/datasets/TT100K.yaml +346 -0
- ultralytics/cfg/datasets/coco12-formats.yaml +101 -0
- ultralytics/cfg/default.yaml +3 -1
- ultralytics/cfg/models/26/yolo26-cls.yaml +33 -0
- ultralytics/cfg/models/26/yolo26-obb.yaml +52 -0
- ultralytics/cfg/models/26/yolo26-p2.yaml +60 -0
- ultralytics/cfg/models/26/yolo26-p6.yaml +62 -0
- ultralytics/cfg/models/26/yolo26-pose.yaml +53 -0
- ultralytics/cfg/models/26/yolo26-seg.yaml +52 -0
- ultralytics/cfg/models/26/yolo26.yaml +52 -0
- ultralytics/cfg/models/26/yoloe-26-seg.yaml +53 -0
- ultralytics/cfg/models/26/yoloe-26.yaml +53 -0
- ultralytics/data/annotator.py +2 -2
- ultralytics/data/augment.py +15 -0
- ultralytics/data/converter.py +76 -45
- ultralytics/data/dataset.py +1 -1
- ultralytics/data/utils.py +2 -2
- ultralytics/engine/exporter.py +34 -28
- ultralytics/engine/model.py +38 -37
- ultralytics/engine/predictor.py +17 -17
- ultralytics/engine/results.py +22 -15
- ultralytics/engine/trainer.py +83 -48
- ultralytics/engine/tuner.py +20 -11
- ultralytics/engine/validator.py +16 -16
- ultralytics/models/fastsam/predict.py +1 -1
- ultralytics/models/yolo/classify/predict.py +1 -1
- ultralytics/models/yolo/classify/train.py +1 -1
- ultralytics/models/yolo/classify/val.py +1 -1
- ultralytics/models/yolo/detect/predict.py +2 -2
- ultralytics/models/yolo/detect/train.py +6 -3
- ultralytics/models/yolo/detect/val.py +7 -1
- ultralytics/models/yolo/model.py +8 -8
- ultralytics/models/yolo/obb/predict.py +2 -2
- ultralytics/models/yolo/obb/train.py +3 -3
- ultralytics/models/yolo/obb/val.py +1 -1
- ultralytics/models/yolo/pose/predict.py +1 -1
- ultralytics/models/yolo/pose/train.py +3 -1
- ultralytics/models/yolo/pose/val.py +1 -1
- ultralytics/models/yolo/segment/predict.py +3 -3
- ultralytics/models/yolo/segment/train.py +4 -4
- ultralytics/models/yolo/segment/val.py +2 -2
- ultralytics/models/yolo/yoloe/train.py +6 -1
- ultralytics/models/yolo/yoloe/train_seg.py +6 -1
- ultralytics/nn/autobackend.py +14 -8
- ultralytics/nn/modules/__init__.py +8 -0
- ultralytics/nn/modules/block.py +128 -8
- ultralytics/nn/modules/head.py +788 -203
- ultralytics/nn/tasks.py +86 -41
- ultralytics/nn/text_model.py +5 -2
- ultralytics/optim/__init__.py +5 -0
- ultralytics/optim/muon.py +338 -0
- ultralytics/solutions/ai_gym.py +3 -3
- ultralytics/solutions/config.py +1 -1
- ultralytics/solutions/heatmap.py +1 -1
- ultralytics/solutions/instance_segmentation.py +2 -2
- ultralytics/solutions/object_counter.py +1 -1
- ultralytics/solutions/parking_management.py +1 -1
- ultralytics/solutions/solutions.py +2 -2
- ultralytics/trackers/byte_tracker.py +7 -7
- ultralytics/trackers/track.py +1 -1
- ultralytics/utils/__init__.py +8 -8
- ultralytics/utils/benchmarks.py +26 -26
- ultralytics/utils/callbacks/platform.py +173 -64
- ultralytics/utils/callbacks/tensorboard.py +2 -0
- ultralytics/utils/callbacks/wb.py +6 -1
- ultralytics/utils/checks.py +28 -9
- ultralytics/utils/dist.py +1 -0
- ultralytics/utils/downloads.py +5 -3
- ultralytics/utils/export/engine.py +19 -10
- ultralytics/utils/export/imx.py +38 -20
- ultralytics/utils/export/tensorflow.py +21 -21
- ultralytics/utils/files.py +2 -2
- ultralytics/utils/loss.py +597 -203
- ultralytics/utils/metrics.py +2 -1
- ultralytics/utils/ops.py +11 -2
- ultralytics/utils/patches.py +42 -0
- ultralytics/utils/plotting.py +3 -0
- ultralytics/utils/tal.py +100 -20
- ultralytics/utils/torch_utils.py +1 -1
- ultralytics/utils/tqdm.py +4 -1
- ultralytics/utils/tuner.py +2 -5
- {dgenerate_ultralytics_headless-8.3.248.dist-info → dgenerate_ultralytics_headless-8.4.7.dist-info}/entry_points.txt +0 -0
- {dgenerate_ultralytics_headless-8.3.248.dist-info → dgenerate_ultralytics_headless-8.4.7.dist-info}/licenses/LICENSE +0 -0
- {dgenerate_ultralytics_headless-8.3.248.dist-info → dgenerate_ultralytics_headless-8.4.7.dist-info}/top_level.txt +0 -0
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import os
|
|
4
4
|
import platform
|
|
5
|
+
import re
|
|
5
6
|
import socket
|
|
6
7
|
import sys
|
|
7
8
|
from concurrent.futures import ThreadPoolExecutor
|
|
@@ -11,9 +12,18 @@ from time import time
|
|
|
11
12
|
from ultralytics.utils import ENVIRONMENT, GIT, LOGGER, PYTHON_VERSION, RANK, SETTINGS, TESTS_RUNNING, colorstr
|
|
12
13
|
|
|
13
14
|
PREFIX = colorstr("Platform: ")
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
|
|
16
|
+
# Configurable platform URL for debugging (e.g. ULTRALYTICS_PLATFORM_URL=http://localhost:3000)
|
|
17
|
+
PLATFORM_URL = os.getenv("ULTRALYTICS_PLATFORM_URL", "https://platform.ultralytics.com").rstrip("/")
|
|
18
|
+
PLATFORM_API_URL = f"{PLATFORM_URL}/api/webhooks"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def slugify(text):
|
|
22
|
+
"""Convert text to URL-safe slug (e.g., 'My Project 1' -> 'my-project-1')."""
|
|
23
|
+
if not text:
|
|
24
|
+
return text
|
|
25
|
+
return re.sub(r"-+", "-", re.sub(r"[^a-z0-9\s-]", "", str(text).lower()).replace(" ", "-")).strip("-")[:128]
|
|
26
|
+
|
|
17
27
|
|
|
18
28
|
try:
|
|
19
29
|
assert not TESTS_RUNNING # do not log pytest
|
|
@@ -32,6 +42,84 @@ except (AssertionError, ImportError):
|
|
|
32
42
|
_api_key = None
|
|
33
43
|
|
|
34
44
|
|
|
45
|
+
def resolve_platform_uri(uri, hard=True):
|
|
46
|
+
"""Resolve ul:// URIs to signed URLs by authenticating with Ultralytics Platform.
|
|
47
|
+
|
|
48
|
+
Formats:
|
|
49
|
+
ul://username/datasets/slug -> Returns signed URL to NDJSON file
|
|
50
|
+
ul://username/project/model -> Returns signed URL to .pt file
|
|
51
|
+
|
|
52
|
+
Args:
|
|
53
|
+
uri (str): Platform URI starting with "ul://".
|
|
54
|
+
hard (bool): Whether to raise an error if resolution fails (FileNotFoundError only).
|
|
55
|
+
|
|
56
|
+
Returns:
|
|
57
|
+
(str | None): Signed URL on success, None if not found and hard=False.
|
|
58
|
+
|
|
59
|
+
Raises:
|
|
60
|
+
ValueError: If API key is missing/invalid or URI format is wrong.
|
|
61
|
+
PermissionError: If access is denied.
|
|
62
|
+
RuntimeError: If resource is not ready (e.g., dataset still processing).
|
|
63
|
+
FileNotFoundError: If resource not found and hard=True.
|
|
64
|
+
ConnectionError: If network request fails and hard=True.
|
|
65
|
+
"""
|
|
66
|
+
import requests
|
|
67
|
+
|
|
68
|
+
path = uri[5:] # Remove "ul://"
|
|
69
|
+
parts = path.split("/")
|
|
70
|
+
|
|
71
|
+
api_key = os.getenv("ULTRALYTICS_API_KEY") or SETTINGS.get("api_key")
|
|
72
|
+
if not api_key:
|
|
73
|
+
raise ValueError(f"ULTRALYTICS_API_KEY required for '{uri}'. Get key at {PLATFORM_URL}/settings")
|
|
74
|
+
|
|
75
|
+
base = PLATFORM_API_URL
|
|
76
|
+
headers = {"Authorization": f"Bearer {api_key}"}
|
|
77
|
+
|
|
78
|
+
# ul://username/datasets/slug
|
|
79
|
+
if len(parts) == 3 and parts[1] == "datasets":
|
|
80
|
+
username, _, slug = parts
|
|
81
|
+
url = f"{base}/datasets/{username}/{slug}/export"
|
|
82
|
+
|
|
83
|
+
# ul://username/project/model
|
|
84
|
+
elif len(parts) == 3:
|
|
85
|
+
username, project, model = parts
|
|
86
|
+
url = f"{base}/models/{username}/{project}/{model}/download"
|
|
87
|
+
|
|
88
|
+
else:
|
|
89
|
+
raise ValueError(f"Invalid platform URI: {uri}. Use ul://user/datasets/name or ul://user/project/model")
|
|
90
|
+
|
|
91
|
+
try:
|
|
92
|
+
timeout = 3600 if "/datasets/" in url else 90 # NDJSON generation can be slow for large datasets
|
|
93
|
+
r = requests.head(url, headers=headers, allow_redirects=False, timeout=timeout)
|
|
94
|
+
|
|
95
|
+
# Handle redirect responses (301, 302, 303, 307, 308)
|
|
96
|
+
if 300 <= r.status_code < 400 and "location" in r.headers:
|
|
97
|
+
return r.headers["location"] # Return signed URL
|
|
98
|
+
|
|
99
|
+
# Handle error responses
|
|
100
|
+
if r.status_code == 401:
|
|
101
|
+
raise ValueError(f"Invalid ULTRALYTICS_API_KEY for '{uri}'")
|
|
102
|
+
if r.status_code == 403:
|
|
103
|
+
raise PermissionError(f"Access denied for '{uri}'. Check dataset/model visibility settings.")
|
|
104
|
+
if r.status_code == 404:
|
|
105
|
+
if hard:
|
|
106
|
+
raise FileNotFoundError(f"Not found on platform: {uri}")
|
|
107
|
+
LOGGER.warning(f"Not found on platform: {uri}")
|
|
108
|
+
return None
|
|
109
|
+
if r.status_code == 409:
|
|
110
|
+
raise RuntimeError(f"Resource not ready: {uri}. Dataset may still be processing.")
|
|
111
|
+
|
|
112
|
+
# Unexpected response
|
|
113
|
+
r.raise_for_status()
|
|
114
|
+
raise RuntimeError(f"Unexpected response from platform for '{uri}': {r.status_code}")
|
|
115
|
+
|
|
116
|
+
except requests.exceptions.RequestException as e:
|
|
117
|
+
if hard:
|
|
118
|
+
raise ConnectionError(f"Failed to resolve {uri}: {e}") from e
|
|
119
|
+
LOGGER.warning(f"Failed to resolve {uri}: {e}")
|
|
120
|
+
return None
|
|
121
|
+
|
|
122
|
+
|
|
35
123
|
def _interp_plot(plot, n=101):
|
|
36
124
|
"""Interpolate plot curve data from 1000 to n points to reduce storage size."""
|
|
37
125
|
import numpy as np
|
|
@@ -60,22 +148,28 @@ def _interp_plot(plot, n=101):
|
|
|
60
148
|
return result
|
|
61
149
|
|
|
62
150
|
|
|
63
|
-
def _send(event, data, project, name):
|
|
64
|
-
"""Send event to Platform endpoint."""
|
|
151
|
+
def _send(event, data, project, name, model_id=None):
|
|
152
|
+
"""Send event to Platform endpoint. Returns response JSON on success."""
|
|
65
153
|
try:
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
154
|
+
payload = {"event": event, "project": project, "name": name, "data": data}
|
|
155
|
+
if model_id:
|
|
156
|
+
payload["modelId"] = model_id
|
|
157
|
+
r = requests.post(
|
|
158
|
+
f"{PLATFORM_API_URL}/training/metrics",
|
|
159
|
+
json=payload,
|
|
69
160
|
headers={"Authorization": f"Bearer {_api_key}"},
|
|
70
161
|
timeout=10,
|
|
71
|
-
)
|
|
162
|
+
)
|
|
163
|
+
r.raise_for_status()
|
|
164
|
+
return r.json()
|
|
72
165
|
except Exception as e:
|
|
73
166
|
LOGGER.debug(f"Platform: Failed to send {event}: {e}")
|
|
167
|
+
return None
|
|
74
168
|
|
|
75
169
|
|
|
76
|
-
def _send_async(event, data, project, name):
|
|
170
|
+
def _send_async(event, data, project, name, model_id=None):
|
|
77
171
|
"""Send event asynchronously using bounded thread pool."""
|
|
78
|
-
_executor.submit(_send, event, data, project, name)
|
|
172
|
+
_executor.submit(_send, event, data, project, name, model_id)
|
|
79
173
|
|
|
80
174
|
|
|
81
175
|
def _upload_model(model_path, project, name):
|
|
@@ -87,7 +181,7 @@ def _upload_model(model_path, project, name):
|
|
|
87
181
|
|
|
88
182
|
# Get signed upload URL
|
|
89
183
|
response = requests.post(
|
|
90
|
-
"
|
|
184
|
+
f"{PLATFORM_API_URL}/models/upload",
|
|
91
185
|
json={"project": project, "name": name, "filename": model_path.name},
|
|
92
186
|
headers={"Authorization": f"Bearer {_api_key}"},
|
|
93
187
|
timeout=10,
|
|
@@ -104,7 +198,7 @@ def _upload_model(model_path, project, name):
|
|
|
104
198
|
timeout=600, # 10 min timeout for large models
|
|
105
199
|
).raise_for_status()
|
|
106
200
|
|
|
107
|
-
# url = f"
|
|
201
|
+
# url = f"{PLATFORM_URL}/{project}/{name}"
|
|
108
202
|
# LOGGER.info(f"{PREFIX}Model uploaded to {url}")
|
|
109
203
|
return data.get("gcsPath")
|
|
110
204
|
|
|
@@ -169,132 +263,146 @@ def _get_environment_info():
|
|
|
169
263
|
return env
|
|
170
264
|
|
|
171
265
|
|
|
266
|
+
def _get_project_name(trainer):
|
|
267
|
+
"""Get slugified project and name from trainer args."""
|
|
268
|
+
raw = str(trainer.args.project)
|
|
269
|
+
parts = raw.split("/", 1)
|
|
270
|
+
project = f"{parts[0]}/{slugify(parts[1])}" if len(parts) == 2 else slugify(raw)
|
|
271
|
+
return project, slugify(str(trainer.args.name or "train"))
|
|
272
|
+
|
|
273
|
+
|
|
172
274
|
def on_pretrain_routine_start(trainer):
|
|
173
275
|
"""Initialize Platform logging at training start."""
|
|
174
|
-
global _console_logger, _last_upload
|
|
175
|
-
|
|
176
276
|
if RANK not in {-1, 0} or not trainer.args.project:
|
|
177
277
|
return
|
|
178
278
|
|
|
179
|
-
#
|
|
180
|
-
|
|
279
|
+
# Per-trainer state to isolate concurrent training runs
|
|
280
|
+
trainer._platform_model_id = None
|
|
281
|
+
trainer._platform_last_upload = time()
|
|
181
282
|
|
|
182
|
-
project, name =
|
|
183
|
-
url = f"
|
|
283
|
+
project, name = _get_project_name(trainer)
|
|
284
|
+
url = f"{PLATFORM_URL}/{project}/{name}"
|
|
184
285
|
LOGGER.info(f"{PREFIX}Streaming to {url}")
|
|
185
286
|
|
|
186
287
|
# Create callback to send console output to Platform
|
|
187
288
|
def send_console_output(content, line_count, chunk_id):
|
|
188
289
|
"""Send batched console output to Platform webhook."""
|
|
189
|
-
_send_async(
|
|
290
|
+
_send_async(
|
|
291
|
+
"console_output",
|
|
292
|
+
{"chunkId": chunk_id, "content": content, "lineCount": line_count},
|
|
293
|
+
project,
|
|
294
|
+
name,
|
|
295
|
+
getattr(trainer, "_platform_model_id", None),
|
|
296
|
+
)
|
|
190
297
|
|
|
191
298
|
# Start console capture with batching (5 lines or 5 seconds)
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
# Gather model info for richer metadata
|
|
196
|
-
model_info = {}
|
|
197
|
-
try:
|
|
198
|
-
info = model_info_for_loggers(trainer)
|
|
199
|
-
model_info = {
|
|
200
|
-
"parameters": info.get("model/parameters", 0),
|
|
201
|
-
"gflops": info.get("model/GFLOPs", 0),
|
|
202
|
-
"classes": getattr(trainer.model, "yaml", {}).get("nc", 0), # number of classes
|
|
203
|
-
}
|
|
204
|
-
except Exception:
|
|
205
|
-
pass
|
|
299
|
+
trainer._platform_console_logger = ConsoleLogger(batch_size=5, flush_interval=5.0, on_flush=send_console_output)
|
|
300
|
+
trainer._platform_console_logger.start_capture()
|
|
206
301
|
|
|
207
302
|
# Collect environment info (W&B-style metadata)
|
|
208
303
|
environment = _get_environment_info()
|
|
209
304
|
|
|
210
|
-
|
|
305
|
+
# Build trainArgs - callback runs before get_dataset() so args.data is still original (e.g., ul:// URIs)
|
|
306
|
+
# Note: model_info is sent later in on_fit_epoch_end (epoch 0) when the model is actually loaded
|
|
307
|
+
train_args = {k: str(v) for k, v in vars(trainer.args).items()}
|
|
308
|
+
|
|
309
|
+
# Send synchronously to get modelId for subsequent webhooks
|
|
310
|
+
response = _send(
|
|
211
311
|
"training_started",
|
|
212
312
|
{
|
|
213
|
-
"trainArgs":
|
|
313
|
+
"trainArgs": train_args,
|
|
214
314
|
"epochs": trainer.epochs,
|
|
215
315
|
"device": str(trainer.device),
|
|
216
|
-
"modelInfo": model_info,
|
|
217
316
|
"environment": environment,
|
|
218
317
|
},
|
|
219
318
|
project,
|
|
220
319
|
name,
|
|
221
320
|
)
|
|
321
|
+
if response and response.get("modelId"):
|
|
322
|
+
trainer._platform_model_id = response["modelId"]
|
|
222
323
|
|
|
223
324
|
|
|
224
325
|
def on_fit_epoch_end(trainer):
|
|
225
326
|
"""Log training and system metrics at epoch end."""
|
|
226
|
-
global _system_logger
|
|
227
|
-
|
|
228
327
|
if RANK not in {-1, 0} or not trainer.args.project:
|
|
229
328
|
return
|
|
230
329
|
|
|
231
|
-
project, name =
|
|
330
|
+
project, name = _get_project_name(trainer)
|
|
232
331
|
metrics = {**trainer.label_loss_items(trainer.tloss, prefix="train"), **trainer.metrics}
|
|
233
332
|
|
|
234
333
|
if trainer.optimizer and trainer.optimizer.param_groups:
|
|
235
334
|
metrics["lr"] = trainer.optimizer.param_groups[0]["lr"]
|
|
335
|
+
|
|
336
|
+
# Extract model info at epoch 0 (sent as separate field, not in metrics)
|
|
337
|
+
model_info = None
|
|
236
338
|
if trainer.epoch == 0:
|
|
237
339
|
try:
|
|
238
|
-
|
|
340
|
+
info = model_info_for_loggers(trainer)
|
|
341
|
+
model_info = {
|
|
342
|
+
"parameters": info.get("model/parameters", 0),
|
|
343
|
+
"gflops": info.get("model/GFLOPs", 0),
|
|
344
|
+
"speedMs": info.get("model/speed_PyTorch(ms)", 0),
|
|
345
|
+
}
|
|
239
346
|
except Exception:
|
|
240
347
|
pass
|
|
241
348
|
|
|
242
|
-
# Get system metrics (cache SystemLogger for efficiency)
|
|
349
|
+
# Get system metrics (cache SystemLogger on trainer for efficiency)
|
|
243
350
|
system = {}
|
|
244
351
|
try:
|
|
245
|
-
if
|
|
246
|
-
|
|
247
|
-
system =
|
|
352
|
+
if not hasattr(trainer, "_platform_system_logger"):
|
|
353
|
+
trainer._platform_system_logger = SystemLogger()
|
|
354
|
+
system = trainer._platform_system_logger.get_metrics(rates=True)
|
|
248
355
|
except Exception:
|
|
249
356
|
pass
|
|
250
357
|
|
|
358
|
+
payload = {
|
|
359
|
+
"epoch": trainer.epoch,
|
|
360
|
+
"metrics": metrics,
|
|
361
|
+
"system": system,
|
|
362
|
+
"fitness": trainer.fitness,
|
|
363
|
+
"best_fitness": trainer.best_fitness,
|
|
364
|
+
}
|
|
365
|
+
if model_info:
|
|
366
|
+
payload["modelInfo"] = model_info
|
|
367
|
+
|
|
251
368
|
_send_async(
|
|
252
369
|
"epoch_end",
|
|
253
|
-
|
|
254
|
-
"epoch": trainer.epoch,
|
|
255
|
-
"metrics": metrics,
|
|
256
|
-
"system": system,
|
|
257
|
-
"fitness": trainer.fitness,
|
|
258
|
-
"best_fitness": trainer.best_fitness,
|
|
259
|
-
},
|
|
370
|
+
payload,
|
|
260
371
|
project,
|
|
261
372
|
name,
|
|
373
|
+
getattr(trainer, "_platform_model_id", None),
|
|
262
374
|
)
|
|
263
375
|
|
|
264
376
|
|
|
265
377
|
def on_model_save(trainer):
|
|
266
378
|
"""Upload model checkpoint (rate limited to every 15 min)."""
|
|
267
|
-
global _last_upload
|
|
268
|
-
|
|
269
379
|
if RANK not in {-1, 0} or not trainer.args.project:
|
|
270
380
|
return
|
|
271
381
|
|
|
272
382
|
# Rate limit to every 15 minutes (900 seconds)
|
|
273
|
-
if time() -
|
|
383
|
+
if time() - getattr(trainer, "_platform_last_upload", 0) < 900:
|
|
274
384
|
return
|
|
275
385
|
|
|
276
386
|
model_path = trainer.best if trainer.best and Path(trainer.best).exists() else trainer.last
|
|
277
387
|
if not model_path:
|
|
278
388
|
return
|
|
279
389
|
|
|
280
|
-
project, name =
|
|
390
|
+
project, name = _get_project_name(trainer)
|
|
281
391
|
_upload_model_async(model_path, project, name)
|
|
282
|
-
|
|
392
|
+
trainer._platform_last_upload = time()
|
|
283
393
|
|
|
284
394
|
|
|
285
395
|
def on_train_end(trainer):
|
|
286
396
|
"""Log final results, upload best model, and send validation plot data."""
|
|
287
|
-
global _console_logger
|
|
288
|
-
|
|
289
397
|
if RANK not in {-1, 0} or not trainer.args.project:
|
|
290
398
|
return
|
|
291
399
|
|
|
292
|
-
project, name =
|
|
400
|
+
project, name = _get_project_name(trainer)
|
|
293
401
|
|
|
294
402
|
# Stop console capture
|
|
295
|
-
if
|
|
296
|
-
|
|
297
|
-
|
|
403
|
+
if hasattr(trainer, "_platform_console_logger") and trainer._platform_console_logger:
|
|
404
|
+
trainer._platform_console_logger.stop_capture()
|
|
405
|
+
trainer._platform_console_logger = None
|
|
298
406
|
|
|
299
407
|
# Upload best model (blocking to ensure it completes)
|
|
300
408
|
model_path = None
|
|
@@ -332,8 +440,9 @@ def on_train_end(trainer):
|
|
|
332
440
|
},
|
|
333
441
|
project,
|
|
334
442
|
name,
|
|
443
|
+
getattr(trainer, "_platform_model_id", None),
|
|
335
444
|
)
|
|
336
|
-
url = f"
|
|
445
|
+
url = f"{PLATFORM_URL}/{project}/{name}"
|
|
337
446
|
LOGGER.info(f"{PREFIX}View results at {url}")
|
|
338
447
|
|
|
339
448
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# Ultralytics 🚀 AGPL-3.0 License - https://ultralytics.com/license
|
|
2
2
|
|
|
3
3
|
from ultralytics.utils import LOGGER, SETTINGS, TESTS_RUNNING, colorstr, torch_utils
|
|
4
|
+
from ultralytics.utils.torch_utils import smart_inference_mode
|
|
4
5
|
|
|
5
6
|
try:
|
|
6
7
|
assert not TESTS_RUNNING # do not log pytest
|
|
@@ -38,6 +39,7 @@ def _log_scalars(scalars: dict, step: int = 0) -> None:
|
|
|
38
39
|
WRITER.add_scalar(k, v, step)
|
|
39
40
|
|
|
40
41
|
|
|
42
|
+
@smart_inference_mode()
|
|
41
43
|
def _log_tensorboard_graph(trainer) -> None:
|
|
42
44
|
"""Log model graph to TensorBoard.
|
|
43
45
|
|
|
@@ -128,10 +128,15 @@ def _log_plots(plots, step):
|
|
|
128
128
|
def on_pretrain_routine_start(trainer):
|
|
129
129
|
"""Initialize and start wandb project if module is present."""
|
|
130
130
|
if not wb.run:
|
|
131
|
+
from datetime import datetime
|
|
132
|
+
|
|
133
|
+
name = str(trainer.args.name).replace("/", "-").replace(" ", "_")
|
|
131
134
|
wb.init(
|
|
132
135
|
project=str(trainer.args.project).replace("/", "-") if trainer.args.project else "Ultralytics",
|
|
133
|
-
name=
|
|
136
|
+
name=name,
|
|
134
137
|
config=vars(trainer.args),
|
|
138
|
+
id=f"{name}_{datetime.now().strftime('%Y%m%d_%H%M%S')}", # add unique id
|
|
139
|
+
dir=str(trainer.save_dir),
|
|
135
140
|
)
|
|
136
141
|
|
|
137
142
|
|
ultralytics/utils/checks.py
CHANGED
|
@@ -530,7 +530,7 @@ def check_torchvision():
|
|
|
530
530
|
)
|
|
531
531
|
|
|
532
532
|
|
|
533
|
-
def check_suffix(file="
|
|
533
|
+
def check_suffix(file="yolo26n.pt", suffix=".pt", msg=""):
|
|
534
534
|
"""Check file(s) for acceptable suffix.
|
|
535
535
|
|
|
536
536
|
Args:
|
|
@@ -584,7 +584,7 @@ def check_model_file_from_stem(model="yolo11n"):
|
|
|
584
584
|
"""
|
|
585
585
|
path = Path(model)
|
|
586
586
|
if not path.suffix and path.stem in downloads.GITHUB_ASSETS_STEMS:
|
|
587
|
-
return path.with_suffix(".pt") # add suffix, i.e.
|
|
587
|
+
return path.with_suffix(".pt") # add suffix, i.e. yolo26n -> yolo26n.pt
|
|
588
588
|
return model
|
|
589
589
|
|
|
590
590
|
|
|
@@ -592,7 +592,7 @@ def check_file(file, suffix="", download=True, download_dir=".", hard=True):
|
|
|
592
592
|
"""Search/download file (if necessary), check suffix (if provided), and return path.
|
|
593
593
|
|
|
594
594
|
Args:
|
|
595
|
-
file (str): File name or path.
|
|
595
|
+
file (str): File name or path, URL, platform URI (ul://), or GCS path (gs://).
|
|
596
596
|
suffix (str | tuple): Acceptable suffix or tuple of suffixes to validate against the file.
|
|
597
597
|
download (bool): Whether to download the file if it doesn't exist locally.
|
|
598
598
|
download_dir (str): Directory to download the file to.
|
|
@@ -610,7 +610,26 @@ def check_file(file, suffix="", download=True, download_dir=".", hard=True):
|
|
|
610
610
|
or file.lower().startswith("grpc://")
|
|
611
611
|
): # file exists or gRPC Triton images
|
|
612
612
|
return file
|
|
613
|
-
elif download and file.lower().startswith(
|
|
613
|
+
elif download and file.lower().startswith("ul://"): # Ultralytics Platform URI
|
|
614
|
+
from ultralytics.utils.callbacks.platform import resolve_platform_uri
|
|
615
|
+
|
|
616
|
+
url = resolve_platform_uri(file, hard=hard) # Convert to signed HTTPS URL
|
|
617
|
+
if url is None:
|
|
618
|
+
return [] # Not found, soft fail (consistent with file search behavior)
|
|
619
|
+
# Use URI path for unique directory structure: ul://user/project/model -> user/project/model/filename.pt
|
|
620
|
+
uri_path = file[5:] # Remove "ul://"
|
|
621
|
+
local_file = Path(download_dir) / uri_path / url2file(url)
|
|
622
|
+
if local_file.exists():
|
|
623
|
+
LOGGER.info(f"Found {clean_url(url)} locally at {local_file}")
|
|
624
|
+
else:
|
|
625
|
+
local_file.parent.mkdir(parents=True, exist_ok=True)
|
|
626
|
+
downloads.safe_download(url=url, file=local_file, unzip=False)
|
|
627
|
+
return str(local_file)
|
|
628
|
+
elif download and file.lower().startswith(
|
|
629
|
+
("https://", "http://", "rtsp://", "rtmp://", "tcp://", "gs://")
|
|
630
|
+
): # download
|
|
631
|
+
if file.startswith("gs://"):
|
|
632
|
+
file = "https://storage.googleapis.com/" + file[5:] # convert gs:// to public HTTPS URL
|
|
614
633
|
url = file # warning: Pathlib turns :// -> :/
|
|
615
634
|
file = Path(download_dir) / url2file(file) # '%2F' to '/', split https://url.com/file.txt?auth
|
|
616
635
|
if file.exists():
|
|
@@ -793,7 +812,7 @@ def check_amp(model):
|
|
|
793
812
|
Examples:
|
|
794
813
|
>>> from ultralytics import YOLO
|
|
795
814
|
>>> from ultralytics.utils.checks import check_amp
|
|
796
|
-
>>> model = YOLO("
|
|
815
|
+
>>> model = YOLO("yolo26n.pt").model.cuda()
|
|
797
816
|
>>> check_amp(model)
|
|
798
817
|
"""
|
|
799
818
|
from ultralytics.utils.torch_utils import autocast
|
|
@@ -832,14 +851,14 @@ def check_amp(model):
|
|
|
832
851
|
try:
|
|
833
852
|
from ultralytics import YOLO
|
|
834
853
|
|
|
835
|
-
assert amp_allclose(YOLO("
|
|
854
|
+
assert amp_allclose(YOLO("yolo26n.pt"), im)
|
|
836
855
|
LOGGER.info(f"{prefix}checks passed ✅")
|
|
837
856
|
except ConnectionError:
|
|
838
|
-
LOGGER.warning(f"{prefix}checks skipped. Offline and unable to download
|
|
857
|
+
LOGGER.warning(f"{prefix}checks skipped. Offline and unable to download YOLO26n for AMP checks. {warning_msg}")
|
|
839
858
|
except (AttributeError, ModuleNotFoundError):
|
|
840
859
|
LOGGER.warning(
|
|
841
860
|
f"{prefix}checks skipped. "
|
|
842
|
-
f"Unable to load
|
|
861
|
+
f"Unable to load YOLO26n for AMP checks due to possible Ultralytics package modifications. {warning_msg}"
|
|
843
862
|
)
|
|
844
863
|
except AssertionError:
|
|
845
864
|
LOGGER.error(
|
|
@@ -945,7 +964,7 @@ def is_rockchip():
|
|
|
945
964
|
with open("/proc/device-tree/compatible") as f:
|
|
946
965
|
dev_str = f.read()
|
|
947
966
|
*_, soc = dev_str.split(",")
|
|
948
|
-
if soc.replace("\x00", "") in RKNN_CHIPS:
|
|
967
|
+
if soc.replace("\x00", "").split("-", 1)[0] in RKNN_CHIPS:
|
|
949
968
|
return True
|
|
950
969
|
except OSError:
|
|
951
970
|
return False
|
ultralytics/utils/dist.py
CHANGED
|
@@ -49,6 +49,7 @@ def generate_ddp_file(trainer):
|
|
|
49
49
|
|
|
50
50
|
content = f"""
|
|
51
51
|
# Ultralytics Multi-GPU training temp file (should be automatically deleted after use)
|
|
52
|
+
from pathlib import Path, PosixPath # For model arguments stored as Path instead of str
|
|
52
53
|
overrides = {vars(trainer.args)}
|
|
53
54
|
|
|
54
55
|
if __name__ == "__main__":
|
ultralytics/utils/downloads.py
CHANGED
|
@@ -18,12 +18,14 @@ GITHUB_ASSETS_NAMES = frozenset(
|
|
|
18
18
|
[f"yolov8{k}{suffix}.pt" for k in "nsmlx" for suffix in ("", "-cls", "-seg", "-pose", "-obb", "-oiv7")]
|
|
19
19
|
+ [f"yolo11{k}{suffix}.pt" for k in "nsmlx" for suffix in ("", "-cls", "-seg", "-pose", "-obb")]
|
|
20
20
|
+ [f"yolo12{k}{suffix}.pt" for k in "nsmlx" for suffix in ("",)] # detect models only currently
|
|
21
|
+
+ [f"yolo26{k}{suffix}.pt" for k in "nsmlx" for suffix in ("", "-cls", "-seg", "-pose", "-obb")]
|
|
21
22
|
+ [f"yolov5{k}{resolution}u.pt" for k in "nsmlx" for resolution in ("", "6")]
|
|
22
23
|
+ [f"yolov3{k}u.pt" for k in ("", "-spp", "-tiny")]
|
|
23
24
|
+ [f"yolov8{k}-world.pt" for k in "smlx"]
|
|
24
25
|
+ [f"yolov8{k}-worldv2.pt" for k in "smlx"]
|
|
25
26
|
+ [f"yoloe-v8{k}{suffix}.pt" for k in "sml" for suffix in ("-seg", "-seg-pf")]
|
|
26
27
|
+ [f"yoloe-11{k}{suffix}.pt" for k in "sml" for suffix in ("-seg", "-seg-pf")]
|
|
28
|
+
+ [f"yoloe-26{k}{suffix}.pt" for k in "nsmlx" for suffix in ("-seg", "-seg-pf")]
|
|
27
29
|
+ [f"yolov9{k}.pt" for k in "tsmce"]
|
|
28
30
|
+ [f"yolov10{k}.pt" for k in "nsmblx"]
|
|
29
31
|
+ [f"yolo_nas_{k}.pt" for k in "sml"]
|
|
@@ -418,13 +420,13 @@ def get_github_assets(
|
|
|
418
420
|
LOGGER.warning(f"GitHub assets check failure for {url}: {r.status_code} {r.reason}")
|
|
419
421
|
return "", []
|
|
420
422
|
data = r.json()
|
|
421
|
-
return data["tag_name"], [x["name"] for x in data["assets"]] # tag, assets i.e. ['
|
|
423
|
+
return data["tag_name"], [x["name"] for x in data["assets"]] # tag, assets i.e. ['yolo26n.pt', 'yolo11s.pt', ...]
|
|
422
424
|
|
|
423
425
|
|
|
424
426
|
def attempt_download_asset(
|
|
425
427
|
file: str | Path,
|
|
426
428
|
repo: str = "ultralytics/assets",
|
|
427
|
-
release: str = "v8.
|
|
429
|
+
release: str = "v8.4.0",
|
|
428
430
|
**kwargs,
|
|
429
431
|
) -> str:
|
|
430
432
|
"""Attempt to download a file from GitHub release assets if it is not found locally.
|
|
@@ -439,7 +441,7 @@ def attempt_download_asset(
|
|
|
439
441
|
(str): The path to the downloaded file.
|
|
440
442
|
|
|
441
443
|
Examples:
|
|
442
|
-
>>> file_path = attempt_download_asset("
|
|
444
|
+
>>> file_path = attempt_download_asset("yolo26n.pt", repo="ultralytics/assets", release="latest")
|
|
443
445
|
"""
|
|
444
446
|
from ultralytics.utils import SETTINGS # scoped for circular import
|
|
445
447
|
|
|
@@ -143,7 +143,7 @@ def onnx2engine(
|
|
|
143
143
|
for inp in inputs:
|
|
144
144
|
profile.set_shape(inp.name, min=min_shape, opt=shape, max=max_shape)
|
|
145
145
|
config.add_optimization_profile(profile)
|
|
146
|
-
if int8:
|
|
146
|
+
if int8 and not is_trt10: # deprecated in TensorRT 10, causes internal errors
|
|
147
147
|
config.set_calibration_profile(profile)
|
|
148
148
|
|
|
149
149
|
LOGGER.info(f"{prefix} building {'INT8' if int8 else 'FP' + ('16' if half else '32')} engine as {engine_file}")
|
|
@@ -226,12 +226,21 @@ def onnx2engine(
|
|
|
226
226
|
config.set_flag(trt.BuilderFlag.FP16)
|
|
227
227
|
|
|
228
228
|
# Write file
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
if
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
229
|
+
if is_trt10:
|
|
230
|
+
# TensorRT 10+ returns bytes directly, not a context manager
|
|
231
|
+
engine = builder.build_serialized_network(network, config)
|
|
232
|
+
if engine is None:
|
|
233
|
+
raise RuntimeError("TensorRT engine build failed, check logs for errors")
|
|
234
|
+
with open(engine_file, "wb") as t:
|
|
235
|
+
if metadata is not None:
|
|
236
|
+
meta = json.dumps(metadata)
|
|
237
|
+
t.write(len(meta).to_bytes(4, byteorder="little", signed=True))
|
|
238
|
+
t.write(meta.encode())
|
|
239
|
+
t.write(engine)
|
|
240
|
+
else:
|
|
241
|
+
with builder.build_engine(network, config) as engine, open(engine_file, "wb") as t:
|
|
242
|
+
if metadata is not None:
|
|
243
|
+
meta = json.dumps(metadata)
|
|
244
|
+
t.write(len(meta).to_bytes(4, byteorder="little", signed=True))
|
|
245
|
+
t.write(meta.encode())
|
|
246
|
+
t.write(engine.serialize())
|