A high-throughput and memory-efficient inference and serving engine for LLMs
Mirrored from https://github.com/tduncan-lum/Nvidia-Optimization-Stack---VLLM.git
Synced Nov 10, 00:15 UTC
[NIXL][Metrics] Track `nixl_num_kv_expired_reqs` metric in Prometheus (#32340)
Add a new metric to track the number of requests that had their KV blocks expire. The scenario is particularly important to surface and track as it is a vital indicator of the health of the deployment. Currently we're resorting to track these failures through unstructured log parsing (which is, among other thing, error string dependent); current main: > Releasing expired KV blocks for request cmpl-071d which were retrieved by 0 decode worker(s) within 0 seconds. Signed-off-by: NickLucche <nlucches@redhat.com>
Nicolò Lucchesi committed
Jan 19, 2026 at 13:28 UTC
758df5afe7ac8cdc8141fd25f472ee3632377d90
1 file changed
+31
-6
vllm/distributed/kv_transfer/kv_connector/v1/nixl_connector.py
+31
-6
@@ -1930,6 +1930,7 @@ class NixlConnectorWorker:
1930
if now < expires:
1931
break
1932
count = self.consumer_notification_counts_by_req.pop(req_id, 0)
1933
+ self.xfer_stats.record_kv_expired_req()
1934
logger.warning(
1935
"Releasing expired KV blocks for request %s which were "
1936
"retrieved by %d decode worker(s) within %d seconds.",
@@ -2499,13 +2500,14 @@ class NixlKVConnectorStats(KVConnectorStats):
2500
2501
def reset(self):
2502
# Must be serializable
2502
- self.data: dict[str, list[float]] = {
2503
+ self.data: dict[str, list[float | int]] = {
2504
"transfer_duration": [],
2505
"post_duration": [],
2506
"bytes_transferred": [],
2507
"num_descriptors": [],
2508
"num_failed_transfers": [],
2509
"num_failed_notifications": [],
2510
+ "num_kv_expired_reqs": [],
2511
}
2512
2513
def record_transfer(self, res: nixlXferTelemetry):
@@ -2517,11 +2519,15 @@ class NixlKVConnectorStats(KVConnectorStats):
2519
2520
def record_failed_transfer(self):
2521
"""Record a failed NIXL transfer operation."""
2520
- self.data["num_failed_transfers"].append(1.0)
2522
+ self.data["num_failed_transfers"].append(1)
2523
2524
def record_failed_notification(self):
2525
"""Record a failed NIXL notification (send_notif)."""
2524
- self.data["num_failed_notifications"].append(1.0)
2526
+ self.data["num_failed_notifications"].append(1)
2527
+
2528
+ def record_kv_expired_req(self):
2529
+ """Record a request that had its KV blocks expire."""
2530
+ self.data["num_kv_expired_reqs"].append(1)
2531
2532
def clone_and_reset(self) -> "NixlKVConnectorStats":
2533
old = copy.copy(self)
@@ -2529,7 +2535,13 @@ class NixlKVConnectorStats(KVConnectorStats):
2535
return old
2536
2537
def is_empty(self) -> bool:
2532
- return self.num_successful_transfers == 0
2538
+ # Do not discard metrics update that are entirely failures related.
2539
+ return (
2540
+ self.num_successful_transfers == 0
2541
+ and len(self.data["num_failed_transfers"]) == 0
2542
+ and len(self.data["num_failed_notifications"]) == 0
2543
+ and len(self.data["num_kv_expired_reqs"]) == 0
2544
+ )
2545
2546
def aggregate(self, other: KVConnectorStats) -> KVConnectorStats:
2547
if not other.is_empty():
@@ -2541,7 +2553,9 @@ class NixlKVConnectorStats(KVConnectorStats):
2553
2554
def reduce(self) -> dict[str, int | float]:
2555
# Compute compact representative stats suitable for CLI logging
2544
- if self.is_empty():
2556
+ if self.num_successful_transfers == 0:
2557
+ # CLI logging only reports successful transfers stats. If all requests in
2558
+ # the interval were unsuccessful, Prom will report failures stats instead.
2559
return {
2560
"Num successful transfers": 0,
2561
"Avg xfer time (ms)": 0,
@@ -2677,6 +2691,16 @@ class NixlPromMetrics(KVConnectorPromMetrics):
2691
counter_nixl_num_failed_notifications
2692
)
2693
2694
+ counter_nixl_num_kv_expired_reqs = self._counter_cls(
2695
+ name="vllm:nixl_num_kv_expired_reqs",
2696
+ documentation="Number of requests that had their KV expire. "
2697
+ "NOTE: This metric is tracked on the P instance.",
2698
+ labelnames=labelnames,
2699
+ )
2700
+ self.counter_nixl_num_kv_expired_reqs = self.make_per_engine(
2701
+ counter_nixl_num_kv_expired_reqs
2702
+ )
2703
+
2704
def observe(self, transfer_stats_data: dict[str, Any], engine_idx: int = 0):
2705
for prom_obj, list_item_key in zip(
2706
[
@@ -2698,8 +2722,9 @@ class NixlPromMetrics(KVConnectorPromMetrics):
2722
[
2723
self.counter_nixl_num_failed_transfers,
2724
self.counter_nixl_num_failed_notifications,
2725
+ self.counter_nixl_num_kv_expired_reqs,
2726
],
2702
- ["num_failed_transfers", "num_failed_notifications"],
2727
+ ["num_failed_transfers", "num_failed_notifications", "num_kv_expired_reqs"],
2728
):
2729
for list_item in transfer_stats_data[counter_item_key]:
2730
counter_obj[engine_idx].inc(list_item)