Coverage for  / home / jenkins / .local / lib / python3.10 / site-packages / hyper_parallel / auto_parallel / sapp_nd / memory_estimation / evaluators / body.py: 98%

114 statements  

« prev     ^ index     » next       coverage.py v7.13.1, created at 2026-08-04 05:18 +0800

1# Copyright 2025-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"""Body module""" 

16from __future__ import annotations 

17from typing import TYPE_CHECKING 

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

19from hyper_parallel.auto_parallel.sapp_nd.memory_estimation.evaluators.utils import EvalUtils 

20from hyper_parallel.auto_parallel.sapp_nd.memory_estimation.evaluators.comm import EvalLayerComm 

21from hyper_parallel.auto_parallel.sapp_nd.nd.common.cp_types import ( 

22 CPMemoryBreakdown, 

23 CPAlgo, 

24 _resolve_cp_algo, 

25) 

26from hyper_parallel.auto_parallel.sapp_nd.nd.common.cost_model_preprocess import ( 

27 detect_attention_type, 

28 compute_kv_dim, 

29) 

30 

31if TYPE_CHECKING: 

32 from hyper_parallel.auto_parallel.sapp_nd.nd.common.cost_model_preprocess import CostModelConfig 

33 from hyper_parallel.auto_parallel.sapp_nd.memory_estimation._context import Context 

34 from typing import Tuple 

35 

36 

37class EvalBody: 

38 """Body layer formulas class""" 

39 

40 @staticmethod 

41 def num_params_layer( 

42 ccfg: CostModelConfig, ctx: Context 

43 ) -> Tuple[float, float, float]: 

44 """Parameters count. 

45 

46 Returns a 3-tuple (non_exp, routed, shared): 

47 - non_exp: attention + norm params (and dense FFN if n_exp==1) 

48 - routed: routed expert params (0 if n_exp==1) 

49 - shared: shared expert params (0 if n_shared_exp==0 or no pointer) 

50 """ 

51 non_exp = ctx.attn_num_p(ccfg, ctx) + ctx.norm_num_p(ccfg, ctx) 

52 routed = 0.0 

53 shared = 0.0 

54 if ccfg.n_exp == 1: 

55 non_exp += ctx.ffn_num_p(ccfg, ctx) 

56 else: 

57 if ctx.ffn_routed_num_p is not None: 

58 routed = ctx.ffn_routed_num_p(ccfg, ctx) 

59 if ctx.ffn_shared_num_p is not None: 

60 shared = ctx.ffn_shared_num_p(ccfg, ctx) 

61 return (non_exp, routed, shared) 

62 

63 @staticmethod 

64 def stat_p_layer(ccfg: CostModelConfig, ctx: Context) -> float: 

65 """model param""" 

66 non_exp_p, routed_p, shared_p = ctx.eval.num_p(ccfg, ctx) 

67 # Routed experts: EP sharding 

68 routed_mem = routed_p / ccfg.ep * ccfg.bytes_p / ccfg.shard_p_os_exp 

69 # Shared experts: partial DP sharding 

70 shared_mem = shared_p * ccfg.bytes_p / ccfg.shard_p_os_exp_partial 

71 # Non expert 

72 non_exp_mem = non_exp_p * ccfg.bytes_p / ccfg.shard_p_os_non_exp_partial 

73 return non_exp_mem + routed_mem + shared_mem 

74 

75 @staticmethod 

76 def stat_os_layer(ccfg: CostModelConfig, ctx: Context) -> float: 

77 """optim state""" 

78 if ctx.swap_os: 

79 return 0 

80 non_exp_p, routed_p, shared_p = ctx.eval.num_p(ccfg, ctx) 

81 # Routed experts 

82 routed_mem = routed_p / ccfg.ep * 2 * ccfg.bytes_os / ccfg.shard_p_os_exp 

83 # Shared experts 

84 shared_mem = shared_p * 2 * ccfg.bytes_os / ccfg.shard_p_os_exp_partial 

85 # Non expert 

86 non_exp_mem = non_exp_p * 2 * ccfg.bytes_os / ccfg.shard_p_os_non_exp_partial 

87 return non_exp_mem + routed_mem + shared_mem 

88 

89 @staticmethod 

90 def stat_grad_layer(ccfg: CostModelConfig, ctx: Context) -> float: 

91 """gradients""" 

92 non_exp_p, routed_p, shared_p = ctx.eval.num_p(ccfg, ctx) 

93 # Routed experts 

94 routed_mem = routed_p / ccfg.ep * ccfg.bytes_grad / ccfg.shard_grad_exp 

95 # Shared experts: use shard_grad_exp_partial (independent of os sharding) 

96 shared_mem = shared_p * ccfg.bytes_grad / ccfg.shard_grad_exp_partial 

97 # Non expert 

98 non_exp_mem = non_exp_p * ccfg.bytes_grad / ccfg.shard_grad_non_exp 

99 return non_exp_mem + routed_mem + shared_mem 

100 

101 # No recompute and select recompute 

102 

103 @staticmethod 

104 def layer_activ(ccfg: CostModelConfig, ctx: Context) -> float: 

105 """activations""" 

106 attn_size = sum( 

107 [ 

108 ctx.attn_qkv_activ(ccfg, ctx), 

109 ctx.attn_score_activ(ccfg, ctx), 

110 ctx.attn_proj_activ(ccfg, ctx), 

111 ] 

112 ) 

113 if ccfg.n_exp == 1: 

114 ffn_size = ctx.ffn_activ(ccfg, ctx) 

115 else: 

116 ffn_size = ctx.ffn_moe_activ(ccfg, ctx) 

117 norm_size = ctx.norm_activ(ccfg, ctx) 

118 return attn_size + ffn_size + norm_size 

119 

120 # Full recompute 

121 

122 @staticmethod 

123 def fullrec_layer_activ(ccfg: CostModelConfig, ctx: Context) -> float: 

124 """activations""" 

125 micro_factor = ctx.micro_factor 

126 forward_activation = ( 

127 micro_factor * ccfg.bytes_compute * ccfg.s * ccfg.b * ccfg.h 

128 ) 

129 forward_activation /= ccfg.shard_recompute_input 

130 return forward_activation 

131 

132 @staticmethod 

133 def fullrec_layer_activ_gradclip( 

134 ccfg: CostModelConfig, ctx: Context 

135 ) -> float: 

136 """special case with gradient clipping""" 

137 non_exp_p, routed_p, shared_p = ctx.eval.num_p(ccfg, ctx) 

138 grad_clip_mem = ( 

139 non_exp_p 

140 + routed_p / ccfg.ep * ccfg.bytes_os / ccfg.shard_p_os_exp 

141 + shared_p * ccfg.bytes_os / ccfg.shard_p_os_exp_partial 

142 ) 

143 grad_clip_mem *= ccfg.bytes_os / ccfg.shard_p_os_non_exp_partial 

144 grad_clip_mem *= int(ccfg.has_clip) 

145 forward_activation = EvalBody.fullrec_layer_activ(ccfg, ctx) 

146 dp_comm_size = ctx.eval.dyn.comm.dp(ccfg, ctx) 

147 if forward_activation + dp_comm_size > grad_clip_mem: 

148 return forward_activation 

149 logger.debug( 

150 "gradient clipping %s > %s", 

151 EvalUtils.mb(grad_clip_mem), 

152 EvalUtils.mb(forward_activation + dp_comm_size), 

153 ) 

154 return grad_clip_mem 

155 

156 @staticmethod 

157 def fullrec_layer_comm_gradclip( 

158 ccfg: CostModelConfig, ctx: Context 

159 ) -> float: 

160 """special case with gradient clipping""" 

161 if EvalBody.fullrec_layer_activ_gradclip(ccfg, ctx) > 0: 

162 return ctx.eval.dyn.comm.dp(ccfg, ctx) 

163 return 0 

164 

165 @staticmethod 

166 def act_cp_layer( 

167 ccfg: CostModelConfig, 

168 ctx: Context 

169 ) -> CPMemoryBreakdown: 

170 """Estimate CP activation memory impact for one transformer layer. 

171 

172 Ring CP (colossalai_cp / hybrid_cp): 

173 Each rank holds s/cp tokens (Q sharded along seq) and a/t heads; 

174 KV is all-gathered, so only one S dim of the S² score tensor 

175 is divided. 

176 KV cache: (s/cp) × b × kv_dim_per_rank 

177 Attn scores: (s/cp) × s × b × (a/t) 

178 

179 Ulysses CP: 

180 Each rank holds all s tokens but a/(t*cp) heads. 

181 KV cache: s × b × kv_dim_per_rank / cp 

182 Attn scores: s × s × b × (a/(t*cp)) 

183 """ 

184 s, b = ccfg.s, ccfg.b 

185 a = ccfg.a 

186 t = max(1, ccfg.t) 

187 

188 if a <= 0: 

189 raise ValueError(f"Number of attention heads must be positive, got {a}") 

190 

191 cp = ccfg.cp 

192 

193 if cp <= 0: 

194 raise ValueError(f"CP degree must be positive, got {cp}") 

195 

196 attention_type = detect_attention_type(ccfg) 

197 cp_algo = _resolve_cp_algo(ccfg) 

198 

199 attention_scores_bytes = 4 

200 softmax_outputs_bytes = 4 

201 dropout_mask_bytes = 1 

202 fp16_bytes = 2 

203 kv_bytes = fp16_bytes * 2 

204 

205 kv_dim = compute_kv_dim(ccfg) 

206 a_per_rank = a / t 

207 

208 if cp_algo == CPAlgo.ULYSSES_CP: 

209 a_per_cp_rank = a_per_rank / cp 

210 kv_dim_per_cp_rank = kv_dim / cp 

211 

212 kv_cache_memory = kv_bytes * s * b * kv_dim_per_cp_rank 

213 attention_scores_memory = attention_scores_bytes * s * s * b * a_per_cp_rank 

214 softmax_outputs_memory = softmax_outputs_bytes * s * s * b * a_per_cp_rank 

215 dropout_mask_memory = dropout_mask_bytes * s * s * b * a_per_cp_rank 

216 

217 s2_items_no_cp = ( 

218 (attention_scores_bytes + softmax_outputs_bytes + dropout_mask_bytes) 

219 * s * s * b * a_per_rank 

220 ) 

221 s2_items_with_cp = ( 

222 (attention_scores_bytes + softmax_outputs_bytes + dropout_mask_bytes) 

223 * s * s * b * a_per_cp_rank 

224 ) 

225 s2_items_reduction = s2_items_no_cp - s2_items_with_cp 

226 kv_reduction = kv_bytes * s * b * kv_dim * ((cp - 1) / cp) 

227 else: 

228 kv_cache_memory = kv_bytes * (s / cp) * b * kv_dim 

229 attention_scores_memory = attention_scores_bytes * (s / cp) * s * b * a_per_rank 

230 softmax_outputs_memory = softmax_outputs_bytes * (s / cp) * s * b * a_per_rank 

231 dropout_mask_memory = dropout_mask_bytes * (s / cp) * s * b * a_per_rank 

232 

233 s2_items_reduction = ( 

234 (attention_scores_bytes + softmax_outputs_bytes + dropout_mask_bytes) 

235 * s * s * b * a_per_rank * ((cp - 1) / cp) 

236 ) 

237 kv_reduction = kv_bytes * s * b * kv_dim * ((cp - 1) / cp) 

238 

239 comm_buffer = EvalLayerComm.cp_comm_buffer(ccfg, ctx) 

240 

241 total_memory = ( 

242 kv_cache_memory + attention_scores_memory + 

243 softmax_outputs_memory + dropout_mask_memory + comm_buffer 

244 ) 

245 total_reduction = s2_items_reduction + kv_reduction - comm_buffer 

246 

247 return CPMemoryBreakdown( 

248 kv_cache_memory=kv_cache_memory, 

249 attention_scores_memory=attention_scores_memory, 

250 softmax_outputs_memory=softmax_outputs_memory, 

251 dropout_mask_memory=dropout_mask_memory, 

252 comm_buffer_memory=comm_buffer, 

253 kv_reduction=kv_reduction, 

254 s2_reduction=s2_items_reduction, 

255 total_reduction=total_reduction, 

256 total_memory=total_memory, 

257 cp_degree=int(cp), 

258 seq_len=int(s), 

259 attention_type=attention_type, 

260 cp_algo=cp_algo, 

261 )