Coverage for  / home / jenkins / .local / lib / python3.10 / site-packages / hyper_parallel / codegen / inline / framework_spec.py: 16%

201 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"""Resolve inline codegen declarations from structure, not family render specs. 

16 

17This module replaces per-family ``render_spec.py`` files. Every fact a 

18generated artifact needs is derived from the family's *existing runtime 

19providers* plus the frozen plan, never from a family-name dispatch table: 

20 

21* a YAML ``replace_module._target_`` names the family's own 

22 ``@module_replacement`` factory; inspecting that factory's source reveals 

23 which generic component it constructs (``RMSNorm`` / ``GroupedExperts`` / 

24 ``GQAAttention``) and, for fused attention, the kernel entry it hands over; 

25* a YAML ``replace_module._target_`` that names a generic component class 

26 directly (under ``hyper_parallel.components.``) resolves through that 

27 component's own wrap-style constructor -- the same declaration the native 

28 ``_target_replacement_factory`` builds, so no family factory is required; 

29* a YAML ``local_compute_fn._target_`` that names a known MoE archetype 

30 factory resolves to that archetype's strategy kind / target class / boundary 

31 sub-patterns / EP body keys; 

32* a CP ``inner_wrapper`` target under the ``models/<family>/adapter`` 

33 convention resolves to the framework CP attention strategy (imports only). 

34 

35The source class name an artifact must model (``Qwen3MoeRMSNorm``) is not 

36duplicated here either: it is read from the rule's own ``module_type``, which 

37the YAML already carries for its type check. No model-family codegen 

38declaration remains. 

39 

40Heavy resolution (importing the real generic component to render a fused 

41attention class) stays lazy: it only runs when a fused-attention target is 

42actually present, so importing this module never pulls torch / torch_npu. 

43Resolved specs are cached so repeated lookups stay cheap and stable (callers 

44may rely on object identity across calls). 

45""" 

46 

47from __future__ import annotations 

48 

49from dataclasses import asdict 

50import importlib 

51import inspect 

52from typing import Any, Iterable, Mapping 

53 

54from hyper_parallel.codegen.inline.ir import ImportPatch, ModuleSnippetPatch 

55from hyper_parallel.codegen.inline.recognition import ( 

56 ReplacementArgument, 

57 ReplacementTarget, 

58 adapter_path_parts, 

59 archetype_for_factory, 

60 class_name_of, 

61 family_from_target, 

62 replacement_target, 

63) 

64from hyper_parallel.codegen.inline.spec_bundle import ReplacementSpec, StrategySpec 

65 

66#: The framework CP attention strategy kind (structure-keyed, not family-named). 

67CP_ATTENTION_KIND = "cp_attention" 

68 

69_EP_IMPORTS = ( 

70 ImportPatch("hyper_parallel.distributed.expert_parallel.experts", ("moe_ep_forward",)), 

71) 

72 

73 

74def _target_path(value: Any) -> str | None: 

75 """Return the dotted ``_target_`` of a projected override value.""" 

76 if value is None: 

77 return None 

78 if isinstance(value, str): 

79 return value 

80 if isinstance(value, Mapping): 

81 target = value.get("_target_") or value.get("target") 

82 return str(target) if target else None 

83 return None 

84 

85 

86def _family_provider(model_type: str | None, provider: str) -> Any | None: 

87 """Return one family adapter provider module, or ``None`` if unregistered.""" 

88 if not model_type: 

89 return None 

90 from hyper_parallel.models.registry import get_model_adapter # pylint: disable=C0415 

91 

92 adapter = get_model_adapter(model_type) 

93 loader = getattr(adapter, provider, None) if adapter is not None else None 

94 if loader is None: 

95 return None 

96 return loader() 

97 

98 

99def _family_key(model_type: str | None, target: str | None) -> str | None: 

100 """Canonical family key for one lookup. 

101 

102 Every spelling of an identity (``qwen3_moe`` / ``Qwen3MoeForCausalLM``) 

103 normalizes to the registered ``model_type``, so repeated lookups share one 

104 resolved spec -- callers may rely on object identity. A target under the 

105 ``models/<family>/adapter`` convention supplies the family when the caller 

106 did not name one. An unregistered identity is kept verbatim so resolution 

107 still fails closed (``None``) instead of falling back to another family. 

108 """ 

109 if model_type is None: 

110 model_type = family_from_target(target) 

111 if not model_type: 

112 return None 

113 from hyper_parallel.models.registry import get_model_adapter # pylint: disable=C0415 

114 

115 adapter = get_model_adapter(model_type) 

116 return adapter.model_type if adapter is not None else model_type 

117 

118 

119def _factory_function(target: str, model_type: str | None): 

120 """Resolve a YAML replacement ``_target_`` to the family's real factory. 

121 

122 Returns ``None`` when the target is not a factory exported by this family's 

123 replacements provider — a target the framework cannot structurally resolve. 

124 """ 

125 module = _family_provider(model_type, "replacements") 

126 if module is None: 

127 return None 

128 prefix = module.__name__ + "." 

129 if not target.startswith(prefix): 

130 return None 

131 name = target[len(prefix):] 

132 if "." in name: 

133 return None 

134 return getattr(module, name, None) 

135 

136 

137#: YAML ``replace_module`` targets under this prefix name a generic component 

138#: class directly (``hyper_parallel.components.modules.engram.EngramModule``) 

139#: -- the declaration the native path builds through 

140#: ``_target_replacement_factory`` as ``Cls(module=..., module_fqn=..., 

141#: context=...)``. 

142_COMPONENTS_PREFIX = "hyper_parallel.components." 

143 

144 

145def _direct_component_class(target: str) -> type | None: 

146 """Import the generic component class a target names directly. 

147 

148 Fail closed: only a ``hyper_parallel.components.*`` path that resolves to 

149 a class yields a component. Any other target (a family factory path, a 

150 user helper, a typo) is not a component declaration and returns ``None``. 

151 """ 

152 if not target.startswith(_COMPONENTS_PREFIX): 

153 return None 

154 module_name, _, attr = target.rpartition(".") 

155 if not module_name or not attr: 

156 return None 

157 try: 

158 module = importlib.import_module(module_name) 

159 except ImportError: 

160 return None 

161 component = getattr(module, attr, None) 

162 return component if isinstance(component, type) else None 

163 

164 

165def _component_replacement_target(component: type) -> ReplacementTarget | None: 

166 """Read a wrap-style component's own constructor as its replacement target. 

167 

168 The structural mirror of ``_target_replacement_factory``: a component 

169 whose constructor accepts the wrapped ``module`` declares the same 

170 replacement a family factory does -- the matched source instance is 

171 handed over (``wraps_source``), and each remaining framework keyword the 

172 native factory passes (``module_fqn`` / ``context``) applies the 

173 component's own constructor default. 

174 """ 

175 parameters = inspect.signature(component).parameters 

176 if "module" not in parameters: 

177 return None 

178 arguments = tuple( 

179 ReplacementArgument(name, None) 

180 for name in ("module_fqn", "context") 

181 if name in parameters 

182 ) 

183 return ReplacementTarget( 

184 component=component.__name__, 

185 module=component.__module__, 

186 wraps_source=True, 

187 arguments=arguments, 

188 ) 

189 

190 

191def _component_class(target: ReplacementTarget) -> type: 

192 """Import the component class one factory constructs. 

193 

194 Lazy by construction: the component's own module is imported only when a 

195 rule that constructs it is actually present (``components.modules`` 

196 re-exports lazily, so this pulls in one submodule, never the whole set). 

197 """ 

198 module = importlib.import_module(target.module) 

199 component = getattr(module, target.component, None) 

200 if not isinstance(component, type): 

201 raise RuntimeError( 

202 f"codegen: {target.module!r} does not export the class " 

203 f"{target.component!r} the replacement factory constructs" 

204 ) 

205 return component 

206 

207 

208def _render_arguments( 

209 target: ReplacementTarget, component: type 

210) -> tuple[str, ...] | None: 

211 """Render the constructor keywords the generated call passes. 

212 

213 Arguments the factory binds to its own parameters have no value at 

214 generation time, so the component's own constructor default is emitted -- 

215 the framework never restates a component's keyword shape. ``None`` means the 

216 component does not accept one of the keywords the factory passes, i.e. the 

217 factory constructs an interface codegen cannot sink. 

218 """ 

219 parameters = inspect.signature(component).parameters 

220 rendered: list[str] = [] 

221 for argument in target.arguments: 

222 if argument.value is not None: 

223 rendered.append(f"{argument.name}={argument.value}") 

224 continue 

225 if argument.name not in parameters: 

226 return None 

227 rendered.append(f"{argument.name}={parameters[argument.name].default!r}") 

228 return tuple(rendered) 

229 

230 

231def _interface_function(factory: Any, target: ReplacementTarget) -> Any: 

232 """The family kernel entry the factory hands its component.""" 

233 interface = getattr(factory, "__globals__", {}).get(target.interface) 

234 if not callable(interface): 

235 raise RuntimeError( 

236 f"codegen: replacement factory {getattr(factory, '__name__', factory)!r} " 

237 f"hands over {target.interface!r}, which its module does not define" 

238 ) 

239 return interface 

240 

241 

242def _build_replacement_spec( 

243 factory: Any, target: ReplacementTarget, old_ctor: str 

244) -> ReplacementSpec | None: 

245 """Build the source-level replacement one factory declares. 

246 

247 Every field comes from the factory's own structure: the class it constructs 

248 (and the module it imports it from), whether it hands the matched module over 

249 as a wrap, the constructor keywords it passes, and the family interface it 

250 hands to the component -- which is what makes the component's class rendered 

251 (visible in the artifact) instead of merely imported. ``None`` means the 

252 construction is not one codegen can sink into the generated ``__init__``. 

253 """ 

254 component = _component_class(target) 

255 keyword_args = _render_arguments(target, component) 

256 if keyword_args is None: 

257 return None 

258 # A component that receives a family interface is rendered into the artifact, 

259 # so its class is defined there -- importing it as well would be dead weight 

260 # the rendered definition shadows. 

261 imports: list[ImportPatch] = ( 

262 [] if target.interface else [ImportPatch(target.module, (target.component,))] 

263 ) 

264 snippets: tuple[ModuleSnippetPatch, ...] = () 

265 if target.interface: 

266 from hyper_parallel.codegen.inline.attention import ( # pylint: disable=C0415 

267 render_attention_class, 

268 ) 

269 

270 expanded = render_attention_class( 

271 component, interface=_interface_function(factory, target) 

272 ) 

273 imports.extend( 

274 ImportPatch(module="", names=(), raw=line) for line in expanded.imports 

275 ) 

276 snippets = (ModuleSnippetPatch(expanded.source),) 

277 mode = "wrap_source" if target.wraps_source else "name" 

278 return ReplacementSpec( 

279 old_ctor=old_ctor, 

280 new_ctor=target.component, 

281 mode=mode, 

282 keyword_args=keyword_args, 

283 imports=tuple(imports), 

284 remove_class=not target.wraps_source, 

285 snippets=snippets, 

286 replacement_note=( 

287 f"{target.component} wraps {old_ctor}; the original class is kept " 

288 "as the wrapper input." 

289 if target.wraps_source 

290 else f"{target.component} replaces {old_ctor}." 

291 ), 

292 ) 

293 

294 

295#: Resolved-spec caches. Keyed by everything the resolution reads, so a 

296#: repeated lookup returns the same object (callers may compare identity) and 

297#: the expensive fused-attention render runs once. 

298_REPLACEMENT_CACHE: dict[tuple[str | None, Any, str | None], ReplacementSpec | None] = {} 

299_STRATEGY_CACHE: dict[tuple[str | None, str | None, bool], StrategySpec | None] = {} 

300 

301 

302def replacement_spec_for( 

303 target: str | None, 

304 model_type: str | None = None, 

305 *, 

306 module_type: str | None = None, 

307) -> ReplacementSpec | None: 

308 """Resolve a ``replace_module`` target to its structural component spec. 

309 

310 ``module_type`` is the matched source module's dotted type path from the 

311 YAML rule; the source class name (``old_ctor``) is its class name — a 

312 property of the matched source module, never a hand-written literal. 

313 """ 

314 if target is None: 

315 return None 

316 if not adapter_path_parts(target): 

317 return _component_replacement_spec(target, model_type, module_type=module_type) 

318 model_type = _family_key(model_type, target) 

319 factory = _factory_function(target, model_type) 

320 if factory is None: 

321 return None 

322 # The spec is derived from the *factory's* structure, so the factory is 

323 # part of the key: a target re-pointed at a different factory (or a family 

324 # re-registered with one) must not reuse the previous resolution. 

325 key = (model_type, factory, module_type) 

326 if key in _REPLACEMENT_CACHE: 

327 return _REPLACEMENT_CACHE[key] 

328 spec = _resolve_replacement_spec(factory, model_type, module_type, target) 

329 _REPLACEMENT_CACHE[key] = spec 

330 return spec 

331 

332 

333def _resolve_replacement_spec( 

334 factory: Any, model_type: str | None, module_type: str | None, target: str 

335) -> ReplacementSpec | None: 

336 """One construction path for every replacement: read the factory. 

337 

338 Whatever component a family's factory constructs, the spec is derived from 

339 that factory's own source, so a new family (or a new component inside one) 

340 needs no codegen declaration. A factory with no constructor call to sink -- 

341 or a rule with no ``module_type`` naming the source class it replaces -- has 

342 no spec, and the rule is left to the native path. 

343 """ 

344 del model_type, target 

345 # The source class the constructor call replaces: without the rule's 

346 # ``module_type`` there is no call site to rewrite, and guessing one would 

347 # silently target the wrong class. 

348 old_ctor = class_name_of(module_type) 

349 if not old_ctor: 

350 return None 

351 replacement = replacement_target(factory) 

352 if replacement is None: 

353 return None 

354 return _build_replacement_spec(factory, replacement, old_ctor) 

355 

356 

357def _component_replacement_spec( 

358 target: str, model_type: str | None, *, module_type: str | None 

359) -> ReplacementSpec | None: 

360 """Resolve a target that names a generic component class directly. 

361 

362 The component's own constructor is the declaration: the spec is derived 

363 from it exactly as the factory channel derives one from the factory's 

364 construction call, so a wrap-style component needs no family factory to 

365 be sunk into the generated ``__init__``. 

366 """ 

367 component = _direct_component_class(target) 

368 if component is None: 

369 return None 

370 key = (model_type, component, module_type) 

371 if key in _REPLACEMENT_CACHE: 

372 return _REPLACEMENT_CACHE[key] 

373 spec = None 

374 old_ctor = class_name_of(module_type) 

375 if old_ctor: 

376 replacement = _component_replacement_target(component) 

377 if replacement is not None: 

378 # No interface: a direct component hands over no family kernel, 

379 # so the factory argument of _build_replacement_spec is unused. 

380 spec = _build_replacement_spec(None, replacement, old_ctor) 

381 _REPLACEMENT_CACHE[key] = spec 

382 return spec 

383 

384 

385def strategy_spec_for( 

386 target: str | None, 

387 model_type: str | None = None, 

388 *, 

389 inner_wrapper: bool = False, 

390) -> StrategySpec | None: 

391 """Resolve a strategy target from structure. 

392 

393 A known MoE archetype factory (EP) resolves to its structural strategy; a 

394 CP ``inner_wrapper`` target under the adapter convention resolves to the 

395 framework CP attention strategy (imports only). Anything else is 

396 unresolvable and yields ``None`` — recognition never guesses a strategy. 

397 """ 

398 if target is None: 

399 return None 

400 model_type = _family_key(model_type, target) 

401 key = (target, model_type, inner_wrapper) 

402 if key in _STRATEGY_CACHE: 

403 return _STRATEGY_CACHE[key] 

404 spec = _resolve_strategy_spec(target, model_type, inner_wrapper) 

405 _STRATEGY_CACHE[key] = spec 

406 return spec 

407 

408 

409def _resolve_strategy_spec( 

410 target: str, model_type: str | None, inner_wrapper: bool 

411) -> StrategySpec | None: 

412 archetype = archetype_for_factory(target) 

413 if archetype is not None: 

414 return _ep_strategy_spec(archetype) 

415 if inner_wrapper and adapter_path_parts(target): 

416 return StrategySpec(kind=CP_ATTENTION_KIND, imports=()) 

417 return None 

418 

419 

420def _ep_strategy_spec(archetype: Any) -> StrategySpec: 

421 """Build the EP strategy spec: the inlined forward plus its imports. 

422 

423 The inlined EP forward orchestrates through the instance-attribute channel 

424 (``self.ep_enable`` / ``self._hp_ep_group``) like every other generated 

425 forward, so it declares no module-level snippet. 

426 """ 

427 from hyper_parallel.codegen.inline.templates import moe_ep_forward_body # pylint: disable=C0415 

428 

429 return StrategySpec( 

430 kind=archetype.kind, 

431 target_class=archetype.target_class, 

432 body_template=moe_ep_forward_body( 

433 router_kind=archetype.router_kind, shared=archetype.shared 

434 ), 

435 imports=_EP_IMPORTS, 

436 strip_boundary_subpatterns=archetype.strip_boundary_subpatterns, 

437 ) 

438 

439 

440def interface_component_class(model_type: str | None) -> str | None: 

441 """The class this family's factories render, because one hands over an interface. 

442 

443 Structural: the component is named by the replacement factory itself, so 

444 codegen carries no fused-attention class literal. A rendered component 

445 keeps the component's own ``forward`` and therefore takes a *compiled 

446 boundary* (``hp_install_boundaries``) rather than external state — see 

447 :func:`external_state_classes_for`. 

448 """ 

449 module = _family_provider(model_type, "replacements") 

450 if module is None: 

451 return None 

452 for name in dir(module): 

453 factory = getattr(module, name) 

454 if not callable(factory) or getattr(factory, "_hp_module_replacement", False) is not True: 

455 continue 

456 target = replacement_target(factory) 

457 if target is not None and target.interface: 

458 return target.component 

459 return None 

460 

461 

462def external_state_classes_for( 

463 model_type: str | None, 

464 injections: Iterable[Mapping[str, Any]] | None = None, 

465) -> tuple[str, ...]: 

466 """Classes whose ``forward`` an inline strategy body *replaces*. 

467 

468 Structure-proven, not declared: every strategy target this plan injects is 

469 resolved to its :class:`StrategySpec`, and a spec that carries a 

470 ``body_template`` rewrites that class's whole ``forward`` with an inline 

471 orchestration body (the MoE EP shell). Such a class owns its collectives, 

472 so it must keep its parameter sharding without a compiled boundary — it is 

473 skipped by the boundary / compute / inner-wrapper installers and reads its 

474 parallel channel from install-time instance attributes instead. 

475 

476 A *rendered* component class (the class this family's replacement 

477 factories build, e.g. the fused attention) is deliberately NOT one of 

478 these: its forward is the component's own plus the emitted boundary form, 

479 so the shared compiled-boundary path 

480 (``hp_install_boundaries`` / ``hp_apply_inner_wrapper``) owns it. 

481 """ 

482 inlined: set[str] = set() 

483 for record in injections or (): 

484 spec = strategy_spec_for( 

485 _target_path(record.get("local_compute_fn")), model_type 

486 ) 

487 if spec is None or spec.body_template is None or spec.target_class is None: 

488 continue 

489 inlined.add(spec.target_class) 

490 return tuple(sorted(inlined)) 

491 

492 

493def declaration_summary( 

494 model_type: str | None, 

495 overrides: Iterable[Mapping[str, Any]] | None = None, 

496) -> dict[str, Any]: 

497 """Canonical, JSON-safe summary of the declarations an override set resolves to. 

498 

499 Adapter declarations live outside the codegen implementation tree, so their 

500 resolved content must invalidate cached artifacts. Summarizing exactly the 

501 YAML targets this override set will inline keeps that invalidation faithful 

502 without a per-family declaration site. 

503 """ 

504 replacement_specs: dict[str, Any] = {} 

505 strategy_specs: dict[str, Any] = {} 

506 for entry in overrides or (): 

507 replace_target = _target_path(entry.get("replace_module")) 

508 if replace_target is not None: 

509 spec = replacement_spec_for( 

510 replace_target, model_type, module_type=entry.get("module_type") 

511 ) 

512 replacement_specs[replace_target] = asdict(spec) if spec is not None else None 

513 local_target = _target_path(entry.get("local_compute_fn")) 

514 if local_target is not None: 

515 spec = strategy_spec_for(local_target, model_type) 

516 strategy_specs[local_target] = asdict(spec) if spec is not None else None 

517 inner_target = _target_path(entry.get("inner_wrapper")) 

518 if inner_target is not None: 

519 spec = strategy_spec_for(inner_target, model_type, inner_wrapper=True) 

520 strategy_specs[inner_target] = asdict(spec) if spec is not None else None 

521 return {"replacement_specs": replacement_specs, "strategy_specs": strategy_specs} 

522 

523 

524__all__ = [ 

525 "CP_ATTENTION_KIND", 

526 "declaration_summary", 

527 "external_state_classes_for", 

528 "interface_component_class", 

529 "replacement_spec_for", 

530 "strategy_spec_for", 

531]