Record a process's placement at publish, not at group build (#40071)
This commit is contained in:
@@ -93,10 +93,12 @@ from sglang.srt.model_executor.cuda_graph_config import (
|
||||
from sglang.srt.model_executor.forward_batch_info import ForwardBatch
|
||||
from sglang.srt.model_executor.model_runner import ModelRunner
|
||||
from sglang.srt.runtime_context import (
|
||||
SpawnRanks,
|
||||
get_model,
|
||||
get_parallel,
|
||||
get_schedule,
|
||||
publish,
|
||||
spawn_world_rank,
|
||||
)
|
||||
from sglang.srt.sampling.sampling_params import SamplingParams
|
||||
from sglang.srt.server_args import PortArgs, ServerArgs
|
||||
@@ -707,7 +709,15 @@ def correctness_test(
|
||||
gpu_id,
|
||||
tp_rank,
|
||||
):
|
||||
publish(server_args, role="scheduler")
|
||||
# With the placement this process was spawned with, so a rank read here
|
||||
# does not need a process group -- the same bundle the runner is handed.
|
||||
publish(
|
||||
server_args,
|
||||
role="scheduler",
|
||||
ranks=SpawnRanks(
|
||||
world_rank=spawn_world_rank(server_args, tp_rank=tp_rank, pp_rank=0)
|
||||
),
|
||||
)
|
||||
|
||||
# Configure the logger
|
||||
configure_logger(server_args, prefix=f" TP{tp_rank}")
|
||||
@@ -912,7 +922,13 @@ def latency_test(
|
||||
cfg = resolving_view(server_args)
|
||||
# `main` runs this inline for tp_size == 1 and spawns it per rank otherwise;
|
||||
# a spawned child arrives with nothing published.
|
||||
publish(server_args, role="scheduler")
|
||||
publish(
|
||||
server_args,
|
||||
role="scheduler",
|
||||
ranks=SpawnRanks(
|
||||
world_rank=spawn_world_rank(server_args, tp_rank=tp_rank, pp_rank=0)
|
||||
),
|
||||
)
|
||||
initialize_moe_config()
|
||||
initialize_fp8_gemm_config()
|
||||
initialize_fp4_gemm_config()
|
||||
|
||||
@@ -14,7 +14,7 @@ from sglang.srt.constrained.base_grammar_backend import (
|
||||
from sglang.srt.constrained.reasoner_grammar_backend import ReasonerGrammarObject
|
||||
from sglang.srt.distributed.communication_tags import P2PTag
|
||||
from sglang.srt.environ import envs
|
||||
from sglang.srt.runtime_context import get_serving
|
||||
from sglang.srt.runtime_context import get_parallel, get_serving
|
||||
from sglang.srt.sampling.sampling_params import (
|
||||
get_request_reasoning_end_token_ids,
|
||||
)
|
||||
@@ -53,8 +53,8 @@ class GrammarManager:
|
||||
self.grammar_sync_size = scheduler.dp_tp_group.world_size
|
||||
self.grammar_sync_entry = scheduler.dp_tp_group.first_rank
|
||||
self.is_grammar_sync_entry = scheduler.dp_tp_group.is_first_rank
|
||||
self.pp_rank = scheduler.ps.pp_rank
|
||||
self.pp_size = scheduler.ps.pp_size
|
||||
self.pp_rank = get_parallel().pp_rank
|
||||
self.pp_size = get_parallel().pp_size
|
||||
self.pp_group = scheduler.pp_group
|
||||
self.grammar_pp_sync_work_list = []
|
||||
|
||||
|
||||
@@ -393,7 +393,7 @@ class DecodePreallocQueue(DecodeHiCachePreallocMixin):
|
||||
self.bootstrap_port = bootstrap_port
|
||||
self.max_total_num_tokens = max_total_num_tokens
|
||||
self.pp_rank = pp_rank
|
||||
self.pp_size = scheduler.ps.pp_size
|
||||
self.pp_size = get_parallel().pp_size
|
||||
self.num_reserved_decode_tokens = num_reserved_decode_tokens
|
||||
self.transfer_backend = transfer_backend
|
||||
# Queue for requests pending pre-allocation
|
||||
|
||||
@@ -997,7 +997,7 @@ class SchedulerDisaggregationPrefillMixin:
|
||||
KVPoll.Failed,
|
||||
):
|
||||
logger.warning_once(
|
||||
f"PP rank {self.ps.pp_rank}: unexpected poll state {poll} for rid {req.rid} "
|
||||
f"PP rank {get_parallel().pp_rank}: unexpected poll state {poll} for rid {req.rid} "
|
||||
f"from consensus; treating as undone",
|
||||
)
|
||||
undone_reqs.append(req)
|
||||
@@ -1077,7 +1077,7 @@ class SchedulerDisaggregationPrefillMixin:
|
||||
) -> Optional[Exception]:
|
||||
"""Conclude an inflight request whose KV transfer failed."""
|
||||
error_message = (
|
||||
f"Prefill transfer failed for request rank={self.ps.tp_rank} "
|
||||
f"Prefill transfer failed for request rank={get_parallel().tp_rank} "
|
||||
f"{req.rid=} {req.bootstrap_room=}"
|
||||
)
|
||||
exc: Optional[Exception] = None
|
||||
@@ -1142,7 +1142,7 @@ class SchedulerDisaggregationPrefillMixin:
|
||||
def handle_bootstrap_failure(self: Scheduler, req: Req) -> None:
|
||||
self.clear_pending_chunk_send(req)
|
||||
error_message = (
|
||||
f"Prefill bootstrap failed for request rank={self.ps.tp_rank} "
|
||||
f"Prefill bootstrap failed for request rank={get_parallel().tp_rank} "
|
||||
f"{req.rid=} {req.bootstrap_room=}"
|
||||
)
|
||||
is_propagated = False
|
||||
|
||||
@@ -99,6 +99,7 @@ def init_torch_distributed(
|
||||
)
|
||||
|
||||
# Only initialize the distributed environment on the target model worker.
|
||||
# This builds the groups behind the context's live group-handle reads.
|
||||
_init_parallel_groups(
|
||||
backend=backend,
|
||||
dist_init_method=dist_init_method,
|
||||
|
||||
@@ -3029,7 +3029,14 @@ def patch_pipeline_parallel_group(pp_group: GroupCoordinator):
|
||||
global _PP
|
||||
_PP = pp_group
|
||||
try:
|
||||
yield
|
||||
# `pp_size` is a configured leaf: unlike the rank and the handle it
|
||||
# does not follow the group being swapped, so the scope has to name it.
|
||||
with get_parallel().override(
|
||||
pp_size=pp_group.world_size,
|
||||
pp_rank=pp_group.rank_in_group,
|
||||
pp_group=pp_group,
|
||||
):
|
||||
yield
|
||||
finally:
|
||||
_PP_STATE_PATCHED = False
|
||||
_PP = old_pp_group
|
||||
|
||||
@@ -509,7 +509,7 @@ def pp_parallel_deep_gemm_warmup(runner) -> None:
|
||||
logger.info(
|
||||
"PP-parallel DeepGEMM warmup start "
|
||||
"(pp_rank=%d, tp_rank=%d, batch_sizes=%s, disagg=%s).",
|
||||
model_runner.ps.pp_rank,
|
||||
get_parallel().pp_rank,
|
||||
model_runner.ps.tp_rank,
|
||||
batch_sizes,
|
||||
disagg_mode,
|
||||
@@ -538,5 +538,5 @@ def pp_parallel_deep_gemm_warmup(runner) -> None:
|
||||
logger.info(
|
||||
"PP-parallel DeepGEMM warmup done in %.2fs (pp_rank=%d).",
|
||||
time.perf_counter() - t0,
|
||||
model_runner.ps.pp_rank,
|
||||
get_parallel().pp_rank,
|
||||
)
|
||||
|
||||
@@ -27,6 +27,7 @@ from sglang.srt.distributed.device_communicators.pynccl_allocator import (
|
||||
)
|
||||
from sglang.srt.environ import envs
|
||||
from sglang.srt.runtime_context import (
|
||||
derive_attention_ranks,
|
||||
derive_attention_widths,
|
||||
get_device,
|
||||
get_exec,
|
||||
@@ -349,15 +350,12 @@ def compute_dp_attention_world_info(
|
||||
dp_size=dp_size,
|
||||
enable_dp_attention=enable_dp_attention,
|
||||
)
|
||||
attn_tp_rank = tp_rank % attn_tp_size
|
||||
|
||||
if not enable_dp_attention:
|
||||
attn_dp_rank = 0
|
||||
else:
|
||||
# Rank layout is (dp, cp, tp) where tp is the fastest-changing dim:
|
||||
# tp_rank = (attn_dp_rank * attn_cp_size + attn_cp_rank) * attn_tp_size + attn_tp_rank
|
||||
attn_dp_rank = tp_rank // (attn_tp_size * attn_cp_size)
|
||||
|
||||
attn_tp_rank, attn_dp_rank = derive_attention_ranks(
|
||||
tp_rank=tp_rank,
|
||||
attn_tp_size=attn_tp_size,
|
||||
attn_cp_size=attn_cp_size,
|
||||
enable_dp_attention=enable_dp_attention,
|
||||
)
|
||||
return attn_tp_rank, attn_tp_size, attn_dp_rank, attn_dp_size
|
||||
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@ from http import HTTPStatus
|
||||
from typing import TYPE_CHECKING, Any, Deque, Dict, List, Optional, Set, Tuple, Union
|
||||
|
||||
from sglang.srt.runtime_context import (
|
||||
SpawnRanks,
|
||||
attention_backends,
|
||||
get_context,
|
||||
get_device,
|
||||
@@ -44,6 +45,7 @@ from sglang.srt.runtime_context import (
|
||||
get_serving,
|
||||
get_spec,
|
||||
publish,
|
||||
spawn_world_rank,
|
||||
)
|
||||
|
||||
from sglang.srt.utils.common import suppress_noisy_warnings # isort: skip
|
||||
@@ -595,9 +597,9 @@ class Scheduler(
|
||||
enable_metrics=get_observability().enable_metrics,
|
||||
enable_kv_cache_events=bool(
|
||||
get_observability().kv_events_config
|
||||
and self.ps.pp_rank == 0
|
||||
and self.ps.attn_tp_rank == 0
|
||||
and self.ps.attn_cp_rank == 0
|
||||
and get_parallel().pp_rank == 0
|
||||
and get_parallel().attn_tp_rank == 0
|
||||
and get_parallel().attn_cp_rank == 0
|
||||
),
|
||||
ps=self.ps,
|
||||
tp_group=self.tp_group,
|
||||
@@ -759,8 +761,8 @@ class Scheduler(
|
||||
return
|
||||
|
||||
rank = (
|
||||
self.ps.dp_rank
|
||||
if self.ps.dp_rank is not None
|
||||
get_parallel().dp_rank
|
||||
if get_parallel().dp_rank is not None
|
||||
else self.tp_group.rank_in_group
|
||||
)
|
||||
logger.info("HCCL DP prewarm start: rank=%s", rank)
|
||||
@@ -775,10 +777,10 @@ class Scheduler(
|
||||
if _is_npu:
|
||||
from sglang.srt.hardware_backend.npu.utils import init_zbal
|
||||
|
||||
if self.ps.pp_size > 1:
|
||||
if get_parallel().pp_size > 1:
|
||||
logger.error("only zbal mix mode support pp_size > 1!")
|
||||
init_zbal(
|
||||
self.ps.tp_size, self.ps.gpu_id, self.ps.tp_rank
|
||||
get_parallel().tp_size, self.ps.gpu_id, get_parallel().tp_rank
|
||||
) # only switch allocator if is mix mode
|
||||
|
||||
def init_model_config(self):
|
||||
@@ -811,9 +813,9 @@ class Scheduler(
|
||||
|
||||
def init_ipc_channels(self, port_args: PortArgs):
|
||||
is_rank_zero = (
|
||||
self.ps.pp_rank == 0
|
||||
and self.ps.attn_tp_rank == 0
|
||||
and self.ps.attn_cp_rank == 0
|
||||
get_parallel().pp_rank == 0
|
||||
and get_parallel().attn_tp_rank == 0
|
||||
and get_parallel().attn_cp_rank == 0
|
||||
)
|
||||
self.ipc_channels = SchedulerIpcChannels.create(
|
||||
port_args=port_args,
|
||||
@@ -824,7 +826,7 @@ class Scheduler(
|
||||
skip_tokenizer_init=self.skip_tokenizer_init,
|
||||
metrics_enabled=get_observability().enable_metrics
|
||||
and (
|
||||
self.ps.attn_tp_rank == 0
|
||||
get_parallel().attn_tp_rank == 0
|
||||
or get_observability().enable_metrics_for_all_schedulers
|
||||
),
|
||||
enable_scripted_runtime=envs.SGLANG_TEST_SCRIPTED_RUNTIME.get(),
|
||||
@@ -837,7 +839,7 @@ class Scheduler(
|
||||
return
|
||||
|
||||
self.recv_from_tokenizer = self.ipc_channels.recv_from_tokenizer
|
||||
dp_rank = self.ps.dp_rank if self.ps.dp_rank is not None else 0
|
||||
dp_rank = get_parallel().dp_rank if get_parallel().dp_rank is not None else 0
|
||||
try:
|
||||
self.load_snapshot_writer = create_load_snapshot_writer(
|
||||
port_args,
|
||||
@@ -850,9 +852,9 @@ class Scheduler(
|
||||
|
||||
def init_idle_sleeper(self) -> None:
|
||||
if (
|
||||
self.ps.pp_rank == 0
|
||||
and self.ps.attn_tp_rank == 0
|
||||
and self.ps.attn_cp_rank == 0
|
||||
get_parallel().pp_rank == 0
|
||||
and get_parallel().attn_tp_rank == 0
|
||||
and get_parallel().attn_cp_rank == 0
|
||||
and get_device().sleep_on_idle
|
||||
):
|
||||
self.idle_sleeper = IdleSleeper(
|
||||
@@ -1032,8 +1034,8 @@ class Scheduler(
|
||||
|
||||
if (
|
||||
envs.SGLANG_ENABLE_PP_SPEC.get()
|
||||
and self.ps.pp_size > 1
|
||||
and self.ps.pp_rank != self.ps.pp_size - 1
|
||||
and get_parallel().pp_size > 1
|
||||
and get_parallel().pp_rank != get_parallel().pp_size - 1
|
||||
):
|
||||
# PP+spec: the draft model (MTP layer) needs final hidden states and
|
||||
# the lm_head, both of which live on the last PP stage only.
|
||||
@@ -1130,7 +1132,7 @@ class Scheduler(
|
||||
# Match run_batch / _pp_launch_batch so warmup allocations stay reusable.
|
||||
forward_stream = (
|
||||
model_runner.forward_stream
|
||||
if self.enable_overlap or self.ps.pp_size > 1 or use_mlx()
|
||||
if self.enable_overlap or get_parallel().pp_size > 1 or use_mlx()
|
||||
else self.schedule_stream
|
||||
)
|
||||
with device_module.stream(forward_stream):
|
||||
@@ -1208,7 +1210,7 @@ class Scheduler(
|
||||
get_context().override(
|
||||
"scheduler.pp_max_micro_batch_size_default",
|
||||
pp_max_micro_batch_size=max(
|
||||
self.max_running_requests // self.ps.pp_size, 1
|
||||
self.max_running_requests // get_parallel().pp_size, 1
|
||||
),
|
||||
)
|
||||
|
||||
@@ -1239,7 +1241,7 @@ class Scheduler(
|
||||
self.startup_available_gpu_memory_gb = get_available_gpu_memory(
|
||||
self.device, self.ps.gpu_id, empty_cache=False
|
||||
)
|
||||
if self.ps.tp_rank == 0:
|
||||
if get_parallel().tp_rank == 0:
|
||||
logger.info(
|
||||
f"max_total_num_tokens={self.max_total_num_tokens}, "
|
||||
f"chunked_prefill_size={get_schedule().chunked_prefill_size}, "
|
||||
@@ -1344,7 +1346,7 @@ class Scheduler(
|
||||
def maybe_init_dynamic_chunk_sizer(self) -> None:
|
||||
"""Profile a PP prefill latency model that sizes chunks per stage."""
|
||||
self.dynamic_chunk_sizer: Optional[DynamicChunkSizer] = None
|
||||
if not (get_schedule().enable_dynamic_chunking and self.ps.pp_size > 1):
|
||||
if not (get_schedule().enable_dynamic_chunking and get_parallel().pp_size > 1):
|
||||
return
|
||||
sizer = DynamicChunkSizer(
|
||||
model_runner=self.tp_worker.model_runner,
|
||||
@@ -1359,7 +1361,7 @@ class Scheduler(
|
||||
device=self.device,
|
||||
pp_group=self.pp_group,
|
||||
world_group=self.world_group,
|
||||
pp_rank=self.ps.pp_rank,
|
||||
pp_rank=get_parallel().pp_rank,
|
||||
)
|
||||
if sizer.profile_and_fit():
|
||||
self.dynamic_chunk_sizer = sizer
|
||||
@@ -1422,7 +1424,7 @@ class Scheduler(
|
||||
else:
|
||||
self.prefill_delayer = PrefillDelayer(
|
||||
dp_size=self.ps.dp_size,
|
||||
attn_tp_size=self.ps.attn_tp_size,
|
||||
attn_tp_size=get_parallel().attn_tp_size,
|
||||
cpu_group=self.tp_cpu_group,
|
||||
device_group=self.tp_group.device_group,
|
||||
metrics_collector=(
|
||||
@@ -1433,7 +1435,7 @@ class Scheduler(
|
||||
max_delay_passes=get_schedule().prefill_delayer_max_delay_passes,
|
||||
token_usage_low_watermark=get_schedule().prefill_delayer_token_usage_low_watermark,
|
||||
device=self.tp_group.device,
|
||||
debug_log_enabled=self.ps.attn_tp_rank == 0,
|
||||
debug_log_enabled=get_parallel().attn_tp_rank == 0,
|
||||
)
|
||||
|
||||
# NOTE: preemption is enabled by default for priority scheduling.
|
||||
@@ -1464,7 +1466,7 @@ class Scheduler(
|
||||
# Init recv skipper and input blocker
|
||||
self.recv_skipper = SchedulerRecvSkipper.maybe_create()
|
||||
self.input_blocker = (
|
||||
SchedulerInputBlocker(noop=self.ps.attn_tp_rank != 0)
|
||||
SchedulerInputBlocker(noop=get_parallel().attn_tp_rank != 0)
|
||||
if get_bool_env_var("SGLANG_ENABLE_COLOCATED_BATCH_GEN")
|
||||
else None
|
||||
)
|
||||
@@ -1553,7 +1555,7 @@ class Scheduler(
|
||||
self.disagg_decode_transfer_queue = DecodeTransferQueue(
|
||||
gloo_group=self.attn_tp_cpu_group,
|
||||
req_to_metadata_buffer_idx_allocator=self.req_to_metadata_buffer_idx_allocator,
|
||||
tp_rank=self.ps.tp_rank,
|
||||
tp_rank=get_parallel().tp_rank,
|
||||
metadata_buffers=self.disagg_metadata_buffers,
|
||||
scheduler=self,
|
||||
tree_cache=self.tree_cache,
|
||||
@@ -1570,13 +1572,13 @@ class Scheduler(
|
||||
transfer_queue=self.disagg_decode_transfer_queue,
|
||||
tree_cache=self.tree_cache,
|
||||
gloo_group=self.attn_tp_cpu_group,
|
||||
tp_rank=self.ps.tp_rank,
|
||||
tp_size=self.ps.tp_size,
|
||||
tp_rank=get_parallel().tp_rank,
|
||||
tp_size=get_parallel().tp_size,
|
||||
dp_size=get_parallel().dp_size,
|
||||
gpu_id=self.ps.gpu_id,
|
||||
bootstrap_port=get_disagg().disaggregation_bootstrap_port,
|
||||
max_total_num_tokens=self.max_total_num_tokens,
|
||||
pp_rank=self.ps.pp_rank,
|
||||
pp_rank=get_parallel().pp_rank,
|
||||
num_reserved_decode_tokens=get_disagg().num_reserved_decode_tokens,
|
||||
transfer_backend=self.transfer_backend,
|
||||
)
|
||||
@@ -1602,16 +1604,16 @@ class Scheduler(
|
||||
draft_token_to_kv_pool=draft_token_to_kv_pool,
|
||||
req_to_metadata_buffer_idx_allocator=self.req_to_metadata_buffer_idx_allocator,
|
||||
metadata_buffers=self.disagg_metadata_buffers,
|
||||
tp_rank=self.ps.tp_rank,
|
||||
tp_size=self.ps.tp_size,
|
||||
tp_rank=get_parallel().tp_rank,
|
||||
tp_size=get_parallel().tp_size,
|
||||
gpu_id=self.ps.gpu_id,
|
||||
bootstrap_port=get_disagg().disaggregation_bootstrap_port,
|
||||
gloo_group=self.attn_tp_cpu_group,
|
||||
max_total_num_tokens=self.max_total_num_tokens,
|
||||
scheduler=self,
|
||||
scheduler_stage_metrics=self.scheduler_stage_metrics,
|
||||
pp_rank=self.ps.pp_rank,
|
||||
pp_size=self.ps.pp_size,
|
||||
pp_rank=get_parallel().pp_rank,
|
||||
pp_size=get_parallel().pp_size,
|
||||
transfer_backend=self.transfer_backend,
|
||||
)
|
||||
# The prefill requests that are in the middle of kv sending
|
||||
@@ -1638,8 +1640,8 @@ class Scheduler(
|
||||
self.server_args,
|
||||
dtype=self.model_config.dtype,
|
||||
hf_config=self.model_config.hf_config,
|
||||
pp_rank=self.ps.pp_rank,
|
||||
tp_rank=self.ps.tp_rank,
|
||||
pp_rank=get_parallel().pp_rank,
|
||||
tp_rank=get_parallel().tp_rank,
|
||||
tp_group=self.tp_group,
|
||||
scheduler=self,
|
||||
)
|
||||
@@ -1895,7 +1897,9 @@ class Scheduler(
|
||||
|
||||
if self.device == "cpu":
|
||||
self.schedule_stream.synchronize = lambda: None # No-op for CPU
|
||||
elif (is_cuda() or _is_hip) and (self.enable_overlap or self.ps.pp_size > 1):
|
||||
elif (is_cuda() or _is_hip) and (
|
||||
self.enable_overlap or get_parallel().pp_size > 1
|
||||
):
|
||||
# CUDA/HIP streams come from a fixed round-robin pool. Redraw if this
|
||||
# stream aliases forward_stream, which would eliminate scheduler
|
||||
# overlap. Only CUDA/HIP streams expose a ``cuda_stream`` handle;
|
||||
@@ -2090,9 +2094,9 @@ class Scheduler(
|
||||
"""
|
||||
local_reqs = []
|
||||
if (
|
||||
self.ps.pp_rank == 0
|
||||
and self.ps.attn_tp_rank == 0
|
||||
and self.ps.attn_cp_rank == 0
|
||||
get_parallel().pp_rank == 0
|
||||
and get_parallel().attn_tp_rank == 0
|
||||
and get_parallel().attn_cp_rank == 0
|
||||
):
|
||||
local_reqs = self._poll_timeout_aborts()
|
||||
recv_reqs = self.request_receiver.recv_requests(local_reqs=local_reqs)
|
||||
@@ -2290,9 +2294,9 @@ class Scheduler(
|
||||
and with it the server-process duties a Python ``TokenizerManager``
|
||||
would otherwise own (e.g. serving the PD KV bootstrap registry)."""
|
||||
return envs.SGLANG_RUST_SERVER.get() and (
|
||||
self.ps.pp_rank == 0
|
||||
and self.ps.attn_tp_rank == 0
|
||||
and self.ps.attn_cp_rank == 0
|
||||
get_parallel().pp_rank == 0
|
||||
and get_parallel().attn_tp_rank == 0
|
||||
and get_parallel().attn_cp_rank == 0
|
||||
)
|
||||
|
||||
def maybe_init_rust_server(self) -> None:
|
||||
@@ -2417,10 +2421,10 @@ class Scheduler(
|
||||
self.kv_events_publisher = SchedulerKvEventsPublisher(
|
||||
kv_events_config=get_observability().kv_events_config,
|
||||
ps=self.ps,
|
||||
attn_tp_rank=self.ps.attn_tp_rank,
|
||||
attn_cp_rank=self.ps.attn_cp_rank,
|
||||
attn_tp_rank=get_parallel().attn_tp_rank,
|
||||
attn_cp_rank=get_parallel().attn_cp_rank,
|
||||
attn_dp_rank=self.ps.attn_dp_rank,
|
||||
dp_rank=self.ps.dp_rank,
|
||||
dp_rank=get_parallel().dp_rank,
|
||||
tree_cache=self.tree_cache,
|
||||
send_metrics_from_scheduler=self.ipc_channels.send_metrics_from_scheduler,
|
||||
max_running_requests=self.max_running_requests,
|
||||
@@ -3398,7 +3402,7 @@ class Scheduler(
|
||||
|
||||
if (timeout_s := envs.SGLANG_REQ_RUNNING_TIMEOUT.get()) > 0:
|
||||
deadline = time.perf_counter() - timeout_s
|
||||
if self.ps.pp_size == 1:
|
||||
if get_parallel().pp_size == 1:
|
||||
inflight_batches = [self.running_batch, self.last_batch]
|
||||
else:
|
||||
inflight_batches = [*self.running_mbs, *self.mbs]
|
||||
@@ -4460,7 +4464,7 @@ class Scheduler(
|
||||
batch.input_ids = None
|
||||
self._copy_auxiliary_output_to_cpu(batch, batch_result)
|
||||
elif not batch.spec_algorithm.is_none():
|
||||
is_verify_round = self.ps.pp_size > 1 and not (
|
||||
is_verify_round = get_parallel().pp_size > 1 and not (
|
||||
batch.forward_mode.is_extend() or batch.is_extend_in_batch
|
||||
)
|
||||
# The relayed tree is what the requests carry between rounds;
|
||||
@@ -4548,7 +4552,10 @@ class Scheduler(
|
||||
# Only the last PP rank owns real results requiring D2H; other ranks
|
||||
# consume device tensors rebuilt from the output ring.
|
||||
batch_result.copy_done = self.device_module.Event()
|
||||
if batch_result.has_sampled_token_ids and self.ps.pp_size == 1:
|
||||
if (
|
||||
batch_result.has_sampled_token_ids
|
||||
and get_parallel().pp_size == 1
|
||||
):
|
||||
batch_result.copy_to_cpu(
|
||||
return_logprob=batch.return_logprob,
|
||||
return_hidden_states=batch.return_hidden_states,
|
||||
@@ -4685,7 +4692,7 @@ class Scheduler(
|
||||
):
|
||||
return
|
||||
# PP transports the device output to the first rank before copying it.
|
||||
if self.ps.pp_size > 1:
|
||||
if get_parallel().pp_size > 1:
|
||||
return
|
||||
if result.copy_done is not None:
|
||||
raise RuntimeError(
|
||||
@@ -5024,7 +5031,7 @@ class Scheduler(
|
||||
return idle
|
||||
|
||||
def _pp_microbatches_drained(self) -> bool:
|
||||
if self.ps.pp_size == 1:
|
||||
if get_parallel().pp_size == 1:
|
||||
return True
|
||||
return all(x.is_empty() for x in self.running_mbs) and all(
|
||||
mb is None or mb.is_empty() for mb in self.mbs
|
||||
@@ -5265,10 +5272,10 @@ class Scheduler(
|
||||
if_success = False
|
||||
break
|
||||
elif k == "pp_max_micro_batch_size" and (
|
||||
v > self.max_running_requests // self.ps.pp_size or v < 1
|
||||
v > self.max_running_requests // get_parallel().pp_size or v < 1
|
||||
):
|
||||
logging.warning(
|
||||
f"Updating {k} to {v} is rejected because it is out of the valid range [1, {self.max_running_requests // self.ps.pp_size}]."
|
||||
f"Updating {k} to {v} is rejected because it is out of the valid range [1, {self.max_running_requests // get_parallel().pp_size}]."
|
||||
)
|
||||
if_success = False
|
||||
break
|
||||
@@ -5382,7 +5389,7 @@ class Scheduler(
|
||||
)
|
||||
|
||||
def collect_inflight_reqs(self) -> Set[Req]:
|
||||
if self.ps.pp_size == 1:
|
||||
if get_parallel().pp_size == 1:
|
||||
inflight_batches = [self.running_batch, self.last_batch]
|
||||
else:
|
||||
inflight_batches = [*self.running_mbs, *self.mbs]
|
||||
@@ -5472,7 +5479,7 @@ class Scheduler(
|
||||
|
||||
if hasattr(req.disagg_kv_sender, "abort"):
|
||||
req.disagg_kv_sender.abort()
|
||||
if self.ps.pp_size > 1:
|
||||
if get_parallel().pp_size > 1:
|
||||
prepare_abort(req, "Aborted by AbortReq.")
|
||||
|
||||
# Abort in-flight requests
|
||||
@@ -5488,7 +5495,7 @@ class Scheduler(
|
||||
if recv_req.abort_all or decode_req.req.rid.startswith(recv_req.rid):
|
||||
logger.debug(f"Abort prealloc queue request. {decode_req.req.rid=}")
|
||||
decode_req.kv_receiver.abort()
|
||||
if self.ps.pp_size > 1:
|
||||
if get_parallel().pp_size > 1:
|
||||
prepare_abort(decode_req.req, "Aborted by AbortReq.")
|
||||
|
||||
# Abort requests waiting for kvcache to release tree cache
|
||||
@@ -5821,7 +5828,11 @@ class Scheduler(
|
||||
output = self.session_controller.open(recv_req)
|
||||
if output.success and self.enable_session_radix_cache:
|
||||
self.tree_cache.open_radix_session(recv_req.session_id)
|
||||
if self.ps.pp_rank == 0 and self.ps.tp_rank == 0 and self.ps.attn_cp_rank == 0:
|
||||
if (
|
||||
get_parallel().pp_rank == 0
|
||||
and get_parallel().tp_rank == 0
|
||||
and get_parallel().attn_cp_rank == 0
|
||||
):
|
||||
return output
|
||||
return None
|
||||
|
||||
@@ -5922,6 +5933,20 @@ def _dispatch_event_loop_once(scheduler: Scheduler):
|
||||
scheduler.event_loop_normal_disagg_decode()
|
||||
|
||||
|
||||
def resolve_spawn_dp_rank(dp_rank: Optional[int]) -> Optional[int]:
|
||||
"""The `dp_rank` this process was spawned with, in either of its two forms.
|
||||
|
||||
A router does not pass it as an argument, it sets `SGLANG_DP_RANK`. Both
|
||||
forms are the launcher naming this process's place, so both have to be in
|
||||
hand before `publish` records the placement -- resolving one of them after
|
||||
would leave the context answering `None` for a process that has a rank.
|
||||
"""
|
||||
if dp_rank is None and "SGLANG_DP_RANK" in os.environ:
|
||||
# [For Router] if env var "SGLANG_DP_RANK" exist, set dp_rank to the value of the env var
|
||||
return int(os.environ["SGLANG_DP_RANK"])
|
||||
return dp_rank
|
||||
|
||||
|
||||
def configure_scheduler_process(
|
||||
server_args: ServerArgs,
|
||||
gpu_id: int,
|
||||
@@ -5934,18 +5959,15 @@ def configure_scheduler_process(
|
||||
display_tp_rank: Optional[int] = None,
|
||||
display_dp_rank: Optional[int] = None,
|
||||
display_moe_ep_rank: Optional[int] = None,
|
||||
) -> Optional[int]:
|
||||
) -> None:
|
||||
"""Configure scheduler worker logging and process title.
|
||||
|
||||
display_* ranks are cosmetic; runtime ranks stay local.
|
||||
display_* ranks are cosmetic; runtime ranks stay local. `dp_rank` arrives
|
||||
already resolved -- see `resolve_spawn_dp_rank`.
|
||||
"""
|
||||
kill_itself_when_parent_died()
|
||||
|
||||
# Generate the logger prefix
|
||||
if dp_rank is None and "SGLANG_DP_RANK" in os.environ:
|
||||
# [For Router] if env var "SGLANG_DP_RANK" exist, set dp_rank to the value of the env var
|
||||
dp_rank = int(os.environ["SGLANG_DP_RANK"])
|
||||
|
||||
shown_dp = display_dp_rank if display_dp_rank is not None else dp_rank
|
||||
shown_tp = display_tp_rank if display_tp_rank is not None else tp_rank
|
||||
shown_moe_ep = (
|
||||
@@ -5987,8 +6009,6 @@ def configure_scheduler_process(
|
||||
if numa_node is not None:
|
||||
numa_bind_to_node(numa_node)
|
||||
|
||||
return dp_rank
|
||||
|
||||
|
||||
def run_scheduler_process(
|
||||
server_args: ServerArgs,
|
||||
@@ -6007,9 +6027,20 @@ def run_scheduler_process(
|
||||
):
|
||||
# Load plugins so hooks can override Scheduler and its dependencies.
|
||||
load_plugins()
|
||||
# Publish before anything in this process reads configuration.
|
||||
publish(server_args, role="scheduler")
|
||||
dp_rank = configure_scheduler_process(
|
||||
dp_rank = resolve_spawn_dp_rank(dp_rank)
|
||||
# Publish before anything in this process reads configuration, with the
|
||||
# placement the launcher decided: from here on a rank read is answered
|
||||
# without a process group, which is what every reader needs before
|
||||
# `init_torch_distributed` has run.
|
||||
publish(
|
||||
server_args,
|
||||
role="scheduler",
|
||||
ranks=SpawnRanks(
|
||||
world_rank=spawn_world_rank(server_args, tp_rank=tp_rank, pp_rank=pp_rank),
|
||||
dp_rank=dp_rank,
|
||||
),
|
||||
)
|
||||
configure_scheduler_process(
|
||||
server_args,
|
||||
gpu_id,
|
||||
tp_rank,
|
||||
|
||||
@@ -548,8 +548,8 @@ class SchedulerDPAttnAdapter:
|
||||
local_batch,
|
||||
model_runner=self.model_runner,
|
||||
dp_size=get_parallel().dp_size,
|
||||
attn_tp_size=self.ps.attn_tp_size,
|
||||
attn_cp_size=self.ps.attn_cp_size,
|
||||
attn_tp_size=get_parallel().attn_tp_size,
|
||||
attn_cp_size=get_parallel().attn_cp_size,
|
||||
tp_group=self.tp_group,
|
||||
get_idle_batch=self.get_idle_batch,
|
||||
disable_cuda_graph=cuda_graph_fully_disabled(),
|
||||
|
||||
@@ -19,6 +19,7 @@ from sglang.srt.disaggregation.kv_events import (
|
||||
select_kv_publisher_dp_rank,
|
||||
)
|
||||
from sglang.srt.managers.io_struct import hook_custom_types, sock_send
|
||||
from sglang.srt.runtime_context import get_parallel
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from sglang.srt.distributed.parallel_state_wrapper import ParallelState
|
||||
@@ -68,7 +69,7 @@ class SchedulerKvEventsPublisher:
|
||||
self.kv_event_publisher = EventPublisherFactory.create(
|
||||
kv_events_config,
|
||||
select_kv_publisher_dp_rank(
|
||||
self.ps.attn_dp_size, self.ps.attn_dp_rank, self.ps.dp_rank
|
||||
self.ps.attn_dp_size, self.ps.attn_dp_rank, get_parallel().dp_rank
|
||||
),
|
||||
)
|
||||
|
||||
@@ -87,7 +88,7 @@ class SchedulerKvEventsPublisher:
|
||||
kv_metrics.gpu_cache_usage_perc = self.get_stats().token_usage
|
||||
kv_metrics.gpu_prefix_cache_hit_rate = self.get_stats().cache_hit_rate
|
||||
kv_metrics.data_parallel_rank = (
|
||||
self.ps.dp_rank if self.ps.dp_rank is not None else 0
|
||||
get_parallel().dp_rank if get_parallel().dp_rank is not None else 0
|
||||
)
|
||||
|
||||
if not self.send_metrics_from_scheduler.closed:
|
||||
|
||||
@@ -14,7 +14,7 @@ from sglang.srt.managers.load_snapshot import (
|
||||
QueueMetrics,
|
||||
SpeculativeMetrics,
|
||||
)
|
||||
from sglang.srt.runtime_context import get_lora
|
||||
from sglang.srt.runtime_context import get_lora, get_parallel
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from sglang.srt.distributed.parallel_state_wrapper import ParallelState
|
||||
@@ -215,7 +215,9 @@ class SchedulerLoadInquirer:
|
||||
decode_moments = list(totals) if totals[0] > 0 else None
|
||||
|
||||
return LoadSnapshot(
|
||||
dp_rank=int(self.ps.dp_rank) if self.ps.dp_rank is not None else 0,
|
||||
dp_rank=int(get_parallel().dp_rank)
|
||||
if get_parallel().dp_rank is not None
|
||||
else 0,
|
||||
timestamp=time.time(),
|
||||
num_running_reqs=num_running_reqs,
|
||||
num_waiting_reqs=num_waiting_reqs,
|
||||
|
||||
@@ -309,7 +309,7 @@ class SchedulerMetricsReporter:
|
||||
if (
|
||||
get_observability().enable_forward_pass_metrics
|
||||
and self.scheduler.ps.attn_tp_rank == 0
|
||||
and self.scheduler.ps.pp_rank == self.scheduler.ps.pp_size - 1
|
||||
and get_parallel().pp_rank == get_parallel().pp_size - 1
|
||||
):
|
||||
from sglang.srt.observability.forward_pass_metrics import (
|
||||
_FpmPublisherThread,
|
||||
|
||||
@@ -32,7 +32,7 @@ from sglang.srt.managers.schedule_batch import (
|
||||
Req,
|
||||
)
|
||||
from sglang.srt.mem_cache.base_prefix_cache import BasePrefixCache
|
||||
from sglang.srt.runtime_context import get_observability, get_serving
|
||||
from sglang.srt.runtime_context import get_observability, get_parallel, get_serving
|
||||
from sglang.srt.server_args import ServerArgs
|
||||
from sglang.srt.speculative.spec_info import SpeculativeAlgorithm
|
||||
from sglang.srt.utils.weight_versions import compute_weight_version_spans
|
||||
@@ -205,7 +205,7 @@ class SchedulerOutputStreamer:
|
||||
|
||||
# Send to detokenizer
|
||||
payload = acc.to_payload(
|
||||
dp_rank=self.ps.dp_rank,
|
||||
dp_rank=get_parallel().dp_rank,
|
||||
is_idle_batch=is_idle_batch,
|
||||
)
|
||||
if payload is not None:
|
||||
@@ -232,7 +232,7 @@ class SchedulerOutputStreamer:
|
||||
def _maybe_log_time_stats(self, *, req: Req) -> None:
|
||||
if (
|
||||
req.finished()
|
||||
and self.ps.attn_tp_rank == 0
|
||||
and get_parallel().attn_tp_rank == 0
|
||||
and get_observability().enable_request_time_stats_logging
|
||||
):
|
||||
req.log_time_stats()
|
||||
|
||||
@@ -21,7 +21,7 @@ from sglang.srt.managers.io_struct import ProfileReq, ProfileReqOutput, ProfileR
|
||||
from sglang.srt.model_executor.forward_batch_info import ForwardMode
|
||||
from sglang.srt.model_executor.step_span_utils import set_detailed_annotations_enabled
|
||||
from sglang.srt.platforms import current_platform
|
||||
from sglang.srt.runtime_context import get_device
|
||||
from sglang.srt.runtime_context import get_device, get_parallel
|
||||
from sglang.srt.utils import is_mps, is_npu
|
||||
from sglang.srt.utils.profile_merger import ProfileMerger
|
||||
from sglang.srt.utils.profile_utils import ProfileManager
|
||||
@@ -207,10 +207,13 @@ class SchedulerProfilerManager:
|
||||
|
||||
self.rpd_profile_path = os.path.join(
|
||||
self.torch_profiler_output_dir,
|
||||
"rpd-" + str(time.time()) + f"-TP-{self.ps.tp_rank}" + ".trace.json.gz",
|
||||
"rpd-"
|
||||
+ str(time.time())
|
||||
+ f"-TP-{get_parallel().tp_rank}"
|
||||
+ ".trace.json.gz",
|
||||
)
|
||||
|
||||
if self.ps.tp_rank == 0:
|
||||
if get_parallel().tp_rank == 0:
|
||||
import sqlite3
|
||||
|
||||
from rocpd.schema import RocpdSchema
|
||||
@@ -282,13 +285,13 @@ class SchedulerProfilerManager:
|
||||
if not self.merge_profiles:
|
||||
return ""
|
||||
|
||||
if self.ps.tp_rank != 0:
|
||||
if get_parallel().tp_rank != 0:
|
||||
return ""
|
||||
if self.ps.dp_size > 1 and self.ps.dp_rank != 0:
|
||||
if self.ps.dp_size > 1 and get_parallel().dp_rank != 0:
|
||||
return ""
|
||||
if self.ps.pp_size > 1 and self.ps.pp_rank != 0:
|
||||
if get_parallel().pp_size > 1 and get_parallel().pp_rank != 0:
|
||||
return ""
|
||||
if self.ps.moe_ep_size > 1 and self.ps.moe_ep_rank != 0:
|
||||
if get_parallel().moe_ep_size > 1 and get_parallel().moe_ep_rank != 0:
|
||||
return ""
|
||||
|
||||
try:
|
||||
@@ -336,15 +339,15 @@ class SchedulerProfilerManager:
|
||||
self.torch_profiler.stop()
|
||||
if not _is_npu:
|
||||
# Build filename with only non-zero ranks to maintain backward compatibility
|
||||
filename_parts = [self.profile_id, f"TP-{self.ps.tp_rank}"]
|
||||
filename_parts = [self.profile_id, f"TP-{get_parallel().tp_rank}"]
|
||||
|
||||
# Only add other ranks if parallelism is enabled (size > 1)
|
||||
if self.ps.dp_size > 1:
|
||||
filename_parts.append(f"DP-{self.ps.dp_rank}")
|
||||
if self.ps.pp_size > 1:
|
||||
filename_parts.append(f"PP-{self.ps.pp_rank}")
|
||||
if self.ps.moe_ep_size > 1:
|
||||
filename_parts.append(f"EP-{self.ps.moe_ep_rank}")
|
||||
filename_parts.append(f"DP-{get_parallel().dp_rank}")
|
||||
if get_parallel().pp_size > 1:
|
||||
filename_parts.append(f"PP-{get_parallel().pp_rank}")
|
||||
if get_parallel().moe_ep_size > 1:
|
||||
filename_parts.append(f"EP-{get_parallel().moe_ep_rank}")
|
||||
|
||||
filename = (
|
||||
stage_prefix
|
||||
@@ -364,7 +367,7 @@ class SchedulerProfilerManager:
|
||||
self.rpd_profiler.flush()
|
||||
|
||||
torch.distributed.barrier(self.dp_tp_cpu_group)
|
||||
if self.ps.tp_rank == 0:
|
||||
if get_parallel().tp_rank == 0:
|
||||
from sglang.srt.utils.rpd_utils import rpd_to_chrome_trace
|
||||
|
||||
rpd_to_chrome_trace("trace.rpd", self.rpd_profile_path)
|
||||
@@ -376,7 +379,7 @@ class SchedulerProfilerManager:
|
||||
self.torch_profiler_output_dir,
|
||||
stage_prefix
|
||||
+ str(time.time())
|
||||
+ f"-TP-{self.ps.tp_rank}-memory"
|
||||
+ f"-TP-{get_parallel().tp_rank}-memory"
|
||||
+ stage_suffix
|
||||
+ ".pickle",
|
||||
)
|
||||
|
||||
@@ -109,7 +109,7 @@ class SchedulerRequestReceiver:
|
||||
|
||||
recv_reqs = self._broadcast_reqs_across_ranks(recv_reqs, local_reqs)
|
||||
|
||||
if self.ps.pp_rank == 0:
|
||||
if get_parallel().pp_rank == 0:
|
||||
self.unwrap_pickle_wrapper(recv_reqs)
|
||||
|
||||
recv_reqs = self._apply_mm_receiver(recv_reqs)
|
||||
@@ -119,7 +119,7 @@ class SchedulerRequestReceiver:
|
||||
return recv_reqs
|
||||
|
||||
def _pull_raw_reqs(self) -> Optional[List]:
|
||||
if self.ps.pp_rank == 0:
|
||||
if get_parallel().pp_rank == 0:
|
||||
if self.ps.attn_tp_rank == 0 and self.ps.attn_cp_rank == 0:
|
||||
recv_reqs = []
|
||||
|
||||
@@ -158,10 +158,10 @@ class SchedulerRequestReceiver:
|
||||
)
|
||||
recv_reqs = point_to_point_pyobj(
|
||||
[],
|
||||
self.ps.pp_rank * self.ps.tp_size + dp_offset,
|
||||
get_parallel().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,
|
||||
(get_parallel().pp_rank - 1) * self.ps.tp_size + dp_offset,
|
||||
get_parallel().pp_rank * self.ps.tp_size + dp_offset,
|
||||
)
|
||||
else:
|
||||
recv_reqs = None
|
||||
@@ -232,7 +232,7 @@ class SchedulerRequestReceiver:
|
||||
def _apply_mm_receiver(self, recv_reqs: List) -> List:
|
||||
# Process MM requests under EPD-disaggregation mode
|
||||
if (
|
||||
self.ps.pp_rank == 0
|
||||
get_parallel().pp_rank == 0
|
||||
and get_disagg().language_only
|
||||
and get_disagg().encoder_transfer_backend
|
||||
in ["zmq_to_scheduler", "mooncake"]
|
||||
|
||||
@@ -103,7 +103,9 @@ class SchedulerPPMixin:
|
||||
for mb_id in range(self.pp_loop_size):
|
||||
self.running_batch = self.running_mbs[mb_id]
|
||||
self.last_batch = self.last_mbs[mb_id]
|
||||
next_first_rank_mb_id = (mb_id + self.ps.pp_size) % self.pp_loop_size
|
||||
next_first_rank_mb_id = (
|
||||
mb_id + get_parallel().pp_size
|
||||
) % self.pp_loop_size
|
||||
next_mb_id = (mb_id + 1) % self.pp_loop_size
|
||||
with torch.profiler.record_function("recv_requests"):
|
||||
recv_reqs = self.ingest_requests()
|
||||
@@ -243,7 +245,9 @@ class SchedulerPPMixin:
|
||||
for mb_id in range(self.pp_loop_size):
|
||||
self.running_batch = self.running_mbs[mb_id]
|
||||
self.last_batch = self.last_mbs[mb_id]
|
||||
next_first_rank_mb_id = (mb_id + self.ps.pp_size) % self.pp_loop_size
|
||||
next_first_rank_mb_id = (
|
||||
mb_id + get_parallel().pp_size
|
||||
) % self.pp_loop_size
|
||||
next_mb_id = (mb_id + 1) % self.pp_loop_size
|
||||
|
||||
next_pp_outputs = None
|
||||
@@ -391,7 +395,9 @@ class SchedulerPPMixin:
|
||||
for mb_id in range(self.pp_loop_size):
|
||||
self.running_batch = self.running_mbs[mb_id]
|
||||
self.last_batch = self.last_mbs[mb_id]
|
||||
next_first_rank_mb_id = (mb_id + self.ps.pp_size) % self.pp_loop_size
|
||||
next_first_rank_mb_id = (
|
||||
mb_id + get_parallel().pp_size
|
||||
) % self.pp_loop_size
|
||||
next_mb_id = (mb_id + 1) % self.pp_loop_size
|
||||
|
||||
next_pp_outputs = None
|
||||
@@ -561,7 +567,9 @@ class SchedulerPPMixin:
|
||||
self.on_idle()
|
||||
|
||||
def init_pp_loop_state(self: Scheduler):
|
||||
self.pp_loop_size: int = self.ps.pp_size + get_parallel().pp_async_batch_depth
|
||||
self.pp_loop_size: int = (
|
||||
get_parallel().pp_size + get_parallel().pp_async_batch_depth
|
||||
)
|
||||
self.mbs = [None] * self.pp_loop_size
|
||||
self.last_mbs = [None] * self.pp_loop_size
|
||||
self.running_mbs = [
|
||||
@@ -573,7 +581,7 @@ class SchedulerPPMixin:
|
||||
self.last_rank_comm_queue: deque[Tuple[torch.Event, PPProxyTensors]] = deque()
|
||||
self._pp_spec_relay = (
|
||||
envs.SGLANG_ENABLE_PP_SPEC.get()
|
||||
and self.ps.pp_size > 1
|
||||
and get_parallel().pp_size > 1
|
||||
and not self.spec_algorithm.is_none()
|
||||
)
|
||||
|
||||
@@ -805,31 +813,39 @@ class SchedulerPPMixin:
|
||||
|
||||
def _pp_send_pyobj_to_next_stage(self: Scheduler, data, async_send: bool = False):
|
||||
p2p_work = []
|
||||
if self.ps.attn_tp_rank == 0 and self.ps.attn_cp_rank == 0:
|
||||
if get_parallel().attn_tp_rank == 0 and get_parallel().attn_cp_rank == 0:
|
||||
dp_offset = (
|
||||
self.ps.attn_dp_rank * self.ps.attn_cp_size * self.ps.attn_tp_size
|
||||
self.ps.attn_dp_rank
|
||||
* get_parallel().attn_cp_size
|
||||
* get_parallel().attn_tp_size
|
||||
)
|
||||
p2p_work = point_to_point_pyobj(
|
||||
data,
|
||||
self.ps.pp_rank * self.ps.tp_size + dp_offset,
|
||||
get_parallel().pp_rank * get_parallel().tp_size + dp_offset,
|
||||
self.world_group.cpu_group,
|
||||
self.ps.pp_rank * self.ps.tp_size + dp_offset,
|
||||
((self.ps.pp_rank + 1) % self.ps.pp_size) * self.ps.tp_size + dp_offset,
|
||||
get_parallel().pp_rank * get_parallel().tp_size + dp_offset,
|
||||
((get_parallel().pp_rank + 1) % get_parallel().pp_size)
|
||||
* get_parallel().tp_size
|
||||
+ dp_offset,
|
||||
async_send=async_send,
|
||||
)
|
||||
return p2p_work
|
||||
|
||||
def _pp_recv_pyobj_from_prev_stage(self: Scheduler):
|
||||
if self.ps.attn_tp_rank == 0 and self.ps.attn_cp_rank == 0:
|
||||
if get_parallel().attn_tp_rank == 0 and get_parallel().attn_cp_rank == 0:
|
||||
dp_offset = (
|
||||
self.ps.attn_dp_rank * self.ps.attn_cp_size * self.ps.attn_tp_size
|
||||
self.ps.attn_dp_rank
|
||||
* get_parallel().attn_cp_size
|
||||
* get_parallel().attn_tp_size
|
||||
)
|
||||
data = point_to_point_pyobj(
|
||||
[],
|
||||
self.ps.pp_rank * self.ps.tp_size + dp_offset,
|
||||
get_parallel().pp_rank * get_parallel().tp_size + dp_offset,
|
||||
self.world_group.cpu_group,
|
||||
((self.ps.pp_rank - 1) % self.ps.pp_size) * self.ps.tp_size + dp_offset,
|
||||
self.ps.pp_rank * self.ps.tp_size + dp_offset,
|
||||
((get_parallel().pp_rank - 1) % get_parallel().pp_size)
|
||||
* get_parallel().tp_size
|
||||
+ dp_offset,
|
||||
get_parallel().pp_rank * get_parallel().tp_size + dp_offset,
|
||||
)
|
||||
else:
|
||||
data = None
|
||||
@@ -1470,7 +1486,7 @@ class SchedulerPPMixin:
|
||||
# posted, so the parity-based send-first/recv-first ordering used
|
||||
# for NPU is replaced by batch_isend_irecv which submits all
|
||||
# send/recv operations atomically.
|
||||
if _is_npu and self.ps.pp_size == 2:
|
||||
if _is_npu and get_parallel().pp_size == 2:
|
||||
return self._pp2_only_send_recv_output_tensors_npu(
|
||||
next_first_rank_mb_id,
|
||||
next_mb_id,
|
||||
@@ -1498,7 +1514,7 @@ class SchedulerPPMixin:
|
||||
# makes rank 1 post its recv first, which breaks the cycle for any
|
||||
# pp_size > 1.
|
||||
needs_pairing = is_xpu() or self._pp_spec_relay
|
||||
send_first = (not needs_pairing) or ((self.ps.pp_rank % 2) == 0)
|
||||
send_first = (not needs_pairing) or ((get_parallel().pp_rank % 2) == 0)
|
||||
|
||||
def _do_send():
|
||||
return self._pp_send_output_to_next_stage(
|
||||
|
||||
@@ -418,7 +418,7 @@ class TpModelWorker(BaseTpWorker):
|
||||
else:
|
||||
self.random_seed = broadcast_pyobj(
|
||||
[get_device().random_seed],
|
||||
self.ps.tp_size * self.ps.pp_rank + self.ps.tp_rank,
|
||||
self.ps.tp_size * get_parallel().pp_rank + self.ps.tp_rank,
|
||||
self.world_group.cpu_group,
|
||||
src=self.world_group.ranks[0],
|
||||
)[0]
|
||||
@@ -451,7 +451,8 @@ class TpModelWorker(BaseTpWorker):
|
||||
assert self.model_runner.max_running_requests > 0, "max_running_request is zero"
|
||||
max_req_len = min(
|
||||
self.model_config.context_len - 1,
|
||||
self.model_runner.effective_max_total_num_tokens * self.ps.attn_dcp_size
|
||||
self.model_runner.effective_max_total_num_tokens
|
||||
* get_parallel().attn_dcp_size
|
||||
- 1,
|
||||
)
|
||||
assert max_req_len > 0, "Memory pool size is too small"
|
||||
@@ -578,7 +579,8 @@ class TpModelWorker(BaseTpWorker):
|
||||
def get_worker_info(self):
|
||||
max_req_len = min(
|
||||
self.model_config.context_len - 1,
|
||||
self.model_runner.effective_max_total_num_tokens * self.ps.attn_dcp_size
|
||||
self.model_runner.effective_max_total_num_tokens
|
||||
* get_parallel().attn_dcp_size
|
||||
- 1,
|
||||
)
|
||||
return (
|
||||
|
||||
@@ -2420,7 +2420,9 @@ class KVCacheConfigurator:
|
||||
sum(1 for i in all_mamba_layers if start <= i < end)
|
||||
for start, end in (
|
||||
get_pp_indices(
|
||||
self.model_config.num_hidden_layers, rank, self.ps.pp_size
|
||||
self.model_config.num_hidden_layers,
|
||||
rank,
|
||||
self.ps.pp_size,
|
||||
)
|
||||
for rank in range(self.ps.pp_size)
|
||||
)
|
||||
|
||||
@@ -445,7 +445,7 @@ class ModelRunner:
|
||||
self.prefill_shared_read_stager: Optional[Callable[[ForwardBatch], bool]] = None
|
||||
|
||||
# CPU offload
|
||||
set_offloader(create_offloader(dp_rank=self.ps.dp_rank))
|
||||
set_offloader(create_offloader(dp_rank=get_parallel().dp_rank))
|
||||
|
||||
self._weight_checker = WeightChecker(get_model=lambda: self.model, ps=self.ps)
|
||||
|
||||
@@ -481,7 +481,7 @@ class ModelRunner:
|
||||
"pp_proxy_tensors" in inspect.signature(self.model.forward).parameters
|
||||
)
|
||||
|
||||
if self.ps.pp_size > 1:
|
||||
if get_parallel().pp_size > 1:
|
||||
if not (envs.SGLANG_ENABLE_PP_SPEC.get() and self.is_draft_worker):
|
||||
assert self.support_pp, (
|
||||
"Pipeline Parallel is not compatible with this model."
|
||||
@@ -647,8 +647,8 @@ class ModelRunner:
|
||||
from sglang.srt.model_executor.mindspore_runner import init_ms_distributed
|
||||
|
||||
init_ms_distributed(
|
||||
world_size=self.ps.tp_size * self.ps.pp_size,
|
||||
rank=self.ps.tp_size * self.ps.pp_rank + self.ps.tp_rank,
|
||||
world_size=self.ps.tp_size * get_parallel().pp_size,
|
||||
rank=self.ps.tp_size * get_parallel().pp_rank + self.ps.tp_rank,
|
||||
local_rank=self.gpu_id,
|
||||
port=self.dist_port,
|
||||
)
|
||||
@@ -667,8 +667,8 @@ class ModelRunner:
|
||||
prepare_moe_topk(
|
||||
model=self.model,
|
||||
model_config=self.model_config,
|
||||
moe_ep_size=self.ps.moe_ep_size,
|
||||
moe_ep_rank=self.ps.moe_ep_rank,
|
||||
moe_ep_size=get_parallel().moe_ep_size,
|
||||
moe_ep_rank=get_parallel().moe_ep_rank,
|
||||
)
|
||||
|
||||
self.maybe_init_dwdp()
|
||||
@@ -708,7 +708,7 @@ class ModelRunner:
|
||||
def maybe_init_expert_location_metadata(self):
|
||||
if self.is_draft_worker:
|
||||
return
|
||||
expert_rank = self.ps.moe_ep_rank + (
|
||||
expert_rank = get_parallel().moe_ep_rank + (
|
||||
get_parallel().ep_join_rank_offset
|
||||
if get_exec().moe.is_ep_scale_joiner
|
||||
else 0
|
||||
@@ -767,8 +767,8 @@ class ModelRunner:
|
||||
self.expert_backup_client = (
|
||||
ExpertBackupClient(
|
||||
model_config=self.model_config,
|
||||
moe_ep_size=self.ps.moe_ep_size,
|
||||
moe_ep_rank=self.ps.moe_ep_rank,
|
||||
moe_ep_size=get_parallel().moe_ep_size,
|
||||
moe_ep_rank=get_parallel().moe_ep_rank,
|
||||
get_model=lambda: self.model,
|
||||
)
|
||||
if (
|
||||
@@ -803,16 +803,16 @@ class ModelRunner:
|
||||
def get_pp_proxy_topk_size(self) -> Optional[int]:
|
||||
return misc_utils.resolve_pp_proxy_topk_size(
|
||||
model_config=self.model_config,
|
||||
pp_size=self.ps.pp_size,
|
||||
pp_rank=self.ps.pp_rank,
|
||||
pp_size=get_parallel().pp_size,
|
||||
pp_rank=get_parallel().pp_rank,
|
||||
start_layer=self.layer_info.start_layer,
|
||||
)
|
||||
|
||||
def get_pp_proxy_residual_num_blocks(self) -> Optional[int]:
|
||||
return misc_utils.resolve_pp_proxy_residual_num_blocks(
|
||||
model_config=self.model_config,
|
||||
pp_size=self.ps.pp_size,
|
||||
pp_rank=self.ps.pp_rank,
|
||||
pp_size=get_parallel().pp_size,
|
||||
pp_rank=get_parallel().pp_rank,
|
||||
start_layer=self.layer_info.start_layer,
|
||||
)
|
||||
|
||||
@@ -980,7 +980,7 @@ class ModelRunner:
|
||||
swap_in_block_size=hisparse_cfg.swap_in_block_size,
|
||||
shared_index_layers=resolve_shared_index_layers(
|
||||
hf_text_config=self.model_config.hf_text_config,
|
||||
pp_size=self.ps.pp_size,
|
||||
pp_size=get_parallel().pp_size,
|
||||
is_speculative=self.spec_algorithm.is_speculative(),
|
||||
),
|
||||
)
|
||||
@@ -1157,8 +1157,8 @@ class ModelRunner:
|
||||
check_quantized_moe_compatibility(
|
||||
model_config=self.model_config,
|
||||
tp_size=self.ps.tp_size,
|
||||
moe_ep_size=self.ps.moe_ep_size,
|
||||
moe_dp_size=self.ps.moe_dp_size,
|
||||
moe_ep_size=get_parallel().moe_ep_size,
|
||||
moe_dp_size=get_parallel().moe_dp_size,
|
||||
)
|
||||
|
||||
def init_torch_distributed(self):
|
||||
@@ -1293,7 +1293,7 @@ class ModelRunner:
|
||||
is_draft_worker=self.is_draft_worker,
|
||||
tp_size=self.ps.tp_size,
|
||||
tp_rank=self.ps.tp_rank,
|
||||
pp_rank=self.ps.pp_rank,
|
||||
pp_rank=get_parallel().pp_rank,
|
||||
)
|
||||
|
||||
if dumper.may_enable:
|
||||
@@ -1580,7 +1580,7 @@ class ModelRunner:
|
||||
dp_size = 1 if get_parallel().enable_dp_attention else self.ps.dp_size
|
||||
self.local_omp_cpuid = numa_utils.init_threads_binding(
|
||||
numa_index=self.gpu_id,
|
||||
world_size=dp_size * self.ps.tp_size * self.ps.pp_size,
|
||||
world_size=dp_size * self.ps.tp_size * get_parallel().pp_size,
|
||||
)
|
||||
|
||||
def apply_torch_tp(self):
|
||||
|
||||
@@ -274,7 +274,7 @@ class BaseRunner(ABC):
|
||||
if (
|
||||
envs.SGLANG_PP_PARALLEL_DEEPGEMM_WARMUP.get()
|
||||
and deep_gemm_wrapper.ENABLE_JIT_DEEPGEMM
|
||||
and mr.ps.pp_size > 1
|
||||
and get_parallel().pp_size > 1
|
||||
and not mr.spec_algorithm.is_speculative()
|
||||
):
|
||||
from sglang.srt.layers.deep_gemm_wrapper.compile_utils import (
|
||||
@@ -546,7 +546,7 @@ class BaseRunner(ABC):
|
||||
pp_hidden_tokens = num_tokens
|
||||
if (
|
||||
capture_forward_mode == ForwardMode.EXTEND
|
||||
and mr.ps.pp_rank != 0
|
||||
and get_parallel().pp_rank != 0
|
||||
and mr.ps.attn_cp_size > 1
|
||||
):
|
||||
pp_hidden_tokens = num_tokens // mr.ps.attn_cp_size
|
||||
|
||||
@@ -30,6 +30,7 @@ from sglang.srt.runtime_context import (
|
||||
get_disagg,
|
||||
get_exec,
|
||||
get_model,
|
||||
get_parallel,
|
||||
get_schedule,
|
||||
get_spec,
|
||||
max_prefill_buffer_tokens,
|
||||
@@ -147,7 +148,7 @@ def flashinfer_autotune_cache_path(model_runner: ModelRunner) -> Path:
|
||||
str(get_model().quantization),
|
||||
str(get_exec().moe.moe_runner_backend),
|
||||
str(mr.ps.tp_size),
|
||||
str(mr.ps.pp_size),
|
||||
str(get_parallel().pp_size),
|
||||
str(mr.ps.attn_dp_size),
|
||||
str(mr.ps.moe_ep_size),
|
||||
str(mr.model_config.hf_config.__class__.__name__),
|
||||
@@ -170,7 +171,7 @@ def flashinfer_autotune_cache_path(model_runner: ModelRunner) -> Path:
|
||||
cache_dir.mkdir(parents=True, exist_ok=True)
|
||||
return (
|
||||
cache_dir
|
||||
/ f"rank_tp{mr.ps.tp_rank}_pp{mr.ps.pp_rank}_dp{mr.ps.dp_rank or 0}.json"
|
||||
/ f"rank_tp{mr.ps.tp_rank}_pp{get_parallel().pp_rank}_dp{mr.ps.dp_rank or 0}.json"
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -34,6 +34,7 @@ from sglang.srt.runtime_context import (
|
||||
get_context,
|
||||
get_disagg,
|
||||
get_observability,
|
||||
get_parallel,
|
||||
get_schedule,
|
||||
get_serving,
|
||||
)
|
||||
@@ -1118,7 +1119,7 @@ class SchedulerMetricsCollector(_StatLoggerDIMixin):
|
||||
)
|
||||
enable_kv_cache_events = bool(
|
||||
get_observability().kv_events_config
|
||||
and ps.pp_rank == 0
|
||||
and get_parallel().pp_rank == 0
|
||||
and ps.attn_tp_rank == 0
|
||||
and ps.attn_cp_rank == 0
|
||||
)
|
||||
|
||||
@@ -21,7 +21,7 @@ from typing import Any, Dict, Optional
|
||||
import ray
|
||||
|
||||
from sglang.srt.arg_groups.overrides import declare_resolution
|
||||
from sglang.srt.runtime_context import publish
|
||||
from sglang.srt.runtime_context import SpawnRanks, publish, spawn_world_rank
|
||||
from sglang.srt.server_args import PortArgs, ServerArgs
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -49,7 +49,11 @@ class SchedulerActor:
|
||||
dist_init_addr: Optional[str] = None,
|
||||
):
|
||||
from sglang.srt.environ import envs
|
||||
from sglang.srt.managers.scheduler import Scheduler, configure_scheduler_process
|
||||
from sglang.srt.managers.scheduler import (
|
||||
Scheduler,
|
||||
configure_scheduler_process,
|
||||
resolve_spawn_dp_rank,
|
||||
)
|
||||
from sglang.srt.utils.numa_utils import (
|
||||
get_numa_node_if_available,
|
||||
numa_bind_to_node,
|
||||
@@ -77,12 +81,23 @@ class SchedulerActor:
|
||||
actual_gpu_id = gpu_id
|
||||
logger.info(f"[TP{tp_rank}] Using passed gpu_id: {gpu_id}")
|
||||
|
||||
dp_rank = resolve_spawn_dp_rank(dp_rank)
|
||||
|
||||
# This actor takes the place of run_scheduler_process, which is where
|
||||
# a forked scheduler publishes.
|
||||
publish(server_args, role="scheduler")
|
||||
publish(
|
||||
server_args,
|
||||
role="scheduler",
|
||||
ranks=SpawnRanks(
|
||||
world_rank=spawn_world_rank(
|
||||
server_args, tp_rank=tp_rank, pp_rank=pp_rank
|
||||
),
|
||||
dp_rank=dp_rank,
|
||||
),
|
||||
)
|
||||
|
||||
# Configure worker (logging, process title, etc.)
|
||||
dp_rank = configure_scheduler_process(
|
||||
configure_scheduler_process(
|
||||
server_args,
|
||||
actual_gpu_id,
|
||||
tp_rank,
|
||||
|
||||
@@ -132,6 +132,10 @@ class Live(msgspec.Struct, frozen=True):
|
||||
|
||||
source: Any = None
|
||||
doc: str = ""
|
||||
# For a stamp-only name (`source=None`): what a reader should be told when
|
||||
# nothing has stamped it. These names have no fallback by construction, so
|
||||
# the message is the only thing pointing at what did not happen.
|
||||
unstamped: str = ""
|
||||
|
||||
|
||||
_LIVE_READS: dict = {
|
||||
@@ -177,7 +181,35 @@ _LIVE_READS: dict = {
|
||||
"attn_cp_rank": "get_attn_context_model_parallel_rank",
|
||||
"dcp_rank": "get_dcp_rank",
|
||||
"attn_dcp_rank": lambda self: self.dcp_rank if self.dcp_enabled else 0,
|
||||
"attn_dp_rank": None,
|
||||
"attn_dp_rank": Live(
|
||||
source=None,
|
||||
doc=(
|
||||
"This process's index in the attention-DP group. Computed from "
|
||||
"`tp_rank` when the attention topology is initialized, and moved "
|
||||
"by an elastic scale-up, so no coordinator can answer it."
|
||||
),
|
||||
unstamped=(
|
||||
"it is computed from this process's `tp_rank` when the attention "
|
||||
"topology is initialized, so a process that never ran "
|
||||
"`initialize_dp_attention` has no answer to give"
|
||||
),
|
||||
),
|
||||
"dp_rank": Live(
|
||||
source=None,
|
||||
doc=(
|
||||
"Which data-parallel replica this process serves, as the data "
|
||||
"parallel controller numbered them at spawn. `None` when there is "
|
||||
"no controller. Unlike `attn_dp_rank` and `moe_dp_rank` it is not "
|
||||
"a position in any process group -- no group has one member per "
|
||||
"replica -- which is why nothing can derive it and the spawn "
|
||||
"states it instead."
|
||||
),
|
||||
unstamped=(
|
||||
"it is a spawn identity, handed to `publish(..., ranks=...)` by "
|
||||
"the process entry; a process that published without a rank "
|
||||
"bundle has no replica index to report"
|
||||
),
|
||||
),
|
||||
"world_group": "get_world_group",
|
||||
"tp_group": "get_tp_group",
|
||||
"pp_group": "get_pp_group",
|
||||
@@ -227,6 +259,80 @@ def derive_attention_widths(
|
||||
return attn_dp_size, tp_size // attn_dp_size // attn_cp_size
|
||||
|
||||
|
||||
def derive_attention_ranks(
|
||||
*, tp_rank: int, attn_tp_size: int, attn_cp_size: int, enable_dp_attention: bool
|
||||
) -> tuple:
|
||||
"""(attn_tp_rank, attn_dp_rank) for a process at `tp_rank`.
|
||||
|
||||
The rank layout is (dp, cp, tp) with tp the fastest-changing dimension::
|
||||
|
||||
tp_rank = (attn_dp_rank * attn_cp_size + attn_cp_rank) * attn_tp_size
|
||||
+ attn_tp_rank
|
||||
|
||||
Split out beside `derive_attention_widths` because two places need it from
|
||||
different inputs: `publish` has this process's `tp_rank` from the spawn and
|
||||
the widths from the configuration, while `initialize_dp_attention` has them
|
||||
from the groups it just built. They must not carry separate copies of the
|
||||
arithmetic -- the point of computing it at publish is that the two agree.
|
||||
"""
|
||||
attn_tp_rank = tp_rank % attn_tp_size
|
||||
if not enable_dp_attention:
|
||||
return attn_tp_rank, 0
|
||||
return attn_tp_rank, tp_rank // (attn_tp_size * attn_cp_size)
|
||||
|
||||
|
||||
def spawn_world_rank(server_args, *, tp_rank: int, pp_rank: int) -> int:
|
||||
"""This process's place in WORLD, from the ranks its entry was given.
|
||||
|
||||
The inverse of `derive_spawn_ranks`, for the entries that have the pieces
|
||||
but not the whole: the same expression `bootstrap` hands to
|
||||
`init_distributed_environment` when it builds the group.
|
||||
|
||||
Reads a resolving view because it runs before `publish`.
|
||||
"""
|
||||
from sglang.srt.arg_groups.model_override_base import resolving_view
|
||||
|
||||
cfg = resolving_view(server_args)
|
||||
return cfg.ep_join_rank_offset + cfg.tp_size * pp_rank + tp_rank
|
||||
|
||||
|
||||
def derive_spawn_ranks(
|
||||
*,
|
||||
world_rank: int,
|
||||
tp_size: int,
|
||||
ep_join_rank_offset: int,
|
||||
attn_cp_size: int,
|
||||
attn_tp_size: int,
|
||||
moe_dp_size: int,
|
||||
moe_ep_size: int,
|
||||
) -> dict:
|
||||
"""Every rank a process group would answer, from its place in WORLD.
|
||||
|
||||
WORLD is laid out `rank = ep_join_rank_offset + tp_size * pp_rank +
|
||||
tp_rank`: `initialize_model_parallel` builds tensor-parallel groups as
|
||||
contiguous blocks of `tp_size` and pipeline groups strided by it, so the
|
||||
map is a bijection and this is its inverse. The attention and MoE ranks
|
||||
are then positions inside the tensor-parallel block, which is what the
|
||||
launcher computes when it decides what to spawn.
|
||||
|
||||
Pure arithmetic over the published widths: no group is consulted, which is
|
||||
the point -- this runs at publish, before any of them exist.
|
||||
"""
|
||||
local = world_rank - ep_join_rank_offset
|
||||
tp_rank = local % tp_size
|
||||
return {
|
||||
"tp_rank": tp_rank,
|
||||
"pp_rank": local // tp_size,
|
||||
"attn_cp_rank": (tp_rank // attn_tp_size) % attn_cp_size,
|
||||
"moe_dp_rank": tp_rank // (tp_size // moe_dp_size),
|
||||
"moe_ep_rank": (
|
||||
tp_rank
|
||||
% (tp_size // moe_dp_size)
|
||||
// (tp_size // moe_dp_size // moe_ep_size)
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
def derive_parallel_widths(
|
||||
*,
|
||||
tp_size: int,
|
||||
@@ -327,6 +433,31 @@ def dcp_enabled_of(cfg: Any):
|
||||
return parallel_widths_of(cfg)["dcp_enabled"]
|
||||
|
||||
|
||||
class SpawnRanks(msgspec.Struct, frozen=True):
|
||||
"""Where the launcher put this process, in the two numbers only it knows.
|
||||
|
||||
`world_rank` is this process's place in the WORLD group, which fixes every
|
||||
other rank: the groups are laid out from the published widths, so
|
||||
`tp_rank`, `pp_rank` and the attention / MoE ranks are functions of it (see
|
||||
`derive_spawn_ranks`). Passing them separately would be passing the same
|
||||
fact five more times, with five more ways for an entry to contradict
|
||||
itself.
|
||||
|
||||
`dp_rank` is the exception, because data-parallel replicas are separate
|
||||
WORLD groups: with `--dp-size 2` each replica holds ranks `0 .. n-1`, so
|
||||
the rank cannot say which replica this is. `None` means "no controller",
|
||||
which is an answer rather than an absence, and it is recorded as one.
|
||||
|
||||
Nothing else belongs here. A device index, for instance, is a placement
|
||||
decision rather than a position -- the launcher may reindex it, and Ray
|
||||
assigns it from its own allocator -- so it stays an argument to whoever
|
||||
was handed it.
|
||||
"""
|
||||
|
||||
world_rank: int
|
||||
dp_rank: Optional[int] = None
|
||||
|
||||
|
||||
class ParallelContext:
|
||||
"""Parallel-topology namespace: one spelling per name.
|
||||
|
||||
@@ -394,11 +525,10 @@ class ParallelContext:
|
||||
return getattr(_ps(), source)()
|
||||
if source is not None:
|
||||
return source(self)
|
||||
why = live.unstamped if isinstance(live, Live) else ""
|
||||
raise RuntimeError(
|
||||
f"parallel rank {name!r} is not available: it is computed from "
|
||||
"this process's `tp_rank` when the attention topology is "
|
||||
"initialized, so a process that never ran "
|
||||
"`initialize_dp_attention` has no answer to give"
|
||||
f"parallel name {name!r} is not available: "
|
||||
+ (why or "nothing has stamped it in this process")
|
||||
)
|
||||
if config is None and name in _parallel_config_leaves():
|
||||
raise ValueError("config namespace 'parallel' not published")
|
||||
@@ -1598,7 +1728,13 @@ def _dump_recorded_namespace_reads() -> None:
|
||||
)
|
||||
|
||||
|
||||
def publish(server_args, *, role: str, hf_config: Any = None) -> RuntimeContext:
|
||||
def publish(
|
||||
server_args,
|
||||
*,
|
||||
role: str,
|
||||
hf_config: Any = None,
|
||||
ranks: SpawnRanks | None = None,
|
||||
) -> RuntimeContext:
|
||||
"""Install process-wide config for this OS process.
|
||||
|
||||
Records the process ``role`` — one of the ``ROLE_NAMESPACE_SETS`` keys,
|
||||
@@ -1609,6 +1745,12 @@ def publish(server_args, *, role: str, hf_config: Any = None) -> RuntimeContext:
|
||||
namespace-read enforcement (``record`` audits the reads instead).
|
||||
``hf_config`` is accepted for forward-compat and currently unused.
|
||||
|
||||
``ranks`` is who this process is, from the entry that spawned it. It is
|
||||
optional because most roles are not placed in the topology at all -- a
|
||||
tokenizer has no ``tp_rank`` -- and those processes raise on a rank read
|
||||
exactly as they do today, with a message naming the missing bundle rather
|
||||
than an absent process group.
|
||||
|
||||
A process holds at most one live config: the bags always describe the
|
||||
engine running now. Re-publish is allowed and is **last-publish-wins**
|
||||
(bags re-projected, provenance reset, role overwritten), which is what
|
||||
@@ -1634,6 +1776,36 @@ def publish(server_args, *, role: str, hf_config: Any = None) -> RuntimeContext:
|
||||
),
|
||||
)
|
||||
_CONTEXT._publish_role = role
|
||||
if ranks is not None:
|
||||
# The placement, worked out here rather than carried: the widths are on
|
||||
# the bag a moment ago, and `world_rank` fixes the rest. A read of any
|
||||
# of these then needs no process group, which is the point -- they are
|
||||
# read long before one exists. The scoped overrides that swap a group
|
||||
# for a draft worker sit above the record in the read chain, so a
|
||||
# scope still wins.
|
||||
parallel = _CONTEXT.parallel
|
||||
placement = derive_spawn_ranks(
|
||||
world_rank=ranks.world_rank,
|
||||
tp_size=parallel.tp_size,
|
||||
ep_join_rank_offset=parallel.ep_join_rank_offset,
|
||||
attn_cp_size=parallel.attn_cp_size,
|
||||
attn_tp_size=parallel.attn_tp_size,
|
||||
moe_dp_size=parallel.moe_dp_size,
|
||||
moe_ep_size=parallel.moe_ep_size,
|
||||
)
|
||||
# `moe_dp_rank` is a different quantity when the MoE-DP group is
|
||||
# aliased to the attention-CP one: the group answers the CP index,
|
||||
# while this computes the MoE-DP index. Leave it to the group there, so
|
||||
# one name does not mean two things.
|
||||
if parallel.moe_dp_size < parallel.attn_cp_size:
|
||||
placement.pop("moe_dp_rank")
|
||||
# `dp_rank` is recorded whatever it is, None included: replicas are
|
||||
# separate WORLD groups, so no rank implies it and `None` is the answer
|
||||
# "no controller" rather than an absence.
|
||||
placement["dp_rank"] = ranks.dp_rank
|
||||
placement["launch_world_rank"] = ranks.world_rank
|
||||
parallel.override_permanently(**placement)
|
||||
_stamp_attention_ranks(parallel, placement["tp_rank"])
|
||||
if _ROLE_NS_MODE == "record":
|
||||
# The '-' marker distinguishes a zero-read role from a process where
|
||||
# recording never ran (signal teardown skips atexit).
|
||||
@@ -1648,6 +1820,27 @@ def publish(server_args, *, role: str, hf_config: Any = None) -> RuntimeContext:
|
||||
return _CONTEXT
|
||||
|
||||
|
||||
def _stamp_attention_ranks(parallel, tp_rank: int) -> None:
|
||||
"""Place this process in the attention topology, from the configuration.
|
||||
|
||||
The widths are already on the bag -- `publish` computed them a moment ago --
|
||||
and the rank comes from the spawn, so the position is known here, before any
|
||||
process group exists. That is the point: a rank read then works in a process
|
||||
that never initialises distributed, which is what `ParallelState` provided
|
||||
by being a plain frozen record.
|
||||
|
||||
It is a stamp rather than a bag leaf because it is a per-process fact, and
|
||||
nothing about the configuration distinguishes one rank from another.
|
||||
"""
|
||||
attn_tp_rank, attn_dp_rank = derive_attention_ranks(
|
||||
tp_rank=tp_rank,
|
||||
attn_tp_size=parallel.attn_tp_size,
|
||||
attn_cp_size=parallel.attn_cp_size,
|
||||
enable_dp_attention=parallel.enable_dp_attention,
|
||||
)
|
||||
parallel.override_permanently(attn_tp_rank=attn_tp_rank, attn_dp_rank=attn_dp_rank)
|
||||
|
||||
|
||||
def assert_published(server_args, *, role: str) -> RuntimeContext:
|
||||
"""This record, under this role, is already published -- or fail loud.
|
||||
|
||||
|
||||
@@ -124,7 +124,7 @@ class RustServer:
|
||||
# The joining TP group is entirely local to this node.
|
||||
tp_size_per_node = scheduler.ps.tp_size
|
||||
else:
|
||||
nnodes_per_pp_rank = max(get_parallel().nnodes // scheduler.ps.pp_size, 1)
|
||||
nnodes_per_pp_rank = max(get_parallel().nnodes // get_parallel().pp_size, 1)
|
||||
tp_size_per_node = scheduler.ps.tp_size // nnodes_per_pp_rank
|
||||
dp_group_width = scheduler.ps.attn_tp_size * scheduler.ps.attn_cp_size
|
||||
# Count DP leaders within this node's TP range. The first leader must
|
||||
|
||||
@@ -84,6 +84,7 @@ from sglang.srt.speculative.spec_utils import (
|
||||
GrammarTree,
|
||||
assign_req_to_token_pool_func,
|
||||
build_grammar_vocab_mask,
|
||||
draft_pp_context,
|
||||
draft_tp_context,
|
||||
)
|
||||
from sglang.srt.utils import is_cuda, is_hip, is_npu, is_xpu
|
||||
@@ -402,7 +403,7 @@ class DFlashWorkerV2(BaseSpecWorker):
|
||||
draft_init_ctx = draft_tp_context(get_parallel().attn_tp_group)
|
||||
else:
|
||||
draft_init_ctx = empty_context()
|
||||
with draft_init_ctx:
|
||||
with draft_pp_context(), draft_init_ctx:
|
||||
bundle = build_draft_tp_worker(
|
||||
server_args=server_args,
|
||||
gpu_id=gpu_id,
|
||||
@@ -598,7 +599,10 @@ class DFlashWorkerV2(BaseSpecWorker):
|
||||
)
|
||||
|
||||
def init_attention_backends(self):
|
||||
with self.draft_tp_context(self.draft_model_runner.tp_group):
|
||||
with (
|
||||
draft_pp_context(),
|
||||
self.draft_tp_context(self.draft_model_runner.tp_group),
|
||||
):
|
||||
self._draft_worker.init_attention_backends()
|
||||
self._need_mamba_verify_commit = mambaish_config(
|
||||
self.model_runner.model_config
|
||||
@@ -608,7 +612,10 @@ class DFlashWorkerV2(BaseSpecWorker):
|
||||
)
|
||||
|
||||
def init_cuda_graphs(self):
|
||||
with self.draft_tp_context(self.draft_model_runner.tp_group):
|
||||
with (
|
||||
draft_pp_context(),
|
||||
self.draft_tp_context(self.draft_model_runner.tp_group),
|
||||
):
|
||||
capture_decode_cuda_graph = (
|
||||
get_exec().graph.cuda_graph_config.decode.backend != Backend.DISABLED
|
||||
)
|
||||
|
||||
@@ -74,6 +74,7 @@ from sglang.srt.speculative.spec_tp_sync import SpecTpSync, SpecTpSyncSite
|
||||
from sglang.srt.speculative.spec_utils import (
|
||||
GrammarTree,
|
||||
build_grammar_vocab_mask,
|
||||
draft_pp_context,
|
||||
draft_tp_context,
|
||||
prepare_mamba_track_for_verify,
|
||||
)
|
||||
@@ -165,7 +166,7 @@ class DSparkWorkerV2(BaseSpecWorker):
|
||||
"MoE-under-DP all-reduce."
|
||||
)
|
||||
|
||||
with self._draft_context():
|
||||
with draft_pp_context(), self._draft_context():
|
||||
bundle = build_draft_tp_worker(
|
||||
server_args=server_args,
|
||||
gpu_id=gpu_id,
|
||||
@@ -428,7 +429,7 @@ class DSparkWorkerV2(BaseSpecWorker):
|
||||
)
|
||||
|
||||
def init_attention_backends(self):
|
||||
with self._draft_context():
|
||||
with draft_pp_context(), self._draft_context():
|
||||
self._draft_worker.init_attention_backends()
|
||||
self._target_hidden_projection_enabled = _configure_target_hidden_projection(
|
||||
target_model=self.target_worker.model_runner.model,
|
||||
@@ -463,7 +464,7 @@ class DSparkWorkerV2(BaseSpecWorker):
|
||||
"memory is available after target backend initialization.",
|
||||
available_mem,
|
||||
)
|
||||
with self._draft_context():
|
||||
with draft_pp_context(), self._draft_context():
|
||||
if capture_decode_cuda_graph:
|
||||
# Keep the draft model graph enabled when folded proposal is
|
||||
# disabled, but do not capture the proposal head as a tail
|
||||
|
||||
@@ -338,6 +338,7 @@ class EagleDraftWorker(EagleDraftWorkerBase):
|
||||
|
||||
def init_attention_backends(self):
|
||||
with (
|
||||
draft_pp_context(),
|
||||
self.draft_tp_context(self.draft_runner.tp_group),
|
||||
speculative_moe_backend_context(),
|
||||
speculative_moe_a2a_backend_context(),
|
||||
@@ -347,6 +348,7 @@ class EagleDraftWorker(EagleDraftWorkerBase):
|
||||
|
||||
def init_cuda_graphs(self):
|
||||
with (
|
||||
draft_pp_context(),
|
||||
self.draft_tp_context(self.draft_runner.tp_group),
|
||||
speculative_moe_backend_context(),
|
||||
speculative_moe_a2a_backend_context(),
|
||||
|
||||
@@ -73,6 +73,7 @@ from sglang.srt.speculative.frozen_kv_mtp_utils import (
|
||||
)
|
||||
from sglang.srt.speculative.spec_info import SpeculativeAlgorithm
|
||||
from sglang.srt.speculative.spec_utils import (
|
||||
draft_pp_context,
|
||||
draft_tp_context,
|
||||
fast_topk,
|
||||
get_plan_stream,
|
||||
@@ -134,7 +135,7 @@ class FrozenKVMTPDraftWorker(EagleDraftWorkerBase, TpModelWorker):
|
||||
self.hot_token_id = None
|
||||
|
||||
with (
|
||||
empty_context(),
|
||||
draft_pp_context(),
|
||||
speculative_moe_backend_context(),
|
||||
speculative_moe_a2a_backend_context(),
|
||||
draft_model_build_scope(),
|
||||
@@ -204,6 +205,7 @@ class FrozenKVMTPDraftWorker(EagleDraftWorkerBase, TpModelWorker):
|
||||
|
||||
def init_attention_backends(self):
|
||||
with (
|
||||
draft_pp_context(),
|
||||
self.draft_tp_context(self.draft_model_runner.tp_group),
|
||||
speculative_moe_backend_context(),
|
||||
speculative_moe_a2a_backend_context(),
|
||||
@@ -214,6 +216,7 @@ class FrozenKVMTPDraftWorker(EagleDraftWorkerBase, TpModelWorker):
|
||||
|
||||
def init_cuda_graphs(self):
|
||||
with (
|
||||
draft_pp_context(),
|
||||
self.draft_tp_context(self.draft_model_runner.tp_group),
|
||||
speculative_moe_backend_context(),
|
||||
speculative_moe_a2a_backend_context(),
|
||||
|
||||
@@ -82,6 +82,7 @@ from sglang.srt.speculative.multi_layer_eagle_utils import (
|
||||
)
|
||||
from sglang.srt.speculative.spec_info import SpeculativeAlgorithm
|
||||
from sglang.srt.speculative.spec_utils import (
|
||||
draft_pp_context,
|
||||
draft_tp_context,
|
||||
get_plan_stream,
|
||||
sample_draft_proposal,
|
||||
@@ -163,7 +164,7 @@ class MultiLayerEagleDraftWorker(EagleDraftWorkerBase):
|
||||
|
||||
# Load draft model weights only.
|
||||
with (
|
||||
empty_context(),
|
||||
draft_pp_context(),
|
||||
speculative_moe_backend_context(),
|
||||
draft_model_build_scope(),
|
||||
):
|
||||
@@ -221,6 +222,7 @@ class MultiLayerEagleDraftWorker(EagleDraftWorkerBase):
|
||||
|
||||
def init_attention_backends(self):
|
||||
with (
|
||||
draft_pp_context(),
|
||||
self.draft_tp_context(self.draft_runner_list[0].tp_group),
|
||||
speculative_moe_backend_context(),
|
||||
):
|
||||
@@ -228,6 +230,7 @@ class MultiLayerEagleDraftWorker(EagleDraftWorkerBase):
|
||||
|
||||
def init_cuda_graphs(self):
|
||||
with (
|
||||
draft_pp_context(),
|
||||
self.draft_tp_context(self.draft_runner_list[0].tp_group),
|
||||
speculative_moe_backend_context(),
|
||||
):
|
||||
|
||||
@@ -27,7 +27,11 @@ from sglang.srt.speculative.base_spec_worker import (
|
||||
from sglang.srt.speculative.eagle_utils import default_tree_mask_mode
|
||||
from sglang.srt.speculative.eagle_worker_v2 import EagleDraftWorker, EAGLEWorkerV2
|
||||
from sglang.srt.speculative.spec_info import SpeculativeAlgorithm
|
||||
from sglang.srt.speculative.spec_utils import draft_tp_context, get_plan_stream
|
||||
from sglang.srt.speculative.spec_utils import (
|
||||
draft_pp_context,
|
||||
draft_tp_context,
|
||||
get_plan_stream,
|
||||
)
|
||||
from sglang.srt.utils import empty_context, get_bool_env_var
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -76,7 +80,7 @@ class StandaloneDraftWorker(EagleDraftWorker):
|
||||
# whose MoE gates run during construction; the scope routes their
|
||||
# fusion decision to the speculative leaf (it does not swap
|
||||
# runner_backend — the draft's forwards run outside that context).
|
||||
with empty_context(), draft_model_build_scope():
|
||||
with draft_pp_context(), draft_model_build_scope():
|
||||
self.draft_worker = TpModelWorker(
|
||||
server_args=server_args,
|
||||
gpu_id=gpu_id,
|
||||
|
||||
@@ -18,7 +18,7 @@ from sglang.srt.model_executor.step_span_utils import (
|
||||
set_detailed_annotations_enabled,
|
||||
)
|
||||
from sglang.srt.platforms import current_platform
|
||||
from sglang.srt.runtime_context import get_device
|
||||
from sglang.srt.runtime_context import get_device, get_parallel
|
||||
from sglang.srt.utils import is_npu
|
||||
from sglang.srt.utils.torch_npu_patch_utils import apply_torch_npu_patches
|
||||
|
||||
@@ -358,15 +358,15 @@ class _ProfilerTorch(_ProfilerConcreteBase):
|
||||
self.torch_profiler.stop()
|
||||
if not _is_npu:
|
||||
# Build filename with only non-zero ranks to maintain backward compatibility
|
||||
filename_parts = [self.profile_id, f"TP-{self.ps.tp_rank}"]
|
||||
filename_parts = [self.profile_id, f"TP-{get_parallel().tp_rank}"]
|
||||
|
||||
# Only add other ranks if parallelism is enabled (size > 1)
|
||||
if self.ps.dp_size > 1:
|
||||
filename_parts.append(f"DP-{self.ps.dp_rank}")
|
||||
if self.ps.pp_size > 1:
|
||||
filename_parts.append(f"PP-{self.ps.pp_rank}")
|
||||
if self.ps.moe_ep_size > 1:
|
||||
filename_parts.append(f"EP-{self.ps.moe_ep_rank}")
|
||||
filename_parts.append(f"DP-{get_parallel().dp_rank}")
|
||||
if get_parallel().pp_size > 1:
|
||||
filename_parts.append(f"PP-{get_parallel().pp_rank}")
|
||||
if get_parallel().moe_ep_size > 1:
|
||||
filename_parts.append(f"EP-{get_parallel().moe_ep_rank}")
|
||||
|
||||
filename = (
|
||||
(self.output_prefix + "-" if self.output_prefix else "")
|
||||
@@ -396,7 +396,7 @@ class _ProfilerMemory(_ProfilerConcreteBase):
|
||||
self.output_dir,
|
||||
(self.output_prefix + "-" if self.output_prefix else "")
|
||||
+ str(time.time())
|
||||
+ f"-TP-{self.ps.tp_rank}-memory"
|
||||
+ f"-TP-{get_parallel().tp_rank}-memory"
|
||||
+ self.output_suffix
|
||||
+ ".pickle",
|
||||
)
|
||||
@@ -426,10 +426,13 @@ class _ProfilerRPD(_ProfilerConcreteBase):
|
||||
|
||||
self.rpd_profile_path = os.path.join(
|
||||
self.output_dir,
|
||||
"rpd-" + str(time.time()) + f"-TP-{self.ps.tp_rank}" + ".trace.json.gz",
|
||||
"rpd-"
|
||||
+ str(time.time())
|
||||
+ f"-TP-{get_parallel().tp_rank}"
|
||||
+ ".trace.json.gz",
|
||||
)
|
||||
|
||||
if self.ps.tp_rank == 0:
|
||||
if get_parallel().tp_rank == 0:
|
||||
import sqlite3
|
||||
|
||||
from rocpd.schema import RocpdSchema
|
||||
@@ -454,7 +457,7 @@ class _ProfilerRPD(_ProfilerConcreteBase):
|
||||
self.rpd_profiler.flush()
|
||||
|
||||
torch.distributed.barrier(self.cpu_group)
|
||||
if self.ps.tp_rank == 0:
|
||||
if get_parallel().tp_rank == 0:
|
||||
from sglang.srt.utils.rpd_utils import rpd_to_chrome_trace
|
||||
|
||||
rpd_to_chrome_trace("trace.rpd", self.rpd_profile_path)
|
||||
|
||||
@@ -51,9 +51,11 @@ from sglang.srt.arg_groups.overrides import resolving_view
|
||||
from sglang.srt.configs.load_config import LoadConfig
|
||||
from sglang.srt.platforms import current_platform
|
||||
from sglang.srt.runtime_context import (
|
||||
SpawnRanks,
|
||||
get_exec,
|
||||
get_parallel,
|
||||
publish,
|
||||
spawn_world_rank,
|
||||
)
|
||||
|
||||
from .protocol import (
|
||||
@@ -281,7 +283,17 @@ class WeightCacheDaemon:
|
||||
from sglang.srt.model_loader.loader import get_model_loader
|
||||
|
||||
server_args = self.server_args
|
||||
publish(server_args, role="weight_cache_daemon")
|
||||
# The launcher told this daemon where it sits, and it builds the same
|
||||
# groups a scheduler does, so the same one number places it.
|
||||
publish(
|
||||
server_args,
|
||||
role="weight_cache_daemon",
|
||||
ranks=SpawnRanks(
|
||||
world_rank=spawn_world_rank(
|
||||
server_args, tp_rank=self.tp_rank, pp_rank=self.pp_rank
|
||||
)
|
||||
),
|
||||
)
|
||||
|
||||
from sglang.srt.layers.moe import initialize_moe_config
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import argparse
|
||||
import asyncio
|
||||
import contextlib
|
||||
import copy
|
||||
import doctest
|
||||
import importlib.util
|
||||
@@ -2002,6 +2003,36 @@ def maybe_stub_sgl_kernel():
|
||||
sys.meta_path.insert(0, _SglKernelFinder())
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def published_topology(role: str = "test", *, ranks=None, **server_args_fields):
|
||||
"""Publish a record describing the parallel topology a test wants.
|
||||
|
||||
Replaces standing a per-process parallel record into the object under
|
||||
test. The widths arrive the way production gets them -- from published
|
||||
configuration -- and the per-process ranks the way a spawned process gets
|
||||
them, so a rank read is answered without building a process group. Stating
|
||||
the topology through the same door production uses also keeps the derived
|
||||
widths honest: a hand-built double can claim an `attn_tp_size` the
|
||||
configuration would never produce.
|
||||
|
||||
`ranks` overrides the spawn identities; by default this process is rank
|
||||
zero of the world, which fixes every other rank. The context is reset on exit, including when the
|
||||
test fails.
|
||||
"""
|
||||
from sglang.srt.runtime_context import SpawnRanks, publish, reset_context
|
||||
from sglang.srt.server_args import ServerArgs
|
||||
|
||||
bundle = dict(world_rank=0, dp_rank=None)
|
||||
bundle.update(ranks or {})
|
||||
server_args = ServerArgs(model_path="dummy", **server_args_fields)
|
||||
reset_context()
|
||||
publish(server_args, role=role, ranks=SpawnRanks(**bundle))
|
||||
try:
|
||||
yield server_args
|
||||
finally:
|
||||
reset_context()
|
||||
|
||||
|
||||
_GPU_IDLE_TIMEOUT_SECS = 30.0
|
||||
_GPU_IDLE_POLL_INTERVAL_SECS = 2.0
|
||||
_GPU_IDLE_USED_MEMORY_THRESHOLD = 2 << 30 # 2 GiB
|
||||
@@ -2253,6 +2284,17 @@ def enter_override(test_case, override):
|
||||
return installed
|
||||
|
||||
|
||||
def enter_scope(test_case, scope):
|
||||
"""Enter a context manager for the length of one test.
|
||||
|
||||
The `with`-statement form of `enter_override` above, and 3.10-safe for the
|
||||
same reason: `enterContext` arrived in 3.11.
|
||||
"""
|
||||
entered = scope.__enter__()
|
||||
test_case.addCleanup(scope.__exit__, None, None, None)
|
||||
return entered
|
||||
|
||||
|
||||
class CustomTestCase(unittest.TestCase):
|
||||
def __init_subclass__(cls, **kwargs):
|
||||
super().__init_subclass__(**kwargs)
|
||||
|
||||
Reference in New Issue
Block a user