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

93 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""" 

16Distributed implementation for Conv3d operator. 

17""" 

18 

19from typing import Callable, Optional, Tuple 

20 

21from hyper_parallel.core.dtensor.layout import Layout 

22from .parallel_ops import DistributedOp 

23 

24 

25def _normalize_conv3d_args(input_tensor, weight, bias=None, stride=1, padding=0, dilation=1, groups=1): 

26 return (input_tensor, weight, bias, stride, padding, dilation, groups), {} 

27 

28 

29class Conv3dDistributedOp(DistributedOp): 

30 """ 

31 Distributed implementation for torch.nn.functional.conv3d. 

32 Supports Data Parallel, Tensor Parallel (Column/Row), and Spatial Parallel. 

33 """ 

34 

35 def __init__(self, op_name): 

36 super().__init__(op_name) 

37 self._allow_partial_inputs = False 

38 

39 def _validate_row_parallelism(self, in_map, w_map, groups): 

40 """ 

41 Validate constraints for Row Parallelism. 

42 """ 

43 # 1. Handle Groups Constraint for Row Parallelism 

44 if groups > 1: 

45 if in_map[1] != "None" or w_map[1] != "None": 

46 # Row Parallelism with groups > 1 requires advanced group-wise communication 

47 raise ValueError(f"For {self.op_name}, Sharding on C_in with groups > 1 is not supported.") 

48 

49 # 2. Check Row Parallelism (Sharding on Channel In) 

50 # Input: (N, C_in, D, H, W), Weight: (C_out, C_in/groups, kD, kH, kW) 

51 if in_map[1] != "None": 

52 if in_map[1] != w_map[1]: 

53 raise ValueError(f"For {self.op_name}, Input C_in and Weight C_in must be sharded on the same axis.") 

54 

55 def _validate_column_parallelism(self, w_layout, b_layout, groups): 

56 """ 

57 Validate constraints for Column Parallelism. 

58 """ 

59 w_map = w_layout.alias_tensor_map 

60 w_map_0 = w_map[0] 

61 

62 if w_map_0 != "None": 

63 # Check bias alignment 

64 if b_layout is not None: 

65 b_map = b_layout.alias_tensor_map 

66 b_map_0 = b_map[0] 

67 if w_map_0 != b_map_0: 

68 raise ValueError( 

69 f"For {self.op_name}, Weight C_out and Bias C_out must be sharded on the same axis." 

70 ) 

71 

72 # Check groups divisibility for Column Parallelism 

73 if groups > 1: 

74 dev_num = 1 

75 axes = w_map_0 if isinstance(w_map_0, tuple) else (w_map_0,) 

76 for axis_name in axes: 

77 dev_num *= w_layout.mesh.get_device_num_along_axis(axis_name) 

78 

79 if groups % dev_num != 0: 

80 raise ValueError( 

81 f"For {self.op_name}, groups ({groups}) " 

82 f"must be divisible by tp_size ({dev_num})." 

83 ) 

84 

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

86 """ 

87 Preprocess arguments for Conv3d operator. 

88 

89 Args: 

90 args (tuple): Conv3d positional arguments. 

91 kwargs (dict): Conv3d keyword arguments. 

92 

93 Returns: 

94 tuple: (local_args, local_kwargs, cache_values) 

95 """ 

96 args, _ = _normalize_conv3d_args(*args, **kwargs) 

97 input_tensor, weight, bias, stride, padding, dilation, groups = args 

98 local_args = ( 

99 input_tensor.to_local(), 

100 weight.to_local(), 

101 bias.to_local() if hasattr(bias, '_layout') else bias, 

102 stride, 

103 padding, 

104 dilation, 

105 groups, 

106 ) 

107 local_kwargs = {} 

108 cache_values = [ 

109 input_tensor.layout, 

110 weight.layout, 

111 bias.layout if hasattr(bias, '_layout') else None, 

112 stride, 

113 padding, 

114 dilation, 

115 groups, 

116 ] 

117 return local_args, local_kwargs, cache_values 

118 

119 def infer_layout(self, cache_values: list) -> Tuple[tuple, None]: # pylint: disable=W0221 

120 """ 

121 Infer output layout for Conv3d operator. 

122 

123 Rules: 

124 1. Input and weight must not have Partial status. 

125 2. Input and weight must both be 5D. 

126 3. Input C_in and weight C_in sharding must match for row parallelism. 

127 4. Sharding C_in with groups > 1 is not supported. 

128 5. Bias C_out sharding must match weight C_out sharding. 

129 6. Output layout inherits N/D/H/W sharding from input and C_out sharding from weight. 

130 7. Row parallelism marks output as Partial('sum') on the C_in mesh axis. 

131 

132 Args: 

133 cache_values (list): [input_layout, weight_layout, bias_layout_or_None, 

134 stride, padding, dilation, groups] 

135 

136 Returns: 

137 tuple: ((output_layout,), None) 

138 

139 Raises: 

140 ValueError: If layouts are missing, partial, malformed, or violate Conv3d 

141 sharding constraints. 

142 """ 

143 

144 in_layout, w_layout, b_layout = cache_values[0], cache_values[1], cache_values[2] 

145 groups = cache_values[6] 

146 

147 if not in_layout or not w_layout: 

148 raise ValueError(f"For {self.op_name}, Requires at least input and weight layouts.") 

149 

150 self._check_partial_inputs([in_layout, w_layout]) 

151 

152 if b_layout is not None: 

153 self._check_partial_inputs([b_layout]) 

154 

155 if in_layout.mesh_shape != w_layout.mesh_shape: 

156 raise ValueError( 

157 f"For {self.op_name}, input and weight must have the same mesh_shape, " 

158 f"but got input: {in_layout.mesh_shape} and weight: {w_layout.mesh_shape}" 

159 ) 

160 if b_layout is not None and b_layout.mesh_shape != in_layout.mesh_shape: 

161 raise ValueError( 

162 f"For {self.op_name}, bias and input must have the same mesh_shape, " 

163 f"but got bias: {b_layout.mesh_shape} and input: {in_layout.mesh_shape}" 

164 ) 

165 

166 in_map = in_layout.alias_tensor_map 

167 w_map = w_layout.alias_tensor_map 

168 

169 # Validate dimensions 

170 if len(in_map) != 5 or len(w_map) != 5: 

171 raise ValueError(f"For {self.op_name}, Input and weight must be 5D.") 

172 

173 # Delegate validation to helper methods to reduce cyclomatic complexity 

174 self._validate_row_parallelism(in_map, w_map, groups) 

175 self._validate_column_parallelism(w_layout, b_layout, groups) 

176 

177 # Construct Output Map (N, C_out, D_out, H_out, W_out) 

178 out_map = [ 

179 in_map[0], # N 

180 w_map[0], # C_out 

181 in_map[2], # D 

182 in_map[3], # H 

183 in_map[4] # W 

184 ] 

185 

186 # Build Layout 

187 output_layout = Layout( 

188 mesh_shape=in_layout.mesh_shape, 

189 alias_name=in_layout.alias_name, 

190 rank_list=in_layout.rank_list, 

191 ) 

192 output_layout = output_layout(*tuple(out_map)) 

193 

194 # Set Partial status for Row Parallelism 

195 if in_map[1] != "None": 

196 axes = in_map[1] if isinstance(in_map[1], tuple) else (in_map[1],) 

197 for axis in axes: 

198 output_layout.set_partial_by_dev_axis(axis, "sum") 

199 

200 return (output_layout,), None 

201 

202 def get_expand_impl(self, func: Optional[Callable], infer_result: tuple, # pylint: disable=W0221 

203 cache_values: list) -> Optional[Callable]: 

204 """ 

205 Get expand implementation for the operator. 

206 Intercepts the execution to handle Grouped Convolution with Column Parallelism. 

207 """ 

208 w_layout = cache_values[1] 

209 w_map = w_layout.alias_tensor_map 

210 w_map_0 = w_map[0] 

211 

212 # If Weight is NOT sharded on C_out (dim=0), native conv3d works fine. 

213 if w_map_0 == "None": 

214 return None 

215 

216 parsed_groups = cache_values[6] 

217 if parsed_groups == 1: 

218 return None 

219 

220 mesh = w_layout.mesh 

221 axes = w_map_0 if isinstance(w_map_0, tuple) else (w_map_0,) 

222 dev_num = 1 

223 local_rank = 0 

224 for axis_name in axes: 

225 axis_size = mesh.get_device_num_along_axis(axis_name) 

226 dev_num *= axis_size 

227 local_rank = local_rank * axis_size + mesh.get_local_rank(axis_name) 

228 

229 # Pre-calculate local groups and group boundaries for the current device ahead of time. 

230 # This hoisting optimization avoids redundant calculations during every forward pass. 

231 local_groups = parsed_groups // dev_num 

232 start_group = local_rank * local_groups 

233 end_group = start_group + local_groups 

234 

235 def distributed_conv3d_impl(input_tensor, weight_tensor, bias=None, stride=1, padding=0, dilation=1, groups=1): 

236 # --- Handling Groups > 1 with Column Parallelism --- 

237 # Calculate the input channel chunk size 

238 c_in = input_tensor.shape[1] 

239 c_in_per_group = c_in // groups 

240 

241 # Map the pre-calculated groups to the actual input channels 

242 # Uses start_group and end_group captured from the outer scope 

243 start_channel = start_group * c_in_per_group 

244 end_channel = end_group * c_in_per_group 

245 

246 # Slice the replicated input to match the local groups 

247 sliced_input = input_tensor[:, start_channel:end_channel, ...] 

248 

249 # Execute native conv3d with the sliced input and adjusted local groups 

250 return func(sliced_input, weight_tensor, bias, stride, padding, dilation, local_groups) 

251 

252 return distributed_conv3d_impl