Created
June 23, 2026 07:17
-
-
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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