Files
bench/tests/test_specdec.py
T
Karti Tripathi 006feee0f7
CI / verify (push) Successful in 24s
CI / deploy (push) Failing after 1m14s
Lumbridge Bench
2026-08-04 00:44:07 -07:00

103 lines
4.1 KiB
Python

"""Speculative-decoding counter parsing.
The parser reads a live Prometheus payload, so its failure modes are all "the server said
something slightly different than expected". Each of these is a shape a real /metrics endpoint
actually produces.
python3 -m unittest discover -s tests -v
"""
from __future__ import annotations
import sys
import unittest
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
from kbench.specdec import SpecCounters, metrics_url, parse_metrics # noqa: E402
PAYLOAD = """\
# HELP vllm:spec_decode_num_draft_tokens_total Number of draft tokens.
# TYPE vllm:spec_decode_num_draft_tokens_total counter
vllm:spec_decode_num_draft_tokens_total{model_name="qwen"} 1000.0
# HELP vllm:spec_decode_num_accepted_tokens_total Number accepted.
# TYPE vllm:spec_decode_num_accepted_tokens_total counter
vllm:spec_decode_num_accepted_tokens_total{model_name="qwen"} 720.0
vllm:num_requests_running{model_name="qwen"} 4.0
"""
class Parsing(unittest.TestCase):
def test_reads_both_counters(self):
c = parse_metrics(PAYLOAD)
self.assertEqual((c.draft, c.accepted), (1000.0, 720.0))
self.assertEqual(c.acceptance_rate, 0.72)
def test_ignores_comments_and_unrelated_series(self):
c = parse_metrics(PAYLOAD)
self.assertEqual(c.draft, 1000.0, "an unrelated metric leaked into the total")
def test_sums_across_label_sets(self):
# A server hosting two models exposes one series each. Summing is correct, and taking
# the first would silently measure only one of them.
two = PAYLOAD + (
'vllm:spec_decode_num_draft_tokens_total{model_name="b"} 500.0\n'
'vllm:spec_decode_num_accepted_tokens_total{model_name="b"} 250.0\n'
)
c = parse_metrics(two)
self.assertEqual((c.draft, c.accepted), (1500.0, 970.0))
def test_no_speculative_counters_means_none_not_zero(self):
# An engine without speculative decoding must report "no data". Zero would render as a
# 0% acceptance rate, which reads as a broken draft model rather than an absent one.
self.assertIsNone(parse_metrics("vllm:num_requests_running 1.0\n"))
def test_a_missing_half_is_not_usable(self):
self.assertIsNone(
parse_metrics('vllm:spec_decode_num_draft_tokens_total{m="x"} 10.0\n')
)
def test_malformed_values_do_not_raise(self):
payload = (
'vllm:spec_decode_num_draft_tokens_total{m="x"} not-a-number\n'
'vllm:spec_decode_num_accepted_tokens_total{m="x"} 5.0\n'
)
self.assertIsNone(parse_metrics(payload))
def test_unlabelled_series_are_read(self):
payload = (
"vllm:spec_decode_num_draft_tokens_total 100.0\n"
"vllm:spec_decode_num_accepted_tokens_total 50.0\n"
)
self.assertEqual(parse_metrics(payload).acceptance_rate, 0.5)
class WindowedRate(unittest.TestCase):
def test_the_delta_is_what_gets_reported(self):
# Lifetime counters. Reporting the total would fold warm-up and every earlier
# concurrency level into this point's number.
before = SpecCounters(accepted=700.0, draft=1000.0)
after = SpecCounters(accepted=1600.0, draft=2000.0)
window = after - before
self.assertEqual(window.acceptance_rate, 0.9)
self.assertNotEqual(window.acceptance_rate, after.acceptance_rate)
def test_no_drafting_in_the_window_is_none(self):
same = SpecCounters(accepted=10.0, draft=10.0)
self.assertIsNone((same - same).acceptance_rate)
class MetricsUrl(unittest.TestCase):
def test_strips_the_openai_prefix(self):
# base_url points at the OpenAI-compatible surface; /metrics is at the server root.
self.assertEqual(metrics_url("http://host:8001/v1"), "http://host:8001/metrics")
self.assertEqual(metrics_url("http://host:8001/v1/"), "http://host:8001/metrics")
def test_leaves_a_bare_root_alone(self):
self.assertEqual(metrics_url("http://host:8001"), "http://host:8001/metrics")
if __name__ == "__main__":
unittest.main()