94057c3d3e
PR Test (NPU) / check-changes (push) Has been cancelled
PR Test (NPU) / pr-gate (push) Has been cancelled
PR Test (NPU) / set-image-config (push) Has been cancelled
PR Test (NPU) / stage-b-test-1-npu-a2 (0) (push) Has been cancelled
PR Test (NPU) / stage-b-test-1-npu-a2 (1) (push) Has been cancelled
PR Test (NPU) / stage-b-test-2-npu-a2 (0) (push) Has been cancelled
PR Test (NPU) / stage-b-test-2-npu-a2 (1) (push) Has been cancelled
PR Test (NPU) / stage-b-test-4-npu-a3 (push) Has been cancelled
PR Test (NPU) / stage-b-test-16-npu-a3 (push) Has been cancelled
PR Test (NPU) / multimodal-gen-test-1-npu-a3 (push) Has been cancelled
PR Test (NPU) / multimodal-gen-test-2-npu-a3 (push) Has been cancelled
PR Test (Arm64) / pr-gate (push) Has been cancelled
PR Test (Arm64) / check-changes (push) Has been cancelled
PR Test (Arm64) / build-test (push) Has been cancelled
PR Test (sgl-router) / gate (push) Has been cancelled
PR Test (sgl-router) / tier-1 — lint (push) Has been cancelled
PR Test (sgl-router) / tier-2 — build + test (push) Has been cancelled
PR Test (sgl-router) / tier-3 — docker (placeholder) (push) Has been cancelled
PR Test (sgl-router) / tier-3 — k8s integration (push) Has been cancelled
PR Test (sgl-router) / tier-3 — e2e (push) Has been cancelled
PR Test (sgl-router) / finish (push) Has been cancelled
PR Test (NPU) / single-node-poc (map[name:qwen3_6_27b_w8a8_1p_in64k_out1k_50ms runner:linux-aarch64-a3-2 test_case:test/registered/ascend/performance/qwen3_6_27b/test_npu_qwen3_6_27b_w8a8_1p_in64k_out1k_50ms.py test_type:perf]) (push) Has been cancelled
PR Test (NPU) / pr-test-npu-finish (push) Has been cancelled
PR Test (Xeon) / pr-gate (push) Has been cancelled
PR Test (Xeon) / check-changes (push) Has been cancelled
PR Test (Xeon) / build-test (, xeon-gnr, base-b-test-cpu) (push) Has been cancelled
PR Test (XPU) / check-changes (push) Has been cancelled
PR Test (XPU) / pr-gate (push) Has been cancelled
PR Test (XPU) / stage-a-test-1-gpu-xpu (push) Has been cancelled
PR Test (XPU) / wait-for-stage-a (push) Has been cancelled
PR Test (XPU) / stage-b-test-1-gpu-xpu (push) Has been cancelled
PR Test (XPU) / finish (push) Has been cancelled
CI Model Inventory / build-inventory (push) Has been cancelled
Lint / lint (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark Compilation Check (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark - Manual Policy (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark - Request Processing (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark Summary (push) Has been cancelled
PR Test (SMG) / build-wheel (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on windows (x86_64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on macos (x86_64 - auto) (push) Has been cancelled
PR Test (SMG) / python-unit-tests (push) Has been cancelled
PR Test (SMG) / unit-tests (push) Has been cancelled
PR Test (SMG) / benchmarks (push) Has been cancelled
PR Test (SMG) / chat-completions (push) Has been cancelled
PR Test (SMG) / chat-completions-4gpu (push) Has been cancelled
PR Test (SMG) / e2e (push) Has been cancelled
PR Test (SMG) / docker-build-test (push) Has been cancelled
PR Test (SMG) / k8s-integration (push) Has been cancelled
PR Test (SMG) / finish (push) Has been cancelled
PR Test (SMG) / summarize-benchmarks (push) Has been cancelled
Release SGLang Model Gateway Docker Image / publish (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on macos (aarch64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (aarch64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (x86_64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (aarch64 - musllinux_1_1) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (x86_64 - musllinux_1_1) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / Build SDist (push) Has been cancelled
Release SGLang Model Gateway to PyPI / Upload to PyPI (push) Has been cancelled
Release SGLang Kernels / build-cu129-matrix (aarch64, 12.9, 3.10, arm-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / build-cu129-matrix (x86_64, 12.9, 3.10, x64-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / release-cu129 (push) Has been cancelled
Release SGLang Kernels / build-cu130-matrix (aarch64, 13.0, 3.10, arm-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / build-cu130-matrix (x86_64, 13.0, 3.10, x64-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / release-cu130 (push) Has been cancelled
Release SGLang Kernels / build-rocm-matrix (3.10, 700) (push) Has been cancelled
Release SGLang Kernels / build-rocm-matrix (3.10, 720) (push) Has been cancelled
Release SGLang Kernels / release-rocm700 (push) Has been cancelled
Release SGLang Kernels / release-rocm720 (push) Has been cancelled
Release SGLang Kernels / build-musa43 (43, 3.10) (push) Has been cancelled
Release SGLang Kernels / release-musa43 (push) Has been cancelled
294 lines
13 KiB
Python
294 lines
13 KiB
Python
# Copyright 2023-2024 SGLang Team
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
# ==============================================================================
|
|
"""Ray-aware DataParallelController that launches SchedulerActors instead of mp.Process."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
from typing import List, Optional
|
|
|
|
import ray
|
|
import zmq
|
|
|
|
from sglang.srt.entrypoints.engine import _calculate_rank_ranges
|
|
from sglang.srt.layers.dp_attention import compute_dp_attention_world_info
|
|
from sglang.srt.managers.data_parallel_controller import DataParallelController
|
|
from sglang.srt.ray.engine import (
|
|
_compute_world_size,
|
|
_create_scheduler_actor,
|
|
_get_bundle_node_ip,
|
|
_resolve_bundle_indices,
|
|
)
|
|
from sglang.srt.server_args import PortArgs, ServerArgs
|
|
from sglang.srt.utils.network import bind_port, get_zmq_socket, get_zmq_socket_on_host
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class RayDataParallelController(DataParallelController):
|
|
"""DataParallelController that uses Ray actors for scheduler processes.
|
|
|
|
Overrides the process-spawning methods to create SchedulerActor Ray actors
|
|
instead of mp.Process. Runs in-process (not as a separate mp.Process) and
|
|
reuses the parent's event_loop, dispatching, and ZMQ routing.
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
server_args: ServerArgs,
|
|
port_args: PortArgs,
|
|
placement_group,
|
|
bundle_for_node: Optional[List[int]],
|
|
rank0_node_ip: str,
|
|
):
|
|
# Set Ray-specific attributes BEFORE super().__init__() because the
|
|
# parent constructor calls launch_dp_schedulers / launch_dp_attention_schedulers
|
|
# which we override, and those methods need these attributes.
|
|
self.pg = placement_group
|
|
self.bundle_for_node = bundle_for_node
|
|
self.rank0_node_ip = rank0_node_ip
|
|
self.scheduler_actors: List = []
|
|
self.event_loop_refs: List = []
|
|
|
|
# super().__init__ will call our overridden launch methods via MRO.
|
|
# Pass run_scheduler_process_func=None since we don't spawn mp.Process.
|
|
super().__init__(server_args, port_args, run_scheduler_process_func=None)
|
|
|
|
def launch_dp_schedulers(self, server_args: ServerArgs, port_args: PortArgs):
|
|
"""Override: launch Ray scheduler actors per DP rank."""
|
|
sockets = []
|
|
dp_port_args_list = []
|
|
|
|
for dp_rank in range(server_args.dp_size):
|
|
tmp_port_args = PortArgs.init_new(server_args)
|
|
tmp_port_args.tokenizer_ipc_name = port_args.tokenizer_ipc_name
|
|
tmp_port_args.detokenizer_ipc_name = port_args.detokenizer_ipc_name
|
|
tmp_port_args.instance_id = port_args.instance_id
|
|
|
|
# Hold NCCL port so the next DP rank gets a different one
|
|
sockets.append(bind_port(tmp_port_args.nccl_port))
|
|
dp_port_args_list.append(tmp_port_args)
|
|
|
|
# Create ZMQ PUSH socket for this DP rank (controller → scheduler)
|
|
if server_args.node_rank == 0:
|
|
self.workers[dp_rank] = get_zmq_socket(
|
|
self.context,
|
|
zmq.PUSH,
|
|
tmp_port_args.scheduler_input_ipc_name,
|
|
True,
|
|
)
|
|
|
|
# Release held ports before creating actors
|
|
for sock in sockets:
|
|
sock.close()
|
|
|
|
# Create actors for each DP rank sequentially
|
|
for dp_rank in range(server_args.dp_size):
|
|
self._launch_ray_tp_group(server_args, dp_port_args_list[dp_rank], dp_rank)
|
|
|
|
def launch_dp_attention_schedulers(
|
|
self, server_args: ServerArgs, port_args: PortArgs
|
|
):
|
|
"""Override: pre-allocate ports, skip broadcast, create Ray actors."""
|
|
# Pre-allocate worker ports on the controller node, binding to the
|
|
# rank-0 node IP instead of tcp://* to avoid exposing unauthenticated
|
|
# ZMQ sockets (CVE-2026-3060).
|
|
worker_ports = []
|
|
for dp_rank in range(server_args.dp_size):
|
|
worker_port, worker_socket = get_zmq_socket_on_host(
|
|
self.context, zmq.PUSH, host=self.rank0_node_ip
|
|
)
|
|
worker_ports.append(worker_port)
|
|
self.workers[dp_rank] = worker_socket
|
|
logger.debug(f"Assigned port {worker_port} to worker {dp_rank}")
|
|
|
|
# Skip _broadcast_worker_ports — Ray creates all actors centrally,
|
|
# so there's no need for the inter-node handshake protocol.
|
|
self._launch_ray_tp_group(
|
|
server_args, port_args, dp_rank=None, worker_ports=worker_ports
|
|
)
|
|
|
|
def _launch_ray_tp_group(
|
|
self,
|
|
server_args: ServerArgs,
|
|
port_args: PortArgs,
|
|
dp_rank: Optional[int],
|
|
worker_ports: Optional[List[int]] = None,
|
|
):
|
|
"""Create SchedulerActor Ray actors for one TP group (one DP rank).
|
|
|
|
Args:
|
|
dp_rank: DP rank for regular DP; None for DP attention (derived from tp_rank).
|
|
worker_ports: Pre-allocated ports for DP attention; None for regular DP.
|
|
"""
|
|
nnodes = server_args.nnodes
|
|
batch_start_idx = len(self.scheduler_actors)
|
|
|
|
if self.server_args.placement_group is None:
|
|
for node_idx in range(nnodes):
|
|
bundle_idx = self.bundle_for_node[node_idx]
|
|
pp_range, tp_range, pp_per_node, tp_per_node = _calculate_rank_ranges(
|
|
nnodes, server_args.pp_size, server_args.tp_size, node_rank=node_idx
|
|
)
|
|
for pp_rank in pp_range:
|
|
for tp_rank in tp_range:
|
|
rank_port_args = port_args
|
|
actual_dp_rank = dp_rank
|
|
|
|
local_gpu_idx = (pp_rank % pp_per_node) * tp_per_node + (
|
|
tp_rank % tp_per_node
|
|
)
|
|
|
|
if server_args.enable_dp_attention:
|
|
_, _, actual_dp_rank, _ = compute_dp_attention_world_info(
|
|
server_args.enable_dp_attention,
|
|
tp_rank,
|
|
server_args.tp_size,
|
|
server_args.dp_size,
|
|
server_args.attn_cp_size,
|
|
)
|
|
rank_port_args = PortArgs.init_new(
|
|
server_args, actual_dp_rank, worker_ports
|
|
)
|
|
# All DP ranks share the same NCCL port (reuse TP group)
|
|
rank_port_args.nccl_port = port_args.nccl_port
|
|
rank_port_args.instance_id = port_args.instance_id
|
|
# The detokenizer and tokenizer bind using the
|
|
# original port_args addresses (127.0.0.1 when
|
|
# dist_init_addr is unset). Scheduler actors must
|
|
# connect to the same addresses.
|
|
rank_port_args.detokenizer_ipc_name = (
|
|
port_args.detokenizer_ipc_name
|
|
)
|
|
rank_port_args.tokenizer_ipc_name = (
|
|
port_args.tokenizer_ipc_name
|
|
)
|
|
|
|
dist_init_addr = (
|
|
f"{self.rank0_node_ip}:{rank_port_args.nccl_port}"
|
|
)
|
|
|
|
actor = _create_scheduler_actor(
|
|
pg=self.pg,
|
|
bundle_idx=bundle_idx,
|
|
gpu_id=local_gpu_idx,
|
|
server_args=server_args,
|
|
port_args=rank_port_args,
|
|
tp_rank=tp_rank,
|
|
pp_rank=pp_rank,
|
|
dp_rank=actual_dp_rank,
|
|
dist_init_addr=dist_init_addr,
|
|
rank0_node_ip=self.rank0_node_ip,
|
|
)
|
|
self.scheduler_actors.append(actor)
|
|
|
|
else:
|
|
world_size = _compute_world_size(server_args)
|
|
bundle_indices = _resolve_bundle_indices(self.pg, world_size)
|
|
|
|
ranks_per_tp_group = server_args.tp_size * server_args.pp_size
|
|
if dp_rank is not None:
|
|
start_rank = dp_rank * ranks_per_tp_group
|
|
end_rank = start_rank + ranks_per_tp_group
|
|
# Each DP group must use its own local rank-0's node IP for
|
|
# NCCL rendezvous, not the world rank-0's node IP.
|
|
local_rank0_bundle_idx = bundle_indices[start_rank]
|
|
local_rank0_node_ip = _get_bundle_node_ip(
|
|
self.pg, local_rank0_bundle_idx
|
|
)
|
|
else:
|
|
start_rank = 0
|
|
end_rank = world_size
|
|
local_rank0_node_ip = self.rank0_node_ip
|
|
|
|
for global_rank in range(start_rank, end_rank):
|
|
local_rank = global_rank % ranks_per_tp_group
|
|
pp_rank = local_rank // server_args.tp_size
|
|
tp_rank = local_rank % server_args.tp_size
|
|
rank_port_args = port_args
|
|
actual_dp_rank = dp_rank
|
|
|
|
bundle_idx = bundle_indices[global_rank]
|
|
|
|
if server_args.enable_dp_attention:
|
|
_, _, actual_dp_rank, _ = compute_dp_attention_world_info(
|
|
server_args.enable_dp_attention,
|
|
tp_rank,
|
|
server_args.tp_size,
|
|
server_args.dp_size,
|
|
server_args.attn_cp_size,
|
|
)
|
|
rank_port_args = PortArgs.init_new(
|
|
server_args, actual_dp_rank, worker_ports
|
|
)
|
|
rank_port_args.nccl_port = port_args.nccl_port
|
|
rank_port_args.detokenizer_ipc_name = port_args.detokenizer_ipc_name
|
|
rank_port_args.tokenizer_ipc_name = port_args.tokenizer_ipc_name
|
|
|
|
dist_init_addr = f"{local_rank0_node_ip}:{rank_port_args.nccl_port}"
|
|
|
|
actor = _create_scheduler_actor(
|
|
pg=self.pg,
|
|
bundle_idx=bundle_idx,
|
|
gpu_id=0, # Each bundle has exactly 1 GPU
|
|
server_args=server_args,
|
|
port_args=rank_port_args,
|
|
tp_rank=tp_rank,
|
|
pp_rank=pp_rank,
|
|
dp_rank=actual_dp_rank,
|
|
dist_init_addr=dist_init_addr,
|
|
rank0_node_ip=local_rank0_node_ip,
|
|
)
|
|
self.scheduler_actors.append(actor)
|
|
|
|
# Wait for all actors created in this call to initialize
|
|
batch_actors = self.scheduler_actors[batch_start_idx:]
|
|
try:
|
|
scheduler_infos = ray.get(
|
|
[actor.get_info.remote() for actor in batch_actors]
|
|
)
|
|
except ray.exceptions.RayActorError as e:
|
|
for actor in self.scheduler_actors:
|
|
try:
|
|
ray.kill(actor)
|
|
except Exception:
|
|
logger.error(f"Failed to kill Ray scheduler actor: {actor}")
|
|
raise RuntimeError(f"Scheduler actor failed to initialize: {e}")
|
|
|
|
# Store init info from the first actor (same across all actors)
|
|
if scheduler_infos:
|
|
self.max_total_num_tokens = scheduler_infos[0]["max_total_num_tokens"]
|
|
self.max_req_input_len = scheduler_infos[0]["max_req_input_len"]
|
|
|
|
# Start event loops (non-blocking — runs until actor is killed)
|
|
self.event_loop_refs.extend(
|
|
[actor.run_event_loop.remote() for actor in batch_actors]
|
|
)
|
|
|
|
# Override launch_tensor_parallel_group to be a no-op since we don't use it.
|
|
# The parent's launch_dp_schedulers/launch_dp_attention_schedulers call this,
|
|
# but our overrides call _launch_ray_tp_group instead.
|
|
def launch_tensor_parallel_group(
|
|
self,
|
|
server_args: ServerArgs,
|
|
port_args: PortArgs,
|
|
base_gpu_id: int,
|
|
dp_rank: Optional[int],
|
|
worker_ports: Optional[List[int]] = None,
|
|
):
|
|
raise RuntimeError(
|
|
"RayDataParallelController should not call launch_tensor_parallel_group. "
|
|
"Use _launch_ray_tp_group instead."
|
|
)
|