trainloop 0.1.0__tar.gz → 0.3.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.3.0/PKG-INFO +77 -0
- trainloop-0.3.0/README.md +66 -0
- {trainloop-0.1.0 → trainloop-0.3.0}/pyproject.toml +3 -2
- {trainloop-0.1.0 → trainloop-0.3.0}/src/trainloop/hooks.py +53 -18
- {trainloop-0.1.0 → trainloop-0.3.0}/src/trainloop/trainer.py +2 -0
- trainloop-0.1.0/PKG-INFO +0 -24
- trainloop-0.1.0/README.md +0 -13
- {trainloop-0.1.0 → trainloop-0.3.0}/src/trainloop/__init__.py +0 -0
- {trainloop-0.1.0 → trainloop-0.3.0}/src/trainloop/py.typed +0 -0
- {trainloop-0.1.0 → trainloop-0.3.0}/src/trainloop/utils.py +0 -0
trainloop-0.3.0/PKG-INFO
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: trainloop
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: Minimal PyTorch training loop with hooks and checkpointing.
|
|
5
|
+
Author: Karim Abou Zeid
|
|
6
|
+
Author-email: Karim Abou Zeid <contact@ka.codes>
|
|
7
|
+
Requires-Dist: pillow>=11.3.0
|
|
8
|
+
Requires-Dist: torch>=2.0.0
|
|
9
|
+
Requires-Python: >=3.10
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
|
|
12
|
+
# trainloop
|
|
13
|
+
|
|
14
|
+
[](https://pypi.org/project/trainloop/)
|
|
15
|
+
|
|
16
|
+
Minimal PyTorch training loop with hooks for logging, checkpointing, and customization.
|
|
17
|
+
|
|
18
|
+
Docs: https://karimknaebel.github.io/trainloop/
|
|
19
|
+
|
|
20
|
+
## Install
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
pip install trainloop
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Basic example
|
|
27
|
+
|
|
28
|
+
```python
|
|
29
|
+
import logging
|
|
30
|
+
|
|
31
|
+
import torch
|
|
32
|
+
import torch.nn as nn
|
|
33
|
+
|
|
34
|
+
from trainloop import BaseTrainer, CheckpointingHook, ProgressHook
|
|
35
|
+
|
|
36
|
+
logging.basicConfig(level=logging.INFO)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class MyTrainer(BaseTrainer):
|
|
40
|
+
def build_data_loader(self):
|
|
41
|
+
class ToyDataset(torch.utils.data.IterableDataset):
|
|
42
|
+
def __iter__(self):
|
|
43
|
+
while True:
|
|
44
|
+
data = torch.randn(784)
|
|
45
|
+
target = torch.randint(0, 10, (1,)).item()
|
|
46
|
+
yield data, target
|
|
47
|
+
|
|
48
|
+
return torch.utils.data.DataLoader(ToyDataset(), batch_size=32)
|
|
49
|
+
|
|
50
|
+
def build_model(self):
|
|
51
|
+
return nn.Sequential(
|
|
52
|
+
nn.Linear(784, 128),
|
|
53
|
+
nn.ReLU(),
|
|
54
|
+
nn.Linear(128, 10),
|
|
55
|
+
).to(self.device)
|
|
56
|
+
|
|
57
|
+
def build_optimizer(self):
|
|
58
|
+
return torch.optim.AdamW(self.model.parameters(), lr=3e-4)
|
|
59
|
+
|
|
60
|
+
def build_hooks(self):
|
|
61
|
+
return [
|
|
62
|
+
ProgressHook(interval=50, with_records=True),
|
|
63
|
+
CheckpointingHook(interval=500, keep_previous=2),
|
|
64
|
+
]
|
|
65
|
+
|
|
66
|
+
def forward(self, batch):
|
|
67
|
+
x, y = batch
|
|
68
|
+
x, y = x.to(self.device), y.to(self.device)
|
|
69
|
+
logits = self.model(x)
|
|
70
|
+
loss = nn.functional.cross_entropy(logits, y)
|
|
71
|
+
accuracy = (logits.argmax(1) == y).float().mean().item()
|
|
72
|
+
return loss, {"accuracy": accuracy}
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
trainer = MyTrainer(max_steps=2000, device="cpu", workspace="runs/demo")
|
|
76
|
+
trainer.train()
|
|
77
|
+
```
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# trainloop
|
|
2
|
+
|
|
3
|
+
[](https://pypi.org/project/trainloop/)
|
|
4
|
+
|
|
5
|
+
Minimal PyTorch training loop with hooks for logging, checkpointing, and customization.
|
|
6
|
+
|
|
7
|
+
Docs: https://karimknaebel.github.io/trainloop/
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
pip install trainloop
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Basic example
|
|
16
|
+
|
|
17
|
+
```python
|
|
18
|
+
import logging
|
|
19
|
+
|
|
20
|
+
import torch
|
|
21
|
+
import torch.nn as nn
|
|
22
|
+
|
|
23
|
+
from trainloop import BaseTrainer, CheckpointingHook, ProgressHook
|
|
24
|
+
|
|
25
|
+
logging.basicConfig(level=logging.INFO)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class MyTrainer(BaseTrainer):
|
|
29
|
+
def build_data_loader(self):
|
|
30
|
+
class ToyDataset(torch.utils.data.IterableDataset):
|
|
31
|
+
def __iter__(self):
|
|
32
|
+
while True:
|
|
33
|
+
data = torch.randn(784)
|
|
34
|
+
target = torch.randint(0, 10, (1,)).item()
|
|
35
|
+
yield data, target
|
|
36
|
+
|
|
37
|
+
return torch.utils.data.DataLoader(ToyDataset(), batch_size=32)
|
|
38
|
+
|
|
39
|
+
def build_model(self):
|
|
40
|
+
return nn.Sequential(
|
|
41
|
+
nn.Linear(784, 128),
|
|
42
|
+
nn.ReLU(),
|
|
43
|
+
nn.Linear(128, 10),
|
|
44
|
+
).to(self.device)
|
|
45
|
+
|
|
46
|
+
def build_optimizer(self):
|
|
47
|
+
return torch.optim.AdamW(self.model.parameters(), lr=3e-4)
|
|
48
|
+
|
|
49
|
+
def build_hooks(self):
|
|
50
|
+
return [
|
|
51
|
+
ProgressHook(interval=50, with_records=True),
|
|
52
|
+
CheckpointingHook(interval=500, keep_previous=2),
|
|
53
|
+
]
|
|
54
|
+
|
|
55
|
+
def forward(self, batch):
|
|
56
|
+
x, y = batch
|
|
57
|
+
x, y = x.to(self.device), y.to(self.device)
|
|
58
|
+
logits = self.model(x)
|
|
59
|
+
loss = nn.functional.cross_entropy(logits, y)
|
|
60
|
+
accuracy = (logits.argmax(1) == y).float().mean().item()
|
|
61
|
+
return loss, {"accuracy": accuracy}
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
trainer = MyTrainer(max_steps=2000, device="cpu", workspace="runs/demo")
|
|
65
|
+
trainer.train()
|
|
66
|
+
```
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "trainloop"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.3.0"
|
|
4
4
|
description = "Minimal PyTorch training loop with hooks and checkpointing."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
authors = [
|
|
@@ -18,9 +18,10 @@ build-backend = "uv_build"
|
|
|
18
18
|
|
|
19
19
|
[dependency-groups]
|
|
20
20
|
dev = [
|
|
21
|
-
"wandb>=0.20.1",
|
|
22
21
|
"pytest>=8.4.0",
|
|
23
22
|
"ruff>=0.11.13",
|
|
24
23
|
"zensical>=0.0.10",
|
|
25
24
|
"mkdocstrings-python>=2.0.1",
|
|
25
|
+
"wandb>=0.20.1",
|
|
26
|
+
"numpy>=2.2.6",
|
|
26
27
|
]
|
|
@@ -30,6 +30,23 @@ from .trainer import BaseTrainer, Records
|
|
|
30
30
|
from .utils import flatten_nested_dict, key_average
|
|
31
31
|
|
|
32
32
|
|
|
33
|
+
def _dist_is_initialized() -> bool:
|
|
34
|
+
return dist.is_available() and dist.is_initialized()
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _dist_world_size() -> int:
|
|
38
|
+
return dist.get_world_size() if _dist_is_initialized() else 1
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _dist_rank() -> int:
|
|
42
|
+
return dist.get_rank() if _dist_is_initialized() else 0
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _dist_barrier() -> None:
|
|
46
|
+
if _dist_is_initialized() and dist.get_world_size() > 1:
|
|
47
|
+
dist.barrier()
|
|
48
|
+
|
|
49
|
+
|
|
33
50
|
class BaseHook:
|
|
34
51
|
"""Lifecycle hooks for `BaseTrainer`."""
|
|
35
52
|
|
|
@@ -84,6 +101,7 @@ class _StatsHook(BaseHook):
|
|
|
84
101
|
self.grad_norms = []
|
|
85
102
|
self.data_times = []
|
|
86
103
|
self.step_times = []
|
|
104
|
+
self.non_finite_grad_retry_counts = []
|
|
87
105
|
self.max_memories = []
|
|
88
106
|
|
|
89
107
|
def on_after_step(self, trainer: BaseTrainer):
|
|
@@ -94,6 +112,9 @@ class _StatsHook(BaseHook):
|
|
|
94
112
|
self.records_ls.append(key_average(trainer.step_info["records"]))
|
|
95
113
|
self.data_times.append(sum(trainer.step_info["data_time"])) # total
|
|
96
114
|
self.step_times.append(trainer.step_info["step_time"])
|
|
115
|
+
self.non_finite_grad_retry_counts.append(
|
|
116
|
+
trainer.step_info["non_finite_grad_retry_count"]
|
|
117
|
+
)
|
|
97
118
|
if "max_memory" in trainer.step_info:
|
|
98
119
|
self.max_memories.append(trainer.step_info["max_memory"])
|
|
99
120
|
|
|
@@ -104,9 +125,12 @@ class _StatsHook(BaseHook):
|
|
|
104
125
|
records = key_average(self.records_ls)
|
|
105
126
|
data_time = sum(self.data_times) / len(self.data_times)
|
|
106
127
|
step_time = sum(self.step_times) / len(self.step_times)
|
|
128
|
+
non_finite_grad_retry_count = sum(self.non_finite_grad_retry_counts) / len(
|
|
129
|
+
self.non_finite_grad_retry_counts
|
|
130
|
+
)
|
|
107
131
|
max_memory = max(self.max_memories) if self.max_memories else None
|
|
108
132
|
|
|
109
|
-
if self.sync:
|
|
133
|
+
if self.sync and _dist_world_size() > 1:
|
|
110
134
|
# aggregate accross all ranks
|
|
111
135
|
dist.all_reduce(loss, op=dist.ReduceOp.AVG)
|
|
112
136
|
if grad_norm is not None:
|
|
@@ -119,12 +143,16 @@ class _StatsHook(BaseHook):
|
|
|
119
143
|
"records": records,
|
|
120
144
|
"data_time": data_time,
|
|
121
145
|
"step_time": step_time,
|
|
146
|
+
"non_finite_grad_retry_count": non_finite_grad_retry_count,
|
|
122
147
|
"max_memory": max_memory,
|
|
123
148
|
},
|
|
124
149
|
)
|
|
125
150
|
records = key_average([stat["records"] for stat in gathered])
|
|
126
151
|
data_time = sum(stat["data_time"] for stat in gathered) / len(gathered)
|
|
127
152
|
step_time = sum(stat["step_time"] for stat in gathered) / len(gathered)
|
|
153
|
+
non_finite_grad_retry_count = sum(
|
|
154
|
+
stat["non_finite_grad_retry_count"] for stat in gathered
|
|
155
|
+
) / len(gathered)
|
|
128
156
|
if "max_memory" in trainer.step_info:
|
|
129
157
|
max_memory = max(stat["max_memory"] for stat in gathered)
|
|
130
158
|
|
|
@@ -134,6 +162,7 @@ class _StatsHook(BaseHook):
|
|
|
134
162
|
grad_norm.item() if grad_norm is not None else None,
|
|
135
163
|
step_time,
|
|
136
164
|
data_time,
|
|
165
|
+
non_finite_grad_retry_count,
|
|
137
166
|
max_memory,
|
|
138
167
|
records,
|
|
139
168
|
)
|
|
@@ -146,6 +175,7 @@ class _StatsHook(BaseHook):
|
|
|
146
175
|
grad_norm: float | None,
|
|
147
176
|
step_time: float,
|
|
148
177
|
data_time: float,
|
|
178
|
+
non_finite_grad_retry_count: float,
|
|
149
179
|
max_memory: float | None,
|
|
150
180
|
records: Records,
|
|
151
181
|
):
|
|
@@ -233,6 +263,7 @@ class ProgressHook(_StatsHook):
|
|
|
233
263
|
grad_norm: float | None,
|
|
234
264
|
step_time: float,
|
|
235
265
|
data_time: float,
|
|
266
|
+
non_finite_grad_retry_count: float,
|
|
236
267
|
max_memory: float | None,
|
|
237
268
|
records: Records,
|
|
238
269
|
):
|
|
@@ -285,6 +316,7 @@ class LoggingHook(_StatsHook):
|
|
|
285
316
|
grad_norm: float | None,
|
|
286
317
|
step_time: float,
|
|
287
318
|
data_time: float,
|
|
319
|
+
non_finite_grad_retry_count: float,
|
|
288
320
|
max_memory: float | None,
|
|
289
321
|
records: Records,
|
|
290
322
|
):
|
|
@@ -301,6 +333,7 @@ class LoggingHook(_StatsHook):
|
|
|
301
333
|
"loss": loss,
|
|
302
334
|
"data_time": data_time,
|
|
303
335
|
"step_time": step_time,
|
|
336
|
+
"non_finite_grad_retry_count": non_finite_grad_retry_count,
|
|
304
337
|
"lr": {f"group_{i}": lr for i, lr in lrs},
|
|
305
338
|
}
|
|
306
339
|
}
|
|
@@ -326,7 +359,7 @@ class CheckpointingHook(BaseHook):
|
|
|
326
359
|
interval: int,
|
|
327
360
|
keep_previous: int = 0, # keep N previous checkpoints
|
|
328
361
|
keep_interval: int = 0, # keep checkpoints of every N-th step
|
|
329
|
-
path: Path | str = "
|
|
362
|
+
path: Path | str = "checkpoints",
|
|
330
363
|
load: Path | str | Literal["latest"] | None = "latest",
|
|
331
364
|
exit_signals: list[signal.Signals] | signal.Signals = None,
|
|
332
365
|
exit_code: int | Literal["128+signal"] = "128+signal",
|
|
@@ -375,9 +408,7 @@ class CheckpointingHook(BaseHook):
|
|
|
375
408
|
|
|
376
409
|
trainer.logger.info(f"=> Loading checkpoint from {load_path} ...")
|
|
377
410
|
state_dict = {
|
|
378
|
-
file.with_suffix("").name: torch.load(
|
|
379
|
-
file, map_location=trainer.device, weights_only=True
|
|
380
|
-
)
|
|
411
|
+
file.with_suffix("").name: torch.load(file, map_location=trainer.device)
|
|
381
412
|
for file in load_path.iterdir()
|
|
382
413
|
if file.is_file() and file.suffix == ".pt"
|
|
383
414
|
}
|
|
@@ -387,15 +418,19 @@ class CheckpointingHook(BaseHook):
|
|
|
387
418
|
def on_before_step(self, trainer: BaseTrainer):
|
|
388
419
|
if self.has_exit_signal_handlers:
|
|
389
420
|
self.dist_exit_signal.fill_(self.local_exit_signal)
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
self.
|
|
393
|
-
|
|
421
|
+
if _dist_world_size() > 1:
|
|
422
|
+
# micro optimization: reduce async during step and read after step
|
|
423
|
+
self.dist_exit_signal_work = dist.all_reduce(
|
|
424
|
+
self.dist_exit_signal, op=dist.ReduceOp.MAX, async_op=True
|
|
425
|
+
)
|
|
426
|
+
else:
|
|
427
|
+
self.dist_exit_signal_work = None
|
|
394
428
|
|
|
395
429
|
def on_after_step(self, trainer: BaseTrainer):
|
|
396
430
|
save_and_exit = False
|
|
397
431
|
if self.has_exit_signal_handlers:
|
|
398
|
-
self.dist_exit_signal_work
|
|
432
|
+
if self.dist_exit_signal_work is not None:
|
|
433
|
+
self.dist_exit_signal_work.wait()
|
|
399
434
|
exit_signal = self.dist_exit_signal.item()
|
|
400
435
|
save_and_exit = exit_signal != -1
|
|
401
436
|
|
|
@@ -414,7 +449,7 @@ class CheckpointingHook(BaseHook):
|
|
|
414
449
|
keep=self.keep_interval > 0 and trainer.step % self.keep_interval == 0,
|
|
415
450
|
)
|
|
416
451
|
if save_and_exit:
|
|
417
|
-
|
|
452
|
+
_dist_barrier()
|
|
418
453
|
if self.exit_wait > 0:
|
|
419
454
|
trainer.logger.info(
|
|
420
455
|
f"=> Waiting {self.exit_wait:.0f} seconds before exit ..."
|
|
@@ -435,7 +470,7 @@ class CheckpointingHook(BaseHook):
|
|
|
435
470
|
while retaining or pruning older checkpoints are logged but not raised.
|
|
436
471
|
"""
|
|
437
472
|
|
|
438
|
-
|
|
473
|
+
_dist_barrier()
|
|
439
474
|
|
|
440
475
|
state_dict = trainer.state_dict()
|
|
441
476
|
|
|
@@ -447,7 +482,7 @@ class CheckpointingHook(BaseHook):
|
|
|
447
482
|
# dst=0,
|
|
448
483
|
# )
|
|
449
484
|
|
|
450
|
-
if
|
|
485
|
+
if _dist_rank() == 0:
|
|
451
486
|
# make dir
|
|
452
487
|
save_path = self.path / str(trainer.step)
|
|
453
488
|
if not save_path.is_absolute():
|
|
@@ -606,7 +641,7 @@ class WandbHook(BaseHook):
|
|
|
606
641
|
self.wandb_kwargs = wandb_kwargs
|
|
607
642
|
|
|
608
643
|
def on_before_train(self, trainer: BaseTrainer):
|
|
609
|
-
if
|
|
644
|
+
if _dist_rank() == 0:
|
|
610
645
|
wandb_run_id = self._load_wandb_run_id(trainer)
|
|
611
646
|
|
|
612
647
|
tags = os.getenv("WANDB_TAGS", "")
|
|
@@ -627,11 +662,11 @@ class WandbHook(BaseHook):
|
|
|
627
662
|
self._save_wandb_run_id(trainer, self.wandb.id)
|
|
628
663
|
|
|
629
664
|
def on_after_train(self, trainer: BaseTrainer):
|
|
630
|
-
if
|
|
665
|
+
if _dist_rank() == 0:
|
|
631
666
|
self.wandb.finish()
|
|
632
667
|
|
|
633
668
|
def on_log(self, trainer: BaseTrainer, records: dict, dry_run: bool = False):
|
|
634
|
-
if
|
|
669
|
+
if _dist_rank() == 0:
|
|
635
670
|
data = {"/".join(k): v for k, v in flatten_nested_dict(records).items()}
|
|
636
671
|
if not dry_run:
|
|
637
672
|
self.wandb.log(data, step=trainer.step)
|
|
@@ -639,7 +674,7 @@ class WandbHook(BaseHook):
|
|
|
639
674
|
trainer.logger.debug(f"Dry run log. Would log: {data}")
|
|
640
675
|
|
|
641
676
|
def on_log_images(self, trainer: BaseTrainer, records: dict, dry_run: bool = False):
|
|
642
|
-
if
|
|
677
|
+
if _dist_rank() == 0:
|
|
643
678
|
wandb_data = {}
|
|
644
679
|
for k, img in flatten_nested_dict({"vis": records}).items():
|
|
645
680
|
file_type = self.image_format(k[-1])
|
|
@@ -705,7 +740,7 @@ class ImageFileLoggerHook(BaseHook):
|
|
|
705
740
|
self.image_format = lambda _: image_format
|
|
706
741
|
|
|
707
742
|
def on_log_images(self, trainer: BaseTrainer, records: dict, dry_run: bool = False):
|
|
708
|
-
if
|
|
743
|
+
if _dist_rank() == 0:
|
|
709
744
|
for k, img in flatten_nested_dict(records).items():
|
|
710
745
|
p = trainer.workspace / "visualizations" / str(trainer.step) / Path(*k)
|
|
711
746
|
p = Path(str(p) + "." + self.image_format(k[-1]))
|
|
@@ -204,6 +204,7 @@ class BaseTrainer:
|
|
|
204
204
|
reset_step_info()
|
|
205
205
|
self.step_info["data_time"] = []
|
|
206
206
|
non_finite_grad_retry_count = 0
|
|
207
|
+
self.step_info["non_finite_grad_retry_count"] = non_finite_grad_retry_count
|
|
207
208
|
i_acc = 0
|
|
208
209
|
while i_acc < self.gradient_accumulation_steps:
|
|
209
210
|
is_accumulating = i_acc < self.gradient_accumulation_steps - 1
|
|
@@ -275,6 +276,7 @@ class BaseTrainer:
|
|
|
275
276
|
< self.max_non_finite_grad_retries
|
|
276
277
|
):
|
|
277
278
|
non_finite_grad_retry_count += 1
|
|
279
|
+
self.step_info["non_finite_grad_retry_count"] = non_finite_grad_retry_count
|
|
278
280
|
self.logger.warning(
|
|
279
281
|
f"Gradient is non-finite. Retrying step {self.step} (retry {non_finite_grad_retry_count}"
|
|
280
282
|
+ (
|
trainloop-0.1.0/PKG-INFO
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.3
|
|
2
|
-
Name: trainloop
|
|
3
|
-
Version: 0.1.0
|
|
4
|
-
Summary: Minimal PyTorch training loop with hooks and checkpointing.
|
|
5
|
-
Author: Karim Abou Zeid
|
|
6
|
-
Author-email: Karim Abou Zeid <contact@ka.codes>
|
|
7
|
-
Requires-Dist: pillow>=11.3.0
|
|
8
|
-
Requires-Dist: torch>=2.0.0
|
|
9
|
-
Requires-Python: >=3.10
|
|
10
|
-
Description-Content-Type: text/markdown
|
|
11
|
-
|
|
12
|
-
# trainloop
|
|
13
|
-
|
|
14
|
-
[](https://pypi.org/project/trainloop/)
|
|
15
|
-
|
|
16
|
-
Minimal PyTorch training loop with hooks for logging, checkpointing, and customization.
|
|
17
|
-
|
|
18
|
-
Docs: https://kabouzeid.github.io/trainloop/
|
|
19
|
-
|
|
20
|
-
## Install
|
|
21
|
-
|
|
22
|
-
```bash
|
|
23
|
-
pip install trainloop
|
|
24
|
-
```
|
trainloop-0.1.0/README.md
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
# trainloop
|
|
2
|
-
|
|
3
|
-
[](https://pypi.org/project/trainloop/)
|
|
4
|
-
|
|
5
|
-
Minimal PyTorch training loop with hooks for logging, checkpointing, and customization.
|
|
6
|
-
|
|
7
|
-
Docs: https://kabouzeid.github.io/trainloop/
|
|
8
|
-
|
|
9
|
-
## Install
|
|
10
|
-
|
|
11
|
-
```bash
|
|
12
|
-
pip install trainloop
|
|
13
|
-
```
|
|
File without changes
|
|
File without changes
|
|
File without changes
|