Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions tests/serve/multimodal_profiles/sglang.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,22 @@
make_image_payload,
make_image_payload_cached_tokens,
)
from tests.utils.payloads import CachedTokensChatPayload


class SGLangMmRoutingPayload(CachedTokensChatPayload):
def bind_environment(self, env: dict[str, str]) -> None:
"""Keep router observations while relaxing the floor on wide-pad builds."""
# Defer the optional backend import until this test runs.
from dynamo.sglang._compat import sglang_has_wide_mm_pad_values

super().bind_environment(env)
if sglang_has_wide_mm_pad_values():
# Wide-pad SGLang builds route media by text prefix only (#17653),
# so exact media overlap is required only on scalar-pad builds.
# The router must still report hit-rate observations.
self.min_avg_kv_hit_rate = 0.0
Comment thread
glamr-agent marked this conversation as resolved.
Resolved


# pd = multimodal prefill+decode on one GPU, no separate encode worker.
SGLANG_TOPOLOGY_SCRIPTS: dict[str, str] = {
Expand Down Expand Up @@ -62,6 +78,7 @@
["green"],
require_rust_processor_init=True,
min_avg_kv_hit_rate=0.9,
payload_cls=SGLangMmRoutingPayload,
)
)
],
Expand Down Expand Up @@ -94,6 +111,7 @@
["green"],
require_rust_processor_init=True,
min_avg_kv_hit_rate=0.9,
payload_cls=SGLangMmRoutingPayload,
)
)
],
Expand All @@ -116,6 +134,7 @@
["green"],
require_rust_processor_init=True,
min_avg_kv_hit_rate=0.9,
payload_cls=SGLangMmRoutingPayload,
)
)
],
Expand Down
16 changes: 2 additions & 14 deletions tests/serve/test_sglang.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
from tests.serve.multimodal_profiles.sglang import (
SGLANG_MULTIMODAL_PROFILES,
SGLANG_TOPOLOGY_SCRIPTS,
SGLangMmRoutingPayload,
)
from tests.utils.constants import DefaultPort, DynamoPortRange
from tests.utils.engine_process import EngineConfig
Expand All @@ -52,7 +53,6 @@
router_selection_chat_payload_default,
)
from tests.utils.payloads import (
CachedTokensChatPayload,
ChatPayload,
HttpErrorPayload,
ImageGenerationPayload,
Expand Down Expand Up @@ -85,18 +85,6 @@ class SGLangConfig(EngineConfig):
stragglers: list[str] = field(default_factory=lambda: ["SGLANG:EngineCore"])


class SGLangVideoRoutingPayload(CachedTokensChatPayload):
def bind_environment(self, env: dict[str, str]) -> None:
# Defer the optional backend import until this test runs.
from dynamo.sglang._compat import sglang_has_wide_mm_pad_values

super().bind_environment(env)
if sglang_has_wide_mm_pad_values():
# Wide IDs use text-prefix routing, not exact media overlap.
# Keep response-content and worker cached-token checks enabled.
self.min_avg_kv_hit_rate = 0.0


sglang_dir = os.environ.get("SGLANG_DIR") or os.path.join(
WORKSPACE_DIR, "examples/backends/sglang"
)
Expand Down Expand Up @@ -807,7 +795,7 @@ def bind_environment(self, env: dict[str, str]) -> None:
timeout=450,
frontend_port=DefaultPort.FRONTEND.value,
request_payloads=[
SGLangVideoRoutingPayload(
SGLangMmRoutingPayload(
body={
"messages": [
{
Expand Down
5 changes: 4 additions & 1 deletion tests/utils/multimodal.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ def make_image_payload_cached_tokens(
min_routing_total_blocks: int = 0,
min_avg_kv_hit_rate: float = 0.0,
prompt_filler_repeats: int = 0,
payload_cls: type[CachedTokensChatPayload] = CachedTokensChatPayload,
) -> CachedTokensChatPayload:
"""Image payload that asserts MM-aware KV cache reuse on repeats.

Expand All @@ -113,11 +114,13 @@ def make_image_payload_cached_tokens(
``prompt_filler_repeats`` prepends N copies of a topic-aligned filler so
the routing-token sequence spans multiple blocks on specs that use a
coarse routing-block size (e.g. qwen3_5 family at block_size=544).
``payload_cls`` lets a backend substitute a subclass that adjusts the
checks at run time (e.g. SGLang's wide-pad gate).
"""
prompt = _MULTIMODAL_COLOR_PROMPT
if prompt_filler_repeats > 0:
prompt = (_COLOR_PROMPT_FILLER * prompt_filler_repeats).strip() + " " + prompt
return CachedTokensChatPayload(
return payload_cls(
body={
"messages": [
{
Expand Down
19 changes: 14 additions & 5 deletions tests/utils/payloads.py
Original file line number Diff line number Diff line change
Expand Up @@ -1474,6 +1474,9 @@ def __init__(
# block-count, etc.), move into a RouterMetricsAssertion
# mixin/subclass.
self.min_avg_kv_hit_rate = min_avg_kv_hit_rate
# Require new router_kv_hit_rate observations on R2+. A subclass may
# drop the floor at run time and keep this check.
self.require_kv_hit_rate_observations = min_avg_kv_hit_rate > 0
self._metrics_baseline: Optional[tuple[float, float]] = None

def validate(self, response: Any, content: str) -> None:
Expand Down Expand Up @@ -1528,7 +1531,7 @@ def validate(self, response: Any, content: str) -> None:
if (
self._metrics_baseline is None
and self._request_count == 1
and self.min_avg_kv_hit_rate > 0
and self.require_kv_hit_rate_observations
):
self._metrics_baseline = self._scrape_router_kv_hit_rate()

Expand Down Expand Up @@ -1564,8 +1567,8 @@ def _scrape_router_kv_hit_rate(self) -> Optional[tuple[float, float]]:

def final_validation(self) -> None:
"""Assert cached_tokens >= min_cached_tokens on at least one repeat
(only when min_cached_tokens > 0), and (if set) router_kv_hit_rate
post-R1 mean >= min_avg_kv_hit_rate.
(only when min_cached_tokens > 0), and (if set) new router_kv_hit_rate
observations with a post-R1 mean >= min_avg_kv_hit_rate.
"""
# Only assert cached tokens when a positive threshold is set; a caller
# validating purely via the router metric passes min_cached_tokens=0.
Expand All @@ -1581,11 +1584,11 @@ def final_validation(self) -> None:
"✓ Final validation PASSED: cached_tokens found in repeated requests"
)

if self.min_avg_kv_hit_rate <= 0:
if not self.require_kv_hit_rate_observations:
return
if self._metrics_baseline is None:
raise AssertionError(
"min_avg_kv_hit_rate set but no metrics baseline captured "
"router_kv_hit_rate check set but no metrics baseline captured "
"(R1 validate() didn't run or /metrics was unreachable)."
)
after = self._scrape_router_kv_hit_rate()
Expand All @@ -1604,6 +1607,12 @@ def final_validation(self) -> None:
f"MM-routing likely not engaging on repeat requests."
)
avg = d_sum / d_count
if self.min_avg_kv_hit_rate <= 0:
logger.info(
f"✓ router_kv_hit_rate: {d_count} new observations over R2+ "
f"(mean {avg:.3f}, no floor)"
)
return
if avg < self.min_avg_kv_hit_rate:
raise AssertionError(
f"router_kv_hit_rate: mean over R2+ ({avg:.3f}) below required "
Expand Down
Loading