Skip to content

[metrics 1/5] Add scoped vLLM offload and latency window metrics - #2364

Merged
SumanthRH merged 6 commits into
mainfrom
metrics/window-statistics
Oct 4, 2026
Merged

SumanthRH merged 6 commits into
mainfrom
metrics/window-statistics

Conversation

@SumanthRH

@SumanthRH SumanthRH commented Oct 1, 2026 •

Copy link
Copy Markdown
Member

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.

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.


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.

Reviewed by Cursor Bugbot for commit 1e2064c. Bugbot is set up for automated code reviews on this repo. Configure here.

Comment thread skyrl/backends/skyrl_train/inference_servers/vllm_server_actor.py Outdated
codex added 3 commits October 1, 2026 14:32
Signed-off-by: Codex <codex@users.noreply.github.com>
Signed-off-by: SumanthRH <sumanthrh99@gmail.com>
Signed-off-by: Codex <codex@users.noreply.github.com>
Signed-off-by: SumanthRH <sumanthrh99@gmail.com>
Signed-off-by: Codex <codex@users.noreply.github.com>
Signed-off-by: SumanthRH <sumanthrh99@gmail.com>
Signed-off-by: SumanthRH <sumanthrh99@gmail.com>
Signed-off-by: SumanthRH <sumanthrh99@gmail.com>
Signed-off-by: SumanthRH <sumanthrh99@gmail.com>
@SumanthRH
SumanthRH removed this pull request from stack #2378 October 4, 2026 01:08
@SumanthRH
SumanthRH added this pull request to stack #2400 October 4, 2026 01:08
@SumanthRH SumanthRH changed the title [metrics 1/5] Add scoped vLLM offload and latency window metrics [metrics 1/4] Add scoped vLLM offload and latency window metrics Oct 4, 2026
@SumanthRH SumanthRH changed the title [metrics 1/4] Add scoped vLLM offload and latency window metrics [metrics 1/5] Add scoped vLLM offload and latency window metrics Oct 4, 2026
@SumanthRH
SumanthRH marked this pull request as ready for review October 4, 2026 04:43

@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 enhances vLLM metrics collection by adding several new metrics (such as preemptions, external prefix cache hit rates, and P90 latency percentiles), filtering snapshots by Ray worker IDs, and introducing a new window statistics utility. The review feedback highlights a critical bug in get_ray_worker_id where calling a non-existent get_worker_id() method on the Ray runtime context will raise an AttributeError. Additionally, the feedback suggests robustness improvements to prevent a single counter reset from invalidating all latency metrics, as well as performance optimizations to avoid redundant dictionary allocations and multiple iterations over metric labels during scraping.

"""Return the Ray worker ID of the actor process hosting this API server."""
import ray

return ray.get_runtime_context().get_worker_id()

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.

high

The ray.get_runtime_context() object does not have a get_worker_id() method. Calling it will raise an AttributeError, which will silently disable vLLM metrics collection during setup. Instead, use the worker_id property and convert it to a hex string.

Suggested change
return ray.get_runtime_context().get_worker_id()
return ray.get_runtime_context().worker_id.hex()

Comment on lines +45 to +46
if not math.isfinite(delta) or delta < 0:
return None

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.

medium

Returning None when any single counter resets invalidates all latency metrics (both TTFT and TPOT), even if the reset occurred in an unrelated counter (like speculative decoding or prefix cache queries). Skipping the resetting counter using continue is much more robust, as the downstream latency_metrics and histogram_quantile functions are already designed to handle missing or incomplete counters safely.

Suggested change
if not math.isfinite(delta) or delta < 0:
return None
if not math.isfinite(delta) or delta < 0:
continue

Comment on lines +286 to +287
if self._worker_ids is not None:
parsed = {key: value for key, value in parsed.items() if dict(key[1]).get("WorkerId") in self._worker_ids}

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.

medium

Converting key[1] (which is a frozenset of label tuples) to a dictionary via dict(key[1]) for every single metric sample on every step is inefficient. We can perform a direct generator check over the frozenset tuples to avoid allocating a new dictionary for every key.

Suggested change
if self._worker_ids is not None:
parsed = {key: value for key, value in parsed.items() if dict(key[1]).get("WorkerId") in self._worker_ids}
if self._worker_ids is not None:
parsed = {
key: value
for key, value in parsed.items()
if any(k == "WorkerId" and v in self._worker_ids for k, v in key[1])
}

Comment on lines +295 to +299
label_dict = dict(labels)
bound = label_dict.get("le")
if bound is None:
continue
owner = frozenset((k, v) for k, v in labels if k != "le")

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.

medium

Creating a dictionary dict(labels) and then iterating over labels again to construct owner is inefficient as it performs multiple passes over the labels. We can extract bound and construct the owner pairs in a single pass over labels.

            bound = None
            owner_pairs = []
            for k, v in labels:
                if k == "le":
                    bound = v
                else:
                    owner_pairs.append((k, v))
            if bound is None:
                continue
            owner = frozenset(owner_pairs)

@greptile-apps

greptile-apps Bot commented Oct 4, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerConfidence Score: 4/5

[Medium risk] Adds new vLLM metrics collection with worker filtering.

The PR should not merge until an unrelated counter reset can no longer discard valid latency windows and aggregate summaries.

Findings

  1. P1 Unrelated resets discard latency ▶
  2. P2 Filtered samples disappear silently ▶
Diagram
%%{init: {'theme': 'neutral'}}%%
flowchart LR
  A[Launched server actors] -->|WorkerIds| F[Snapshot filter]
  R[Ray metrics endpoints] --> F
  F --> C[Merge counters and histogram buckets]
  C --> W[Window deltas]
  W --> T[Step metrics and tracker]
  W --> S[Higher-stack run summaries]
Loading

Reviews (1) · Last reviewed commit: "Use metric constants and expose the Ray ..."

Comment thread skyrl/train/utils/vllm_window_statistics.py
Comment on lines +286 to +289
if self._worker_ids is not None:
parsed = {key: value for key, value in parsed.items() if dict(key[1]).get("WorkerId") in self._worker_ids}
if not parsed:
return None

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.

P2 Filtered samples disappear silently If a successful scrape contains samples but none match the fixed actor membership, this filter returns no snapshot without a warning: the empty-sample warning runs before filtering. The run then loses vLLM telemetry, and operators have no diagnostic pointing to the membership mismatch.

Knowledge Base Used: Trainer execution and evaluation

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 1e2064c. Configure here.

Comment thread skyrl/train/utils/vllm_window_statistics.py
@SumanthRH
SumanthRH merged commit fc6c1cc into main Oct 4, 2026
9 checks passed

This branch was successfully deployed

1 active deployment
Preview — 1e2064c2 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.

2 participants