Coverage for / home / jenkins / .local / lib / python3.10 / site-packages / hyper_parallel / core / tensor_parallel / api.py: 100%
49 statements
« prev ^ index » next coverage.py v7.13.1, created at 2026-10-26 05:43 +0800
« prev ^ index » next coverage.py v7.13.1, created at 2026-10-26 05:43 +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"""High-level API to apply parallel styles to modules (aligned with PyTorch ``parallelize_module``)."""
16from __future__ import annotations
18import warnings
19from contextlib import contextmanager
20from fnmatch import fnmatch
21from typing import Iterator, Optional, Union
23from torch import nn
25from hyper_parallel.core.dtensor.device_mesh import DeviceMesh, _mesh_resources
26from hyper_parallel.core.tensor_parallel.style import ParallelStyle
28__all__ = ["parallelize_module"]
31def _named_children(module: nn.Module):
32 """Return the immediate named children of a PyTorch module."""
33 return module.named_children()
36@contextmanager
37def _tensor_parallel_mesh_context(device_mesh: DeviceMesh) -> Iterator[DeviceMesh]:
38 """Internal: same thread-local stack as ``with device_mesh:`` for ``parallelize_module(..., None)``.
40 Prefer user code using ``with mesh:``; this exists for tests and library helpers.
41 """
42 with device_mesh:
43 yield device_mesh
46def _validate_tp_mesh_dim(device_mesh: DeviceMesh) -> None:
47 """Require a 1-D mesh, matching PyTorch tensor-parallel constraints."""
48 if device_mesh.ndim > 1:
49 raise ValueError(
50 f"Tensor Parallel only accepts a 1D DeviceMesh, but found {device_mesh.ndim}D! "
51 f'If you have a 2-D or N-D device_mesh, consider passing in device_mesh["tp"] '
52 f'or another 1-D sub-mesh slice (e.g. mesh["cp"]).'
53 )
56def parallelize_module( # type: ignore[return]
57 module: nn.Module,
58 device_mesh: Optional[DeviceMesh] = None,
59 parallelize_plan: Optional[Union[ParallelStyle, dict[str, ParallelStyle]]] = None,
60 *,
61 src_data_rank: Optional[int] = 0,
62) -> nn.Module:
63 """Apply parallel styles to *module* or submodules (PyTorch-compatible interface).
65 Behaviour follows ``torch.distributed.tensor.parallel.parallelize_module``:
67 - *device_mesh* should normally be passed explicitly. Omitting it (``None``) requires an
68 active mesh context: ``with mesh:`` (see :meth:`hyper_parallel.core.dtensor.device_mesh.DeviceMesh.__enter__`)
69 or :func:`_tensor_parallel_mesh_context` for tests/libraries.
70 - *parallelize_plan* may be a single :class:`ParallelStyle` (applied to *module*)
71 or a dict mapping submodule paths to styles. Path segments support ``fnmatch``
72 patterns (e.g. ``\"layers.*\"``) like PyTorch FQN rules.
73 - Only **1-D** :class:`DeviceMesh` is accepted; slice a sub-mesh from a multi-dim mesh first.
74 - *src_data_rank* is stored on the style (``style.src_data_rank``) before ``apply``; styles
75 that shard parameters from a logical global tensor may use it (see PyTorch TP).
77 Note:
78 When ``parallelize_plan`` is a single :class:`ParallelStyle` (not a dict), this
79 function modifies it in-place by setting ``parallelize_plan.src_data_rank``.
80 The caller should be aware that the passed object will be mutated.
82 Args:
83 module: Root module to parallelize.
84 device_mesh: Mesh for this TP/CP slice. Use ``None`` only inside ``with mesh:`` (or
85 :func:`_tensor_parallel_mesh_context`) so ``_mesh_resources.get_current_mesh()`` resolves
86 (see PyTorch ``distribute_module``).
87 parallelize_plan: A :class:`ParallelStyle` or dict ``{path: ParallelStyle}``.
88 src_data_rank: Source rank for global tensor semantics; ``None`` means use local data only
89 (PyTorch parity). Default ``0``.
91 Returns:
92 *module* after in-place parallelization.
93 """
94 if device_mesh is None:
95 device_mesh = _mesh_resources.get_current_mesh()
96 _validate_tp_mesh_dim(device_mesh)
98 if parallelize_plan is None:
99 warnings.warn(
100 "No parallelize_plan is provided and auto-parallel is not supported "
101 "at the moment, so this parallelize_module call will do nothing.",
102 stacklevel=2,
103 )
104 return module
106 if isinstance(parallelize_plan, ParallelStyle):
107 parallelize_plan.src_data_rank = src_data_rank
108 return parallelize_plan.apply(module, device_mesh)
109 if isinstance(parallelize_plan, dict):
111 def _apply_path(
112 current_module: nn.Module,
113 atoms: list[str],
114 style: ParallelStyle,
115 src_rank: Optional[int],
116 ) -> bool:
117 atom = atoms[0]
118 matched_children = list(
119 filter(
120 lambda t, pattern=atom: fnmatch(t[0], pattern),
121 _named_children(current_module),
122 )
123 )
124 applied = False
125 for _, submodule in matched_children:
126 if len(atoms) == 1:
127 parallelize_module(
128 submodule,
129 device_mesh,
130 style,
131 src_data_rank=src_rank,
132 )
133 applied = True
134 else:
135 applied = _apply_path(submodule, atoms[1:], style, src_rank) or applied
136 return applied
138 for module_path, parallelize_style in parallelize_plan.items():
139 if not isinstance(parallelize_style, ParallelStyle):
140 raise TypeError(
141 "Expect ParallelStyle values in parallelize_plan dict, but got "
142 f"{type(parallelize_style)} for path '{module_path}'."
143 )
144 path_splits = module_path.split(".")
145 if module_path == "" or any(path == "" for path in path_splits):
146 raise ValueError(
147 f"Expect module path to be non-empty dot-separated atoms, but got '{module_path}'."
148 )
149 if not _apply_path(module, path_splits, parallelize_style, src_data_rank):
150 warnings.warn(
151 f"parallelize_plan path '{module_path}' has no matches, so this path is skipped.",
152 stacklevel=2,
153 )
154 return module
155 raise TypeError(
156 "Expect Union[ParallelStyle, Dict[str, ParallelStyle]] for"
157 f" parallelize_plan, {type(parallelize_plan)} found!"
158 )