Skip to content

Instantly share code, notes, and snippets.

@Dixon3
Created June 23, 2026 07:17
Show Gist options
  • Select an option

  • Save Dixon3/28d56cad1390afa9b2c645698a1bca5d to your computer and use it in GitHub Desktop.

Select an option

Save Dixon3/28d56cad1390afa9b2c645698a1bca5d to your computer and use it in GitHub Desktop.
python/sglang/srt/managers/scheduler_components/request_receiver.py fix for shm race conditions
from __future__ import annotations
from dataclasses import dataclass
from http import HTTPStatus
from typing import (
TYPE_CHECKING,
Any,
Callable,
List,
Optional,
Union,
)
import zmq
from torch.distributed import barrier
from sglang.srt.disaggregation.utils import prepare_abort
from sglang.srt.managers.io_struct import (
BatchTokenizedEmbeddingReqInput,
BatchTokenizedGenerateReqInput,
TokenizedEmbeddingReqInput,
TokenizedGenerateReqInput,
)
from sglang.srt.managers.mm_utils import (
has_shm_features,
unwrap_shm_features,
)
from sglang.srt.utils import (
broadcast_pyobj,
point_to_point_pyobj,
)
if TYPE_CHECKING:
from sglang.srt.configs.model_config import ModelConfig
from sglang.srt.distributed.parallel_state_wrapper import ParallelState
from sglang.srt.server_args import ServerArgs
from sglang.test.scripted_runtime.scheduler_hook import ScriptedSchedulerHook
from sglang.test.scripted_runtime.tokenizer_recv_proxy import (
ScriptedTokenizerRecvProxy,
)
@dataclass(kw_only=True, slots=True, frozen=True)
class SchedulerRequestReceiver:
recv_from_tokenizer: Union[zmq.Socket, "ScriptedTokenizerRecvProxy"]
recv_from_rpc: Optional[zmq.Socket]
recv_skipper: Any
input_blocker: Any
mm_receiver: Any
ps: "ParallelState"
tp_group: Any
tp_cpu_group: Any
attn_tp_group: Any
attn_tp_cpu_group: Any
attn_cp_group: Any
attn_cp_cpu_group: Any
world_group: Any
server_args: "ServerArgs"
model_config: "ModelConfig"
max_recv_per_poll: int
stream_output: Callable[..., None]
get_last_forward_mode: Callable[[], Any]
scripted_scheduler_hook: Optional["ScriptedSchedulerHook"] = None
def recv_limit_reached(self, num_recv_reqs: int) -> bool:
if self.max_recv_per_poll < 0:
return False
return num_recv_reqs >= self.max_recv_per_poll
def recv_requests(
self,
) -> List[Union[TokenizedGenerateReqInput, TokenizedEmbeddingReqInput, Any]]:
"""Receive results at tp_rank = 0 and broadcast it to all other TP ranks."""
if self.scripted_scheduler_hook is not None:
self.scripted_scheduler_hook.step()
if self.recv_skipper is not None:
if not self.recv_skipper.handle(self.get_last_forward_mode()):
return []
recv_reqs = self._pull_raw_reqs()
if self.input_blocker is not None:
recv_reqs = self.input_blocker.handle(recv_reqs)
recv_reqs = self._broadcast_reqs_across_ranks(recv_reqs)
recv_reqs = self._apply_mm_receiver(recv_reqs)
self._finalize_shm_features(recv_reqs)
return recv_reqs
def _pull_raw_reqs(self) -> Optional[List]:
if self.ps.pp_rank == 0:
if self.ps.attn_tp_rank == 0 and self.ps.attn_cp_rank == 0:
recv_reqs = []
while True:
try:
if self.recv_limit_reached(len(recv_reqs)):
break
recv_req = self.recv_from_tokenizer.recv_pyobj(zmq.NOBLOCK)
except zmq.ZMQError:
break
recv_reqs.append(recv_req)
while True:
try:
if self.recv_limit_reached(len(recv_reqs)):
break
recv_rpc = self.recv_from_rpc.recv_pyobj(zmq.NOBLOCK)
except zmq.ZMQError:
break
recv_reqs.append(recv_rpc)
else:
recv_reqs = None
else:
if self.ps.attn_tp_rank == 0 and self.ps.attn_cp_rank == 0:
dp_offset = self.ps.attn_dp_rank * self.ps.attn_tp_size
recv_reqs = point_to_point_pyobj(
[],
self.ps.pp_rank * self.ps.tp_size + dp_offset,
self.world_group.cpu_group,
(self.ps.pp_rank - 1) * self.ps.tp_size + dp_offset,
self.ps.pp_rank * self.ps.tp_size + dp_offset,
)
else:
recv_reqs = None
return recv_reqs
def _broadcast_reqs_across_ranks(self, recv_reqs: Optional[List]) -> List:
if self.server_args.enable_dp_attention:
if self.ps.attn_tp_rank == 0 and self.ps.attn_cp_rank == 0:
work_reqs, control_reqs = self._split_work_and_control_reqs(recv_reqs)
else:
work_reqs = None
control_reqs = None
if self.ps.attn_tp_size != 1:
work_reqs = broadcast_pyobj(
work_reqs,
self.attn_tp_group.rank,
self.attn_tp_cpu_group,
src=self.attn_tp_group.ranks[0],
)
if self.ps.attn_cp_size != 1:
work_reqs = broadcast_pyobj(
work_reqs,
self.attn_cp_group.rank,
self.attn_cp_cpu_group,
src=self.attn_cp_group.ranks[0],
)
# When dp_attention_local_control_broadcast is enabled, each DP
# group leader already receives control messages from the DP
# controller, so we broadcast within attn_tp_group + attn_cp_group
# instead of the full tp_group. This avoids an expensive
# all-ranks gloo sync.
_local_ctrl = self.server_args.enable_dp_attention_local_control_broadcast
if _local_ctrl:
if self.ps.attn_tp_size != 1:
control_reqs = broadcast_pyobj(
control_reqs,
self.attn_tp_group.rank,
self.attn_tp_cpu_group,
src=self.attn_tp_group.ranks[0],
)
if self.ps.attn_cp_size != 1:
control_reqs = broadcast_pyobj(
control_reqs,
self.attn_cp_group.rank,
self.attn_cp_cpu_group,
src=self.attn_cp_group.ranks[0],
)
elif self.ps.tp_size != 1:
control_reqs = broadcast_pyobj(
control_reqs,
self.tp_group.rank,
self.tp_cpu_group,
src=self.tp_group.ranks[0],
)
recv_reqs = work_reqs + control_reqs
elif self.ps.tp_size != 1:
recv_reqs = broadcast_pyobj(
recv_reqs,
self.tp_group.rank,
self.tp_cpu_group,
src=self.tp_group.ranks[0],
)
return recv_reqs
def _apply_mm_receiver(self, recv_reqs: List) -> List:
# Process MM requests under EPD-disaggregation mode
if (
self.ps.pp_rank == 0
and self.server_args.language_only
and self.server_args.encoder_transfer_backend
in ["zmq_to_scheduler", "mooncake"]
):
recv_reqs, abort_reqs = self.mm_receiver.process_waiting_requests(recv_reqs)
for req, error_msg, error_code in abort_reqs:
status_code = (
HTTPStatus.BAD_REQUEST
if error_code == 400
else HTTPStatus.INTERNAL_SERVER_ERROR
)
prepare_abort(req, error_msg, status_code=status_code)
self.stream_output([req], req.return_logprob)
return recv_reqs
def _finalize_shm_features(self, recv_reqs: Optional[List]) -> None:
# Unwrap shared memory features AFTER all broadcasts complete,
# so that ShmPointerMMData metadata (not full tensor data) is what
# gets serialized during broadcast_pyobj.
#
# ---------------------------------------------------------------
# DP-attention multimodal /psm_* unlink-vs-open race fix.
#
# The original code skipped the barrier under DP-attention, trusting
# the step-3 control_reqs broadcast on tp_cpu_group to act as an
# implicit global barrier between every rank's shm_open (steps 1-2,
# the per-attn_tp_group work_reqs broadcast) and any rank's
# materialize()/shm_unlink here. That invariant does NOT hold:
# * with enable_dp_attention_local_control_broadcast the step-3
# broadcast is LOCAL (attn_tp_group), so there is no global sync
# across DP groups at all; and
# * even without it, independent DP groups run forward passes of
# very different lengths, so a fast group can reach shm_unlink
# before a slow group finishes shm_open on the same segment.
# Result: FileNotFoundError: '/psm_XXXXXXXX' in __setstate__ ->
# scheduler exception -> SIGQUIT -> whole engine dies.
#
# Fix: under DP-attention, run a GLOBAL barrier on tp_cpu_group on
# EVERY rank, EVERY iteration, before any unwrap/materialize/unlink.
# It must be unconditional (gated only on server/model config, which
# is identical on all ranks) because recv_reqs and has_shm_features()
# differ per DP group -- gating a collective on them would deadlock.
# Cost: one extra all-ranks gloo barrier per scheduler step while a
# multimodal model is served; acceptable for correctness.
if (
self.server_args.enable_dp_attention
and self.ps.tp_size > 1
and self.model_config.is_multimodal
):
barrier(group=self.tp_cpu_group)
if recv_reqs:
# Non-DP-attention path (unchanged): a single broadcast_pyobj on
# tp_cpu_group where the source rank returns the original objects
# immediately while other ranks are still in pickle.loads
# (-> __setstate__ -> shm_open). Without a barrier the source can
# call materialize() / shm_unlink before others open the segment.
# recv_reqs is consistent across all ranks here (same broadcast),
# so the guard is deadlock-free.
if (
not self.server_args.enable_dp_attention
and self.ps.tp_size > 1
and self.model_config.is_multimodal
and has_shm_features(recv_reqs)
):
barrier(group=self.tp_cpu_group)
for req in recv_reqs:
unwrap_shm_features(req)
def _split_work_and_control_reqs(self, recv_reqs: List):
work_reqs = [
req
for req in recv_reqs
if isinstance(
req,
(
TokenizedGenerateReqInput,
TokenizedEmbeddingReqInput,
BatchTokenizedGenerateReqInput,
BatchTokenizedEmbeddingReqInput,
),
)
]
control_reqs = [
req
for req in recv_reqs
if not isinstance(
req,
(
TokenizedGenerateReqInput,
TokenizedEmbeddingReqInput,
BatchTokenizedGenerateReqInput,
BatchTokenizedEmbeddingReqInput,
),
)
]
return work_reqs, control_reqs
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment