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

249 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"""Framework structural recognition for inline component selection. 

16 

17Which generic component replaces a matched source module is decided by the 

18*structure of the family's own replacement factory* -- the generic component it 

19constructs, the module that class comes from, the constructor arguments it 

20passes and the kernel entry it hands over -- never by the model's name and 

21never by a component-name table. The factory is not codegen-specific: the YAML 

22already names it for the native path, so reading it adds no family codegen 

23declaration. 

24 

25This is what makes "native HF model + one YAML" sufficient: a family whose 

26attention is fused-QKV, whose experts are batched, or whose norm is an RMSNorm 

27is covered the moment its runtime factory exists. 

28""" 

29 

30from __future__ import annotations 

31 

32import ast 

33from dataclasses import dataclass 

34from functools import lru_cache 

35import inspect 

36import os 

37import sys 

38import textwrap 

39from typing import Any 

40 

41 

42@dataclass(frozen=True) 

43class ReplacementArgument: 

44 """One constructor keyword a replacement factory passes. 

45 

46 ``value`` is the argument's source text; ``None`` marks an argument bound to 

47 the factory's own parameter (a runtime value such as ``module_fqn``), whose 

48 generated value is the component's constructor default. 

49 """ 

50 

51 name: str 

52 value: str | None = None 

53 

54 

55@dataclass(frozen=True) 

56class ReplacementTarget: 

57 """The component a replacement factory constructs, read from its source. 

58 

59 Every fact the generated replacement needs is here: ``component`` (the class 

60 the factory builds) and ``module`` (where that class is imported from) give 

61 the emitted import; ``wraps_source`` says whether the matched module is 

62 handed over as the component's ``module`` argument (a wrap) or whether the 

63 component drops in for the source class; ``arguments`` carries the remaining 

64 constructor keywords; ``interface`` names the family symbol the factory 

65 hands over -- a component that receives one is rendered rather than merely 

66 imported, so its class and that interface stay visible in the artifact. 

67 """ 

68 

69 component: str 

70 module: str 

71 wraps_source: bool 

72 arguments: tuple[ReplacementArgument, ...] 

73 interface: str | None = None 

74 

75 

76@dataclass(frozen=True) 

77class InnerWrapperTarget: 

78 """Source-visible helper and companion mesh attribute of an inner wrapper.""" 

79 

80 helper_module: str | None = None 

81 helper_name: str | None = None 

82 state_attr: str | None = None 

83 inline_body: str | None = None 

84 

85 

86def class_name_of(qualified_type: str | None) -> str | None: 

87 """Return the bare class name of a dotted ``module_type`` path.""" 

88 if not qualified_type: 

89 return None 

90 return qualified_type.rsplit(".", 1)[-1] or None 

91 

92 

93def family_from_target(target: str | None) -> str | None: 

94 """Infer the adapter family directory from a framework adapter path. 

95 

96 ``hyper_parallel.models.<family>.adapter...`` -> ``<family>``. Returns 

97 ``None`` for targets that are not model-adapter paths (for example a 

98 framework recipe), which callers treat as "no family provider". 

99 """ 

100 parts = (target or "").split(".") 

101 if len(parts) >= 3 and parts[:2] == ["hyper_parallel", "models"]: 

102 return parts[2] 

103 return None 

104 

105 

106def adapter_path_parts(target: str | None) -> bool: 

107 """Whether a target lives under the ``models/<family>/adapter`` convention.""" 

108 parts = (target or "").split(".") 

109 return len(parts) >= 4 and parts[:2] == ["hyper_parallel", "models"] and parts[3] == "adapter" 

110 

111 

112def replacement_target(factory: Any) -> ReplacementTarget | None: 

113 """Read the component one ``@module_replacement`` factory constructs. 

114 

115 Only a factory that constructs an imported class directly (``Ctor(...)``) 

116 yields a target: that is the call codegen can sink into the generated 

117 ``__init__``. A factory that builds through a class method 

118 (``SomeExperts.from_module(...)``) has no constructor call to sink, so the 

119 rule is left untouched -- the same conservative behaviour a component-name 

120 table gave, without the table. 

121 """ 

122 try: 

123 source = textwrap.dedent(inspect.getsource(factory)) 

124 tree = ast.parse(source) 

125 except (OSError, TypeError, SyntaxError): 

126 return None 

127 imported = _factory_imported_modules(tree) 

128 parameters = _parameter_names(factory) 

129 for node in ast.walk(tree): 

130 if not isinstance(node, ast.Call) or not isinstance(node.func, ast.Name): 

131 continue 

132 component = node.func.id 

133 module = imported.get(component) or _global_class_module(factory, component) 

134 if module is None: 

135 continue 

136 return ReplacementTarget( 

137 component=component, 

138 module=module, 

139 wraps_source=any( 

140 keyword.arg == "module" 

141 and isinstance(keyword.value, ast.Name) 

142 and keyword.value.id in parameters 

143 for keyword in node.keywords 

144 ), 

145 arguments=_factory_arguments(node, parameters), 

146 interface=next( 

147 ( 

148 keyword.value.id 

149 for keyword in node.keywords 

150 if isinstance(keyword.value, ast.Name) 

151 and keyword.value.id in getattr(factory, "__globals__", {}) 

152 and callable(factory.__globals__[keyword.value.id]) 

153 ), 

154 None, 

155 ), 

156 ) 

157 return None 

158 

159 

160def _factory_imported_modules(tree: ast.AST) -> dict[str, str]: 

161 """Module path per name the factory source imports (``from X import Y``).""" 

162 imported: dict[str, str] = {} 

163 for node in ast.walk(tree): 

164 if isinstance(node, ast.ImportFrom) and node.module: 

165 for alias in node.names: 

166 imported.setdefault(alias.asname or alias.name, node.module) 

167 elif isinstance(node, ast.Import): 

168 for alias in node.names: 

169 imported.setdefault(alias.asname or alias.name.split(".")[0], alias.name) 

170 return imported 

171 

172 

173def _global_class_module(factory: Any, name: str) -> str | None: 

174 """Module path of a class the factory's own module imported at module level.""" 

175 value = getattr(factory, "__globals__", {}).get(name) 

176 module = getattr(value, "__module__", None) 

177 return module if isinstance(value, type) and module else None 

178 

179 

180def _parameter_names(factory: Any) -> set[str]: 

181 """Parameter names of the factory itself (runtime values, not literals).""" 

182 try: 

183 return set(inspect.signature(factory).parameters) 

184 except (TypeError, ValueError): 

185 return set() 

186 

187 

188def _factory_arguments( 

189 call: ast.Call, parameters: set[str] 

190) -> tuple[ReplacementArgument, ...]: 

191 """The constructor keywords a factory passes, minus the wrapped module. 

192 

193 A value that names one of the factory's own parameters is recorded without 

194 its text (the component's constructor default applies); a value naming a 

195 callable in the factory's globals -- a kernel entry handed over -- keeps its 

196 symbol so the artifact stays readable; anything else keeps its own text. 

197 """ 

198 arguments: list[ReplacementArgument] = [] 

199 for keyword in call.keywords: 

200 if keyword.arg is None: 

201 continue 

202 if keyword.arg == "module" and isinstance(keyword.value, ast.Name): 

203 continue 

204 value: str | None = ( 

205 None 

206 if isinstance(keyword.value, ast.Name) and keyword.value.id in parameters 

207 else ast.unparse(keyword.value) 

208 ) 

209 arguments.append(ReplacementArgument(keyword.arg, value)) 

210 return tuple(arguments) 

211 

212 

213def inner_wrapper_target(factory_path: str | None) -> InnerWrapperTarget | None: 

214 """Recover an inner wrapper's emitted helper contract from its source.""" 

215 if not factory_path: 

216 return None 

217 module_name, _, factory_name = factory_path.rpartition(".") 

218 if not module_name or not factory_name: 

219 return None 

220 tree = _module_source_tree(module_name) 

221 if tree is None: 

222 return None 

223 factory_node = _module_function(tree, factory_name) 

224 if factory_node is None: 

225 return None 

226 request_owner = _request_owner(factory_node, tree) 

227 replacement_name = _replacement_callable(request_owner) 

228 helper_name = ( 

229 _forward_helper_name(request_owner, replacement_name) 

230 if request_owner is not None and replacement_name 

231 else None 

232 ) 

233 module_functions = { 

234 node.name 

235 for node in tree.body 

236 if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)) 

237 } 

238 if not helper_name or helper_name not in module_functions: 

239 inline_body = _nested_forward_body(request_owner, replacement_name) 

240 if inline_body is None: 

241 return None 

242 return InnerWrapperTarget(inline_body=inline_body) 

243 return InnerWrapperTarget( 

244 helper_module=module_name, 

245 helper_name=helper_name, 

246 state_attr=_cp_mesh_companion_attr(tree), 

247 ) 

248 

249 

250def _module_function(tree: ast.Module, name: str) -> ast.AST | None: 

251 """Return a named top-level function from a parsed module.""" 

252 return next( 

253 ( 

254 node 

255 for node in tree.body 

256 if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)) 

257 and node.name == name 

258 ), 

259 None, 

260 ) 

261 

262 

263def _replacement_callable(request_owner: ast.AST | None) -> str | None: 

264 """Resolve the callable installed by an inner-wrapper request.""" 

265 if request_owner is None: 

266 return None 

267 return _rewrite_request_callable(request_owner) or _returned_nested_callable(request_owner) 

268 

269 

270def _request_owner(factory_node: ast.AST, tree: ast.Module) -> ast.AST | None: 

271 """Return the factory or its module-level delegate that builds the request.""" 

272 if ( 

273 _rewrite_request_callable(factory_node) is not None 

274 or _returned_nested_callable(factory_node) is not None 

275 ): 

276 return factory_node 

277 functions = { 

278 node.name: node 

279 for node in tree.body 

280 if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)) 

281 } 

282 for node in ast.walk(factory_node): 

283 if not isinstance(node, ast.Return) or not isinstance(node.value, ast.Call): 

284 continue 

285 if not isinstance(node.value.func, ast.Name): 

286 continue 

287 delegate = functions.get(node.value.func.id) 

288 if delegate is not None and _rewrite_request_callable(delegate) is not None: 

289 return delegate 

290 return None 

291 

292 

293def _returned_nested_callable(tree: ast.AST) -> str | None: 

294 """Return a nested callable returned directly by an inner wrapper.""" 

295 nested = { 

296 node.name 

297 for node in ast.iter_child_nodes(tree) 

298 if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)) 

299 } 

300 for node in ast.walk(tree): 

301 if isinstance(node, ast.Return) and isinstance(node.value, ast.Name): 

302 if node.value.id in nested: 

303 return node.value.id 

304 return None 

305 

306 

307@lru_cache(maxsize=None) 

308def _module_source(module_name: str) -> str | None: 

309 """Load module source once without importing optional accelerator deps.""" 

310 relative = os.path.join(*module_name.split(".")) + ".py" 

311 for root in sys.path: 

312 path = os.path.join(root or os.curdir, relative) 

313 if not os.path.isfile(path): 

314 continue 

315 try: 

316 with open(path, "r", encoding="utf-8") as handle: 

317 return handle.read() 

318 except (OSError, UnicodeError): 

319 return None 

320 return None 

321 

322 

323def _module_source_tree(module_name: str) -> ast.Module | None: 

324 """Parse cached module source into an isolated syntax tree.""" 

325 source = _module_source(module_name) 

326 if source is None: 

327 return None 

328 try: 

329 return ast.parse(source) 

330 except SyntaxError: 

331 return None 

332 

333 

334def _rewrite_request_callable(tree: ast.AST) -> str | None: 

335 """Return the replacement callable passed to ``_ForwardRewriteRequest``.""" 

336 for node in ast.walk(tree): 

337 if not isinstance(node, ast.Call) or not isinstance(node.func, ast.Name): 

338 continue 

339 if node.func.id == "_ForwardRewriteRequest" and len(node.args) >= 2: 

340 if isinstance(node.args[1], ast.Name): 

341 return node.args[1].id 

342 return None 

343 

344 

345def _forward_helper_name(tree: ast.AST, replacement_name: str) -> str | None: 

346 """Return the module-level helper called by the replacement forward.""" 

347 for node in ast.walk(tree): 

348 if not isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)) or node.name != replacement_name: 

349 continue 

350 for child in ast.walk(node): 

351 if isinstance(child, ast.Return) and isinstance(child.value, ast.Call): 

352 if isinstance(child.value.func, ast.Name): 

353 return child.value.func.id 

354 return None 

355 

356 

357def _cp_mesh_companion_attr(tree: ast.AST) -> str | None: 

358 """Return the companion attribute whose value is the wrapper's CP mesh.""" 

359 return ( 

360 _cp_mesh_dict_attr(tree) 

361 or _cp_mesh_setattr_attr(tree) 

362 or _cp_mesh_assignment_attr(tree) 

363 ) 

364 

365 

366def _cp_mesh_dict_attr(tree: ast.AST) -> str | None: 

367 """Return the CP mesh attribute declared in companion_attrs.""" 

368 for node in ast.walk(tree): 

369 if not isinstance(node, ast.keyword) or node.arg != "companion_attrs": 

370 continue 

371 if not isinstance(node.value, ast.Dict): 

372 continue 

373 for key, value in zip(node.value.keys, node.value.values): 

374 if ( 

375 isinstance(key, ast.Constant) 

376 and isinstance(key.value, str) 

377 and isinstance(value, ast.Name) 

378 and value.id == "cp_mesh" 

379 ): 

380 return key.value 

381 return None 

382 

383 

384def _cp_mesh_setattr_attr(tree: ast.AST) -> str | None: 

385 """Return the CP mesh attribute installed through setattr.""" 

386 for node in ast.walk(tree): 

387 if ( 

388 isinstance(node, ast.Call) 

389 and isinstance(node.func, ast.Name) 

390 and node.func.id == "setattr" 

391 and len(node.args) == 3 

392 and isinstance(node.args[1], ast.Constant) 

393 and isinstance(node.args[1].value, str) 

394 and isinstance(node.args[2], ast.Name) 

395 and node.args[2].id == "cp_mesh" 

396 ): 

397 return node.args[1].value 

398 return None 

399 

400 

401def _cp_mesh_assignment_attr(tree: ast.AST) -> str | None: 

402 """Return the CP mesh attribute installed through assignment.""" 

403 for node in ast.walk(tree): 

404 if not isinstance(node, (ast.Assign, ast.AnnAssign)): 

405 continue 

406 value = node.value 

407 targets = node.targets if isinstance(node, ast.Assign) else (node.target,) 

408 if not isinstance(value, ast.Name) or value.id != "cp_mesh": 

409 continue 

410 for target in targets: 

411 if isinstance(target, ast.Attribute) and isinstance(target.value, ast.Name): 

412 return target.attr 

413 return None 

414 

415 

416def _nested_forward_body(tree: ast.AST, replacement_name: str | None) -> str | None: 

417 """Lower a closure-backed rewrite into an imported-host dispatch body. 

418 

419 A custom wrapper may build its replacement forward as a nested closure 

420 instead of delegating to a module-level helper. The closure remains 

421 source-visible when its captured values are also published through the 

422 request's ``companion_attrs`` mapping. In that form, replace captured 

423 state reads with instance attributes and the saved original forward with 

424 ``super().forward``; the imported-host materializer supplies the subclass. 

425 """ 

426 if not replacement_name: 

427 return None 

428 forward = next( 

429 ( 

430 node 

431 for node in ast.walk(tree) 

432 if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)) 

433 and node.name == replacement_name 

434 ), 

435 None, 

436 ) 

437 if forward is None: 

438 return None 

439 companion_attrs = _companion_attr_bindings(tree) 

440 original_forward = _original_forward_binding(tree) 

441 if original_forward is None: 

442 return None 

443 

444 class ClosureBindings(ast.NodeTransformer): 

445 """Rewrite captured closure bindings to generated-instance state.""" 

446 

447 def visit_Name(self, node: ast.Name) -> ast.AST: # pylint: disable=invalid-name 

448 """Rewrite a referenced symbol into its resolved source form.""" 

449 attr = companion_attrs.get(node.id) 

450 if attr is None: 

451 return node 

452 return ast.copy_location( 

453 ast.Attribute( 

454 value=ast.Name(id="self", ctx=ast.Load()), 

455 attr=attr, 

456 ctx=node.ctx, 

457 ), 

458 node, 

459 ) 

460 

461 def visit_Call(self, node: ast.Call) -> ast.AST: # pylint: disable=invalid-name 

462 """Rewrite a call after recursively resolving its arguments.""" 

463 node = self.generic_visit(node) 

464 if isinstance(node.func, ast.Name) and node.func.id == original_forward: 

465 node.func = ast.Attribute( 

466 value=ast.Call( 

467 func=ast.Name(id="super", ctx=ast.Load()), 

468 args=[], 

469 keywords=[], 

470 ), 

471 attr="forward", 

472 ctx=ast.Load(), 

473 ) 

474 return node 

475 

476 body = list(forward.body) 

477 if ( 

478 body 

479 and isinstance(body[0], ast.Expr) 

480 and isinstance(body[0].value, ast.Constant) 

481 and isinstance(body[0].value.value, str) 

482 ): 

483 body = body[1:] 

484 lowered = [ClosureBindings().visit(ast.fix_missing_locations(node)) for node in body] 

485 if not lowered: 

486 return None 

487 active = "\n".join(ast.unparse(node) for node in lowered) 

488 return ( 

489 "if self.cp_enable:\n" 

490 f"{textwrap.indent(active, ' ')}\n\n" 

491 "return self._forward_impl(__hp_forward_args__)" 

492 ) 

493 

494 

495def _companion_attr_bindings(tree: ast.AST) -> dict[str, str]: 

496 """Map closure variable names to their published companion attributes.""" 

497 result: dict[str, str] = {} 

498 for node in ast.walk(tree): 

499 if not isinstance(node, ast.keyword) or node.arg != "companion_attrs": 

500 continue 

501 if not isinstance(node.value, ast.Dict): 

502 continue 

503 for key, value in zip(node.value.keys, node.value.values): 

504 if ( 

505 isinstance(key, ast.Constant) 

506 and isinstance(key.value, str) 

507 and isinstance(value, ast.Name) 

508 ): 

509 result[value.id] = key.value 

510 return result 

511 

512 

513def _original_forward_binding(tree: ast.AST) -> str | None: 

514 """Return the local name that saves ``target.forward`` before rewriting.""" 

515 for node in ast.walk(tree): 

516 if not isinstance(node, ast.Assign) or len(node.targets) != 1: 

517 continue 

518 target = node.targets[0] 

519 value = node.value 

520 if ( 

521 isinstance(target, ast.Name) 

522 and isinstance(value, ast.Attribute) 

523 and value.attr == "forward" 

524 ): 

525 return target.id 

526 return None 

527 

528 

529__all__ = [ 

530 "InnerWrapperTarget", 

531 "ReplacementArgument", 

532 "ReplacementTarget", 

533 "adapter_path_parts", 

534 "class_name_of", 

535 "family_from_target", 

536 "inner_wrapper_target", 

537 "replacement_target", 

538]