Skip to main content

EzTrain

A small, framework-agnostic training-loop library. The trainer companion to EzConfy.

Every ML project rewrites the same trainer: a loop over epochs or updates, periodic evaluation, callbacks, early stopping, checkpoint scheduling, metric logging, graceful Ctrl+C. EzTrain extracts exactly that skeleton and nothing else:

  • Core never imports torch or jax. It abstracts the iteration, not the tensor: an iteration is anything that returns a Mapping[str, Any] of metrics — an epoch over a dataloader, an RL collect→update cycle, a world-model phase.
  • Run lifecycle built in. A run id (<name>_<timestamp>) is shared between checkpoints and the experiment tracker, with three resume modes: FRESH (from scratch), CONTINUE (same run: weights + optimizer + tracker), FORK (new run seeded with old weights).
  • Composition over inheritance where it matters: Logger, Metric and Checkpointer are Protocols you inject; Callback hooks are dispatched dynamically by name so trainers can invent their own hooks.
  • No hidden defaults. No optimizer/scheduler construction, no concrete metrics, no config system (that's EzConfy's job), no distributed magic.

Core dependencies: tqdm, loguru. That's it.

Install

uv add eztrain                  # core
uv add "eztrain[wandb]"         # + Weights & Biases logger
uv add "eztrain[tensorboard]"   # + TensorBoard logger

Supervised (epoch-based)

from eztrain import EpochTrainer, EarlyStopping, MetricCollection, WandbLogger

class MyTrainer(EpochTrainer):
    def __init__(self, *, model, optimizer, **kwargs):
        super().__init__(**kwargs)
        self.model, self.optimizer = model, optimizer

    def train_step(self, batch):
        loss = ...                      # your forward/backward/step
        return {"loss": loss.item()}    # averaged over the epoch -> "train/loss"

    def eval_step(self, batch):
        preds, loss = ...
        self.metrics.update(preds, batch.y)   # your metrics, your signature
        return {"loss": loss.item()}          # -> "val/loss"

trainer = MyTrainer(
    model=model,
    optimizer=optimizer,
    train_loader=train_loader,
    val_loader=val_loader,
    metrics=MetricCollection([MyAccuracy()]),
    max_iterations=100,                 # epochs
    eval_freq=1,
    callbacks=[EarlyStopping(monitor="val/loss", patience=10)],
    logger=WandbLogger(project="my-project"),
    run_name="baseline",
)
trainer.fit()

RL (update-based)

Subclass Trainer directly — one iteration is one update:

from eztrain import Trainer

class PPOTrainer(Trainer):
    def __init__(self, *, env, agent, num_steps, **kwargs):
        super().__init__(unit="update", **kwargs)
        self.env, self.agent, self.num_steps = env, agent, num_steps
        self.obs = env.reset()

    def train_iteration(self, update):
        segment, self.obs = collect_rollouts(self.env, self.agent, self.num_steps, self.obs)
        advantages, returns = compute_gae(segment)
        return self.agent.learn_from(segment, advantages, returns)

    def evaluate(self):
        return {"eval/reward": evaluate(self.agent)}

    def log_step(self):                       # log by env steps, not updates
        return self.iteration * self.num_steps * self.env.num_envs

Resume and fork

Trainer(run_name="exp-1")                                          # FRESH
Trainer(run_name="exp-1", resume_from="exp-1_20260530_051406")     # CONTINUE
Trainer(run_name="exp-2", resume_from="exp-1_20260530_051406")     # FORK

Pass resume_from to start from an existing run's checkpoint:

  • Same run_name -> the run continues;
  • a new run_name -> it forks into a new run.

Callbacks

from eztrain import Callback

class MyCallback(Callback):
    def on_train_start(self, trainer): ...
    def on_iteration_end(self, trainer, iteration): ...
    def on_eval_end(self, trainer): ...          # trainer.history has fresh metrics
    def on_train_end(self, trainer): ...         # always runs, even on Ctrl+C

The stable surface callbacks can rely on: trainer.run, trainer.iteration, trainer.start_iteration (writable), trainer.history, trainer.should_stop (writable), trainer.checkpointables, trainer.logger, trainer.call_hook. Custom hooks compose freely: self.call_hook("on_rollout_end", num_steps=n) inside your trainer reaches any callback that defines it.

With EzConfy

Every public class takes plain keyword arguments, so it can be instantiated straight from YAML:

# schema.yaml
types:
  Callback: eztrain.callbacks:Callback
  Logger: eztrain.loggers:Logger
schema:
  trainer:
    callbacks: list[Callback]
    logger: Logger
# config.yaml
trainer:
  logger:
    _target_type_: eztrain.loggers:WandbLogger
    _init_args_: { project: my-project, entity: me }
  callbacks:
    - _target_type_: eztrain.callbacks:EarlyStopping
      _init_args_: { monitor: val/loss, mode: min, patience: 10 }

What's deliberately not here

Concrete train steps, models, losses, optimizers/schedulers (inject your own — a library default becomes a cage), metrics implementations, config loading, distributed training. Keep those in your project; eztrain only owns the loop around them.

Metadata

Release files for eztrain 0.1.2

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for eztrain 0.1.2
File Size Uploaded
eztrain-0.1.2.tar.gz 130.4 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for eztrain 0.1.2
File Interpreter ABI Platform
eztrain-0.1.2-py3-none-any.whl Python 3 none any Details

Total release size: 145.2 kB

Release files / eztrain-0.1.2.tar.gz

Download URL eztrain-0.1.2.tar.gz
Size 130.4 kB
Tags Source
SHA-256 checksum
How to use checksums
5d5f995b45b8f06781c190a9e2118b86ba7711bb5b9e30ceb16891459e970c44
BLAKE2b-256 checksum
How to use checksums
3490e74c7beded1227665dd2ec562426e6021710e34f94d82246e922b608c4a3
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 6, 2026.

Transparency log

Release files / eztrain-0.1.2-py3-none-any.whl

Download URL eztrain-0.1.2-py3-none-any.whl
Size 14.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
8af9f0337bec183ed287bf2c5a9c8dd34f01519fe6450442aa1babd99aef9c2c
BLAKE2b-256 checksum
How to use checksums
8964495d9ed405431f8d2bb51f9f67460747dcfc27a4bef57717d3f168f959ec
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 6, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.1.2 This release

2 release files

0.1.1

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page