Coverage for  / home / jenkins / .local / lib / python3.10 / site-packages / hyper_parallel / core / utils / moe_utils.py: 100%

44 statements  

« prev     ^ index     » next       coverage.py v7.13.1, created at 2026-11-15 07:18 +0800

1# Copyright 2026 Huawei Technologies Co., Ltd 

2# 

3# Licensed under the Apache License, Version 2.0 (the "License"); 

4# you may not use this file except in compliance with the License. 

5# You may obtain a copy of the License at 

6# 

7# http://www.apache.org/licenses/LICENSE-2.0 

8# 

9# Unless required by applicable law or agreed to in writing, software 

10# distributed under the License is distributed on an "AS IS" BASIS, 

11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 

12# See the License for the specific language governing permissions and 

13# limitations under the License. 

14# ============================================================================ 

15"""MoE utilities for distributed training.""" 

16 

17from typing import TYPE_CHECKING, Optional, Any 

18 

19import torch.distributed as dist 

20from torch import nn 

21 

22from hyper_parallel.core.activation_memory.wrapper import ActivationWrapper 

23 

24if TYPE_CHECKING: 

25 from hyper_parallel.components.modules.moe import MoE 

26 

27 

28def sync_and_update_expert_bias( 

29 moe: "MoE", 

30 lr: float = 1e-3, 

31 tp_group: Optional[Any] = None, 

32 cp_group: Optional[Any] = None, 

33 dp_group: Optional[Any] = None, 

34 num_recomputations: int = 1, 

35) -> None: 

36 """Synchronize tokens_per_expert across distributed ranks, then update expert bias. 

37 

38 This function handles distributed synchronization for expert bias updates in 

39 MoE (Mixture of Experts) models. It should be called after optimizer.step() in 

40 the training loop. 

41 

42 The synchronization is needed because: 

43 - **DP**: Different ranks process different samples, need to aggregate global batch stats 

44 - **TP+SP**: Standard TP does not need sync (all ranks see same input). 

45 Only needed when TP is combined with sequence parallelism (TP+SP), 

46 where activations are sequence-sharded across TP ranks. 

47 - **CP**: Sequence dimension is sharded across ranks 

48 

49 For pure EP scenario (no DP/TP/CP), call moe.update_expert_bias() directly. 

50 

51 Args: 

52 moe: The MoE module whose expert bias should be updated. 

53 lr: Step size for the bias update. Defaults to ``1e-3``. 

54 tp_group: Tensor parallel process group (ProcessGroup or GroupInfo). 

55 Only needed when TP is combined with sequence parallelism (TP+SP), 

56 where activations are sequence-sharded across TP ranks. 

57 Standard TP (weight-only sharding) does not need this. 

58 cp_group: Context parallel process group (ProcessGroup or GroupInfo). 

59 Sync across sequence shards dimension. 

60 dp_group: Data parallel process group (ProcessGroup or GroupInfo). 

61 Sync across different samples to get global batch statistics. 

62 num_recomputations: Number of forward executions per optimizer step. 

63 Default ``1`` (normal training). Set to ``2`` when activation 

64 checkpoint is enabled. 

65 

66 Example: 

67 >>> # DP scenario 

68 >>> sync_and_update_expert_bias(moe_layer, lr=1e-3, dp_group=dp_group_info) 

69 >>> 

70 >>> # TP×CP×DP scenario (reference: Megatron-LM) 

71 >>> sync_and_update_expert_bias( 

72 ... moe_layer, 

73 ... lr=1e-3, 

74 ... tp_group=tp_group_info, 

75 ... cp_group=cp_group_info, 

76 ... dp_group=dp_group_info, 

77 ... ) 

78 

79 Note: 

80 Reference implementation: Megatron-LM megatron/core/transformer/moe/moe_utils.py 

81 uses TP×CP×DP group synchronization for global batch statistics. 

82 Model adapters may provide ``expert_bias_update_groups`` and 

83 ``expert_bias_update_rate`` to override generic groups and update speed. 

84 """ 

85 groups = getattr(moe, "expert_bias_update_groups", None) 

86 if groups is None: 

87 groups = (tp_group, cp_group, dp_group) 

88 for group in groups: 

89 if group is not None: 

90 dist.all_reduce(moe.tokens_per_expert, group=getattr(group, "group", group)) 

91 

92 moe.update_expert_bias( 

93 lr=getattr(moe, "expert_bias_update_rate", lr), 

94 num_recomputations=num_recomputations, 

95 ) 

96 

97 

98def _get_moe_layers(model: "nn.Module") -> list: 

99 """Collect all MoE sub-modules with ``enable_expert_bias=True``. 

100 

101 Uses duck-typing (``getattr(m, 'enable_expert_bias', False)``) instead 

102 of ``isinstance`` to avoid importing platform-specific MoE classes in 

103 platform-agnostic ``core/`` code. 

104 

105 Args: 

106 model: Root module to search. 

107 

108 Returns: 

109 List of MoE module instances. 

110 """ 

111 # Transparent wrappers forward the child's attributes but own no router state. 

112 return [ 

113 module for module in model.modules() 

114 if not isinstance(module, ActivationWrapper) and getattr(module, 'enable_expert_bias', False) 

115 ] 

116 

117 

118class MoEMonitorCallback: 

119 """Callback that automates expert bias updates across all MoE layers. 

120 

121 Provides a three-layer invocation architecture: 

122 

123 - **Layer 0**: :func:`sync_and_update_expert_bias` — per-module sync + update 

124 - **Layer 1**: :meth:`on_step_end` — iterates all MoE layers, calling Layer 0 

125 - **Layer 2**: :meth:`register` — registers optimizer post-hook for auto-trigger 

126 

127 Each layer can be used independently; Layer 0 is sufficient for basic usage, 

128 while Layers 1–2 add convenience for production training loops. 

129 

130 Args: 

131 model: The model containing MoE layers to monitor. 

132 lr: Step size for expert bias updates. Defaults to ``1e-3``. 

133 tp_group: Tensor parallel process group. Only needed for TP+SP 

134 (sequence-sharded activations across TP ranks). Standard TP 

135 does not need this — pass ``None``. 

136 cp_group: Context parallel process group. Sync across sequence shards. 

137 dp_group: Data parallel process group. Sync across different samples. 

138 num_recomputations: Number of forward executions per optimizer step. 

139 Default ``1``. Set to ``2`` when activation checkpoint is enabled. 

140 

141 Example: 

142 >>> # Layer 1: manual trigger in training loop 

143 >>> callback = MoEMonitorCallback( 

144 ... model, lr=1e-3, dp_group=dp_group_info, 

145 ... ) 

146 >>> for batch in dataloader: 

147 ... loss = model(batch) 

148 ... loss.backward() 

149 ... optimizer.step() 

150 ... callback.on_step_end() # auto-updates all MoE layers 

151 ... optimizer.zero_grad() 

152 >>> 

153 >>> # Layer 2: auto-trigger via optimizer post-hook 

154 >>> callback = MoEMonitorCallback(model, lr=1e-3, dp_group=dp_group_info) 

155 >>> callback.register(optimizer) 

156 >>> # optimizer.step() now auto-triggers callback.on_step_end() 

157 

158 Note: 

159 Sync dimensions use ``dp_group``/``tp_group``/``cp_group`` (not ``ep_group``). 

160 EP does not need synchronization because all EP ranks have identical 

161 routing data (``tokens_per_expert`` shape = ``[total_experts]``). 

162 """ 

163 

164 def __init__( 

165 self, 

166 model: "nn.Module", 

167 lr: float = 1e-3, 

168 tp_group: Optional[Any] = None, 

169 cp_group: Optional[Any] = None, 

170 dp_group: Optional[Any] = None, 

171 num_recomputations: int = 1, 

172 ) -> None: 

173 """Initialize MoEMonitorCallback.""" 

174 self.model = model 

175 self.lr = lr 

176 self.tp_group = tp_group 

177 self.cp_group = cp_group 

178 self.dp_group = dp_group 

179 self.num_recomputations = num_recomputations 

180 self._hook_handle = None 

181 self.last_mean_aux_loss: Optional[float] = None 

182 

183 def on_step_end(self) -> None: 

184 """Update expert bias for all MoE layers in the model. 

185 

186 Iterates over all :class:`MoE` sub-modules with 

187 ``enable_expert_bias=True`` and calls 

188 :func:`sync_and_update_expert_bias` for each one. 

189 

190 When ``load_balance_coeff > 0`` on any MoE layer, collects 

191 ``last_aux_loss`` values and logs the mean. 

192 """ 

193 moe_layers = _get_moe_layers(self.model) 

194 if not moe_layers: 

195 return 

196 

197 for moe in moe_layers: 

198 sync_and_update_expert_bias( 

199 moe, 

200 lr=self.lr, 

201 tp_group=self.tp_group, 

202 cp_group=self.cp_group, 

203 dp_group=self.dp_group, 

204 num_recomputations=self.num_recomputations, 

205 ) 

206 

207 # Log aux_loss if any MoE layer has it enabled. 

208 aux_losses = [ 

209 moe.last_aux_loss.item() 

210 for moe in moe_layers 

211 if moe.last_aux_loss is not None 

212 ] 

213 if aux_losses: 

214 mean_aux_loss = sum(aux_losses) / len(aux_losses) 

215 self.last_mean_aux_loss = mean_aux_loss 

216 else: 

217 self.last_mean_aux_loss = None 

218 

219 def register(self, optimizer: Any) -> None: 

220 """Register an optimizer post-hook to auto-trigger :meth:`on_step_end`. 

221 

222 On PyTorch 2.0+, uses ``optimizer.register_step_post_hook``. 

223 The hook fires after each ``optimizer.step()`` call, so that 

224 expert bias is updated once parameters have been updated. Repeated 

225 calls replace the existing hook registration. 

226 

227 Args: 

228 optimizer: The optimizer instance to register the hook on. 

229 

230 Raises: 

231 RuntimeError: If the optimizer does not support step post-hooks. 

232 """ 

233 if not hasattr(optimizer, "register_step_post_hook"): 

234 raise RuntimeError( 

235 "Optimizer does not support register_step_post_hook. " 

236 "Requires PyTorch 2.0+. Call on_step_end() manually instead." 

237 ) 

238 self.remove() 

239 self._hook_handle = optimizer.register_step_post_hook( 

240 lambda *_: self.on_step_end(), 

241 ) 

242 

243 def remove(self) -> None: 

244 """Remove the optimizer post-hook if registered.""" 

245 if self._hook_handle is not None: 

246 self._hook_handle.remove() 

247 self._hook_handle = None