Coverage for  / home / jenkins / .local / lib / python3.10 / site-packages / hyper_parallel / distributed / _builder / default_templates.py: 99%

112 statements  

« prev     ^ index     » next       coverage.py v7.13.1, created at 2026-10-25 05:45 +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"""default_templates: semantic role → placement templates (05 §3.5 canonical). 

16 

17``ShardingTemplate`` is the public template type (re-exported from 

18``hyper_parallel.distributed``); ``TEMPLATES`` is the private 

19default table consumed by the planner. The template-referencing Phase 4 

20derivation helpers (``_build_spec_from_template`` / ``_placement_for_role`` 

21and friends) also live here — they are pure functions of the templates. 

22""" 

23 

24import copy 

25from dataclasses import dataclass, field 

26from typing import Dict, List, Optional, Tuple 

27 

28from hyper_parallel.core.dtensor.placement_types import ( 

29 Partial, 

30 Placement, 

31 Replicate, 

32 Shard, 

33) 

34from hyper_parallel.distributed.plan import ShardingPlan 

35from hyper_parallel.distributed.recipe_spec import ( 

36 CP, 

37 EP, 

38 TP, 

39 ModuleShardingSpec, 

40 NamedPlacement, 

41 _normalize_out_fields, 

42) 

43from hyper_parallel.distributed.tensor_parallel.head_count import ( 

44 build_tp_local_attr_plan, 

45) 

46from hyper_parallel.distributed.tensor_parallel.param_role import ParamRole 

47 

48 

49@dataclass 

50class ShardingTemplate: 

51 """Semantic role → placement template (05 §3.5). 

52 

53 Each I/O field declares placements for all active mesh dimensions 

54 (TP+CP+EP); the ShardingPlanner filters out dimensions not enabled by the 

55 actual mesh_dim_names (resolve_placements looks up keys by mesh_dim_names, 

56 so extra keys are naturally dropped). 

57 

58 Note: sp_out_src / nosp_out_src etc. are scalar NamedPlacement shorthands 

59 (single-output modules); they are wrapped into {"output": ...} during 

60 normalization in _build_spec_from_template. 

61 """ 

62 # Parameter sharding rules 

63 colwise_placement: Placement = field(default_factory=lambda: Shard(0)) 

64 rowwise_placement: Placement = field(default_factory=lambda: Shard(1)) 

65 norm_placement: Placement = field(default_factory=Replicate) 

66 moe_expert_placement: Placement = field(default_factory=lambda: Shard(0)) 

67 

68 # SP-mode I/O (full TP+CP+EP, three dims) 

69 sp_in_src: Dict[str, NamedPlacement] = field(default_factory=dict) 

70 sp_in_dst: Dict[str, NamedPlacement] = field(default_factory=dict) 

71 sp_out_src: Optional[NamedPlacement] = None 

72 sp_out_dst: Optional[NamedPlacement] = None 

73 

74 # non-SP-mode I/O 

75 nosp_in_src: Dict[str, NamedPlacement] = field(default_factory=dict) 

76 nosp_in_dst: Dict[str, NamedPlacement] = field(default_factory=dict) 

77 nosp_out_src: Optional[NamedPlacement] = None 

78 nosp_out_dst: Optional[NamedPlacement] = None 

79 

80 # Special flags 

81 # region_dispatch: template-level declaration that the matched module's own 

82 # forward CANNOT dispatch (data-dependent logic inside, e.g. an EP-aware 

83 # custom MoE's a2a) — the planner copies False into the spec (derived 

84 # metadata, logged); None = ordinary dispatchable module. 

85 region_dispatch: Optional[bool] = None 

86 needs_cp_attn: bool = False # CP: inner attention needs a CP-aware forward 

87 

88 

89def _multi_dim(tp=None, cp=None, ep=None) -> NamedPlacement: 

90 """Build multi-dim placement dict, filtering out None dims.""" 

91 result = {} 

92 if tp is not None: 

93 result[TP] = tp 

94 if cp is not None: 

95 result[CP] = cp 

96 if ep is not None: 

97 result[EP] = ep 

98 return result 

99 

100 

101def _hid(tp_p, cp_p, ep_p=None) -> Dict[str, NamedPlacement]: 

102 """Shorthand for the single-input hidden_states contract.""" 

103 return {"hidden_states": _multi_dim(tp=tp_p, cp=cp_p, ep=ep_p or Replicate())} 

104 

105 

106def _out(tp_p, cp_p, ep_p=None) -> NamedPlacement: 

107 """Shorthand for the single-output (scalar shorthand) contract.""" 

108 return _multi_dim(tp=tp_p, cp=cp_p, ep=ep_p or Replicate()) 

109 

110 

111# ── TEMPLATES: complete templates for the 7 semantic roles (05 §3.5, declared over TP+CP+EP) ── 

112# CP-dim rule: parameters are always Replicate (CP does not shard parameters); 

113# input sequence activations use Shard(1) independently of TP sequence_parallel. 

114# EP-dim rule: non-MoE modules Replicate; MoE experts Shard(0). 

115TEMPLATES: Dict[str, ShardingTemplate] = { 

116 # ── Attention (q/k/v Colwise + o Rowwise) ── 

117 # The CP dim keeps Shard(1) in in_dst: the K/V all-gather is done by the 

118 # inner attention wrapper inside SDPA/FlexAttention (needs_cp_attn=True), 

119 # not at the boundary layer. 

120 "attention": ShardingTemplate( 

121 colwise_placement=Shard(0), # q/k/v: [H/tp, H] 

122 rowwise_placement=Shard(1), # o: [H, H/tp] 

123 sp_in_src=_hid(Shard(1), Shard(1)), 

124 sp_in_dst=_hid(Replicate(), Shard(1)), 

125 sp_out_src=_out(Partial(), Shard(1)), # local Q-chunk output → CP Shard(1) 

126 sp_out_dst=_out(Shard(1), Shard(1)), 

127 nosp_in_src=_hid(Replicate(), Shard(1)), 

128 nosp_in_dst=_hid(Replicate(), Shard(1)), 

129 nosp_out_src=_out(Partial(), Shard(1)), 

130 nosp_out_dst=_out(Replicate(), Shard(1)), 

131 needs_cp_attn=True, 

132 ), 

133 

134 # ── MLP (gate/up Colwise + down Rowwise) ── 

135 # The CP dim stays Shard(1) throughout (revision D-06): MLP is pointwise and 

136 # CP needs no communication; if in_dst had CP=Replicate, the full-sequence 

137 # reduce-scatter under TP×CP would produce a tp-major sequence layout 

138 # inconsistent with embed/attention (cp-major). 

139 "mlp": ShardingTemplate( 

140 colwise_placement=Shard(0), 

141 rowwise_placement=Shard(1), 

142 sp_in_src=_hid(Shard(1), Shard(1)), 

143 sp_in_dst=_hid(Replicate(), Shard(1)), 

144 sp_out_src=_out(Partial(), Shard(1)), 

145 sp_out_dst=_out(Shard(1), Shard(1)), 

146 nosp_in_src=_hid(Replicate(), Shard(1)), 

147 nosp_in_dst=_hid(Replicate(), Shard(1)), 

148 nosp_out_src=_out(Partial(), Shard(1)), 

149 nosp_out_dst=_out(Replicate(), Shard(1)), 

150 ), 

151 

152 # ── Norm (RMSNorm/LayerNorm: weight replicated, zero communication) ── 

153 "norm": ShardingTemplate( 

154 norm_placement=Replicate(), 

155 sp_in_src=_hid(Shard(1), Shard(1)), 

156 sp_in_dst=_hid(Shard(1), Shard(1)), # identity 

157 sp_out_src=_out(Shard(1), Shard(1)), 

158 sp_out_dst=_out(Shard(1), Shard(1)), # identity 

159 nosp_in_src=_hid(Replicate(), Shard(1)), 

160 nosp_in_dst=_hid(Replicate(), Shard(1)), 

161 nosp_out_src=_out(Replicate(), Shard(1)), 

162 nosp_out_dst=_out(Replicate(), Shard(1)), 

163 ), 

164 

165 # ── Embedding (weight Shard(0) along the vocab dim, output Partial → SP+CP) ── 

166 "embed": ShardingTemplate( 

167 colwise_placement=Shard(0), # weight: [V/tp, H] 

168 sp_in_src={"input": _multi_dim(tp=Replicate(), cp=Shard(1), ep=Replicate())}, 

169 sp_in_dst={"input": _multi_dim(tp=Replicate(), cp=Shard(1), ep=Replicate())}, 

170 sp_out_src=_out(Partial(), Shard(1)), 

171 sp_out_dst=_out(Shard(1), Shard(1)), # reduce-scatter → SP+CP 

172 nosp_in_src={"input": _multi_dim(tp=Replicate(), cp=Shard(1), ep=Replicate())}, 

173 nosp_in_dst={"input": _multi_dim(tp=Replicate(), cp=Shard(1), ep=Replicate())}, 

174 nosp_out_src=_out(Partial(), Shard(1)), 

175 nosp_out_dst=_out(Replicate(), Shard(1)), 

176 ), 

177 

178 # ── LM Head (weight Shard(0), output Shard(-1); out_dst is overridden according to loss_parallel) ── 

179 # The CP dim stays Shard(1) throughout (revision D-07): R8 — the CP dim is 

180 # always identity at the boundary layer (the CP sequence all-gather happens 

181 # only inside attention for K/V). lm_head computes logits/loss on the local 

182 # CP chunk (the standard Megatron CP practice), with no CP gather. 

183 "lm_head": ShardingTemplate( 

184 colwise_placement=Shard(0), # weight: [V/tp, H] 

185 sp_in_src=_hid(Shard(1), Shard(1)), 

186 sp_in_dst=_hid(Replicate(), Shard(1)), 

187 sp_out_src=_out(Shard(-1), Shard(1)), 

188 sp_out_dst=_out(Shard(-1), Shard(1)), # loss_parallel=true default; 

189 # when loss_parallel=false, _build_spec_from_template overrides it to 

190 # {TP: Replicate, CP: Shard(1)} 

191 nosp_in_src=_hid(Replicate(), Shard(1)), 

192 nosp_in_dst=_hid(Replicate(), Shard(1)), 

193 nosp_out_src=_out(Shard(-1), Shard(1)), 

194 nosp_out_dst=_out(Replicate(), Shard(1)), 

195 ), 

196 

197 # ── MoE Gate (Router: weight replicated, output redistributes → EP) ── 

198 "moe_gate": ShardingTemplate( 

199 norm_placement=Replicate(), # router weight/bias: replicated 

200 sp_in_src=_hid(Shard(1), Shard(1)), 

201 sp_in_dst=_hid(Replicate(), Replicate()), 

202 sp_out_src=_out(Replicate(), Replicate()), 

203 sp_out_dst=_out(Replicate(), Replicate(), Shard(0)), 

204 nosp_in_src=_hid(Replicate(), Shard(1)), 

205 nosp_in_dst=_hid(Replicate(), Replicate()), 

206 nosp_out_src=_out(Replicate(), Replicate()), 

207 nosp_out_dst=_out(Replicate(), Replicate(), Shard(0)), 

208 ), 

209 

210 # ── MoE MLP (gate + routed experts + optional shared experts) ── 

211 # CP dim same as mlp (D-06): pointwise per-token, CP stays Shard(1) throughout. 

212 "moe_mlp": ShardingTemplate( 

213 colwise_placement=Shard(0), # expert w1/w3: Colwise on TP 

214 rowwise_placement=Shard(1), # expert w2: Rowwise on TP 

215 norm_placement=Replicate(), # gate/norm: replicated 

216 moe_expert_placement=Shard(0), # expert params: Shard(0) on EP 

217 sp_in_src={"x_BLD": _multi_dim(tp=Shard(1), cp=Shard(1), ep=Replicate())}, 

218 sp_in_dst={"x_BLD": _multi_dim(tp=Replicate(), cp=Shard(1), ep=Replicate())}, 

219 sp_out_src=_out(Partial(), Shard(1)), 

220 sp_out_dst=_out(Shard(1), Shard(1)), 

221 nosp_in_src={"x_BLD": _multi_dim(tp=Replicate(), cp=Shard(1), ep=Replicate())}, 

222 nosp_in_dst={"x_BLD": _multi_dim(tp=Replicate(), cp=Shard(1), ep=Replicate())}, 

223 nosp_out_src=_out(Partial(), Shard(1)), 

224 nosp_out_dst=_out(Replicate(), Shard(1)), 

225 region_dispatch=False, # MoE forward has its own a2a; dispatch not allowed 

226 ), 

227} 

228 

229 

230# ──────────────────────────────────────────────────────────────────────────── 

231# Template → spec derivation (planner Phase 4 machinery, 05 §3.5) 

232# ──────────────────────────────────────────────────────────────────────────── 

233 

234def _infer_colwise_vs_rowwise(param_path: str, template: ShardingTemplate): 

235 """Infer the TP placement from the parameter name suffix: w2/down → rowwise, everything else → colwise.""" 

236 name = param_path.lower() 

237 if any(k in name for k in ("w2", "down_proj", "down.")): 

238 return template.rowwise_placement 

239 return template.colwise_placement 

240 

241 

242def _moe_expert_tp_placement(param_path: str, ndim: int, 

243 template: ShardingTemplate): 

244 """TP placement for MOE_EXPERT (revision D-08, ndim-aware per parameter). 

245 

246 When expert weights use a batched 3D layout [E, H_out, H_in] (ndim>=3), 

247 tensor dim 0 is the expert dim (owned by EP Shard(0)), so the TP 

248 colwise/rowwise sharding must apply at dim +1: 

249 colwise (shard H_out) → Shard(1); rowwise (shard the contraction dim 

250 H_in) → Shard(2). 

251 The per-expert 2D layout (experts.N.w1 [H_out, H_in]) keeps the standard 

252 Shard(0)/Shard(1) — but then EP Shard(0) would shard H_out, which is 

253 semantically invalid: EP must be implemented as "each rank holds a 

254 subset of experts" (module level), requiring family sharding_rules / 

255 SpecialHandler; it is outside template coverage. 

256 """ 

257 name = param_path.lower() 

258 is_rowwise = any(k in name for k in ("w2", "down_proj", "down.")) 

259 if ndim >= 3: 

260 return Shard(2) if is_rowwise else Shard(1) 

261 return template.rowwise_placement if is_rowwise else template.colwise_placement 

262 

263 

264def _placement_for_role( 

265 param_path: str, role: ParamRole, template: ShardingTemplate, 

266 has_tp: bool, has_ep: bool, ndim: int = 2, 

267) -> Optional[NamedPlacement]: 

268 """13 roles → placement mapping (05 §3.5 mapping table + D-08 

269 ndim-aware).""" 

270 if role in (ParamRole.COLWISE, ParamRole.EMBED, ParamRole.LM_HEAD, 

271 ParamRole.FUSED_QKV, ParamRole.FUSED_GATE_UP): 

272 return _multi_dim(tp=template.colwise_placement if has_tp else None, 

273 cp=Replicate(), ep=Replicate()) 

274 if role == ParamRole.ROWWISE: 

275 return _multi_dim(tp=template.rowwise_placement if has_tp else None, 

276 cp=Replicate(), ep=Replicate()) 

277 if role in (ParamRole.NORM, ParamRole.MOE_GATE): 

278 return _multi_dim(tp=template.norm_placement if has_tp else None, 

279 cp=Replicate(), ep=Replicate()) 

280 if role == ParamRole.MOE_EXPERT: 

281 # 05 §3.5 NOTE: when has_tp=False, use an explicit Replicate 

282 # (rather than omitting the TP key). 

283 # D-08: the TP dim of a 3D expert weight [E, H_out, H_in] is 

284 # shifted according to ndim. 

285 tp_p = (_moe_expert_tp_placement(param_path, ndim, template) 

286 if has_tp else Replicate()) 

287 return _multi_dim(tp=tp_p, cp=Replicate(), 

288 ep=template.moe_expert_placement if has_ep else None) 

289 if role == ParamRole.SHARED_EXPERT: 

290 # replicated along the EP dim; TP per w1/w3(colwise)/w2(rowwise) 

291 tp_p = _infer_colwise_vs_rowwise(param_path, template) 

292 return _multi_dim(tp=tp_p if has_tp else None, 

293 cp=Replicate(), ep=Replicate()) 

294 if role == ParamRole.BIAS: 

295 # Generic bias whose owning Linear role could not be inferred. 

296 # Named colwise/rowwise Linear biases are classified with their 

297 # weight role before reaching this fallback. 

298 return _multi_dim(tp=Replicate(), cp=Replicate(), ep=Replicate()) 

299 if role == ParamRole.REPLICATED: 

300 # MLA down-projections etc. (explicitly assigned via 

301 # family sharding_rules): replicate on all dims. 

302 # The output latent is identical within the TP group, so the 

303 # input contract of the downstream q_b/kv_b (COLWISE), sharded 

304 # along the head dim, matches standard attention. 

305 return _multi_dim(tp=Replicate(), cp=Replicate(), ep=Replicate()) 

306 # SPECIAL → Phase 6; SKIP → not sharded 

307 return None 

308 

309 

310def _build_param_placements( 

311 boundary_fqn: str, group: List[Tuple[str, ParamRole]], 

312 template: ShardingTemplate, has_tp: bool, has_ep: bool, 

313 param_ndims: Optional[Dict[str, int]], 

314) -> Dict[str, NamedPlacement]: 

315 """Build relative parameter paths and their resolved placements.""" 

316 params = {} 

317 for param_fqn, role in group: 

318 param_path = param_fqn[len(boundary_fqn) + 1:] 

319 ndim = (param_ndims or {}).get(param_fqn, 2) 

320 placement = _placement_for_role( 

321 param_path, role, template, has_tp, has_ep, ndim=ndim, 

322 ) 

323 if placement is not None: 

324 params[param_path] = placement 

325 return params 

326 

327 

328def _build_spec_from_template( 

329 templates, boundary_fqn: str, group: List[Tuple[str, ParamRole]], 

330 template: ShardingTemplate, sequence_parallel: bool, loss_parallel: bool, 

331 mesh_dim_names: Tuple[str, ...], param_ndims: Optional[Dict[str, int]] = None, 

332) -> Optional[ModuleShardingSpec]: 

333 """Template + ParamRole → ModuleShardingSpec (05 §3.5 Template Mapping).""" 

334 has_tp = "tp" in mesh_dim_names 

335 has_cp = "cp" in mesh_dim_names 

336 has_ep = "ep" in mesh_dim_names 

337 # The derived spec is materialized directly with concrete dicts (None 

338 # is the "undeclared" semantics on the override input side; derived 

339 # artifacts always hold concrete values). 

340 spec = ModuleShardingSpec(params=_build_param_placements( 

341 boundary_fqn, group, template, has_tp, has_ep, param_ndims, 

342 )) 

343 

344 # Step 2: select the I/O contract per the SP switch (deep copy, so 

345 # chain propagation cannot dirty the shared templates) 

346 if sequence_parallel: 

347 spec.in_src = copy.deepcopy(template.sp_in_src) 

348 spec.in_dst = copy.deepcopy(template.sp_in_dst) 

349 spec.out_src = copy.deepcopy(template.sp_out_src) 

350 spec.out_dst = copy.deepcopy(template.sp_out_dst) 

351 else: 

352 spec.in_src = copy.deepcopy(template.nosp_in_src) 

353 spec.in_dst = copy.deepcopy(template.nosp_in_dst) 

354 spec.out_src = copy.deepcopy(template.nosp_out_src) 

355 spec.out_dst = copy.deepcopy(template.nosp_out_dst) 

356 

357 # Step 2.5: lm_head's out_dst depends on loss_parallel (a runtime 

358 # decision). 

359 # The CP dim is always Shard(1) (D-07/R8): under CP the loss is 

360 # computed on the local chunk; no gather is performed. 

361 if template is templates.get("lm_head"): 

362 cp_placement = Shard(1) if has_cp else Replicate() 

363 for input_contract in (spec.in_src, spec.in_dst): 

364 for placements in input_contract.values(): 

365 placements[CP] = cp_placement 

366 spec.out_src = _multi_dim( 

367 tp=Shard(-1), 

368 cp=cp_placement, 

369 ep=Replicate(), 

370 ) 

371 spec.out_dst = _multi_dim( 

372 tp=Shard(-1) if loss_parallel else Replicate(), 

373 cp=cp_placement, 

374 ep=Replicate(), 

375 ) 

376 

377 # Step 2.6: embed's CP contract (revision D-05): the CP data 

378 # pipeline (shard_batch_for_cp, 05 §6.3.4) has already sharded 

379 # input_ids along CP — the CP dim of in/out is Shard(1) rather than 

380 # the template's default Replicate, otherwise the boundary would 

381 # scatter the already-sharded chunk a second time (the sequence 

382 # would be sharded twice). 

383 if template is templates.get("embed") and has_cp: 

384 spec.in_src = {"input": _multi_dim(tp=Replicate(), cp=Shard(1), 

385 ep=Replicate())} 

386 spec.in_dst = {"input": _multi_dim(tp=Replicate(), cp=Shard(1), 

387 ep=Replicate())} 

388 spec.out_src = _multi_dim(tp=Partial(), cp=Shard(1), ep=Replicate()) 

389 

390 # Step 3: special flags 

391 spec.region_dispatch = template.region_dispatch 

392 if template.needs_cp_attn: 

393 spec._needs_cp_attn = True # pylint: disable=protected-access # planner owns the spec DSL internals 

394 

395 # Step 4: normalize out_src/out_dst scalar shorthand 

396 return _normalize_out_fields(spec) 

397 

398 

399def _finalize_tp_local_attr_plans( 

400 plan: ShardingPlan, model, *, tp_size: int, 

401 mesh_dim_names: Tuple[str, ...], 

402) -> None: 

403 """Build internal auto/user TP-local attribute plans after overrides.""" 

404 modules = dict(model.named_modules()) 

405 for module_fqn, spec in plan.modules.items(): 

406 module = modules.get(module_fqn) 

407 if module is None: 

408 raise ValueError( 

409 f"Cannot finalize TP-local attributes: module " 

410 f"{module_fqn!r} is not present in model.named_modules()") 

411 spec._tp_local_attr_plan = build_tp_local_attr_plan( # pylint: disable=protected-access # planner owns the spec DSL internals 

412 module, spec, module_fqn, tp_size, mesh_dim_names, 

413 )