Files
vllm-omni/tests/metrics/test_stats.py
T
2026-10-02 18:03:14 +00:00

197 lines
6.1 KiB
Python

# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project
from __future__ import annotations
import pytest
from vllm_omni.metrics import OrchestratorAggregator
from vllm_omni.metrics.stats import RequestE2EStats, StageRequestStats, StageStats
pytestmark = [pytest.mark.core_model, pytest.mark.cpu]
def _get_request_entry(table: list[dict], request_id: str) -> dict:
for entry in table:
if entry.get("request_id") == request_id:
return entry
raise AssertionError(f"request_id={request_id} not found")
def test_orchestrator_aggregator_builds_summary() -> None:
agg = OrchestratorAggregator(num_stages=2, log_stats=True, wall_start_ts=0.0, final_stage_id_for_e2e=1)
agg.stage_first_ts[0] = 0.0
agg.stage_last_ts[0] = 0.03
agg.stage_first_ts[1] = 0.05
agg.stage_last_ts[1] = 0.07
agg.on_forward(0, 1, "r1", size_bytes=1024, tx_ms=5.0, used_shm=False)
agg.on_stage_metrics(
0,
"r1",
StageRequestStats(
batch_id=1,
batch_size=1,
num_tokens_in=3,
num_tokens_out=3,
stage_gen_time_ms=30.0,
rx_transfer_bytes=0,
rx_decode_time_ms=0.0,
rx_in_flight_time_ms=0.0,
stage_stats=StageStats(),
),
)
agg.on_stage_metrics(
1,
"r1",
StageRequestStats(
batch_id=1,
batch_size=1,
num_tokens_in=0,
num_tokens_out=4,
stage_gen_time_ms=20.0,
rx_transfer_bytes=1024,
rx_decode_time_ms=5.0,
rx_in_flight_time_ms=2.0,
stage_stats=StageStats(),
),
)
agg.on_finalize_request(1, "r1", req_start_ts=0.0)
summary = agg.build_and_log_summary()
overall = summary["overall_summary"]
assert overall["e2e_requests"] == 1
stage_entry = _get_request_entry(summary["stage_table"], "r1")
stage_ids = [row["stage_id"] for row in stage_entry["stages"]]
assert stage_ids == [0, 1]
transfer_entry = _get_request_entry(summary["trans_table"], "r1")
assert transfer_entry["transfers"][0]["edge"] == "0->1"
assert transfer_entry["transfers"][0]["size_kbytes"] == 1.0
e2e_entry = _get_request_entry(summary["e2e_table"], "r1")
assert e2e_entry["e2e_total_tokens"] == 10
def test_build_and_log_summary_e2e_only() -> None:
agg = OrchestratorAggregator(num_stages=1, log_stats=True, wall_start_ts=0.0, final_stage_id_for_e2e=0)
agg.e2e_events.append(
RequestE2EStats(
request_id="r",
e2e_total_ms=10.0,
e2e_total_tokens=5,
transfers_total_time_ms=0.0,
transfers_total_bytes=0,
)
)
summary = agg.build_and_log_summary()
e2e_entry = _get_request_entry(summary["e2e_table"], "r")
assert e2e_entry["e2e_total_tokens"] == 5
stage_entry = _get_request_entry(summary["stage_table"], "r")
assert stage_entry["stages"] == []
def test_build_and_log_summary_multiple_requests() -> None:
agg = OrchestratorAggregator(
num_stages=2, log_stats=True, wall_start_ts=0.0, final_stage_id_for_e2e={"r1": 1, "r2": 0}
)
# Request r1 goes through both stages
agg.on_stage_metrics(
0,
"r1",
StageRequestStats(
batch_id=1,
batch_size=1,
num_tokens_in=2,
num_tokens_out=4,
stage_gen_time_ms=10.0,
rx_transfer_bytes=0,
rx_decode_time_ms=0.0,
rx_in_flight_time_ms=0.0,
stage_stats=StageStats(),
),
)
agg.on_stage_metrics(
1,
"r1",
StageRequestStats(
batch_id=1,
batch_size=1,
num_tokens_in=4,
num_tokens_out=5,
stage_gen_time_ms=8.0,
rx_transfer_bytes=0,
rx_decode_time_ms=0.0,
rx_in_flight_time_ms=0.0,
stage_stats=StageStats(),
),
)
agg.on_finalize_request(1, "r1", req_start_ts=0.0)
# Request r2 only goes through stage 0
agg.on_stage_metrics(
0,
"r2",
StageRequestStats(
batch_id=2,
batch_size=1,
num_tokens_in=1,
num_tokens_out=2,
stage_gen_time_ms=12.0,
rx_transfer_bytes=0,
rx_decode_time_ms=0.0,
rx_in_flight_time_ms=0.0,
stage_stats=StageStats(),
),
)
agg.on_finalize_request(0, "r2", req_start_ts=0.0)
summary = agg.build_and_log_summary()
assert len(summary["stage_table"]) == 2
assert {entry["request_id"] for entry in summary["e2e_table"]} == {"r1", "r2"}
# Check that r1 has two stages and r2 has one
r1_stage_entry = next(e for e in summary["stage_table"] if e["request_id"] == "r1")
r2_stage_entry = next(e for e in summary["stage_table"] if e["request_id"] == "r2")
assert len(r1_stage_entry["stages"]) == 2
assert len(r2_stage_entry["stages"]) == 1
def test_log_timing_summary_skips_tables_unless_debug(monkeypatch) -> None:
from vllm_omni.metrics import stats
agg = OrchestratorAggregator(num_stages=1, log_stats=True, wall_start_ts=0.0, final_stage_id_for_e2e=0)
agg.stage_first_ts[0] = 0.0
agg.stage_last_ts[0] = 0.02
agg.on_stage_metrics(
0,
"r1",
StageRequestStats(
batch_id=1,
batch_size=1,
num_tokens_in=3,
num_tokens_out=3,
stage_gen_time_ms=20.0,
rx_transfer_bytes=0,
rx_decode_time_ms=0.0,
rx_in_flight_time_ms=0.0,
stage_stats=StageStats(),
),
)
agg.on_finalize_request(0, "r1", req_start_ts=0.0)
tables, lines = [], []
def record_table(*args, **kwargs):
tables.append(args)
return ""
monkeypatch.setattr(stats, "_format_table", record_table)
monkeypatch.setattr(stats.logger, "info", lambda msg, *args: lines.append(msg % args))
monkeypatch.setattr(stats.logger, "isEnabledFor", lambda level: False)
agg.log_timing_summary()
assert not tables
assert len(lines) == 1 and lines[0].startswith("[OmniTiming] req=r1 total=")