trainloop 0.5.2__tar.gz → 0.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.
- {trainloop-0.5.2 → trainloop-0.7.0}/PKG-INFO +1 -1
- {trainloop-0.5.2 → trainloop-0.7.0}/pyproject.toml +1 -1
- {trainloop-0.5.2 → trainloop-0.7.0}/src/trainloop/__init__.py +4 -2
- {trainloop-0.5.2 → trainloop-0.7.0}/src/trainloop/hooks.py +75 -108
- {trainloop-0.5.2 → trainloop-0.7.0}/README.md +0 -0
- {trainloop-0.5.2 → trainloop-0.7.0}/src/trainloop/py.typed +0 -0
- {trainloop-0.5.2 → trainloop-0.7.0}/src/trainloop/trainer.py +0 -0
- {trainloop-0.5.2 → trainloop-0.7.0}/src/trainloop/utils.py +0 -0
|
@@ -4,8 +4,9 @@ from .hooks import (
|
|
|
4
4
|
CudaMaxMemoryHook,
|
|
5
5
|
EmaHook,
|
|
6
6
|
ImageFileLoggerHook,
|
|
7
|
-
LoggingHook,
|
|
8
7
|
ProgressHook,
|
|
8
|
+
StatsHook,
|
|
9
|
+
TrainingStats,
|
|
9
10
|
WandbHook,
|
|
10
11
|
)
|
|
11
12
|
from .trainer import BaseTrainer, LossNoneWarning, map_nested_tensor
|
|
@@ -15,8 +16,9 @@ __all__ = [
|
|
|
15
16
|
"BaseHook",
|
|
16
17
|
"CheckpointingHook",
|
|
17
18
|
"CudaMaxMemoryHook",
|
|
18
|
-
"LoggingHook",
|
|
19
19
|
"ProgressHook",
|
|
20
|
+
"StatsHook",
|
|
21
|
+
"TrainingStats",
|
|
20
22
|
"EmaHook",
|
|
21
23
|
"WandbHook",
|
|
22
24
|
"ImageFileLoggerHook",
|
|
@@ -5,6 +5,7 @@ import sys
|
|
|
5
5
|
import tempfile
|
|
6
6
|
import time
|
|
7
7
|
import warnings
|
|
8
|
+
from dataclasses import dataclass
|
|
8
9
|
from datetime import timedelta
|
|
9
10
|
from numbers import Number
|
|
10
11
|
from pathlib import Path
|
|
@@ -78,21 +79,38 @@ class BaseHook:
|
|
|
78
79
|
pass
|
|
79
80
|
|
|
80
81
|
|
|
81
|
-
|
|
82
|
-
|
|
82
|
+
@dataclass(frozen=True)
|
|
83
|
+
class TrainingStats:
|
|
84
|
+
loss: float
|
|
85
|
+
grad_norm: float | None
|
|
86
|
+
step_time: float
|
|
87
|
+
data_time: float
|
|
88
|
+
non_finite_grad_retry_count: float
|
|
89
|
+
max_memory: float | None
|
|
90
|
+
records: Records
|
|
91
|
+
param_groups: list[dict[str, Any]]
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
class StatsHook(BaseHook):
|
|
95
|
+
"""Aggregate training stats and pass them to a callback.
|
|
83
96
|
|
|
84
97
|
Args:
|
|
85
98
|
interval: Emit stats every N steps.
|
|
86
99
|
sync: If True, aggregate stats across distributed ranks.
|
|
100
|
+
callback: Function called with the trainer and aggregated stats.
|
|
87
101
|
"""
|
|
88
102
|
|
|
89
103
|
def __init__(
|
|
90
104
|
self,
|
|
91
|
-
|
|
92
|
-
|
|
105
|
+
callback: Callable[[BaseTrainer, TrainingStats], None],
|
|
106
|
+
interval: int = 10,
|
|
107
|
+
sync: bool = True,
|
|
108
|
+
param_group_keys: Sequence[str] = ("name", "lr"),
|
|
93
109
|
):
|
|
94
110
|
self.interval = interval
|
|
95
111
|
self.sync = sync
|
|
112
|
+
self.callback = callback
|
|
113
|
+
self.param_group_keys = tuple(param_group_keys)
|
|
96
114
|
self.reset()
|
|
97
115
|
|
|
98
116
|
def reset(self):
|
|
@@ -104,6 +122,13 @@ class _StatsHook(BaseHook):
|
|
|
104
122
|
self.non_finite_grad_retry_counts = []
|
|
105
123
|
self.max_memories = []
|
|
106
124
|
|
|
125
|
+
def on_before_step(self, trainer: BaseTrainer):
|
|
126
|
+
super().on_before_step(trainer)
|
|
127
|
+
self.param_groups = [
|
|
128
|
+
{k: param_group[k] for k in self.param_group_keys if k in param_group}
|
|
129
|
+
for param_group in trainer.optimizer.param_groups
|
|
130
|
+
] # record the LR before the scheduler steps
|
|
131
|
+
|
|
107
132
|
def on_after_step(self, trainer: BaseTrainer):
|
|
108
133
|
# collect and aggregate over accumulation steps
|
|
109
134
|
self.losses.append(torch.stack(trainer.step_info["loss"]).mean())
|
|
@@ -156,31 +181,21 @@ class _StatsHook(BaseHook):
|
|
|
156
181
|
if "max_memory" in trainer.step_info:
|
|
157
182
|
max_memory = max(stat["max_memory"] for stat in gathered)
|
|
158
183
|
|
|
159
|
-
self.
|
|
184
|
+
self.callback(
|
|
160
185
|
trainer,
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
186
|
+
TrainingStats(
|
|
187
|
+
loss=loss.item(),
|
|
188
|
+
grad_norm=grad_norm.item() if grad_norm is not None else None,
|
|
189
|
+
step_time=step_time,
|
|
190
|
+
data_time=data_time,
|
|
191
|
+
non_finite_grad_retry_count=non_finite_grad_retry_count,
|
|
192
|
+
max_memory=max_memory,
|
|
193
|
+
records=records,
|
|
194
|
+
param_groups=self.param_groups,
|
|
195
|
+
),
|
|
168
196
|
)
|
|
169
197
|
self.reset()
|
|
170
198
|
|
|
171
|
-
def process_stats(
|
|
172
|
-
self,
|
|
173
|
-
trainer: BaseTrainer,
|
|
174
|
-
loss: float,
|
|
175
|
-
grad_norm: float | None,
|
|
176
|
-
step_time: float,
|
|
177
|
-
data_time: float,
|
|
178
|
-
non_finite_grad_retry_count: float,
|
|
179
|
-
max_memory: float | None,
|
|
180
|
-
records: Records,
|
|
181
|
-
):
|
|
182
|
-
raise NotImplementedError("Subclasses must implement this method.")
|
|
183
|
-
|
|
184
199
|
|
|
185
200
|
class ETATracker:
|
|
186
201
|
def __init__(self, warmup_steps: int):
|
|
@@ -212,7 +227,7 @@ class ETATracker:
|
|
|
212
227
|
return timedelta(seconds=int(eta_seconds))
|
|
213
228
|
|
|
214
229
|
|
|
215
|
-
class ProgressHook(
|
|
230
|
+
class ProgressHook(StatsHook):
|
|
216
231
|
"""Log progress to stdout with optional metrics, ETA, and memory.
|
|
217
232
|
|
|
218
233
|
Args:
|
|
@@ -230,8 +245,14 @@ class ProgressHook(_StatsHook):
|
|
|
230
245
|
sync: bool = False,
|
|
231
246
|
eta_warmup: int = 10,
|
|
232
247
|
show_units: bool = True,
|
|
248
|
+
param_group_keys: Sequence[str] = ("name", "lr"),
|
|
233
249
|
):
|
|
234
|
-
super().__init__(
|
|
250
|
+
super().__init__(
|
|
251
|
+
self.log_progress,
|
|
252
|
+
interval=interval,
|
|
253
|
+
sync=sync,
|
|
254
|
+
param_group_keys=param_group_keys,
|
|
255
|
+
)
|
|
235
256
|
self.with_records = with_records
|
|
236
257
|
self.eta_warmup = eta_warmup
|
|
237
258
|
self.show_units = show_units
|
|
@@ -245,47 +266,41 @@ class ProgressHook(_StatsHook):
|
|
|
245
266
|
super().on_after_train(trainer)
|
|
246
267
|
trainer.logger.info("=> Finished training")
|
|
247
268
|
|
|
248
|
-
def on_before_step(self, trainer: BaseTrainer):
|
|
249
|
-
super().on_before_step(trainer)
|
|
250
|
-
self.lrs = [
|
|
251
|
-
(param_group.get("name", str(i)), param_group["lr"])
|
|
252
|
-
for i, param_group in enumerate(trainer.optimizer.param_groups)
|
|
253
|
-
] # record the LR before the scheduler steps
|
|
254
|
-
|
|
255
269
|
def on_after_step(self, trainer: BaseTrainer):
|
|
256
|
-
self.eta_tracker.step() # should be called before
|
|
270
|
+
self.eta_tracker.step() # should be called before log_progress
|
|
257
271
|
super().on_after_step(trainer)
|
|
258
272
|
|
|
259
|
-
def
|
|
260
|
-
self,
|
|
261
|
-
trainer: BaseTrainer,
|
|
262
|
-
loss: float,
|
|
263
|
-
grad_norm: float | None,
|
|
264
|
-
step_time: float,
|
|
265
|
-
data_time: float,
|
|
266
|
-
non_finite_grad_retry_count: float,
|
|
267
|
-
max_memory: float | None,
|
|
268
|
-
records: Records,
|
|
269
|
-
):
|
|
273
|
+
def log_progress(self, trainer: BaseTrainer, stats: TrainingStats):
|
|
270
274
|
eta = self.eta_tracker.get_eta(trainer.max_steps - trainer.step)
|
|
271
275
|
trainer.logger.info(
|
|
272
276
|
f"Step {trainer.step}/{trainer.max_steps}:"
|
|
273
|
-
+ f" step {step_time:.4f}{'s' if self.show_units else ''} data {data_time:.4f}{'s' if self.show_units else ''}"
|
|
277
|
+
+ f" step {stats.step_time:.4f}{'s' if self.show_units else ''} data {stats.data_time:.4f}{'s' if self.show_units else ''}"
|
|
274
278
|
+ (f" eta {eta}" if eta is not None else "")
|
|
275
279
|
+ (
|
|
276
|
-
f" mem {max_memory:#.3g}{'GiB' if self.show_units else ''}"
|
|
277
|
-
if max_memory is not None
|
|
280
|
+
f" mem {stats.max_memory:#.3g}{'GiB' if self.show_units else ''}"
|
|
281
|
+
if stats.max_memory is not None
|
|
282
|
+
else ""
|
|
283
|
+
)
|
|
284
|
+
+ f" loss {stats.loss:.4f}"
|
|
285
|
+
+ (
|
|
286
|
+
f" grad_norm {stats.grad_norm:.4f}"
|
|
287
|
+
if stats.grad_norm is not None
|
|
278
288
|
else ""
|
|
279
289
|
)
|
|
280
|
-
+
|
|
281
|
-
|
|
282
|
-
|
|
290
|
+
+ (
|
|
291
|
+
" "
|
|
292
|
+
+ " ".join(
|
|
293
|
+
f"lr/{group.get('name', f'group_{i}')} {group['lr']:.2e}"
|
|
294
|
+
for i, group in enumerate(stats.param_groups)
|
|
295
|
+
if "lr" in group
|
|
296
|
+
)
|
|
297
|
+
)
|
|
283
298
|
+ (
|
|
284
299
|
(
|
|
285
300
|
" | "
|
|
286
301
|
+ " ".join(
|
|
287
302
|
f"{'/'.join(k)} {f'{v:#.4g}' if isinstance(v, Number) else v}"
|
|
288
|
-
for k, v in flatten_nested_dict(records).items()
|
|
303
|
+
for k, v in flatten_nested_dict(stats.records).items()
|
|
289
304
|
)
|
|
290
305
|
)
|
|
291
306
|
if self.with_records
|
|
@@ -294,55 +309,6 @@ class ProgressHook(_StatsHook):
|
|
|
294
309
|
)
|
|
295
310
|
|
|
296
311
|
|
|
297
|
-
class LoggingHook(_StatsHook):
|
|
298
|
-
"""Aggregate stats and forward them to ``trainer.log``.
|
|
299
|
-
|
|
300
|
-
Args:
|
|
301
|
-
interval: Log every N steps.
|
|
302
|
-
sync: If True, aggregate across distributed ranks.
|
|
303
|
-
"""
|
|
304
|
-
|
|
305
|
-
def __init__(
|
|
306
|
-
self,
|
|
307
|
-
interval: int = 10,
|
|
308
|
-
sync: bool = True,
|
|
309
|
-
):
|
|
310
|
-
super().__init__(interval, sync)
|
|
311
|
-
|
|
312
|
-
def on_before_step(self, trainer: BaseTrainer):
|
|
313
|
-
super().on_before_step(trainer)
|
|
314
|
-
self.lrs = [
|
|
315
|
-
(param_group.get("name", f"group_{i}"), param_group["lr"])
|
|
316
|
-
for i, param_group in enumerate(trainer.optimizer.param_groups)
|
|
317
|
-
] # record the LR before the scheduler steps
|
|
318
|
-
|
|
319
|
-
def process_stats(
|
|
320
|
-
self,
|
|
321
|
-
trainer: BaseTrainer,
|
|
322
|
-
loss: float,
|
|
323
|
-
grad_norm: float | None,
|
|
324
|
-
step_time: float,
|
|
325
|
-
data_time: float,
|
|
326
|
-
non_finite_grad_retry_count: float,
|
|
327
|
-
max_memory: float | None,
|
|
328
|
-
records: Records,
|
|
329
|
-
):
|
|
330
|
-
trainer.log(
|
|
331
|
-
{
|
|
332
|
-
"train": records
|
|
333
|
-
| ({"grad_norm": grad_norm} if grad_norm is not None else {})
|
|
334
|
-
| ({"max_memory": max_memory} if max_memory is not None else {})
|
|
335
|
-
| {
|
|
336
|
-
"loss": loss,
|
|
337
|
-
"data_time": data_time,
|
|
338
|
-
"step_time": step_time,
|
|
339
|
-
"non_finite_grad_retry_count": non_finite_grad_retry_count,
|
|
340
|
-
"lr": {name: lr for name, lr in self.lrs},
|
|
341
|
-
}
|
|
342
|
-
}
|
|
343
|
-
)
|
|
344
|
-
|
|
345
|
-
|
|
346
312
|
class CheckpointingHook(BaseHook):
|
|
347
313
|
"""Save and optionally restore checkpoints at regular intervals.
|
|
348
314
|
|
|
@@ -631,7 +597,8 @@ class WandbHook(BaseHook):
|
|
|
631
597
|
project: W&B project name.
|
|
632
598
|
config: Optional config dict or JSON file path to log.
|
|
633
599
|
tags: Optional tag list.
|
|
634
|
-
image_format: File format for images or a callable
|
|
600
|
+
image_format: File format for images or a callable taking the flattened
|
|
601
|
+
image key and returning the format.
|
|
635
602
|
**wandb_kwargs: Extra arguments forwarded to ``wandb.init``.
|
|
636
603
|
"""
|
|
637
604
|
|
|
@@ -640,7 +607,7 @@ class WandbHook(BaseHook):
|
|
|
640
607
|
project: str,
|
|
641
608
|
config: dict[str, Any] | str | None = None,
|
|
642
609
|
tags: Sequence[str] | None = None,
|
|
643
|
-
image_format: str | None | Callable[[str], str | None] = "png",
|
|
610
|
+
image_format: str | None | Callable[[tuple[str, ...]], str | None] = "png",
|
|
644
611
|
**wandb_kwargs,
|
|
645
612
|
):
|
|
646
613
|
self.project = project
|
|
@@ -689,7 +656,7 @@ class WandbHook(BaseHook):
|
|
|
689
656
|
if _dist_rank() == 0:
|
|
690
657
|
wandb_data = {}
|
|
691
658
|
for k, img in flatten_nested_dict({"vis": records}).items():
|
|
692
|
-
file_type = self.image_format(k
|
|
659
|
+
file_type = self.image_format(k)
|
|
693
660
|
wandb_data.setdefault("/".join(k[:-1]), []).append(
|
|
694
661
|
wandb.Image(
|
|
695
662
|
self._ensure_jpeg_compatible(img)
|
|
@@ -739,12 +706,12 @@ class ImageFileLoggerHook(BaseHook):
|
|
|
739
706
|
"""Persist logged images to ``workspace/visualizations`` on rank 0.
|
|
740
707
|
|
|
741
708
|
Args:
|
|
742
|
-
image_format: File extension or callable taking the
|
|
709
|
+
image_format: File extension or callable taking the flattened image key.
|
|
743
710
|
"""
|
|
744
711
|
|
|
745
712
|
def __init__(
|
|
746
713
|
self,
|
|
747
|
-
image_format: str | Callable[[str], str] = "png",
|
|
714
|
+
image_format: str | Callable[[tuple[str, ...]], str] = "png",
|
|
748
715
|
):
|
|
749
716
|
if callable(image_format):
|
|
750
717
|
self.image_format = image_format
|
|
@@ -755,7 +722,7 @@ class ImageFileLoggerHook(BaseHook):
|
|
|
755
722
|
if _dist_rank() == 0:
|
|
756
723
|
for k, img in flatten_nested_dict(records).items():
|
|
757
724
|
p = trainer.workspace / "visualizations" / str(trainer.step) / Path(*k)
|
|
758
|
-
p = Path(str(p) + "." + self.image_format(k
|
|
725
|
+
p = Path(str(p) + "." + self.image_format(k))
|
|
759
726
|
if not dry_run:
|
|
760
727
|
p.parent.mkdir(parents=True, exist_ok=True)
|
|
761
728
|
img.save(p)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|