-
Notifications
You must be signed in to change notification settings - Fork 360
RDT weight sync: GPU->GPU zero copy transfer through SGLang Ray actor backend #1313
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
c414b94
5eb5756
b74ab7f
4c5a351
f8bc705
f961917
4f5d2d7
96aa1ee
bcd5cbf
0150527
14fd82f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -9,6 +9,9 @@ | |
| import requests | ||
| import sglang_router | ||
| from packaging.version import parse | ||
| from sglang.srt.environ import envs | ||
| from sglang.srt.layers.dp_attention import compute_dp_attention_world_info | ||
| from sglang.srt.ray import get_scheduler_actor_name | ||
| from sglang.srt.server_args import ServerArgs | ||
| from sglang.srt.utils import kill_process_tree | ||
| from urllib3.exceptions import NewConnectionError | ||
|
|
@@ -68,7 +71,12 @@ def _get_gpu_uuids(gpu_ids: list[int]) -> list[str | None]: | |
|
|
||
|
|
||
| def launch_server_process(server_args: ServerArgs) -> multiprocessing.Process: | ||
| from sglang.srt.entrypoints.http_server import launch_server | ||
| if server_args.use_ray: | ||
| # entrypoints.http_server ignores use_ray and starts mp.Process schedulers, | ||
| # which have no SchedulerActor for RDT to pull from. | ||
| from sglang.srt.ray.http_server import launch_server | ||
| else: | ||
| from sglang.srt.entrypoints.http_server import launch_server | ||
|
|
||
| multiprocessing.set_start_method("spawn", force=True) | ||
| server_args.host = server_args.host.strip("[]") | ||
|
|
@@ -132,13 +140,16 @@ def __init__( | |
| base_gpu_id: int | None = None, | ||
| sglang_overrides: dict | None = None, | ||
| num_gpus_per_engine: int | None = None, | ||
| pg_bundles: list[int] | None = None, | ||
| ): | ||
| self.args = args | ||
| self.rank = rank | ||
| self.worker_type = worker_type | ||
| self.base_gpu_id = base_gpu_id | ||
| self.sglang_overrides = sglang_overrides or {} | ||
| self.num_gpus_per_engine = num_gpus_per_engine | ||
| self.pg_bundles = pg_bundles | ||
| self._scheduler_actors = [] | ||
|
|
||
| def get_topology_info(self) -> dict: | ||
| """Placement facts for the dashboard timeline. ``base_gpu_id`` is | ||
|
|
@@ -244,8 +255,35 @@ def _sanity_check_server_args(actual_server_args, expect_server_args): | |
| _sanity_check_server_args(actual_server_args, expect_server_args) | ||
|
|
||
| def _init_normal(self, server_args_dict): | ||
| logger.info(f"Launch HttpServerEngineAdapter at: {self.server_host}:{self.server_port}") | ||
| self.process = launch_server_process(ServerArgs(**server_args_dict)) | ||
| use_rdt = self.args.update_weight_transfer_mode == "rdt" | ||
| if use_rdt: | ||
| if self.node_rank != 0: | ||
| # For a multi-node engine, the node-0 server's RayEngine spawns | ||
| # the SchedulerActors of ALL ranks (placed cross-node via the | ||
| # placement group), so non-zero node ranks launch nothing. | ||
| self.process = None | ||
| return | ||
| server_args_dict["use_ray"] = True | ||
| server_args_dict["enable_rdt_weight_sync"] = True | ||
| assert self.pg_bundles | ||
| logger.info( | ||
| f"Launch HttpServerEngineAdapter at: {self.server_host}:{self.server_port}" | ||
| f"{' (use_ray=True for RDT)' if use_rdt else ''}" | ||
| ) | ||
| server_args = ServerArgs(**server_args_dict) | ||
| if use_rdt: | ||
| import ray | ||
|
|
||
| placement_group = ray.util.get_current_placement_group() | ||
| assert placement_group is not None | ||
| envs.SGLANG_RAY_BUNDLE_INDICES.set(",".join(str(bundle) for bundle in self.pg_bundles)) | ||
| server_args.override( | ||
| "miles.rdt.ray_context", | ||
| placement_group=placement_group, | ||
| ray_runtime_env=dict(ray.get_runtime_context().runtime_env), | ||
| ray_namespace=ray.get_runtime_context().namespace, | ||
| ) | ||
| self.process = launch_server_process(server_args) | ||
|
|
||
| if self.node_rank == 0 and self.router_ip and self.router_port: | ||
| if parse(sglang_router.__version__) <= parse("0.2.1") or self.args.use_miles_router: | ||
|
|
@@ -465,6 +503,9 @@ def flush_cache(self): | |
| def shutdown(self): | ||
| if self.args.rollout_external: | ||
| return | ||
| if getattr(self, "process", None) is None: | ||
| # Non-zero node ranks of an RDT multi-node engine launch no server. | ||
| return | ||
|
|
||
| logger.info(f"Shutdown engine {self.server_host}:{self.server_port}...") | ||
| if self.node_rank == 0: | ||
|
|
@@ -514,6 +555,50 @@ def unload_lora_adapter(self, lora_name: str): | |
| {"lora_name": lora_name}, | ||
| ) | ||
|
|
||
| def get_scheduler_actors(self) -> list: | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This function looks a bit of hardcoded
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. addressed hardcoding by adding |
||
| """Return this engine's SchedulerActor handles (RDT mode, use_ray=True). | ||
|
|
||
| Reconstructs each actor name with ``get_scheduler_actor_name`` from the | ||
| bundle list handed to RayEngine at launch. | ||
| """ | ||
| if self._scheduler_actors: | ||
| return self._scheduler_actors | ||
|
|
||
| assert self.pg_bundles, "get_scheduler_actors requires the RDT bundle list" | ||
|
|
||
| import ray | ||
|
|
||
| tp_size = self.num_gpus_per_engine or self.args.rollout_num_gpus_per_engine | ||
| enable_dp_attention = bool(getattr(self.args, "sglang_enable_dp_attention", False)) | ||
| dp_size = getattr(self.args, "sglang_dp_size", 1) | ||
| attn_cp_size = getattr(self.args, "sglang_attn_cp_size", 1) | ||
| rank0_node_ip = ray.util.get_node_ip_address() | ||
|
|
||
| actors = [] | ||
| for rank, bundle_idx in enumerate(self.pg_bundles): | ||
| tp_rank = rank % tp_size | ||
| _, _, dp_rank, _ = compute_dp_attention_world_info( | ||
| enable_dp_attention, tp_rank, tp_size, dp_size, attn_cp_size | ||
| ) | ||
| name = get_scheduler_actor_name( | ||
| rank0_node_ip=rank0_node_ip, | ||
| dp_rank=dp_rank, | ||
| pp_rank=rank // tp_size, | ||
| tp_rank=tp_rank, | ||
| port=self.server_port, | ||
| bundle_idx=bundle_idx, | ||
| ) | ||
| try: | ||
| actors.append(ray.get_actor(name)) | ||
| except ValueError as e: | ||
| raise RuntimeError( | ||
| f"SchedulerActor {name!r} not found for engine " | ||
| f"{self.server_host}:{self.server_port} rank={rank}" | ||
| ) from e | ||
|
|
||
| self._scheduler_actors = actors | ||
| return actors | ||
|
|
||
| def release_memory_occupation(self, tags: list[str] = None): | ||
| """Release memory occupation. Available tags: weights, kv_cache.""" | ||
| self.flush_cache() | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This breaks Miles' contract of using the process to control the lifecycle of server engines, which will conflict with the process-based control logics, including cleanup, fault tolerance, etc...
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In non-rdt path, we also return None for the process for non-zero node ranks
https://github.com/xyuzh/miles/blob/xinyu/rdt-weight-sync/miles/backends/sglang_utils/sglang_engine.py#L69-L80
The engine only needs the process on rank 0 node