euler-train 2.4.0__tar.gz → 2.5.0__tar.gz
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.
- {euler_train-2.4.0 → euler_train-2.5.0}/PKG-INFO +3 -1
- {euler_train-2.4.0 → euler_train-2.5.0}/euler_train/__init__.py +8 -0
- {euler_train-2.4.0 → euler_train-2.5.0}/euler_train/outputs.py +10 -1
- {euler_train-2.4.0 → euler_train-2.5.0}/euler_train/run.py +47 -2
- {euler_train-2.4.0 → euler_train-2.5.0}/euler_train.egg-info/PKG-INFO +3 -1
- {euler_train-2.4.0 → euler_train-2.5.0}/euler_train.egg-info/requires.txt +3 -0
- {euler_train-2.4.0 → euler_train-2.5.0}/pyproject.toml +2 -1
- {euler_train-2.4.0 → euler_train-2.5.0}/tests/test_runlog.py +212 -0
- {euler_train-2.4.0 → euler_train-2.5.0}/README.md +0 -0
- {euler_train-2.4.0 → euler_train-2.5.0}/euler_train/_dataset_contract.py +0 -0
- {euler_train-2.4.0 → euler_train-2.5.0}/euler_train/architecture.py +0 -0
- {euler_train-2.4.0 → euler_train-2.5.0}/euler_train/environment.py +0 -0
- {euler_train-2.4.0 → euler_train-2.5.0}/euler_train/git_info.py +0 -0
- {euler_train-2.4.0 → euler_train-2.5.0}/euler_train/serialization.py +0 -0
- {euler_train-2.4.0 → euler_train-2.5.0}/euler_train/slurm.py +0 -0
- {euler_train-2.4.0 → euler_train-2.5.0}/euler_train/stream.py +0 -0
- {euler_train-2.4.0 → euler_train-2.5.0}/euler_train.egg-info/SOURCES.txt +0 -0
- {euler_train-2.4.0 → euler_train-2.5.0}/euler_train.egg-info/dependency_links.txt +0 -0
- {euler_train-2.4.0 → euler_train-2.5.0}/euler_train.egg-info/top_level.txt +0 -0
- {euler_train-2.4.0 → euler_train-2.5.0}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: euler-train
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.5.0
|
|
4
4
|
Summary: Lightweight file-based experiment logger for PyTorch
|
|
5
5
|
Requires-Python: >=3.9
|
|
6
6
|
Requires-Dist: numpy
|
|
@@ -13,6 +13,8 @@ Provides-Extra: architecture
|
|
|
13
13
|
Requires-Dist: onnx; extra == "architecture"
|
|
14
14
|
Requires-Dist: onnxruntime; extra == "architecture"
|
|
15
15
|
Requires-Dist: onnxsim; extra == "architecture"
|
|
16
|
+
Provides-Extra: naming
|
|
17
|
+
Requires-Dist: euler-metric-naming; extra == "naming"
|
|
16
18
|
Provides-Extra: dev
|
|
17
19
|
Requires-Dist: pytest; extra == "dev"
|
|
18
20
|
Requires-Dist: Pillow; extra == "dev"
|
|
@@ -39,6 +39,7 @@ def init(
|
|
|
39
39
|
evaluations: dict[str, dict] | None = None,
|
|
40
40
|
mode: str | None = None,
|
|
41
41
|
stream: Any = None,
|
|
42
|
+
metric_naming: dict[str, Any] | None = None,
|
|
42
43
|
) -> Run:
|
|
43
44
|
"""Create a new run — or resume an existing one — and return the handle.
|
|
44
45
|
|
|
@@ -112,6 +113,12 @@ def init(
|
|
|
112
113
|
``model_id`` / ``api_token`` (or legacy ``access_token``)
|
|
113
114
|
enables best-effort dual-write streaming to the Euler View
|
|
114
115
|
model-run ingest API.
|
|
116
|
+
metric_naming:
|
|
117
|
+
Optional structured metric naming declaration. When provided,
|
|
118
|
+
stored in ``meta.json`` under ``metric_naming`` and included
|
|
119
|
+
in the stream init event so euler-view can resolve per-metric
|
|
120
|
+
namespaces. Typically generated by
|
|
121
|
+
``euler_metric_naming.MetricNamespace.training_naming_config()``.
|
|
115
122
|
"""
|
|
116
123
|
return Run(
|
|
117
124
|
dir=dir, config=config, meta=meta,
|
|
@@ -121,4 +128,5 @@ def init(
|
|
|
121
128
|
datasets=datasets, run_name=run_name,
|
|
122
129
|
evaluations=evaluations, mode=mode,
|
|
123
130
|
stream=stream,
|
|
131
|
+
metric_naming=metric_naming,
|
|
124
132
|
)
|
|
@@ -68,10 +68,19 @@ def _save_slot(
|
|
|
68
68
|
visualization_overrides: dict[str, Any],
|
|
69
69
|
) -> None:
|
|
70
70
|
slot_dir.mkdir(parents=True, exist_ok=True)
|
|
71
|
-
items = _unpack(data)
|
|
72
71
|
visualization = _resolve_visualization(
|
|
73
72
|
output_type, leaf_name, visualization_overrides,
|
|
74
73
|
)
|
|
74
|
+
|
|
75
|
+
# Dict with string keys → named outputs (e.g. {"scene_042": img}).
|
|
76
|
+
if isinstance(data, dict):
|
|
77
|
+
for name, raw in data.items():
|
|
78
|
+
item = _prepare(raw)
|
|
79
|
+
fmt = _resolve_format(item, output_type, leaf_name, format_overrides)
|
|
80
|
+
_save_item(slot_dir / f"{name}.{fmt}", item, fmt, visualization)
|
|
81
|
+
return
|
|
82
|
+
|
|
83
|
+
items = _unpack(data)
|
|
75
84
|
for idx, item in enumerate(items):
|
|
76
85
|
fmt = _resolve_format(item, output_type, leaf_name, format_overrides)
|
|
77
86
|
_save_item(slot_dir / f"{idx:04d}.{fmt}", item, fmt, visualization)
|
|
@@ -48,6 +48,7 @@ class Run:
|
|
|
48
48
|
evaluations: dict[str, dict[str, Any]] | None = None,
|
|
49
49
|
mode: str | None = None,
|
|
50
50
|
stream: Any = None,
|
|
51
|
+
metric_naming: dict[str, Any] | None = None,
|
|
51
52
|
) -> None:
|
|
52
53
|
# ── detect run-directory shorthand ────────────────────────
|
|
53
54
|
# When `dir` points to an existing run directory (contains
|
|
@@ -175,6 +176,12 @@ class Run:
|
|
|
175
176
|
evaluations=evaluations,
|
|
176
177
|
existing=self._meta.get("evaluations"),
|
|
177
178
|
)
|
|
179
|
+
|
|
180
|
+
# ── metric naming (optional structured naming declaration) ──
|
|
181
|
+
if metric_naming is not None:
|
|
182
|
+
_validate_metric_naming(metric_naming)
|
|
183
|
+
self._meta["metric_naming"] = metric_naming
|
|
184
|
+
|
|
178
185
|
self._mark_mode_running()
|
|
179
186
|
self._flush_meta()
|
|
180
187
|
self._bind_stream()
|
|
@@ -311,7 +318,14 @@ class Run:
|
|
|
311
318
|
# ── GPU stats ─────────────────────────────────────────────────
|
|
312
319
|
|
|
313
320
|
def _get_gpu_stats(self) -> dict[str, Any]:
|
|
314
|
-
"""Return GPU utilization & memory stats, or {} if unavailable.
|
|
321
|
+
"""Return GPU utilization & memory stats, or {} if unavailable.
|
|
322
|
+
|
|
323
|
+
When the run has a ``metric_naming`` declaration, keys are
|
|
324
|
+
namespaced under ``sys.train.*`` so euler-view can resolve them
|
|
325
|
+
against the declared ``sys.train`` namespace. Legacy runs
|
|
326
|
+
(without ``metric_naming``) retain the flat key names for
|
|
327
|
+
backward compatibility.
|
|
328
|
+
"""
|
|
315
329
|
if self._gpu_available is None:
|
|
316
330
|
try:
|
|
317
331
|
import pynvml
|
|
@@ -328,7 +342,7 @@ class Run:
|
|
|
328
342
|
try:
|
|
329
343
|
util = pynvml.nvmlDeviceGetUtilizationRates(self._gpu_handle)
|
|
330
344
|
mem = pynvml.nvmlDeviceGetMemoryInfo(self._gpu_handle)
|
|
331
|
-
|
|
345
|
+
raw = {
|
|
332
346
|
"gpu_util_pct": util.gpu,
|
|
333
347
|
"gpu_mem_util_pct": util.memory,
|
|
334
348
|
"gpu_mem_used_gb": round(mem.used / 1e9, 3),
|
|
@@ -336,6 +350,9 @@ class Run:
|
|
|
336
350
|
}
|
|
337
351
|
except Exception:
|
|
338
352
|
return {}
|
|
353
|
+
if self._meta.get("metric_naming") is not None:
|
|
354
|
+
return {f"sys.train.{k}": v for k, v in raw.items()}
|
|
355
|
+
return raw
|
|
339
356
|
|
|
340
357
|
# ── visual / heavy outputs ────────────────────────────────────
|
|
341
358
|
|
|
@@ -358,6 +375,14 @@ class Run:
|
|
|
358
375
|
aux=dict(transmission=t_map)),
|
|
359
376
|
)
|
|
360
377
|
|
|
378
|
+
Slot values can also be dicts mapping custom string IDs to
|
|
379
|
+
items, producing named files instead of sequential indices::
|
|
380
|
+
|
|
381
|
+
run.save_outputs(
|
|
382
|
+
epoch=1, step=500,
|
|
383
|
+
rgb=dict(pred={"scene_042": img_a, "scene_117": img_b}),
|
|
384
|
+
)
|
|
385
|
+
|
|
361
386
|
Accepted slot keys: ``pred``, ``gt``, ``input``, ``aux``.
|
|
362
387
|
``aux`` expects a sub-dict of named arrays.
|
|
363
388
|
|
|
@@ -1035,6 +1060,26 @@ def _normalize_mode(mode: str | None) -> str | None:
|
|
|
1035
1060
|
return normalized
|
|
1036
1061
|
|
|
1037
1062
|
|
|
1063
|
+
def _validate_metric_naming(value: Any) -> None:
|
|
1064
|
+
"""Lightweight structural validation of a metric_naming payload."""
|
|
1065
|
+
if not isinstance(value, dict):
|
|
1066
|
+
raise TypeError("metric_naming must be a dict")
|
|
1067
|
+
namespaces = value.get("namespaces")
|
|
1068
|
+
if not isinstance(namespaces, dict):
|
|
1069
|
+
raise ValueError(
|
|
1070
|
+
"metric_naming must contain a 'namespaces' dict"
|
|
1071
|
+
)
|
|
1072
|
+
for ns_key, ns_decl in namespaces.items():
|
|
1073
|
+
if not isinstance(ns_key, str):
|
|
1074
|
+
raise ValueError(
|
|
1075
|
+
f"metric_naming namespace key must be a string, got {type(ns_key).__name__}"
|
|
1076
|
+
)
|
|
1077
|
+
if not isinstance(ns_decl, dict):
|
|
1078
|
+
raise ValueError(
|
|
1079
|
+
f"metric_naming namespace {ns_key!r} must be a dict"
|
|
1080
|
+
)
|
|
1081
|
+
|
|
1082
|
+
|
|
1038
1083
|
def _build_datasets_meta(
|
|
1039
1084
|
datasets: Mapping[str, Any],
|
|
1040
1085
|
existing: Any,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: euler-train
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.5.0
|
|
4
4
|
Summary: Lightweight file-based experiment logger for PyTorch
|
|
5
5
|
Requires-Python: >=3.9
|
|
6
6
|
Requires-Dist: numpy
|
|
@@ -13,6 +13,8 @@ Provides-Extra: architecture
|
|
|
13
13
|
Requires-Dist: onnx; extra == "architecture"
|
|
14
14
|
Requires-Dist: onnxruntime; extra == "architecture"
|
|
15
15
|
Requires-Dist: onnxsim; extra == "architecture"
|
|
16
|
+
Provides-Extra: naming
|
|
17
|
+
Requires-Dist: euler-metric-naming; extra == "naming"
|
|
16
18
|
Provides-Extra: dev
|
|
17
19
|
Requires-Dist: pytest; extra == "dev"
|
|
18
20
|
Requires-Dist: Pillow; extra == "dev"
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "euler-train"
|
|
7
|
-
version = "2.
|
|
7
|
+
version = "2.5.0"
|
|
8
8
|
description = "Lightweight file-based experiment logger for PyTorch"
|
|
9
9
|
requires-python = ">=3.9"
|
|
10
10
|
dependencies = ["numpy", "euler-dataset-contract"]
|
|
@@ -13,6 +13,7 @@ dependencies = ["numpy", "euler-dataset-contract"]
|
|
|
13
13
|
images = ["Pillow"]
|
|
14
14
|
gpu = ["nvidia-ml-py"]
|
|
15
15
|
architecture = ["onnx", "onnxruntime", "onnxsim"]
|
|
16
|
+
naming = ["euler-metric-naming"]
|
|
16
17
|
dev = ["pytest", "Pillow", "torch", "nvidia-ml-py", "onnx", "onnxruntime", "onnxsim"]
|
|
17
18
|
|
|
18
19
|
[tool.pytest.ini_options]
|
|
@@ -1444,6 +1444,42 @@ class TestSaveOutputsVariants:
|
|
|
1444
1444
|
assert path.name == "epoch_2_step_500"
|
|
1445
1445
|
run.finish()
|
|
1446
1446
|
|
|
1447
|
+
def test_dict_slot_produces_named_files(self, tmp_path):
|
|
1448
|
+
run = euler_train.init(dir=str(tmp_path / "r"), config={})
|
|
1449
|
+
a = np.zeros((4, 4, 3), dtype=np.uint8)
|
|
1450
|
+
b = np.ones((4, 4, 3), dtype=np.uint8) * 128
|
|
1451
|
+
run.save_outputs(epoch=0, step=0, rgb=dict(pred={"scene_a": a, "scene_b": b}))
|
|
1452
|
+
|
|
1453
|
+
pred_dir = run.dir / "outputs" / "epoch_0_step_0" / "rgb" / "pred"
|
|
1454
|
+
names = sorted(f.name for f in pred_dir.iterdir())
|
|
1455
|
+
assert names == ["scene_a.png", "scene_b.png"]
|
|
1456
|
+
run.finish()
|
|
1457
|
+
|
|
1458
|
+
def test_dict_slot_in_aux(self, tmp_path):
|
|
1459
|
+
run = euler_train.init(dir=str(tmp_path / "r"), config={})
|
|
1460
|
+
m1 = np.ones((4, 4), dtype=np.float32)
|
|
1461
|
+
m2 = np.zeros((4, 4), dtype=np.float32)
|
|
1462
|
+
run.save_outputs(
|
|
1463
|
+
epoch=0, step=0,
|
|
1464
|
+
depth=dict(aux=dict(attn={"head_0": m1, "head_1": m2})),
|
|
1465
|
+
)
|
|
1466
|
+
|
|
1467
|
+
attn_dir = run.dir / "outputs" / "epoch_0_step_0" / "depth" / "aux" / "attn"
|
|
1468
|
+
names = sorted(f.name for f in attn_dir.iterdir())
|
|
1469
|
+
assert names == ["head_0.npy", "head_1.npy"]
|
|
1470
|
+
run.finish()
|
|
1471
|
+
|
|
1472
|
+
def test_mixed_dict_and_array_slots(self, tmp_path):
|
|
1473
|
+
run = euler_train.init(dir=str(tmp_path / "r"), config={})
|
|
1474
|
+
a = np.zeros((4, 4, 3), dtype=np.uint8)
|
|
1475
|
+
run.save_outputs(epoch=0, step=0, rgb=dict(pred={"frame_x": a}, gt=a))
|
|
1476
|
+
|
|
1477
|
+
pred_dir = run.dir / "outputs" / "epoch_0_step_0" / "rgb" / "pred"
|
|
1478
|
+
gt_dir = run.dir / "outputs" / "epoch_0_step_0" / "rgb" / "gt"
|
|
1479
|
+
assert sorted(f.name for f in pred_dir.iterdir()) == ["frame_x.png"]
|
|
1480
|
+
assert sorted(f.name for f in gt_dir.iterdir()) == ["0000.png"]
|
|
1481
|
+
run.finish()
|
|
1482
|
+
|
|
1447
1483
|
def test_returns_output_base_path(self, tmp_path):
|
|
1448
1484
|
run = euler_train.init(dir=str(tmp_path / "r"), config={})
|
|
1449
1485
|
arr = np.zeros((4, 4, 3), dtype=np.uint8)
|
|
@@ -2042,3 +2078,179 @@ class TestInterruptHandling:
|
|
|
2042
2078
|
meta = _read_json(run.dir / "meta.json")
|
|
2043
2079
|
assert meta["status"] == "interrupted"
|
|
2044
2080
|
assert "SIGINT" in meta["error"]
|
|
2081
|
+
|
|
2082
|
+
|
|
2083
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
2084
|
+
# Metric naming
|
|
2085
|
+
# ═══════════════════════════════════════════════════════════════════════════
|
|
2086
|
+
|
|
2087
|
+
_SAMPLE_METRIC_NAMING = {
|
|
2088
|
+
"producer_key": "euler_train.weather_metric",
|
|
2089
|
+
"producer_version": "0.1.0",
|
|
2090
|
+
"namespaces": {
|
|
2091
|
+
"depth.train": {
|
|
2092
|
+
"axes": {
|
|
2093
|
+
"kind": {"position": 0, "optional": False, "values": ["loss", "diag", "stat"]},
|
|
2094
|
+
"stage": {"position": 1, "optional": True, "values": ["prior", "final"]},
|
|
2095
|
+
}
|
|
2096
|
+
},
|
|
2097
|
+
"sys.train": {
|
|
2098
|
+
"axes": {}
|
|
2099
|
+
},
|
|
2100
|
+
},
|
|
2101
|
+
}
|
|
2102
|
+
|
|
2103
|
+
|
|
2104
|
+
class TestMetricNaming:
|
|
2105
|
+
def test_metric_naming_stored_in_meta_json(self, tmp_path):
|
|
2106
|
+
run = euler_train.init(
|
|
2107
|
+
dir=str(tmp_path / "r"),
|
|
2108
|
+
config={"lr": 1e-3},
|
|
2109
|
+
metric_naming=_SAMPLE_METRIC_NAMING,
|
|
2110
|
+
)
|
|
2111
|
+
meta = _read_json(run.dir / "meta.json")
|
|
2112
|
+
assert meta["metric_naming"] == _SAMPLE_METRIC_NAMING
|
|
2113
|
+
run.finish()
|
|
2114
|
+
|
|
2115
|
+
def test_metric_naming_none_by_default(self, tmp_path):
|
|
2116
|
+
run = euler_train.init(
|
|
2117
|
+
dir=str(tmp_path / "r"),
|
|
2118
|
+
config={},
|
|
2119
|
+
)
|
|
2120
|
+
meta = _read_json(run.dir / "meta.json")
|
|
2121
|
+
assert "metric_naming" not in meta
|
|
2122
|
+
run.finish()
|
|
2123
|
+
|
|
2124
|
+
def test_metric_naming_in_stream_init_event(self, tmp_path):
|
|
2125
|
+
consumer = _RecordingStreamConsumer()
|
|
2126
|
+
run = euler_train.init(
|
|
2127
|
+
dir=str(tmp_path / "r"),
|
|
2128
|
+
config={},
|
|
2129
|
+
stream=consumer,
|
|
2130
|
+
metric_naming=_SAMPLE_METRIC_NAMING,
|
|
2131
|
+
)
|
|
2132
|
+
run.finish()
|
|
2133
|
+
|
|
2134
|
+
init_event = consumer.events[0]
|
|
2135
|
+
assert init_event["type"] == "init"
|
|
2136
|
+
assert init_event["meta"]["metric_naming"] == _SAMPLE_METRIC_NAMING
|
|
2137
|
+
|
|
2138
|
+
def test_metric_naming_not_in_stream_when_absent(self, tmp_path):
|
|
2139
|
+
consumer = _RecordingStreamConsumer()
|
|
2140
|
+
run = euler_train.init(
|
|
2141
|
+
dir=str(tmp_path / "r"),
|
|
2142
|
+
config={},
|
|
2143
|
+
stream=consumer,
|
|
2144
|
+
)
|
|
2145
|
+
run.finish()
|
|
2146
|
+
|
|
2147
|
+
init_event = consumer.events[0]
|
|
2148
|
+
assert "metric_naming" not in init_event["meta"]
|
|
2149
|
+
|
|
2150
|
+
def test_metric_naming_preserved_on_resume(self, tmp_path):
|
|
2151
|
+
run1 = euler_train.init(
|
|
2152
|
+
dir=str(tmp_path / "r"),
|
|
2153
|
+
config={},
|
|
2154
|
+
metric_naming=_SAMPLE_METRIC_NAMING,
|
|
2155
|
+
)
|
|
2156
|
+
run1.finish()
|
|
2157
|
+
|
|
2158
|
+
run2 = euler_train.init(
|
|
2159
|
+
dir=str(run1.dir),
|
|
2160
|
+
)
|
|
2161
|
+
meta = _read_json(run2.dir / "meta.json")
|
|
2162
|
+
assert meta["metric_naming"] == _SAMPLE_METRIC_NAMING
|
|
2163
|
+
run2.finish()
|
|
2164
|
+
|
|
2165
|
+
def test_metric_naming_validation_not_dict(self, tmp_path):
|
|
2166
|
+
with pytest.raises(TypeError, match="metric_naming must be a dict"):
|
|
2167
|
+
euler_train.init(
|
|
2168
|
+
dir=str(tmp_path / "r"),
|
|
2169
|
+
config={},
|
|
2170
|
+
metric_naming="invalid",
|
|
2171
|
+
)
|
|
2172
|
+
|
|
2173
|
+
def test_metric_naming_validation_missing_namespaces(self, tmp_path):
|
|
2174
|
+
with pytest.raises(ValueError, match="namespaces"):
|
|
2175
|
+
euler_train.init(
|
|
2176
|
+
dir=str(tmp_path / "r"),
|
|
2177
|
+
config={},
|
|
2178
|
+
metric_naming={"producer_key": "test"},
|
|
2179
|
+
)
|
|
2180
|
+
|
|
2181
|
+
def test_metric_naming_validation_namespaces_not_dict(self, tmp_path):
|
|
2182
|
+
with pytest.raises(ValueError, match="namespaces"):
|
|
2183
|
+
euler_train.init(
|
|
2184
|
+
dir=str(tmp_path / "r"),
|
|
2185
|
+
config={},
|
|
2186
|
+
metric_naming={"namespaces": "invalid"},
|
|
2187
|
+
)
|
|
2188
|
+
|
|
2189
|
+
def test_metric_naming_validation_namespace_entry_not_dict(self, tmp_path):
|
|
2190
|
+
with pytest.raises(ValueError, match="must be a dict"):
|
|
2191
|
+
euler_train.init(
|
|
2192
|
+
dir=str(tmp_path / "r"),
|
|
2193
|
+
config={},
|
|
2194
|
+
metric_naming={"namespaces": {"depth.train": "invalid"}},
|
|
2195
|
+
)
|
|
2196
|
+
|
|
2197
|
+
def test_gpu_stats_namespaced_when_metric_naming_present(self, tmp_path):
|
|
2198
|
+
"""GPU stats should use sys.train.* keys when metric_naming is set."""
|
|
2199
|
+
run = euler_train.init(
|
|
2200
|
+
dir=str(tmp_path / "r"),
|
|
2201
|
+
config={},
|
|
2202
|
+
metric_naming=_SAMPLE_METRIC_NAMING,
|
|
2203
|
+
)
|
|
2204
|
+
# Force GPU stats to be "available" with a mock
|
|
2205
|
+
run._gpu_available = True
|
|
2206
|
+
|
|
2207
|
+
class _FakeUtil:
|
|
2208
|
+
gpu = 75
|
|
2209
|
+
memory = 50
|
|
2210
|
+
|
|
2211
|
+
class _FakeMem:
|
|
2212
|
+
used = 4_000_000_000
|
|
2213
|
+
total = 8_000_000_000
|
|
2214
|
+
|
|
2215
|
+
import unittest.mock as mock
|
|
2216
|
+
with mock.patch.dict("sys.modules", {"pynvml": mock.MagicMock()}) as _:
|
|
2217
|
+
import sys as _sys
|
|
2218
|
+
pynvml_mock = _sys.modules["pynvml"]
|
|
2219
|
+
pynvml_mock.nvmlDeviceGetUtilizationRates.return_value = _FakeUtil()
|
|
2220
|
+
pynvml_mock.nvmlDeviceGetMemoryInfo.return_value = _FakeMem()
|
|
2221
|
+
|
|
2222
|
+
stats = run._get_gpu_stats()
|
|
2223
|
+
|
|
2224
|
+
assert "sys.train.gpu_util_pct" in stats
|
|
2225
|
+
assert "sys.train.gpu_mem_used_gb" in stats
|
|
2226
|
+
assert "gpu_util_pct" not in stats
|
|
2227
|
+
run.finish()
|
|
2228
|
+
|
|
2229
|
+
def test_gpu_stats_flat_when_no_metric_naming(self, tmp_path):
|
|
2230
|
+
"""GPU stats should use flat keys when metric_naming is absent."""
|
|
2231
|
+
run = euler_train.init(
|
|
2232
|
+
dir=str(tmp_path / "r"),
|
|
2233
|
+
config={},
|
|
2234
|
+
)
|
|
2235
|
+
run._gpu_available = True
|
|
2236
|
+
|
|
2237
|
+
class _FakeUtil:
|
|
2238
|
+
gpu = 75
|
|
2239
|
+
memory = 50
|
|
2240
|
+
|
|
2241
|
+
class _FakeMem:
|
|
2242
|
+
used = 4_000_000_000
|
|
2243
|
+
total = 8_000_000_000
|
|
2244
|
+
|
|
2245
|
+
import unittest.mock as mock
|
|
2246
|
+
with mock.patch.dict("sys.modules", {"pynvml": mock.MagicMock()}) as _:
|
|
2247
|
+
import sys as _sys
|
|
2248
|
+
pynvml_mock = _sys.modules["pynvml"]
|
|
2249
|
+
pynvml_mock.nvmlDeviceGetUtilizationRates.return_value = _FakeUtil()
|
|
2250
|
+
pynvml_mock.nvmlDeviceGetMemoryInfo.return_value = _FakeMem()
|
|
2251
|
+
|
|
2252
|
+
stats = run._get_gpu_stats()
|
|
2253
|
+
|
|
2254
|
+
assert "gpu_util_pct" in stats
|
|
2255
|
+
assert "sys.train.gpu_util_pct" not in stats
|
|
2256
|
+
run.finish()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|