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

161 statements  

« prev     ^ index     » next       coverage.py v7.13.1, created at 2026-11-15 07: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"""applier: apply-phase orchestration — injection preflight (fail-fast before 

16any mutation), apply-stage mesh selection, special-handler dispatch, and the 

17Phase C per-boundary driving loop. 

18 

19The forward writers themselves live in ``forward_rewriter.py``; Target/spec 

20resolution lives in ``rule_resolver.py``; parameter sharding lives in 

21``parameter_sharding.py``. 

22""" 

23 

24import importlib.metadata 

25import logging 

26 

27import numpy as np 

28 

29from hyper_parallel.components.modules.module_family import get_family_build_info 

30from hyper_parallel.core.dtensor.device_mesh import DeviceMesh, init_device_mesh 

31from hyper_parallel.distributed._builder.forward_rewriter import ( 

32 _bind_input_indices, 

33 _descendant_boundary_fqns, 

34 _install_bias_suppression, 

35 _is_vocab_parallel_embed, 

36 _keep_loss_parallel_output, 

37 _wrap_inner_attention, 

38 _wrap_local_region_forward, 

39 _wrap_production_forward, 

40 _wrap_validate_forward, 

41 _wrap_vocab_parallel_embedding, 

42) 

43from hyper_parallel.distributed._builder.parameter_sharding import ( 

44 _resolve_module, 

45) 

46from hyper_parallel.distributed._builder.planner import ( 

47 _get_architecture, 

48) 

49from hyper_parallel.distributed._builder.precompiled_boundary import ( 

50 PrecompiledBoundary, 

51) 

52from hyper_parallel.distributed._builder.rule_resolver import ( 

53 _require_region_dispatch, 

54 _resolve_local_compute_fn, 

55) 

56from hyper_parallel.distributed._builder.special_handlers import ( 

57 SPECIAL_HANDLERS, 

58) 

59from hyper_parallel.distributed._builder.tp_collective_lowering import ( 

60 create_tp_collective_lowerer, 

61) 

62from hyper_parallel.distributed.tensor_parallel.head_count import ( 

63 maybe_update_head_counts, 

64) 

65 

66logger = logging.getLogger(__name__) 

67 

68 

69# ──────────────────────────────────────────────────────────────────────────── 

70# Injection preflight (any mutation happens only after all validators pass) 

71# ──────────────────────────────────────────────────────────────────────────── 

72 

73def _validate_inner_wrapper_injections(plan, cp_mesh, transformers_version): 

74 """Validate the structural contract of every inner wrapper.""" 

75 # Lazy import: wrappers imports forward_rewriter at module level; 

76 # keeping it lazy decouples the applier's import graph. 

77 from hyper_parallel.distributed.context_parallel.wrappers import ( # pylint: disable=C0415 

78 INNER_WRAPPER_REQUIREMENTS, 

79 ) 

80 for fqn, spec in plan.modules.items(): 

81 wrapper = getattr(spec, "inner_wrapper", None) 

82 target = getattr(spec, "inner_target", None) 

83 region_dispatch = getattr(spec, "region_dispatch", None) 

84 if wrapper is None and target is not None: 

85 raise ValueError( 

86 f"Invalid inner-wrapper plan at boundary {fqn!r} " 

87 f"(transformers={transformers_version}): inner_target={target!r} " 

88 "locates a module but inner_wrapper is missing. Declare both " 

89 "fields or remove inner_target." 

90 ) 

91 if wrapper is not None and target is None: 

92 raise ValueError( 

93 f"Invalid inner-wrapper plan at boundary {fqn!r} " 

94 f"(transformers={transformers_version}): " 

95 f"inner_wrapper={wrapper!r}, inner_target=None, " 

96 f"region_dispatch={region_dispatch!r}. Declare " 

97 "inner_target='self' for the boundary module or an explicit " 

98 "child attribute name." 

99 ) 

100 requirements = ( 

101 INNER_WRAPPER_REQUIREMENTS.get(wrapper) 

102 if isinstance(wrapper, str) else None 

103 ) 

104 if requirements is not None: 

105 required_dispatch = requirements["region_dispatch"] 

106 if region_dispatch is not required_dispatch: 

107 raise ValueError( 

108 f"Invalid built-in CP wrapper plan at boundary {fqn!r} " 

109 f"(transformers={transformers_version}): " 

110 f"inner_wrapper={wrapper!r} contains CP communication and " 

111 f"requires region_dispatch={required_dispatch}, got " 

112 f"{region_dispatch!r}. Set region_dispatch on the " 

113 f"match={fqn + '.forward'!r} method replacement." 

114 ) 

115 if requirements["requires_cp"] and ( 

116 cp_mesh is None or cp_mesh.size() <= 1): 

117 raise ValueError( 

118 f"Invalid built-in CP wrapper plan at boundary {fqn!r}: " 

119 f"inner_wrapper={wrapper!r} requires an active cp mesh, " 

120 "but the plan has no cp axis with size > 1" 

121 ) 

122 if wrapper is not None and target != "self" and getattr( 

123 spec, "inner_out_src", None) is None: 

124 raise ValueError( 

125 f"Invalid inner-wrapper plan at boundary {fqn!r}: wrapper " 

126 f"{wrapper!r} targets child {target!r}, but inner_out_src is " 

127 "missing. Declare inner_out_src='first_input' for a " 

128 "layout-preserving single output, provide explicit output " 

129 "placements, or use inner_target='self' to reuse boundary " 

130 "out_src." 

131 ) 

132 

133 

134def _validate_cp_compute_injections(plan, cp_mesh): 

135 """Require an explicit forward replacement for CP attention boundaries.""" 

136 if cp_mesh is None or cp_mesh.size() <= 1: 

137 return 

138 for fqn, spec in plan.modules.items(): 

139 if fqn in plan.parallel_modules: 

140 continue 

141 if not spec.is_boundary or not getattr(spec, "_needs_cp_attn", False): 

142 continue 

143 if spec.inner_wrapper is not None or spec.local_compute_fn is not None: 

144 continue 

145 raise ValueError( 

146 f"cp_size={cp_mesh.size()} is active, but attention boundary " 

147 f"{fqn!r} has no CP-aware forward replacement. Declare:\n" 

148 " plan_overrides:\n" 

149 f" - match: {(fqn + '.forward')!r}\n" 

150 " when: cp\n" 

151 " replace:\n" 

152 " kind: method\n" 

153 " implementation: hyper_parallel.distributed.context_parallel." 

154 "wrappers.sdpa_hf_cp_wrapper\n" 

155 "Choose an implementation compatible with this attention's forward signature." 

156 ) 

157 

158 

159def _validate_ep_compute_injections(plan, model): 

160 """Require a compute source for each active EP-sharded boundary.""" 

161 # Lazy import: see _validate_inner_wrapper_injections. 

162 from hyper_parallel.distributed.expert_parallel.recipes import ( # pylint: disable=C0415 

163 EP_ARCHETYPE_SUGGESTIONS, 

164 ) 

165 for fqn, spec in plan.modules.items(): 

166 if fqn in plan.parallel_modules: 

167 continue 

168 if not spec.is_boundary or not getattr(spec, "_ep_size", 0): 

169 continue 

170 if spec.local_compute_fn is not None or spec.region_dispatch is False: 

171 continue 

172 suggestion = "" 

173 if model is not None: 

174 architecture = _get_architecture(model) 

175 archetype = EP_ARCHETYPE_SUGGESTIONS.get(architecture) 

176 if archetype is not None: 

177 suggestion = f"Candidate for {architecture!r}: {archetype!r}. " 

178 raise ValueError( 

179 f"ep_size={spec._ep_size} is active, but boundary {fqn!r} has " 

180 "no EP-aware compute source. " 

181 f"{suggestion}Declare one of:\n" 

182 " - match: '*.mlp.forward'\n" 

183 " when: ep\n" 

184 " replace:\n" 

185 " kind: method\n" 

186 " implementation: <EP-aware factory import path>\n" 

187 "or use a rule with region_dispatch: false when the module's " 

188 "original forward already owns the EP all-to-all." 

189 ) 

190 

191 

192def _preflight_compute_injection(plan, mesh, model=None): 

193 """Fail fast before mutating a plan with incomplete CP/EP injection.""" 

194 transformers_version = importlib.metadata.version("transformers") 

195 cp_mesh = _get_cp_submesh(mesh, plan.mesh_dim_names) 

196 _validate_inner_wrapper_injections(plan, cp_mesh, transformers_version) 

197 _validate_cp_compute_injections(plan, cp_mesh) 

198 _validate_ep_compute_injections(plan, model) 

199 

200 

201def _log_injection_choice(module_fqn, spec): 

202 """Observability enhancement: the resolution chosen for an injection 

203 boundary is immediately visible (one INFO line per boundary), closing 

204 the "declare → see the consequence" feedback loop. Must be called after 

205 _require_region_dispatch (the injection + region_dispatch combination is 

206 already validated as legal at this point).""" 

207 rd = getattr(spec, "region_dispatch", None) 

208 has_fn = getattr(spec, "local_compute_fn", None) is not None 

209 has_wrap = getattr(spec, "inner_wrapper", None) is not None 

210 if not (has_fn or has_wrap or rd is not None): 

211 return # ordinary boundary: no injection, axiomatic default dispatch-through — do not spam the log 

212 candidates = (("local_compute_fn", has_fn), ("inner_wrapper", has_wrap)) 

213 enabled = [] 

214 for name, is_enabled in candidates: 

215 if is_enabled: 

216 enabled.append(name) 

217 what = "+".join(enabled) or "the module's own forward (no fn injected, region_dispatch=False declared)" 

218 if rd is True: 

219 effect = ("validate dispatch-through true validation enabled " 

220 "(in-region strategy propagation + true out_src " 

221 "validation)") 

222 else: # False (None + injection was already intercepted by _require_region_dispatch) 

223 effect = ("black-box hosting (local execution inside the region, " 

224 "propagation checks skipped, declarative rewrap)") 

225 logger.info( 

226 "boundary %s: injection[%s], region_dispatch=%s → %s", 

227 module_fqn, what, rd, effect) 

228 

229 

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

231# Phase B: special handlers (registry dispatch only; concrete handlers live 

232# in special_handlers.py) 

233# ──────────────────────────────────────────────────────────────────────────── 

234 

235def _apply_plan_special_handlers(models, plan, mesh): 

236 """Run parameter handlers declared by the sharding plan.""" 

237 for model in models: 

238 for param_ref, handler_name in plan.special_handlers.items(): 

239 handler = SPECIAL_HANDLERS.get(handler_name) 

240 if handler is None: 

241 logger.warning( 

242 "SPECIAL_HANDLERS has no registered handler: %s", 

243 handler_name, 

244 ) 

245 continue 

246 module_fqn, param_name = param_ref.rsplit(".", 1) 

247 handler(_resolve_module(model, module_fqn), param_name, mesh) 

248 

249 

250# ──────────────────────────────────────────────────────────────────────────── 

251# Apply-stage mesh selection 

252# ──────────────────────────────────────────────────────────────────────────── 

253 

254def _get_active_mesh(mesh, mesh_dim_names): 

255 """Return the active sub-mesh aligned with plan.mesh_dim_names (the dimension set after stripping size=1 axes).""" 

256 names = tuple(getattr(mesh, "mesh_dim_names", ()) or ()) 

257 if names == tuple(mesh_dim_names): 

258 return mesh 

259 if mesh_dim_names and names and all(n in names for n in mesh_dim_names): 

260 return mesh[tuple(mesh_dim_names)] 

261 return mesh 

262 

263 

264def _get_tp_submesh(mesh, mesh_dim_names): 

265 if "tp" not in mesh_dim_names: 

266 return None 

267 return mesh["tp"] 

268 

269 

270def _get_cp_submesh(mesh, mesh_dim_names): 

271 if "cp" not in mesh_dim_names: 

272 return None 

273 return mesh["cp"] 

274 

275 

276def _get_ep_submesh(mesh, mesh_dim_names): 

277 if "ep" not in mesh_dim_names: 

278 return None 

279 return mesh["ep"] 

280 

281 

282def _expert_mesh_layout(mesh, mesh_dim_names, ep_size): 

283 """(shape, dim_names, rank_list) of the derived expert mesh (pure mapping; no process group is created). 

284 

285 D-10 TP-extend-EP (05 §6.4.8 / 06 §4.5.1): the expert domain = the full 

286 dense region (all ranks on the non-pp axes of the mesh, i.e. 

287 dp_replicate x dp_cp x tp). After a row-major flatten in mesh axis order, 

288 it is re-sliced as (edp = D/ep_size, ep = ep_size): 

289 - EP groups (inner, the a2a communication domain): ep_size consecutive 

290 ranks in flatten order -- tp is usually the innermost axis, so an EP 

291 group first spans the entire TP group and then extends to adjacent 

292 dp/cp ranks (isomorphic to MindSpeed TP-extend-EP / Megatron etp=1 with 

293 ep spanning TP). 

294 Example: mesh (dp=4, tp=2), ep_size=4 -> EP groups {0,1,2,3} / {4,5,6,7}; 

295 - edp groups (outer): expert data-parallel degree = D/ep_size. 

296 Expert weights are only Shard(0) on the ep axis (the expert dim); there is 

297 no second-axis sharding. 

298 """ 

299 if "pp" in mesh_dim_names: 

300 raise NotImplementedError( 

301 "D-10 TP-extend-EP v1 does not yet support the pp axis (call after splitting the mesh by stage)") 

302 arr = np.array(mesh.rank_list).reshape(mesh.mesh_shape) 

303 domain = int(np.prod(arr.shape)) 

304 if ep_size <= 0 or domain % ep_size != 0: 

305 raise ValueError( 

306 f"ep_size ({ep_size}) must divide the dense region ({domain})" 

307 ) 

308 edp = domain // ep_size 

309 derived = arr.reshape(edp, ep_size) 

310 return (edp, ep_size), ("edp", "ep"), tuple( 

311 int(r) for r in derived.flatten()) 

312 

313 

314def _build_expert_mesh(mesh, mesh_dim_names, ep_size): 

315 """D-10 (05 §6.4.8 / 06 §4.5.1): repartition the full dense region into the derived expert mesh (edp, ep).""" 

316 shape, names, rank_list = _expert_mesh_layout(mesh, mesh_dim_names, ep_size) 

317 # Propagate the no-backend (metadata-only) mode of the source mesh — 

318 # a meta mesh has no _dim_group_names (no process groups were created). 

319 return init_device_mesh(mesh.device_type, shape, mesh_dim_names=names, 

320 rank_list=rank_list, 

321 init_backend=hasattr(mesh, "_dim_group_names")) 

322 

323 

324def build_expert_mesh(mesh: DeviceMesh, ep_size: int) -> DeviceMesh: 

325 """Public: derive the D-10 TP-extend-EP expert mesh (edp, ep) from *mesh* 

326 (the FULL mesh — the dense region must include dp/cp axes). 

327 

328 Standalone helper for introspection and for custom code that needs the 

329 expert domain outside the injection path. Injected factories/wrappers do 

330 NOT need to call this: the framework derives the expert mesh once at 

331 apply time (shared by parameter sharding and injected compute) and hands 

332 it to them as the ``ep_mesh`` context. 

333 

334 Args: 

335 mesh: The full device mesh (dense region including dp/cp axes). 

336 ep_size: The expert-parallel group size. 

337 

338 Returns: 

339 The derived ``(edp, ep)`` expert mesh. 

340 """ 

341 return _build_expert_mesh(mesh, tuple(mesh.mesh_dim_names), ep_size) 

342 

343 

344# ──────────────────────────────────────────────────────────────────────────── 

345# Phase C: forward wrapping driving loop (05 §4.4) 

346# ──────────────────────────────────────────────────────────────────────────── 

347 

348def _boundary_post_order_key(item): 

349 """Sort key for boundary installation: deepest FQN first (post-order). 

350 

351 D-14 invariant 2 (05 §13.3): boundaries are wrapped in post-order — an 

352 outer boundary's local_compute_fn may cache inner forwards, and the 

353 unpack-scope exclusion (invariant 3) requires inner wrappers to be 

354 installed first. 

355 """ 

356 return -item[0].count(".") 

357 

358 

359def _apply_phase_c(model, plan, mesh, validate_mode, expert_mesh=None): 

360 """Phase C: wrap forward (production/validate/moe/cp/vocab_embed, five paths). 

361 

362 D-14 invariant 2 (05 §13.3): boundaries are wrapped in post-order (deepest 

363 FQN first) — an outer boundary's local_compute_fn may cache inner forwards, 

364 and the unpack-scope exclusion (invariant 3) requires inner wrappers to be 

365 installed first. 

366 """ 

367 mesh_dim_names = plan.mesh_dim_names 

368 cp_mesh = _get_cp_submesh(mesh, mesh_dim_names) 

369 tp_mesh = _get_tp_submesh(mesh, mesh_dim_names) 

370 boundary_op_lowerer = ( 

371 None 

372 if validate_mode 

373 else create_tp_collective_lowerer(mesh, mesh_dim_names) 

374 ) 

375 for module_fqn, spec in sorted( 

376 plan.modules.items(), key=_boundary_post_order_key): 

377 if module_fqn in plan.parallel_modules: 

378 continue 

379 if not spec.is_boundary: 

380 continue 

381 module = _resolve_module(model, module_fqn) 

382 family = get_family_build_info(module) 

383 if family is not None and family.layout is not None: 

384 continue 

385 boundary = PrecompiledBoundary( 

386 spec, 

387 mesh, 

388 mesh_dim_names, 

389 op_lowerer=boundary_op_lowerer, 

390 ) 

391 _bind_input_indices(boundary, module) 

392 # Injection discipline: a declared injection requires an explicit 

393 # region_dispatch; declaring True without any injection is redundant 

394 _require_region_dispatch(spec, source=f"boundary {module_fqn!r}") 

395 # Observability: the injection choice is immediately visible (the 

396 # "declare → consequence" feedback loop) 

397 _log_injection_choice(module_fqn, spec) 

398 

399 # Step 0.5 (D-22): rowwise bias defer — install the bias-free region 

400 # forward BEFORE inner-wrap/forward wrapping, in BOTH modes (the 

401 # in-region computation stays instruction-for-instruction identical). 

402 # The bias Parameter itself is untouched; it is added once after the 

403 # boundary exit reduction by _maybe_add_deferred_biases. 

404 if spec._deferred_bias_params: # pylint: disable=protected-access 

405 _install_bias_suppression(module, spec) 

406 

407 # Step 1: inner-wrap — the generic "weave into / replace the inner 

408 # forward" mechanism 

409 # (D-01'': production and validate inject the same wrapper, so the 

410 # in-region computation is instruction-for-instruction identical). 

411 # NOT gated on cp_mesh since the generalization: the derived gate is 

412 # the resolution chain itself (explicit inner_wrapper declaration -> 

413 # applied; nothing declared -> None -> no-op). cp_mesh may be None 

414 # (no cp axis) — the four shipped CP wrappers self-guard and fail 

415 # fast then; custom callables/Targets receive None and own their 

416 # semantics. 

417 # (_preflight_compute_injection has already failed fast when a CP 

418 # attention boundary declares nothing) 

419 _wrap_inner_attention( 

420 module, cp_mesh, spec=spec, mesh=mesh, 

421 mesh_dim_names=mesh_dim_names, tp_mesh=tp_mesh, 

422 ep_mesh=expert_mesh, validate_mode=validate_mode, 

423 module_fqn=module_fqn, 

424 ) 

425 

426 # Step 2: forward wrapping 

427 # local region path (D-03'): the gate is derived from the compute_fn 

428 # resolution chain (non-None means take the skeleton) — the remaining 

429 # sources after the explicit-injection rework are user 

430 # local_compute_fn (callable or factory Target) and the 

431 # region_dispatch=False gate; the built-in EP auto-injection link 

432 # was REMOVED (05 §4.4.3) 

433 compute_fn = _resolve_local_compute_fn( 

434 module, spec, mesh, mesh_dim_names, expert_mesh) 

435 if compute_fn is not None: 

436 if validate_mode: 

437 # D-17: inside the local region the module sees local 

438 # tensors in both modes -- validate rewrites cached head 

439 # counts only for local-region modules (boundary modules 

440 # keep global counts for DTensor dispatch) 

441 maybe_update_head_counts( 

442 module, spec, module_fqn, mesh, mesh_dim_names) 

443 # D-14 invariant 3 (05 §13.3): the region's temp-unwrap scope 

444 # excludes nested-boundary subtrees — their parameters must stay 

445 # DTensors for the inner validate islands (dispatch needs 

446 # __torch_function__) 

447 nested = _descendant_boundary_fqns(plan, module_fqn) 

448 if nested: 

449 # E5 (accuracy_fix_plan.md §3): make the nested-boundary call 

450 # contract visible at apply time 

451 logger.info( 

452 "local-region boundary %s: nested boundaries " 

453 "(communication_owner: nested_boundary): %s — their " 

454 "return values are already in the declared out_dst " 

455 "layout with TP communication sealed inside; the region " 

456 "compute MUST NOT apply compensating collectives " 

457 "(all-reduce/reduce-scatter/all-gather) to them", 

458 module_fqn, nested) 

459 _wrap_local_region_forward( 

460 module, boundary, spec, mesh, mesh_dim_names, 

461 validate_mode=validate_mode, compute_fn=compute_fn, 

462 exclude_subtrees=nested) 

463 elif validate_mode: 

464 _wrap_validate_forward( 

465 module, 

466 boundary, 

467 spec, 

468 mesh_dim_names, 

469 keep_output_dtensor=_keep_loss_parallel_output(plan, spec), 

470 ) 

471 else: 

472 # D-02: production vocab-parallel embedding masked wrapper 

473 if _is_vocab_parallel_embed(module, spec, tp_mesh): 

474 _wrap_vocab_parallel_embedding(module, tp_mesh) 

475 _wrap_production_forward( 

476 module, 

477 boundary, 

478 spec, 

479 keep_output_dtensor=_keep_loss_parallel_output(plan, spec), 

480 )