Source code for kempnerforge.config.data
"""Data pipeline configuration."""
from __future__ import annotations
from dataclasses import dataclass, field
TOKEN_DTYPES = ("uint16", "uint32")
[docs]
def check_token_dtype(value: str, key: str) -> None:
"""Reject a ``token_dtype`` that is neither ``""`` nor one of ``TOKEN_DTYPES``."""
if value != "" and value not in TOKEN_DTYPES:
raise ValueError(
f"{key} must be 'uint16' or 'uint32', the integer type the raw .bin token "
f"shards were written with, or empty to take it from the metadata.yaml next "
f"to the shards; got {value!r}"
)
[docs]
@dataclass
class DatasetSource:
"""A single data source in a multi-dataset mixture.
Either ``path`` (pre-tokenized) or ``hf_name`` (HuggingFace) must be set.
``weight`` controls the relative sampling probability. ``token_dtype``
overrides ``data.token_dtype`` for the ``.bin`` shards under ``path``.
"""
path: str = ""
weight: float = 1.0
name: str = ""
hf_name: str = ""
hf_config: str = ""
token_dtype: str = ""
def __post_init__(self) -> None:
if self.weight <= 0:
raise ValueError(f"Dataset weight must be positive, got {self.weight}")
check_token_dtype(self.token_dtype, "[[data.datasets]] token_dtype")
[docs]
@dataclass
class TrainingPhase:
"""A training phase with custom dataset weights and LR scaling.
Used for data annealing: at ``start_step``, the mixture sampler switches
to ``dataset_weights`` and the learning rate is multiplied by ``lr_scale``.
"""
start_step: int = 0
dataset_weights: dict[str, float] = field(default_factory=dict)
lr_scale: float = 1.0
def __post_init__(self) -> None:
if self.start_step < 0:
raise ValueError("TrainingPhase.start_step must be non-negative")
if self.lr_scale <= 0:
raise ValueError("TrainingPhase.lr_scale must be positive")
for name, w in self.dataset_weights.items():
if w < 0:
raise ValueError(f"TrainingPhase.dataset_weights['{name}'] must be non-negative")
[docs]
@dataclass
class DataConfig:
"""Data pipeline settings.
``token_dtype`` is the integer type of the raw ``.bin`` token shards,
``"uint16"`` or ``"uint32"``, for ``dataset_path`` and every ``datasets``
source that does not set its own. Left empty, the ``metadata.yaml`` next to
the shards decides, as ``MemoryMappedDataset`` describes. ``.npy`` shards
carry their dtype in their header and ignore it.
"""
dataset_path: str = ""
file_pattern: str = "*.npy"
token_dtype: str = ""
tokenizer_path: str = ""
num_workers: int = 4
pin_memory: bool = True
prefetch_factor: int = 2
hf_dataset_name: str | None = None
hf_dataset_config: str | None = None
hf_dataset_split: str = "train"
hf_dataset_text_field: str = "text"
hf_dataset_image_field: str = "image"
hf_dataset_prompt_field: str = ""
hf_image_size: int = 224
hf_streaming: bool = False
pack_sequences: bool = False
datasets: list[DatasetSource] = field(default_factory=list)
mix_temperature: float = 1.0
phases: list[TrainingPhase] = field(default_factory=list)
anneal_start_step: int = 0
anneal_weights: dict[str, float] = field(default_factory=dict)
def __post_init__(self) -> None:
if self.num_workers < 0:
raise ValueError("num_workers must be non-negative")
if self.prefetch_factor < 1:
raise ValueError("prefetch_factor must be >= 1")
if self.mix_temperature <= 0:
raise ValueError("mix_temperature must be positive")
check_token_dtype(self.token_dtype, "data.token_dtype")
for src in self.datasets:
if not src.path and not src.hf_name:
raise ValueError(f"DatasetSource '{src.name}' must have either path or hf_name")
if self.phases:
steps = [p.start_step for p in self.phases]
if steps != sorted(steps) or len(steps) != len(set(steps)):
raise ValueError(
"data.phases start_steps must be strictly monotonically increasing"
)
if self.anneal_start_step < 0:
raise ValueError("anneal_start_step must be non-negative")
if self.phases and self.anneal_start_step > 0:
raise ValueError(
"Cannot use both data.phases and data.anneal_start_step — "
"use phases for multi-phase scheduling"
)