main
py 123 lines 4.23 KB
Raw
1 # SPDX-License-Identifier: Apache-2.0
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 """Utility functions for EPLB (Expert Parallel Load Balancing)."""
4
5 import contextlib
6 import os
7 import threading
8
9 import torch
10
11 from vllm.config import ParallelConfig
12 from vllm.logger import init_logger
13
14 logger = init_logger(__name__)
15
16
17 @contextlib.contextmanager
18 def device_stream(stream: torch.Stream | None):
19 """Platform-agnostic context manager that activates *stream* as the
20 current accelerator stream for the duration of the ``with`` block.
21 A no-op when *stream* is ``None``."""
22 if stream is None:
23 yield
24 return
25 prev = torch.accelerator.current_stream()
26 torch.accelerator.set_stream(stream)
27 try:
28 yield
29 finally:
30 torch.accelerator.set_stream(prev)
31
32
33 class CpuGpuEvent:
34 """Combines a CUDA event with a CPU threading event to enforce record->wait
35 ordering across two threads.
36
37 This class is designed for exactly two threads: one producer that calls
38 record() and one consumer that calls wait(). Using it with more than two
39 threads is not supported and will produce undefined behavior.
40
41 CUDA events alone are insufficient for cross-thread synchronization because
42 waiting on an unrecorded CUDA event is a no-op. The wait will return
43 immediately instead of blocking. This class adds a threading.Event so
44 that the waiting thread blocks on the CPU side until record() is called, at
45 which point the CUDA event is guaranteed to be in-flight and event.wait() will
46 correctly synchronize the GPU stream.
47 """
48
49 def __init__(self):
50 self._event = torch.Event()
51 self._recorded = threading.Event()
52
53 def wait(self, stream: torch.Stream | None = None):
54 """Blocks the calling thread until record finishes. Used to guarantee that the
55 record kernel is called before wait.
56
57 Should only be called by the Async Eplb thread.
58 """
59 self._recorded.wait()
60 self._event.wait(stream)
61 self._recorded.clear()
62
63 def record(self, stream: torch.Stream | None = None):
64 """Unblocks the waiting thread after calling event.record().
65
66 Should only be called by the main thread.
67 """
68 if self._recorded.is_set():
69 raise RuntimeError(
70 "CpuGpuEvent.record() called before the previous event was "
71 "consumed by wait()"
72 )
73 self._event = torch.Event()
74 self._event.record(stream)
75 self._recorded.set()
76
77
78 def override_envs_for_eplb(
79 parallel_config: ParallelConfig,
80 moe_backend: str | None = None,
81 ) -> None:
82 """Override environment variables for EPLB when specific conditions are met.
83
84 Args:
85 parallel_config: The parallel configuration object.
86 moe_backend: The configured MoE backend (e.g. ``deep_gemm_mega_moe``).
87
88 """
89 is_data_parallel = parallel_config.data_parallel_size > 1
90 is_eplb_enabled = parallel_config.enable_eplb
91 is_mega_moe = moe_backend == "deep_gemm_mega_moe"
92 is_nccl_based_eplb_communicator = parallel_config.eplb_config.communicator in (
93 "torch_nccl",
94 "pynccl",
95 )
96
97 # Override NCCL_MAX_CTAS to avoid hangs when EPLB's NCCL weight exchange
98 # contends with MoE backend's cooperative-launch on GPU SMs.
99 #
100 # DeepGEMM Mega MoE uses cooperative launch, which tries to reserve a
101 # large fraction of the GPU's SMs. If those SMs are occupied by NCCL,
102 # the cooperative launch blocks until enough SMs are freed, causing a
103 # deadlock. Limiting NCCL occupancy via NCCL_MAX_CTAS leaves space for
104 # the cooperative kernel to launch and complete.
105 if (
106 is_data_parallel
107 and is_eplb_enabled
108 and is_nccl_based_eplb_communicator
109 and is_mega_moe
110 ):
111 current_value_str = os.getenv("NCCL_MAX_CTAS")
112
113 if current_value_str and current_value_str.isdigit():
114 return
115
116 override_value = 8
117 os.environ["NCCL_MAX_CTAS"] = str(override_value)
118 logger.info_once(
119 f"EPLB: Setting NCCL_MAX_CTAS={override_value} "
120 f"for expert parallel with NCCL-based EPLB communicator and "
121 f"cooperative MoE backend (deep_gemm_mega_moe)",
122 scope="global",
123 )