Skip to content

RunnerConfig

danling.runners.RunnerConfig

Bases: Config

Top-level configuration for DanLing runners.

RunnerConfig owns runner lifecycle settings, restore sources, and typed subsystem sections. Detailed subsystem field semantics live on the matching subconfig class, for example OptimizerConfig, SchedulerConfig, ScoreConfig, CheckpointConfig, WorkspaceConfig, DataloaderConfig, FsdpConfig, and ParallelConfig.

RunnerConfig inherits from Config and provides attribute-style access to nested values:

Python
1
2
3
4
config = RunnerConfig()
config.workspace.experiment = "resnet50"
config.dataloader.batch_size = 32
config["optim"] = {"type": "adamw", "lr": 1e-3}

Command-line integration is built in:

Python
1
2
config = MyConfig()
config.parse()  # Parse CLI args, e.g., --epochs 20 --optim.lr 0.01
Core attributes

seed, deterministic: Reproducibility controls. steps, epochs: Mutually exclusive training boundaries. accum_steps: Number of micro-batches per optimizer step. train_splits, evaluate_splits: Optional split selection overrides. precision: Optional autocast precision. max_grad_value, max_grad_norm, skip_nonfinite_loss, skip_nonfinite_grad: Gradient safety controls. checkpoint, resume, pretrained: Restore sources. Source priority is checkpoint > resume > pretrained. deepspeed: Optional raw DeepSpeed config mapping.

Nested sections

optim, sched, score, workspace, logging, tensorboard, wandb, mlflow, ft, compile, dist, gc, profiling, heartbeat, ckpt, dataloader, performance, activation_checkpoint, fsdp, and parallel.

Examples:

Basic usage:

Python
1
2
3
4
5
6
7
8
9
# Create a config
config = RunnerConfig()
config.workspace.experiment = "resnet18"
config.dataloader.batch_size = 32
config["optim"] = {"type": "adamw", "lr": 1e-3}
config.epochs = 10

# Use in a runner
runner = Runner(config)

Custom config class with typed attributes:

Python
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
class TrainingConfig(RunnerConfig):
    # Type annotations provide auto-completion and validation
    model: str = "resnet18"
    epochs: int = 100
    precision: str = "bf16"

    def __init__(self):
        super().__init__()
        self.dataloader.batch_size = 32
        self["optim"] = {"type": "adamw", "lr": 1e-3}

    def post(self):
        # Called after parsing CLI args
        super().post()
        # Create derived settings
        lr = self.get("optim.lr")
        self.workspace.experiment = f"{self.model}_bs{self.dataloader.batch_size}_lr{lr}"

Command-line integration:

Bash
1
2
# Override config settings via CLI
python train.py --epochs 50 --dataloader.batch_size 64 --optim.lr 0.0005

Note

Always store all parameters needed to reproduce a run in the RunnerConfig. The RunnerConfig is automatically saved with checkpoints, enabling exact resumption.

See Also
Source code in danling/runners/config.py
Python
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
class RunnerConfig(chanfig.Config):  # pylint: disable=too-many-instance-attributes
    r"""
    Top-level configuration for DanLing runners.

    `RunnerConfig` owns runner lifecycle settings, restore sources, and typed
    subsystem sections. Detailed subsystem field semantics live on the matching
    subconfig class, for example `OptimizerConfig`, `SchedulerConfig`,
    `ScoreConfig`, `CheckpointConfig`, `WorkspaceConfig`, `DataloaderConfig`,
    `FsdpConfig`, and `ParallelConfig`.

    `RunnerConfig` inherits from [`Config`][chanfig.Config] and provides
    attribute-style access to nested values:

    ```python
    config = RunnerConfig()
    config.workspace.experiment = "resnet50"
    config.dataloader.batch_size = 32
    config["optim"] = {"type": "adamw", "lr": 1e-3}
    ```

    Command-line integration is built in:

    ```python
    config = MyConfig()
    config.parse()  # Parse CLI args, e.g., --epochs 20 --optim.lr 0.01
    ```

    Core attributes:
        stack: Runner stack selector used by `danling.runners.Runner`. Supported
            values include `"auto"`, `"ddp"`/`"torch"`, `"graph"`,
            `"deepspeed"`/`"ds"`, and `"parallel"`.
        seed, deterministic: Reproducibility controls.
        steps, epochs: Mutually exclusive training boundaries.
        accum_steps: Number of micro-batches per optimizer step.
        train_splits, evaluate_splits: Optional split selection overrides.
        precision: Optional autocast precision.
        max_grad_value, max_grad_norm, skip_nonfinite_loss, skip_nonfinite_grad:
            Gradient safety controls.
        checkpoint, resume, pretrained: Restore sources. Source priority is
            `checkpoint` > `resume` > `pretrained`.
        deepspeed: Optional raw DeepSpeed config mapping.

    Nested sections:
        `optim`, `sched`, `score`, `workspace`, `logging`, `tensorboard`,
        `wandb`, `mlflow`, `ft`, `compile`, `dist`, `gc`, `profiling`, `heartbeat`,
        `ckpt`, `dataloader`, `performance`, `activation_checkpoint`,
        `fsdp`, and `parallel`.

    Examples:
        Basic usage:
        ```python
        # Create a config
        config = RunnerConfig()
        config.workspace.experiment = "resnet18"
        config.dataloader.batch_size = 32
        config["optim"] = {"type": "adamw", "lr": 1e-3}
        config.epochs = 10

        # Use in a runner
        runner = Runner(config)
        ```

        Custom config class with typed attributes:
        ```python
        class TrainingConfig(RunnerConfig):
            # Type annotations provide auto-completion and validation
            model: str = "resnet18"
            epochs: int = 100
            precision: str = "bf16"

            def __init__(self):
                super().__init__()
                self.dataloader.batch_size = 32
                self["optim"] = {"type": "adamw", "lr": 1e-3}

            def post(self):
                # Called after parsing CLI args
                super().post()
                # Create derived settings
                lr = self.get("optim.lr")
                self.workspace.experiment = f"{self.model}_bs{self.dataloader.batch_size}_lr{lr}"
        ```

        Command-line integration:
        ```bash
        # Override config settings via CLI
        python train.py --epochs 50 --dataloader.batch_size 64 --optim.lr 0.0005
        ```

    Note:
        Always store all parameters needed to reproduce a run in the RunnerConfig.
        The RunnerConfig is automatically saved with checkpoints, enabling exact resumption.

    See Also:
        - [`Runner`][danling.runners.Runner]: Main runner class that uses this config.
        - [`chanfig.Config`](https://github.com/ultmaster/chanfig): Base config implementation.
    """

    # Defining mapping equality disables inherited hashing for this mutable class.
    __eq__ = dict.__eq__

    stack: str = "auto"
    name: Optional[str] = None

    seed: Optional[int] = 1016
    deterministic: bool = False

    steps: Optional[int] = None
    epochs: Optional[int] = None
    accum_steps: int = 1
    train_splits: Union[Sequence[str], str, None] = None
    evaluate_splits: Union[Sequence[str], str, None] = None
    precision: Optional[str] = None
    max_grad_value: Optional[float] = None
    max_grad_norm: Optional[float] = None
    skip_nonfinite_loss: bool = False
    skip_nonfinite_grad: bool = False

    checkpoint: Optional[str] = None
    resume: bool = False
    pretrained: Optional[str] = None

    optim: Optional[OptimizerConfig]
    sched: Optional[SchedulerConfig]
    fp8: Fp8Config = Fp8Config()
    deepspeed: Optional[Mapping[str, Any]] = None

    score: ScoreConfig = ScoreConfig()
    workspace: WorkspaceConfig = WorkspaceConfig()
    logging: LoggingConfig = LoggingConfig()
    tensorboard: TensorboardConfig = TensorboardConfig()
    wandb: WandbConfig = WandbConfig()
    mlflow: MlflowConfig = MlflowConfig()
    ft: FaultToleranceConfig = FaultToleranceConfig()

    compile: CompileConfig = CompileConfig()
    dist: DistributedConfig = DistributedConfig()
    ddp: DdpConfig = DdpConfig()
    gc: GcConfig = GcConfig()
    profiling: ProfilingConfig = ProfilingConfig()
    heartbeat: HeartbeatConfig = HeartbeatConfig()
    ckpt: CheckpointConfig = CheckpointConfig()
    dataloader: DataloaderConfig = DataloaderConfig()
    performance: PerformanceConfig = PerformanceConfig()
    activation_checkpoint: ActivationCheckpointConfig = ActivationCheckpointConfig()
    fsdp: FsdpConfig = FsdpConfig()
    parallel: ParallelConfig = ParallelConfig()

    def __post_init__(self, *args, **kwargs) -> None:
        super().__post_init__(*args, **kwargs)
        self.validate()

    def post(self) -> None:
        super().post()
        self.validate()

    def validate(self) -> None:
        if self.steps is not None and self.epochs is not None:
            raise ValueError("`steps` and `epochs` are mutually exclusive; set only one training boundary")

    @staticmethod
    def _semantic_section(section: Any, defaults: chanfig.Config) -> chanfig.NestedDict:
        if not isinstance(section, Mapping):
            return chanfig.NestedDict()
        return defaults.difference(section)

    def canonical(self) -> chanfig.NestedDict:
        canonical = chanfig.NestedDict(self.dict())
        stack = normalize_stack_name(canonical.get("stack", "auto"))
        canonical["stack"] = stack
        for key in NON_SEMANTIC_CONFIG_KEYS:
            canonical.pop(key, None)

        ckpt_backend = canonical.get("ckpt.backend")
        canonical.pop("ckpt", None)
        if ckpt_backend is not None:
            ckpt_backend = str(ckpt_backend).strip().lower()
            if ckpt_backend != "auto":
                canonical["ckpt"] = chanfig.NestedDict({"backend": ckpt_backend})

        for key, defaults in DEFAULT_FILTERED_CONFIG_SECTIONS:
            semantic_section = self._semantic_section(canonical.get(key), defaults)
            if semantic_section:
                canonical[key] = semantic_section
            else:
                canonical.pop(key, None)

        if stack != "parallel":
            canonical.pop("fsdp", None)
            canonical.pop("parallel", None)
        return canonical

    def fingerprint(self) -> int:
        """Return an unsigned 64-bit fingerprint of the current canonical config.

        Hash the UTF-8 canonical YAML with SHA-1 and interpret its first eight
        bytes as a big-endian integer. Runtime-only settings are excluded by
        ``canonical()``. The configuration remains mutable and unhashable;
        this method recomputes the fingerprint on each call.

        Returns:
            int: A value in ``[0, 2**64)`` derived from the current canonical YAML.
        """
        digest = hashlib.sha1(self.canonical().yamls().encode("utf-8")).digest()
        return int.from_bytes(digest[:8], byteorder="big", signed=False)

fingerprint

Python
fingerprint() -> int

Return an unsigned 64-bit fingerprint of the current canonical config.

Hash the UTF-8 canonical YAML with SHA-1 and interpret its first eight bytes as a big-endian integer. Runtime-only settings are excluded by canonical(). The configuration remains mutable and unhashable; this method recomputes the fingerprint on each call.

Returns:

Name Type Description
int int

A value in [0, 2**64) derived from the current canonical YAML.

Source code in danling/runners/config.py
Python
747
748
749
750
751
752
753
754
755
756
757
758
759
def fingerprint(self) -> int:
    """Return an unsigned 64-bit fingerprint of the current canonical config.

    Hash the UTF-8 canonical YAML with SHA-1 and interpret its first eight
    bytes as a big-endian integer. Runtime-only settings are excluded by
    ``canonical()``. The configuration remains mutable and unhashable;
    this method recomputes the fingerprint on each call.

    Returns:
        int: A value in ``[0, 2**64)`` derived from the current canonical YAML.
    """
    digest = hashlib.sha1(self.canonical().yamls().encode("utf-8")).digest()
    return int.from_bytes(digest[:8], byteorder="big", signed=False)