Skip to content

[metrics 5/5] Scope prefill/decode and external vLLM metrics - #2401

Draft
SumanthRH wants to merge 3 commits into
mainfrom
metrics/pd-run-summaries
Draft

SumanthRH wants to merge 3 commits into
mainfrom
metrics/pd-run-summaries

Conversation

@SumanthRH

@SumanthRH SumanthRH commented Oct 4, 2026 •

Copy link
Copy Markdown
Member

What does this PR do?

Scope vLLM step metrics and fresh-run summaries separately for prefill and decode, including externally deployed SkyRL servers in the same Ray cluster.

TLDR: Resolve the servers' frontend WorkerIds and roles, then reuse the existing worker-filtered collectors and reductions for each role.

Why this is needed

Prefill and decode can count the same prompt, prefill can report a placeholder output token, and each role measures different latency boundaries. Combining their counters or histograms obscures queue pressure and produces ambiguous run comparisons. External clients also lack the server actor handles used for managed worker discovery.

Usage

Enable generator.inference_engine.enable_ray_prometheus_stats=true on both the serving and client configurations. External clients specify individual external_server_urls with run_engines_locally=false, optionally together with external_proxy_url. The same Ray cluster must expose the servers' metrics agents. PD roles come from the servers; the client does not need to repeat their engine settings.

Output Namespace
Sync charts vllm/{train,eval}/{prefill,decode}/*
Fully async charts vllm/{prefill,decode}/*
W&B Summary vllm_correct_aggregate/{train,eval,combined}/{prefill,decode}/*

How it works

Managed deployments resolve WorkerIds from their existing server groups. External clients query /get_metrics_worker_info on each backend for the exporting frontend's WorkerId and optional prefill/decode role. This endpoint is exposed only with Ray stats enabled and avoids vLLM's /metrics mount. Each role gets its own collector for samples, generation windows and finalization; sync role clocks begin after both baseline reads complete.

Keep each available metric in its role scope, including engine imbalance within that role. Gauges remain step snapshots. Fresh-run-only publication, missing/reset omission and the bounded terminal attempt remain; an unavailable role omits only that role's summary. Each new client takes its own baseline against a reused deployment, excluding earlier served work. Concurrent clients on the same measured engines are not distinguishable by engine counters.

Legacy external endpoints retain cluster-wide collection. Unknown-role external PD suppresses summaries when the client sets enable_pd=true. The metrics guide documents setup and engine-level token/latency interpretation.

The accompanying small simulation fix supplies the timing and checkpoint-finalization hooks now called unconditionally by the async trainer, allowing the simulation entrypoint to complete its run.

Test plan

PD cases cover sync/async role separation, weighted counters/histograms, reused baselines, gauges, missing role workers and managed setup. External setup cases cover regular serving, automatic PD role discovery, explicit PD and legacy fallback. A native vLLM HTTP-app test verifies worker metadata is not captured by the Prometheus route. Existing lifecycle cases cover unknown roles and checkpoint resumption.

Validation

180 focused tests passed, including metrics, HTTP inference, async training and collation. Ruff/Black, secret checks and the production docs build passed. --noconftest avoids the repository's automatic Ray lifecycle fixture touching the live cluster.

uv run --isolated --offline --frozen --extra fsdp --extra dev --with ray==2.58.0 pytest --noconftest \
  tests/train/test_grafana_annotations.py tests/train/test_metrics_lifecycle.py \
  tests/train/test_vllm_run_statistics.py tests/train/test_vllm_metrics_scraper.py \
  tests/train/test_vllm_window_statistics.py tests/train/test_vllm_engine_imbalance.py \
  tests/train/test_vllm_pd_metrics.py tests/train/test_tracking.py \
  tests/train/test_fully_async_trainer.py tests/train/test_async_batch_collation.py \
  tests/backends/skyrl_train/inference_servers/test_external_metrics.py \
  tests/backends/skyrl_train/inference_servers/test_vllm_sample_support.py \
  tests/backends/skyrl_train/inference_servers/test_remote_inference_client.py -q

Real-inference validation used Qwen2.5-1.5B-Instruct/GSM8K, B200 GPUs, vLLM 0.30 and Ray 2.58, with graph execution enabled and NixlConnector for 1P1D. Two external simulation clients each completed two steps against a reused regular server; another two clients did the same against reused 1P1D servers. No gradient computation or model transfer is performed by the simulation.

bash /home/ray/hackskyrl/logs/1004/metrics_external_mode/run_regular.sh
bash /home/ray/hackskyrl/logs/1004/metrics_external_mode/run_pd.sh
Mode W&B runs Independent Prometheus aggregate matches
Regular client 1, client 2 24 / 24
1P1D client 1, client 2 52 / 52

Query the recorded baseline/terminal timestamps with each role's exact WorkerIds and ClusterId. Subtract observed counters, check resets, divide summed hits by summed queries, and reduce histogram sum/count deltas and merged bucket deltas. Throughput uses independently checked async observation seconds. Each client excludes earlier server work: regular/decode totals are 1,536 output tokens and 2,526 prompt tokens; prefill's 24 placeholder outputs stay separate.

W&B readback verifies all 120 PD step values and 42 regular step values. Regular client 1's second history row remains unavailable through the API, although its explicit run aggregates all match Prometheus. Earlier managed-PD evaluation validation also matched all 26 aggregates and 60 role-scoped chart values in this run.

Validation-only export waits establish visibility; PD async durations include a 25-second terminal idle wait. Production finalization has no export wait. These are compatibility checks, not a throughput benchmark.

Configs, raw observations, timestamps, queries, comparison tables and logs are retained under /home/ray/hackskyrl/logs/1004/metrics_external_mode/ and the earlier /home/ray/hackskyrl/logs/1004/metrics_pd/.

Existing cleanup issue observed during validation

Both PD clients completed successfully and finalized metrics. The serving driver then raised ActorDiedError during teardown: InferenceOnlyEntrypoint._teardown() lists its PD groups twice because _server_groups already contains both roles. This code is unchanged in this PR and present in main. All test servers were stopped and GPU memory release was verified. Existing aiohttp session warnings also appear after the synchronous harness closes training event loops.

@SumanthRH
SumanthRH added this pull request to stack #2400 October 4, 2026 02:40
@SumanthRH SumanthRH changed the title [metrics 5/5] Scope prefill and decode metrics and run summaries [metrics 5/5] Scope prefill/decode step metrics and run summaries Oct 4, 2026
@SumanthRH
SumanthRH force-pushed the metrics/pd-run-summaries branch from a3cf1e6 to 04290d6 Compare October 4, 2026 05:03
SumanthRH added a commit that referenced this pull request Oct 4, 2026
#2364)

# What does this PR do?

Add vLLM preemption, external prefix-cache, KV reload and
request-latency metrics to SkyRL's existing step collection.

TLDR: Collect the launched servers' metrics, reduce counter and
histogram deltas, and use request TPOT for `tpot_seconds_avg`.

## Why this is needed

The existing scraper does not report preemptions or
external-cache/offload activity, and its TPOT field uses inter-token
latency. These gaps make it difficult to compare cache configurations
alongside training throughput. Unfiltered cluster samples can also
include other deployments.

## How it works

- Add `VLLMServerActor.get_ray_worker_id()` and resolve the API server
actors' WorkerIds during setup. Filter samples to those workers. The
lookup has a 10-second timeout; failure disables collection without
stopping training. External deployments without owned actors retain
unfiltered collection.
- Sum engine counters before taking deltas. Merge compatible
classic-histogram buckets before interpolating P90. Failed endpoint
scrapes discard partial snapshots; missing baselines and invalid latency
windows are omitted. A missing bucket baseline is treated as zero only
when the previous histogram count is explicitly zero.
- Log preemption counts and preemptions per million output tokens,
external hit rate, KV reload bytes/s, and TTFT/request TPOT mean and
P90. TPOT uses `request_time_per_output_token_seconds`. ITL, P50 and
offload store throughput remain available as raw metrics for Grafana.

Sync keys retain `vllm/train/*` and `vllm/eval/*`; fully async keys
retain `vllm/*`. Collection uses the existing
`generator.inference_engine.enable_ray_prometheus_stats` flag.

## Validation

Collector tests cover worker filtering, bounded identity lookup, partial
HTTP failures, counter resets, latency formulas and lazy histogram
buckets. API-server source and historical actor PID/worker/export
records confirm that the lookup returns the WorkerId used by vLLM's Ray
metrics logger.

Combined-stack CPU check at `04290d61` (#2401): **70 tests passed**.
`--noconftest` skips the repository's automatic Ray lifecycle fixture so
these mock/CPU checks do not attach to the live cluster. Ray 2.58.0
matches the validation cluster.

```bash
uv run --isolated --extra skyrl-train --extra dev --with ray==2.58.0 pytest --noconftest \
  tests/train/test_grafana_annotations.py tests/train/test_metrics_lifecycle.py \
  tests/train/test_vllm_run_statistics.py tests/train/test_vllm_metrics_scraper.py \
  tests/train/test_vllm_window_statistics.py tests/train/test_vllm_engine_imbalance.py \
  tests/train/test_vllm_pd_metrics.py tests/train/test_tracking.py -q
```

No new GPU benchmark was run for this layer.

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Medium Risk**
> Changes how TPOT and percentiles are defined for logged metrics and
adds setup-time Ray worker identity resolution; failures are handled by
disabling collection, but mis-scoped or missing metrics could affect
monitoring during training.
> 
> **Overview**
> Expands step-level vLLM metrics logged to the trainer tracker and
**scopes scraping to SkyRL-launched inference servers** by resolving
each `VLLMServerActor`'s Ray `WorkerId` at setup (10s timeout; lookup
failure disables collection without aborting training).
> 
> The scraper now emits **preemptions**, **external prefix-cache hit
rate**, **KV offload load throughput**, and **TTFT/request-TPOT averages
plus P90** (merged histogram bucket deltas via new
`vllm_window_statistics`). **`tpot_seconds_avg` switches from
inter-token latency to `request_time_per_output_token_seconds`**, so it
is not comparable to older runs. Partial node scrape failures drop the
whole snapshot and reset the delta baseline.
> 
> Docs and tests cover worker filtering, incomplete snapshots, and the
new reduction paths.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
1e2064c. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

---------

Signed-off-by: Codex <codex@users.noreply.github.com>
Signed-off-by: SumanthRH <sumanthrh99@gmail.com>
Co-authored-by: Codex <codex@users.noreply.github.com>
@SumanthRH
SumanthRH force-pushed the metrics/pd-run-summaries branch from 04290d6 to 59a2eab Compare October 4, 2026 05:31
@SumanthRH
SumanthRH force-pushed the metrics/pd-run-summaries branch from 59a2eab to e700214 Compare October 4, 2026 05:35
SumanthRH added a commit that referenced this pull request Oct 4, 2026
# What does this PR do?

Report how evenly inference engines share prompt and output generation
work.

TLDR: Add aggregate coefficients of variation (CV) for per-engine token
deltas, including observed idle engines.

## How it works

Group token counters by WorkerId and engine index, then compare each
engine's deltas over the sampling interval. CV is population standard
deviation divided by mean; a common time denominator cancels from this
ratio.

Emit `prompt_throughput_cv` and `generation_throughput_cv`, plus a
`_num_engines` field for each. Engines observed at both boundaries
contribute; invalid/reset deltas are excluded. Omit CV for fewer than
two contributing engines or zero mean. Sync windows require a valid
start snapshot before deriving CV or engine counts; failed baselines
leave only the current gauges. The training logger receives aggregate
scalars.

## Validation

Focused cases check idle-engine inclusion, equal load, missing engines
and zero work.

Combined-stack CPU check at `04290d61` (#2401): **70 tests passed**.
`--noconftest` skips the repository's automatic Ray lifecycle fixture so
these mock/CPU checks do not attach to the live cluster. Ray 2.58.0
matches the validation cluster.

```bash
uv run --isolated --extra skyrl-train --extra dev --with ray==2.58.0 pytest --noconftest \
  tests/train/test_grafana_annotations.py tests/train/test_metrics_lifecycle.py \
  tests/train/test_vllm_run_statistics.py tests/train/test_vllm_metrics_scraper.py \
  tests/train/test_vllm_window_statistics.py tests/train/test_vllm_engine_imbalance.py \
  tests/train/test_vllm_pd_metrics.py tests/train/test_tracking.py -q
```

The existing HTTP503 reproducer confirms that a failed sync baseline
returns current gauges without CV or engine counts. All 29 existing
collector, histogram and imbalance tests also passed on this branch. No
tests were added or edited, and no GPU benchmark was run.


<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Low Risk**
> Observability-only additions to metrics aggregation with guarded CV
math; no changes to inference or training control flow.
> 
> **Overview**
> Adds **per-engine load imbalance** signals to vLLM Ray metrics
scraping by tracking prompt and generation token counters per
`(WorkerId/ReplicaId, engine)` and emitting aggregate **coefficients of
variation** over each sampling window.
> 
> `VLLMMetricsScraper` now builds an `_engine_snapshot` on each scrape
(including idle engines seen via `num_requests_running`), keeps
previous/window baselines, and merges `prompt_throughput_cv`,
`generation_throughput_cv`, and matching `_num_engines` into both step
`sample()` output (`vllm/…`) and explicit `start`/`stop` windows
(`{label}/…`). CV is computed from non-negative token deltas across
engines present at both boundaries; it is omitted when fewer than two
engines contribute or mean delta is zero.
> 
> New unit tests cover idle-engine inclusion, equal load (CV=0), and
missing/zero-work cases for `engine_imbalance`.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
e9e9edf. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

---------

Signed-off-by: Codex <codex@users.noreply.github.com>
Signed-off-by: SumanthRH <sumanthrh99@gmail.com>
Co-authored-by: Codex <codex@users.noreply.github.com>
@SumanthRH
SumanthRH force-pushed the metrics/pd-run-summaries branch from e700214 to b2ecc84 Compare October 4, 2026 05:41
@SumanthRH
SumanthRH force-pushed the metrics/pd-run-summaries branch from b2ecc84 to de639cf Compare October 4, 2026 05:52
@SumanthRH
SumanthRH force-pushed the metrics/pd-run-summaries branch from de639cf to 703cecc Compare October 4, 2026 06:22
@SumanthRH
SumanthRH force-pushed the metrics/pd-run-summaries branch from 703cecc to 481238d Compare October 4, 2026 07:33
SumanthRH added a commit that referenced this pull request Oct 4, 2026
# What does this PR do?

Publish weighted vLLM run aggregates to W&B Summary before tracking
closes.

TLDR: Reduce raw observed counters, histogram statistics and measured
durations once at the end of a fresh run.

## Why this is needed

Last-step values and averages of step rates do not describe a whole run.
For example, 1,000 tokens in 10 seconds followed by 9,000 tokens in 30
seconds gives 250 tokens/s overall, while averaging the two rates gives
200 tokens/s.

## How it works

Accumulate counter and histogram deltas over non-overlapping windows.
Compute throughput from total tokens or bytes divided by total measured
seconds, cache hit rates from total hits/queries, and latency means/P90
from merged histogram statistics. Include token, preemption and
offload-byte totals plus `measurement_seconds`.

Ray omits counters until their first nonzero increment. After validating
a complete scrape and the expected worker set, a live engine gauge
establishes zero baselines for prompt and generated token totals. This
includes the first generation in step throughput and run aggregates;
missing scrapes, reset handling and the aggregate intersection rule
retain their existing behavior.

Sync train/eval windows remain separate. Fully async collection takes a
baseline before generation and uses elapsed time between samples,
including idle and weight-sync time. Publish summaries under
`vllm_correct_aggregate/{train,eval,combined}/*`; keep `vllm/*` in step
history. Gauges remain step snapshots.

`finalize_metrics()` makes one final collection attempt with a 10-second
outer bound and closes HTTP clients. Finalization runs on success and
handled failure. Tracking publishes `run_status` and uses W&B exit code
1 for failures. Loading a checkpoint suppresses aggregates; `LATEST`
without a checkpoint remains fresh. Generic PD summaries stay disabled
in this layer; #2401 adds separate role scopes.

## Collection limits

Missing/reset windows omit the affected scope; counters missing from
part of a scope are omitted. Reused external servers require an observed
baseline. Collection does not wait for another Ray export, so short runs
or delayed terminal exports can leave aggregates unavailable or
incomplete. No metric checkpoint history or local summary files are
added.

SkyRL requests `summary="none"` and removes SDK summary entries for step
keys, but W&B can still infer last values. Use
`vllm_correct_aggregate/*` for comparisons. A hard kill cannot publish
final status or summaries.

## Validation

Tests cover weighted rates/ratios, request-weighted latency, merged
histograms, missing/reset windows, reused baselines, checkpoint
detection and finalization on success/failure/cancellation.

The cold-start regression covers sync and async collection through the
HTTP parser: the baseline contains only a live engine gauge, token
counters appear after generation, and both first-window rates and
weighted run totals include those tokens. **2 cases failed before the
fix** with missing first-window throughput keys. After the fix, **54
focused tests passed** on this PR at `f9ca0a21`, and **72 focused tests
passed** on the complete stack at `481238da` (#2401), including both
regression cases. Ruff, Black and secret checks passed.

These CPU tests use mocked HTTP responses and do not attach to the live
Ray cluster. `--noconftest` skips the repository's automatic Ray
lifecycle fixture; Ray 2.58.0 matches the validation cluster.
`--offline` uses cached uv dependencies.

```bash
uv run --isolated --offline --frozen --extra skyrl-train --extra dev --with ray==2.58.0 pytest --noconftest \
  tests/train/test_grafana_annotations.py tests/train/test_metrics_lifecycle.py \
  tests/train/test_vllm_run_statistics.py tests/train/test_vllm_metrics_scraper.py \
  tests/train/test_vllm_window_statistics.py tests/train/test_vllm_engine_imbalance.py \
  tests/train/test_vllm_pd_metrics.py tests/train/test_tracking.py -q
```

A read-only W&B/Prometheus audit of
[metrics-offload-0930-freshdash-191711](https://wandb.ai/anyscale-llm-forge/skyrl-metrics-validation/runs/9ht9q4o3)
matched **15 aggregates** within floating-point rounding: **24,576
output tokens, 73 preemptions and 259.904735 output tok/s**.

The audit used workload bounds **2026-09-30
19:21:51.471927–19:23:26.153083 UTC**, the two owned WorkerIds and
ClusterId, and terminal visibility through end +35 seconds. It checked
counter resets, reduced raw totals/histograms, and used the recorded
94.557723 seconds of generation for throughput. Workload-wall throughput
was 259.565906 tok/s, a 0.1304% difference.

That historical run used the older namespace and an export wait. It
verifies its bookkeeping, not completeness of this PR's one-attempt
terminal collection. No new GPU benchmark was run for this layer.


<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Medium Risk**
> Touches training teardown, W&B lifecycle, and metrics on failure
paths; incorrect finalization could drop summaries or double-finish
runs, but behavior is guarded and heavily tested.
> 
> **Overview**
> Adds **end-of-run vLLM aggregates** under
`vllm_correct_aggregate/{train,eval,combined}/*` by accumulating
counter/histogram deltas across non-overlapping windows
(`RunStatistics`), then publishing weighted throughput, cache rates,
latency, and totals via W&B **summary** (not step history). Step metrics
stay on `vllm/*`; W&B auto-summaries for those keys are disabled and
stripped on finish so dashboards should use the aggregate namespace.
> 
> **`finalize_metrics()`** runs once on success or handled failure
(10s-bound final scrape, HTTP client close). Fresh runs publish scraper
summaries; **checkpoint resume** and **PD** skip aggregate summary
upload. The PPO entrypoint keeps the tracker on the experiment,
finalizes trainer metrics before exception logging, and always
**`finish()`**es tracking with **`run_status`** and a non-zero exit code
on failure.
> 
> The scraper now records run windows on `sample()`/`stop()`, adds KV
offload store bytes, requires a full worker set when filtered, and
treats incomplete scrapes as omitting scope totals. Fully-async training
samples a baseline before the loop and uses the shared finalization
path.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
f9ca0a2. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

---------

Signed-off-by: SumanthRH <sumanthrh99@gmail.com>
@SumanthRH
SumanthRH force-pushed the metrics/pd-run-summaries branch from 481238d to f6ef039 Compare October 4, 2026 18:46
Base automatically changed from metrics/grafana-annotations to main October 4, 2026 18:50
Signed-off-by: SumanthRH <sumanthrh99@gmail.com>
Signed-off-by: SumanthRH <sumanthrh99@gmail.com>
Signed-off-by: SumanthRH <sumanthrh99@gmail.com>
@SumanthRH SumanthRH changed the title [metrics 5/5] Scope prefill/decode step metrics and run summaries [metrics 5/5] Scope prefill/decode and external vLLM metrics Oct 4, 2026

This branch was successfully deployed

1 active deployment
Preview — a8e9156b Deployed Oct 4, 2026 by vercel[bot]
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.

1 participant