Coverage for / home / jenkins / .local / lib / python3.10 / site-packages / hyper_parallel / codegen / inline / replacement_pass.py: 26%
35 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"""First inline pass: sink YAML module replacements into source constructors."""
17from __future__ import annotations
19import logging
20from collections import defaultdict
21from typing import Any
23from hyper_parallel.codegen.inline.ir import (
24 ClassRemovalPatch,
25 ConstructorReplacePatch,
26 InlinePatchSet,
27 InlineRule,
28 ModuleSnippetPatch,
29)
30from hyper_parallel.codegen.inline.specs import replacement_spec
32logger = logging.getLogger(__name__)
35def build_replacement_patches(rules: tuple[InlineRule, ...], model_type: str | None = None) -> InlinePatchSet:
36 """Build constructor replacement patches from ``replace_module`` targets.
38 The rule's settled FQNs (``rule.match``, recorded from the same plan the
39 native path applies) define the *scope* of each rewrite: only the
40 ``self.<attr>`` call sites those FQNs name are sunk. A rule that matched no
41 module is not expanded at all -- generating a replacement the plan never
42 decided would make the artifact diverge from the trained model.
44 The plan records one entry per matched FQN, so the entries sharing a
45 ``replace_module`` target must be merged: taking only the first would scope
46 the rewrite to a single matched attribute (``input_layernorm``) and silently
47 leave its siblings (``post_attention_layernorm``, ``model.norm``) on the
48 original class.
50 Args:
51 rules: Inline rules recovered from the plan's frozen metadata.
52 model_type: Model family key used to resolve framework declarations.
54 Returns:
55 The constructor replacement patches for the matched rules.
56 """
58 patch_set = InlinePatchSet()
59 scopes_by_target: dict[str, set[str]] = defaultdict(set)
60 specs_by_target: dict[str, Any] = {}
61 for rule in rules:
62 target = rule.replace_target
63 if target is None:
64 continue
65 spec = replacement_spec(target, model_type, module_type=rule.module_type)
66 if spec is None:
67 continue
68 specs_by_target.setdefault(target, spec)
69 scopes_by_target[target].update(_scope_attrs(rule))
70 for target, spec in specs_by_target.items():
71 scope_attrs = tuple(sorted(scopes_by_target[target]))
72 if not scope_attrs:
73 logger.warning(
74 "codegen: replace_module %r matched no module and is not expanded",
75 target,
76 )
77 continue
78 patch_set.imports.extend(spec.imports)
79 if spec.replacement_note:
80 patch_set.module_snippets.append(
81 ModuleSnippetPatch(f"# Codegen replacement: {spec.replacement_note}")
82 )
83 patch_set.module_snippets.extend(spec.snippets)
84 patch_set.constructor_replaces.append(
85 ConstructorReplacePatch(
86 old_ctor=spec.old_ctor,
87 new_ctor=spec.new_ctor,
88 mode=spec.mode, # type: ignore[arg-type]
89 keyword_args=spec.keyword_args,
90 scope_attrs=scope_attrs,
91 )
92 )
93 if spec.remove_class:
94 patch_set.class_removals.append(
95 ClassRemovalPatch(old_name=spec.old_ctor, new_name=spec.new_ctor)
96 )
97 return patch_set
100def _scope_attrs(rule: InlineRule) -> tuple[str, ...]:
101 """Attribute names of the rule's settled FQNs, i.e. the rewrite scope.
103 ``model.layers.3.post_attention_layernorm`` scopes the rewrite to
104 ``self.post_attention_layernorm = <ctor>(...)`` assignments, which is the
105 granularity native's FQN matching produces. ``model.norm`` is a sole-name
106 FQN, so the whole FQN is the attribute.
107 """
109 return tuple(sorted({fqn.rsplit(".", 1)[-1] for fqn in rule.match if fqn}))