Coverage for / home / jenkins / .local / lib / python3.10 / site-packages / hyper_parallel / codegen / inline / pipeline.py: 22%
77 statements
« prev ^ index » next coverage.py v7.13.1, created at 2026-10-25 05:45 +0800
« 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"""Two-stage inline modeling source pipeline."""
17from __future__ import annotations
19import ast
20import importlib
21from typing import Any
23from hyper_parallel.codegen.inline.ir import InlinePatchSet
24from hyper_parallel.codegen.inline.meta_plan import normalize_inline_meta
25from hyper_parallel.codegen.inline.patch_engine import apply_patch_set, drop_shadowed_definitions
26from hyper_parallel.codegen.inline.replacement_pass import build_replacement_patches
27from hyper_parallel.codegen.inline.specs import replacement_spec, strategy_spec
28from hyper_parallel.codegen.inline.strategy_pass import build_strategy_patches
29from hyper_parallel.codegen.inline.yaml_rules import collect_inline_rules
32def render_inline_modeling(source_text: str, meta: Any, model_type: str | None = None) -> str:
33 """Render the inline-patched modeling file for a frozen plan.
35 This is the only artifact route. A YAML target with no adapter declaration
36 is a hard error rather than a silent fallback, so an inline coverage gap
37 surfaces at generation time instead of degrading the artifact. A plan with
38 no inline rules needs no patch and the source is returned unchanged.
40 Args:
41 source_text: Modeling source the inline patches are applied to.
42 meta: Frozen plan metadata recorded by the generator.
43 model_type: Model family key used to resolve framework declarations.
45 Returns:
46 The rendered modeling source.
47 """
49 rules = collect_inline_rules(meta)
50 if not rules:
51 return source_text
52 _require_inline_coverage(rules, model_type)
53 replacement_patches = build_replacement_patches(rules, model_type)
54 strategy_patches = build_strategy_patches(rules, model_type)
55 patch_set = _merge_patch_sets(replacement_patches, strategy_patches)
56 rendered = apply_patch_set(source_text, patch_set)
57 # The copied modeling source brings its own decorated helpers that share
58 # names with the library functions the artifact imports, and a local
59 # definition wins over the import: the native path calls the library
60 # implementation (whose kernel binding is resolved in its own module), so a
61 # redefined copy makes the generated model run something else. Only names
62 # the base source defines with a decorator are candidates, so the classes and
63 # snippets the inline passes render (which share names with library imports
64 # on purpose) keep their definitions.
65 rendered = drop_shadowed_definitions(
66 rendered, _library_import_names(rendered) & _decorated_names(source_text)
67 )
68 normalize_inline_meta(meta, rules, model_type)
69 return rendered
72def _library_import_names(text: str) -> set[str]:
73 """Names the artifact imports from the library, i.e. the winning bindings.
75 Args:
76 text: Rendered modeling source.
78 Returns:
79 The set of names bound by its ``hyper_parallel`` imports.
80 """
82 names: set[str] = set()
83 try:
84 tree = ast.parse(text)
85 except SyntaxError:
86 return names
87 for node in ast.walk(tree):
88 if isinstance(node, ast.ImportFrom) and (node.module or "").startswith("hyper_parallel"):
89 names.update(alias.asname or alias.name for alias in node.names)
90 return names
93def _decorated_names(text: str) -> set[str]:
94 """Names of the module-level definitions the base source decorates.
96 A decorator is what makes a copied helper behave differently from the library
97 function of the same name (a per-module kernel binding, for instance), so
98 only those definitions may be dropped.
100 Args:
101 text: Base modeling source before the inline patches are applied.
103 Returns:
104 The set of decorated top-level definition names.
105 """
107 try:
108 tree = ast.parse(text)
109 except SyntaxError:
110 return set()
111 return {
112 node.name
113 for node in tree.body
114 if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef))
115 and node.decorator_list
116 }
119def _is_local_compute_factory(target: str) -> bool:
120 """Whether a local_compute target is a ``@local_compute`` factory.
122 Structural injection discipline: the decorator stamps the factory with
123 ``_injection_meta`` (kind ``LOCAL_COMPUTE``), so a decorated factory is
124 recognizable without a table. Such a target carries no inline strategy
125 body -- its region is bound at install time (the emitted region template
126 for an in-source class, the runtime local-region skeleton for an imported
127 class), which resolves the factory exactly as the native applier does.
128 """
129 from hyper_parallel.distributed.recipe_spec import LOCAL_COMPUTE # pylint: disable=C0415
131 module_name, _, attr = target.rpartition(".")
132 if not module_name or not attr:
133 return False
134 try:
135 module = importlib.import_module(module_name)
136 except ImportError:
137 return False
138 factory = getattr(module, attr, None)
139 if not callable(factory):
140 return False
141 meta = getattr(factory, "_injection_meta", None)
142 return getattr(meta, "kind", None) == LOCAL_COMPUTE
145def _require_inline_coverage(rules: tuple[Any, ...], model_type: str | None = None) -> None:
146 """Fail when a YAML rule has no structural resolution behind it.
148 Covers both rule kinds: a replacement target needs a ``ReplacementSpec``,
149 and every strategy target needs a ``StrategySpec``. One exception: a
150 ``local_compute_fn`` target that is itself a ``@local_compute`` factory
151 needs no inline strategy -- its region is runtime-bound (see
152 :func:`_is_local_compute_factory`), so the factory's injection discipline
153 is the coverage proof. Reporting all gaps at once keeps one fix cycle
154 from hiding the next gap.
155 """
157 uncovered: list[str] = []
158 for rule in rules:
159 if (
160 rule.replace_target is not None
161 and replacement_spec(rule.replace_target, model_type, module_type=rule.module_type) is None
162 ):
163 uncovered.append(rule.replace_target)
164 for target in (rule.local_compute_target, rule.inner_wrapper_target):
165 if target is None:
166 continue
167 if strategy_spec(
168 target, model_type, inner_wrapper=target == rule.inner_wrapper_target
169 ) is not None:
170 continue
171 # An inner_wrapper target has no runtime channel and stays
172 # fail-closed; a local_compute target passes through the
173 # @local_compute decoration check.
174 if target == rule.local_compute_target and _is_local_compute_factory(target):
175 continue
176 uncovered.append(target)
177 if uncovered:
178 raise RuntimeError(
179 f"codegen: no inline declaration for {sorted(set(uncovered))} "
180 f"(model_type={model_type!r}); the target names no known generic "
181 "component or framework strategy, and a local_compute target "
182 "names no @local_compute factory"
183 )
186def _merge_patch_sets(*sets: InlinePatchSet) -> InlinePatchSet:
187 merged = InlinePatchSet()
188 for patch_set in sets:
189 merged.imports.extend(patch_set.imports)
190 merged.module_snippets.extend(patch_set.module_snippets)
191 merged.constructor_replaces.extend(patch_set.constructor_replaces)
192 merged.forward_extracts.extend(patch_set.forward_extracts)
193 merged.class_removals.extend(patch_set.class_removals)
194 return merged