[CI] Add Kimi-K3 MMMU-Pro accuracy coverage (#36284)

This commit is contained in:
Baizhou Zhang
2026-08-25 16:33:46 -07:00
committed by GitHub
parent aa718f7343
commit 2d88c79b3e
7 changed files with 253 additions and 80 deletions
@@ -1,8 +1,7 @@
"""B300 per-commit CI coverage for Kimi-K3 serving recipes.
Runs the Low Latency DSPARK, Balanced DCP/HiCache, and MegaMoE recipes on
eight B300 GPUs. Each server must preserve basic model quality on GSM8K, and
the Low Latency recipe must also preserve single-request decode performance.
Runs the Balanced DCP/HiCache and MegaMoE recipes on eight B300 GPUs, retaining
their GSM8K accuracy gates.
"""
import unittest
@@ -10,7 +9,6 @@ import unittest
from sglang.srt.utils import kill_process_tree
from sglang.test.ci.ci_register import register_cuda_ci
from sglang.test.kits.eval_accuracy_kit import GSM8KMixin
from sglang.test.kits.spec_decoding_kit import SpecDecodingMixin
from sglang.test.test_utils import (
DEFAULT_URL_FOR_TEST,
CustomTestCase,
@@ -37,59 +35,6 @@ def _stop_server(process):
_wait_for_gpu_idle_in_ci(timeout=GPU_IDLE_TIMEOUT)
class TestKimiK3B300LowLatency(GSM8KMixin, SpecDecodingMixin, CustomTestCase):
"""TP8 Low Latency recipe with DSPARK linear ReplaySSM speculation."""
gsm8k_score_threshold = 0.95
gsm8k_num_examples = 200
gsm8k_num_threads = 37
# Gated on GSM8K rather than on test_bs_1_speed below: a 200-question
# average holds steady when a numerics change moves where the single
# greedy prompt hits EOS.
gsm8k_accept_length_thres = 4.5
# Both scale with how far that one greedy prompt runs, and speed is
# end-to-end, so launch and TTFT are amortized over the output -- it sits
# well below the steady decode rate the server logs. Coarse guards only.
accept_length_thres = 4.0
bs_1_speed_thres = 300
@classmethod
def setUpClass(cls):
cls.model = MODEL_PATH
cls.base_url = DEFAULT_URL_FOR_TEST
cls.process = popen_launch_server(
cls.model,
cls.base_url,
timeout=SERVER_LAUNCH_TIMEOUT,
other_args=[
"--trust-remote-code",
"--tp-size",
"8",
"--mem-fraction-static",
"0.85",
"--model-loader-extra-config",
MODEL_LOADER_EXTRA_CONFIG,
"--reasoning-parser",
"kimi_k3",
"--tool-call-parser",
"kimi_k3",
"--mamba-full-memory-ratio",
"0.86",
"--speculative-algorithm",
"DSPARK",
"--speculative-draft-model-path",
DSPARK_DRAFT_MODEL,
"--speculative-dspark-block-size",
"7",
"--enable-linear-replayssm-spec",
],
)
@classmethod
def tearDownClass(cls):
_stop_server(getattr(cls, "process", None))
class TestKimiK3B300Balanced(GSM8KMixin, CustomTestCase):
"""TP8/DCP8 Balanced recipe with hierarchical cache."""
@@ -0,0 +1,89 @@
"""B300 per-commit CI coverage for the Kimi-K3 Low Latency recipe.
Runs the TP8 DSPARK recipe on eight B300 GPUs and checks MMMU-Pro quality,
speculative acceptance, and single-request decode performance.
"""
import unittest
from sglang.srt.utils import kill_process_tree
from sglang.test.ci.ci_register import register_cuda_ci
from sglang.test.kits.eval_accuracy_kit import MMMUProMixin
from sglang.test.kits.spec_decoding_kit import SpecDecodingMixin
from sglang.test.test_utils import (
DEFAULT_URL_FOR_TEST,
CustomTestCase,
_wait_for_gpu_idle_in_ci,
popen_launch_server,
)
register_cuda_ci(est_time=1800, stage="base-c", runner_config="8-gpu-b300")
MODEL_PATH = "moonshotai/Kimi-K3"
DSPARK_DRAFT_MODEL = "RadixArk/Kimi-K3-DSpark"
MODEL_LOADER_EXTRA_CONFIG = '{"enable_multithread_load": true, "num_threads": 12}'
SERVER_LAUNCH_TIMEOUT = 3600
GPU_IDLE_TIMEOUT = 120
def _stop_server(process):
if process:
kill_process_tree(process.pid)
_wait_for_gpu_idle_in_ci(timeout=GPU_IDLE_TIMEOUT)
class TestKimiK3B300LowLatency(MMMUProMixin, SpecDecodingMixin, CustomTestCase):
"""TP8 Low Latency recipe with DSPARK linear ReplaySSM speculation."""
mmmu_pro_score_threshold = 0.75
mmmu_pro_num_examples = 200
mmmu_pro_load_preset_from_model_id = MODEL_PATH
# MMMU-Pro's long multimodal reasoning has a lower speculative average than
# GSM8K (2.62 in the first B300 run). Keep a workload-specific regression
# gate here; test_bs_1_speed below retains the stricter single-prompt gate.
mmmu_pro_accept_length_thres = 2.4
# Both scale with how far that one greedy prompt runs, and speed is
# end-to-end, so launch and TTFT are amortized over the output -- it sits
# well below the steady decode rate the server logs. Coarse guards only.
accept_length_thres = 4.0
bs_1_speed_thres = 300
@classmethod
def setUpClass(cls):
cls.model = MODEL_PATH
cls.base_url = DEFAULT_URL_FOR_TEST
cls.process = popen_launch_server(
cls.model,
cls.base_url,
timeout=SERVER_LAUNCH_TIMEOUT,
other_args=[
"--trust-remote-code",
"--tp-size",
"8",
"--mem-fraction-static",
"0.85",
"--model-loader-extra-config",
MODEL_LOADER_EXTRA_CONFIG,
"--reasoning-parser",
"kimi_k3",
"--tool-call-parser",
"kimi_k3",
"--mamba-full-memory-ratio",
"0.86",
"--speculative-algorithm",
"DSPARK",
"--speculative-draft-model-path",
DSPARK_DRAFT_MODEL,
"--speculative-dspark-block-size",
"7",
"--enable-linear-replayssm-spec",
],
)
@classmethod
def tearDownClass(cls):
_stop_server(getattr(cls, "process", None))
if __name__ == "__main__":
unittest.main()
@@ -7,7 +7,7 @@ from types import SimpleNamespace
from unittest.mock import patch
from sglang.test.ci.ci_register import register_cpu_ci
from sglang.test.run_eval import _run_sgl_eval
from sglang.test.run_eval import _run_sgl_eval, run_eval
from sglang.test.test_utils import CustomTestCase
register_cpu_ci(est_time=6, suite="base-b-test-cpu")
@@ -166,6 +166,53 @@ class TestRunSglEval(CustomTestCase):
self.assertIn(flag, cmd)
self.assertEqual(cmd[cmd.index(flag) + 1], value)
def test_model_preset_owns_model_and_sampling_defaults(self):
cmd = self._capture_cmd(
eval_name="mmmu_pro",
model=None,
num_examples=300,
num_threads=None,
temperature=None,
load_preset_from_model_id="moonshotai/Kimi-K3",
)
self.assertEqual(cmd[:3], ["sgl-eval", "run", "mmmu_pro"])
self.assertIn("--load-preset-from-model-id", cmd)
self.assertEqual(
cmd[cmd.index("--load-preset-from-model-id") + 1],
"moonshotai/Kimi-K3",
)
self.assertEqual(cmd[cmd.index("--num-examples") + 1], "300")
for flag in (
"--model",
"--num-threads",
"--temperature",
"--top-p",
"--max-tokens",
"--thinking",
):
self.assertNotIn(flag, cmd)
def test_non_preset_cli_keeps_legacy_top_p_default(self):
cmd = self._capture_cmd(top_p=None, _sgl_eval_from_cli=True)
self.assertIn("--top-p", cmd)
self.assertEqual(cmd[cmd.index("--top-p") + 1], "1.0")
@patch("sglang.test.run_eval._run_sgl_eval", return_value={"score": 0.8})
def test_run_eval_dispatches_hyphenated_mmmu_pro_name(self, mock_sgl_eval):
args = SimpleNamespace(
base_url="http://127.0.0.1:30000",
eval_name="mmmu-pro",
)
try:
result = run_eval(args)
except ValueError as exc:
self.fail(f"mmmu-pro must dispatch to sgl-eval: {exc}")
self.assertEqual(result, {"score": 0.8})
mock_sgl_eval.assert_called_once_with("mmmu_pro", args)
def test_thinking_auto_detected_from_model_name(self):
self.assertIn(
"--thinking", self._capture_cmd(model="Qwen/Qwen3.5-397B-A17B-FP8")
@@ -1,4 +1,4 @@
"""Unit tests for the GSM8K backend dispatch + sgl-eval skip in eval_accuracy_kit.
"""Unit tests for sgl-eval-backed accuracy mixin dispatch.
Hermetic (no server, no real sgl-eval install). These guard the behavior that
existing consumers rely on -- not the sgl-eval happy path, which the live
@@ -8,7 +8,8 @@ accuracy runs already cover:
the ~47 existing GSM8K consumers must never be silently rerouted.
2. The legacy ``gsm8k_accuracy_thres`` alias is still honored as the pass/fail
gate when the canonical ``gsm8k_score_threshold`` is unset.
3. The sgl-eval reasoning path skips (does not error) when sgl-eval is absent,
3. MMMU-Pro delegates model and sampling selection to a built-in model preset.
4. The sgl-eval reasoning path skips (does not error) when sgl-eval is absent,
so CI without the optional dependency stays green.
"""
@@ -20,7 +21,7 @@ import requests
from sglang.test.ci.ci_register import register_cpu_ci
from sglang.test.kits import eval_accuracy_kit as kit
from sglang.test.kits.eval_accuracy_kit import GPQAMixin, GSM8KMixin
from sglang.test.kits.eval_accuracy_kit import GPQAMixin, GSM8KMixin, MMMUProMixin
from sglang.test.test_utils import CustomTestCase
register_cpu_ci(est_time=5, suite="base-a-test-cpu")
@@ -95,6 +96,39 @@ class TestEvalKitBackendDispatch(CustomTestCase):
with self.assertRaises(unittest.SkipTest):
host.test_gpqa()
def _run_mmmu_pro(self, score):
captured = {}
def fake_run_eval(args):
captured["args"] = args
return {"score": score}
host = _make_host(MMMUProMixin, "test_mmmu_pro")
host.base_url = "http://127.0.0.1:0"
host.model = "deployment-model"
host.mmmu_pro_score_threshold = 0.75
host.mmmu_pro_load_preset_from_model_id = "moonshotai/Kimi-K3"
with patch.object(kit, "run_eval", side_effect=fake_run_eval), patch.object(
kit.requests, "get", side_effect=_fake_get
):
host.test_mmmu_pro()
return captured["args"]
def test_mmmu_pro_uses_kimi_preset_and_300_examples(self):
args = self._run_mmmu_pro(0.80)
self.assertEqual(args.eval_name, "mmmu_pro")
self.assertEqual(args.load_preset_from_model_id, "moonshotai/Kimi-K3")
self.assertEqual(args.num_examples, 300)
self.assertIsNone(args.num_threads)
self.assertIsNone(args.model)
for attr in ("temperature", "top_p", "max_tokens", "reasoning_effort"):
self.assertFalse(hasattr(args, attr))
def test_mmmu_pro_score_threshold_gates_result(self):
with self.assertRaises(AssertionError):
self._run_mmmu_pro(0.74)
if __name__ == "__main__":
unittest.main()