[CI] Share VLM engines and prune launch matrices on the per-commit H100/H200 suites (#33944)

This commit is contained in:
Liangsheng Yin
2026-08-07 01:22:22 -07:00
committed by GitHub
parent 3ed2a0adf3
commit 7395ee833e
19 changed files with 134 additions and 73 deletions
@@ -51,6 +51,15 @@ LEAK_FILLER = (
"We promptly judged antique ivory buckles for the next prize. "
) * 20
SWA_MODEL = "openai/gpt-oss-20b"
# Common gpt-oss-20b launch args. Matches TestSessionLatency/TestSWARadixCacheKL.
SWA_COMMON_ARGS = [
"--mem-fraction-static",
"0.70",
"--cuda-graph-backend-prefill=disabled",
]
ABORT_REPRO_CONTEXT_LEN = 512
ABORT_REPRO_PAGE_SIZE = 256
ABORT_REPRO_GEN_LEN = 4
@@ -24,7 +24,7 @@ from sglang.test.test_utils import (
)
# CI Registration — large suite to fit the integration test's server startup.
register_cuda_ci(est_time=79, stage="base-b", runner_config="1-gpu-large")
register_cuda_ci(est_time=60, stage="base-b", runner_config="1-gpu-large")
register_amd_ci(est_time=200, suite="stage-c-test-large-8-gpu-amd-mi35x")
@@ -388,7 +388,7 @@ class TestBreakableCudaGraph(CustomTestCase):
base_url=self.base_url,
model=self.model,
eval_name="mgsm_en",
num_examples=1319,
num_examples=200,
num_threads=1024,
)
@@ -30,7 +30,7 @@ from sglang.test.lora_utils import (
)
from sglang.test.test_utils import CustomTestCase
register_cuda_ci(est_time=48, stage="base-b", runner_config="1-gpu-large")
register_cuda_ci(est_time=380, stage="base-b", runner_config="1-gpu-large")
register_amd_ci(est_time=75, suite="stage-b-test-1-gpu-small-amd")
@@ -28,7 +28,7 @@ from sglang.test.test_utils import (
)
# CI Registration
register_cuda_ci(est_time=240, stage="base-b", runner_config="1-gpu-large")
register_cuda_ci(est_time=100, stage="base-b", runner_config="1-gpu-large")
STDOUT_FILENAME = "post_capture_kv_sizing_stdout.log"
STDERR_FILENAME = "post_capture_kv_sizing_stderr.log"
@@ -88,7 +88,7 @@ class TestPostCaptureKVSizing(CustomTestCase):
base_url=self.base_url,
model=self.model,
eval_name="gsm8k",
num_examples=500,
num_examples=200,
num_threads=1024,
)
metrics = run_eval(args)
@@ -31,7 +31,7 @@ from sglang.test.test_utils import (
popen_launch_server,
)
register_cuda_ci(est_time=600, stage="base-b", runner_config="1-gpu-large")
register_cuda_ci(est_time=250, stage="base-b", runner_config="1-gpu-large")
# Defaults to the HF `test` revision; override MODEL/REVISION to point at a
# local checkpoint. Empty REVISION drops the flag (for local paths).
@@ -7,13 +7,11 @@ from sglang.test.kits.eval_accuracy_kit import GSM8KMixin
from sglang.test.kits.spec_decoding_kit import SpecDecodingMixin
from sglang.test.server_fixtures.default_fixture import DefaultServerBase
register_cuda_ci(est_time=350, stage="base-c", runner_config="8-gpu-h200")
register_cuda_ci(est_time=200, stage="base-c", runner_config="8-gpu-h200")
class TestMiMoV2Flash(GSM8KMixin, SpecDecodingMixin, DefaultServerBase):
gsm8k_accuracy_thres = 0.75
gsm8k_num_questions = 1319
gsm8k_num_threads = 1319
model = "XiaomiMiMo/MiMo-V2-Flash"
other_args = [
@@ -14,7 +14,7 @@ from sglang.test.test_utils import (
write_github_step_summary,
)
register_cuda_ci(est_time=250, stage="base-c", runner_config="8-gpu-h200")
register_cuda_ci(est_time=160, stage="base-c", runner_config="8-gpu-h200")
MINIMAX_M25_MODEL_PATH = "MiniMaxAI/MiniMax-M2.5"
@@ -53,8 +53,8 @@ class TestMiniMaxM25Basic(CustomTestCase):
args = SimpleNamespace(
num_shots=20,
data_path=None,
num_questions=1400,
parallel=1400,
num_questions=200,
parallel=200,
max_new_tokens=512,
host="http://127.0.0.1",
port=int(self.base_url.split(":")[-1]),
@@ -7,7 +7,7 @@ from sglang.test.kits.kl_divergence_kit import KLDivergenceMixin
from sglang.test.kits.prefix_cache_branching_kit import PrefixCacheBranchingMixin
from sglang.test.server_fixtures.default_fixture import DefaultServerBase
register_cuda_ci(est_time=430, stage="base-c", runner_config="4-gpu-h100")
register_cuda_ci(est_time=290, stage="base-c", runner_config="4-gpu-h100")
QWEN3_NEXT_MODEL = "Qwen/Qwen3-Next-80B-A3B-Instruct"
@@ -69,6 +69,11 @@ class TestQwen3NextMTPTopk(
]
@unittest.skip(
"Manual-only: topk == 1 is covered by TestQwen3NextMTPLazyV2 and extra_buffer "
"by TestQwen3NextMTPTopk. Kept runnable locally for the plain (topk=1, "
"extra_buffer) baseline."
)
class TestQwen3NextMTPV2(GSM8KMixin, KLDivergenceMixin, DefaultServerBase):
model = QWEN3_NEXT_MODEL
gsm8k_accuracy_thres = 0.93
@@ -1,3 +1,9 @@
"""Per-commit streaming-session tests on a hybrid-SWA model.
Baseline + large-page retract + abort-leak repro stay per-commit; the
mixed-chunk retract variant lives in test_streaming_session_swa_extra.py.
"""
import unittest
from sglang.test.ci.ci_register import register_cuda_ci
@@ -9,20 +15,12 @@ from sglang.test.server_fixtures.streaming_session_fixture import (
ABORT_REPRO_CHUNKED_PREFILL_SIZE,
ABORT_REPRO_CONTEXT_LEN,
ABORT_REPRO_PAGE_SIZE,
SWA_COMMON_ARGS,
SWA_MODEL,
StreamingSessionServerBase,
)
register_cuda_ci(est_time=519, stage="base-b", runner_config="1-gpu-large")
SWA_MODEL = "openai/gpt-oss-20b"
# Common gpt-oss-20b launch args. Matches TestSessionLatency/TestSWARadixCacheKL.
SWA_COMMON_ARGS = [
"--mem-fraction-static",
"0.70",
"--cuda-graph-backend-prefill=disabled",
]
register_cuda_ci(est_time=390, stage="base-b", runner_config="1-gpu-large")
class TestStreamingSessionSWA(StreamingSessionServerBase, StreamingSessionKitMixin):
@@ -48,21 +46,6 @@ class TestStreamingSessionSWARetractLargePage(
env_overrides = [("SGLANG_TEST_RETRACT", True)]
class TestStreamingSessionSWARetractMixedChunk(
StreamingSessionServerBase, StreamingSessionKitMixin
):
"""SWA under retract decode with --enable-mixed-chunk."""
model = SWA_MODEL
extra_args = [
"--chunked-prefill-size",
"128",
"--enable-mixed-chunk",
*SWA_COMMON_ARGS,
]
env_overrides = [("SGLANG_TEST_RETRACT", True)]
class TestStreamingSessionSWAAbortLeakRepro(
StreamingSessionServerBase, AbortLeakReproKitMixin
):
@@ -0,0 +1,35 @@
"""Label-gated SWA streaming-session variants.
CUDA-only: gpt-oss-20b is not part of the AMD streaming-session coverage.
"""
import unittest
from sglang.test.ci.ci_register import register_cuda_ci
from sglang.test.kits.streaming_session_kit import StreamingSessionKitMixin
from sglang.test.server_fixtures.streaming_session_fixture import (
SWA_COMMON_ARGS,
SWA_MODEL,
StreamingSessionServerBase,
)
register_cuda_ci(est_time=130, stage="extra-a", runner_config="1-gpu-large")
class TestStreamingSessionSWARetractMixedChunk(
StreamingSessionServerBase, StreamingSessionKitMixin
):
"""SWA under retract decode with --enable-mixed-chunk."""
model = SWA_MODEL
extra_args = [
"--chunked-prefill-size",
"128",
"--enable-mixed-chunk",
*SWA_COMMON_ARGS,
]
env_overrides = [("SGLANG_TEST_RETRACT", True)]
if __name__ == "__main__":
unittest.main()
@@ -20,7 +20,7 @@ from sglang.test.vlm_utils import (
terminate_and_kill_process_tree,
)
register_cuda_ci(est_time=780, stage="base-b", runner_config="1-gpu-large")
register_cuda_ci(est_time=560, stage="base-b", runner_config="1-gpu-large")
class TestLlavaServer(ImageOpenAITestMixin):
@@ -43,17 +43,10 @@ class TestQwen3VLServer(ImageOpenAITestMixin, VideoOpenAITestMixin):
extra_args = ["--cuda-graph-max-bs-decode=4"]
class TestQwen3OmniServer(OmniOpenAITestMixin):
model = "Qwen/Qwen3-Omni-30B-A3B-Instruct"
extra_args = [ # workaround to fit into H100
"--mem-fraction-static=0.90",
"--disable-cuda-graph",
"--disable-fast-image-processor",
"--grammar-backend=none",
]
class TestQwen2VLContextLengthServer(CustomTestCase):
# --context-length 300 is calibrated to this model's mm-token expansion:
# it must sit above the warmup image's expanded length but below the test
# image's. A cheaper VLM needs the bound recalibrated, not just swapped.
@classmethod
def setUpClass(cls):
cls.model = "Qwen/Qwen2-VL-7B-Instruct"
@@ -0,0 +1,27 @@
"""Label-gated vision/omni server launches too expensive for the per-commit
budget; the per-commit set lives in test_vision_openai_server_a.py."""
import unittest
from sglang.test.ci.ci_register import register_cuda_ci
from sglang.test.vlm_utils import OmniOpenAITestMixin
register_cuda_ci(est_time=180, stage="extra-a", runner_config="1-gpu-large")
class TestQwen3OmniServer(OmniOpenAITestMixin):
model = "Qwen/Qwen3-Omni-30B-A3B-Instruct"
extra_args = [ # workaround to fit into H100
"--mem-fraction-static=0.90",
"--disable-cuda-graph",
"--disable-fast-image-processor",
"--grammar-backend=none",
]
# Delete the mixin so it is not collected as a test case in its own right.
del OmniOpenAITestMixin
if __name__ == "__main__":
unittest.main()
+34 -23
View File
@@ -37,8 +37,9 @@ from sglang.srt.entrypoints.openai.protocol import ChatCompletionRequest
from sglang.srt.parser.conversation import generate_chat_conv
from sglang.srt.utils.common import is_cuda, is_xpu
from sglang.srt.utils.hf_transformers_utils import _fix_added_tokens_encoding
from sglang.test.test_utils import CustomTestCase
register_cuda_ci(est_time=747, stage="base-b", runner_config="1-gpu-large")
register_cuda_ci(est_time=300, stage="base-b", runner_config="1-gpu-large")
IMAGE_MAN_IRONING_URL = "https://raw.githubusercontent.com/sgl-project/sgl-test-files/refs/heads/main/images/man_ironing_on_back_of_suv.png"
IMAGE_SGL_LOGO_URL = "https://raw.githubusercontent.com/sgl-project/sgl-test-files/refs/heads/main/images/sgl_logo.png"
@@ -52,6 +53,7 @@ class VLMInputTestBase:
chat_template = None
processor = None
visual = None # Should be a callable for precomputed embeddings
engine = None
@classmethod
def setUpClass(cls):
@@ -76,25 +78,34 @@ class VLMInputTestBase:
)
_fix_added_tokens_encoding(cls.processor.tokenizer)
cls._init_visual()
cls._start_engine()
@classmethod
def _init_visual(cls):
"""Override in subclass to set up cls.visual as a callable for precomputed embeddings."""
raise NotImplementedError
def setUp(self):
self.engine = Engine(
model_path=self.model_path,
chat_template=self.chat_template,
device=self.device.type,
@classmethod
def _start_engine(cls):
# One engine per class: every test only reads through it. The tests are
# sync rather than async because the tokenizer manager pins handle_loop
# to the loop of the first request, so a per-test loop would strand it.
cls.engine = Engine(
model_path=cls.model_path,
chat_template=cls.chat_template,
device=cls.device.type,
mem_fraction_static=0.8,
enable_multimodal=True,
disable_cuda_graph=True,
trust_remote_code=True,
)
def tearDown(self):
self.engine.shutdown()
@classmethod
def tearDownClass(cls):
# CustomTestCase runs tearDownClass even when setUpClass raised.
if cls.engine is not None:
cls.engine.shutdown()
cls.engine = None
def verify_response(self, output):
# The goal is to check that the model roughly understands:
@@ -157,25 +168,25 @@ class VLMInputTestBase:
return inputs, text
async def test_accepts_image(self):
def test_accepts_image(self):
req = self.get_completion_request()
conv = generate_chat_conv(req, template_name=self.chat_template)
text = conv.get_prompt()
output = await self.engine.async_generate(
output = self.engine.generate(
prompt=text,
image_data=self.main_image,
sampling_params=dict(temperature=0.0, max_new_tokens=512),
)
self.verify_response(output)
async def test_accepts_precomputed_embeddings(self):
def test_accepts_precomputed_embeddings(self):
req = self.get_completion_request()
processor_output, _ = self.get_processor_output(req=req)
with torch.inference_mode():
precomputed_embeddings = self.__class__.visual(processor_output)
output = await self.engine.async_generate(
output = self.engine.generate(
input_ids=processor_output["input_ids"][0].detach().cpu().tolist(),
image_data=[
self._precomputed_image_data(processor_output, precomputed_embeddings)
@@ -184,10 +195,10 @@ class VLMInputTestBase:
)
self.verify_response(output)
async def test_accepts_processor_output(self):
def test_accepts_processor_output(self):
req = self.get_completion_request()
processor_output, prompt = self.get_processor_output(req=req)
output = await self.engine.async_generate(
output = self.engine.generate(
input_ids=processor_output["input_ids"][0].detach().cpu().tolist(),
image_data=[self._processor_output_image_data(processor_output)],
sampling_params=dict(temperature=0.0, max_new_tokens=512),
@@ -207,7 +218,7 @@ class VLMInputTestBase:
raise NotImplementedError
class TestQwenVLUnderstandsImage(VLMInputTestBase, unittest.IsolatedAsyncioTestCase):
class TestQwenVLUnderstandsImage(VLMInputTestBase, CustomTestCase):
model_path = "Qwen/Qwen2.5-VL-3B-Instruct"
chat_template = "qwen2-vl"
@@ -234,7 +245,7 @@ class TestQwenVLUnderstandsImage(VLMInputTestBase, unittest.IsolatedAsyncioTestC
return dict(processor_output, format="processor_output")
class TestGemmaUnderstandsImage(VLMInputTestBase, unittest.IsolatedAsyncioTestCase):
class TestGemmaUnderstandsImage(VLMInputTestBase, CustomTestCase):
model_path = "google/gemma-3-4b-it"
chat_template = "gemma-it"
@@ -263,9 +274,7 @@ class TestGemmaUnderstandsImage(VLMInputTestBase, unittest.IsolatedAsyncioTestCa
# Updated Kimi-VL test to use the new input format.
class TestKimiVLImageUnderstandsImage(
VLMInputTestBase, unittest.IsolatedAsyncioTestCase
):
class TestKimiVLImageUnderstandsImage(VLMInputTestBase, CustomTestCase):
model_path = "moonshotai/Kimi-VL-A3B-Instruct"
chat_template = "kimi-vl"
@@ -322,7 +331,7 @@ class TestKimiVLImageUnderstandsImage(
# not for CI: too large
# class TestLlama4ImageUnderstandsImage(
# VLMInputTestBase, unittest.IsolatedAsyncioTestCase
# VLMInputTestBase, CustomTestCase
# ):
# # Allow overriding via env for local/offline runs.
# model_path = "meta-llama/Llama-4-Scout-17B-16E-Instruct"
@@ -364,7 +373,7 @@ class TestKimiVLImageUnderstandsImage(
# return dict(processor_output, format="processor_output")
# class TestLlavaUnderstandsImage(VLMInputTestBase, unittest.IsolatedAsyncioTestCase):
# class TestLlavaUnderstandsImage(VLMInputTestBase, CustomTestCase):
# model_path = "llava-hf/llava-1.5-7b-hf"
# chat_template = "vicuna_v1.1"
@@ -403,7 +412,7 @@ class TestKimiVLImageUnderstandsImage(
# return dict(processor_output, format="processor_output")
class TestInternVLUnderstandsImage(VLMInputTestBase, unittest.IsolatedAsyncioTestCase):
class TestInternVLUnderstandsImage(VLMInputTestBase, CustomTestCase):
model_path = "OpenGVLab/InternVL2-2B"
chat_template = "internvl-2-5"
@@ -427,6 +436,7 @@ class TestInternVLUnderstandsImage(VLMInputTestBase, unittest.IsolatedAsyncioTes
cls.model_path, trust_remote_code=True
)
cls._init_visual()
cls._start_engine()
@classmethod
def _init_visual(cls):
@@ -591,7 +601,7 @@ class TestInternVLUnderstandsImage(VLMInputTestBase, unittest.IsolatedAsyncioTes
@unittest.skip("temporarily disabled: NaN in next_token_logits")
class TestMiniCPMVUnderstandsImage(VLMInputTestBase, unittest.IsolatedAsyncioTestCase):
class TestMiniCPMVUnderstandsImage(VLMInputTestBase, CustomTestCase):
model_path = "openbmb/MiniCPM-V-4"
chat_template = "minicpmv"
@@ -618,6 +628,7 @@ class TestMiniCPMVUnderstandsImage(VLMInputTestBase, unittest.IsolatedAsyncioTes
cls.processor.tokenizer = get_tokenizer(cls.model_path, trust_remote_code=True)
_fix_added_tokens_encoding(cls.processor.tokenizer)
cls._init_visual()
cls._start_engine()
@classmethod
def _init_visual(cls):