main
py 160 lines 6.13 KB
Raw
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 """The async worker that transfers experts in the background."""
4
5 import threading
6 from typing import TYPE_CHECKING
7
8 import torch
9
10 from vllm.distributed.parallel_state import get_eplb_group
11 from vllm.logger import init_logger
12
13 from .eplb_utils import CpuGpuEvent, device_stream
14 from .rebalance_execute import AsyncEplbLayerResult, transfer_layer
15
16 if TYPE_CHECKING:
17 from .eplb_state import EplbModelState, EplbState
18
19 logger = init_logger(__name__)
20
21
22 def start_async_worker(
23 state: "EplbState",
24 is_profile: bool = False,
25 ) -> threading.Thread:
26 rank = get_eplb_group().device_group.rank()
27 device_index = state.device_index
28 assert state.is_async
29
30 def thread_target() -> None:
31 assert device_index is not None
32 torch.accelerator.set_device_index(device_index)
33 stream = torch.Stream(device=torch.accelerator.current_accelerator())
34 try:
35 transfer_run_periodically(
36 state=state,
37 stream=stream,
38 is_profile=is_profile,
39 )
40 except Exception as exc: # pragma: no cover - diagnostic path
41 logger.exception("async loop error (Rank %d): %s", rank, str(exc))
42
43 thread = threading.Thread(target=thread_target, daemon=True)
44 thread.start()
45 return thread
46
47
48 def run_rebalance_experts(
49 model_state: "EplbModelState",
50 eplb_state: "EplbState",
51 physical_to_logical_map_cpu: torch.Tensor,
52 stream: torch.Stream,
53 ) -> torch.Tensor:
54 assert model_state.eplb_stats is not None
55 eplb_stats = model_state.eplb_stats
56
57 # Move the global expert load window to CPU for computation.
58 with device_stream(stream):
59 global_expert_load_window = eplb_stats.global_expert_load_window.cpu()
60 # Compute new expert mappings for the model
61 new_physical_to_logical_map = eplb_state.policy.rebalance_experts(
62 global_expert_load_window,
63 eplb_stats.num_replicas,
64 eplb_stats.num_groups,
65 eplb_stats.num_nodes,
66 eplb_stats.num_gpus,
67 physical_to_logical_map_cpu,
68 )
69 assert new_physical_to_logical_map.device == torch.device("cpu")
70
71 return new_physical_to_logical_map
72
73
74 def transfer_run_periodically(
75 state: "EplbState",
76 stream: torch.Stream,
77 is_profile: bool = False,
78 ) -> None:
79 while True:
80 state.rearrange_event.wait(stream=stream)
81
82 eplb_group = get_eplb_group().device_group
83 eplb_cpu_group = get_eplb_group().cpu_group
84 ep_rank = eplb_group.rank()
85
86 assert state.is_async
87 for model_state in state.model_states.values():
88 layer_idx = 0
89 # Set the async worker's CUDA stream on the communicator
90 model_state.communicator.set_stream(stream)
91 num_layers = model_state.model.num_moe_layers
92
93 # Snapshot the physical_to_logical_map (synchronized with
94 # rearrange_event) and copy it to CPU
95 with device_stream(stream):
96 physical_to_logical_map_cpu = model_state.physical_to_logical_map.cpu()
97
98 new_physical_to_logical_map = run_rebalance_experts(
99 model_state, state, physical_to_logical_map_cpu, stream
100 )
101
102 # Execute one EPLB layer transfer per model forward pass. Each iteration
103 # of this loop will copy the new set of expert weights into
104 # model_state.expert_buffer, which will be consumed by the main thread in
105 # move_to_workspace.
106 # We sync the rebalanced flag across ranks before each iteration so
107 # all ranks make a coordinated decision to continue or stop.
108 while layer_idx < num_layers:
109 flag = torch.tensor(
110 [int(model_state.rebalanced)],
111 dtype=torch.int32,
112 device="cpu",
113 )
114 torch.distributed.all_reduce(flag, group=eplb_cpu_group)
115 if int(flag.item()) != eplb_cpu_group.size():
116 logger.warning(
117 "async worker (rank=%d): layer %d coordinated stop "
118 "(flag_sum=%d, group_size=%d)",
119 ep_rank,
120 layer_idx,
121 int(flag.item()),
122 eplb_cpu_group.size(),
123 )
124 model_state.rebalanced = False
125 break
126
127 transfer_metadata = transfer_layer(
128 old_layer_indices=physical_to_logical_map_cpu[layer_idx],
129 new_layer_indices=new_physical_to_logical_map[layer_idx],
130 expert_weights=model_state.model.expert_weights[layer_idx],
131 expert_weights_buffer=model_state.expert_buffer,
132 communicator=model_state.communicator,
133 ep_group=eplb_group,
134 is_profile=is_profile,
135 stream=stream,
136 layer_idx=layer_idx,
137 )
138
139 # Wait until all writes to expert_buffer have finished before making the
140 # AsyncEplbLayerResult visible to the main thread.
141 stream.synchronize()
142
143 # This event guarantees that expert_buffer will not be overwritten by
144 # subsequent iterations of this loop until the main thread has consumed
145 # it. Record is called by the main thread after move_from_buffer().
146 consumed_event = CpuGpuEvent()
147
148 model_state.pending_result = AsyncEplbLayerResult(
149 layer_idx=layer_idx,
150 new_physical_to_logical_map=new_physical_to_logical_map[layer_idx],
151 transfer_metadata=transfer_metadata,
152 consumed_event=consumed_event,
153 )
154
155 # Block this thread until the main thread and main stream
156 # finish copying model_state.expert_buffer into
157 # model_state.model.expert_weights[layer_idx]
158 consumed_event.wait(stream=stream)
159 assert model_state.pending_result is None
160 layer_idx += 1