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-08-21 04:29 +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 (auto-derived from ``time_`` when not provided) 

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: Optional[float] = None, 

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 (Optional[float], optional): Optional forward time; ``None`` means 

83 derive from ``time`` via :meth:`compute_internal_time`. Default: ``None``. 

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

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

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

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

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

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

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

91 """ 

92 if backward_time_rec is None: 

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

94 if backward_coef_rec is None: 

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

96 if memory_activation_rec is None: 

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

98 self.name_ = name 

99 self.model_name_ = model_name 

100 self.type_ = ltype 

101 self.nb_layer_ = nb_layer 

102 self.time_ = time 

103 self.memory_activation_rec_ = memory_activation_rec 

104 self.memory_parameter_ = memory_parameter 

105 self.backward_time_rec_ = backward_time_rec 

106 self.backward_coef_rec_ = backward_coef_rec 

107 self.forward_time_ = forward_time 

108 self.recompute_considered_ = self.find_recompute_considered() 

109 self.compute_internal_time() 

110 

111 def __str__(self) -> str: 

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

113 result = "Layer Description:\n" 

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

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

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

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

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

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

120 for r in Recompute.TYPE: 

121 if self.recompute_considered_[r]: 

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

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

124 result += " forward_time = " 

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

126 for r in Recompute.TYPE: 

127 if self.recompute_considered_[r]: 

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

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

130 return result 

131 

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

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

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

135 

136 def to_json(self) -> None: 

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

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

139 

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

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

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

143 

144 for rec in Recompute.TYPE: 

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

146 recompute_considered[rec] = True 

147 

148 return recompute_considered 

149 

150 def compute_internal_time( 

151 self, 

152 back_ratio: float = backward_default_ratio, 

153 force_fb: bool = False, 

154 ) -> None: 

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

156 if force_fb or self.forward_time_ is None: 

157 self.forward_time_ = self.time_ 

158 self.backward_time_ = back_ratio * self.time_ 

159 

160 for rec in Recompute.TYPE: 

161 if self.recompute_considered_[rec]: 

162 if ( 

163 self.backward_time_rec_[rec] is None 

164 or self.backward_time_rec_[rec] == 0 

165 ): 

166 if self.backward_coef_rec_[rec] is None: 

167 self.backward_time_rec_[rec] = ( 

168 1 + Recompute.DEFAULT_COEF[rec] 

169 ) * self.backward_time_ 

170 else: 

171 self.backward_time_rec_[rec] = ( 

172 1 + self.backward_coef_rec_[rec] 

173 ) * self.backward_time_ 

174 

175 def update_internal_time_for_seqpp( 

176 self, 

177 back_ratio: float = backward_default_ratio, 

178 force_fb: bool = False, 

179 ) -> None: 

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

181 if force_fb or self.forward_time_ is None: 

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

183 self.backward_time_ = back_ratio * self.time_ 

184 

185 for rec in Recompute.TYPE: 

186 if self.recompute_considered_[rec]: 

187 if self.backward_coef_rec_[rec] is None: 

188 self.backward_time_rec_[rec] = ( 

189 1 + Recompute.DEFAULT_COEF[rec] 

190 ) * self.backward_time_ 

191 else: 

192 self.backward_time_rec_[rec] = ( 

193 1 + self.backward_coef_rec_[rec] 

194 ) * self.backward_time_ 

195 

196 def compute_timer( 

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

198 ) -> None: 

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

200 layer_time = ComputationAnalyzer( 

201 timeline_folder, 

202 self.model_name_, 

203 num_of_micro_batch=0, 

204 layer_list=tmp_layer_info, 

205 ) 

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

207 self.compute_internal_time(force_fb=True) 

208 

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

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

211 logger.error( 

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

213 ) 

214 

215 

216# Helper functions on layer list 

217 

218 

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

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

221 layers = [] 

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

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

224 layer_data_json = json.load(json_file) 

225 if "layers_description" in layer_data_json: 

226 for layer_data in layer_data_json["layers_description"]: 

227 new_layer = Layer( 

228 name=layer_data["name"], 

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

230 nb_layer=layer_data["nb_layer"], 

231 time=layer_data["time"], 

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

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

234 backward_time_rec={ 

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

236 for r in Recompute.TYPE 

237 }, 

238 backward_coef_rec={ 

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

240 for r in Recompute.TYPE 

241 }, 

242 memory_activation_rec={ 

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

244 for r in Recompute.TYPE 

245 }, 

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

247 ) 

248 new_layer.compute_internal_time() 

249 layers.append(new_layer) 

250 else: 

251 logger.error( 

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

253 json_layer, 

254 ) 

255 return layers 

256 

257 

258def filter_layer_type( 

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

260) -> list[Layer]: 

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

262 kept_layers = [] 

263 for layer in layers: 

264 if layer.type_ == layer_type: 

265 kept_layers.append(layer) 

266 return kept_layers 

267 

268 

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

270 """Aggregate all layers into one.""" 

271 

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

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

274 if a is None: 

275 return b 

276 if b is None: 

277 return a 

278 return a + b 

279 

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

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

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

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

284 

285 aggregation = layers[0] 

286 layers.pop(0) 

287 for layer in layers: 

288 aggregation.time_ += layer.time_ 

289 aggregation.backward_time_rec_ = add_rec_dict( 

290 aggregation.backward_time_rec_, layer.backward_time_rec_ 

291 ) 

292 aggregation.memory_activation_rec_ = add_rec_dict( 

293 aggregation.memory_activation_rec_, layer.memory_activation_rec_ 

294 ) 

295 aggregation.memory_parameter_ = add_none( 

296 aggregation.memory_parameter_, layer.memory_parameter_ 

297 ) 

298 aggregation.nb_layer_ = add_none( 

299 aggregation.nb_layer_, layer.nb_layer_ 

300 ) 

301 return aggregation