[metrics 1/5] Add scoped vLLM offload and latency window metrics - #2364
Conversation
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>
077898b to
314eac2
Compare
Signed-off-by: SumanthRH <sumanthrh99@gmail.com>
Signed-off-by: SumanthRH <sumanthrh99@gmail.com>
Signed-off-by: SumanthRH <sumanthrh99@gmail.com>
There was a problem hiding this comment.
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() |
There was a problem hiding this comment.
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.
| return ray.get_runtime_context().get_worker_id() | |
| return ray.get_runtime_context().worker_id.hex() |
| if not math.isfinite(delta) or delta < 0: | ||
| return None |
There was a problem hiding this comment.
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.
| if not math.isfinite(delta) or delta < 0: | |
| return None | |
| if not math.isfinite(delta) or delta < 0: | |
| continue |
| 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} |
There was a problem hiding this comment.
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.
| 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]) | |
| } |
| 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") |
There was a problem hiding this comment.
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)
|
| 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 |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ 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.

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
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.request_time_per_output_token_seconds. ITL, P50 and offload store throughput remain available as raw metrics for Grafana.Sync keys retain
vllm/train/*andvllm/eval/*; fully async keys retainvllm/*. Collection uses the existinggenerator.inference_engine.enable_ray_prometheus_statsflag.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.--noconftestskips 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.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 RayWorkerIdat 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_avgswitches from inter-token latency torequest_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.