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
204 changes: 196 additions & 8 deletions docker/patch/latest/sglang.patch
Original file line number Diff line number Diff line change
Expand Up @@ -719,7 +719,7 @@ index ca54a931b..6c102a251 100644
if enable_dual_stream:
current_stream = torch.cuda.current_stream()
diff --git a/python/sglang/srt/layers/attention/nsa/utils.py b/python/sglang/srt/layers/attention/nsa/utils.py
index 00ef96f9b..9885fcd0d 100644
index 00ef96f9b..5adaec804 100644
--- a/python/sglang/srt/layers/attention/nsa/utils.py
+++ b/python/sglang/srt/layers/attention/nsa/utils.py
@@ -54,7 +54,12 @@ def can_nsa_prefill_cp_round_robin_split(forward_batch: "ForwardBatch"):
Expand Down Expand Up @@ -775,20 +775,32 @@ index 00ef96f9b..9885fcd0d 100644
if can_nsa_prefill_cp_round_robin_split(forward_batch):
tokens = ceil_div(tokens, attn_cp_size)
return tokens
@@ -152,10 +166,9 @@ class NSAContextParallelMetadata:
@@ -152,10 +166,20 @@ class NSAContextParallelMetadata:

def can_cp_split(seq_len: int, cp_size: int, use_nsa: bool, forward_batch):
if is_nsa_prefill_cp_round_robin_split():
+ if seq_len < cp_size or seq_len % cp_size != 0:
+ return False
cur_cp_seq_len = seq_len // cp_size
- cur_cp_seq_len = seq_len // cp_size
- assert (
- seq_len % cp_size == 0
- ), f"seq_len {seq_len} is not divisible by cp_size {cp_size} when nsa_prefill_cp_mode is round-robin-split"
+ # Use actual extend sequence length instead of (possibly padded) input_ids
+ # length to stay consistent with can_nsa_prefill_cp_round_robin_split(),
+ # which also checks sum(extend_seq_lens_cpu). When prepare_mlp_sync_batch
+ # pads input_ids to ceil_align(n, attn_cp_size), len(input_ids) can become
+ # divisible by cp_size even though the real extend length is not, causing
+ # hidden_states to be CP-split while the attention metadata is not.
+ actual_seq_len = (
+ sum(forward_batch.extend_seq_lens_cpu)
+ if forward_batch.extend_seq_lens_cpu is not None
+ else seq_len
+ )
+ if actual_seq_len < cp_size or actual_seq_len % cp_size != 0:
+ return False
+ cur_cp_seq_len = actual_seq_len // cp_size
else:
# TODO current just support prefill batch=1 and len(input_ids) > self.cp_size * 2
# Note: (self.cp_size * 2) To achieve load balancing for seq computation,
@@ -175,10 +188,6 @@ def can_cp_split(seq_len: int, cp_size: int, use_nsa: bool, forward_batch):
@@ -175,10 +199,6 @@ def can_cp_split(seq_len: int, cp_size: int, use_nsa: bool, forward_batch):

def cp_split_and_rebuild_data(forward_batch, input_: torch.Tensor):
if is_nsa_prefill_cp_round_robin_split():
Expand All @@ -799,7 +811,7 @@ index 00ef96f9b..9885fcd0d 100644
return nsa_cp_round_robin_split_data(input_)

input_list = list(
@@ -192,11 +201,6 @@ def cp_split_and_rebuild_data(forward_batch, input_: torch.Tensor):
@@ -192,11 +212,6 @@ def cp_split_and_rebuild_data(forward_batch, input_: torch.Tensor):

def cp_split_and_rebuild_position(forward_batch, positions: torch.Tensor):
if is_nsa_prefill_cp_round_robin_split():
Expand Down Expand Up @@ -2402,6 +2414,122 @@ index cc673a9ca..06c430d2c 100644
if hasattr(backend, "use_mha") and backend.use_mha:
return AttnForwardMethod.MHA_ONE_SHOT
return AttnForwardMethod.MLA
diff --git a/python/sglang/srt/models/deepseek_nextn.py b/python/sglang/srt/models/deepseek_nextn.py
index cb13a7c67..d62111471 100644
--- a/python/sglang/srt/models/deepseek_nextn.py
+++ b/python/sglang/srt/models/deepseek_nextn.py
@@ -29,6 +29,7 @@ from sglang.srt.layers.attention.nsa.utils import (
can_cp_split,
cp_all_gather_rerange_output,
cp_split_and_rebuild_data,
+ cp_split_and_rebuild_position,
is_nsa_enable_prefill_cp,
nsa_use_prefill_cp,
prepare_input_dp_with_cp_dsa,
@@ -160,6 +161,7 @@ class DeepseekModelNextN(nn.Module):

if nsa_use_prefill_cp(forward_batch, self.nsa_enable_prefill_cp):
hidden_states = cp_split_and_rebuild_data(forward_batch, hidden_states)
+ positions = cp_split_and_rebuild_position(forward_batch, positions)
residual = None
with get_global_expert_distribution_recorder().disable_this_region():
hidden_states, residual = self.decoder(
diff --git a/python/sglang/srt/models/glm4v_moe.py b/python/sglang/srt/models/glm4v_moe.py
index 324de18b4..c99723f49 100644
--- a/python/sglang/srt/models/glm4v_moe.py
+++ b/python/sglang/srt/models/glm4v_moe.py
@@ -52,11 +52,31 @@ class Glm4vMoeForConditionalGeneration(Glm4vForConditionalGeneration):
self.num_fused_shared_experts = 0
self.determine_num_fused_shared_experts()

- self.model = Glm4MoeModel(
- config,
- quant_config,
- prefix=add_prefix("language_model", prefix),
- )
+ if not self.config.encoder_only:
+ self.model = Glm4MoeModel(
+ config,
+ quant_config,
+ prefix=add_prefix("language_model", prefix),
+ )
+
+ if self.pp_group.is_last_rank:
+ if self.pp_group.world_size == 1 and self.config.tie_word_embeddings:
+ self.lm_head = self.model.embed_tokens
+ else:
+ self.lm_head = ParallelLMHead(
+ config.vocab_size,
+ config.hidden_size,
+ quant_config=quant_config,
+ prefix=add_prefix("lm_head", prefix),
+ use_attn_tp_group=get_global_server_args().enable_dp_lm_head,
+ )
+ else:
+ # ranks other than the last rank will have a placeholder layer
+ self.lm_head = PPMissingLayer()
+ else:
+ # encoder_only mode: no language model, so no lm_head needed
+ self.lm_head = None
+
self.visual = Glm4vVisionModel(
config.vision_config,
quant_config=quant_config,
@@ -64,21 +84,6 @@ class Glm4vMoeForConditionalGeneration(Glm4vForConditionalGeneration):
use_data_parallel=self.use_data_parallel,
)

- if self.pp_group.is_last_rank:
- if self.pp_group.world_size == 1 and self.config.tie_word_embeddings:
- self.lm_head = self.model.embed_tokens
- else:
- self.lm_head = ParallelLMHead(
- config.vocab_size,
- config.hidden_size,
- quant_config=quant_config,
- prefix=add_prefix("lm_head", prefix),
- use_attn_tp_group=get_global_server_args().enable_dp_lm_head,
- )
- else:
- # ranks other than the last rank will have a placeholder layer
- self.lm_head = PPMissingLayer()
-
self.logits_processor = LogitsProcessor(config)
self.pooler = Pooler(pooling_type=PoolingType.LAST, normalize=True)
self.is_mrope_enabled = "mrope_section" in self.config.rope_scaling
@@ -219,6 +224,11 @@ class Glm4vMoeForConditionalGeneration(Glm4vForConditionalGeneration):
# Skip loading extra bias for GPTQ models.
if name.endswith(".bias") and name not in params_dict:
continue
+ # Skip loading visual/language model weights
+ if (
+ self.config.encoder_only or self.config.language_only
+ ) and name not in params_dict:
+ continue
if name not in params_dict:
continue

@@ -234,6 +244,8 @@ class Glm4vMoeForConditionalGeneration(Glm4vForConditionalGeneration):
param_name, weight_name, expert_id, shard_id = mapping
if weight_name not in name:
continue
+ if "visual" in name or self.config.encoder_only:
+ continue

# Mark as expert weight regardless of whether we can process it
is_expert_weight = True
@@ -265,6 +277,11 @@ class Glm4vMoeForConditionalGeneration(Glm4vForConditionalGeneration):
# Skip loading extra bias for GPTQ models.
if name.endswith(".bias") and name not in params_dict:
continue
+ # Skip loading mm/language parameters
+ if (
+ self.config.encoder_only or self.config.language_only
+ ) and name not in params_dict:
+ continue
if name not in params_dict:
continue

diff --git a/python/sglang/srt/models/gpt_oss.py b/python/sglang/srt/models/gpt_oss.py
index 2cf813bce..1250c49e4 100644
--- a/python/sglang/srt/models/gpt_oss.py
Expand Down Expand Up @@ -2517,7 +2645,7 @@ index 2cf813bce..1250c49e4 100644
weights_out_dict = dict(weights_in)

diff --git a/python/sglang/srt/models/qwen3_5.py b/python/sglang/srt/models/qwen3_5.py
index f012254..1dad8bb 100644
index f01225487..1dad8bb8e 100644
--- a/python/sglang/srt/models/qwen3_5.py
+++ b/python/sglang/srt/models/qwen3_5.py
@@ -372,6 +372,7 @@ class Qwen3_5LinearDecoderLayer(nn.Module):
Expand Down Expand Up @@ -2620,6 +2748,66 @@ index d641826e3..3abc39ef3 100644
hidden_states, residual = layer(
positions,
hidden_states,
diff --git a/python/sglang/srt/multimodal/processors/glm4v.py b/python/sglang/srt/multimodal/processors/glm4v.py
index 33cce6fe2..0970c4550 100644
--- a/python/sglang/srt/multimodal/processors/glm4v.py
+++ b/python/sglang/srt/multimodal/processors/glm4v.py
@@ -1,6 +1,9 @@
from typing import List, Union

+import torch
+
from sglang.srt.layers.rotary_embedding import MRotaryEmbedding
+from sglang.srt.managers.schedule_batch import Modality, MultimodalDataItem
from sglang.srt.models.glm4v import Glm4vForConditionalGeneration
from sglang.srt.models.glm4v_moe import Glm4vMoeForConditionalGeneration
from sglang.srt.multimodal.processors.base_processor import (
@@ -45,6 +48,8 @@ class Glm4vImageProcessor(SGLangBaseProcessor):
self.IMAGE_END_TOKEN_ID = hf_config.image_end_token_id
self.VIDEO_START_TOKEN_ID = hf_config.video_start_token_id
self.VIDEO_END_TOKEN_ID = hf_config.video_end_token_id
+ self.IM_START_TOKEN_ID = self.IMAGE_START_TOKEN_ID
+ self.IM_END_TOKEN_ID = self.IMAGE_END_TOKEN_ID

# Vision config
self.IMAGE_FACTOR = 28
@@ -59,6 +64,36 @@ class Glm4vImageProcessor(SGLangBaseProcessor):
video_token_id=self.IM_TOKEN_ID,
).build(_processor)

+ def get_mm_data(self, prompt, embeddings, img_grid_thw):
+ input_ids, offsets = self.build_input_ids(prompt, img_grid_thw)
+ mm_items = [
+ MultimodalDataItem(
+ modality=Modality.IMAGE,
+ offsets=offsets,
+ precomputed_embeddings=embeddings,
+ )
+ ]
+
+ input_ids_tensor = torch.tensor(input_ids)
+ mrope_positions, mrope_position_delta = MRotaryEmbedding.get_rope_index_glm4v(
+ input_ids=input_ids_tensor.unsqueeze(0),
+ hf_config=self.hf_config,
+ image_grid_thw=img_grid_thw,
+ video_grid_thw=None,
+ attention_mask=None,
+ )
+ mrope_positions = mrope_positions.squeeze(1)
+
+ return {
+ "input_ids": input_ids,
+ "mm_items": mm_items,
+ "im_start_id": self.IM_START_TOKEN_ID,
+ "im_end_id": self.IM_END_TOKEN_ID,
+ "im_token_id": self.IM_TOKEN_ID,
+ "mrope_positions": mrope_positions,
+ "mrope_position_delta": mrope_position_delta,
+ }
+
async def process_mm_data_async(
self,
image_data: List[Union[str, bytes]],
diff --git a/python/sglang/srt/multimodal/processors/qwen_vl.py b/python/sglang/srt/multimodal/processors/qwen_vl.py
index 4395654e4..f9b5ea4ab 100644
--- a/python/sglang/srt/multimodal/processors/qwen_vl.py
Expand Down
2 changes: 1 addition & 1 deletion docker/version.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
nightly-dev-20260309a
nightly-dev-20260311a
8 changes: 4 additions & 4 deletions slime/ray/rollout.py
Original file line number Diff line number Diff line change
Expand Up @@ -1007,7 +1007,7 @@ def start_rollout_servers(args, pg) -> dict[str, RolloutServer]:

has_epd = model_cfg.has_encoder_disaggregation

def _make_group(group_cfg, overrides_extra=None):
def _make_group(group_cfg, router_ip, router_port, overrides_extra=None):
nonlocal engine_offset, gpu_offset
gpus_per_engine = group_cfg.num_gpus_per_engine
num_gpu_per_engine_local = min(gpus_per_engine, args.num_gpus_per_node)
Expand Down Expand Up @@ -1051,7 +1051,7 @@ def _make_group(group_cfg, overrides_extra=None):
for group_cfg in model_cfg.server_groups:
if group_cfg.worker_type != "encoder":
continue
group = _make_group(group_cfg)
group = _make_group(group_cfg, router_ip, router_port)
handles, port_cursors = group.start_engines(port_cursors)
if handles:
ray.get(handles)
Expand All @@ -1070,7 +1070,7 @@ def _make_group(group_cfg, overrides_extra=None):
if encoder_urls and group_cfg.worker_type == "prefill":
overrides_extra["language_only"] = True
overrides_extra["encoder_urls"] = encoder_urls
group = _make_group(group_cfg, overrides_extra=overrides_extra)
group = _make_group(group_cfg, router_ip, router_port, overrides_extra=overrides_extra)
handles, port_cursors = group.start_engines(port_cursors)
non_encoder_handles.extend(handles)
server_groups.append(group)
Expand All @@ -1081,7 +1081,7 @@ def _make_group(group_cfg, overrides_extra=None):
# No EPD — start all groups in one pass (original path).
all_init_handles: list = []
for group_cfg in model_cfg.server_groups:
group = _make_group(group_cfg)
group = _make_group(group_cfg, router_ip, router_port)
handles, port_cursors = group.start_engines(port_cursors)
all_init_handles.extend(handles)
server_groups.append(group)
Expand Down
4 changes: 3 additions & 1 deletion slime_plugins/mbridge/qwen3_5.py
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,9 @@ def _weight_name_mapping_mtp_mlp(self, name: str) -> list[str]:
if keyword in name:
if "{expert_id}" in mapping_names[0]:
expert_id = name.split("weight")[-1]
convert_names.extend([x.format(layer_number=layer_number, expert_id=expert_id) for x in mapping_names])
convert_names.extend(
[x.format(layer_number=layer_number, expert_id=expert_id) for x in mapping_names]
)
else:
convert_names.extend([x.format(layer_number=layer_number) for x in mapping_names])
break
Expand Down
Loading
Loading