Skip to content

[logging] Publish rollout buffer depth and group keep-rate metrics - #1924

Merged
pcmoritz merged 12 commits into
NovaSky-AI:mainfrom
eicherseiji:seiji/buffer-depth-gauges
Jul 24, 2026
Merged

[logging] Publish rollout buffer depth and group keep-rate metrics#1924
pcmoritz merged 12 commits into
NovaSky-AI:mainfrom
eicherseiji:seiji/buffer-depth-gauges

Conversation

@eicherseiji

@eicherseiji eicherseiji commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

What does this PR do?

Logs the rollout buffer state to Prometheus. Generation and training run concurrently: generators fill a buffer and the trainer pulls mini_batch_size batches per step. The buffer depth tells which side is the bottleneck. Near zero means the trainer is starving for rollouts; near the cap means generation is paused at the staleness limit. Same idle-GPU symptom, opposite cause.

New metrics:

  • skyrl_gen_buffer_qsize (Prometheus, per step): buffer depth at step start. The W&B counterpart is [chore] Log generation output buffer size at the start of a training step  #1930's async/gen_buffer_qsize_at_wait_start.
  • skyrl_gen_buffer_maxsize, skyrl_mini_batch_size (Prometheus, once at startup): run constants for dashboard panels, e.g. a fill ratio or capacity line.
  • async/keep_rate (W&B) and skyrl_gen_group_keep_rate (Prometheus): mini_batch_size / (mini_batch_size + dropped) under sample_full_batch, the zero-variance drop rate for the last mini-batch, so a deep buffer of mostly droppable groups is distinguishable from a usable one.

How

ScalarGauges (in skyrl/train/utils/metrics.py, alongside the phase gauge from #1923) lazily creates one Prometheus gauge per name at the first set(). The trainer publishes step-start values directly because the tracker flushes at step end, after training and weight sync, when the buffer has already moved on.

Tests

  • tests/train/utils/test_metrics.py (extended): gauge created once per name with its first description, values coerced to float.
  • ruff and black clean on changed files. End to end needs a GPU cluster.

Adds skyrl/train/utils/phase_metrics.py with TrainingPhaseGauge, which
sets a skyrl_training_phase gauge to 1.0 for the active macro-phase and
0.0 for the rest via ray.util.metrics. Ray exports it to the same
Prometheus that scrapes node GPU metrics, so phase windows join GPU
utilization on one wall-clock axis without correlating the experiment
tracker by hand. The async loop sets the phase at each existing Timer
boundary. Best-effort: the gauge silently no-ops when Ray metrics are
unavailable, so it never breaks training or tests.
Generators produce rollout batches into a buffer and the trainer pulls
mini_batch_size batches per step. The buffer depth tells which side is
the bottleneck. Near zero means the trainer is starving for rollouts;
near the cap means generation is paused at the staleness limit. It was
previously visible only in a transient tqdm postfix.

Log async/gen_buffer_qsize and async/gen_buffer_maxsize to the tracker
each step, and publish the same values plus skyrl_mini_batch_size and
skyrl_gen_group_keep_rate as Prometheus gauges via a new ScalarGauges
helper (best-effort, no-ops without Ray, like TrainingPhaseGauge).
keep_rate is kept / (kept + dropped) for the last mini-batch, the
zero-variance drop rate under sample_full_batch, so a deep buffer of
mostly droppable groups is distinguishable from a usable one.
- Emit only the two changed series per transition instead of sweeping
  all phases; the full sweep runs once at construction to seed every
  series for PromQL selectors.
- Warn and keep the current phase on an unknown phase name instead of
  silently zeroing everything.
- Rename phases to match their paired Timer keys so wandb timing/* keys
  and the Prometheus phase label share one vocabulary.
- Construct the gauge in __init__ behind trainer.enable_training_phase_gauge,
  matching the RayGpuMonitor lifecycle.
- Deduplicate docstrings and comments; add unknown-phase and
  disabled-by-flag tests.
Merges the updated seiji/training-phase-gauge base (gauge in __init__
behind a config flag, Timer-key phase names, two-write emit) and applies
this PR's own review findings:

- ScalarGauges takes a description at the first set() for a name instead
  of a constructor descriptions dict.
- ScalarGauges construction and the loop-invariant skyrl_mini_batch_size
  set move to __init__; the step loop reads qsize/maxsize once into
  locals.
- keep_rate uses mini_batch_size directly (the collect loop returns
  exactly that many kept groups here) and is also logged to the tracker
  as async/keep_rate, so both stores carry both drop signals.
- Comment trims.
The gauge writes two in-process values per phase transition with no thread or I/O, so there is nothing an operator needs to switch off. Construct it unconditionally; it already no-ops without Ray.
Base dropped the enable_training_phase_gauge toggle; the gauge constructs unconditionally.
ScalarGauges had no functional dependency on TrainingPhaseGauge; the
stack existed only because both classes shared phase_metrics.py. Move
ScalarGauges to its own module (scalar_gauges.py, one module per
concern) and revert the phase-gauge content from this branch's tree, so
this PR is independently mergeable and its diff is buffer-only. Forward
revert, no history rewrite; NovaSky-AI#1923 still owns the phase gauge.
The gauge description strings already say what each metric is; drop the comments restating them and the dashboard interpretation. Keep the one non-obvious invariant (collect returns exactly mini_batch_size kept groups) and the set() description semantics.
@eicherseiji
eicherseiji marked this pull request as ready for review July 18, 2026 00:57

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a new ScalarGauges utility class to publish best-effort scalar metrics to Prometheus via ray.util.metrics, and integrates it into the FullyAsyncTrainer to track metrics like mini-batch size, generation buffer queue size, buffer capacity, and generation group keep rate. Unit tests were also added to verify the behavior of ScalarGauges. Feedback was provided to separate the float conversion from the Ray-specific metric operations to prevent a single malformed metric value from permanently disabling all Prometheus metrics.

Comment thread skyrl/train/utils/scalar_gauges.py Outdated
Comment thread skyrl/train/fully_async_trainer.py Outdated
Comment thread skyrl/train/fully_async_trainer.py Outdated
…#1930

Review feedback from SumanthRH. The buffer capacity is a run constant:
compute it once in __init__ as the single source for both the queue
construction and a one-time skyrl_gen_buffer_maxsize publish, kept for
dashboard panels. Drop the per-step W&B keys: NovaSky-AI#1930 already logs
async/gen_buffer_qsize_at_wait_start, and this PR keeps only the
step-start Prometheus gauge for the depth.
Comment thread skyrl/train/utils/scalar_gauges.py Outdated
Publish gauges directly so a metrics error surfaces instead of silently
latching gauge publishing off. Call sites pass ints.
@pcmoritz

Copy link
Copy Markdown
Collaborator

Let's merge #1923 and then also put this into metrics.py?

@eicherseiji eicherseiji changed the title [logging] Log rollout buffer depth to W&B and Prometheus [logging] Publish rollout buffer depth and group keep-rate metrics Jul 24, 2026
Keep both the phase gauge (NovaSky-AI#1923) and the buffer/keep-rate gauges in
fully_async_trainer.py. Move ScalarGauges into metrics.py alongside
TrainingPhaseGauge and drop the standalone scalar_gauges.py.
@pcmoritz
pcmoritz merged commit e4c5581 into NovaSky-AI:main Jul 24, 2026
4 of 6 checks passed
eicherseiji added a commit to eicherseiji/SkyRL that referenced this pull request Jul 24, 2026
Import ScalarGauges from metrics.py and drop the standalone scalar_gauges.py
(now folded into metrics.py). Take main's buffer/keep-rate gauges in
fully_async_trainer.py and keep only this PR's step/epoch marking hooks.
Move the mirror's disable-on-failure guard into Tracking._publish_gauge,
since metrics.py's ScalarGauges is intentionally unguarded.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants