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
18 changes: 18 additions & 0 deletions python/sglang/srt/configs/model_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -452,6 +452,9 @@ def __init__(
self.is_multimodal_piecewise_cuda_graph_supported = enable_multimodal and (
is_multimodal_piecewise_cuda_graph_supported(self.hf_config.architectures)
)
self.is_multimodal_breakable_cuda_graph_supported = enable_multimodal and (
is_multimodal_breakable_cuda_graph_supported(self.hf_config.architectures)
)
self.dtype = _get_and_verify_dtype(self.hf_text_config, dtype)

# Derive context length and model shapes
Expand Down Expand Up @@ -1757,6 +1760,13 @@ def is_generation_model(model_architectures: List[str], is_embedding: bool = Fal
"MiniMaxM3SparseForConditionalGeneration",
]

# Multimodal archs whose LM prefill is validated under breakable CUDA graph;
# embed-carrying batches are rejected at replay (can_run_graph) and run eager.
multimodal_breakable_cuda_graph_supported_model_archs = [
"Qwen3_5ForConditionalGeneration",
"Qwen3_5MoeForConditionalGeneration",
]

if external_mm_model_arch := envs.SGLANG_EXTERNAL_MM_MODEL_ARCH.get():
multimodal_model_archs.append(external_mm_model_arch)

Expand Down Expand Up @@ -1823,6 +1833,14 @@ def is_multimodal_piecewise_cuda_graph_supported(model_architectures: List[str])
)


def is_multimodal_breakable_cuda_graph_supported(model_architectures: List[str]):
"""Whether a multimodal arch may keep prefill breakable CUDA graph enabled."""
return any(
arch in multimodal_breakable_cuda_graph_supported_model_archs
for arch in model_architectures
)


# SequenceClassification models that use CrossEncodingPooler
_cross_encoding_pooler_archs = [
"BertForSequenceClassification",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -612,6 +612,12 @@ def can_run_graph(self, forward_batch: ForwardBatch) -> bool:
return False
if forward_batch.replace_embeds is not None:
return False
# The captured graph embeds from input_ids only; multimodal batches
# merge mm embeddings in the outer wrapper, which capture bypasses.
if forward_batch.mm_inputs is not None and any(
x is not None for x in forward_batch.mm_inputs
):
return False
# tc_piecewise captures with ForwardMode.EXTEND and spec_info=None.
if forward_batch.forward_mode.is_target_verify():
return False
Expand Down
8 changes: 6 additions & 2 deletions python/sglang/srt/server_args.py
Original file line number Diff line number Diff line change
Expand Up @@ -3627,8 +3627,12 @@ def _disable_breakable_cudagraph_if_incompatible(self):
),
# DP-attn × BCG capture/replay not yet validated.
("DP attention", lambda: self._resolved().enable_dp_attention),
# Multimodal prefill replay faults under BCG.
("multimodal model", lambda: self.get_model_config().is_multimodal),
# Multimodal prefill replay faults under BCG; allowlisted archs opt back in.
(
"multimodal model",
lambda: self.get_model_config().is_multimodal
and not self.get_model_config().is_multimodal_breakable_cuda_graph_supported,
),
]
for name, predicate in rules:
if predicate():
Expand Down
58 changes: 58 additions & 0 deletions test/registered/unit/server_args/test_server_args.py
Original file line number Diff line number Diff line change
Expand Up @@ -1181,6 +1181,64 @@ def test_cuda_graph_explicit_decode_backend_survives_prefill_role(self):
self.assertIn((Phase.DECODE, "backend"), args._cuda_graph_config_locked)


class TestBreakableCudaGraphMultimodalAllowlist(CustomTestCase):
"""The BCG "multimodal model" rule exempts archs on the BCG multimodal
opt-in allowlist (multimodal_breakable_cuda_graph_supported_model_archs)."""

def _handled_args(self, *, architectures, is_multimodal, allowlisted):
args = ServerArgs(model_path="dummy")
args.model_config = SimpleNamespace(
hf_config=SimpleNamespace(architectures=architectures),
is_piecewise_cuda_graph_disabled_model=False,
is_multimodal=is_multimodal,
is_multimodal_piecewise_cuda_graph_supported=False,
is_multimodal_breakable_cuda_graph_supported=allowlisted,
)
with (
patch("sglang.srt.utils.is_cuda", return_value=True),
patch.object(ServerArgs, "use_mla_backend", return_value=False),
):
args._handle_cuda_graph_config()
return args

def test_multimodal_arch_disables_prefill_breakable(self):
args = self._handled_args(
architectures=["Qwen3VLForConditionalGeneration"],
is_multimodal=True,
allowlisted=False,
)
self.assertEqual(args.cuda_graph_config.prefill.backend, Backend.DISABLED)

def test_allowlisted_multimodal_arch_keeps_prefill_breakable(self):
args = self._handled_args(
architectures=["Qwen3_5MoeForConditionalGeneration"],
is_multimodal=True,
allowlisted=True,
)
self.assertEqual(args.cuda_graph_config.prefill.backend, Backend.BREAKABLE)

def test_allowlist_membership(self):
from sglang.srt.configs.model_config import (
is_multimodal_breakable_cuda_graph_supported,
)

self.assertTrue(
is_multimodal_breakable_cuda_graph_supported(
["Qwen3_5MoeForConditionalGeneration"]
)
)
self.assertTrue(
is_multimodal_breakable_cuda_graph_supported(
["Qwen3_5ForConditionalGeneration"]
)
)
self.assertFalse(
is_multimodal_breakable_cuda_graph_supported(
["Qwen3VLForConditionalGeneration"]
)
)


class TestCutedslMoeMaxNumTokens(CustomTestCase):
"""The shared CuteDSL MoE per-forward token bound. Fields are set directly
to exercise the math independently of __post_init__ resolution.
Expand Down
Loading