chore: import upstream snapshot with attribution
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

This commit is contained in:
wehub-resource-sync
2026-07-13 12:38:16 +08:00
commit 94057c3d3e
7152 changed files with 2120455 additions and 0 deletions
@@ -0,0 +1,51 @@
from sglang.srt.debug_utils.schedule_simulator.data_source import (
generate_gsp_requests,
generate_random_requests,
load_from_request_logger,
)
from sglang.srt.debug_utils.schedule_simulator.entrypoint import create_arg_parser, main
from sglang.srt.debug_utils.schedule_simulator.gpu_state import GPUState, StepRecord
from sglang.srt.debug_utils.schedule_simulator.metrics import (
AttentionComputeBalancednessRecorder,
AvgBatchSizeRecorder,
BatchSizeBalancednessRecorder,
MetricRecorder,
)
from sglang.srt.debug_utils.schedule_simulator.request import SimRequest
from sglang.srt.debug_utils.schedule_simulator.routers import (
RandomRouter,
RoundRobinRouter,
RouterPolicy,
StickyRouter,
)
from sglang.srt.debug_utils.schedule_simulator.schedulers import (
FIFOScheduler,
SchedulerPolicy,
)
from sglang.srt.debug_utils.schedule_simulator.simulator import (
SimulationResult,
Simulator,
)
__all__ = [
"SimRequest",
"GPUState",
"Simulator",
"SimulationResult",
"StepRecord",
"RouterPolicy",
"RandomRouter",
"RoundRobinRouter",
"StickyRouter",
"SchedulerPolicy",
"FIFOScheduler",
"MetricRecorder",
"BatchSizeBalancednessRecorder",
"AttentionComputeBalancednessRecorder",
"AvgBatchSizeRecorder",
"load_from_request_logger",
"generate_random_requests",
"generate_gsp_requests",
"create_arg_parser",
"main",
]
@@ -0,0 +1,6 @@
from sglang.srt.debug_utils.schedule_simulator.entrypoint import create_arg_parser, main
if __name__ == "__main__":
parser = create_arg_parser()
args = parser.parse_args()
main(args)
@@ -0,0 +1,13 @@
from sglang.srt.debug_utils.schedule_simulator.data_source.data_loader import (
load_from_request_logger,
)
from sglang.srt.debug_utils.schedule_simulator.data_source.data_synthesis import (
generate_gsp_requests,
generate_random_requests,
)
__all__ = [
"load_from_request_logger",
"generate_random_requests",
"generate_gsp_requests",
]
@@ -0,0 +1,34 @@
import json
from pathlib import Path
from typing import List, Union
from sglang.srt.debug_utils.schedule_simulator.request import SimRequest
def load_from_request_logger(file_path: Union[str, Path]) -> List[SimRequest]:
requests = []
file_path = Path(file_path)
with file_path.open(encoding="utf-8") as f:
for line_num, line in enumerate(f):
line = line.strip()
if not line or not line.startswith("{"):
continue
data = json.loads(line)
if data.get("event") != "request.finished":
continue
rid = data.get("rid", f"req_{line_num}")
meta_info = data["out"]["meta_info"]
requests.append(
SimRequest(
request_id=rid,
input_len=meta_info["prompt_tokens"],
output_len=meta_info["completion_tokens"],
)
)
return requests
@@ -0,0 +1,79 @@
import random
from typing import List, Optional
from sglang.srt.debug_utils.schedule_simulator.request import SimRequest
def generate_random_requests(
num_requests: int,
input_len: int,
output_len: int,
range_ratio: float = 1.0,
seed: Optional[int] = None,
) -> List[SimRequest]:
if seed is not None:
random.seed(seed)
requests = []
for i in range(num_requests):
isl = _random_len(input_len, range_ratio)
osl = _random_len(output_len, range_ratio)
requests.append(
SimRequest(
request_id=f"syn{i}",
input_len=isl,
output_len=osl,
)
)
print(
f"Generated {len(requests)} random requests "
f"(input_len={input_len}, output_len={output_len}, range_ratio={range_ratio})"
)
return requests
def generate_gsp_requests(
num_groups: int,
prompts_per_group: int,
system_prompt_len: int,
question_len: int,
output_len: int,
range_ratio: float = 1.0,
seed: Optional[int] = None,
) -> List[SimRequest]:
if seed is not None:
random.seed(seed)
requests = []
idx = 0
for group_idx in range(num_groups):
group_id = f"g{group_idx}"
prefix_len = _random_len(system_prompt_len, range_ratio)
for _ in range(prompts_per_group):
q_len = _random_len(question_len, range_ratio)
osl = _random_len(output_len, range_ratio)
requests.append(
SimRequest(
request_id=f"gsp{idx}",
input_len=prefix_len + q_len,
output_len=osl,
group_id=group_id,
prefix_len=prefix_len,
)
)
idx += 1
random.shuffle(requests)
print(
f"Generated {len(requests)} GSP requests "
f"({num_groups} groups x {prompts_per_group} prompts, "
f"system_prompt_len={system_prompt_len}, question_len={question_len}, "
f"output_len={output_len})"
)
return requests
def _random_len(full_len: int, range_ratio: float) -> int:
min_len = max(int(full_len * range_ratio), 1)
return random.randint(min_len, full_len)
@@ -0,0 +1,168 @@
import argparse
import json
import random
from typing import List
from sglang.srt.debug_utils.schedule_simulator.data_source.data_loader import (
load_from_request_logger,
)
from sglang.srt.debug_utils.schedule_simulator.data_source.data_synthesis import (
generate_gsp_requests,
generate_random_requests,
)
from sglang.srt.debug_utils.schedule_simulator.metrics import (
AttentionComputeBalancednessRecorder,
AvgBatchSizeRecorder,
BatchSizeBalancednessRecorder,
)
from sglang.srt.debug_utils.schedule_simulator.request import SimRequest
from sglang.srt.debug_utils.schedule_simulator.routers import (
RandomRouter,
RoundRobinRouter,
StickyRouter,
)
from sglang.srt.debug_utils.schedule_simulator.schedulers import FIFOScheduler
from sglang.srt.debug_utils.schedule_simulator.simulator import (
SimulationResult,
Simulator,
)
def create_arg_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(
description="Schedule Simulator for analyzing request scheduling across GPUs"
)
data_group = parser.add_mutually_exclusive_group(required=True)
data_group.add_argument(
"--input", type=str, help="Path to request_logger JSON file"
)
data_group.add_argument(
"--synthetic", action="store_true", help="Use synthetic data generation"
)
data_group.add_argument(
"--synth-gsp",
action="store_true",
help="Use generated-shared-prefix (GSP) data generation",
)
# Shared synthetic arguments
parser.add_argument("--synth-seed", type=int, default=None)
# Random dataset arguments (aligned with bench_serving.py --random-* options)
parser.add_argument("--synth-random-num-requests", type=int, default=1000)
parser.add_argument("--synth-random-input-len", type=int, default=1024)
parser.add_argument("--synth-random-output-len", type=int, default=256)
parser.add_argument("--synth-random-range-ratio", type=float, default=0.0)
# GSP dataset arguments (aligned with bench_serving.py --gsp-* options)
parser.add_argument("--synth-gsp-num-groups", type=int, default=64)
parser.add_argument("--synth-gsp-prompts-per-group", type=int, default=16)
parser.add_argument("--synth-gsp-system-prompt-len", type=int, default=2048)
parser.add_argument("--synth-gsp-question-len", type=int, default=128)
parser.add_argument("--synth-gsp-output-len", type=int, default=256)
parser.add_argument("--synth-gsp-range-ratio", type=float, default=1.0)
parser.add_argument("--num-gpus-per-engine", type=int, default=8)
parser.add_argument("--num-engines", type=int, default=1)
parser.add_argument(
"--router",
type=str,
choices=["random", "round_robin", "sticky"],
default="round_robin",
)
parser.add_argument("--scheduler", type=str, choices=["fifo"], default="fifo")
parser.add_argument("--max-total-tokens", type=int, default=100000)
parser.add_argument(
"--stop-criteria",
type=str,
choices=["all_done", "exist_no_pending"],
default="all_done",
help="all_done: run until all requests complete; exist_no_pending: stop when any GPU has no pending requests",
)
parser.add_argument("--max-steps", type=int, default=None)
parser.add_argument("--output", type=str, default=None)
parser.add_argument("--log-level", type=int, choices=[0, 1, 2], default=0)
return parser
def _load_requests(args: argparse.Namespace) -> List[SimRequest]:
if args.input:
requests = load_from_request_logger(args.input)
print(f"Loaded {len(requests)} requests from {args.input}")
elif args.synth_gsp:
requests = generate_gsp_requests(
num_groups=args.synth_gsp_num_groups,
prompts_per_group=args.synth_gsp_prompts_per_group,
system_prompt_len=args.synth_gsp_system_prompt_len,
question_len=args.synth_gsp_question_len,
output_len=args.synth_gsp_output_len,
range_ratio=args.synth_gsp_range_ratio,
seed=args.synth_seed,
)
else:
requests = generate_random_requests(
num_requests=args.synth_random_num_requests,
input_len=args.synth_random_input_len,
output_len=args.synth_random_output_len,
range_ratio=args.synth_random_range_ratio,
seed=args.synth_seed,
)
return requests
def _create_router(name: str, total_gpus: int):
if name == "random":
return RandomRouter(total_gpus)
if name == "round_robin":
return RoundRobinRouter(total_gpus)
if name == "sticky":
return StickyRouter(total_gpus)
raise ValueError(f"Unknown router: {name}")
def _create_scheduler(name: str):
if name == "fifo":
return FIFOScheduler()
raise ValueError(f"Unknown scheduler: {name}")
def main(args: argparse.Namespace) -> SimulationResult:
if args.synth_seed is not None:
random.seed(args.synth_seed)
requests = _load_requests(args)
total_gpus = args.num_gpus_per_engine * args.num_engines
router = _create_router(args.router, total_gpus)
scheduler = _create_scheduler(args.scheduler)
sim = Simulator(
num_gpus_per_engine=args.num_gpus_per_engine,
router=router,
scheduler=scheduler,
recorders=[
BatchSizeBalancednessRecorder(),
AttentionComputeBalancednessRecorder(),
AvgBatchSizeRecorder(),
],
log_level=args.log_level,
max_total_tokens=args.max_total_tokens,
stop_criteria=args.stop_criteria,
max_steps=args.max_steps,
)
print(
f"Running simulation with {args.num_gpus_per_engine} GPUs/engine x {args.num_engines} engines, router={args.router}, scheduler={args.scheduler}"
)
result = sim.run(requests)
print("\n=== Summary ===")
for key, value in result.summary.items():
print(f"{key}: {value:.4f}" if isinstance(value, float) else f"{key}: {value}")
if args.output:
with open(args.output, "w") as f:
json.dump(result.summary, f, indent=2)
print(f"\nSummary saved to {args.output}")
return result
@@ -0,0 +1,70 @@
from dataclasses import dataclass, field
from typing import List, Optional
from sglang.srt.debug_utils.schedule_simulator.request import SimRequest
@dataclass
class StepRecord:
step: int
gpu_id: int
running_count: int
pending_count: int
total_seq_len: int
running_req_ids: List[str] = field(default_factory=list)
pending_req_ids: List[str] = field(default_factory=list)
@dataclass
class GPUState:
gpu_id: int
max_total_tokens: int
pending_requests: List[SimRequest] = field(default_factory=list)
running_requests: List[SimRequest] = field(default_factory=list)
def batch_size(self) -> int:
return len(self.running_requests)
def total_attention_compute(self) -> int:
return sum(req.seq_len() for req in self.running_requests)
def total_seq_len(self, extra_reqs: Optional[List[SimRequest]] = None) -> int:
seen_groups = set()
total = 0
for req in self.running_requests + (extra_reqs or []):
is_shared = req.group_id is not None and req.group_id in seen_groups
total += req.seq_len() - (req.prefix_len if is_shared else 0)
if req.group_id is not None:
seen_groups.add(req.group_id)
return total
def is_valid(self) -> bool:
return self.total_seq_len() <= self.max_total_tokens
def start_request(self, req: SimRequest) -> None:
assert req in self.pending_requests
self.pending_requests.remove(req)
self.running_requests.append(req)
def evict_request(self, req: SimRequest) -> None:
assert req in self.running_requests
self.running_requests.remove(req)
self.pending_requests.insert(0, req)
def execute_step(self) -> None:
for req in self.running_requests:
req.decoded_tokens += 1
self.running_requests = [
r for r in self.running_requests if not r.is_finished()
]
def get_step_record(self, step: int) -> StepRecord:
return StepRecord(
step=step,
gpu_id=self.gpu_id,
running_count=len(self.running_requests),
pending_count=len(self.pending_requests),
total_seq_len=self.total_seq_len(),
running_req_ids=[r.request_id for r in self.running_requests],
pending_req_ids=[r.request_id for r in self.pending_requests],
)
@@ -0,0 +1,60 @@
from abc import ABC, abstractmethod
from typing import Any, Callable, Dict, List
from sglang.srt.debug_utils.schedule_simulator.gpu_state import GPUState
class MetricRecorder(ABC):
@abstractmethod
def on_step_end(self, step: int, gpu_states: List[GPUState]) -> None: ...
@abstractmethod
def get_summary(self) -> Dict[str, Any]: ...
class BalancednessRecorder(MetricRecorder):
def __init__(self, name: str, value_fn: Callable[[GPUState], float]):
self._name = name
self._value_fn = value_fn
self._history: List[float] = []
def on_step_end(self, step: int, gpu_states: List[GPUState]) -> None:
values = [self._value_fn(gpu) for gpu in gpu_states]
max_val = max(values) if values else 0
mean_val = sum(values) / len(values) if values else 0
balancedness = mean_val / max_val if max_val > 0 else 1.0
self._history.append(balancedness)
def get_summary(self) -> Dict[str, Any]:
if not self._history:
return {f"{self._name}_mean": 0.0}
return {
f"{self._name}_mean": sum(self._history) / len(self._history),
f"{self._name}_min": min(self._history),
f"{self._name}_max": max(self._history),
}
def BatchSizeBalancednessRecorder() -> BalancednessRecorder:
return BalancednessRecorder("batch_size_balancedness", lambda gpu: gpu.batch_size())
def AttentionComputeBalancednessRecorder() -> BalancednessRecorder:
return BalancednessRecorder(
"attention_compute_balancedness", lambda gpu: gpu.total_attention_compute()
)
class AvgBatchSizeRecorder(MetricRecorder):
def __init__(self):
self._total_running = 0
self._num_records = 0
def on_step_end(self, step: int, gpu_states: List[GPUState]) -> None:
for gpu in gpu_states:
self._total_running += gpu.batch_size()
self._num_records += 1
def get_summary(self) -> Dict[str, Any]:
avg = self._total_running / self._num_records if self._num_records else 0.0
return {"avg_batch_size": avg}
@@ -0,0 +1,18 @@
from dataclasses import dataclass
from typing import Optional
@dataclass
class SimRequest:
request_id: str
input_len: int
output_len: int
decoded_tokens: int = 0
group_id: Optional[str] = None
prefix_len: int = 0
def seq_len(self) -> int:
return self.input_len + self.decoded_tokens
def is_finished(self) -> bool:
return self.decoded_tokens >= self.output_len
@@ -0,0 +1,8 @@
from sglang.srt.debug_utils.schedule_simulator.routers.base import RouterPolicy
from sglang.srt.debug_utils.schedule_simulator.routers.random_router import RandomRouter
from sglang.srt.debug_utils.schedule_simulator.routers.round_robin_router import (
RoundRobinRouter,
)
from sglang.srt.debug_utils.schedule_simulator.routers.sticky_router import StickyRouter
__all__ = ["RouterPolicy", "RandomRouter", "RoundRobinRouter", "StickyRouter"]
@@ -0,0 +1,8 @@
from abc import ABC, abstractmethod
from sglang.srt.debug_utils.schedule_simulator.request import SimRequest
class RouterPolicy(ABC):
@abstractmethod
def route(self, incoming_request: SimRequest) -> int: ...
@@ -0,0 +1,12 @@
import random
from sglang.srt.debug_utils.schedule_simulator.request import SimRequest
from sglang.srt.debug_utils.schedule_simulator.routers.base import RouterPolicy
class RandomRouter(RouterPolicy):
def __init__(self, num_gpus: int):
self._num_gpus = num_gpus
def route(self, incoming_request: SimRequest) -> int:
return random.randint(0, self._num_gpus - 1)
@@ -0,0 +1,13 @@
from sglang.srt.debug_utils.schedule_simulator.request import SimRequest
from sglang.srt.debug_utils.schedule_simulator.routers.base import RouterPolicy
class RoundRobinRouter(RouterPolicy):
def __init__(self, num_gpus: int):
self._num_gpus = num_gpus
self._counter = 0
def route(self, incoming_request: SimRequest) -> int:
gpu_id = self._counter % self._num_gpus
self._counter += 1
return gpu_id
@@ -0,0 +1,20 @@
import random
from collections import defaultdict
from sglang.srt.debug_utils.schedule_simulator.request import SimRequest
from sglang.srt.debug_utils.schedule_simulator.routers.base import RouterPolicy
class StickyRouter(RouterPolicy):
def __init__(self, num_gpus: int):
self._num_gpus = num_gpus
self._group_to_gpu = defaultdict(self._assign_gpu)
def _assign_gpu(self) -> int:
return random.randint(0, self._num_gpus - 1)
def route(self, incoming_request: SimRequest) -> int:
group_id = incoming_request.group_id
if group_id is None:
return random.randint(0, self._num_gpus - 1)
return self._group_to_gpu[group_id]
@@ -0,0 +1,6 @@
from sglang.srt.debug_utils.schedule_simulator.schedulers.base import SchedulerPolicy
from sglang.srt.debug_utils.schedule_simulator.schedulers.fifo_scheduler import (
FIFOScheduler,
)
__all__ = ["SchedulerPolicy", "FIFOScheduler"]
@@ -0,0 +1,10 @@
from abc import ABC, abstractmethod
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from sglang.srt.debug_utils.schedule_simulator.gpu_state import GPUState
class SchedulerPolicy(ABC):
@abstractmethod
def schedule(self, gpu_state: "GPUState") -> None: ...
@@ -0,0 +1,16 @@
from typing import TYPE_CHECKING
from sglang.srt.debug_utils.schedule_simulator.schedulers.base import SchedulerPolicy
if TYPE_CHECKING:
from sglang.srt.debug_utils.schedule_simulator.gpu_state import GPUState
class FIFOScheduler(SchedulerPolicy):
def schedule(self, gpu_state: "GPUState") -> None:
while not gpu_state.is_valid() and gpu_state.running_requests:
gpu_state.evict_request(gpu_state.running_requests[-1])
for req in list(gpu_state.pending_requests):
if gpu_state.total_seq_len(extra_reqs=[req]) <= gpu_state.max_total_tokens:
gpu_state.start_request(req)
@@ -0,0 +1,122 @@
from dataclasses import dataclass
from typing import Any, Dict, List, Optional
from sglang.srt.debug_utils.schedule_simulator.gpu_state import GPUState, StepRecord
from sglang.srt.debug_utils.schedule_simulator.metrics import MetricRecorder
from sglang.srt.debug_utils.schedule_simulator.request import SimRequest
from sglang.srt.debug_utils.schedule_simulator.routers.base import RouterPolicy
from sglang.srt.debug_utils.schedule_simulator.schedulers.base import SchedulerPolicy
@dataclass
class SimulationResult:
step_records: List[StepRecord]
summary: Dict[str, Any]
class Simulator:
def __init__(
self,
num_gpus_per_engine: int,
router: RouterPolicy,
scheduler: SchedulerPolicy,
recorders: Optional[List[MetricRecorder]] = None,
log_level: int = 0,
max_total_tokens: int = 100000,
stop_criteria: str = "all_done",
max_steps: Optional[int] = None,
):
self.num_gpus_per_engine = num_gpus_per_engine
self.router = router
self.scheduler = scheduler
self.recorders = recorders or []
self.log_level = log_level
self.max_total_tokens = max_total_tokens
self.stop_criteria = stop_criteria
self.max_steps = max_steps
self.gpu_states: List[GPUState] = []
self.step = 0
def run(self, requests: List[SimRequest]) -> SimulationResult:
self.gpu_states = [
GPUState(gpu_id=i, max_total_tokens=self.max_total_tokens)
for i in range(self.num_gpus_per_engine)
]
self.step = 0
step_records: List[StepRecord] = []
incoming_requests = list(requests)
while True:
self._route_requests(incoming_requests)
incoming_requests.clear()
self._schedule_all_gpus()
if self._should_stop():
break
self._execute_step()
step_records.extend(
gpu.get_step_record(self.step) for gpu in self.gpu_states
)
self._log_step()
self._record_metrics()
self.step += 1
return SimulationResult(step_records=step_records, summary=self._get_summary())
def _should_stop(self) -> bool:
if self.max_steps is not None and self.step >= self.max_steps:
return True
if self.stop_criteria == "exist_no_pending":
return any(not gpu.pending_requests for gpu in self.gpu_states)
if self.stop_criteria == "all_done":
return not any(
gpu.pending_requests or gpu.running_requests for gpu in self.gpu_states
)
raise ValueError(f"Unknown stop criteria: {self.stop_criteria}")
def _route_requests(self, incoming_requests: List[SimRequest]) -> None:
for req in incoming_requests:
gpu_id = self.router.route(req)
if gpu_id < self.num_gpus_per_engine:
self.gpu_states[gpu_id].pending_requests.append(req)
def _schedule_all_gpus(self) -> None:
for gpu in self.gpu_states:
self.scheduler.schedule(gpu)
assert gpu.is_valid(), (
f"GPU{gpu.gpu_id} invalid after scheduling "
f"({gpu.total_seq_len()=}, {gpu.max_total_tokens=})"
)
def _execute_step(self) -> None:
for gpu in self.gpu_states:
gpu.execute_step()
def _log_step(self) -> None:
if self.log_level == 0 and self.step % 100 != 0:
return
parts = [f"step={self.step:<4}"]
for gpu in self.gpu_states:
r, q = len(gpu.running_requests), len(gpu.pending_requests)
if self.log_level <= 1:
parts.append(f"GPU{gpu.gpu_id}[R={r:<3} Q={q:<3}]")
else:
run_ids = _format_ids(gpu.running_requests)
queue_ids = _format_ids(gpu.pending_requests)
parts.append(f"GPU{gpu.gpu_id}[R={r}:{run_ids} Q={q}:{queue_ids}]")
print(" | ".join(parts))
def _record_metrics(self) -> None:
for recorder in self.recorders:
recorder.on_step_end(self.step, self.gpu_states)
def _get_summary(self) -> Dict[str, Any]:
return {k: v for r in self.recorders for k, v in r.get_summary().items()}
def _format_ids(requests: List[SimRequest], limit: int = 5) -> str:
if not requests:
return "-"
ids = ",".join(r.request_id for r in requests[:limit])
if len(requests) > limit:
ids += f"...+{len(requests) - limit}"
return ids