Coverage for  / home / jenkins / .local / lib / python3.10 / site-packages / hyper_parallel / auto_parallel / sapp_nd / nd / common / framework_parsers / cost_model_parser_hyperparallel.py: 79%

185 statements  

« prev     ^ index     » next       coverage.py v7.13.1, created at 2026-10-25 05:45 +0800

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

16import ast 

17from pathlib import Path 

18import importlib.util 

19import time 

20import sys 

21import os 

22from hyper_parallel.auto_parallel._op_profiles import infer_qk_norm 

23from hyper_parallel.auto_parallel.sapp_nd.nd.common.config import Config 

24from hyper_parallel.auto_parallel.sapp_nd.nd.common.framework_parsers._cost_model_parser import _CostModelParser 

25from hyper_parallel.auto_parallel.sapp_nd.memory_estimation.size import Memory 

26from hyper_parallel.auto_parallel.sapp_nd.memory_estimation.logger import logger 

27from hyper_parallel.auto_parallel.sapp_nd.nd.common.derive import derive 

28 

29 

30class CostModelParserHyperparallel(_CostModelParser): 

31 """parser class for HyperParallel format""" 

32 

33 def parse(self): 

34 """Parse a HyperParallel TOML configuration.""" 

35 if self.ccfg.source_code: 

36 specs = self.__parse_init_code() 

37 self.ccfg.specs = specs 

38 self.__parse_toml() 

39 else: 

40 now = time.time() 

41 home_path = os.path.expanduser("~") 

42 if home_path not in sys.path: 

43 sys.path.append(home_path) 

44 spec_torch = importlib.util.find_spec("torch") 

45 spec_torchtitan = importlib.util.find_spec("torchtitan") 

46 if spec_torch is not None and spec_torchtitan is not None: 

47 # existing torchtitan package 

48 spec_path = spec_torchtitan.submodule_search_locations[0] 

49 if spec_path not in sys.path: 

50 sys.path.append(spec_path) 

51 try: 

52 logger.info( 

53 "found torchtitan package from homedir: %s", 

54 spec_path, 

55 ) 

56 logger.info("importing getter from torchitan...") 

57 os.environ["ASCEND_SLOG_PRINT_TO_STDOUT"] = "0" 

58 # pylint: disable=import-outside-toplevel 

59 import torchtitan.protocols.train_spec as train_spec_module 

60 logger.info( 

61 "import time: %s sec", 

62 round(time.time() - now, 2), 

63 ) 

64 train_spec = train_spec_module.get_train_spec( 

65 self.config.model.name 

66 ) 

67 model_args = train_spec.model_args[self.config.model.flavor] 

68 # Convert recursively to Config 

69 # pprint.pprint(train_spec.model_args) 

70 # pprint.pprint(model_args) 

71 # model = train_spec.model_cls(model_args) 

72 # print(model) 

73 # print(dir(model)) 

74 # print("NAMED PARAMETERS") 

75 # for k,_ in model.named_parameters(): 

76 # print(k) 

77 specs = self.__obj_to_config(model_args) 

78 self.ccfg.specs = specs 

79 self.__parse_toml() 

80 return 

81 except ModuleNotFoundError: 

82 pass 

83 raise AttributeError( 

84 "Hyperparallel config: Could not find torch/torchtitan package. " 

85 "Please specify argument --code-path with __init__.py path " 

86 ) 

87 

88 def __obj_to_config(self, obj): 

89 """convert to Config""" 

90 if obj and not isinstance(obj, (str, int, float, bool, list)): 

91 res = Config({}) 

92 for k, v in obj.__dict__.items(): 

93 setattr(res, k, self.__obj_to_config(v)) 

94 return res 

95 return obj 

96 

97 def __parse_init_code(self): 

98 """parse __init__.py through ast""" 

99 def parse_args(res, arg): 

100 """parse config from target flavor (var = dict: flavor -> config)""" 

101 for kw in arg.keywords: 

102 if not isinstance(kw.value, ast.Call): 

103 res[kw.arg] = ast.literal_eval(kw.value) 

104 else: 

105 res[kw.arg] = {} 

106 parse_args(res[kw.arg], kw.value) 

107 

108 path = Path(self.ccfg.source_code) 

109 source_code = path.read_text(encoding="utf-8") 

110 tree = ast.parse(source_code, filename=path.name) 

111 # fetch get_spec() AST 

112 tree_get_spec = next( 

113 node 

114 for node in ast.walk(tree) 

115 if isinstance(node, ast.FunctionDef) and node.name == "get_train_spec" 

116 ) 

117 # fetch model_args variable AST 

118 var_model_args = next( 

119 k.value.id 

120 for k in tree_get_spec.body[0].value.keywords 

121 if k.arg == "model_args" 

122 ) 

123 var_tree = None 

124 for node in ast.walk(tree): 

125 if isinstance(node, ast.Assign) and node.targets[0].id == var_model_args: 

126 var_tree = node 

127 break 

128 # fetch dict of hyperparameters according to flavor 

129 params = {} 

130 for k, v in zip(var_tree.value.keys, var_tree.value.values): 

131 if k.value == self.config.model.flavor: 

132 parse_args(params, v) 

133 break 

134 return Config(params) 

135 

136 def __parse_toml(self): 

137 """main parsing order""" 

138 self.ccfg.model_name = self.config.model.name 

139 self.config_op_counts(self.ccfg) 

140 self.ccfg.config_format = "toml" 

141 self.ccfg.multimodal = False 

142 self.ccfg.device_capacity = Memory.from_string("56GB") # important 

143 self.ccfg.mm_ccfgs = None 

144 self.ccfg.mm_order = None 

145 self.__parse_feature_flag() 

146 self.__parse_hyperparam() 

147 self.__parse_strat() 

148 self.__parse_moe() 

149 self.__parse_batch() 

150 self.__init_shard() 

151 self.__init_bytes() 

152 self.ccfg.n_mtp = 0 

153 self.config_layer_stack(self.ccfg) 

154 self.ccfg.overwrite_eval_functions = {} 

155 derive(self.ccfg) # optimizer sharding needs adapting to FSDP 

156 

157 def __parse_strat(self): 

158 """strategy vars""" 

159 self.ccfg.d = max( 

160 1, 

161 self.config.parallelism.data_parallel_replicate_degree 

162 * self.config.parallelism.data_parallel_shard_degree, 

163 ) # need correction 

164 self.ccfg.t = max(1, self.config.parallelism.tensor_parallel_degree) 

165 self.ccfg.p = max(1, self.config.parallelism.pipeline_parallel_degree) 

166 self.ccfg.cp = max(1, self.config.parallelism.context_parallel_degree) 

167 self.ccfg.ep = max(1, self.config.parallelism.expert_parallel_degree) 

168 # TorchTitan's FSDP2 frees a block's gathered parameters after its 

169 # forward under the "always" policy, and under the default one when 

170 # nothing is pipelined. 

171 policy = self.config.parallelism.fsdp_reshard_after_forward or "default" 

172 self.ccfg.reshard_params = policy == "always" or (policy == "default" and self.ccfg.p == 1) 

173 self.ccfg.sequence_parallel = True 

174 self.ccfg.vp = 1 

175 self.ccfg.op_weight_shard = self.config.parallelism.data_parallel_shard_degree 

176 self.ccfg.os_max_shard = ( 

177 self.ccfg.op_weight_shard if self.ccfg.op_weight_shard >= 1 

178 else self.ccfg.d 

179 ) 

180 self.ccfg.offset = 0 # important 

181 self.ccfg.full_rec = self.config.activation_checkpoint.mode == "full" 

182 self.ccfg.sel_rec = self.config.activation_checkpoint.mode == "selective" 

183 self.ccfg.pp_sched = self.config.parallelism.pipeline_parallel_schedule 

184 if self.ccfg.pp_sched: 

185 # From Pytorch code 

186 schedule_map = { 

187 "1F1B": "1f1b", 

188 "Interleaved1F1B": "1f1b", 

189 "GPipe": "gpipe", 

190 "FlexibleInterleaved1F1B": "1f1b", # ?? 

191 "LoopedBFS": "1f1b", 

192 "InterleavedZeroBubble": "1f1b", 

193 "ScheduleZBVZeroBubble": "zero_bubble_v", 

194 "PipelineScheduleSingle": None, 

195 "PipelineScheduleMulti": None, 

196 } 

197 if "Interleaved" in self.ccfg.pp_sched and self.ccfg.p > 1: 

198 self.ccfg.vp = 2 

199 if self.ccfg.pp_sched in schedule_map: 

200 self.ccfg.pp_sched = schedule_map[self.ccfg.pp_sched] 

201 else: 

202 logger.warning( 

203 "Unsupported pipeline_parallel_schedule '%s'. " 

204 "Defaulting to 1f1b.", 

205 self.ccfg.pp_sched, 

206 ) 

207 self.ccfg.pp_sched = "1f1b" 

208 else: 

209 self.ccfg.pp_sched = "1f1b" 

210 self.ccfg.emb_out_in_offset = True 

211 self.ccfg.n_s_split = 1 

212 self.ccfg.cp_algo = "colossalai_cp" 

213 self.ccfg.sel_rec_rule = "hyperparallel" 

214 self.ccfg.pp_partition = None 

215 

216 def __parse_hyperparam(self): 

217 """hyperparameter vars""" 

218 # A flavor may state either as None, TorchTitan's default: no multiplier. 

219 self.ccfg.multiple_of = max(1, self.ccfg.specs.multiple_of or 1) 

220 self.ccfg.fdm = max(1, self.ccfg.specs.ffn_dim_multiplier or 1) 

221 self.ccfg.h = self.ccfg.specs.dim 

222 self.ccfg.hff = self.ccfg.specs.inter_dim 

223 if not self.ccfg.hff: 

224 self.ccfg.hff = self.ccfg.specs.hidden_dim 

225 if not self.ccfg.hff: 

226 if "llama" in self.ccfg.model_name: 

227 self.ccfg.hff = self.init_hff() 

228 else: 

229 self.ccfg.hff = self.ccfg.h 

230 self.ccfg.v = self.ccfg.specs.vocab_size 

231 self.ccfg.s = self.config.training.seq_len 

232 self.ccfg.a = self.ccfg.specs.n_heads 

233 self.ccfg.n_lay = self.ccfg.specs.n_layers 

234 self.ccfg.n_kv = self.ccfg.specs.n_kv_heads 

235 if not self.ccfg.n_kv: 

236 self.ccfg.n_kv = self.ccfg.a 

237 self.ccfg.dh = self.ccfg.h / self.ccfg.a 

238 self.ccfg.dc_kv = self.ccfg.specs.kv_lora_rank 

239 self.ccfg.dc_q = self.ccfg.specs.q_lora_rank 

240 self.ccfg.dhr = self.ccfg.specs.qk_rope_head_dim 

241 # TorchTitan's Qwen3 flavors normalize each head's queries and keys 

242 # unless they state otherwise; its other families have no QK-norm. 

243 stated = vars(self.ccfg.specs) 

244 self.ccfg.qk_norm = ( 

245 bool(stated["qk_norm"]) if "qk_norm" in stated else infer_qk_norm(self.ccfg.model_name) 

246 ) 

247 self.ccfg.k_1st_dense = self.ccfg.specs.n_dense_layers 

248 self.ccfg.is_mtp_in_offset = True 

249 

250 def __parse_moe(self): 

251 """MoE vars""" 

252 self.ccfg.hff_exp = ( 

253 self.ccfg.specs.moe_inter_dim if self.ccfg.specs.moe_inter_dim 

254 else self.ccfg.hff 

255 ) 

256 # A flavor that states its experts runs them unless it says it does 

257 # not: TorchTitan's DeepSeek-V3 states moe_args and no moe_enabled. 

258 # The specs answer a missing field with 0, so read what they state. 

259 stated = vars(self.ccfg.specs) 

260 moe_enabled = stated["moe_enabled"] if "moe_enabled" in stated else True 

261 if moe_enabled and self.ccfg.specs.moe_args: 

262 self.ccfg.n_exp = self.ccfg.specs.moe_args.num_experts 

263 self.ccfg.n_chosen_exp = self.ccfg.specs.moe_args.top_k 

264 self.ccfg.n_shared_exp = ( 

265 self.ccfg.specs.moe_args.num_shared_experts 

266 ) 

267 else: 

268 self.ccfg.n_exp = 1 

269 self.ccfg.n_chosen_exp = 1 

270 self.ccfg.n_shared_exp = 0 

271 self.ccfg.cap_fact = 1 # Assuming 

272 self.ccfg.etp = self.config.parallelism.expert_tensor_parallel_degree 

273 

274 def __parse_feature_flag(self): 

275 """training feature vars""" 

276 self.ccfg.has_op = True # Assuming 

277 self.ccfg.has_grad_shard = True # Assuming FSDP 

278 # FSDP holds every gradient sharded as its parameter, at any 

279 # pipeline degree. 

280 self.ccfg.grad_shard_as_params = True 

281 self.ccfg.grad_accumulation = True 

282 self.ccfg.freeze = False 

283 self.ccfg.has_fa = True # Assuming 

284 self.ccfg.vp_less_mem = False 

285 self.ccfg.has_clip = False 

286 self.ccfg.gmm = True 

287 self.ccfg.tie_emb_out = self.ccfg.specs.enable_weight_tying 

288 

289 def __parse_batch(self): 

290 """batch related vars""" 

291 self.ccfg.b = self.config.training.local_batch_size 

292 self.ccfg.m = self.ccfg.p 

293 self.ccfg.gbs = self.ccfg.b * self.ccfg.d * self.ccfg.m 

294 

295 def __init_shard(self): 

296 """sharding vars""" 

297 # The embedding is priced split over tensor parallelism alone: FSDP's 

298 # sharding of it over data parallelism is not modelled yet. 

299 self.ccfg.vocab_emb_dp = False 

300 self.ccfg.emb_dp_sharded = False 

301 self.ccfg.is_shard_mtp_param = True 

302 

303 def __init_bytes(self): 

304 """fp bytes vars""" 

305 self.ccfg.bytes_p = 4 

306 self.ccfg.grad_bytes = self.ccfg.bytes_p # FSDP: the parameters' dtype 

307 self.ccfg.bytes_compute = 2 

308 self.ccfg.bytes_softmax = 4