euler-train 2.6.0__tar.gz → 2.7.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.6.0 → euler_train-2.7.0}/PKG-INFO +1 -1
- {euler_train-2.6.0 → euler_train-2.7.0}/README.md +113 -3
- {euler_train-2.6.0 → euler_train-2.7.0}/euler_train/stream.py +78 -0
- {euler_train-2.6.0 → euler_train-2.7.0}/euler_train.egg-info/PKG-INFO +1 -1
- {euler_train-2.6.0 → euler_train-2.7.0}/pyproject.toml +1 -1
- {euler_train-2.6.0 → euler_train-2.7.0}/euler_train/__init__.py +0 -0
- {euler_train-2.6.0 → euler_train-2.7.0}/euler_train/_dataset_contract.py +0 -0
- {euler_train-2.6.0 → euler_train-2.7.0}/euler_train/architecture.py +0 -0
- {euler_train-2.6.0 → euler_train-2.7.0}/euler_train/environment.py +0 -0
- {euler_train-2.6.0 → euler_train-2.7.0}/euler_train/git_info.py +0 -0
- {euler_train-2.6.0 → euler_train-2.7.0}/euler_train/outputs.py +0 -0
- {euler_train-2.6.0 → euler_train-2.7.0}/euler_train/run.py +0 -0
- {euler_train-2.6.0 → euler_train-2.7.0}/euler_train/serialization.py +0 -0
- {euler_train-2.6.0 → euler_train-2.7.0}/euler_train/slurm.py +0 -0
- {euler_train-2.6.0 → euler_train-2.7.0}/euler_train.egg-info/SOURCES.txt +0 -0
- {euler_train-2.6.0 → euler_train-2.7.0}/euler_train.egg-info/dependency_links.txt +0 -0
- {euler_train-2.6.0 → euler_train-2.7.0}/euler_train.egg-info/requires.txt +0 -0
- {euler_train-2.6.0 → euler_train-2.7.0}/euler_train.egg-info/top_level.txt +0 -0
- {euler_train-2.6.0 → euler_train-2.7.0}/setup.cfg +0 -0
- {euler_train-2.6.0 → euler_train-2.7.0}/tests/test_runlog.py +0 -0
|
@@ -49,6 +49,57 @@ with euler_train.init(dir="runs/exp02", config=cfg) as run:
|
|
|
49
49
|
... # if an exception is raised, meta.json records status="crashed" + traceback
|
|
50
50
|
```
|
|
51
51
|
|
|
52
|
+
## Euler View stream mode
|
|
53
|
+
|
|
54
|
+
`euler_train` can dual-write runs to Euler View while still keeping the normal file-based run directory as the primary source of truth. Streaming is best-effort: local files are always written first, and stream events are sent alongside them when a stream consumer is configured.
|
|
55
|
+
|
|
56
|
+
Pass the stream config via `stream=...` on `euler_train.init()`:
|
|
57
|
+
|
|
58
|
+
```python
|
|
59
|
+
import os
|
|
60
|
+
import euler_train
|
|
61
|
+
|
|
62
|
+
run = euler_train.init(
|
|
63
|
+
dir="runs/experiment_01",
|
|
64
|
+
config=cfg,
|
|
65
|
+
stream={
|
|
66
|
+
"base_url": os.environ["EULER_VIEW_BASE_URL"],
|
|
67
|
+
"model_id": 42,
|
|
68
|
+
"api_token": os.environ["EULER_VIEW_API_TOKEN"],
|
|
69
|
+
"stream_attach_token": os.environ.get("EULER_VIEW_STREAM_ATTACH_TOKEN"),
|
|
70
|
+
},
|
|
71
|
+
)
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
What each field is for:
|
|
75
|
+
|
|
76
|
+
- `base_url`: Euler View server base URL, for example `https://view.example.com`.
|
|
77
|
+
- `model_id`: Euler View model that should receive this run.
|
|
78
|
+
- `api_token` or `access_token`: credential used to open a stream session. This is the normal choice when the package should negotiate its own short-lived ingest token.
|
|
79
|
+
- `stream_attach_token`: optional opaque launch-attachment token. When present, the producer sends it during the session handshake and Euler View attaches the stream to that launch directly. This is the preferred path for interactive or otherwise non-SLURM launches. Internally Euler View currently maps this token to `model_launches.id`, but the package treats it as an opaque token.
|
|
80
|
+
- `stream_token`: optional pre-issued ingest token. If you pass this, the package skips the session handshake entirely and posts events directly to the ingest endpoint.
|
|
81
|
+
- `datasource_id`: optional Euler View datasource hint for the run record.
|
|
82
|
+
- `euler_train_dir` and `run_dir`: optional path overrides. These are auto-derived in normal usage.
|
|
83
|
+
- `session_expires_in_sec`: optional requested session lifetime for the short-lived ingest token.
|
|
84
|
+
|
|
85
|
+
How the handshake works:
|
|
86
|
+
|
|
87
|
+
1. `euler_train` creates the local run directory and writes `meta.json`, `config.json`, and the other standard files as usual.
|
|
88
|
+
2. When the first stream flush happens, the package requests a session from Euler View at `/api/models/{model_id}/runs/{run_id}/stream/session`.
|
|
89
|
+
3. If `stream_attach_token` is configured, it is sent as `streamAttachToken` and used as the explicit launch attachment key.
|
|
90
|
+
4. If no `stream_attach_token` is configured, the package falls back to the SLURM metadata already captured in `meta.json["slurm"]["job_id"]`.
|
|
91
|
+
5. Euler View returns a short-lived ingest token plus the canonical `streamAttachToken`; the package caches that returned token and reuses it on later session refreshes.
|
|
92
|
+
6. Subsequent events are sent to `/api/model-run-stream/ingest`.
|
|
93
|
+
|
|
94
|
+
What has to be configured where:
|
|
95
|
+
|
|
96
|
+
- In the training script or job wrapper: pass `stream={...}` to `euler_train.init(...)`.
|
|
97
|
+
- In Euler View / the launcher: if the run should attach to a specific launch regardless of SLURM availability, provide a `stream_attach_token` to the runtime and forward it into the `stream` config.
|
|
98
|
+
- Under plain SLURM launches: no extra attachment config is required as long as Euler View can uniquely match the run by `slurm.job_id`.
|
|
99
|
+
- If neither `stream_attach_token` nor a unique SLURM match is available, run streaming still works, but launch-to-run linking in Euler View may remain unresolved.
|
|
100
|
+
|
|
101
|
+
For new integrations, prefer `stream_attach_token`. The older `launch_id` / `launchId` config keys are still accepted as compatibility aliases, but new code should treat the attachment value as an opaque stream token rather than a launch ID.
|
|
102
|
+
|
|
52
103
|
## Directory structure
|
|
53
104
|
|
|
54
105
|
Each `euler_train.init(dir=...)` call creates a timestamped subdirectory under `{dir}/runs/`:
|
|
@@ -67,6 +118,7 @@ Each `euler_train.init(dir=...)` call creates a timestamped subdirectory under `
|
|
|
67
118
|
│ └── epoch_{N}.pt
|
|
68
119
|
└── outputs/
|
|
69
120
|
└── epoch_{N}_step_{M}/
|
|
121
|
+
├── manifest.json
|
|
70
122
|
└── {output_type}/
|
|
71
123
|
├── pred/
|
|
72
124
|
├── gt/
|
|
@@ -80,7 +132,7 @@ The run ID and directory are available as `run.run_id` and `run.dir`.
|
|
|
80
132
|
|
|
81
133
|
## API reference
|
|
82
134
|
|
|
83
|
-
### `euler_train.init(dir, config=None, meta=None, output_formats=None, output_visualization=None, run_id=None, datasets=None, run_name=None, evaluations=None, mode=None) → Run`
|
|
135
|
+
### `euler_train.init(dir, config=None, meta=None, output_formats=None, output_visualization=None, run_id=None, datasets=None, run_name=None, evaluations=None, mode=None, stream=None, metric_naming=None) → Run`
|
|
84
136
|
|
|
85
137
|
Creates the run directory and writes `meta.json`, `config.json`, `code_ref.json`, and `run_environment.json`. On resume (`run_id` provided), only `meta.json` and `config.json` are updated. `meta.json` also maintains an `updated_at` map with last-write timestamps for tracked artifacts.
|
|
86
138
|
|
|
@@ -96,6 +148,8 @@ Creates the run directory and writes `meta.json`, `config.json`, `code_ref.json`
|
|
|
96
148
|
| `run_name` | `str \| None` | Optional human-readable run label stored in `meta.json`. |
|
|
97
149
|
| `evaluations` | `dict[str, dict] \| None` | Optional evaluation key → entry map. See [Evaluations](#evaluations). |
|
|
98
150
|
| `mode` | `str \| None` | Optional process label such as `"train"`, `"val"`, or `"eval"`. When set, lifecycle and crash details are also written under `meta.json["modes"][mode]`. |
|
|
151
|
+
| `stream` | `dict \| EulerViewStreamConfig \| consumer \| sequence \| None` | Optional output stream config / consumer. A mapping with `base_url`, `model_id`, and `api_token` (or `access_token`) enables best-effort streaming to Euler View. Prefer `stream_attach_token` for explicit launch attachment; otherwise the package falls back to `meta.json["slurm"]["job_id"]` when available. |
|
|
152
|
+
| `metric_naming` | `dict[str, Any] \| None` | Optional structured metric naming declaration stored in `meta.json["metric_naming"]` and included in the stream init event. |
|
|
99
153
|
|
|
100
154
|
---
|
|
101
155
|
|
|
@@ -112,7 +166,7 @@ run.log({"rgb.psnr": 28.3, "depth.mae": 0.03}, step=100, epoch=1, mode="val")
|
|
|
112
166
|
|
|
113
167
|
---
|
|
114
168
|
|
|
115
|
-
### `run.save_outputs(*, epoch=None, step=None, **output_types)`
|
|
169
|
+
### `run.save_outputs(*, epoch=None, step=None, metadata=None, **output_types)`
|
|
116
170
|
|
|
117
171
|
Saves arrays/images to `outputs/epoch_{N}_step_{M}/{output_type}/{slot}/`.
|
|
118
172
|
|
|
@@ -125,10 +179,11 @@ Each output type is a dict with these slot keys:
|
|
|
125
179
|
| `input` | Model input |
|
|
126
180
|
| `aux` | Dict of named auxiliary outputs (each becomes a subdirectory) |
|
|
127
181
|
|
|
128
|
-
|
|
182
|
+
Slot values can be:
|
|
129
183
|
- A single numpy array, torch tensor, or PIL Image
|
|
130
184
|
- A list of the above (saved as `0000.ext`, `0001.ext`, ...)
|
|
131
185
|
- A 4D numpy/torch array (split along dim 0 as a batch)
|
|
186
|
+
- A dict mapping custom string IDs to items (saved as `{id}.ext`)
|
|
132
187
|
|
|
133
188
|
```python
|
|
134
189
|
run.save_outputs(
|
|
@@ -142,6 +197,61 @@ run.save_outputs(
|
|
|
142
197
|
)
|
|
143
198
|
```
|
|
144
199
|
|
|
200
|
+
#### Named sample IDs
|
|
201
|
+
|
|
202
|
+
When slot values are dicts with string keys, files are saved with the key as the basename instead of sequential indices. This is useful for matching outputs back to specific input samples:
|
|
203
|
+
|
|
204
|
+
```python
|
|
205
|
+
run.save_outputs(
|
|
206
|
+
epoch=1, step=500,
|
|
207
|
+
rgb=dict(pred={"scene_042": img_a, "scene_117": img_b}),
|
|
208
|
+
)
|
|
209
|
+
# produces: rgb/pred/scene_042.png, rgb/pred/scene_117.png
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
#### Source metadata
|
|
213
|
+
|
|
214
|
+
The optional `metadata` parameter attaches dataset provenance and other context to the output manifest. When provided, the `dataset` key is required and must be a non-empty string:
|
|
215
|
+
|
|
216
|
+
```python
|
|
217
|
+
run.save_outputs(
|
|
218
|
+
epoch=1, step=500,
|
|
219
|
+
metadata={"dataset": "vkitti2", "split": "val", "scene": "0001"},
|
|
220
|
+
rgb=dict(pred={"frame_042": img_a}),
|
|
221
|
+
)
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
#### Output manifest
|
|
225
|
+
|
|
226
|
+
Each `save_outputs` call writes a `manifest.json` alongside the saved files, recording what was written and how. The manifest is also streamed via the `output_snapshot` event when streaming is active.
|
|
227
|
+
|
|
228
|
+
```json
|
|
229
|
+
{
|
|
230
|
+
"version": 1,
|
|
231
|
+
"epoch": 1,
|
|
232
|
+
"step": 500,
|
|
233
|
+
"metadata": {"dataset": "vkitti2", "split": "val"},
|
|
234
|
+
"output_types": {
|
|
235
|
+
"rgb": {
|
|
236
|
+
"pred": {
|
|
237
|
+
"id_mode": "named",
|
|
238
|
+
"files": [
|
|
239
|
+
{"sample_id": "scene_042", "filename": "scene_042.png", "format": "png"}
|
|
240
|
+
]
|
|
241
|
+
},
|
|
242
|
+
"gt": {
|
|
243
|
+
"id_mode": "indexed",
|
|
244
|
+
"files": [
|
|
245
|
+
{"sample_id": 0, "filename": "0000.png", "format": "png"}
|
|
246
|
+
]
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
},
|
|
250
|
+
"format_overrides": {},
|
|
251
|
+
"visualization_overrides": {}
|
|
252
|
+
}
|
|
253
|
+
```
|
|
254
|
+
|
|
145
255
|
Torch tensors in `(C,H,W)` or `(B,C,H,W)` layout are automatically transposed to channels-last before saving.
|
|
146
256
|
|
|
147
257
|
Pass `None` for any slot or output type to skip it.
|
|
@@ -56,6 +56,8 @@ class EulerViewStreamConfig:
|
|
|
56
56
|
api_token: str | None = None
|
|
57
57
|
access_token: str | None = None
|
|
58
58
|
stream_token: str | None = None
|
|
59
|
+
stream_attach_token: str | None = None
|
|
60
|
+
launch_id: str | None = None
|
|
59
61
|
datasource_id: int | None = None
|
|
60
62
|
euler_train_dir: str | None = None
|
|
61
63
|
run_dir: str | None = None
|
|
@@ -84,6 +86,24 @@ class EulerViewStreamConfig:
|
|
|
84
86
|
self.stream_token,
|
|
85
87
|
field_name="stream.stream_token",
|
|
86
88
|
)
|
|
89
|
+
self.stream_attach_token = _normalize_optional_text(
|
|
90
|
+
self.stream_attach_token,
|
|
91
|
+
field_name="stream.stream_attach_token",
|
|
92
|
+
)
|
|
93
|
+
self.launch_id = _normalize_optional_text(
|
|
94
|
+
self.launch_id,
|
|
95
|
+
field_name="stream.launch_id",
|
|
96
|
+
)
|
|
97
|
+
if (
|
|
98
|
+
self.stream_attach_token is not None
|
|
99
|
+
and self.launch_id is not None
|
|
100
|
+
and self.stream_attach_token != self.launch_id
|
|
101
|
+
):
|
|
102
|
+
raise ValueError(
|
|
103
|
+
"stream.stream_attach_token and stream.launch_id must match when both are provided",
|
|
104
|
+
)
|
|
105
|
+
if self.stream_attach_token is None:
|
|
106
|
+
self.stream_attach_token = self.launch_id
|
|
87
107
|
self.euler_train_dir = _normalize_optional_text(
|
|
88
108
|
self.euler_train_dir,
|
|
89
109
|
field_name="stream.euler_train_dir",
|
|
@@ -191,6 +211,7 @@ class EulerViewStreamConsumer:
|
|
|
191
211
|
self._context: StreamContext | None = None
|
|
192
212
|
self._pending: list[dict[str, Any]] = []
|
|
193
213
|
self._stream_token: str | None = config.stream_token
|
|
214
|
+
self._resolved_stream_attach_token: str | None = config.stream_attach_token
|
|
194
215
|
self._session_expires_at = float("inf") if config.stream_token else 0.0
|
|
195
216
|
self._ingest_url = f"{self.config.base_url}/api/model-run-stream/ingest"
|
|
196
217
|
self._last_flush_at = 0.0
|
|
@@ -299,6 +320,14 @@ class EulerViewStreamConsumer:
|
|
|
299
320
|
"runDir": self.config.run_dir or str(self._context.run_dir),
|
|
300
321
|
"eulerTrainDir": self.config.euler_train_dir or str(self._context.runs_dir),
|
|
301
322
|
}
|
|
323
|
+
stream_attach_token = (
|
|
324
|
+
self.config.stream_attach_token or self._resolved_stream_attach_token
|
|
325
|
+
)
|
|
326
|
+
if stream_attach_token is not None:
|
|
327
|
+
session_body["streamAttachToken"] = stream_attach_token
|
|
328
|
+
slurm_job_id = _extract_slurm_job_id(self._context.meta)
|
|
329
|
+
if slurm_job_id is not None:
|
|
330
|
+
session_body["slurmJobId"] = slurm_job_id
|
|
302
331
|
if self.config.datasource_id is not None:
|
|
303
332
|
session_body["datasourceId"] = self.config.datasource_id
|
|
304
333
|
if self.config.session_expires_in_sec is not None:
|
|
@@ -328,6 +357,10 @@ class EulerViewStreamConsumer:
|
|
|
328
357
|
else:
|
|
329
358
|
self._ingest_url = f"{self.config.base_url}/api/model-run-stream/ingest"
|
|
330
359
|
|
|
360
|
+
resolved_stream_attach_token = _extract_session_stream_attach_token(payload)
|
|
361
|
+
if resolved_stream_attach_token is not None:
|
|
362
|
+
self._resolved_stream_attach_token = resolved_stream_attach_token
|
|
363
|
+
|
|
331
364
|
self._session_expires_at = _parse_timestamp(payload.get("expiresAt"))
|
|
332
365
|
self._next_retry_at = 0.0
|
|
333
366
|
return True
|
|
@@ -448,6 +481,12 @@ def _config_from_mapping(raw: Mapping[str, Any]) -> EulerViewStreamConfig:
|
|
|
448
481
|
access_token=_mapping_get(raw, "access_token", "accessToken"),
|
|
449
482
|
api_token=_mapping_get(raw, "api_token", "apiToken"),
|
|
450
483
|
stream_token=_mapping_get(raw, "stream_token", "streamToken"),
|
|
484
|
+
stream_attach_token=_mapping_get(
|
|
485
|
+
raw,
|
|
486
|
+
"stream_attach_token",
|
|
487
|
+
"streamAttachToken",
|
|
488
|
+
),
|
|
489
|
+
launch_id=_mapping_get(raw, "launch_id", "launchId"),
|
|
451
490
|
datasource_id=_mapping_get(raw, "datasource_id", "datasourceId"),
|
|
452
491
|
euler_train_dir=_mapping_get(raw, "euler_train_dir", "eulerTrainDir"),
|
|
453
492
|
run_dir=_mapping_get(raw, "run_dir", "runDir"),
|
|
@@ -484,6 +523,45 @@ def _mapping_get(
|
|
|
484
523
|
return default
|
|
485
524
|
|
|
486
525
|
|
|
526
|
+
def _extract_slurm_job_id(meta: Mapping[str, Any] | None) -> int | None:
|
|
527
|
+
if not isinstance(meta, Mapping):
|
|
528
|
+
return None
|
|
529
|
+
slurm = meta.get("slurm")
|
|
530
|
+
if not isinstance(slurm, Mapping):
|
|
531
|
+
return None
|
|
532
|
+
raw = slurm.get("job_id")
|
|
533
|
+
if isinstance(raw, bool):
|
|
534
|
+
return None
|
|
535
|
+
if isinstance(raw, int):
|
|
536
|
+
return raw if raw > 0 else None
|
|
537
|
+
if isinstance(raw, float):
|
|
538
|
+
return int(raw) if raw.is_integer() and raw > 0 else None
|
|
539
|
+
if isinstance(raw, str):
|
|
540
|
+
text = raw.strip()
|
|
541
|
+
if not text:
|
|
542
|
+
return None
|
|
543
|
+
try:
|
|
544
|
+
value = int(text, 10)
|
|
545
|
+
except ValueError:
|
|
546
|
+
return None
|
|
547
|
+
return value if value > 0 else None
|
|
548
|
+
return None
|
|
549
|
+
|
|
550
|
+
|
|
551
|
+
def _extract_session_stream_attach_token(payload: Mapping[str, Any]) -> str | None:
|
|
552
|
+
run = payload.get("run")
|
|
553
|
+
if not isinstance(run, Mapping):
|
|
554
|
+
return None
|
|
555
|
+
stream_attach_token = run.get("streamAttachToken")
|
|
556
|
+
if not isinstance(stream_attach_token, str):
|
|
557
|
+
legacy_launch_id = run.get("launchId")
|
|
558
|
+
if not isinstance(legacy_launch_id, str):
|
|
559
|
+
return None
|
|
560
|
+
stream_attach_token = legacy_launch_id
|
|
561
|
+
normalized = stream_attach_token.strip()
|
|
562
|
+
return normalized or None
|
|
563
|
+
|
|
564
|
+
|
|
487
565
|
def _normalize_optional_text(
|
|
488
566
|
value: Any,
|
|
489
567
|
*,
|
|
@@ -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.7.0"
|
|
8
8
|
description = "Lightweight file-based experiment logger for PyTorch"
|
|
9
9
|
requires-python = ">=3.9"
|
|
10
10
|
dependencies = ["numpy", "euler-dataset-contract"]
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|