From 8a7e326aa2655c1a1e65b77c9f0f342c4462eb57 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 29 Aug 2026 03:46:23 +0000 Subject: [PATCH 1/2] planner: prefer configured model_name over lowercased MDC name resolve_worker_info took the MDC-derived model name ahead of the operator supplied config.model_name. Discovery normalizes that name to lowercase, but engine metrics carry the model's real casing, so every engine-metric query silently returned empty: model=deepseek-ai/DeepSeek-V4-Flash-0731-roce-disagg -> accept_length 3.67 model=deepseek-ai/deepseek-v4-flash-0731-roce-disagg -> None With accept_length missing, _clamp_accept_length falls back to 1.0 and the ITL estimator divides forward-pass time by 1 instead of the real ~3.9, inflating the estimate ~4x. On DeepSeek-V4-Flash that read 265ms against a 39ms actual, so the ITL arm breached its SLA forever and the planner recommended SCALE_UP every cycle regardless of load. After the fix, accept_length resolves to ~3.96 and the ITL estimate lands at 37.9ms against a measured 38.9ms. Co-Authored-By: Claude Opus 5 --- .../src/dynamo/planner/monitoring/worker_info.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/components/src/dynamo/planner/monitoring/worker_info.py b/components/src/dynamo/planner/monitoring/worker_info.py index 2f7825f59c70..6a4390b2d471 100644 --- a/components/src/dynamo/planner/monitoring/worker_info.py +++ b/components/src/dynamo/planner/monitoring/worker_info.py @@ -163,7 +163,21 @@ def resolve_worker_info( model_name = config_model_name else: mdc_model = decode_info.model_name or prefill_info.model_name - if mdc_model: + # Prefer the operator-supplied name. Discovery-derived MDC names are + # normalized to lowercase, but engine metrics carry the model's real + # casing, so an MDC name silently makes every engine-metric query + # (spec-decode accept_length, kv_hit_rate) return empty. + if config_model_name: + model_name = config_model_name + if mdc_model and mdc_model != config_model_name: + logger.info( + "Using model name from config: %s (MDC reported %s)", + model_name, + mdc_model, + ) + else: + logger.info(f"Using model name from config: {model_name}") + elif mdc_model: model_name = mdc_model logger.info(f"Using model name from MDC: {model_name}") elif can_query_mdc: From 913126c7c8ecba7f37cc3d17d96f41be6257e8ca Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 29 Aug 2026 21:19:50 +0000 Subject: [PATCH 2/2] planner: try both dynamo_namespace forms for router kv_hit_rate get_avg_kv_hit_rate sanitized the namespace to underscores unconditionally. The router emits it that way, but some scrape paths relabel it back to the hyphenated k8s form (nvidia.com/dynamo-namespace), so the query silently returned no data: dynamo_namespace=deepseek_ai__DeepSeek_V4_Flash_0731_roce_disagg -> EMPTY dynamo_namespace=deepseek-ai--DeepSeek-V4-Flash-0731-roce-disagg -> 5 series DeepSeek-V3.2 carries both forms, so neither spelling is universally right. Try the sanitized form first, then the raw one, and name both in the miss log. With this and the model-name casing fix, the planner's Observed line goes from "kv_hit_rate: n/a accept_length: n/a" to real values (0.36 / 3.08) and every "No prometheus data" warning disappears. Co-Authored-By: Claude Opus 5 --- .../planner/monitoring/traffic_metrics.py | 28 +++++++++++++------ 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/components/src/dynamo/planner/monitoring/traffic_metrics.py b/components/src/dynamo/planner/monitoring/traffic_metrics.py index 80d611fee054..103651d3538e 100644 --- a/components/src/dynamo/planner/monitoring/traffic_metrics.py +++ b/components/src/dynamo/planner/monitoring/traffic_metrics.py @@ -453,16 +453,28 @@ def get_avg_kv_hit_rate(self, interval: str, model_name: str) -> Optional[float] f"{prometheus_names.router.KV_HIT_RATE}" ) try: - ns = self.dynamo_namespace.replace("-", "_") - ns_filter = f'{prometheus_names.labels.NAMESPACE}="{ns}"' - query = ( - f"sum(increase({full_metric_name}_sum{{{ns_filter}}}[{interval}])) / " - f"sum(increase({full_metric_name}_count{{{ns_filter}}}[{interval}]))" - ) - result = self.prom.custom_query(query=query) + # The router emits `dynamo_namespace` sanitized to underscores, but + # some scrape paths relabel it back to the hyphenated k8s form + # (nvidia.com/dynamo-namespace). Try both so the query works under + # either emission style instead of silently returning no data. + candidates = [] + for ns in (self.dynamo_namespace.replace("-", "_"), self.dynamo_namespace): + if ns not in candidates: + candidates.append(ns) + result = [] + for ns in candidates: + ns_filter = f'{prometheus_names.labels.NAMESPACE}="{ns}"' + query = ( + f"sum(increase({full_metric_name}_sum{{{ns_filter}}}[{interval}])) / " + f"sum(increase({full_metric_name}_count{{{ns_filter}}}[{interval}]))" + ) + result = self.prom.custom_query(query=query) + if result: + break if not result: logger.info( - f"No prometheus data for {full_metric_name}, returning None" + f"No prometheus data for {full_metric_name} " + f"(tried dynamo_namespace in {candidates}), returning None" ) return None value = float(result[0]["value"][1])