trainloop 0.9.0__tar.gz → 0.11.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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: trainloop
3
- Version: 0.9.0
3
+ Version: 0.11.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>
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "trainloop"
3
- version = "0.9.0"
3
+ version = "0.11.0"
4
4
  description = "Minimal PyTorch training loop with hooks and checkpointing."
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.10"
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "trainloop"
3
- version = "0.9.0"
3
+ version = "0.11.0"
4
4
  description = "Minimal PyTorch training loop with hooks and checkpointing."
5
5
  readme = "README.md"
6
6
  authors = [
@@ -83,7 +83,9 @@ class BaseHook:
83
83
  def on_after_train(self, trainer: BaseTrainer):
84
84
  pass
85
85
 
86
- def on_log(self, trainer: BaseTrainer, records: dict, dry_run: bool = False):
86
+ def on_log_scalars(
87
+ self, trainer: BaseTrainer, records: dict, dry_run: bool = False
88
+ ):
87
89
  pass
88
90
 
89
91
  def on_log_images(self, trainer: BaseTrainer, records: dict, dry_run: bool = False):
@@ -588,14 +590,18 @@ class EMAHook(BaseHook):
588
590
  Args:
589
591
  decay: EMA decay rate.
590
592
  use_buffers: Whether to include model buffers in the EMA.
593
+ name: Name used for the EMA state in the trainer state dict.
591
594
  """
592
595
 
593
- def __init__(self, decay: float = 0.999, use_buffers: bool = False):
596
+ def __init__(
597
+ self, decay: float = 0.999, use_buffers: bool = False, name: str = "ema"
598
+ ):
594
599
  self.decay = decay
595
600
  self.use_buffers = use_buffers
601
+ self.name = name
596
602
 
597
603
  def on_before_train(self, trainer: BaseTrainer):
598
- trainer.logger.info("=> Creating EMA model ...")
604
+ trainer.logger.info(f"=> Creating EMA model {self.name!r} ...")
599
605
  # Note that AveragedModel does not seem to support FSDP. It will crash here.
600
606
  self.ema_model = AveragedModel(
601
607
  trainer.model,
@@ -608,10 +614,8 @@ class EMAHook(BaseHook):
608
614
  self.ema_model.update_parameters(trainer.model)
609
615
 
610
616
  def on_load_state_dict(self, trainer: BaseTrainer, state_dict: dict):
611
- trainer.logger.info("=> Loading EMA model state ...")
612
- incompatible_keys = set_model_state_dict(
613
- self.ema_model, state_dict["ema_model"]
614
- )
617
+ trainer.logger.info(f"=> Loading EMA model {self.name!r} state ...")
618
+ incompatible_keys = set_model_state_dict(self.ema_model, state_dict[self.name])
615
619
  # This currently doesn't do anything because strict=True is implicit.
616
620
  log_state_dict_incompatible_keys(
617
621
  trainer.logger,
@@ -620,13 +624,18 @@ class EMAHook(BaseHook):
620
624
  )
621
625
 
622
626
  def on_state_dict(self, trainer: BaseTrainer, state_dict: dict):
627
+ if self.name in state_dict:
628
+ raise ValueError(f"State dict key {self.name!r} already exists")
623
629
  # Note: sadly, we need to keep the AveragedModel wrapper, to save its n_averaged buffer
624
- state_dict["ema_model"] = get_model_state_dict(self.ema_model)
630
+ state_dict[self.name] = get_model_state_dict(self.ema_model)
625
631
 
626
632
 
627
633
  class WandbHook(BaseHook):
628
634
  """Log metrics and images to Weights & Biases (rank 0 only).
629
635
 
636
+ Nested scalar key components are joined with ``/``. Avoid ``/`` within an
637
+ individual component because it is also W&B's panel namespace separator.
638
+
630
639
  Args:
631
640
  project: W&B project name.
632
641
  config: Optional config dict or JSON file path to log.
@@ -678,7 +687,9 @@ class WandbHook(BaseHook):
678
687
  if _dist_rank() == 0:
679
688
  self.wandb.finish()
680
689
 
681
- def on_log(self, trainer: BaseTrainer, records: dict, dry_run: bool = False):
690
+ def on_log_scalars(
691
+ self, trainer: BaseTrainer, records: dict, dry_run: bool = False
692
+ ):
682
693
  if _dist_rank() == 0:
683
694
  data = {"/".join(k): v for k, v in flatten_nested_dict(records).items()}
684
695
  if not dry_run:
@@ -748,6 +759,10 @@ class WandbHook(BaseHook):
748
759
  class TensorBoardHook(BaseHook):
749
760
  """Log metrics and images to TensorBoard (rank 0 only).
750
761
 
762
+ Nested namespace components are joined with ``namespace_separator`` and
763
+ the final name is separated with ``/``. Avoid these separators within
764
+ individual keys because TensorBoard uses them to organize tags.
765
+
751
766
  Args:
752
767
  texts: Optional text values to log when training starts.
753
768
  namespace_separator: Separator for nested tag prefixes; the final TensorBoard
@@ -784,7 +799,9 @@ class TensorBoardHook(BaseHook):
784
799
  self.writer.close()
785
800
  self.writer = None
786
801
 
787
- def on_log(self, trainer: BaseTrainer, records: dict, dry_run: bool = False):
802
+ def on_log_scalars(
803
+ self, trainer: BaseTrainer, records: dict, dry_run: bool = False
804
+ ):
788
805
  if self.writer is None:
789
806
  return
790
807
 
@@ -349,17 +349,22 @@ class BaseTrainer:
349
349
  def unwrapped_model(self):
350
350
  return self.unwrap(self.model)
351
351
 
352
- def log(self, records: dict[str, Any], dry_run: bool = False):
352
+ def log_scalars(self, records: dict[str, Any], dry_run: bool = False):
353
353
  """
354
- Dispatch numeric records to hooks (e.g., trackers or stdout).
354
+ Dispatch nested scalar records to logging hooks.
355
+
356
+ Dictionary keys are treated as path components. Logging hooks may join
357
+ nested components with backend-specific namespace separators, such as
358
+ ``/``. Avoid those separators within individual keys because distinct
359
+ nested paths may otherwise map to the same backend metric name.
355
360
 
356
361
  Args:
357
- records: Nested dict of numeric metrics to log.
362
+ records: Nested dict of scalar metrics to log.
358
363
  dry_run: If True, hooks should avoid side effects and only report intent.
359
364
  """
360
- self.logger.debug("log()")
365
+ self.logger.debug("log_scalars()")
361
366
  for h in self.hooks:
362
- h.on_log(self, records, dry_run=dry_run)
367
+ h.on_log_scalars(self, records, dry_run=dry_run)
363
368
 
364
369
  def log_images(self, records: dict[str, Any], dry_run: bool = False):
365
370
  """
File without changes