Coverage for  / home / jenkins / .local / lib / python3.10 / site-packages / hyper_parallel / core / dtensor / _mesh_layout.py: 99%

210 statements  

« prev     ^ index     » next       coverage.py v7.13.1, created at 2026-07-06 05:41 +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"""Internal layout helpers for DeviceMesh bookkeeping.""" 

16 

17import math 

18from dataclasses import dataclass 

19from typing import Any, Iterator, Optional, Union 

20 

21import numpy as np 

22 

23 

24IntTuple = Union[int, tuple["IntTuple", ...]] 

25 

26 

27def _is_int(value: Any) -> bool: 

28 return isinstance(value, int) and not isinstance(value, bool) 

29 

30 

31def _as_tuple(value: IntTuple) -> tuple[IntTuple, ...]: 

32 return value if isinstance(value, tuple) else (value,) 

33 

34 

35def _flatten_inttuple(value: IntTuple) -> tuple[int, ...]: 

36 if _is_int(value): 

37 return (value,) 

38 flattened: list[int] = [] 

39 for item in value: 

40 flattened.extend(_flatten_inttuple(item)) 

41 return tuple(flattened) 

42 

43 

44def _match_structure(shape: IntTuple, stride: IntTuple) -> bool: 

45 if _is_int(shape): 

46 return _is_int(stride) 

47 if not isinstance(shape, tuple) or not isinstance(stride, tuple) or len(shape) != len(stride): 

48 return False 

49 return all(_match_structure(sub_shape, sub_stride) for sub_shape, sub_stride in zip(shape, stride)) 

50 

51 

52def _numel(value: IntTuple) -> int: 

53 return int(np.prod(np.array(_flatten_inttuple(value), dtype=np.int64))) 

54 

55 

56def _contiguous_strides(mesh_shape: tuple[int, ...]) -> tuple[int, ...]: 

57 if len(mesh_shape) == 0: 

58 return () 

59 strides = [1] * len(mesh_shape) 

60 for idx in range(len(mesh_shape) - 2, -1, -1): 

61 strides[idx] = strides[idx + 1] * mesh_shape[idx + 1] 

62 return tuple(strides) 

63 

64 

65def _scale_inttuple(value: IntTuple, factor: int) -> IntTuple: 

66 if _is_int(value): 

67 return int(value) * factor 

68 return tuple(_scale_inttuple(item, factor) for item in value) 

69 

70 

71def _enumerate_offsets(shape: IntTuple, stride: IntTuple) -> list[int]: 

72 if _is_int(shape): 

73 return [i * int(stride) for i in range(shape)] 

74 

75 offsets = [0] 

76 for sub_shape, sub_stride in zip(_as_tuple(shape), _as_tuple(stride)): 

77 dim_offsets = _enumerate_offsets(sub_shape, sub_stride) 

78 offsets = [base + dim_offset for base in offsets for dim_offset in dim_offsets] 

79 return offsets 

80 

81 

82def _canonicalize_axis(shape, stride) -> tuple[tuple[int, ...], tuple[int, ...]]: 

83 """Normalize one logical axis into a flattened shape/stride pair.""" 

84 flat_shape = _flatten_inttuple(shape) 

85 flat_stride = _flatten_inttuple(stride if stride is not None else _contiguous_strides(flat_shape)) 

86 if len(flat_shape) != len(flat_stride): 

87 raise ValueError( 

88 f"shape and stride must have the same length, got {len(flat_shape)} and {len(flat_stride)}" 

89 ) 

90 

91 normalized_shape: list[int] = [] 

92 normalized_stride: list[int] = [] 

93 for size, step in zip(flat_shape, flat_stride): 

94 if size < 0: 

95 raise ValueError(f"shape entries must be non-negative, got {flat_shape}") 

96 if size == 1: 

97 continue 

98 normalized_shape.append(int(size)) 

99 normalized_stride.append(int(step)) 

100 

101 coalesced_shape: list[int] = [] 

102 coalesced_stride: list[int] = [] 

103 for size, step in zip(normalized_shape, normalized_stride): 

104 if coalesced_shape and coalesced_stride[-1] == step * size: 

105 coalesced_shape[-1] *= size 

106 coalesced_stride[-1] = step 

107 else: 

108 coalesced_shape.append(size) 

109 coalesced_stride.append(step) 

110 return tuple(coalesced_shape), tuple(coalesced_stride) 

111 

112 

113def _nested_from_flat(value: tuple[int, ...]) -> IntTuple: 

114 if len(value) == 1: 

115 return value[0] 

116 return tuple(value) 

117 

118 

119@dataclass(frozen=True) 

120class _FlatLayout: 

121 """Canonicalized layout for one logical DeviceMesh axis.""" 

122 

123 shape: tuple[int, ...] 

124 stride: tuple[int, ...] 

125 

126 def __init__(self, shape: IntTuple, stride: Optional[IntTuple] = None) -> None: 

127 """Canonicalize *shape* and *stride* into a frozen flat layout.""" 

128 flat_shape, flat_stride = _canonicalize_axis(shape, stride) 

129 object.__setattr__(self, "shape", flat_shape) 

130 object.__setattr__(self, "stride", flat_stride) 

131 

132 def numel(self) -> int: 

133 """Return the total number of elements in this layout.""" 

134 return math.prod(self.shape) if len(self.shape) > 0 else 1 

135 

136 def cosize(self) -> int: 

137 """Return the size of the smallest contiguous block containing all ranks.""" 

138 ranks = self.all_ranks_from_zero() 

139 return max(ranks) + 1 if ranks else 1 

140 

141 def check_sorted(self) -> bool: 

142 """Return True if strides are in descending order.""" 

143 return tuple(sorted(self.stride, reverse=True)) == self.stride 

144 

145 def check_orthogonal(self) -> bool: 

146 """Return True if each axis is independent (strides are orthogonal).""" 

147 if len(self.shape) < 2: 

148 return True 

149 stride, shape = zip(*sorted(zip(self.stride, self.shape), reverse=True)) 

150 return all( 

151 stride[idx] % (stride[idx + 1] * shape[idx + 1]) == 0 

152 for idx in range(len(stride) - 1) 

153 ) 

154 

155 def all_ranks_from_zero(self) -> list[int]: 

156 """List every rank offset assuming the base is zero.""" 

157 if len(self.shape) == 0: 

158 return [0] 

159 return [ 

160 int(sum(coord[dim] * self.stride[dim] for dim in range(len(self.shape)))) 

161 for coord in np.ndindex(self.shape) 

162 ] 

163 

164 

165class _MeshLayout: 

166 """Minimal layout helper for DeviceMesh slicing, flattening, and concatenation.""" 

167 

168 def __init__( 

169 self, 

170 shape_or_axes: Union[IntTuple, list[_FlatLayout], tuple[_FlatLayout, ...]], 

171 stride: Optional[IntTuple] = None, 

172 ) -> None: 

173 """Build a layout from a nested shape/stride pair or a list of flat axes.""" 

174 if stride is None and isinstance(shape_or_axes, (list, tuple)) and all( 

175 isinstance(axis, _FlatLayout) for axis in shape_or_axes 

176 ): 

177 axes = list(shape_or_axes) 

178 shape = tuple(_nested_from_flat(axis.shape) for axis in axes) 

179 stride = tuple(_nested_from_flat(axis.stride) for axis in axes) 

180 else: 

181 shape = shape_or_axes 

182 if not _match_structure(shape, stride): 

183 raise ValueError(f"shape {shape} and stride {stride} do not match") 

184 self.shape: IntTuple = shape 

185 self.stride: IntTuple = stride 

186 

187 @classmethod 

188 def from_sizes_strides( 

189 cls, 

190 sizes: tuple[int, ...], 

191 strides: Optional[tuple[int, ...]] = None, 

192 ) -> "_MeshLayout": 

193 """Create a layout from flat sizes and optional strides.""" 

194 if strides is None: 

195 strides = _contiguous_strides(sizes) 

196 return cls(sizes, strides) 

197 

198 @property 

199 def sizes(self) -> IntTuple: 

200 """Return the (possibly nested) shape tuple.""" 

201 return self.shape 

202 

203 @property 

204 def strides(self) -> IntTuple: 

205 """Return the (possibly nested) stride tuple.""" 

206 return self.stride 

207 

208 @property 

209 def axes(self) -> tuple[_FlatLayout, ...]: 

210 """Return each top-level dimension as a separate flat layout.""" 

211 return tuple(self[idx].collapse() for idx in range(len(self))) 

212 

213 def __len__(self) -> int: 

214 """Return the number of top-level dimensions.""" 

215 return len(self.shape) if isinstance(self.shape, tuple) else 1 

216 

217 def __iter__(self) -> Iterator["_MeshLayout"]: 

218 """Yield each top-level dimension as its own layout.""" 

219 for idx in range(len(self)): 

220 yield self[idx] 

221 

222 def __getitem__(self, idx: int) -> "_MeshLayout": 

223 """Select a single top-level dimension by index.""" 

224 if isinstance(self.shape, tuple): 

225 if idx < -len(self.shape) or idx >= len(self.shape): 

226 raise IndexError( 

227 f"Dim {idx} is out of range for layout with {len(self.shape)} dimensions." 

228 ) 

229 return _MeshLayout(self.shape[idx], self.stride[idx]) 

230 if idx not in (0, -1): 

231 raise IndexError("Dim is out of range for 1D layout.") 

232 return _MeshLayout(self.shape, self.stride) 

233 

234 def __eq__(self, other: object) -> bool: 

235 """Return True if *other* has the same shape and stride.""" 

236 if not isinstance(other, _MeshLayout): 

237 return False 

238 return self.shape == other.shape and self.stride == other.stride 

239 

240 def __repr__(self) -> str: 

241 """Return a printable representation of this layout.""" 

242 return f"_MeshLayout(shape={self.shape}, stride={self.stride})" 

243 

244 def numel(self) -> int: 

245 """Return the total number of elements in this layout.""" 

246 return _numel(self.shape) 

247 

248 @property 

249 def top_level_sizes(self) -> tuple[int, ...]: 

250 """Return the element count of each top-level dimension.""" 

251 return tuple(self[idx].numel() for idx in range(len(self))) 

252 

253 def all_ranks_from_zero(self) -> list[int]: 

254 """List every rank offset assuming the base is zero.""" 

255 return _enumerate_offsets(self.shape, self.stride) 

256 

257 def check_non_overlap(self) -> bool: 

258 """Return True if no rank appears more than once in this layout.""" 

259 ranks = self.all_ranks_from_zero() 

260 return len(ranks) == len(set(ranks)) 

261 

262 def coalesce(self) -> "_MeshLayout": 

263 """Merge adjacent contiguous axes while preserving the represented layout.""" 

264 if _is_int(self.shape): 

265 return self 

266 

267 coalesced_shapes: list[IntTuple] = [] 

268 coalesced_strides: list[IntTuple] = [] 

269 for shape, stride in zip(self.shape, self.stride): 

270 child = _MeshLayout(shape, stride).coalesce() 

271 coalesced_shapes.append(child.shape) 

272 coalesced_strides.append(child.stride) 

273 

274 merged_shapes: list[IntTuple] = [] 

275 merged_strides: list[IntTuple] = [] 

276 for shape, stride in zip(coalesced_shapes, coalesced_strides): 

277 can_merge_scalar_axis = ( 

278 bool(merged_shapes) 

279 and _is_int(merged_shapes[-1]) 

280 and _is_int(merged_strides[-1]) 

281 and _is_int(shape) 

282 and _is_int(stride) 

283 ) 

284 if can_merge_scalar_axis and merged_strides[-1] == stride * shape: 

285 merged_shapes[-1] *= shape 

286 merged_strides[-1] = stride 

287 else: 

288 merged_shapes.append(shape) 

289 merged_strides.append(stride) 

290 

291 if len(merged_shapes) == 1: 

292 return _MeshLayout(merged_shapes[0], merged_strides[0]) 

293 return _MeshLayout(tuple(merged_shapes), tuple(merged_strides)) 

294 

295 def composition(self, layout: "_MeshLayout") -> "_MeshLayout": 

296 """Compose *layout* on top of this axis (unflatten with scaled strides).""" 

297 if not _is_int(self.stride): 

298 raise NotImplementedError( 

299 "Currently, _unflatten only supports unflattening a mesh dim with scalar stride." 

300 ) 

301 return _MeshLayout(layout.shape, _scale_inttuple(layout.stride, int(self.stride))) 

302 

303 def nest(self) -> "_MeshLayout": 

304 """Wrap all dimensions into a single outer dimension.""" 

305 if len(self) == 1: 

306 return self 

307 return _MeshLayout((self.shape,), (self.stride,)) 

308 

309 def splice(self, start: int, end: int, layout: "_MeshLayout") -> "_MeshLayout": 

310 """Replace dimensions [start, end) with the axes of *layout*.""" 

311 sizes = list(_as_tuple(self.shape)) 

312 strides = list(_as_tuple(self.stride)) 

313 sizes[start:end] = list(_as_tuple(layout.shape)) 

314 strides[start:end] = list(_as_tuple(layout.stride)) 

315 if len(sizes) == 1: 

316 return _MeshLayout(sizes[0], strides[0]) 

317 return _MeshLayout(tuple(sizes), tuple(strides)) 

318 

319 def collapse(self) -> _FlatLayout: 

320 """Flatten this layout into a single canonicalized flat layout.""" 

321 return _FlatLayout(self.shape, self.stride) 

322 

323 def remap_to_numpy(self, rank_map: Any) -> np.ndarray: 

324 """Materialize this layout as a dense numpy mesh over the provided rank map.""" 

325 rank_map_np = np.asarray(rank_map).reshape(-1) 

326 base_offsets = self.all_ranks_from_zero() 

327 if len(base_offsets) == 0: 

328 raise ValueError("Cannot remap an empty layout.") 

329 

330 groups: list[list[int]] = [] 

331 used: set[int] = set() 

332 world_size = rank_map_np.shape[0] 

333 

334 for anchor in range(world_size): 

335 if anchor in used: 

336 continue 

337 group = [anchor + offset for offset in base_offsets] 

338 if any(index >= world_size for index in group): 

339 continue 

340 if any(index in used for index in group): 

341 continue 

342 groups.append(group) 

343 used.update(group) 

344 

345 if len(used) != world_size: 

346 raise ValueError( 

347 f"Layout {self} does not form a full partition over rank_map with world size {world_size}." 

348 ) 

349 

350 remapped = rank_map_np[np.array(groups, dtype=np.int64)] 

351 remapped = remapped.reshape((len(groups),) + self.top_level_sizes) 

352 return remapped