Coverage for  / home / jenkins / .local / lib / python3.10 / site-packages / hyper_parallel / core / shard / ops / parallel_norm.py: 76%

84 statements  

« prev     ^ index     » next       coverage.py v7.13.1, created at 2026-07-06 05:41 +0800

1# Copyright 2025-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""" 

16Distributed implementation for Norm operators (RmsNorm, layer_norm). 

17""" 

18 

19from typing import Tuple 

20 

21from hyper_parallel.core.dtensor.layout import Layout 

22from .parallel_ops import DistributedOp 

23 

24 

25def _normalize_rmsnorm_args(x, gamma, epsilon=1e-6): 

26 """Normalize RmsNorm args to positional form. 

27 

28 MindSpore Primitive RmsNorm receives (x, gamma, epsilon) as positional arguments. 

29 """ 

30 return (x, gamma, epsilon), {} 

31 

32 

33def _normalize_layernorm_args(input_tensor, normalized_shape, weight=None, bias=None, eps=1e-5): 

34 """Normalize layer_norm args to positional form. 

35 

36 torch.nn.functional.layer_norm(input_tensor, normalized_shape, weight=None, bias=None, eps=1e-5) 

37 has no keyword-only parameters, so everything stays positional. 

38 """ 

39 return (input_tensor, normalized_shape, weight, bias, eps), {} 

40 

41 

42class NormDistributedOp(DistributedOp): 

43 """Distributed implementation for RmsNorm operator.""" 

44 

45 def preprocess(self, args: tuple, kwargs: dict) -> tuple: 

46 """ 

47 Preprocess arguments for RmsNorm operator. 

48 

49 Args: 

50 args (tuple): Positional arguments (x, gamma) where both are DTensors. 

51 kwargs (dict): Keyword arguments (none expected). 

52 

53 Returns: 

54 tuple: (local_args, local_kwargs, cache_values) 

55 """ 

56 args, kwargs = _normalize_rmsnorm_args(*args, **kwargs) 

57 x, gamma, epsilon = args 

58 local_args = (x.to_local(), gamma.to_local(), epsilon) 

59 local_kwargs = {} 

60 cache_values = [x.layout, gamma.layout] 

61 return local_args, local_kwargs, cache_values 

62 

63 def infer_layout(self, cache_values: list) -> Tuple[tuple, None]: 

64 """ 

65 Infer output layouts for RmsNorm operator. 

66 

67 Rules: 

68 1. Inputs must not have Partial status. 

69 2. x and gamma must share the same mesh_shape. 

70 3. Dimensions being normalized (the last len(gamma_tensor_map) dims of x) 

71 must not be sharded. 

72 4. The sharding of the normalized dimensions of x must match gamma's sharding. 

73 5. Output layout keeps sharding on non-normalized dims and replicates 

74 on normalized dims. 

75 

76 Args: 

77 cache_values (list): [x_layout, gamma_layout] 

78 

79 Returns: 

80 tuple: ((x_layout, out_layout), None) 

81 

82 Raises: 

83 ValueError: If any rule above is violated. 

84 """ 

85 if len(cache_values) < 2: 

86 raise ValueError( 

87 f"For {self.op_name}, cache_values size {len(cache_values)} is less than 2." 

88 ) 

89 x_layout = cache_values[0] 

90 gamma_layout = cache_values[1] 

91 # Check partial inputs 

92 if not self._allow_partial_inputs: 

93 self._check_partial_inputs([x_layout, gamma_layout]) 

94 x_mesh_shape = x_layout.mesh_shape 

95 gamma_mesh_shape = gamma_layout.mesh_shape 

96 if x_mesh_shape != gamma_mesh_shape: 

97 raise ValueError(f"{self.op_name} inputs must have same mesh_shape") 

98 x_alias_map = x_layout.alias_tensor_map 

99 gamma_alias_map = gamma_layout.alias_tensor_map 

100 if len(gamma_alias_map) > len(x_alias_map): 

101 raise ValueError( 

102 f"For {self.op_name}, gamma ndim {len(gamma_alias_map)} cannot exceed " 

103 f"input ndim {len(x_alias_map)}." 

104 ) 

105 begin_norm_axis = len(x_alias_map) - len(gamma_alias_map) 

106 for alias_entry in x_alias_map[begin_norm_axis:]: 

107 entries = alias_entry if isinstance(alias_entry, tuple) else (alias_entry,) 

108 for name in entries: 

109 if name == "None": 

110 continue 

111 axis_idx = x_layout.alias_name.index(name) 

112 if x_mesh_shape[axis_idx] > 1: 

113 raise ValueError(f"{self.op_name} is disabled to support the splitting after " 

114 f"begin_norm_axis {begin_norm_axis} for input 0.") 

115 if x_alias_map[begin_norm_axis:] != gamma_alias_map: 

116 raise ValueError(f"For {self.op_name}, input sharding from begin_norm_axis " 

117 f"{begin_norm_axis}, {x_alias_map[begin_norm_axis:]}, should equal " 

118 f"gamma sharding {gamma_alias_map}.") 

119 output_layout = Layout( 

120 mesh_shape=x_layout.mesh_shape, 

121 alias_name=x_layout.alias_name, 

122 rank_list=x_layout.rank_list 

123 ) 

124 output_map = x_alias_map[:begin_norm_axis] + ("None",) * len(gamma_alias_map) 

125 out_layout = output_layout(*output_map) 

126 return ((x_layout, out_layout), None) 

127 

128 

129class LayerNormDistributedOp(DistributedOp): 

130 """Distributed implementation for torch.nn.functional.layer_norm.""" 

131 

132 def preprocess(self, args: tuple, kwargs: dict) -> tuple: 

133 """ 

134 Preprocess arguments for layer_norm operator. 

135 

136 Args: 

137 args (tuple): Positional arguments (input, normalized_shape, weight, bias, eps). 

138 kwargs (dict): Keyword arguments (none expected for this functional API). 

139 

140 Returns: 

141 tuple: (local_args, local_kwargs, cache_values) 

142 """ 

143 args, kwargs = _normalize_layernorm_args(*args, **kwargs) 

144 input_tensor, normalized_shape, weight, bias, eps = args 

145 

146 # Normalize normalized_shape: int → (int,), list → tuple 

147 if isinstance(normalized_shape, int): 

148 normalized_shape = (normalized_shape,) 

149 elif isinstance(normalized_shape, list): 

150 normalized_shape = tuple(normalized_shape) 

151 

152 local_args = [ 

153 input_tensor.to_local(), 

154 normalized_shape, 

155 weight.to_local() if weight is not None and hasattr(weight, 'to_local') else weight, 

156 bias.to_local() if bias is not None and hasattr(bias, 'to_local') else bias, 

157 eps, 

158 ] 

159 local_kwargs = {} 

160 

161 cache_values = [input_tensor.layout, normalized_shape] 

162 return tuple(local_args), local_kwargs, cache_values 

163 

164 def infer_layout(self, cache_values: list) -> Tuple[tuple, None]: 

165 """ 

166 Infer output layout for layer_norm operator. 

167 

168 Rules: 

169 1. Input must not have Partial status. 

170 2. normalized_shape must be int, list, or tuple. 

171 3. normalized_shape dimensions must be ≤ input ndim. 

172 4. All dimensions in normalized_shape must be unsharded. 

173 5. Output layout is identical to input layout. 

174 

175 Args: 

176 cache_values (list): [input_layout, normalized_shape] 

177 

178 Returns: 

179 tuple: ((output_layout,), None) 

180 

181 Raises: 

182 ValueError: If any rule above is violated. 

183 """ 

184 input_layout = cache_values[0] 

185 if input_layout is None: 

186 raise ValueError(f"{self.op_name} requires a valid input tensor layout.") 

187 normalized_shape = cache_values[1] 

188 # Check partial inputs 

189 if not self._allow_partial_inputs: 

190 self._check_partial_inputs([input_layout]) 

191 

192 if normalized_shape is None: 

193 raise ValueError(f"{self.op_name} requires normalized_shape.") 

194 

195 if not isinstance(normalized_shape, tuple): 

196 raise ValueError(f"normalized_shape must be int, list, or tuple, got {type(normalized_shape)}") 

197 

198 in_alias_map = input_layout.alias_tensor_map 

199 input_ndim = len(in_alias_map) 

200 norm_ndim = len(normalized_shape) 

201 

202 if norm_ndim > input_ndim: 

203 raise ValueError( 

204 f"normalized_shape {normalized_shape} (dims={norm_ndim}) is larger than input ndim={input_ndim}." 

205 ) 

206 

207 # The last `norm_ndim` dimensions are going to be normalized 

208 dims_to_normalize = list(range(input_ndim - norm_ndim, input_ndim)) 

209 

210 # All normalized dims must be unsharded 

211 for dim in dims_to_normalize: 

212 alias_entry = in_alias_map[dim] 

213 entries = alias_entry if isinstance(alias_entry, tuple) else (alias_entry,) 

214 for name in entries: 

215 if name == "None": 

216 continue 

217 raise ValueError( 

218 f"Operation {self.op_name}: Cannot perform sharding on normalized dimension {dim}, " 

219 f"but found sharding assignment: {in_alias_map[dim]}" 

220 ) 

221 

222 output_layout = Layout( 

223 mesh_shape=input_layout.mesh_shape, 

224 alias_name=input_layout.alias_name, 

225 rank_list=input_layout.rank_list 

226 ) 

227 output_layout = output_layout(*in_alias_map) 

228 return ((output_layout,), None)