diff --git a/python/sglang/srt/mem_cache/hiradix_cache.py b/python/sglang/srt/mem_cache/hiradix_cache.py index 50ed72ae4..313e3e8b6 100644 --- a/python/sglang/srt/mem_cache/hiradix_cache.py +++ b/python/sglang/srt/mem_cache/hiradix_cache.py @@ -1278,19 +1278,8 @@ class HiRadixCache(RadixCache): "[CP-L3] enabled rank=%d/%d payloads=%s disk_dir=%s", cp_rank, cp_size, list(accessors), self.cp_l3_store.disk_dir, ) - # CP-HiCache metrics (L2 pooled allocator + L3 store): gated on enable_metrics ALONE (the mainline - # storage metrics are dead under CP). Created once; per-rank via tp_rank. Pushed from check_hicache_events. - if getattr(params, "enable_metrics", False) and self.cp_hicache_metrics is None: - from sglang.srt.observability.metrics_collector import CpHiCacheMetricsCollector - - cc = self.cache_controller - labels = { - "tp_rank": cc.tp_rank, "dp_rank": cc.dp_rank, - "pp_rank": cc.pp_rank, "pp_size": cc.pp_size, - } - if self.extra_metric_labels: - labels.update(self.extra_metric_labels) - self.cp_hicache_metrics = CpHiCacheMetricsCollector(labels) + # (CP-HiCache metrics are created lazily in _cp_maybe_collect_metrics, gated on L2 pooling -- so they + # cover PURE-L2-without-L3 too, not just this L3 path.) @staticmethod def _cp_l3_single_slab_mmap(allocator, payload_kind: str): @@ -3841,24 +3830,34 @@ class HiRadixCache(RadixCache): ) def _cp_maybe_collect_metrics(self) -> None: - """Push L2 (pooled allocator) + L3 (store) usage to Prometheus, rate-limited (~1s, per-rank wall-clock; - metrics need no rank-uniformity). Off the data path; cheap (occupancy snapshot + queue sizes + counters).""" - collector = self.cp_hicache_metrics - if collector is None: + """Push L2 (pooled allocator) + L3 (store, if present) usage to Prometheus, rate-limited (~1s, per-rank + wall-clock; metrics need no rank-uniformity). LAZILY creates the collector on first emit, gated on L2 + pooling being on (the allocator exists) -- so it covers PURE-L2 (no L3) as well as L2+L3. Off the data + path; cheap (occupancy snapshot + queue sizes + counters).""" + if not self._enable_metrics_flag: return + allocator = getattr(self.cache_controller, "cp_shared_l2_page_allocator", None) + if allocator is None: + return # L2 pooling not on -> nothing to report now = time.monotonic() if now - self._cp_metrics_last_emit < 1.0: return self._cp_metrics_last_emit = now - allocator = getattr(self.cache_controller, "cp_shared_l2_page_allocator", None) - l2_occ = ( - allocator.occupancy_by_payload() - if allocator is not None and hasattr(allocator, "occupancy_by_payload") - else None - ) - l2_stats = allocator.stats() if allocator is not None and hasattr(allocator, "stats") else None + if self.cp_hicache_metrics is None: + from sglang.srt.observability.metrics_collector import CpHiCacheMetricsCollector + + cc = self.cache_controller + labels = { + "tp_rank": cc.tp_rank, "dp_rank": cc.dp_rank, + "pp_rank": cc.pp_rank, "pp_size": cc.pp_size, + } + if self.extra_metric_labels: + labels.update(self.extra_metric_labels) + self.cp_hicache_metrics = CpHiCacheMetricsCollector(labels) + l2_occ = allocator.occupancy_by_payload() if hasattr(allocator, "occupancy_by_payload") else None + l2_stats = allocator.stats() if hasattr(allocator, "stats") else None l3_stats = self.cp_l3_store.stats() if self.cp_l3_store is not None else None - collector.collect(l2_occupancy=l2_occ, l2_stats=l2_stats, l3_stats=l3_stats) + self.cp_hicache_metrics.collect(l2_occupancy=l2_occ, l2_stats=l2_stats, l3_stats=l3_stats) def _cp_maybe_log_lane_stats(self) -> None: """E1 lane-imbalance probe (read-only, measurement only).