Coverage for / home / jenkins / .local / lib / python3.10 / site-packages / hyper_parallel / core / dtensor / _collective_utils.py: 20%
50 statements
« prev ^ index » next coverage.py v7.13.1, created at 2026-10-25 05:45 +0800
« prev ^ index » next coverage.py v7.13.1, created at 2026-10-25 05:45 +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"""Mesh-scoped collectives for :func:`distribute_tensor` (PyTorch DTensor parity)."""
16from __future__ import annotations
18from typing import Optional, Sequence
20import torch
21import torch.distributed as dist
23from hyper_parallel.core.dtensor.device_mesh import DeviceMesh
25Tensor = torch.Tensor
28def _ensure_mesh_process_groups(mesh: DeviceMesh) -> None:
29 """Lazily create per-axis process groups when mesh was built with ``init_backend=False``."""
30 if hasattr(mesh, "_dim_group_names") and mesh._dim_group_names is not None:
31 return
32 mesh._dim_group_names = DeviceMesh._init_process_groups( # pylint: disable=protected-access
33 mesh._mesh_shape,
34 mesh.mesh_dim_names,
35 mesh._rank_list,
36 )
39def mesh_scatter(
40 output: Tensor,
41 scatter_list: Sequence[Tensor],
42 mesh: DeviceMesh,
43 mesh_dim: int,
44 *,
45 group_src: int = 0,
46) -> Tensor:
47 """Scatter tensor chunks along one mesh dimension (PyTorch ``mesh_scatter`` parity)."""
48 _ensure_mesh_process_groups(mesh)
49 group = mesh.get_group(mesh_dim)
50 contiguous_list = [
51 chunk.contiguous() if hasattr(chunk, "is_contiguous") and not chunk.is_contiguous() else chunk
52 for chunk in scatter_list
53 ]
54 src = dist.get_global_rank(group, group_src)
55 if dist.get_group_rank(group, dist.get_rank()) == group_src:
56 dist.scatter(output, list(contiguous_list), src=src, group=group)
57 else:
58 dist.scatter(output, None, src=src, group=group)
59 return output
62def mesh_scatter_ragged(
63 output: Tensor,
64 scatter_list: Optional[Sequence[Tensor]],
65 mesh: DeviceMesh,
66 mesh_dim: int,
67 *,
68 group_src: int = 0,
69) -> Tensor:
70 """Scatter variable-length flat tensors with point-to-point communication.
72 Args:
73 output: Preallocated receive buffer for the current rank.
74 scatter_list: Source-rank tensors ordered by group rank. Non-source ranks
75 may pass ``None``.
76 mesh: Device mesh containing the communication group.
77 mesh_dim: Mesh dimension along which to scatter.
78 group_src: Source rank relative to the mesh-dimension group.
80 Returns:
81 The populated current-rank output buffer.
83 Raises:
84 ValueError: If the source rank or source scatter list is invalid.
85 """
86 _ensure_mesh_process_groups(mesh)
87 group = mesh.get_group(mesh_dim)
88 group_size = mesh.size(mesh_dim)
89 if group_src < 0 or group_src >= group_size:
90 raise ValueError(
91 f"group_src must be in [0, {group_size}), but got {group_src}"
92 )
94 group_rank = dist.get_group_rank(group, dist.get_rank())
95 source_global_rank = dist.get_global_rank(group, group_src)
96 if group_rank == group_src:
97 if scatter_list is None or len(scatter_list) != group_size:
98 raise ValueError(
99 "source scatter_list length must equal the mesh dimension size, "
100 f"got scatter_list={scatter_list!r}, group_size={group_size}"
101 )
102 output.copy_(scatter_list[group_src])
103 works = []
104 for destination_group_rank, chunk in enumerate(scatter_list):
105 if destination_group_rank == group_src:
106 continue
107 destination_global_rank = dist.get_global_rank(
108 group, destination_group_rank
109 )
110 works.append(
111 dist.isend(
112 chunk.contiguous(),
113 dst=destination_global_rank,
114 group=group,
115 )
116 )
117 for work in works:
118 work.wait()
119 return output
121 work = dist.irecv(
122 output,
123 src=source_global_rank,
124 group=group,
125 )
126 work.wait()
127 return output
130def mesh_broadcast(
131 tensor: Tensor,
132 mesh: DeviceMesh,
133 mesh_dim: int,
134 *,
135 group_src: int = 0,
136) -> Tensor:
137 """Broadcast a tensor along one mesh dimension (PyTorch ``mesh_broadcast`` parity)."""
138 _ensure_mesh_process_groups(mesh)
139 group = mesh.get_group(mesh_dim)
140 if hasattr(tensor, "is_contiguous") and not tensor.is_contiguous():
141 tensor = tensor.contiguous()
142 dist.broadcast(tensor, src=dist.get_global_rank(group, group_src), group=group)
143 return tensor