项目文件夹

文件
wehub-resource-sync 94057c3d3e
PR Test (NPU) / check-changes (push) Has been cancelled
PR Test (NPU) / pr-gate (push) Has been cancelled
PR Test (NPU) / set-image-config (push) Has been cancelled
PR Test (NPU) / stage-b-test-1-npu-a2 (0) (push) Has been cancelled
PR Test (NPU) / stage-b-test-1-npu-a2 (1) (push) Has been cancelled
PR Test (NPU) / stage-b-test-2-npu-a2 (0) (push) Has been cancelled
PR Test (NPU) / stage-b-test-2-npu-a2 (1) (push) Has been cancelled
PR Test (NPU) / stage-b-test-4-npu-a3 (push) Has been cancelled
PR Test (NPU) / stage-b-test-16-npu-a3 (push) Has been cancelled
PR Test (NPU) / multimodal-gen-test-1-npu-a3 (push) Has been cancelled
PR Test (NPU) / multimodal-gen-test-2-npu-a3 (push) Has been cancelled
PR Test (Arm64) / pr-gate (push) Has been cancelled
PR Test (Arm64) / check-changes (push) Has been cancelled
PR Test (Arm64) / build-test (push) Has been cancelled
PR Test (sgl-router) / gate (push) Has been cancelled
PR Test (sgl-router) / tier-1 — lint (push) Has been cancelled
PR Test (sgl-router) / tier-2 — build + test (push) Has been cancelled
PR Test (sgl-router) / tier-3 — docker (placeholder) (push) Has been cancelled
PR Test (sgl-router) / tier-3 — k8s integration (push) Has been cancelled
PR Test (sgl-router) / tier-3 — e2e (push) Has been cancelled
PR Test (sgl-router) / finish (push) Has been cancelled
PR Test (NPU) / single-node-poc (map[name:qwen3_6_27b_w8a8_1p_in64k_out1k_50ms runner:linux-aarch64-a3-2 test_case:test/registered/ascend/performance/qwen3_6_27b/test_npu_qwen3_6_27b_w8a8_1p_in64k_out1k_50ms.py test_type:perf]) (push) Has been cancelled
PR Test (NPU) / pr-test-npu-finish (push) Has been cancelled
PR Test (Xeon) / pr-gate (push) Has been cancelled
PR Test (Xeon) / check-changes (push) Has been cancelled
PR Test (Xeon) / build-test (, xeon-gnr, base-b-test-cpu) (push) Has been cancelled
PR Test (XPU) / check-changes (push) Has been cancelled
PR Test (XPU) / pr-gate (push) Has been cancelled
PR Test (XPU) / stage-a-test-1-gpu-xpu (push) Has been cancelled
PR Test (XPU) / wait-for-stage-a (push) Has been cancelled
PR Test (XPU) / stage-b-test-1-gpu-xpu (push) Has been cancelled
PR Test (XPU) / finish (push) Has been cancelled
CI Model Inventory / build-inventory (push) Has been cancelled
Lint / lint (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark Compilation Check (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark - Manual Policy (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark - Request Processing (push) Has been cancelled
PR Benchmark (SMG Components) / Benchmark Summary (push) Has been cancelled
PR Test (SMG) / build-wheel (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on windows (x86_64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on macos (x86_64 - auto) (push) Has been cancelled
PR Test (SMG) / python-unit-tests (push) Has been cancelled
PR Test (SMG) / unit-tests (push) Has been cancelled
PR Test (SMG) / benchmarks (push) Has been cancelled
PR Test (SMG) / chat-completions (push) Has been cancelled
PR Test (SMG) / chat-completions-4gpu (push) Has been cancelled
PR Test (SMG) / e2e (push) Has been cancelled
PR Test (SMG) / docker-build-test (push) Has been cancelled
PR Test (SMG) / k8s-integration (push) Has been cancelled
PR Test (SMG) / finish (push) Has been cancelled
PR Test (SMG) / summarize-benchmarks (push) Has been cancelled
Release SGLang Model Gateway Docker Image / publish (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on macos (aarch64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (aarch64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (x86_64 - auto) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (aarch64 - musllinux_1_1) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / build on linux (x86_64 - musllinux_1_1) (push) Has been cancelled
Release SGLang Model Gateway to PyPI / Build SDist (push) Has been cancelled
Release SGLang Model Gateway to PyPI / Upload to PyPI (push) Has been cancelled
Release SGLang Kernels / build-cu129-matrix (aarch64, 12.9, 3.10, arm-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / build-cu129-matrix (x86_64, 12.9, 3.10, x64-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / release-cu129 (push) Has been cancelled
Release SGLang Kernels / build-cu130-matrix (aarch64, 13.0, 3.10, arm-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / build-cu130-matrix (x86_64, 13.0, 3.10, x64-kernel-build-node) (push) Has been cancelled
Release SGLang Kernels / release-cu130 (push) Has been cancelled
Release SGLang Kernels / build-rocm-matrix (3.10, 700) (push) Has been cancelled
Release SGLang Kernels / build-rocm-matrix (3.10, 720) (push) Has been cancelled
Release SGLang Kernels / release-rocm700 (push) Has been cancelled
Release SGLang Kernels / release-rocm720 (push) Has been cancelled
Release SGLang Kernels / build-musa43 (43, 3.10) (push) Has been cancelled
Release SGLang Kernels / release-musa43 (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:38:16 +08:00

283 行
10 KiB
Python

"""DPBudget + DataParallelController dispatch tests.
`total_tokens` (the most complex algorithm) is exercised end-to-end in
test/registered/disaggregation/test_disaggregation_dp_attention.py; its
tie-break on `total_requests` transitively covers that state.
Fragility: scheduler tests bypass `DataParallelController.__init__` via
`__new__` and inject only the attrs the schedulers read (`workers`,
`status`, `round_robin_counter`, `dp_budget`). Update `_make_controller`
if a scheduler starts reading another attr. `maybe_external_dp_rank_routing`
is exercised as the real method, no mock.
"""
import unittest
from types import SimpleNamespace
from unittest.mock import MagicMock
import msgspec.structs
from sglang.test.ci.ci_register import register_cpu_ci
from sglang.test.test_utils import CustomTestCase, maybe_stub_sgl_kernel
maybe_stub_sgl_kernel()
from sglang.srt.managers.data_parallel_controller import (
DataParallelController,
DPBudget,
LoadBalanceMethod,
)
from sglang.srt.managers.load_snapshot import LoadSnapshot
register_cpu_ci(est_time=11, suite="base-a-test-cpu")
_BASE_LOAD = msgspec.structs.replace(
LoadSnapshot(dp_rank=0),
max_total_num_tokens=4096,
max_running_requests=128,
)
def _load(**overrides) -> LoadSnapshot:
return msgspec.structs.replace(_BASE_LOAD, **overrides)
def _make_controller(dp_size: int) -> DataParallelController:
"""Bypass __init__; inject only the attrs dispatch methods read."""
ctl = DataParallelController.__new__(DataParallelController)
ctl.workers = [MagicMock(name=f"worker_{i}") for i in range(dp_size)]
ctl.status = [True] * dp_size
ctl.round_robin_counter = 0
ctl.dp_budget = DPBudget(dp_size=dp_size)
return ctl
def _req(routed_dp_rank=None, bootstrap_room=None, input_ids=None):
"""Req stand-in; SimpleNamespace avoids pinning to the Req dataclass schema."""
return SimpleNamespace(
routed_dp_rank=routed_dp_rank,
bootstrap_room=bootstrap_room,
input_ids=input_ids or [],
)
class TestDPBudgetUpdateBudget(CustomTestCase):
def test_maps_running_plus_waiting_to_total_requests(self):
budget = DPBudget(dp_size=2)
budget.update_budget(
[
_load(dp_rank=0, timestamp=1.0, num_running_reqs=3, num_waiting_reqs=2),
_load(dp_rank=1, timestamp=1.0, num_running_reqs=5, num_waiting_reqs=1),
]
)
self.assertEqual(budget.total_requests, [5, 6])
def test_maps_num_total_tokens_not_num_used_tokens(self):
budget = DPBudget(dp_size=2)
budget.update_budget(
[
_load(
dp_rank=0, timestamp=1.0, num_used_tokens=100, num_total_tokens=150
),
_load(
dp_rank=1, timestamp=1.0, num_used_tokens=80, num_total_tokens=80
),
]
)
self.assertEqual(budget.total_tokens, [150, 80])
def test_partial_update_only_affects_reported_rank(self):
budget = DPBudget(dp_size=3)
budget.update_budget(
[
_load(
dp_rank=0, timestamp=1.0, num_running_reqs=10, num_total_tokens=100
),
_load(
dp_rank=1, timestamp=1.0, num_running_reqs=20, num_total_tokens=200
),
_load(
dp_rank=2, timestamp=1.0, num_running_reqs=30, num_total_tokens=300
),
]
)
budget.update_budget(
[
_load(
dp_rank=1,
timestamp=2.0,
num_running_reqs=1,
num_waiting_reqs=1,
num_total_tokens=50,
)
]
)
self.assertEqual(budget.total_requests, [10, 2, 30])
self.assertEqual(budget.total_tokens, [100, 50, 300])
class TestDPBudgetDispatch(CustomTestCase):
"""DPBudget.dispatch picks a rank from current state and updates counters."""
def test_total_requests_dispatch_picks_min_and_increments(self):
budget = DPBudget(dp_size=3)
budget.total_requests = [4, 2, 7]
rank = budget.dispatch(LoadBalanceMethod.TOTAL_REQUESTS)
self.assertEqual(rank, 1)
self.assertEqual(
budget.total_requests[1],
3,
"dispatch should increment chosen worker's request count",
)
def test_total_tokens_dispatch_applies_estimated_tokens(self):
budget = DPBudget(dp_size=3)
budget.total_tokens = [100, 50, 200]
budget.total_requests = [0, 0, 0]
rank = budget.dispatch(LoadBalanceMethod.TOTAL_TOKENS, estimated_tokens=30)
self.assertEqual(rank, 1, "should pick worker with min total_tokens")
self.assertEqual(
budget.total_tokens[1],
80,
"dispatch should add estimated_tokens to chosen worker",
)
self.assertEqual(
budget.total_requests[1],
1,
"dispatch should also increment request count",
)
def test_total_tokens_tie_breaks_on_total_requests(self):
budget = DPBudget(dp_size=3)
budget.total_tokens = [50, 50, 50]
budget.total_requests = [4, 2, 7]
rank = budget.dispatch(LoadBalanceMethod.TOTAL_TOKENS, estimated_tokens=10)
self.assertEqual(
rank, 1, "tie on total_tokens should fall back to min total_requests"
)
def test_dispatch_returns_none_for_methods_not_handled(self):
"""Round-robin and follow_bootstrap_room dispatch elsewhere; DPBudget
only handles the load-aware variants."""
budget = DPBudget(dp_size=3)
self.assertIsNone(budget.dispatch(LoadBalanceMethod.ROUND_ROBIN))
self.assertIsNone(budget.dispatch(LoadBalanceMethod.FOLLOW_BOOTSTRAP_ROOM))
class TestRoundRobinScheduler(CustomTestCase):
def test_cycles_through_active_workers_in_order(self):
ctl = _make_controller(dp_size=4)
for _ in range(8):
ctl.round_robin_scheduler(_req())
# 8 reqs across 4 active workers — 2 each, in round-robin order
for i, worker in enumerate(ctl.workers):
self.assertEqual(worker.send_pyobj.call_count, 2, f"worker {i} call count")
def test_first_dispatch_picks_worker_zero(self):
ctl = _make_controller(dp_size=4)
ctl.round_robin_scheduler(_req())
ctl.workers[0].send_pyobj.assert_called_once()
for i in (1, 2, 3):
ctl.workers[i].send_pyobj.assert_not_called()
def test_skips_inactive_workers(self):
ctl = _make_controller(dp_size=4)
ctl.status[1] = False
ctl.status[3] = False
for _ in range(6):
ctl.round_robin_scheduler(_req())
# Only workers 0 and 2 are active — should split 6 reqs evenly
self.assertEqual(ctl.workers[0].send_pyobj.call_count, 3)
ctl.workers[1].send_pyobj.assert_not_called()
self.assertEqual(ctl.workers[2].send_pyobj.call_count, 3)
ctl.workers[3].send_pyobj.assert_not_called()
def test_routed_dp_rank_bypasses_counter(self):
"""External dp-rank routing must not advance the counter."""
ctl = _make_controller(dp_size=4)
ctl.round_robin_scheduler(_req(routed_dp_rank=2))
ctl.workers[2].send_pyobj.assert_called_once()
self.assertEqual(
ctl.round_robin_counter,
0,
"external routing must not advance the round-robin counter",
)
# Subsequent round-robin req still lands on worker 0
ctl.round_robin_scheduler(_req())
ctl.workers[0].send_pyobj.assert_called_once()
class TestFollowBootstrapRoomScheduler(CustomTestCase):
def test_dispatches_by_bootstrap_room_modulo(self):
ctl = _make_controller(dp_size=4)
for room, expected_rank in [
(0, 0),
(1, 1),
(4, 0),
(5, 1),
(100, 0),
(101, 1),
]:
ctl.follow_bootstrap_room_scheduler(_req(bootstrap_room=room))
ctl.workers[expected_rank].send_pyobj.assert_called()
def test_requires_bootstrap_room(self):
ctl = _make_controller(dp_size=4)
with self.assertRaises(AssertionError):
ctl.follow_bootstrap_room_scheduler(_req(bootstrap_room=None))
def test_routed_dp_rank_bypasses_bootstrap_room(self):
ctl = _make_controller(dp_size=4)
ctl.follow_bootstrap_room_scheduler(_req(routed_dp_rank=3, bootstrap_room=1))
ctl.workers[3].send_pyobj.assert_called_once()
ctl.workers[1].send_pyobj.assert_not_called()
class TestTotalRequestsScheduler(CustomTestCase):
def test_dispatches_to_min_request_worker(self):
ctl = _make_controller(dp_size=4)
ctl.dp_budget.total_requests = [5, 3, 1, 4]
ctl.total_requests_scheduler(_req())
ctl.workers[2].send_pyobj.assert_called_once()
for i in (0, 1, 3):
ctl.workers[i].send_pyobj.assert_not_called()
self.assertEqual(
ctl.dp_budget.total_requests[2],
2,
"DPBudget must record the dispatch by incrementing the counter",
)
def test_routed_dp_rank_bypasses_budget(self):
ctl = _make_controller(dp_size=4)
ctl.dp_budget.total_requests = [5, 3, 1, 4]
ctl.total_requests_scheduler(_req(routed_dp_rank=0))
ctl.workers[0].send_pyobj.assert_called_once()
# DPBudget must not be touched when bypassed
self.assertEqual(
ctl.dp_budget.total_requests,
[5, 3, 1, 4],
"external routing must not mutate DPBudget state",
)
class TestStatusAwarenessInconsistency(CustomTestCase):
"""Document a divergence: ``round_robin_scheduler`` skips workers whose
``status`` is False, but ``total_requests_scheduler`` /
``total_tokens_scheduler`` route purely by DPBudget — they do NOT
consult ``self.status``. If a future change unifies this behaviour,
this test will fail and force a reviewer to confirm intent."""
def test_total_requests_ignores_status(self):
ctl = _make_controller(dp_size=4)
# Worker 2 is the global minimum AND marked inactive.
ctl.dp_budget.total_requests = [5, 3, 1, 4]
ctl.status[2] = False
ctl.total_requests_scheduler(_req())
# Current behaviour: still dispatches to the inactive worker.
ctl.workers[2].send_pyobj.assert_called_once()
if __name__ == "__main__":
unittest.main()