trainloop 0.7.0__tar.gz → 0.8.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.7.0 → trainloop-0.8.0}/PKG-INFO +2 -3
- trainloop-0.8.0/pyproject.toml +26 -0
- trainloop-0.7.0/pyproject.toml → trainloop-0.8.0/pyproject.toml.orig +3 -3
- {trainloop-0.7.0 → trainloop-0.8.0}/src/trainloop/__init__.py +7 -7
- {trainloop-0.7.0 → trainloop-0.8.0}/src/trainloop/hooks.py +71 -35
- {trainloop-0.7.0 → trainloop-0.8.0}/src/trainloop/trainer.py +17 -13
- {trainloop-0.7.0 → trainloop-0.8.0}/src/trainloop/utils.py +21 -2
- {trainloop-0.7.0 → trainloop-0.8.0}/README.md +0 -0
- {trainloop-0.7.0 → trainloop-0.8.0}/src/trainloop/py.typed +0 -0
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: trainloop
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.8.0
|
|
4
4
|
Summary: Minimal PyTorch training loop with hooks and checkpointing.
|
|
5
5
|
Author: Karim Knaebel
|
|
6
6
|
Author-email: Karim Knaebel <contact@knaebel.dev>
|
|
7
|
-
Requires-Dist:
|
|
8
|
-
Requires-Dist: torch>=2.0.0
|
|
7
|
+
Requires-Dist: torch>=2.1.0
|
|
9
8
|
Requires-Python: >=3.10
|
|
10
9
|
Description-Content-Type: text/markdown
|
|
11
10
|
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "trainloop"
|
|
3
|
+
version = "0.8.0"
|
|
4
|
+
description = "Minimal PyTorch training loop with hooks and checkpointing."
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.10"
|
|
7
|
+
dependencies = ["torch>=2.1.0"]
|
|
8
|
+
|
|
9
|
+
[[project.authors]]
|
|
10
|
+
name = "Karim Knaebel"
|
|
11
|
+
email = "contact@knaebel.dev"
|
|
12
|
+
|
|
13
|
+
[build-system]
|
|
14
|
+
requires = ["uv_build>=0.9.0,<0.10.0"]
|
|
15
|
+
build-backend = "uv_build"
|
|
16
|
+
|
|
17
|
+
[dependency-groups]
|
|
18
|
+
dev = [
|
|
19
|
+
"pytest>=8.4.0",
|
|
20
|
+
"ruff>=0.11.13",
|
|
21
|
+
"zensical>=0.0.10",
|
|
22
|
+
"mkdocstrings-python>=2.0.1",
|
|
23
|
+
"pillow>=11.3.0",
|
|
24
|
+
"wandb>=0.20.1",
|
|
25
|
+
"numpy>=2.2.6",
|
|
26
|
+
]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "trainloop"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.8.0"
|
|
4
4
|
description = "Minimal PyTorch training loop with hooks and checkpointing."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
authors = [
|
|
@@ -8,8 +8,7 @@ authors = [
|
|
|
8
8
|
]
|
|
9
9
|
requires-python = ">=3.10"
|
|
10
10
|
dependencies = [
|
|
11
|
-
"
|
|
12
|
-
"torch>=2.0.0",
|
|
11
|
+
"torch>=2.1.0",
|
|
13
12
|
]
|
|
14
13
|
|
|
15
14
|
[build-system]
|
|
@@ -22,6 +21,7 @@ dev = [
|
|
|
22
21
|
"ruff>=0.11.13",
|
|
23
22
|
"zensical>=0.0.10",
|
|
24
23
|
"mkdocstrings-python>=2.0.1",
|
|
24
|
+
"pillow>=11.3.0",
|
|
25
25
|
"wandb>=0.20.1",
|
|
26
26
|
"numpy>=2.2.6",
|
|
27
27
|
]
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
from .hooks import (
|
|
2
2
|
BaseHook,
|
|
3
3
|
CheckpointingHook,
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
CUDAMaxMemoryHook,
|
|
5
|
+
EMAHook,
|
|
6
6
|
ImageFileLoggerHook,
|
|
7
7
|
ProgressHook,
|
|
8
8
|
StatsHook,
|
|
@@ -12,16 +12,16 @@ from .hooks import (
|
|
|
12
12
|
from .trainer import BaseTrainer, LossNoneWarning, map_nested_tensor
|
|
13
13
|
|
|
14
14
|
__all__ = [
|
|
15
|
-
"BaseTrainer",
|
|
16
15
|
"BaseHook",
|
|
16
|
+
"BaseTrainer",
|
|
17
|
+
"CUDAMaxMemoryHook",
|
|
17
18
|
"CheckpointingHook",
|
|
18
|
-
"
|
|
19
|
+
"EMAHook",
|
|
20
|
+
"ImageFileLoggerHook",
|
|
21
|
+
"LossNoneWarning",
|
|
19
22
|
"ProgressHook",
|
|
20
23
|
"StatsHook",
|
|
21
24
|
"TrainingStats",
|
|
22
|
-
"EmaHook",
|
|
23
25
|
"WandbHook",
|
|
24
|
-
"ImageFileLoggerHook",
|
|
25
|
-
"LossNoneWarning",
|
|
26
26
|
"map_nested_tensor",
|
|
27
27
|
]
|
|
@@ -5,21 +5,20 @@ import sys
|
|
|
5
5
|
import tempfile
|
|
6
6
|
import time
|
|
7
7
|
import warnings
|
|
8
|
+
from collections.abc import Callable, Iterable, Sequence
|
|
8
9
|
from dataclasses import dataclass
|
|
9
10
|
from datetime import timedelta
|
|
10
11
|
from numbers import Number
|
|
11
12
|
from pathlib import Path
|
|
12
|
-
from typing import Any,
|
|
13
|
+
from typing import Any, Literal
|
|
13
14
|
|
|
14
15
|
import torch
|
|
15
16
|
import torch.distributed as dist
|
|
16
|
-
from PIL import Image
|
|
17
|
-
from PIL.Image import Image as PILImage
|
|
18
17
|
from torch.distributed.checkpoint.state_dict import (
|
|
19
18
|
get_model_state_dict,
|
|
20
19
|
set_model_state_dict,
|
|
21
20
|
)
|
|
22
|
-
from torch.optim.swa_utils import AveragedModel,
|
|
21
|
+
from torch.optim.swa_utils import AveragedModel, get_ema_multi_avg_fn
|
|
23
22
|
|
|
24
23
|
try:
|
|
25
24
|
import wandb
|
|
@@ -27,8 +26,19 @@ except ImportError:
|
|
|
27
26
|
# only needed for WandbHook
|
|
28
27
|
pass
|
|
29
28
|
|
|
29
|
+
try:
|
|
30
|
+
from PIL import Image
|
|
31
|
+
from PIL.Image import Image as PILImage
|
|
32
|
+
except ImportError:
|
|
33
|
+
# only needed for WandbHook JPEG conversion
|
|
34
|
+
pass
|
|
35
|
+
|
|
30
36
|
from .trainer import BaseTrainer, Records
|
|
31
|
-
from .utils import
|
|
37
|
+
from .utils import (
|
|
38
|
+
flatten_nested_dict,
|
|
39
|
+
key_average,
|
|
40
|
+
log_state_dict_incompatible_keys,
|
|
41
|
+
)
|
|
32
42
|
|
|
33
43
|
|
|
34
44
|
def _dist_is_initialized() -> bool:
|
|
@@ -85,8 +95,8 @@ class TrainingStats:
|
|
|
85
95
|
grad_norm: float | None
|
|
86
96
|
step_time: float
|
|
87
97
|
data_time: float
|
|
88
|
-
|
|
89
|
-
|
|
98
|
+
non_finite_grad_retries: float
|
|
99
|
+
cuda_max_memory: float | None
|
|
90
100
|
records: Records
|
|
91
101
|
param_groups: list[dict[str, Any]]
|
|
92
102
|
|
|
@@ -119,8 +129,8 @@ class StatsHook(BaseHook):
|
|
|
119
129
|
self.grad_norms = []
|
|
120
130
|
self.data_times = []
|
|
121
131
|
self.step_times = []
|
|
122
|
-
self.
|
|
123
|
-
self.
|
|
132
|
+
self.non_finite_grad_retries = []
|
|
133
|
+
self.cuda_max_memories = []
|
|
124
134
|
|
|
125
135
|
def on_before_step(self, trainer: BaseTrainer):
|
|
126
136
|
super().on_before_step(trainer)
|
|
@@ -137,11 +147,11 @@ class StatsHook(BaseHook):
|
|
|
137
147
|
self.records_ls.append(key_average(trainer.step_info["records"]))
|
|
138
148
|
self.data_times.append(sum(trainer.step_info["data_time"])) # total
|
|
139
149
|
self.step_times.append(trainer.step_info["step_time"])
|
|
140
|
-
self.
|
|
141
|
-
trainer.step_info["
|
|
150
|
+
self.non_finite_grad_retries.append(
|
|
151
|
+
trainer.step_info["non_finite_grad_retries"]
|
|
142
152
|
)
|
|
143
|
-
if "
|
|
144
|
-
self.
|
|
153
|
+
if "cuda_max_memory" in trainer.step_info:
|
|
154
|
+
self.cuda_max_memories.append(trainer.step_info["cuda_max_memory"])
|
|
145
155
|
|
|
146
156
|
if trainer.step % self.interval == 0 or trainer.step == trainer.max_steps:
|
|
147
157
|
# aggregate over steps
|
|
@@ -150,10 +160,12 @@ class StatsHook(BaseHook):
|
|
|
150
160
|
records = key_average(self.records_ls)
|
|
151
161
|
data_time = sum(self.data_times) / len(self.data_times)
|
|
152
162
|
step_time = sum(self.step_times) / len(self.step_times)
|
|
153
|
-
|
|
154
|
-
self.
|
|
163
|
+
non_finite_grad_retries = sum(self.non_finite_grad_retries) / len(
|
|
164
|
+
self.non_finite_grad_retries
|
|
165
|
+
)
|
|
166
|
+
cuda_max_memory = (
|
|
167
|
+
max(self.cuda_max_memories) if self.cuda_max_memories else None
|
|
155
168
|
)
|
|
156
|
-
max_memory = max(self.max_memories) if self.max_memories else None
|
|
157
169
|
|
|
158
170
|
if self.sync and _dist_world_size() > 1:
|
|
159
171
|
# aggregate accross all ranks
|
|
@@ -168,18 +180,18 @@ class StatsHook(BaseHook):
|
|
|
168
180
|
"records": records,
|
|
169
181
|
"data_time": data_time,
|
|
170
182
|
"step_time": step_time,
|
|
171
|
-
"
|
|
172
|
-
"
|
|
183
|
+
"non_finite_grad_retries": non_finite_grad_retries,
|
|
184
|
+
"cuda_max_memory": cuda_max_memory,
|
|
173
185
|
},
|
|
174
186
|
)
|
|
175
187
|
records = key_average([stat["records"] for stat in gathered])
|
|
176
188
|
data_time = sum(stat["data_time"] for stat in gathered) / len(gathered)
|
|
177
189
|
step_time = sum(stat["step_time"] for stat in gathered) / len(gathered)
|
|
178
|
-
|
|
179
|
-
stat["
|
|
190
|
+
non_finite_grad_retries = sum(
|
|
191
|
+
stat["non_finite_grad_retries"] for stat in gathered
|
|
180
192
|
) / len(gathered)
|
|
181
|
-
if "
|
|
182
|
-
|
|
193
|
+
if "cuda_max_memory" in trainer.step_info:
|
|
194
|
+
cuda_max_memory = max(stat["cuda_max_memory"] for stat in gathered)
|
|
183
195
|
|
|
184
196
|
self.callback(
|
|
185
197
|
trainer,
|
|
@@ -188,8 +200,8 @@ class StatsHook(BaseHook):
|
|
|
188
200
|
grad_norm=grad_norm.item() if grad_norm is not None else None,
|
|
189
201
|
step_time=step_time,
|
|
190
202
|
data_time=data_time,
|
|
191
|
-
|
|
192
|
-
|
|
203
|
+
non_finite_grad_retries=non_finite_grad_retries,
|
|
204
|
+
cuda_max_memory=cuda_max_memory,
|
|
193
205
|
records=records,
|
|
194
206
|
param_groups=self.param_groups,
|
|
195
207
|
),
|
|
@@ -277,8 +289,8 @@ class ProgressHook(StatsHook):
|
|
|
277
289
|
+ f" step {stats.step_time:.4f}{'s' if self.show_units else ''} data {stats.data_time:.4f}{'s' if self.show_units else ''}"
|
|
278
290
|
+ (f" eta {eta}" if eta is not None else "")
|
|
279
291
|
+ (
|
|
280
|
-
f" mem {stats.
|
|
281
|
-
if stats.
|
|
292
|
+
f" mem {stats.cuda_max_memory:#.3g}{'GiB' if self.show_units else ''}"
|
|
293
|
+
if stats.cuda_max_memory is not None
|
|
282
294
|
else ""
|
|
283
295
|
)
|
|
284
296
|
+ f" loss {stats.loss:.4f}"
|
|
@@ -333,7 +345,7 @@ class CheckpointingHook(BaseHook):
|
|
|
333
345
|
| None = None, # save and keep checkpoints at these steps
|
|
334
346
|
path: Path | str = "checkpoints",
|
|
335
347
|
load: Path | str | Literal["latest"] | None = "latest",
|
|
336
|
-
exit_signals: list[signal.Signals] | signal.Signals = None,
|
|
348
|
+
exit_signals: list[signal.Signals] | signal.Signals | None = None,
|
|
337
349
|
exit_code: int | Literal["128+signal"] = "128+signal",
|
|
338
350
|
exit_wait: timedelta | float = 0.0,
|
|
339
351
|
):
|
|
@@ -551,39 +563,54 @@ class CheckpointingHook(BaseHook):
|
|
|
551
563
|
return False
|
|
552
564
|
|
|
553
565
|
|
|
554
|
-
class
|
|
566
|
+
class CUDAMaxMemoryHook(BaseHook):
|
|
555
567
|
"""Record peak CUDA memory per step into ``trainer.step_info``."""
|
|
556
568
|
|
|
557
569
|
def on_before_step(self, trainer: BaseTrainer):
|
|
558
570
|
torch.cuda.reset_peak_memory_stats(trainer.device)
|
|
559
571
|
|
|
560
572
|
def on_after_step(self, trainer: BaseTrainer):
|
|
561
|
-
trainer.step_info["
|
|
573
|
+
trainer.step_info["cuda_max_memory"] = torch.cuda.max_memory_allocated(
|
|
562
574
|
trainer.device
|
|
563
575
|
) / (1024**3) # GiB
|
|
564
576
|
|
|
565
577
|
|
|
566
|
-
class
|
|
578
|
+
class EMAHook(BaseHook):
|
|
567
579
|
"""Maintain an exponential moving average of model weights.
|
|
568
580
|
|
|
569
581
|
Args:
|
|
570
582
|
decay: EMA decay rate.
|
|
583
|
+
use_buffers: Whether to include model buffers in the EMA.
|
|
571
584
|
"""
|
|
572
585
|
|
|
573
|
-
def __init__(self, decay: float):
|
|
586
|
+
def __init__(self, decay: float = 0.999, use_buffers: bool = False):
|
|
574
587
|
self.decay = decay
|
|
588
|
+
self.use_buffers = use_buffers
|
|
575
589
|
|
|
576
590
|
def on_before_train(self, trainer: BaseTrainer):
|
|
577
591
|
trainer.logger.info("=> Creating EMA model ...")
|
|
578
592
|
# Note that AveragedModel does not seem to support FSDP. It will crash here.
|
|
579
|
-
self.ema_model = AveragedModel(
|
|
593
|
+
self.ema_model = AveragedModel(
|
|
594
|
+
trainer.model,
|
|
595
|
+
multi_avg_fn=get_ema_multi_avg_fn(self.decay),
|
|
596
|
+
use_buffers=self.use_buffers,
|
|
597
|
+
)
|
|
598
|
+
# TODO: could be useful to implement a decay warmup via custom avg_fn
|
|
580
599
|
|
|
581
600
|
def on_after_step(self, trainer: BaseTrainer):
|
|
582
601
|
self.ema_model.update_parameters(trainer.model)
|
|
583
602
|
|
|
584
603
|
def on_load_state_dict(self, trainer: BaseTrainer, state_dict: dict):
|
|
585
604
|
trainer.logger.info("=> Loading EMA model state ...")
|
|
586
|
-
set_model_state_dict(
|
|
605
|
+
incompatible_keys = set_model_state_dict(
|
|
606
|
+
self.ema_model, state_dict["ema_model"]
|
|
607
|
+
)
|
|
608
|
+
# This currently doesn't do anything because strict=True is implicit.
|
|
609
|
+
log_state_dict_incompatible_keys(
|
|
610
|
+
trainer.logger,
|
|
611
|
+
incompatible_keys.missing_keys,
|
|
612
|
+
incompatible_keys.unexpected_keys,
|
|
613
|
+
)
|
|
587
614
|
|
|
588
615
|
def on_state_dict(self, trainer: BaseTrainer, state_dict: dict):
|
|
589
616
|
# Note: sadly, we need to keep the AveragedModel wrapper, to save its n_averaged buffer
|
|
@@ -653,9 +680,18 @@ class WandbHook(BaseHook):
|
|
|
653
680
|
trainer.logger.debug(f"Dry run log. Would log: {data}")
|
|
654
681
|
|
|
655
682
|
def on_log_images(self, trainer: BaseTrainer, records: dict, dry_run: bool = False):
|
|
683
|
+
"""Note that the final component of each flattened key becomes the image caption,
|
|
684
|
+
and the remaining components become the W&B key. For example,
|
|
685
|
+
``foo/bar/name1`` and ``foo/bar/name2`` are logged as two images under
|
|
686
|
+
``foo/bar``, so W&B displays them together in one panel.
|
|
687
|
+
|
|
688
|
+
The list is replaced rather than extended when the same W&B key is logged
|
|
689
|
+
again at the same step. Callers should therefore collect all images for a
|
|
690
|
+
panel and pass them in a single call per step.
|
|
691
|
+
"""
|
|
656
692
|
if _dist_rank() == 0:
|
|
657
693
|
wandb_data = {}
|
|
658
|
-
for k, img in flatten_nested_dict(
|
|
694
|
+
for k, img in flatten_nested_dict(records).items():
|
|
659
695
|
file_type = self.image_format(k)
|
|
660
696
|
wandb_data.setdefault("/".join(k[:-1]), []).append(
|
|
661
697
|
wandb.Image(
|
|
@@ -673,7 +709,7 @@ class WandbHook(BaseHook):
|
|
|
673
709
|
trainer.logger.debug(f"Dry run log. Would log: {wandb_data}")
|
|
674
710
|
|
|
675
711
|
@staticmethod
|
|
676
|
-
def _ensure_jpeg_compatible(img: PILImage, bg_color: tuple = (255, 255, 255)):
|
|
712
|
+
def _ensure_jpeg_compatible(img: "PILImage", bg_color: tuple = (255, 255, 255)):
|
|
677
713
|
if img.mode in ("RGB", "L"):
|
|
678
714
|
return img
|
|
679
715
|
elif img.mode in ("RGBA", "LA"):
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
import time
|
|
3
3
|
import warnings
|
|
4
|
+
from collections.abc import Callable, Iterable, Iterator
|
|
4
5
|
from contextlib import closing, nullcontext
|
|
5
6
|
from logging import Logger
|
|
6
7
|
from numbers import Number
|
|
@@ -8,21 +9,20 @@ from pathlib import Path
|
|
|
8
9
|
from typing import (
|
|
9
10
|
TYPE_CHECKING,
|
|
10
11
|
Any,
|
|
11
|
-
Callable,
|
|
12
|
-
Iterable,
|
|
13
|
-
Iterator,
|
|
14
12
|
TypeAlias,
|
|
15
13
|
Union,
|
|
16
14
|
)
|
|
17
15
|
|
|
18
16
|
import torch
|
|
19
|
-
|
|
17
|
+
from torch import nn
|
|
20
18
|
from torch.distributed.checkpoint.state_dict import (
|
|
21
19
|
StateDictOptions,
|
|
22
20
|
get_state_dict,
|
|
23
21
|
set_state_dict,
|
|
24
22
|
)
|
|
25
23
|
|
|
24
|
+
from .utils import log_state_dict_incompatible_keys
|
|
25
|
+
|
|
26
26
|
if TYPE_CHECKING:
|
|
27
27
|
from .hooks import BaseHook
|
|
28
28
|
|
|
@@ -154,13 +154,18 @@ class BaseTrainer:
|
|
|
154
154
|
self.grad_scaler.load_state_dict(training_state["grad_scaler"])
|
|
155
155
|
|
|
156
156
|
self.logger.info("=> Loading model and optimizer state ...")
|
|
157
|
-
set_state_dict(
|
|
157
|
+
incompatible_keys = set_state_dict(
|
|
158
158
|
self.model,
|
|
159
159
|
self.optimizer,
|
|
160
160
|
model_state_dict=state_dict["model"],
|
|
161
161
|
optim_state_dict=training_state["optimizer"],
|
|
162
162
|
options=self.state_dict_options,
|
|
163
163
|
)
|
|
164
|
+
log_state_dict_incompatible_keys(
|
|
165
|
+
self.logger,
|
|
166
|
+
incompatible_keys.missing_keys,
|
|
167
|
+
incompatible_keys.unexpected_keys,
|
|
168
|
+
)
|
|
164
169
|
|
|
165
170
|
self.logger.info("=> Loading hook states ...")
|
|
166
171
|
for h in self.hooks:
|
|
@@ -203,8 +208,8 @@ class BaseTrainer:
|
|
|
203
208
|
|
|
204
209
|
reset_step_info()
|
|
205
210
|
self.step_info["data_time"] = []
|
|
206
|
-
|
|
207
|
-
self.step_info["
|
|
211
|
+
non_finite_grad_retries = 0
|
|
212
|
+
self.step_info["non_finite_grad_retries"] = non_finite_grad_retries
|
|
208
213
|
i_acc = 0
|
|
209
214
|
while i_acc < self.gradient_accumulation_steps:
|
|
210
215
|
is_accumulating = i_acc < self.gradient_accumulation_steps - 1
|
|
@@ -278,15 +283,14 @@ class BaseTrainer:
|
|
|
278
283
|
)
|
|
279
284
|
):
|
|
280
285
|
if self.max_non_finite_grad_retries is None or (
|
|
281
|
-
|
|
282
|
-
< self.max_non_finite_grad_retries
|
|
286
|
+
non_finite_grad_retries < self.max_non_finite_grad_retries
|
|
283
287
|
):
|
|
284
|
-
|
|
285
|
-
self.step_info["
|
|
286
|
-
|
|
288
|
+
non_finite_grad_retries += 1
|
|
289
|
+
self.step_info["non_finite_grad_retries"] = (
|
|
290
|
+
non_finite_grad_retries
|
|
287
291
|
)
|
|
288
292
|
self.logger.warning(
|
|
289
|
-
f"Gradient is non-finite. Retrying step {self.step} (retry {
|
|
293
|
+
f"Gradient is non-finite. Retrying step {self.step} (retry {non_finite_grad_retries}"
|
|
290
294
|
+ (
|
|
291
295
|
f"/{self.max_non_finite_grad_retries})."
|
|
292
296
|
if self.max_non_finite_grad_retries is not None
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
# modified from: https://github.com/microsoft/MoGe/blob/6b8b43db567ca4b08615c39b42cffd6c76cada29/moge/utils/tools.py
|
|
2
2
|
|
|
3
3
|
import math
|
|
4
|
-
from
|
|
4
|
+
from collections.abc import Generator, MutableMapping
|
|
5
|
+
from logging import Logger
|
|
6
|
+
from typing import Any
|
|
5
7
|
|
|
6
8
|
|
|
7
9
|
def traverse_nested_dict_keys(
|
|
@@ -50,7 +52,7 @@ def key_average(list_of_dicts: list, exclude_nan: bool = False) -> dict[str, Any
|
|
|
50
52
|
|
|
51
53
|
|
|
52
54
|
def flatten_nested_dict(
|
|
53
|
-
d: dict[str, Any], parent_key: tuple[str, ...] = None
|
|
55
|
+
d: dict[str, Any], parent_key: tuple[str, ...] | None = None
|
|
54
56
|
) -> dict[tuple[str, ...], Any]:
|
|
55
57
|
"""
|
|
56
58
|
Flattens a nested dictionary into a single-level dictionary, with keys as tuples.
|
|
@@ -65,3 +67,20 @@ def flatten_nested_dict(
|
|
|
65
67
|
else:
|
|
66
68
|
items.append((new_key, v))
|
|
67
69
|
return dict(items)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def log_state_dict_incompatible_keys(
|
|
73
|
+
logger: Logger, missing_keys: list[str], unexpected_keys: list[str]
|
|
74
|
+
) -> None:
|
|
75
|
+
if missing_keys:
|
|
76
|
+
logger.warning(
|
|
77
|
+
"Missing keys in state_dict: {}.".format(
|
|
78
|
+
", ".join(f'"{key}"' for key in missing_keys)
|
|
79
|
+
)
|
|
80
|
+
)
|
|
81
|
+
if unexpected_keys:
|
|
82
|
+
logger.warning(
|
|
83
|
+
"Unexpected keys in state_dict: {}.".format(
|
|
84
|
+
", ".join(f'"{key}"' for key in unexpected_keys)
|
|
85
|
+
)
|
|
86
|
+
)
|
|
File without changes
|
|
File without changes
|