Skip to content

Progress Bar

RL Engine's sparse progress bar lives in src/rle/reusable/progress_bar and is exported from rle.reusable. It wraps tqdm.auto.tqdm, but only renders at sparse milestones so notebook cells and cluster logs do not fill with repeated progress updates.

As a reusable component, the progress bar is for training loops, evaluation passes, sweeps, and data preparation jobs in new project repos. It keeps the familiar tqdm feel while avoiding noisy append-only logs.

Quick Start

from rle.reusable import ProgressBar

with ProgressBar(200_000, desc="IQL training", unit="env-step") as progress:
    for batch in batches:
        metrics = train_batch(batch)
        progress.update(
            len(batch),
            postfix={"return_mean": f"{metrics['return_mean']:.3f}"},
        )

The default cadence is every 5% plus final completion. This works well for Marimo/Jupyter notebooks and for scheduler logs inspected through viewlog.

Iterable Helper

from rle.reusable import progress_bar

for episode in progress_bar(episodes, desc="Evaluation", unit="episode"):
    evaluate(episode)

If the iterable has a length, the helper uses it as the progress total. For generators, pass total= or refresh_units= when you want sparse rendering.

Public API

helper purpose
ProgressBar(total, ...) manual progress bar for callback-based loops
progress_bar(iterable, ...) iterable wrapper
update(amount=1, postfix=None) track progress and render only at milestones
set_postfix(**kwargs) store postfix values for the next rendered milestone

Use refresh_percent= for percentage milestones, or refresh_units= when a fixed number of units is clearer.

Copying To Another Project

Copy src/rle/reusable/progress_bar and add tqdm to the new project's dependencies. The component is independent of RL Engine and can be renamed by changing only the import path used by callers.

The defaults are tuned for notebooks and scheduler output: sparse updates, dynamic columns, and a long maxinterval so the monitor thread does not redraw unchanged progress into log files.

Adapting To Your Project

Copy src/rle/reusable/progress_bar and add tqdm to the project dependencies. Then re-export ProgressBar and progress_bar from the project package root so trainers and notebooks do not need to know where the helper lives.

The progress bar is intentionally not tied to RL concepts. In a new project, the main thing to decide is the unit of progress for each long-running block:

  • environment steps for step-based trainers;
  • episodes for episodic evaluation;
  • batches for supervised pretraining or model fitting;
  • candidates or profiles for synthesis/certification work;
  • runs for coarse outer-loop scripts.

When using the reusable experiment runner, the progress total normally comes from AlgorithmSpec.config or ExperimentSuiteConfig.common_config:

from rle.reusable.experiments.models import AlgorithmSpec

AlgorithmSpec(
    name="IPPO",
    enabled_flag="run_ippo",
    config={"progress_total": 1_000_000, "progress_unit": "env-step"},
    run=run_ippo,
)

Inside the algorithm hook, call context.progress.update(...) from the loop that best represents real progress. Do not rely on the reusable runner to infer trainer internals:

for batch in trainer.iter_batches():
    metrics = trainer.update(batch)
    context.progress.update(
        batch.num_env_steps,
        postfix={"reward": f"{metrics['reward_mean']:.2f}"},
    )

The blocks most often tuned are refresh_percent, refresh_units, unit, and postfix formatting. For notebook demos, a percentage cadence is usually enough. For large scheduler jobs, refresh_units can be clearer because it aligns updates with meaningful training milestones.

If a project already has a trainer-level progress system, keep using it and adapt only the outer loops to this helper. The reusable experiment runner only expects a ProgressBar-like object with update(...); it does not need to own every inner progress display.