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

68 statements  

« prev     ^ index     » next       coverage.py v7.13.1, created at 2026-10-25 05:45 +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"""How an execution spec reaches a cost-model config. 

16 

17:func:`apply_exec` is the one way a strategy reaches a config: it writes the 

18fields an :class:`~hyper_parallel.auto_parallel._exec_spec.ExecSpec` states 

19and derives the rest. :func:`exec_of` reads a config's ExecSpec back, for a 

20parser that fills the config itself, and :func:`strategy_exec` is the 

21ExecSpec a search's keyword strategy states. A config a search owns refuses 

22any other write of a degree, but :func:`apply_layer_strategy`, which gives 

23the layer about to be priced the degrees its kind runs with. 

24""" 

25from typing import Any, List, Mapping, Optional, Tuple 

26 

27from hyper_parallel.auto_parallel._exec_spec import RECOMPUTE_OPS, ExecSpec, RecomputeRange 

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

29from hyper_parallel.auto_parallel.sapp_nd.nd.common.layer_order import get_model_order, stated_recompute 

30from hyper_parallel.auto_parallel.sapp_nd.nd.common.layer_type import LayerType 

31 

32# Each ExecSpec field, and the config field that holds it. 

33CONFIG_FIELDS = { 

34 "dp": "d", 

35 "tp": "t", 

36 "pp": "p", 

37 "vpp": "vp", 

38 "cp": "cp", 

39 "ep": "ep", 

40 "etp": "etp", 

41 "sequence_parallel": "sequence_parallel", 

42 "shard_activations": "shard_activations", 

43 "loss_parallel": "loss_parallel", 

44 "micro_batch_size": "b", 

45 "micro_batch_num": "m", 

46 "global_batch_size": "gbs", 

47 "optimizer_parallel": "has_op", 

48 "optimizer_shard": "os_max_shard", 

49 "expert_shard": "expert_shard", 

50 "expert_shard_group": "expert_shard_group", 

51 "grad_shard": "has_grad_shard", 

52 "grad_shard_as_params": "grad_shard_as_params", 

53 "shard_spans_cp": "shard_spans_cp", 

54 "grad_accumulation": "grad_accumulation", 

55 "reshard_params": "reshard_params", 

56 "deferred_grad_accumulation": "deferred_grad_accumulation", 

57 "overlapped_grad_reduce": "overlapped_grad_reduce", 

58 "param_casts": "param_casts", 

59 "pp_schedule": "pp_sched", 

60 "offset": "offset", 

61 "seq_split": "n_s_split", 

62 "pp_partition": "pp_partition", 

63 "mtp_in_offset": "is_mtp_in_offset", 

64 "emb_out_in_offset": "emb_out_in_offset", 

65 "full_recompute": "full_rec", 

66 "selective_recompute": "sel_rec", 

67 "selective_comm_recompute": "sel_comm_rec", 

68 "selective_rule": "sel_rec_rule", 

69 "recompute_slice_activation": "recompute_slice_activation", 

70 "recompute": "recompute_ranges", 

71 "param_bytes": "bytes_p", 

72 "compute_bytes": "bytes_compute", 

73 "softmax_bytes": "bytes_softmax", 

74 "grad_bytes": "grad_bytes", 

75 "optimizer_state_bytes": "optimizer_state_bytes", 

76 "optimizer_states": "optimizer_states", 

77 "main_param_bytes": "main_param_bytes", 

78 "norm_bytes": "norm_bytes", 

79 "dropout_bytes": "dropout_bytes", 

80 "flash_attention": "has_fa", 

81 "grad_clip": "has_clip", 

82 "grouped_gemm": "gmm", 

83 "vocab_emb_dp": "vocab_emb_dp", 

84 "emb_dp_sharded": "emb_dp_sharded", 

85 "tie_embeddings": "tie_emb_out", 

86 "cp_algo": "cp_algo", 

87 "capacity_factor": "cap_fact", 

88 "shard_mtp_param": "is_shard_mtp_param", 

89 "frozen": "freeze", 

90 "optimizer": "optimizer", 

91 "seq_length": "s", 

92 "device_memory": "device_capacity", 

93} 

94 

95# The keyword strategy of ``CostModelConfig.set_strategy``: each key that 

96# states an integer, and the ExecSpec field it states. 

97STRATEGY_KEYS = ( 

98 ("dp", "dp"), 

99 ("mp", "tp"), 

100 ("ep", "ep"), 

101 ("etp", "etp"), 

102 ("cp", "cp"), 

103 ("vpp", "vpp"), 

104 ("pp", "pp"), 

105 ("mb", "micro_batch_num"), 

106 ("mbs", "micro_batch_size"), 

107) 

108 

109 

110# The recompute option of each layer type the partition generator places. 

111_OPTIONS = {LayerType.FULL_REC_LAYER: "full", LayerType.SEL_REC_LAYER: "selective"} 

112 

113 

114def _ranges(options: List[str], ops: Mapping[str, str]) -> Tuple[RecomputeRange, ...]: 

115 """Return the runs of *options*, one layer each in model order, as ranges. 

116 

117 A layer that does not recompute is left out, since a layer no range 

118 covers is not recomputed. 

119 """ 

120 out = [] 

121 first = 0 

122 for index in range(1, len(options) + 1): 

123 if index < len(options) and options[index] == options[first]: 

124 continue 

125 if options[first] != "none": 

126 out.append(RecomputeRange( 

127 first=first, count=index - first, option=options[first], 

128 ops=dict(ops) if options[first] == "selective" else None, 

129 )) 

130 first = index 

131 return tuple(out) 

132 

133 

134def recompute_of(ccfg: Any) -> Optional[Tuple[RecomputeRange, ...]]: 

135 """Return how a config's layers recompute, as ranges in model order. 

136 

137 The ranges it states, else the option its partition gives each layer 

138 from ``full_rec`` and ``sel_rec``, with the switches of its selective 

139 recompute as each op's state. ``None`` for a config the partition 

140 generator does not lay out alone, such as a multimodal parent. 

141 """ 

142 stated = stated_recompute(ccfg) 

143 if stated is not None: 

144 return stated 

145 layout = getattr(ccfg, "generate_partitions_vpp_unimodal", None) 

146 if not callable(layout) or getattr(ccfg, "multimodal", False) or not ccfg.n_lay: 

147 return None 

148 stages = layout() 

149 options = [ 

150 _OPTIONS.get(stages[stage_id][chunk_id][lay_id], "none") 

151 for stage_id, chunk_id, lay_id in get_model_order(ccfg, stages) 

152 ] 

153 # From vars: a Config answers 0, recompute, for a switch it lacks. 

154 stated = vars(ccfg.rec_op) if ccfg.rec_op is not None else {} 

155 ops = {op: "keep" if stated.get(op, 1) else "recompute" for op in RECOMPUTE_OPS} 

156 return _ranges(options, ops) 

157 

158 

159def exec_of(ccfg: Any) -> ExecSpec: 

160 """Read back the ExecSpec a config holds, every field it states. 

161 

162 Its recompute comes back as ranges too (:func:`recompute_of`), whichever 

163 form the config states it in. 

164 """ 

165 stated = {name: getattr(ccfg, field) for name, field in CONFIG_FIELDS.items()} 

166 stated["recompute"] = recompute_of(ccfg) 

167 return ExecSpec(**stated) 

168 

169 

170def apply_exec(ccfg: Any, exec_spec: ExecSpec, strict: bool = True) -> None: 

171 """Write the fields *exec_spec* states on *ccfg*, then derive the rest. 

172 

173 A field the spec does not state keeps the value the config holds. 

174 

175 Args: 

176 ccfg: The config to change. 

177 exec_spec: What to change it to. 

178 strict: As in :func:`~hyper_parallel.auto_parallel.sapp_nd.nd.common.derive.derive`. 

179 """ 

180 for name, field in CONFIG_FIELDS.items(): 

181 value = getattr(exec_spec, name) 

182 if value is None: 

183 continue 

184 if name == "device_memory" and isinstance(value, str): 

185 value = _memory(value) 

186 # The one sanctioned write of a strategy field, past the guard. 

187 object.__setattr__(ccfg, field, value) 

188 if exec_spec.recompute is None and ( 

189 exec_spec.full_recompute is not None or exec_spec.selective_recompute is not None 

190 ): 

191 # A per-stage recompute replaces the ranges the config states. 

192 object.__setattr__(ccfg, "recompute_ranges", None) 

193 derive(ccfg, strict) 

194 

195 

196def apply_layer_strategy(ccfg: Any, degrees: Mapping[str, Any]) -> None: 

197 """Give the layer about to be priced the degrees its kind runs with. 

198 

199 A dense kind in a MoE model runs with no expert parallelism. Only the 

200 degrees are written: the fields derived from the model's degrees stay as 

201 the estimators read them. 

202 

203 Args: 

204 ccfg: The config the layer is priced on. 

205 degrees: The strategy fields the kind states. 

206 """ 

207 for name, value in degrees.items(): 

208 object.__setattr__(ccfg, name, value) 

209 

210 

211def strategy_exec(ccfg: Any, strategy: Mapping[str, Any]) -> ExecSpec: 

212 """Return the ExecSpec a keyword strategy states, over the config it changes. 

213 

214 A degree or a micro-batching is stated when it is given as an integer, 

215 and ``op`` states the optimizer sharding. Sequence parallelism is the 

216 candidate's where it states ``sp``, else the run's, and on at every TP 

217 degree for a run that states none; the global batch follows the 

218 micro-batching the strategy leaves. The recompute layers and the offset 

219 are stated when given. 

220 

221 Args: 

222 ccfg: The config the strategy changes, which holds what it leaves. 

223 strategy: ``set_strategy``'s keywords. 

224 """ 

225 stated = { 

226 name: strategy[key] for key, name in STRATEGY_KEYS if isinstance(strategy.get(key), int) 

227 } 

228 op = strategy.get("op") 

229 if isinstance(op, int): 

230 stated["optimizer_shard"] = op 

231 # op <= 1 means no optimizer sharding 

232 stated["optimizer_parallel"] = op > 1 

233 # Every candidate ran sequence parallelism, so a run without it had the 

234 # activations TP does not shard divided by TP all the same, and a search 

235 # over SP priced its twins alike. 

236 sp = strategy.get("sp") 

237 run_sp = getattr(ccfg, "sequence_parallel", None) 

238 stated["sequence_parallel"] = bool(sp) if sp is not None else run_sp is None or bool(run_sp) 

239 stated["global_batch_size"] = ( 

240 stated.get("micro_batch_size", ccfg.b) * stated.get("dp", ccfg.d) 

241 * stated.get("micro_batch_num", ccfg.m) 

242 ) 

243 if strategy.get("full_rec") is not None: 

244 stated["full_recompute"] = strategy["full_rec"] 

245 if strategy.get("sel_rec") is not None: 

246 stated["selective_recompute"] = strategy["sel_rec"] 

247 if isinstance(strategy.get("offset"), (int, list)): 

248 stated["offset"] = strategy["offset"] 

249 return ExecSpec(**stated) 

250 

251 

252def _memory(text: str) -> Any: 

253 """The cost model's memory size for a string such as ``"64GB"``.""" 

254 # The memory estimation package imports the config module, which imports 

255 # this one: importing it here keeps this module importable first. 

256 from hyper_parallel.auto_parallel.sapp_nd.memory_estimation.size import Memory # pylint: disable=import-outside-toplevel 

257 return Memory.from_string(text)