Coverage for  / home / jenkins / .local / lib / python3.10 / site-packages / hyper_parallel / auto_parallel / sapp_ppb / utils / layer.py: 99%

130 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"""Layer descriptor used throughout SAPP-PPB: time, memory and recomputation metadata.""" 

16import json 

17import os 

18from enum import Enum 

19from typing import Any, Dict, Optional 

20 

21import hyper_parallel.auto_parallel.sapp_ppb.utils.recompute as Recompute 

22from hyper_parallel.auto_parallel.sapp_ppb.utils.computation_analyzer import ComputationAnalyzer 

23from hyper_parallel.auto_parallel.sapp_ppb.utils.logger import logger 

24 

25 

26class Layer: 

27 """ 

28 Mandatory parameter: 

29 name_ (str): name of the layer 

30 type_ (LayerType): type of the layer 'HEAD', 'BODY', 'TAIL' 

31 nb_layer_ (int): number of layer to schedule 

32 time_ (float): total time that a layer take 

33 

34 Optional (auto-compute) parameter: 

35 forward_time_ (float): forward time for the layer (1/3 of time) 

36 backward_time_rec_ (dict[Recompute.Type, float]): backward time (2/3 of time) per recomputation 

37 recompute_considered_: dict[Recompute.Type, bool] set recomputations when considered 

38 

39 Optional memory parameter (for recompute): 

40 memory_parameter_ (float): memory used by the layer (all kind) 

41 memory_activation_rec_ (dict[Recompute.Type, float]): activation memory per recomputation 

42 

43 Not manage yet parameter (for multimodal): 

44 model_name_ (str): name of the model the layer be part of (for multimodal) 

45 """ 

46 

47 type_enum = Enum("LayerType", ["UNKNOWN", "HEAD", "BODY", "TAIL"]) 

48 backward_default_ratio = 2 # of forward time 

49 name_: str 

50 model_name_: str 

51 type_: type_enum 

52 nb_layer_: int 

53 time_: float 

54 memory_parameter_: float 

55 memory_activation_rec_: dict[Recompute.TYPE, float] 

56 forward_time_: float 

57 backward_time_rec_: dict[Recompute.TYPE, float] 

58 backward_coef_rec_: dict[Recompute.TYPE, float] 

59 recompute_considered_: dict[Recompute.TYPE, bool] 

60 

61 def __init__( 

62 self, 

63 model_name: str = "misc", 

64 name: str = "misc", 

65 ltype: type_enum = type_enum.UNKNOWN, 

66 nb_layer: int = 0, 

67 time: float = 0.0, 

68 forward_time: float = 0.0, 

69 backward_time_rec: Optional[Dict[Recompute.TYPE, float]] = None, 

70 backward_coef_rec: Optional[Dict[Recompute.TYPE, float]] = None, 

71 memory_parameter: float = 0.0, 

72 memory_activation_rec: Optional[Dict[Recompute.TYPE, float]] = None, 

73 ) -> None: 

74 """Build a :class:`Layer` record. 

75 

76 Args: 

77 model_name (str): Name of the owning model (``"misc"`` for ad-hoc entries). 

78 name (str): Layer name. 

79 ltype (Layer.type_enum): HEAD / BODY / TAIL / UNKNOWN classification. 

80 nb_layer (int): Number of such layers present in the model. 

81 time (float): Total time (forward + backward) for one layer. 

82 forward_time (float, optional): Optional forward time, otherwise derived from ``time``. Default: ``0.0``. 

83 backward_time_rec (Optional[Dict[Recompute.TYPE, float]], optional): Per-recomputation-type backward 

84 times (``None`` -> zeros). Default: ``None``. 

85 backward_coef_rec (Optional[Dict[Recompute.TYPE, float]], optional): Per-recomputation-type overhead 

86 coefficients (``None`` -> zeros). Default: ``None``. 

87 memory_parameter (float, optional): Parameter memory in MB. Default: ``0.0``. 

88 memory_activation_rec (Optional[Dict[Recompute.TYPE, float]], optional): Per-recomputation-type 

89 activation memory (``None`` -> zeros). Default: ``None``. 

90 """ 

91 if backward_time_rec is None: 

92 backward_time_rec = {r: 0 for r in Recompute.TYPE} 

93 if backward_coef_rec is None: 

94 backward_coef_rec = {r: 0 for r in Recompute.TYPE} 

95 if memory_activation_rec is None: 

96 memory_activation_rec = {r: 0.0 for r in Recompute.TYPE} 

97 self.name_ = name 

98 self.model_name_ = model_name 

99 self.type_ = ltype 

100 self.nb_layer_ = nb_layer 

101 self.time_ = time 

102 self.memory_activation_rec_ = memory_activation_rec 

103 self.memory_parameter_ = memory_parameter 

104 self.backward_time_rec_ = backward_time_rec 

105 self.backward_coef_rec_ = backward_coef_rec 

106 self.forward_time_ = forward_time 

107 self.recompute_considered_ = self.find_recompute_considered() 

108 self.compute_internal_time() 

109 

110 def __str__(self) -> str: 

111 """Return a multi-line, human-readable description of the layer.""" 

112 result = "Layer Description:\n" 

113 result += " name = " + self.name_ + "\n" 

114 result += " model_name = " + str(self.model_name_) + "\n" 

115 result += " type = " + self.type_.name + "\n" 

116 result += " nb_layer = " + str(self.nb_layer_) + "\n" 

117 result += " time = " + str(self.time_) + "\n" 

118 result += " memory_parameter = " + str(self.memory_parameter_) + "\n" 

119 for r in Recompute.TYPE: 

120 if self.recompute_considered_[r]: 

121 result += " " + Recompute.JSON_MEMORY_NAME[r] + " = " 

122 result += str(self.memory_activation_rec_[r]) + "\n" 

123 result += " forward_time = " 

124 result += str(self.forward_time_) + "\n" 

125 for r in Recompute.TYPE: 

126 if self.recompute_considered_[r]: 

127 result += " " + Recompute.JSON_TIME_NAME[r] + " = " 

128 result += str(self.backward_time_rec_[r]) + "\n" 

129 return result 

130 

131 def dump(self, dump_file: str) -> None: 

132 """Dump the layer to ``dump_file`` as JSON (currently a placeholder).""" 

133 logger.error("dump file (%s) Not implemented yet!!!", dump_file) 

134 

135 def to_json(self) -> None: 

136 """Generate the JSON representation of the layer (currently a placeholder).""" 

137 logger.error("Not implemented yet!!!") 

138 

139 def find_recompute_considered(self) -> Dict[Recompute.TYPE, bool]: 

140 """Return which recomputation types have valid activation-memory data.""" 

141 recompute_considered = {rec: False for rec in Recompute.TYPE} 

142 

143 for rec in Recompute.TYPE: 

144 if self.memory_activation_rec_[rec] is not None: 

145 recompute_considered[rec] = True 

146 

147 return recompute_considered 

148 

149 def compute_internal_time( 

150 self, 

151 back_ratio: float = backward_default_ratio, 

152 force_fb: bool = False, 

153 ) -> None: 

154 """Derive forward/backward times from ``time_`` if not already set.""" 

155 if force_fb or self.forward_time_ is None: 

156 self.forward_time_ = self.time_ 

157 self.backward_time_ = back_ratio * self.time_ 

158 

159 for rec in Recompute.TYPE: 

160 if self.recompute_considered_[rec]: 

161 if ( 

162 self.backward_time_rec_[rec] is None 

163 or self.backward_time_rec_[rec] == 0 

164 ): 

165 if self.backward_coef_rec_[rec] is None: 

166 self.backward_time_rec_[rec] = ( 

167 1 + Recompute.DEFAULT_COEF[rec] 

168 ) * self.backward_time_ 

169 else: 

170 self.backward_time_rec_[rec] = ( 

171 1 + self.backward_coef_rec_[rec] 

172 ) * self.backward_time_ 

173 

174 def update_internal_time_for_seqpp( 

175 self, 

176 back_ratio: float = backward_default_ratio, 

177 force_fb: bool = False, 

178 ) -> None: 

179 """Adjust ``forward_time_``/``backward_time_`` for the sequence-pipeline mode.""" 

180 if force_fb or self.forward_time_ is None: 

181 self.forward_time_ = (1 - back_ratio) * self.time_ 

182 self.backward_time_ = back_ratio * self.time_ 

183 

184 for rec in Recompute.TYPE: 

185 if self.recompute_considered_[rec]: 

186 if self.backward_coef_rec_[rec] is None: 

187 self.backward_time_rec_[rec] = ( 

188 1 + Recompute.DEFAULT_COEF[rec] 

189 ) * self.backward_time_ 

190 else: 

191 self.backward_time_rec_[rec] = ( 

192 1 + self.backward_coef_rec_[rec] 

193 ) * self.backward_time_ 

194 

195 def compute_timer( 

196 self, timeline_folder: str = "./timeline", tmp_layer_info: Optional[dict] = None 

197 ) -> None: 

198 """Populate ``time_`` from profiling timelines stored in ``timeline_folder``.""" 

199 layer_time = ComputationAnalyzer( 

200 timeline_folder, 

201 self.model_name_, 

202 num_of_micro_batch=0, 

203 layer_list=tmp_layer_info, 

204 ) 

205 self.time_ = layer_time.layer_with_cost_list.get(self.name_) 

206 self.compute_internal_time(force_fb=True) 

207 

208 def compute_memory(self, memory_folder: str = "./memory") -> None: 

209 """Compute the memory information from ``memory_folder`` dry-run logs (placeholder).""" 

210 logger.error( 

211 "compute_memory (%s) Not implemented yet!!!", memory_folder 

212 ) 

213 

214 

215# Helper functions on layer list 

216 

217 

218def generate_layers_list(layer_folder: str, model_name: str) -> list[Layer]: 

219 """ "Parse layer_folder/model_name.json to generate a list of layer""" 

220 layers = [] 

221 json_layer = os.path.join(layer_folder, model_name + ".json") 

222 with open(json_layer, encoding="utf-8") as json_file: 

223 layer_data_json = json.load(json_file) 

224 if "layers_description" in layer_data_json: 

225 for layer_data in layer_data_json["layers_description"]: 

226 new_layer = Layer( 

227 name=layer_data["name"], 

228 ltype=Layer.type_enum[layer_data["type"]], 

229 nb_layer=layer_data["nb_layer"], 

230 time=layer_data["time"], 

231 model_name=layer_data.get("model_name"), 

232 forward_time=layer_data.get("forward_time"), 

233 backward_time_rec={ 

234 r: layer_data.get(Recompute.JSON_TIME_NAME[r]) 

235 for r in Recompute.TYPE 

236 }, 

237 backward_coef_rec={ 

238 r: layer_data.get(Recompute.JSON_COEF_NAME[r]) 

239 for r in Recompute.TYPE 

240 }, 

241 memory_activation_rec={ 

242 r: layer_data.get(Recompute.JSON_MEMORY_NAME[r]) 

243 for r in Recompute.TYPE 

244 }, 

245 memory_parameter=layer_data.get("memory_parameter"), 

246 ) 

247 new_layer.compute_internal_time() 

248 layers.append(new_layer) 

249 else: 

250 logger.error( 

251 'ERROR: File "%s" doesn\'t have layers_description to parse.\n', 

252 json_layer, 

253 ) 

254 return layers 

255 

256 

257def filter_layer_type( 

258 layers: list[Layer], layer_type: Layer.type_enum 

259) -> list[Layer]: 

260 """Filters all layers of layer_type in layers.""" 

261 kept_layers = [] 

262 for layer in layers: 

263 if layer.type_ == layer_type: 

264 kept_layers.append(layer) 

265 return kept_layers 

266 

267 

268def aggregate(layers: list[Layer]) -> Layer: 

269 """Aggregate all layers into one.""" 

270 

271 def add_none(a: Optional[Any], b: Optional[Any]) -> Any: 

272 """Add ``a`` and ``b``, returning whichever is not ``None`` when one is missing.""" 

273 if a is None: 

274 return b 

275 if b is None: 

276 return a 

277 return a + b 

278 

279 def add_rec_dict(a: Dict[Recompute.TYPE, Any], 

280 b: Dict[Recompute.TYPE, Any]) -> Dict[Recompute.TYPE, Any]: 

281 """Element-wise add two per-recomputation-type dictionaries.""" 

282 return {i: a[i] + b[i] for i in Recompute.TYPE} 

283 

284 aggregation = layers[0] 

285 layers.pop(0) 

286 for layer in layers: 

287 aggregation.time_ += layer.time_ 

288 aggregation.backward_time_rec_ = add_rec_dict( 

289 aggregation.backward_time_rec_, layer.backward_time_rec_ 

290 ) 

291 aggregation.memory_activation_rec_ = add_rec_dict( 

292 aggregation.memory_activation_rec_, layer.memory_activation_rec_ 

293 ) 

294 aggregation.memory_parameter_ = add_none( 

295 aggregation.memory_parameter_, layer.memory_parameter_ 

296 ) 

297 aggregation.nb_layer_ = add_none( 

298 aggregation.nb_layer_, layer.nb_layer_ 

299 ) 

300 return aggregation