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

164 statements  

« prev     ^ index     » next       coverage.py v7.13.1, created at 2026-11-15 07:18 +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 CP ``inner_wrapper`` target under the ``models/<family>/adapter`` 

30 convention resolves to the framework CP attention strategy — imports 

31 and a recognizable inner forward rewrite additionally materializes an 

32 in-artifact CP dispatch subclass; 

33 

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

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

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

37declaration remains. 

38 

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

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

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

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

43may rely on object identity across calls). 

44""" 

45 

46from __future__ import annotations 

47 

48import importlib 

49import inspect 

50from typing import Any, Callable, cast 

51 

52from hyper_parallel.codegen.inline.ir import ( 

53 ImportPatch, 

54 ModuleSnippetPatch, 

55 ReplacementSpec, 

56 StrategySpec, 

57) 

58from hyper_parallel.codegen.inline.recognition import ( 

59 ReplacementArgument, 

60 ReplacementTarget, 

61 adapter_path_parts, 

62 class_name_of, 

63 family_from_target, 

64 inner_wrapper_target, 

65 replacement_target, 

66) 

67 

68_CP_DISPATCH_SHELL = '''if self.cp_enable: 

69 return {helper_name}(self, __hp_forward_args__) 

70 

71return self._forward_impl(__hp_forward_args__) 

72''' 

73 

74 

75def cp_dispatch_shell_body(helper_name: str) -> str: 

76 """Render CP dispatch around the extracted original forward.""" 

77 return _CP_DISPATCH_SHELL.format(helper_name=helper_name) 

78 

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

80CP_ATTENTION_KIND = "cp_attention" 

81 

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

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

84 if not model_type: 

85 return None 

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

87 

88 adapter = get_model_adapter(model_type) 

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

90 if loader is None: 

91 return None 

92 return loader() 

93 

94 

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

96 """Canonical family key for one lookup. 

97 

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

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

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

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

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

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

104 """ 

105 if model_type is None: 

106 model_type = family_from_target(target) 

107 if not model_type: 

108 return None 

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

110 

111 adapter = get_model_adapter(model_type) 

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

113 

114 

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

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

117 

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

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

120 """ 

121 module = _family_provider(model_type, "replacements") 

122 if module is None: 

123 return None 

124 prefix = module.__name__ + "." 

125 if not target.startswith(prefix): 

126 return None 

127 name = target[len(prefix):] 

128 if "." in name: 

129 return None 

130 return getattr(module, name, None) 

131 

132 

133#: Generic components are valid direct targets without a decorator. Model-owned 

134#: classes are accepted only when their native replacement decorator marks them. 

135_COMPONENTS_PREFIX = "hyper_parallel.components." 

136 

137 

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

139 """Import a generic or natively decorated replacement class.""" 

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

141 if not module_name or not attr: 

142 return None 

143 try: 

144 module = importlib.import_module(module_name) 

145 except ImportError: 

146 return None 

147 component = getattr(module, attr, None) 

148 if not isinstance(component, type): 

149 return None 

150 if ( 

151 target.startswith(_COMPONENTS_PREFIX) 

152 or getattr(component, "_hp_module_replacement", False) 

153 ): 

154 return component 

155 return None 

156 

157 

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

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

160 

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

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

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

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

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

166 component's own constructor default. 

167 """ 

168 parameters = inspect.signature(component).parameters 

169 if "module" not in parameters: 

170 return None 

171 arguments = tuple( 

172 ReplacementArgument(name, value) 

173 for name, value in (("module_fqn", "''"), ("context", "{}")) 

174 if name in parameters 

175 ) 

176 return ReplacementTarget( 

177 component=component.__name__, 

178 module=component.__module__, 

179 wraps_source=True, 

180 arguments=arguments, 

181 ) 

182 

183 

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

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

186 

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

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

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

190 """ 

191 module = importlib.import_module(target.module) 

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

193 if not isinstance(component, type): 

194 raise RuntimeError( 

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

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

197 ) 

198 return component 

199 

200 

201def _render_arguments( 

202 target: ReplacementTarget, component: type 

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

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

205 

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

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

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

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

210 factory constructs an interface codegen cannot sink. 

211 """ 

212 parameters = inspect.signature(component).parameters 

213 rendered: list[str] = [] 

214 for argument in target.arguments: 

215 if argument.value is not None: 

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

217 continue 

218 if argument.name not in parameters: 

219 return None 

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

221 return tuple(rendered) 

222 

223 

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

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

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

227 if not callable(interface): 

228 raise RuntimeError( 

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

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

231 ) 

232 return interface 

233 

234 

235def _build_replacement_spec( 

236 factory: Any, 

237 target: ReplacementTarget, 

238 old_ctor: str, 

239 model_type: str | None, 

240) -> ReplacementSpec | None: 

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

242 

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

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

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

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

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

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

249 """ 

250 component = _component_class(target) 

251 keyword_args = _render_arguments(target, component) 

252 if keyword_args is None: 

253 return None 

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

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

256 # the rendered definition shadows. 

257 imports: list[ImportPatch] = ( 

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

259 ) 

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

261 if target.interface: 

262 provider = _family_provider(model_type, "codegen") 

263 renderer = getattr(provider, "render_replacement_source", None) 

264 if not callable(renderer): 

265 return None 

266 render_source = cast(Callable[..., Any], renderer) 

267 expanded = render_source( # pylint: disable=not-callable 

268 component, interface=_interface_function(factory, target) 

269 ) 

270 imports.extend( 

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

272 ) 

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

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

275 return ReplacementSpec( 

276 old_ctor=old_ctor, 

277 new_ctor=target.component, 

278 mode=mode, 

279 keyword_args=keyword_args, 

280 imports=tuple(imports), 

281 remove_class=not target.wraps_source, 

282 snippets=snippets, 

283 replacement_note=( 

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

285 "as the wrapper input." 

286 if target.wraps_source 

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

288 ), 

289 ) 

290 

291 

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

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

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

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

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

297 

298 

299def replacement_spec_for( 

300 target: str | None, 

301 model_type: str | None = None, 

302 *, 

303 module_type: str | None = None, 

304) -> ReplacementSpec | None: 

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

306 

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

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

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

310 """ 

311 if target is None: 

312 return None 

313 if _direct_component_class(target) is not None: 

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

315 if not adapter_path_parts(target): 

316 return None 

317 model_type = _family_key(model_type, target) 

318 factory = _factory_function(target, model_type) 

319 if factory is None: 

320 return None 

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

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

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

324 key = (model_type, factory, module_type) 

325 if key in _REPLACEMENT_CACHE: 

326 return _REPLACEMENT_CACHE[key] 

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

328 _REPLACEMENT_CACHE[key] = spec 

329 return spec 

330 

331 

332def _resolve_replacement_spec( 

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

334) -> ReplacementSpec | None: 

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

336 

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

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

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

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

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

342 """ 

343 del target 

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

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

346 # silently target the wrong class. 

347 old_ctor = class_name_of(module_type) 

348 if not old_ctor: 

349 return None 

350 replacement = replacement_target(factory) 

351 if replacement is None: 

352 return None 

353 return _build_replacement_spec(factory, replacement, old_ctor, model_type) 

354 

355 

356def _component_replacement_spec( 

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

358) -> ReplacementSpec | None: 

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

360 

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

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

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

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

365 """ 

366 component = _direct_component_class(target) 

367 if component is None: 

368 return None 

369 key = (model_type, component, module_type) 

370 if key in _REPLACEMENT_CACHE: 

371 return _REPLACEMENT_CACHE[key] 

372 spec = None 

373 old_ctor = class_name_of(module_type) 

374 if old_ctor: 

375 replacement = _component_replacement_target(component) 

376 if replacement is not None: 

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

378 # so the factory argument of _build_replacement_spec is unused. 

379 spec = _build_replacement_spec(None, replacement, old_ctor, model_type) 

380 _REPLACEMENT_CACHE[key] = spec 

381 return spec 

382 

383 

384def strategy_spec_for( 

385 target: str | None, 

386 model_type: str | None = None, 

387 *, 

388 inner_wrapper: bool = False, 

389) -> StrategySpec | None: 

390 """Resolve a strategy target from structure. 

391 

392 A CP ``inner_wrapper`` target under the adapter convention resolves to the 

393 framework CP attention strategy (imports only). EP mutations come from the 

394 builder recorder instead of secondary structural inference. Anything else 

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

396 """ 

397 if target is None: 

398 return None 

399 model_type = _family_key(model_type, target) 

400 key = (target, model_type, inner_wrapper) 

401 if key in _STRATEGY_CACHE: 

402 return _STRATEGY_CACHE[key] 

403 spec = _resolve_strategy_spec(target, inner_wrapper) 

404 _STRATEGY_CACHE[key] = spec 

405 return spec 

406 

407 

408def _resolve_strategy_spec(target: str, inner_wrapper: bool) -> StrategySpec | None: 

409 if inner_wrapper: 

410 return _cp_strategy_spec(target) 

411 return None 

412 

413 

414def _cp_strategy_spec(target: str) -> StrategySpec: 

415 """Build the CP strategy spec: an in-place dispatch shell plus imports. 

416 

417 A recognizable inner forward rewrite participates in inline CP: the 

418 materialization pass derives an 

419 in-artifact rewrite of the attention host whose forward falls back to 

420 ``_forward_impl`` when CP is disabled and delegates to the helper when 

421 active. The helper is the same kernel the runtime CP wrapper delegates 

422 to (trainer and codegen share one CP implementation), and the import is 

423 derived from the wrapper factory source, so this module never hardcodes a 

424 family helper and 

425 stays free of torch imports at resolution time. ``subclass_state_attr`` 

426 carries the family kernel's companion attribute so the runtime binding 

427 satisfies the identical contract the native wrapper installs. 

428 

429 Families without an inline helper keep the original framework CP 

430 strategy (imports only), preserving the runtime wrapper path. 

431 """ 

432 resolved = inner_wrapper_target(target) 

433 if resolved is None: 

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

435 if resolved.inline_body is not None: 

436 return StrategySpec( 

437 kind=CP_ATTENTION_KIND, 

438 subclass_shell=resolved.inline_body, 

439 imports=(), 

440 ) 

441 return StrategySpec( 

442 kind=CP_ATTENTION_KIND, 

443 subclass_shell=cp_dispatch_shell_body(resolved.helper_name), 

444 subclass_state_attr=resolved.state_attr, 

445 imports=(ImportPatch(resolved.helper_module, (resolved.helper_name,)),), 

446 ) 

447 

448 

449__all__ = [ 

450 "CP_ATTENTION_KIND", 

451 "cp_dispatch_shell_body", 

452 "replacement_spec_for", 

453 "strategy_spec_for", 

454]