|
5 | 5 | import itertools |
6 | 6 | import json |
7 | 7 | import time |
| 8 | +import warnings |
8 | 9 | from dataclasses import asdict, dataclass |
9 | 10 | from functools import partial |
10 | 11 | from pathlib import Path |
|
18 | 19 | from torch.distributed.fsdp import FSDPModule as FSDP2 |
19 | 20 | from torch.distributed.fsdp import FullyShardedDataParallel as FSDP1 |
20 | 21 | from torch.distributed.fsdp import ShardingStrategy |
| 22 | +from torch.distributed.tensor import DTensor |
21 | 23 | from torch.distributed.tensor.parallel import ( |
22 | 24 | ColwiseParallel, |
23 | 25 | PrepareModuleInput, |
|
41 | 43 | TransformerMLP, |
42 | 44 | ) |
43 | 45 | from modalities.models.model import ActivationType |
| 46 | +from modalities.models.parallelism.expert_parallelism import ExpertParallel |
44 | 47 | from modalities.nn.model_initialization.initialization_if import ModelInitializationIF |
45 | 48 | from modalities.running_env.env_utils import FSDP2MixedPrecisionSettings, MixedPrecisionSettings |
46 | | -from modalities.running_env.fsdp.device_mesh import ParallelismDegrees |
| 49 | +from modalities.running_env.fsdp.device_mesh import ParallelismDegrees, get_mesh_for_parallelism_method |
47 | 50 | from modalities.running_env.fsdp.fsdp_auto_wrapper import FSDPTransformerAutoWrapPolicyFactory |
48 | 51 | from modalities.training.activation_checkpointing.activation_checkpointing import ( |
49 | 52 | ActivationCheckpointing, |
@@ -253,6 +256,95 @@ def get_fsdp2_wrapped_model( |
253 | 256 | ) |
254 | 257 | return model |
255 | 258 |
|
| 259 | + @staticmethod |
| 260 | + def get_ep_wrapped_model( |
| 261 | + model, |
| 262 | + block_names: list[str], |
| 263 | + device_mesh: DeviceMesh, |
| 264 | + mixed_precision_settings: FSDP2MixedPrecisionSettings, |
| 265 | + ) -> nn.Module: |
| 266 | + """Apply expert parallel wrapping for selected MoE blocks.""" |
| 267 | + block_types = [] |
| 268 | + missing_block_names = [] |
| 269 | + for name in block_names: |
| 270 | + block_type = get_module_class_from_name(model, name) |
| 271 | + if block_type is None: |
| 272 | + missing_block_names.append(name) |
| 273 | + else: |
| 274 | + block_types.append(block_type) |
| 275 | + |
| 276 | + if len(missing_block_names) > 0 and ( |
| 277 | + not dist.is_available() or not dist.is_initialized() or dist.get_rank() == 0 |
| 278 | + ): |
| 279 | + warnings.warn( |
| 280 | + f"Could not resolve some requested MoE block names and they will be ignored: {missing_block_names}", |
| 281 | + stacklevel=2, |
| 282 | + ) |
| 283 | + |
| 284 | + block_types = tuple(block_types) |
| 285 | + |
| 286 | + if len(block_types) == 0: |
| 287 | + raise ValueError(f"None of the requested MoE block names were found: {block_names}") |
| 288 | + |
| 289 | + ep_mesh = get_mesh_for_parallelism_method(device_mesh, ParallelismDegrees.EP) |
| 290 | + target_dtype = mixed_precision_settings.param_dtype.value |
| 291 | + |
| 292 | + wrapped_blocks = 0 |
| 293 | + for module in model.modules(): |
| 294 | + if isinstance(module, block_types): |
| 295 | + if hasattr(module, "experts"): |
| 296 | + ep_target = module |
| 297 | + elif (ffn := getattr(module, "ffn", None)) is not None and hasattr(ffn, "experts"): |
| 298 | + ep_target = ffn |
| 299 | + else: |
| 300 | + raise ValueError( |
| 301 | + f"Module {type(module).__name__} has no EP-compatible experts location. " |
| 302 | + "Expected `experts` or `ffn.experts`." |
| 303 | + ) |
| 304 | + |
| 305 | + if getattr(ep_target, "_ep_enabled", False): |
| 306 | + continue |
| 307 | + |
| 308 | + experts = ep_target.experts |
| 309 | + missing = [a for a in ("w1", "w2") if not hasattr(experts, a)] |
| 310 | + if missing: |
| 311 | + raise ValueError( |
| 312 | + f"Module {type(ep_target).__name__}.experts is not grouped-experts compatible. " |
| 313 | + f"Missing: {missing}" |
| 314 | + ) |
| 315 | + if experts.w1.ndim != 3 or experts.w2.ndim != 3: |
| 316 | + raise ValueError( |
| 317 | + f"Expected grouped expert parameters with ndim=3. Got w1.ndim={experts.w1.ndim}, " |
| 318 | + f"w2.ndim={experts.w2.ndim}" |
| 319 | + ) |
| 320 | + |
| 321 | + ep_target._ep_mesh = ep_mesh |
| 322 | + ep_target._ep_group = ep_mesh.get_group() |
| 323 | + ep_target._ep_size = ep_mesh.size() |
| 324 | + ep_target._ep_rank = ep_mesh.get_local_rank() |
| 325 | + |
| 326 | + ep_target.experts = ExpertParallel()._apply(ep_target.experts, ep_mesh) |
| 327 | + setattr(ep_target.experts, "_ep_enabled", True) |
| 328 | + |
| 329 | + for pname, p in list(ep_target.experts._parameters.items()): |
| 330 | + if isinstance(p, DTensor) and p.dtype != target_dtype: |
| 331 | + local = p.to_local().to(target_dtype) |
| 332 | + ep_target.experts._parameters[pname] = nn.Parameter( |
| 333 | + DTensor.from_local(local, p.device_mesh, p.placements, run_check=False), |
| 334 | + requires_grad=p.requires_grad, |
| 335 | + ) |
| 336 | + |
| 337 | + wrapped_blocks += 1 |
| 338 | + |
| 339 | + if wrapped_blocks == 0: |
| 340 | + raise ValueError(f"No blocks matched the requested types: {[t.__name__ for t in block_types]}") |
| 341 | + |
| 342 | + model._ep_wrapped = True |
| 343 | + model._ep_mesh = ep_mesh |
| 344 | + model._ep_num_wrapped_blocks = wrapped_blocks |
| 345 | + |
| 346 | + return model |
| 347 | + |
256 | 348 | @staticmethod |
257 | 349 | def get_weight_initialized_model(model: nn.Module, model_initializer: ModelInitializationIF) -> nn.Module: |
258 | 350 | """ |
|
0 commit comments