Diff Coverage

Diff: origin/master...HEAD, staged and unstaged changes

Source File Diff Coverage (%) Missing Lines
hyper_parallel/codegen/__init__.py 100%  
hyper_parallel/codegen/__main__.py 0.0% 5,7,9,11-12
hyper_parallel/codegen/artifact.py 27.5% 53-56,78-80,91,93-94,101,109,114,135-136,138-143,145-152,155-157,159-161,180-181,183-185,187-201,203-204,206,212,215,217-218,225,233,238,243-245,254-259
hyper_parallel/codegen/astkit/__init__.py 100%  
hyper_parallel/codegen/astkit/edits.py 31.0% 30-31,44-49,53-54,58,60-68,90-91,96,100-101,118-120,123
hyper_parallel/codegen/astkit/index.py 26.4% 86,94-97,106,108,110,112-114,116-118,120,124-126,133-136,138-141,143,152-156,159,170-174,179-190,192-194,197,204-207,217,222,251-266,277-293,304-309,318-321
hyper_parallel/codegen/check/__init__.py 100%  
hyper_parallel/codegen/check/drift.py 0.0% 17,19-21,23,25,28,37-39,41,43,45-55,58,60,63
hyper_parallel/codegen/check/pin.py 0.0% 15,17-18,20,23-24,27,36-38,40,42-45,47-49,54,57,59-62,65-66,68,71
hyper_parallel/codegen/check/preflight.py 12.2% 41-42,59,64-66,68-70,74-77,84,87-89,93-95,111,113,122,124,134-142,176,178-179,185,190-195,201,207-210,214,217-218,225-227,233,245,251-252,257-258,263-266,272-274,279-281,286,291-294,300-301,335-337,343-344,348,351-361,368,373-374,387-389,391,396-409,414-420
hyper_parallel/codegen/cli.py 0.0% 12,14-18,20,23,25-36,43,45-46,48-54,56-59,62,64-68,70-72,75,77-86,88-89,91-93,95-101,103-107,110,112,114-116,123,126,128-134,137,139,141,144-145,150,153,155-158,161,163,166-167,173,175,179-180,182-184,186-191,193,196,198-207,210
hyper_parallel/codegen/emit/__init__.py 100%  
hyper_parallel/codegen/emit/bundle.py 33.3% 48-49,51-52,54,60,75-77,81-82,84-92,102,118,128-131
hyper_parallel/codegen/emit/diff.py 80.0% 27
hyper_parallel/codegen/emit/modeling.py 11.4% 50-51,53-55,60-61,92-94,96-101,103-110,112-119,122-132,134,136,140-145,147-150,152-155,157-159,170-177,189-195,206-213,215-218,220,225-229,244,250,252-253,258-261,267,269-272,306,311-315,321,331-333,353,357-361
hyper_parallel/codegen/emit/parallel.py 0.0% 65,67-69,71,77,84,94,97,100,124-126,132,136,142,147,152-153,162-168,170-172,176-179,185,190,197-200,206,211,213,215,218,250-254,256,259-260,264-270,273,288-292,295,315-323,327-329,336,380-386,393-396,403,407-408,415,422-426,429,445-451,455-459,462,470,473,483-492,498,501,503-505,508,520-534,538-539,542-543,553,555,558,589-590,596-597,599-601,607,613,619,627-632,634-636,640,643,660,665,672-681,684-695,699,702-704,707,724-737,740,753-770,773,784,788,791-793,796,798,806,808-810,814-815,819,822,842-843,853-862,867-872,877-881,884,907-908,910-911,913-915,920,923,941-947,951-952,959,986,991-993,996,998,1001,1012,1032,1061,1066-1071,1073-1076,1079-1086,1089-1092,1095,1097-1099,1102,1104,1107,1116-1118,1122,1125,1132-1143,1146,1164-1165,1187,1189-1193,1200,1202-1204,1207,1215-1220,1224-1225,1228
hyper_parallel/codegen/emit/replacement.py 0.0% 19,21,28,76-80,82,88-91,94,100,106-109,114,122-125,128-129,132
hyper_parallel/codegen/hash.py 47.4% 19-23,28,36,45,50-51
hyper_parallel/codegen/inline/__init__.py 100%  
hyper_parallel/codegen/inline/attention.py 0.0% 37,39-45,47,59,66,74,77,87-91,93-95,98,104-110,113,115-118,123,126,133-137,142-143,146,148-149,156,158-162,165,171,174,176-184,186-190,193,201-211,214-215,218,220-223,226,249-251,254-259,261,263-281,283-286,291,294,296-300,307-312,315,328-330,337-339,344
hyper_parallel/codegen/inline/expansion.py 0.0% 17-22,25,36-37,40-41,44,51-53,56,59-62,64-70,72,74-81,83-88,90,92-106,109-111,116,118-122,124-131
hyper_parallel/codegen/inline/framework_spec.py 16.4% 76-83,88-90,92-96,109-113,115-116,125-134,152-162,175-178,183,198-201,205,219-228,233-235,239,254-257,261,264-266,270,273,276-278,314-321,325-330,344,348-354,367-377,380-382,398-406,412-417,427,429,449-459,482-484,487-490,504-509,512-521
hyper_parallel/codegen/inline/ir.py 100%  
hyper_parallel/codegen/inline/meta_plan.py 14.5% 52-55,67-76,92-129,159-160,162-169,171-172,174-180
hyper_parallel/codegen/inline/patch_engine.py 11.6% 51-60,62-63,65-67,76-79,89-102,111-114,127-163,165-167,175-186,201-204,219,222-223,232-239,265-280,286-291,314-323,326-327,343-345,355-361,373-381,392-400,406-412,420-422,426-435,439-442,446-450,461,467-473,477-480,484-489
hyper_parallel/codegen/inline/pipeline.py 22.1% 49-56,65,68-69,82-90,107-111,129,131-142,157-159,163-167,170,174-178,187-194
hyper_parallel/codegen/inline/recognition.py 32.1% 75-77,87-90,95-96,109-123,144,149-157,162-164,169-172,185-191,196-197,207-209,213-216
hyper_parallel/codegen/inline/replacement_pass.py 25.7% 58-73,77-80,83-84,93-94,97,109
hyper_parallel/codegen/inline/spec_bundle.py 80.0% 52-57
hyper_parallel/codegen/inline/specs.py 75.0% 44,59
hyper_parallel/codegen/inline/strategy_pass.py 13.3% 34-58,65
hyper_parallel/codegen/inline/templates.py 0.0% 17,30,44,58
hyper_parallel/codegen/inline/yaml_rules.py 17.6% 21-26,33-38,45,49-56,60-66
hyper_parallel/codegen/loader.py 0.0% 23,25-31,33,37,40,50-54,57,70-71,75,79-80,84-85,90,93,104-106,108-110,122,124-128,131-138,141,148,151,161-170,176-186,189-198,201,214-216,220-223,226,230-231,234,237,259,263-264,268-270,276,297,301-302,304-314,320-321,323-326,329,345,347-353,356,367-369,373-375,378,409-410,412-413,429-430,432,435,439-441,445-446,449
hyper_parallel/codegen/manager.py 14.1% 80-81,83-89,91-92,94-96,103-104,106-109,112,115-121,140-142,152-157,160-167,172,191,193-199,223-224,226-227,244-251,255-257,262-272,277-280,298,302-303,305-321,332-344,349-363,369-370,375,387,392-395,411-412,431-432,434-436,438-439,446,454-459,465,473,477,483,487,515,529-543,560-561,566,605,610-611,613-616,621-622,628-629,634-637,643-649,651,657,662,674-675,697-699,701,704,707,711-718,723,730,740-742,754,770-773,778-779,783,788-791,808,812,821-823,845-855,872-879,884-888,892,897,917-926,930-931,936,943,948-949,954-956,962,975,977-978,980-982,984,995,1004-1005,1007-1009,1014-1016
hyper_parallel/codegen/meta.py 41.8% 119,124-125,132-138,143-144,149-160,163-172,175-178,181,184-186,189-192,195,222-223,228-234,238
hyper_parallel/codegen/modeling_backend.py 88.0% 81,83,88
hyper_parallel/codegen/plan/__init__.py 100%  
hyper_parallel/codegen/plan/boundary_forms.py 27.4% 130,135,137-138,143-147,149-157,160-162,166-175,179-181,190-191,200-201,210,235-237,241-249,252,269,274-279,284-287,302-308,313-316,326-328,341-345
hyper_parallel/codegen/plan/derive.py 18.0% 29-30,34-36,40-46,49-58,79-83,85-86,90-91,97-98,100,102-106,110,121-124,126,151-153,155-156
hyper_parallel/codegen/plan/freeze.py 23.3% 44-45,51-52,58,64,78,85,91,103-108,129-131,150,157-158,160-161,163-165,169,171-175,177-183,185-195,198-199,211-217,226-228,279-281,301-308,339-348,365-366,368-373,380-387,389,403-413,418-419,430-440,449-457,463,465-470,474-478
hyper_parallel/codegen/plan/offline_mesh.py 37.0% 55-56,73-87
hyper_parallel/codegen/plan/spec_fields.py 100%  
hyper_parallel/codegen/runtime.py 0.0% 32,34-38,40-41,54,61,68,71,78,81,97-100,104-105,109-114,121,129-130,137,169,172,175,197-198,200-202,208,213-214,220,223-224,233,268,272,281,292,294-301,305-307,310,320-321,323-324,329,332,350-354,357,360-361,364-367,370,379,381-388,404-405,408,411,418-421,428,435,438,442,446,454,469,477,480-481,488-494,498,500-502,506,508-511,515-516,518,525-528,530,532-534,536,544-546,550,558,573,575,577,579,581-583,585,587,589,591,593,595,597,599,601-605,613,616,618-623,626,641-643,646,666-668,673-677,680,682-684,689,698-705,708,726,731-737,741-742,747,757-760,762-769,771-773,776,787-788,790-796,798,801,821,825,829,833-835,838-840,847-849,855,858,879,883,887-893,897-899,902,947-949,953,956,960-961,963-972,975,980-981,987-989,993,996,1003-1008,1011-1015,1021-1022,1025-1027,1034-1035,1042,1044-1047,1050,1067-1076,1078-1079,1082,1098,1103-1105,1110,1112-1115,1117-1119,1130,1162,1165,1167,1170-1171,1174-1181,1184-1187,1194-1195,1200-1204,1209,1214,1242,1247,1250,1252,1255-1256,1259-1270,1274-1278,1282,1293,1295-1297,1300,1302-1304,1310,1319-1323,1327-1328,1336,1342-1343,1345-1350,1352-1353,1356,1358,1363-1370,1372-1374,1377,1388-1390,1394-1395,1402,1417-1419,1423-1425,1429-1434,1437,1446,1449,1477-1479,1485-1488,1490-1493,1498-1505,1507,1515,1526-1527,1531-1546,1549,1551,1554,1585-1586,1591,1598-1601,1603-1606,1609-1611,1616-1618,1622,1630,1636,1644,1652,1657
hyper_parallel/codegen/source/__init__.py 0.0% 9-10,20
hyper_parallel/codegen/source/compat.py 0.0% 37,39-40,42-43,46,51,61-62,65-68,71,79,85-86,88-89,92,94-97,100,106,108-111,113-122,130
hyper_parallel/codegen/source/resolver.py 0.0% 27,29-36,38,40,43-44,47-52,54,56,66,87-93,96-100,103,121-125,127-128,132-137,149-153,157,161,165-166,170,174-177,181,183,189,197-201,205-207,211-218,221,230-240,250,272-276,279,281-283,288-298,301,303-304,308-311,313-316,319-322,324-331,334,336-344,347,355-367,370,379-390,393,395-398,401-403,406,413-421,424,434-440,443,453-460,463,479-481,483-500,503,506,516
hyper_parallel/codegen/spec/__init__.py 100%  
hyper_parallel/codegen/spec/project.py 18.6% 52-57,79-83,86-87,98-104,106-107,117-118,127-132,137-144,149-158,161,170-171,179-190,195-201
hyper_parallel/codegen/spec/types.py 97.3% 76
hyper_parallel/components/modules/__init__.py 66.7% 78
hyper_parallel/components/modules/grouped_experts.py 0.0% 284
hyper_parallel/distributed/_builder/applier.py 21.4% 312-316,319-321,329-330,337
hyper_parallel/distributed/_builder/forward_rewriter.py 71.4% 609,617,1110-1113
hyper_parallel/distributed/_builder/tp_collective_lowering.py 88.0% 60,74,78
hyper_parallel/distributed/expert_parallel/archetypes.py 0.0% 27,29,32-34,37-38,42,45-46,52-53,55,58,83,91,96,98,101,103-104,107,115-116,119
hyper_parallel/distributed/expert_parallel/experts.py 64.7% 1405,1463-1465,1469,1475
hyper_parallel/distributed/expert_parallel/structure.py 0.0% 17-21,23,26,30-31,34-37,40-41,47-56,59,66-73,75,83,85-89,91-92,96-97,100,102-107,109-110,113-114,117-118,122,124-128,130-132,134,136-137,139-144,146-147
hyper_parallel/distributed/recipe_spec.py 100%  
hyper_parallel/trainer/config/parser.py 100%  
hyper_parallel/trainer/config/trainer.py 100%  
hyper_parallel/codegen/__main__.py
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# Copyright 2026 Huawei Technologies Co., Ltd
# Licensed under the Apache License, Version 2.0
# ============================================================================
"""``python -m hyper_parallel.codegen`` entry point."""
from __future__ import annotations

import sys

from hyper_parallel.codegen.cli import main

if __name__ == "__main__":
    sys.exit(main())
hyper_parallel/codegen/artifact.py
49
50
51
52
53
54
55
56
57
58
59
60

        ``artifact_dir`` is ``<yaml_dir>/generated/``. Example directories
        contain one Codegen YAML, so another per-YAML directory is redundant.
        """
        yaml_path = os.path.abspath(yaml_path)
        artifact_dir = os.path.join(os.path.dirname(yaml_path), "generated")
        modeling_name = _default_modeling_name(model_name)
        return cls(
            yaml_path=yaml_path,
            artifact_dir=artifact_dir,
            modeling_path=os.path.join(artifact_dir, modeling_name),
            diff_path=os.path.join(artifact_dir, modeling_name + ".diff"),
74
75
76
77
78
79
80
81
82
83
84
        is kept as an empty string for backward-compatible metadata shape; the
        signature is computed from the resolved config projection, not from a
        YAML file path.
        """
        resolved_dir = os.path.abspath(artifact_dir or os.path.join(os.getcwd(), "generated"))
        modeling_name = _default_modeling_name(model_name)
        return cls(
            yaml_path="",
            artifact_dir=resolved_dir,
            modeling_path=os.path.join(resolved_dir, modeling_name),
            diff_path=os.path.join(resolved_dir, modeling_name + ".diff"),
87
88
89
90
91
92
93
94
95
96
97
98
        )


def _default_modeling_name(model_name: str | None) -> str:
    base = model_name or "model"
    # '' / '-' are not valid Python identifiers in module names.
    safe = "".join(c if c.isalnum() or c == "_" else "_" for c in base)
    return f"modeling_{safe}_gen_npu.py"


def resolve_artifact_layout(
    yaml_path: str, model_name: str | None = None
 97
 98
 99
100
101
102
103
104
105
def resolve_artifact_layout(
    yaml_path: str, model_name: str | None = None
) -> ArtifactLayout:
    """Resolve the artifact layout for ``yaml_path``."""
    return ArtifactLayout.build(yaml_path, model_name)


def resolve_default_artifact_layout(
    artifact_dir: str | None = None,
105
106
107
108
109
110
111
112
113
114
115
116
117
118
    artifact_dir: str | None = None,
    model_name: str | None = None,
) -> ArtifactLayout:
    """Resolve the default model-construction artifact layout."""
    return ArtifactLayout.build_default(artifact_dir, model_name)


def ensure_artifact_dir(layout: ArtifactLayout) -> None:
    """Create the artifact directory (recursive, no-op if present)."""
    Path(layout.artifact_dir).mkdir(parents=True, exist_ok=True)


def write_bundle_atomic(
    layout: ArtifactLayout, files: dict[str, str], meta: object = None
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
    three steps: move the old bundle aside, rename the staged dir into place,
    then delete the backup.  A reader on ``artifact_dir`` therefore sees either
    the old complete bundle or the new complete bundle — never a mix.
    """
    ensure_artifact_dir(layout)
    parent = os.path.dirname(layout.artifact_dir) or "."

    tmpdir = tempfile.mkdtemp(prefix=".codegen_tmp_", dir=parent)
    old_dir = None
    try:
        _stage_bundle(tmpdir, layout, files, has_meta=meta is not None)
        if meta is not None:
            _stage_meta(tmpdir, layout, meta)

        if os.path.exists(layout.artifact_dir):
            old_dir = tempfile.mkdtemp(prefix=".codegen_old_", dir=parent)
            os.rmdir(old_dir)  # reserve a name; must be absent for os.replace
            os.replace(layout.artifact_dir, old_dir)
        os.replace(tmpdir, layout.artifact_dir)
        if old_dir is not None:
            shutil.rmtree(old_dir, ignore_errors=True)
            old_dir = None
    finally:
        # If the staged dir never made it into place, put the old bundle back.
        if old_dir is not None and os.path.isdir(old_dir):
            if not os.path.exists(layout.artifact_dir):
                os.replace(old_dir, layout.artifact_dir)
            else:
                shutil.rmtree(old_dir, ignore_errors=True)
        if os.path.isdir(tmpdir):
            shutil.rmtree(tmpdir, ignore_errors=True)


def _stage_bundle(
    tmpdir: str,
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
    ``has_meta`` is False the prior ``codegen_meta.json`` is also preserved —
    otherwise the caller is going to write a fresh one into the staging dir.
    """

    def _is_tmp_name(name: str) -> bool:
        return name.startswith(".codegen_tmp_") or name.startswith(".codegen_old_")

    normalized = dict(files)
    for name in normalized:
        _validate_bundle_filename(name)

    init_present = "__init__.py" in normalized
    if not has_meta and os.path.isdir(layout.artifact_dir):
        for name in os.listdir(layout.artifact_dir):
            if _is_tmp_name(name):
                continue
            if name == "codegen_meta.json":
                continue  # preserved below, after the loop
            if name in normalized:
                continue
            src = os.path.join(layout.artifact_dir, name)
            if not os.path.isfile(src):
                continue
            if name == "__init__.py":
                init_present = True
            shutil.copy2(src, os.path.join(tmpdir, name))

    if not init_present:
        normalized["__init__.py"] = ""

    for name, content in normalized.items():
        # newline="" disables the platform's default CRLF translation so the
        # staged bytes equal the emitted text bytes exactly.  On Windows the
        # default (newline=None) would turn every LF into CRLF, inflating each
        # file by one byte per line and breaking the drift/hash checks that
208
209
210
211
212
213
214
215
216
217
218
219
220
221
        # staged bytes equal the emitted text bytes exactly.  On Windows the
        # default (newline=None) would turn every LF into CRLF, inflating each
        # file by one byte per line and breaking the drift/hash checks that
        # compare on-disk bytes against the re-emitted LF text.
        with open(
            os.path.join(tmpdir, name), "w", encoding="utf-8", newline=""
        ) as handle:
            handle.write(content)

    if not has_meta and os.path.isfile(layout.meta_path):
        shutil.copy2(
            layout.meta_path, os.path.join(tmpdir, os.path.basename(layout.meta_path))
        )

221
222
223
224
225
226
227
228
229


def _validate_bundle_filename(name: str) -> None:
    """Reject absolute, nested, and parent-relative bundle paths."""
    if (
        not name
        or os.path.isabs(name)
        or os.path.basename(name) != name
        or "/" in name
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
        or "/" in name
        or "\\" in name
        or name in {".", ".."}
    ):
        raise ValueError(f"bundle file keys must be plain names, got {name!r}")


def _stage_meta(tmpdir: str, layout: ArtifactLayout, meta: object) -> None:
    """Hash the staged content files then write ``codegen_meta.json`` last."""
    from hyper_parallel.codegen.meta import (
        record_output_hashes,
        write_codegen_meta,
    )

    record_output_hashes(meta, layout, base_dir=tmpdir)
    meta_path = os.path.join(tmpdir, os.path.basename(layout.meta_path))
    write_codegen_meta(meta, meta_path)


def clean_temp_artifacts(layout: ArtifactLayout) -> None:
    """Remove stale ``.codegen_tmp_*`` / ``.codegen_old_*`` dirs.
250
251
252
253
254
255
256
257
258
259
260
261
262
263

    Staging now happens in the artifact dir's *parent*, so these leftovers are
    scanned there rather than inside the bundle.
    """
    parent = os.path.dirname(layout.artifact_dir) or "."
    if not os.path.isdir(parent):
        return
    for name in os.listdir(parent):
        if name.startswith(".codegen_tmp_") or name.startswith(".codegen_old_"):
            shutil.rmtree(os.path.join(parent, name), ignore_errors=True)


__all__ = [
    "ArtifactLayout",
hyper_parallel/codegen/astkit/edits.py
26
27
28
29
30
31
32
33
34
    end: int
    replacement: str

    def __post_init__(self) -> None:
        if self.start < 0 or self.end < self.start:
            raise ValueError(
                f"TextEdit has an invalid span: start={self.start} end={self.end}"
            )

40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
    every later offset by the edit's length delta, so each edit is re-anchored
    by the cumulative drift of the edits before it.  Edits must be
    non-overlapping and stay within the original text — checked up front.
    """
    ordered = sorted(edits, key=lambda e: (e.start, e.end))
    if ordered:
        prev_end = ordered[0].start
        for edit in ordered:
            if edit.start < prev_end:
                raise ValueError(
                    f"apply_edits: overlapping edits — [{edit.start}, {edit.end}) "
                    f"overlaps the previous edit ending at {prev_end}"
                )
            if edit.end > len(text):
                raise ValueError(
                    f"apply_edits: edit [{edit.start}, {edit.end}) exceeds text "
                    f"length {len(text)}"
                )
            prev_end = edit.end

    out = list(text)
    shift = 0
    for edit in ordered:
        start = edit.start + shift
        end = edit.end + shift
        replacement = edit.replacement
        out[start:end] = replacement
        shift += len(replacement) - (edit.end - edit.start)
    return "".join(out)


def replace_function_body(
    source_text: str,
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105

    Returns an empty tuple when there is nothing to replace (a function with
    no body span and no way to anchor).
    """
    if func.body_start is None or func.body_end is None:
        return ()
    # Re-indent per line, preserving blank lines and inner indentation.  The
    # caller renders ``new_body`` at column-0-relative depth (blank lines carry
    # no indentation), so a statement's relative nesting survives; only the
    # method's own ``indent`` is applied once.
    body_lines = [
        (indent + line) if line.strip() else ""
        for line in new_body.splitlines(keepends=True)
    ]
    replacement = "".join(body_lines)
    return (TextEdit(func.body_start, func.body_end, replacement),)


def insert_after_imports(
    source_text: str,
114
115
116
117
118
119
120
121
122
123
124
125
126
127
    ``None`` ``import_end`` means no import block: fall back to
    ``default_offset`` (the docstring end or 0).  Returns ``None`` when there is
    no sensible anchor — caller treats that as "insert at the top".
    """
    anchor = import_end if import_end is not None else default_offset
    if anchor < 0 or anchor > len(source_text):
        return None
    # Keep the inserted text on its own lines, bracketed by newlines, so it
    # never glues onto a trailing comment or the first statement.
    return TextEdit(anchor, anchor, "\n" + insert_text + "\n")


__all__ = [
    "TextEdit",
hyper_parallel/codegen/astkit/index.py
82
83
84
85
86
87
88
89
    docstring_end: Optional[int]

    def find_class(self, name: str) -> Optional[ClassInfo]:
        """Return the ``ClassInfo`` for a top-level class, or ``None``."""
        return self.classes.get(name)

    def find_forward(self, class_name: str) -> Optional[FunctionInfo]:
        """Return the ``FunctionInfo`` for ``class_name.forward``, or ``None``.
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101

        A class without a ``forward`` (not a module) returns ``None``; the
        caller decides whether that is an error.
        """
        info = self.classes.get(class_name)
        if info is None:
            return None
        return info.methods.get("forward")


def build_source_index(source_text: str) -> SourceIndex:
    """Parse ``source_text`` and answer the three structural questions.
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130

    The index is built against the *original* text.  Caller must apply edits to
    the same unmodified string (edits.py ``apply_edits`` splices by offset).
    """
    tree = ast.parse(source_text)

    line_offsets = _line_offsets(source_text)

    def offset_for(line_no: int) -> int:
        """Byte offset of the start of ``line_no`` (1-based)."""
        if 1 <= line_no <= len(line_offsets):
            return line_offsets[line_no - 1]
        return len(source_text)

    classes: dict[str, ClassInfo] = {}
    import_end: Optional[int] = None
    docstring_end: Optional[int] = None

    body = tree.body
    # Module docstring: the first statement is a constant string (PEP 257).  Its
    # closing quote is the first insertion point for appended constants, so
    # Frozen constants belong after the docstring and before imports.
    if body and isinstance(body[0], ast.Expr) and isinstance(body[0].value, ast.Constant):
        if isinstance(body[0].value.value, str) and body[0].end_lineno is not None:
            docstring_end = offset_for(body[0].end_lineno)

    # Import block: the leading (or maximal) run of Import/ImportFrom at module
    # level.  In a well-formed module imports are first, so we take the span
    # from the first import to the last import before the first non-import
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
    # level.  In a well-formed module imports are first, so we take the span
    # from the first import to the last import before the first non-import
    # statement.  Anything after the docstring but before the first import (a
    # comment, ``__future__`` in a string, a module-level flag) is skipped.
    for node in body:
        if isinstance(node, (ast.Import, ast.ImportFrom)):
            if node.end_lineno is not None:
                import_end = offset_for(node.end_lineno)

    for node in body:
        if isinstance(node, ast.ClassDef):
            class_info = _build_class_info(node, source_text, offset_for)
            classes[class_info.name] = class_info

    return SourceIndex(
        source_text=source_text,
        classes=classes,
        import_end=import_end,
        docstring_end=docstring_end,
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
    )


def _build_class_info(node: ast.ClassDef, text: str, offset_for) -> ClassInfo:
    methods: dict[str, FunctionInfo] = {}
    for child in node.body:
        if isinstance(child, (ast.FunctionDef, ast.AsyncFunctionDef)):
            methods[child.name] = _build_function_info(child, text, offset_for)
    end_offset = (
        offset_for(node.end_lineno) if node.end_lineno is not None else len(text)
    )
    return ClassInfo(
        name=node.name,
        class_line=node.lineno,
        class_offset=offset_for(node.lineno),
        end_offset=end_offset,
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201


def _build_function_info(node: ast.AST, text: str, offset_for) -> FunctionInfo:
    """Collect offsets, parameters and ``*args``/``**kwargs`` names for a def node."""
    decorators = getattr(node, "decorator_list", [])
    first_def_line = node.lineno
    if decorators:
        first_def_line = min(decorators[0].lineno, first_def_line)
    def_offset = offset_for(first_def_line)

    # Positional parameters (``self`` excluded) — the names the boundary's
    # ``arg_name`` binds against.  Only POSITIONAL_ONLY / POSITIONAL_OR_KEYWORD
    # are part of a positional call; ``*args`` / ``**kwargs`` are not.
    params = getattr(node, "args", None)
    positional: list[str] = []
    kwonly: list[str] = []
    vararg_name: Optional[str] = None
    kwarg_name: Optional[str] = None
    if params is not None:
        for arg in params.posonlyargs + params.args:
            if arg.arg != "self":
                positional.append(arg.arg)
        kwonly = [arg.arg for arg in params.kwonlyargs]
        vararg_name = params.vararg.arg if params.vararg is not None else None
        kwarg_name = params.kwarg.arg if params.kwarg is not None else None

    body = node.body
    body_start: Optional[int] = None
    body_end: Optional[int] = None
    # A function whose body is a single `pass` / `...` / single constant
    # statement on the same line has no replaceable body span.
    has_body = any(
        not (
            isinstance(stmt, ast.Pass)
            or (isinstance(stmt, ast.Expr) and isinstance(stmt.value, ast.Constant) and stmt.value.value is Ellipsis)
        )
200
201
202
203
204
205
206
207
208
209
210
211
            or (isinstance(stmt, ast.Expr) and isinstance(stmt.value, ast.Constant) and stmt.value.value is Ellipsis)
        )
        for stmt in body
    )
    if has_body:
        first_stmt = body[0]
        body_start = offset_for(first_stmt.lineno)
        last_stmt = body[-1]
        # End just past the last statement's final character AND its trailing
        # newline (when present).  ``offset_for(end_lineno)`` is only the START
        # of that line — using it here would leave the last statement (e.g.
        # ``return x``) outside the replaceable span, so a body swap would
213
214
215
216
217
218
219
220
221
222
223
224
225
226
        # start-of-line offset plus the statement's column + 1 lands just past
        # the line's newline; when the statement is the last line and the file
        # has no trailing newline, that would overshoot EOF, so clamp to
        # ``len(text)``.
        body_end = (
            min(offset_for(last_stmt.end_lineno) + last_stmt.end_col_offset + 1, len(text))
            if last_stmt.end_lineno is not None
            else len(text)
        )
    return FunctionInfo(
        name=node.name,
        body_start=body_start,
        body_end=body_end,
        def_offset=def_offset,
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
    strips that common prefix so the isolated def parses as a top-level
    statement (an indented first line would raise ``IndentationError`` and
    fail every method closed).
    """
    if func.body_start is None or func.body_end is None:
        return False
    def_line_start = _def_keyword_offset(source_text, func)
    span = textwrap.dedent(source_text[def_line_start:func.body_end])
    try:
        tree = ast.parse(span)
    except SyntaxError:
        return False
    for node in ast.walk(tree):
        if isinstance(node, ast.Return):
            value = node.value
            if value is None:
                continue
            if isinstance(value, (ast.Tuple, ast.List, ast.Starred)):
                return False
    return True


def returns_any_value(source_text: str, func: FunctionInfo) -> bool:
    """Whether every ``return`` exits with a single value or a tuple value.
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
    ``outputs[0]`` and rebuild the original object, matching the compiled plan's
    index-0 behavior. A bare ``return`` has no value to transform; a list return
    would need list-specific rebuilding, so it fails closed for now.
    """
    if func.body_start is None or func.body_end is None:
        return False
    def_line_start = _def_keyword_offset(source_text, func)
    span = textwrap.dedent(source_text[def_line_start:func.body_end])
    try:
        tree = ast.parse(span)
    except SyntaxError:
        return False
    seen_return = False
    for node in ast.walk(tree):
        if isinstance(node, ast.Return):
            seen_return = True
            if node.value is None:
                return False
            if isinstance(node.value, (ast.List, ast.Starred)):
                return False
    return seen_return


def _def_keyword_offset(source_text: str, func: FunctionInfo) -> int:
    """Byte offset of the ``def`` keyword line, skipping decorator lines.
300
301
302
303
304
305
306
307
308
309
310
311
312
313
    one is present); the span fed to ``ast.parse`` must start at the ``def``
    keyword itself.  Shared logic with ``_build_forward_impl_edit`` in
    emit/parallel (kept local — three lines, not worth a cross-module helper).
    """
    def_line_start = func.def_offset
    for line in source_text[def_line_start:func.body_start].splitlines(keepends=True):
        if line.lstrip().startswith("def "):
            break
        def_line_start += len(line)
    return def_line_start


def _line_offsets(text: str) -> list[int]:
    """Byte offset of each line's start (``offsets[line_no - 1]`` is 1-based line ``line_no``).
314
315
316
317
318
319
320
321

    Single implementation, shared with ``codegen.source.compat`` (which imports
    it from here — the existing layering direction is ``source`` -> ``astkit``).
    """
    offsets: list[int] = [0]
    for line in text.splitlines(keepends=True):
        offsets.append(offsets[-1] + len(line))
    return offsets
hyper_parallel/codegen/check/drift.py
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
against the *hashes recorded in meta*): this check re-derives the expected
bytes from the meta itself, so it catches an edit that was also re-touched in
meta, or an emit-layer regression.
"""
from __future__ import annotations

import logging
import os
from typing import Any, Optional

from hyper_parallel.codegen.artifact import resolve_artifact_layout

logger = logging.getLogger(__name__)


def verify_drift_intact(meta: Any, yaml_path: str) -> list[str]:
    """Regenerate the bundle from ``meta`` and compare with the on-disk files.

    Returns a list of drift descriptions (empty = intact).  Raises
    ``FileNotFoundError`` when meta or the bundle directory is missing.  When
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
    the original modeling source file can no longer be read (e.g. a remote-code
    cache was evicted), drift is *reported*, not silently skipped: the artifact
    cannot be re-validated against its origin.
    """
    layout = resolve_artifact_layout(yaml_path, model_name=_model_name(meta))
    if not os.path.isdir(layout.artifact_dir):
        raise FileNotFoundError(f"codegen drift: bundle missing at {layout.artifact_dir}")

    from hyper_parallel.codegen.emit import emit_bundle

    files = emit_bundle(layout, meta)

    drifts: list[str] = []
    for name, expected in files.items():
        path = os.path.join(layout.artifact_dir, name)
        if not os.path.isfile(path):
            drifts.append(f"{name}: missing on disk")
            continue
        with open(path, "r", encoding="utf-8", errors="replace") as handle:
            actual = handle.read()
        if actual != expected:
            drifts.append(f"{name}: bytes differ from re-emitted bundle")
    return drifts


def _model_name(meta: Any) -> Optional[str]:
    """The model name recorded in meta (the generated filename stems from it)."""
    return getattr(meta, "model_name", None)


__all__ = [
    "verify_drift_intact",
]
hyper_parallel/codegen/check/pin.py
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
of an environment mismatch, it just cannot be validated by the pin — so the
caller exits 0 either way, and reserves exit 1 for "the pin itself cannot be
parsed" / "the installed version cannot be read".
"""
from __future__ import annotations

import logging
from typing import Optional, Tuple

logger = logging.getLogger(__name__)

# The public status the CLI maps to an exit code.
OK = "OK"
SKIP = "SKIP"


def verify_transformers_pin(spec: str) -> Tuple[str, str, str]:
    """Compare the installed transformers to ``spec``.

    Returns ``(status, installed, spec)`` where ``status`` is ``OK`` when the
    installed version satisfies ``spec`` and ``SKIP`` otherwise.  Raises
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
    ``ValueError`` when ``spec`` cannot be parsed and ``ImportError`` when the
    installed transformers version cannot be read (both are exit-1 conditions
    for the CLI).
    """
    installed = _installed_version()
    if installed is None:
        raise ImportError("codegen check: cannot read installed transformers version")

    from packaging.specifiers import SpecifierSet

    try:
        pin = SpecifierSet(spec)
    except Exception as exc:
        raise ValueError(f"codegen check: invalid transformers pin {spec!r}: {exc}") from exc

    if installed in pin:
        return OK, installed, spec
    logger.warning(
        "codegen check: installed transformers %s does not satisfy pin %s — "
        "SKIP (artifact is not stale, pin is not satisfiable here)",
        installed, spec,
    )
    return SKIP, installed, spec


def _installed_version() -> Optional[str]:
    """Installed transformers version, or ``None`` when not importable."""
    try:
        return _version_from_importlib_metadata()
    except ImportError:
        return None


def _version_from_importlib_metadata() -> str:
    import importlib.metadata

    return importlib.metadata.version("transformers")


__all__ = [
    "OK",
    "SKIP",
    "verify_transformers_pin",
]
hyper_parallel/codegen/check/preflight.py
37
38
39
40
41
42
43
44
45

    A mismatch means the YAML / source / env changed since generation — the
    bundle is stale and must be regenerated before training.
    """
    if meta.signature != current_signature:
        raise RuntimeError(
            f"codegen preflight: signature mismatch for {meta.yaml_path} "
            f"(stored {meta.signature}, current {current_signature}); regenerate"
        )
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
    The meta file itself is not in that set: a file cannot carry its own
    sha256 (writing the value changes the bytes it hashes), so meta
    integrity is covered by the signature and schema checks instead.
    """
    core_paths = {
        "modeling": layout.modeling_path,
        "diff": layout.diff_path,
        "init": layout.init_path,
    }
    for key in core_paths:
        if key not in meta.outputs:
            raise RuntimeError(f"codegen preflight: missing {key} output hash in meta")

    for sibling in getattr(meta, "remote_siblings", None) or []:
        if sibling not in meta.outputs:
            raise RuntimeError(
                f"codegen preflight: missing output hash for remote sibling {sibling!r}"
            )

    for key, recorded in meta.outputs.items():
        path = core_paths.get(key)
        if path is None:
            if (
                not key
                or os.path.basename(key) != key
                or "/" in key
                or "\\" in key
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
                or "/" in key
                or "\\" in key
                or key in {".", ".."}
            ):
                raise RuntimeError(
                    f"codegen preflight: invalid output filename {key!r}"
                )
            path = os.path.join(layout.artifact_dir, key)
        if not os.path.isfile(path):
            raise RuntimeError(
                f"codegen preflight: {key} file missing at {path} "
                f"(recorded sha256 {recorded.get('sha256')})"
            )
        actual = sha256_file(path)
        if recorded.get("sha256") != actual:
            raise RuntimeError(
                f"codegen preflight: {key} hash drift at {path} "
                f"(recorded {recorded.get('sha256')}, actual {actual})"
            )
107
108
109
110
111
112
113
114
115
116
117
    ``sys.modules`` by the loader, so preflight and the later build reuse the
    same import. A minimal placeholder also imports because preflight checks
    bundle structure, while the emit layer owns lowering completeness.
    """
    from hyper_parallel.codegen.loader import import_generated_module

    import_generated_module(layout.artifact_dir)


def verify_meta_required_fields(meta: Any) -> None:
    """Fail fast when the meta record is missing required schema fields.
118
119
120
121
122
123
124
125
126
127
128

    Delegates to the meta module's schema validation; a stale or hand-edited
    meta file that lost a required field must not pass preflight.
    """
    from hyper_parallel.codegen.meta import validate_meta_schema

    validate_meta_schema(meta_to_dict(meta))


def warn_skipped_overrides(meta: Any) -> None:
    """Warn (not error) when generation skipped any plan_overrides entries.
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
    An entry skipped during generation (or a ``when``-gated entry that never
    applied) must be surfaced
    so the user knows the artifact does not contain what the YAML declared.
    """
    skipped = getattr(meta, "skipped_overrides", None) or []
    if not skipped:
        return
    lines = [f"codegen: {len(skipped)} plan_overrides entries were skipped:"]
    for item in skipped:
        match = item.get("match") if isinstance(item, dict) else None
        reason = item.get("reason") if isinstance(item, dict) else item
        lines.append(f"  - match={match!r}: {reason}")
    logger.warning("\n".join(lines))


def verify_boundary_forms(meta: Any, layout: Any) -> None:
    """Fail fast when persisted forward forms disagree with the frozen plan.
172
173
174
175
176
177
178
179
180
181
182
183
    A drift means the modeling file was edited after generation (or the
    emitter and this check disagree); the install-time validation would
    silently fall back, masking a broken artifact, so fail fast instead.
    """
    import re

    from hyper_parallel.codegen.astkit.index import build_source_index
    from hyper_parallel.codegen.emit.parallel import (
        TOGGLE_FORM_MARKER,
        TP_FORM_ATTRIBUTE,
        TP_FORM_MARKER,
        iter_emitted_forms,
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
        TP_FORM_ATTRIBUTE,
        TP_FORM_MARKER,
        iter_emitted_forms,
    )
    from hyper_parallel.codegen.plan.boundary_forms import (
        FORM_IDENTITY,
        FORM_TP_COLLECTIVE,
    )

    with open(layout.modeling_path, "r", encoding="utf-8") as handle:
        source_text = handle.read()
    index = build_source_index(source_text)
    boundary_classes = getattr(meta, "boundary_classes", None) or {}
    source = getattr(meta, "source", None)
    module_name = (
        source.get("module_name")
        if isinstance(source, dict)
        else getattr(source, "module_name", "")
    )
197
198
199
200
201
202
203
204
205
        if isinstance(source, dict)
        else getattr(source, "module_name", "")
    )

    for class_name, _, emitted, func, _, deferred_bias in iter_emitted_forms(
        source_text,
        meta,
        boundary_classes=boundary_classes,
        module_name=module_name,
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
        meta,
        boundary_classes=boundary_classes,
        module_name=module_name,
    ):
        info = index.find_class(class_name)
        has_impl = info is not None and "_forward_impl" in info.methods
        class_body = source_text[info.class_offset:info.end_offset] if info else ""
        has_marker = f'{TP_FORM_ATTRIBUTE} = "{TP_FORM_MARKER}"' in class_body
        # The opt-in toggle template marks its classes with TOGGLE_FORM_MARKER:
        # a boundary the classifier calls tp_collective carries either marker
        # depending on which generation mode emitted it.
        has_toggle_marker = (
            f'{TP_FORM_ATTRIBUTE} = "{TOGGLE_FORM_MARKER}"' in class_body
        )
        has_inline_marker = "_hp_inline_boundary = True" in class_body
        body = source_text[func.body_start:func.body_end]
        # Every generated communication path — inlined or lowered — reads the
        # instance-attribute channel (``self._hp_tp`` / ``self._hp_cp_mesh``
        # / ``self._hp_ep_group`` / ``self.<axis>_enable``) the runtime binds
        # at install time.  The retired module-level accessor must never come
221
222
223
224
225
226
227
228
229
230
231
        # / ``self._hp_ep_group`` / ``self.<axis>_enable``) the runtime binds
        # at install time.  The retired module-level accessor must never come
        # back: a body still reading it was emitted by the accessor era, and the
        # class would raise NameError at train time.
        stray_accessor = re.search(r"get_parallel_state|get_inline_parallel_state", body)
        if stray_accessor is not None:
            raise RuntimeError(
                f"codegen preflight: class {class_name} still reads the removed "
                f"module-level accessor {stray_accessor.group(0)!r}; the parallel "
                "channel is bound on the instance at install time — regenerate "
                "the artifact"
229
230
231
232
233
234
235
236
237
                f"module-level accessor {stray_accessor.group(0)!r}; the parallel "
                "channel is bound on the instance at install time — regenerate "
                "the artifact"
            )
        has_inline_channel = any(
            token in body
            for token in (
                "self._hp_tp",
                "self._hp_cp_mesh",
241
242
243
244
245
246
247
248
249
                "self.ep_enable",
                "ep_all_to_all",
            )
        )
        if has_inline_marker or (
            has_inline_channel and not (has_impl or has_marker or has_toggle_marker)
        ):
            # A fully inlined component body (the inline-marker shape): it
            # keeps no extracted ``_forward_impl`` and no form marker, and
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
        ):
            # A fully inlined component body (the inline-marker shape): it
            # keeps no extracted ``_forward_impl`` and no form marker, and
            # must expose the generated communication path through the channel.
            if not has_inline_channel:
                raise RuntimeError(
                    f"codegen preflight: inline boundary class {class_name} "
                    "does not expose a generated communication path; "
                    "regenerate the artifact"
                )
            if has_marker:
                raise RuntimeError(
                    f"codegen preflight: clean inline boundary class "
                    f"{class_name} also carries the {TP_FORM_MARKER} marker; "
                    "regenerate the artifact"
                )
            continue
        if emitted == FORM_IDENTITY:
            if has_impl or has_marker:
                raise RuntimeError(
                    f"codegen preflight: class {class_name} was pruned as "
                    f"identity but the source carries a rewritten forward "
                    f"(marker={has_marker}, _forward_impl={has_impl}); "
                    "regenerate the artifact"
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
                    f"identity but the source carries a rewritten forward "
                    f"(marker={has_marker}, _forward_impl={has_impl}); "
                    "regenerate the artifact"
                )
            continue
        if not has_impl:
            raise RuntimeError(
                f"codegen preflight: class {class_name} should carry an "
                f"extracted _forward_impl for form {emitted!r} but does not; "
                "regenerate the artifact"
            )
        if emitted == FORM_TP_COLLECTIVE:
            if not (has_marker or has_toggle_marker):
                raise RuntimeError(
                    f"codegen preflight: class {class_name} should carry the "
                    f"{TP_FORM_MARKER} marker but does not; regenerate the "
                    "artifact"
                )
            body = source_text[func.body_start:func.body_end]
            # The static template's own channel is ``self._hp_tp``; a
            # class whose frozen entry declares D-22 deferred bias params
            # additionally calls ``self._hp_deferred_bias`` at the exit
            # (bound at install time).  Anything else is drift.
            allowed = "tp|deferred_bias" if deferred_bias else "tp"
            stray = re.search(rf"self\._hp_(?!({allowed})\b)", body)
            if stray is not None:
                raise RuntimeError(
                    f"codegen preflight: static tp_collective forward on "
                    f"class {class_name} references {stray.group(0)}* "
                    "attributes outside the static template; regenerate the "
                    "artifact"
296
297
298
299
300
301
302
303
304
305
                    f"class {class_name} references {stray.group(0)}* "
                    "attributes outside the static template; regenerate the "
                    "artifact"
                )
        elif has_marker:
            raise RuntimeError(
                f"codegen preflight: class {class_name} carries the "
                f"{TP_FORM_MARKER} marker but its emitted form is "
                f"{emitted!r}; regenerate the artifact"
            )
331
332
333
334
335
336
337
338
339
340
341
    variant) fails fast instead of silently running with a stale sharding
    contract.  Degrades to a warning when ``model`` is not given (preflight
    without a model handle can still check the plan's internal consistency).
    """
    param_plan = getattr(meta, "param_plan", None) or {}
    if not param_plan:
        raise RuntimeError(
            "codegen preflight: param plan is empty — generation did not "
            "freeze a sharding plan (derivation failed or was skipped); "
            "check generation logs and regenerate the artifact"
        )
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
            "freeze a sharding plan (derivation failed or was skipped); "
            "check generation logs and regenerate the artifact"
        )

    if model is None:
        logger.warning(
            "codegen preflight: no model handle — param-plan coverage "
            "skipped (only internal consistency checked)"
        )
        return

    # Every planned parameter key must resolve to a model parameter.
    planned: set[str] = set()
    missing: list[str] = []
    for boundary, entry in param_plan.items():
        params = entry.get("params", {}) if isinstance(entry, dict) else {}
        for name in params:
            fqn = f"{boundary}.{name}" if name else boundary
            planned.add(fqn)
            if _get_parameter(model, fqn) is None:
                missing.append(fqn)
    if missing:
        raise RuntimeError(
            f"codegen preflight: param plan references parameters missing "
            f"from the model: {missing}"
        )
364
365
366
367
368
369
370
371
372
373
374
375
376
377
        )

    # Every trainable model parameter must be covered by an explicit plan
    # entry (exact FQN or a leaf under a grouped key).
    uncovered = [
        fqn
        for fqn in _iter_trainable_params(model)
        if not any(fqn == key or fqn.startswith(f"{key}.") for key in planned)
    ]
    if uncovered:
        raise RuntimeError(
            f"codegen preflight: {len(uncovered)} trainable parameters are "
            f"not covered by the frozen plan: {uncovered[:20]}"
        )
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424


def meta_to_dict(meta: Any) -> dict[str, Any]:
    """Serialize meta to dict, or pass through a dict already."""
    if isinstance(meta, dict):
        return meta
    from hyper_parallel.codegen.meta import meta_to_dict as _meta_to_dict

    return _meta_to_dict(meta)


def _iter_trainable_params(model: Any):
    """Yield ``fqn`` for every trainable parameter of ``model`` (or its config)."""
    try:
        import torch  # pylint: disable=unused-import
    except ImportError:
        return
    seen: set[int] = set()
    for module_name, module in model.named_modules():
        for param_name, param in module.named_parameters(recurse=False):
            if not param.requires_grad:
                continue
            fqn = f"{module_name}.{param_name}" if module_name else param_name
            if id(param) in seen:
                continue
            seen.add(id(param))
            yield fqn


def _get_parameter(model: Any, fqn: str) -> Any:
    """Look up a parameter by dotted FQN, tolerating an absent attribute."""
    obj = model
    for part in fqn.split("."):
        try:
            obj = getattr(obj, part)
        except AttributeError:
            return None
    return obj if hasattr(obj, "requires_grad") else None


__all__ = [
    "verify_boundary_forms",
hyper_parallel/codegen/cli.py
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40

Exit codes are 0 for success or a skipped pin check, 1 for a check/clean
failure, and 2 for a usage error.
"""
from __future__ import annotations

import argparse
import logging
import os
import sys
from typing import Iterator, Optional

logger = logging.getLogger(__name__)


def main(argv: Optional[list[str]] = None) -> int:
    """argparse entry; returns the process exit code."""
    parser = _build_parser()
    args, config_overrides = parser.parse_known_args(argv)
    setattr(args, "_config_overrides", config_overrides)
    if not getattr(args, "command", None):
        parser.print_usage(sys.stderr)
        return 2
    _configure_logging(getattr(args, "verbose", False))
    try:
        return args.func(args)
    except Exception as exc:  # noqa: BLE001 - a CLI reports, does not traceback
        logger.error("codegen: %s", exc)
        return 1


# ---------------------------------------------------------------------------
# commands
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
# ---------------------------------------------------------------------------
# commands
# ---------------------------------------------------------------------------

def cmd_generate(args: argparse.Namespace) -> int:
    """``generate --config <yaml>`` — build/reuse the bundle for one YAML."""
    config = _load_config(args.config, _cli_overrides(args))
    from hyper_parallel.codegen.manager import ensure_codegen_artifact

    if not getattr(config, "codegen", False):
        logger.info("codegen: generate skipped — codegen is false")
        return 0
    layout = ensure_codegen_artifact(config, args.config)
    if layout is None:
        return 0
    from hyper_parallel.codegen.meta import load_codegen_meta

    meta = load_codegen_meta(layout.meta_path)
    signature = meta.signature if meta is not None else "?"
    logger.info("codegen: artifact %s (signature %s)", layout.artifact_dir, signature)
    return 0


def cmd_check(args: argparse.Namespace) -> int:
    """``check --config <yaml>`` — full preflight for one YAML."""
    config = _load_config(args.config, _cli_overrides(args))
    if not getattr(config, "codegen", False):
        logger.info("codegen: SKIP (codegen disabled)")
        return 0
    from hyper_parallel.codegen.manager import preflight_integrity_check

    preflight_integrity_check(config, args.config)
    logger.info("codegen: PASS %s", args.config)
    return 0


def cmd_check_all(args: argparse.Namespace) -> int:
    """Run preflight for every discovered YAML and check cached artifact drift."""
    if _cli_overrides(args):
        raise ValueError("check --all does not accept per-config CLI overrides")
    failed = 0
    for yaml_path in discover_yaml_files(args.root):
        try:
            config = _load_config(yaml_path)
            if not getattr(config, "codegen", False):
                logger.info("codegen: SKIP (codegen disabled) %s", yaml_path)
                continue
            from hyper_parallel.codegen.manager import preflight_integrity_check

            preflight_integrity_check(config, yaml_path)
            logger.info("codegen: PASS %s", yaml_path)
            # Drift can only be checked when an artifact already exists.
            from hyper_parallel.codegen.check.drift import verify_drift_intact
            from hyper_parallel.codegen.meta import load_codegen_meta
            from hyper_parallel.codegen.artifact import resolve_artifact_layout

            layout = resolve_artifact_layout(yaml_path)
            meta = load_codegen_meta(layout.meta_path)
            if meta is not None:
                drifts = verify_drift_intact(meta, yaml_path)
                if drifts:
                    failed = 1
                    logger.error("codegen: drift in %s: %s", yaml_path, "; ".join(drifts))
                else:
                    logger.info("codegen: drift intact %s", yaml_path)
        except Exception as exc:  # noqa: BLE001 - aggregate, keep scanning
            failed = 1
            logger.error("codegen: FAIL %s: %s", yaml_path, exc)
    return failed


def cmd_check_pin(args: argparse.Namespace) -> int:
    """Validate a Transformers version constraint without regenerating artifacts."""
    from hyper_parallel.codegen.check.pin import verify_transformers_pin

    status, installed, spec = verify_transformers_pin(args.transformers_pin)
    logger.info("codegen: transformers pin %s — installed %s (%s)", spec, installed, status)
    return 0  # OK and SKIP both exit 0


# ---------------------------------------------------------------------------
# discovery & helpers
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
# ---------------------------------------------------------------------------
# discovery & helpers
# ---------------------------------------------------------------------------

_SKIP_DIRS = {"generated", ".git", ".venv", "venv", "__pycache__", ".tmp"}


def discover_yaml_files(root: str) -> Iterator[str]:
    """Walk ``root`` for ``*.yaml`` / ``*.yml``, skipping generated/venv/git dirs."""
    if not os.path.isdir(root):
        return
    for dirpath, dirnames, filenames in os.walk(root):
        dirnames[:] = [d for d in dirnames if d not in _SKIP_DIRS]
        for name in sorted(filenames):
            if name.endswith((".yaml", ".yml")):
                yield os.path.join(dirpath, name)


def _load_config(yaml_path: str, overrides: Optional[list[str]] = None):
    """Load a ``TrainerConfig`` from a YAML path."""
    from hyper_parallel.trainer.config.parser import load_training_config

    return load_training_config(yaml_path, overrides or [])


def _model_name(config: object) -> Optional[str]:
    from hyper_parallel.codegen.manager import (  # pylint: disable=import-outside-toplevel
        _model_name as _manager_model_name,
        _resolve_hf_config_for_layout,
    )

    return _manager_model_name(config, _resolve_hf_config_for_layout(config))


def _model_name_for(yaml_path: str, overrides: Optional[list[str]] = None) -> Optional[str]:
    """Model name from a YAML (used by ``clean`` before config load)."""
    try:
        return _model_name(_load_config(yaml_path, overrides))
    except Exception:  # noqa: BLE001 - clean falls back to default naming
        return None


def _cli_overrides(args: argparse.Namespace) -> list[str]:
    """Typed config overrides captured after argparse consumes codegen flags."""
    return list(getattr(args, "_config_overrides", None) or [])


def _configure_logging(verbose: bool) -> None:
    logging.basicConfig(
        level=logging.INFO if verbose else logging.WARNING,
        format="%(levelname)s:%(name)s:%(message)s",
    )
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
        format="%(levelname)s:%(name)s:%(message)s",
    )


def _build_parser() -> argparse.ArgumentParser:
    """Build the argparse parser for the codegen CLI."""
    parser = argparse.ArgumentParser(
        prog="hyper_parallel.codegen",
        description="Codegen: generate/check/clean codegen artifacts.",
    )
    sub = parser.add_subparsers(dest="command")
    parser.add_argument("--verbose", action="store_true", help="verbose logging")

    gen = sub.add_parser("generate", help="generate or reuse the bundle for one YAML")
    gen.add_argument("--config", required=True, help="path to the training YAML")
    gen.set_defaults(func=cmd_generate)

    check = sub.add_parser("check", help="preflight integrity check")
    check.add_argument("--config", help="path to the training YAML")
    check.add_argument("--all", action="store_true", help="check every discovered YAML")
    check.add_argument("--root", default=".", help="root to search when --all")
    check.add_argument("--transformers-pin", help="Transformers version constraint (e.g. ==5.15.0)")
    check.set_defaults(func=_cmd_check_dispatch)

    return parser


def _cmd_check_dispatch(args: argparse.Namespace) -> int:
    """Route the ``check`` subcommand's mutually-exclusive modes."""
    modes = [bool(args.all), bool(args.transformers_pin)]
    if sum(modes) > 1:
        raise ValueError("check accepts only one of --all / --transformers-pin")
    if args.all:
        return cmd_check_all(args)
    if args.transformers_pin:
        return cmd_check_pin(args)
    if not args.config:
        raise ValueError("check needs --config, --all, or --transformers-pin")
    return cmd_check(args)


__all__ = [
    "cmd_check",
    "cmd_check_all",
    "cmd_check_pin",
    "cmd_generate",
hyper_parallel/codegen/emit/bundle.py
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
    staging dir.

    ``meta.covered`` is prepared by the manager before emission.
    """
    original_text = copy_original_modeling(meta.source)
    generated_text = emit_modeling_file(meta)

    modeling_name = os.path.basename(layout.modeling_path)
    diff_text = emit_diff(original_text, generated_text, layout)

    logger.info(
        "codegen: rendered bundle %s (modeling %s, %d plan boundaries)",
        layout.artifact_dir,
        modeling_name,
        len(getattr(meta, "param_plan", None) or {}),
56
57
58
59
60
61
62
63
64
        layout.artifact_dir,
        modeling_name,
        len(getattr(meta, "param_plan", None) or {}),
    )
    files = {
        # Filename keys (not absolute paths): write_bundle_atomic stages them
        # in a temp sibling and swaps the whole directory in.
        modeling_name: generated_text,
        os.path.basename(layout.diff_path): diff_text,
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
    # package ``hyper_parallel_generated.<digest>``, so those siblings must be
    # IN the bundle and registered under that package.  Copy the transitive
    # closure of the source's relative imports here (original basenames), and
    # record what was copied so preflight/loader can tell a stale bundle.
    siblings: dict[str, str] = {}
    source_obj = meta.source
    source_module = (
        source_obj.get("module_name") if isinstance(source_obj, dict)
        else getattr(source_obj, "module_name", None)
    )
    if source_module == "<remote>":
        from hyper_parallel.codegen.source.resolver import resolve_remote_siblings

        resolved = resolve_remote_siblings(source_obj)
        for path in resolved.values():
            basename = os.path.basename(path)
            with open(path, "r", encoding="utf-8", errors="replace") as handle:
                siblings[basename] = handle.read()
        files.update(siblings)
        if siblings:
            meta.remote_siblings = sorted(siblings)
    return files


def emit_init_file(layout: ArtifactLayout) -> str:
    """Render the bundle's ``__init__.py`` text.
 98
 99
100
101
102
103
104
105
106
    The bundle is imported by file location (loader.import_generated_module),
    not as a package, so this file carries no re-exports — it only marks the
    directory and records what the bundle is for a reader who opens it.
    """
    return (
        '"""Generated codegen bundle — DO NOT EDIT MANUALLY.\n'
        "\n"
        f"Generated from: {layout.yaml_path}\n"
        "Regenerate by re-running training with ``codegen: true``; the bundle\n"
114
115
116
117
118
119
120
121
122

    Returned rather than written so ``emit_bundle`` can write the whole bundle
    in one atomic call.
    """
    return unified_diff(
        original_text,
        generated_text,
        fromfile=f"a/{_source_basename(layout)}",
        tofile=f"b/{os.path.basename(layout.modeling_path)}",
124
125
126
127
128
129
130
131
132
133
134
135


def _source_basename(layout: ArtifactLayout) -> str:
    """Diff ``fromfile`` label: the original modeling filename."""
    name = os.path.basename(layout.modeling_path)
    if name.endswith("_gen_npu.py"):
        return name[: -len("_gen_npu.py")] + ".py"
    return name


__all__ = [
    "emit_bundle",
hyper_parallel/codegen/emit/diff.py
23
24
25
26
27
28
29
30
31
    diff headers (e.g. ``modeling_deepseek_v3.py`` and
    ``modeling_deepseek_v3_gen_npu.py``).  An empty result means the two files
    are identical.
    """
    return "".join(
        difflib.unified_diff(
            original.splitlines(keepends=True),
            generated.splitlines(keepends=True),
            fromfile=fromfile,
hyper_parallel/codegen/emit/modeling.py
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
    a ``file_path`` key.  Accepts either a dict or a ``ResolvedSource`` (the
    dict form is what the manager records, but the helper tolerates the live
    dataclass so it stays usable outside the meta path).
    """
    if isinstance(source, dict):
        path = source.get("file_path")
    else:
        path = getattr(source, "file_path", None)
    if not path or not os.path.isfile(path):
        raise FileNotFoundError(
            f"codegen emit: original modeling source is not a readable file "
            f"({path!r}); generation must run where the model's modeling file "
            "exists"
        )
    with open(path, "r", encoding="utf-8", errors="replace") as handle:
        return handle.read()


def rewrite_relative_imports(text: str, module_name: str) -> str:
    """Rewrite ``from .xxx`` / ``from ...xxx`` imports to absolute imports.
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
    list, comments, and whitespace are preserved.  It uses ``tokenize`` rather
    than a regex so relative-looking text inside string literals or comments is
    never touched.
    """
    unless_anchor = _relative_anchor(module_name)
    if unless_anchor is None:
        return text

    try:
        tokens = list(tokenize.generate_tokens(io.StringIO(text).readline))
    except tokenize.TokenError:
        return text
    if not tokens:
        return text

    edits: list[tuple[int, int, str]] = []
    i = 0
    n = len(tokens)
    while i < n:
        tk = tokens[i]
        if tk.type != tokenize.NAME or tk.string != "from":
            i += 1
            continue
        # the spec must start with a relative (dots) operator
        j = i + 1
        while j < n and tokens[j].type in (tokenize.NL, tokenize.NEWLINE, tokenize.COMMENT):
            j += 1
        if j >= n or tokens[j].type != tokenize.OP or not set(tokens[j].string) <= {"."}:
            i += 1
            continue
        dots_tok = tokens[j]
        dots = len(dots_tok.string)

        # collect the relative module path: NAME ('.' NAME)*
        k = j + 1
        path: list[str] = []
        last_path_tok = None
        while k < n:
            t = tokens[k]
            if t.type == tokenize.NAME and t.string != "import":
                path.append(t.string)
                last_path_tok = t
                k += 1
                if k < n and tokens[k].type == tokenize.OP and tokens[k].string == ".":
                    k += 1  # consume the separator; import follows next round
                else:
                    break
            else:
                break

        # the path must be followed by the ``import`` keyword (allow a comment
        # line in between; a multiline symbol list comes *after* ``import``).
        m = k
        while m < n and tokens[m].type in (tokenize.NL, tokenize.NEWLINE, tokenize.COMMENT):
            m += 1
        if m >= n or tokens[m].type != tokenize.NAME or tokens[m].string != "import":
            i += 1
            continue

        abs_spec = _absolute_spec(unless_anchor, dots, path)
        if abs_spec is None:
            i += 1
            continue

        start = dots_tok.start
        end = last_path_tok.end if last_path_tok is not None else dots_tok.end
        edits.append((start, end, abs_spec))
        i = k + 1

    if not edits:
        return text
    return _splice(text, edits)


def _relative_anchor(module_name: str) -> list[str] | None:
    """Return the dotted package segments a relative spec anchors on, or None.
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
    anchors.  The ``"<remote>"`` sentinel (trusted remote code) and any
    non-dotted / placeholder-laden name cannot anchor — the caller leaves such
    imports untouched.
    """
    if not isinstance(module_name, str) or not module_name:
        return None
    if "<" in module_name or ">" in module_name:
        return None
    segs = module_name.split(".")
    if len(segs) < 2 or any(not seg for seg in segs):
        return None
    return segs


def _absolute_spec(anchor: list[str], dots: int, path: list[str]) -> str | None:
    """Compute the absolute import spec for one relative import, or None.
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
    above the module's parent package, then append ``path``.  Returns None when
    the relative import climbs above the package root (invalid source — leave
    it untouched rather than corrupt it).
    """
    parent = anchor[:-1]
    climb = dots - 1
    if climb > len(parent):
        return None
    base_parts = parent if climb <= 0 else parent[: len(parent) - climb]
    parts = base_parts + path
    return ".".join(part for part in parts if part)


def _splice(text: str, edits: list[tuple[tuple[int, int], tuple[int, int], str]]) -> str:
    """Splice absolute specs back into ``text`` by token (row, col) positions.
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
    contiguous span is replaced in every case: the relative spec starts at the
    dots token and ends at the last module-path token (or the dots token when
    there is no path, i.e. ``from . import``).
    """
    lines = text.splitlines(keepends=True)
    if not lines:
        return text
    line_offsets = []
    acc = 0
    for line in lines:
        line_offsets.append(acc)
        acc += len(line)

    def offset(row: int, col: int) -> int:
        if 1 <= row <= len(line_offsets):
            return line_offsets[row - 1] + col
        return acc

    positioned = sorted(
        ((offset(*start), offset(*end), new) for start, end, new in edits),
        key=lambda item: item[0],
        reverse=True,
    )
    out = text
    for start, end, new in positioned:
        if start < end <= len(out):
            out = out[:start] + new + out[end:]
    return out


# ---------------------------------------------------------------------------
# file assembly
240
241
242
243
244
245
246
247
248
    first. The header banner carries the generation provenance; the source is
    copied verbatim, rewritten by the adapter-driven inline pipeline, and then
    lowered at the boundary classes.
    """
    text = copy_original_modeling(meta.source)
    # Stale-source sanitize: a trust-remote-code modeling file ships with the
    # checkpoint and reflects the author's transformers, not the installed one.
    # Removing ``is_torch_fx_available`` (a transformers>=5.0 casualty) here
    # keeps the artifact importable without touching the source file. No-op
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
    # checkpoint and reflects the author's transformers, not the installed one.
    # Removing ``is_torch_fx_available`` (a transformers>=5.0 casualty) here
    # keeps the artifact importable without touching the source file. No-op
    # when the source does not import the symbol.
    from hyper_parallel.codegen.source.compat import sanitize_source_compat

    text = sanitize_source_compat(text)
    module_name = (
        meta.source.get("module_name")
        if isinstance(meta.source, dict)
        else getattr(meta.source, "module_name", "")
    )
    text = rewrite_relative_imports(text, module_name)
    text = _apply_inline_modeling(text, meta)
    text = _lower_forward_boundaries(text, meta, module_name)
    return _banner(meta) + text


def _apply_inline_modeling(text: str, meta: Any) -> str:
    """Render the artifact through the adapter-driven inline source pipeline."""
263
264
265
266
267
268
269
270
271
272
273
274
275
276

def _apply_inline_modeling(text: str, meta: Any) -> str:
    """Render the artifact through the adapter-driven inline source pipeline."""

    from hyper_parallel.codegen.inline import render_inline_modeling

    source = getattr(meta, "source", None)
    architecture = source.get("architecture") if isinstance(source, dict) else getattr(source, "architecture", None)
    model_type = getattr(meta, "model_class", None) or architecture
    return render_inline_modeling(text, meta, model_type=model_type)


def _lower_forward_boundaries(text: str, meta: Any, module_name: str) -> str:
    """Sink boundary wrappers into the model's ``forward`` bodies.
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
    behavior (params sharded by the native Phase A reuse, no forward rewrite).
    When the map is absent, the source remains untouched and runtime dispatch
    handles the boundaries.
    """
    from hyper_parallel.codegen.emit.parallel import (
        lower_forward_boundaries,
        lower_forward_boundaries_toggle,
    )

    boundary_classes = getattr(meta, "boundary_classes", None) or {}
    if not boundary_classes:
        return text
    if os.environ.get("HP_CODEGEN_TOGGLE_FORWARD"):
        return lower_forward_boundaries_toggle(
            text,
            meta,
            boundary_classes=boundary_classes,
            module_name=module_name,
317
318
319
320
321
322
323
324
325
            meta,
            boundary_classes=boundary_classes,
            module_name=module_name,
        )
    return lower_forward_boundaries(
        text,
        meta,
        boundary_classes=boundary_classes,
        module_name=module_name,
327
328
329
330
331
332
333
334
335
336
337


def _banner(meta: Any) -> str:
    """Render the provenance banner comment."""
    source = meta.source if isinstance(meta.source, dict) else _source_dict(meta.source)
    pd = meta.parallel_dims or {}
    lines = [
        "# Generated by codegen. DO NOT EDIT MANUALLY.",
        "#",
        f"#   config       : {getattr(meta, 'yaml_path', '')}",
        f"#   signature    : {getattr(meta, 'signature', '')}",
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
        "# the runtime shards from it.  Regenerate to change.",
        "#",
        "",
    ]
    return "\n".join(lines)


def _source_dict(source: Any) -> dict[str, Any]:
    if source is None:
        return {}
    if isinstance(source, dict):
        return source
    return {
        "module_name": getattr(source, "module_name", ""),
        "file_path": getattr(source, "file_path", ""),
        "architecture": getattr(source, "architecture", ""),
        "sha256": getattr(source, "sha256", ""),
hyper_parallel/codegen/emit/parallel.py
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
The lowerer is a pure function over the source text + the frozen plan.  It
returns the patched text.  Structural work (finding a class / its forward /
the import block) is delegated to :mod:`astkit`.
"""
from __future__ import annotations

import json
import logging
from typing import Any, Optional

from hyper_parallel.codegen.astkit.edits import (
    TextEdit,
    apply_edits,
    insert_after_imports,
    replace_function_body,
73
74
75
76
77
78
79
80
81
    apply_edits,
    insert_after_imports,
    replace_function_body,
)
from hyper_parallel.codegen.astkit.index import (
    FunctionInfo,
    SourceIndex,
    build_source_index,
    returns_any_value,
80
81
82
83
84
85
86
87
88
    build_source_index,
    returns_any_value,
    returns_single_value,
)
from hyper_parallel.codegen.plan.boundary_forms import (
    FORM_GENERIC,
    FORM_IDENTITY,
    FORM_REGION,
    FORM_TP_COLLECTIVE,
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
    TransitionOp,
    classify_boundary_form,
    is_boundary_entry,
)
from hyper_parallel.codegen.plan.freeze import parse_placement

#: Injection keys that describe how a boundary's compute is regioned.
_INJECTION_KEYS = ("inner_wrapper", "inner_target", "inner_out_src", "local_compute_fn")


def lower_forward_boundaries(
    source_text: str,
    frozen_plan: Any,
    *,
    boundary_classes: Optional[dict[str, str]] = None,
120
121
122
123
124
125
126
127
128
129
130
    ``forward`` bodies; the rewritten bodies reference the ``_hp_boundary``
    / ``_hp_tp`` instance attributes that ``hp_install_boundaries`` binds
    at runtime — no per-boundary global constant is emitted.
    """
    edits: list[TextEdit] = []
    needs_runtime_helper_import = False
    for class_name, form, emitted, func, injection, deferred_bias in iter_emitted_forms(
        source_text,
        frozen_plan,
        boundary_classes=boundary_classes,
        module_name=module_name,
128
129
130
131
132
133
134
135
136
137
138
139
140
        frozen_plan,
        boundary_classes=boundary_classes,
        module_name=module_name,
    ):
        if emitted == FORM_IDENTITY:
            # Pruned: the runtime's install path covers the exact no-op /
            # to-local semantics with its generic wrapper, so leaving the
            # original forward untouched is behavior-preserving.
            if deferred_bias:
                # D-22: an unrewritten forward never calls the exit hook, so
                # the suppressed bias would silently vanish — fail fast
                # instead (the planner defers only rowwise-bias exits that
                # reduce, which classify as non-identity, so this is a
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
                # the suppressed bias would silently vanish — fail fast
                # instead (the planner defers only rowwise-bias exits that
                # reduce, which classify as non-identity, so this is a
                # mis-declared plan, not a supported shape).
                raise NotImplementedError(
                    f"lower_forward_boundaries: boundary class {class_name!r} "
                    "prunes to identity but declares deferred bias params; an "
                    "identity forward has no exit hook to re-add them"
                )
            continue
        # Every emitted forward body (generic, local-region) ends with the
        # runtime helper ``hp_to_local_if_dtensor``; that helper is a
        # dependency of the templates, not of any declaration, so the lowerer
        # must import it itself — otherwise the artifact is not self-contained.
        needs_runtime_helper_import = True
        body = _build_forward_body(
            source_text, injection, func, form, emitted, deferred_bias
        )
        # Keep the original forward as ``_forward_impl``. The
        # extracted method is inserted BEFORE the ``def forward`` (zero-width
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
        # edit at ``def_offset``), and the rewritten body replaces the original
        # body at ``body_start`` — the two spans never overlap, so the
        # incremental-shift validation in ``_apply_edits`` passes.  The static
        # form additionally prefixes the class marker naming the form.
        marker = _TP_FORM_MARKER_LINE if emitted == FORM_TP_COLLECTIVE else None
        impl = _build_forward_impl_edit(source_text, func, prefix=marker)
        if impl is not None:
            edits.append(impl)
        edits.extend(replace_function_body(source_text, func, body, indent=""))
    if not edits:
        return source_text

    nested = _validate_no_circular(edits)
    if nested:
        raise ValueError(
            "lower_forward_boundaries: edits overlap (circular class nesting); "
            "cannot safely rewrite: " + ", ".join(nested)
        )
    patched = apply_edits(source_text, edits)
    if needs_runtime_helper_import:
        patched = _add_runtime_helper_import(patched)
    return patched


#: Runtime helper the emitted forward templates call. The templates (and
#: this constant) must stay in sync — see ``_render_local_compute`` and
181
182
183
184
185
186
187
188
189
190
191
192
193
194

#: Runtime helper the emitted forward templates call. The templates (and
#: this constant) must stay in sync — see ``_render_local_compute`` and
#: ``_render_output_redistribute``.
_RUNTIME_HELPER_IMPORT = (
    "from hyper_parallel.codegen.runtime import hp_to_local_if_dtensor"
)


def _add_runtime_helper_import(source_text: str) -> str:
    """Ensure the forward templates' runtime helper is imported.

    The helper is a template dependency, so nothing in the declarations
    contributes it; without this the generated artifact raises ``NameError`` at
193
194
195
196
197
198
199
200
201
202
203
204
    The helper is a template dependency, so nothing in the declarations
    contributes it; without this the generated artifact raises ``NameError`` at
    the first forward call. No-op when the source already imports it.
    """
    if "import hp_to_local_if_dtensor" in source_text:
        return source_text
    index = build_source_index(source_text)
    edit = insert_after_imports(
        source_text,
        index.import_end,
        _RUNTIME_HELPER_IMPORT,
        default_offset=index.docstring_end or 0,
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
        index.import_end,
        _RUNTIME_HELPER_IMPORT,
        default_offset=index.docstring_end or 0,
    )
    return apply_edits(source_text, [edit]) if edit is not None else source_text


#: Class attribute a ``tp_collective`` forward carries, naming its form so the
#: runtime install path and preflight can recognize the static template.
TP_FORM_ATTRIBUTE = "_hp_boundary_form"
#: The marker value naming the statically lowered TP-collective form.
TP_FORM_MARKER = "tp_collective"
#: The emitted marker statement (method indent, followed by a blank line).
_TP_FORM_MARKER_LINE = f'    {TP_FORM_ATTRIBUTE} = "{TP_FORM_MARKER}"\n\n'


def iter_emitted_forms(
    source_text: str,
    frozen_plan: Any,
    *,
    boundary_classes: Optional[dict[str, str]] = None,
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
    than the in-memory freeze did.  Sorting here makes the emitted byte
    stream a pure function of which classes are boundaries, independent of
    how ``param_plan`` reached us.
    """
    index = build_source_index(source_text)
    param_plan = _plan_field(frozen_plan, "param_plan") or {}
    injections = _plan_field(frozen_plan, "injections") or []
    injection_by_class = _index_injections_by_class(injections, boundary_classes, index)
    mesh_dim_names = tuple(_plan_field(frozen_plan, "mesh_dim_names") or ())

    groups = _group_boundaries_by_class(
        param_plan, boundary_classes, index, module_name=module_name
    )
    for class_name in sorted(groups):
        group = groups[class_name]
        # Fail fast when one class's FQNs disagree on the contract: one shared
        # ``forward`` cannot carry two boundary plans.  The canonical entry is
        # the per-class contract every FQN agreed on.
        canonical_entry = _verify_class_contract(group, class_name)
        func = _require_forward(index, class_name)
        injection = injection_by_class.get(class_name)
        form = classify_boundary_form(canonical_entry, mesh_dim_names, injection)
        emitted = resolve_emitted_form(source_text, func, form)
        deferred_bias = bool(canonical_entry.get("deferred_bias_params"))
        yield class_name, form, emitted, func, injection, deferred_bias


def resolve_emitted_form(
    source_text: str,
    func: FunctionInfo,
    form: BoundaryForm,
) -> str:
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
    renders as ``generic`` instead.  This resolver is the single decision
    shared by the emitter and preflight, so the emitted file and the train-time
    check can never disagree about which template a class got.
    """
    if form.form in (FORM_IDENTITY, FORM_REGION, FORM_GENERIC):
        return form.form
    if _static_gates_pass(source_text, func, form):
        return FORM_TP_COLLECTIVE
    return FORM_GENERIC


def _static_gates_pass(source_text: str, func: FunctionInfo, form: BoundaryForm) -> bool:
    """Whether the static bare-operator template is admissible for ``func``.

    Gates (each mirrors a concrete way the generic engine is more general
    than a static rewrite):
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
    4. Every non-identity output op targets the first declared output — with a
       single-value return the compiled plan applies index-0 ops only; an op
       mapped to a later output position cannot be expressed statically.
    """
    if func.vararg_name is not None:
        return False
    if not returns_any_value(source_text, func):
        return False
    signature_names = set(func.param_names) | set(func.kwonly_names)
    if not set(form.in_declared) <= signature_names:
        return False
    out_names = form.out_names
    for name in form.out_ops:
        # An op name missing from the declared order compiles to index 0
        # (``_compile_output_plan``'s ``name_to_idx.get(name, 0)``), which the
        # single-value template covers; a later position does not.
        if name in out_names and out_names.index(name) != 0:
            return False
    return True


# ---------------------------------------------------------------------------
# grouping / consistency
332
333
334
335
336
337
338
339
340
# ---------------------------------------------------------------------------
# grouping / consistency
# ---------------------------------------------------------------------------

def _group_boundaries_by_class(
    param_plan: dict[str, Any],
    boundary_classes: Optional[dict[str, str]],
    index: SourceIndex,
    *,
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390

    An FQN whose class CANNOT be resolved fails fast — a boundary whose forward
    is never rewritten would silently drop its redistribution.
    """
    logger = logging.getLogger(__name__)
    groups: dict[str, list[tuple[str, dict[str, Any]]]] = {}
    for fqn, entry in param_plan.items():
        if not is_boundary_entry(entry):
            continue
        if entry.get("skip_source_lowering"):
            logger.info(
                "lower_forward_boundaries: boundary %s is marked "
                "skip_source_lowering (class shared with a non-boundary "
                "instance); the per-instance runtime wrapper will be "
                "installed instead",
389
390
391
392
393
394
395
396
397
398
399
400
                "instance); the per-instance runtime wrapper will be "
                "installed instead",
                fqn,
            )
            continue
        class_name = _class_for_fqn(fqn, boundary_classes, index, module_name)
        if class_name is None:
            raise ValueError(
                f"lower_forward_boundaries: cannot resolve the class for boundary "
                f"{fqn!r} — the frozen plan has no class mapping and the source "
                "index does not contain a class whose name matches; a boundary "
                "whose forward is not rewritten would silently drop its "
399
400
401
402
403
404
405
406
407
408
409
410
411
412
                "index does not contain a class whose name matches; a boundary "
                "whose forward is not rewritten would silently drop its "
                "redistribution"
            )
        if index.find_class(class_name) is None:
            # Imported class (Embedding/Linear/LayerNorm): no source ``forward``
            # to rewrite; runtime ``hp_install_boundaries`` installs the
            # in/out boundary wrapper. Not added to ``groups``.
            continue
        if index.find_forward(class_name) is None:
            # In-source class with an inherited forward (an attention
            # subclassing an imported base): no class-body ``def forward``
            # span to rewrite, the same shape an imported class has.  Live
            # instances carry no ``_forward_impl``, so hp_install_boundaries
411
412
413
414
415
416
417
418
419
            # span to rewrite, the same shape an imported class has.  Live
            # instances carry no ``_forward_impl``, so hp_install_boundaries
            # takes its imported-class branch and installs the per-instance
            # entry/exit wrapper.
            logger.info(
                "lower_forward_boundaries: boundary %s is a %s instance whose "
                "forward is inherited (no class-body ``def forward``); the "
                "per-instance runtime wrapper will be installed instead",
                fqn,
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
                "per-instance runtime wrapper will be installed instead",
                fqn,
                class_name,
            )
            continue
        groups.setdefault(class_name, []).append((fqn, entry))
    for class_name, items in groups.items():
        items.sort(key=lambda item: item[0])
    return groups


def _class_for_fqn(
    fqn: str,
    boundary_classes: Optional[dict[str, str]],
    index: SourceIndex,
    module_name: str,
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
       prefix (``module_name``) — remote-code modeling files carry the bare
       class name, and the FQN leaf is usually it;
    4. ``None`` (caller fails fast).
    """
    if boundary_classes:
        cls = boundary_classes.get(fqn)
        if cls:
            return cls
    leaf = fqn.rsplit(".", 1)[-1]
    if leaf in index.classes:
        return leaf
    # A remote-code file may name the class without the module-name prefix on
    # the FQN leaf; only accept it when the leaf matches a class AND the leaf
    # is a plausible modeling class (no lambdas / generics).
    if module_name:
        candidate = module_name.rsplit(".", 1)[-1]
        if candidate in index.classes and _name_matches(candidate, leaf):
            return candidate
    return None


def _name_matches(class_name: str, leaf: str) -> bool:
    """Loose match between a module-name class and an FQN leaf.

    The FQN leaf is the module attribute (``self_attn`` / ``mlp``); the class
    is its type (``Qwen3MoeAttention``).  They usually differ, so this only
466
467
468
469
470
471
472
473
474
475
476
    is its type (``Qwen3MoeAttention``).  They usually differ, so this only
    accepts the trivial identity.  The strong mapping is ``boundary_classes``;
    this fallback is deliberately conservative.
    """
    return class_name == leaf


def _verify_class_contract(
    group: list[tuple[str, dict[str, Any]]], class_name: str
) -> dict[str, Any]:
    """Fail fast unless every FQN of ``class_name`` carries the same contract.
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
    class's boundaries must be describable by the SAME frozen entry (modulo the
    ``params`` tree, which is instance-specific and drives the native Phase A
    parameter sharding, not the forward rewrite).  Returns the canonical entry.
    """
    canonical: Optional[str] = None
    canonical_entry: dict[str, Any] = {}
    for fqn, entry in group:
        entry = _without_params(entry)
        blob = json.dumps(entry, sort_keys=True)
        if canonical is None:
            canonical = blob
            canonical_entry = entry
        elif blob != canonical:
            raise ValueError(
                f"lower_forward_boundaries: class {class_name!r} has FQNs with "
                f"differing boundary contracts ({fqn!r}); a shared forward "
                "cannot carry two contracts — split the plan or align the "
                "per-rule contracts"
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
                f"differing boundary contracts ({fqn!r}); a shared forward "
                "cannot carry two contracts — split the plan or align the "
                "per-rule contracts"
            )
    return canonical_entry


def _without_params(entry: dict[str, Any]) -> dict[str, Any]:
    """Drop the instance-specific ``params`` tree from a contract comparison."""
    if "params" not in entry:
        return entry
    return {k: v for k, v in entry.items() if k != "params"}


def _index_injections_by_class(
    injections: list[dict[str, Any]],
    boundary_classes: Optional[dict[str, str]],
    index: SourceIndex,
) -> dict[str, dict[str, Any]]:
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
    whose ``match`` lands on a FQN of that class.  A class with conflicting
    values for one key (two FQNs declared different local compute fns) fails
    fast, for the same reason as ``_verify_class_contract``.
    """
    by_class: dict[str, dict[str, Any]] = {}
    for rule in injections:
        match = rule.get("match")
        if not isinstance(match, str) or not match:
            continue
        class_name = _class_for_fqn(match, boundary_classes, index, "")
        if class_name is None:
            continue
        payload = {k: rule[k] for k in _INJECTION_KEYS if k in rule}
        if not payload:
            continue
        existing = by_class.setdefault(class_name, {})
        for key, value in payload.items():
            if key in existing and _json_stable(existing[key]) != _json_stable(value):
                raise ValueError(
                    f"lower_forward_boundaries: class {class_name!r} has "
                    f"conflicting injection values for {key!r}"
                )
            existing[key] = value
    return by_class


def _json_stable(value: Any) -> str:
    return json.dumps(value, sort_keys=True, default=str)


# ---------------------------------------------------------------------------
# toggle / switch forward template (parallel, non-default generation mode)
549
550
551
552
553
554
555
556
557
558
559
560
561
562
#: Class marker value naming the switch-based (toggle) template.  Distinct from
#: ``TP_FORM_MARKER`` ("tp_collective") so the runtime install path and preflight
#: can tell the two opt-in templates apart.  Like the static marker it names the
#: template a boundary class actually got.
TOGGLE_FORM_MARKER = "toggle"
#: The emitted marker statement (method indent, then a blank line).
_TOGGLE_FORM_MARKER_LINE = f'    {TP_FORM_ATTRIBUTE} = "{TOGGLE_FORM_MARKER}"\n\n'


def lower_forward_boundaries_toggle(
    source_text: str,
    frozen_plan: Any,
    *,
    boundary_classes: Optional[dict[str, str]] = None,
585
586
587
588
589
590
591
592
593
594
    frozen entry's full per-axis placement dicts), independently of the merged
    ``tp_collective`` classification, because the switch template needs one
    guarded segment per *axis* rather than one merged verdict over all axes.
    """
    edits: list[TextEdit] = []
    for class_name, _, emitted, func, injection, deferred_bias in iter_emitted_forms(
        source_text,
        frozen_plan,
        boundary_classes=boundary_classes,
        module_name=module_name,
592
593
594
595
596
597
598
599
600
601
602
603
604
605
        frozen_plan,
        boundary_classes=boundary_classes,
        module_name=module_name,
    ):
        del injection
        if emitted == FORM_IDENTITY:
            # Pruned as in the parallel mode: identity boundaries need no rewrite.
            continue
        if emitted == FORM_REGION:
            raise NotImplementedError(
                "toggle forward: the region (EP MoE local) form is not emitted "
                f"for {_class_for_fqn(class_name, boundary_classes, build_source_index(source_text), module_name)!r}; "
                "the toggle template assumes a dense (non-MoE) boundary. Split the "
                "plan or keep the default parallel mode for MoE topologies."
603
604
605
606
607
608
609
610
611
                f"for {_class_for_fqn(class_name, boundary_classes, build_source_index(source_text), module_name)!r}; "
                "the toggle template assumes a dense (non-MoE) boundary. Split the "
                "plan or keep the default parallel mode for MoE topologies."
            )
        if deferred_bias:
            # D-22: the toggle template decomposes the exit into per-axis
            # guarded segments and has no single boundary-exit point to hang
            # the deferred-bias re-add on; emitting nothing would silently
            # drop the suppressed bias. Fail fast and point at the mode that
609
610
611
612
613
614
615
616
617
            # guarded segments and has no single boundary-exit point to hang
            # the deferred-bias re-add on; emitting nothing would silently
            # drop the suppressed bias. Fail fast and point at the mode that
            # supports deferral.
            raise NotImplementedError(
                f"toggle forward: boundary class {class_name!r} declares "
                "deferred bias params (D-22); the toggle template has no "
                "boundary exit hook to re-add them — use the default "
                "parallel mode for plans that defer rowwise biases"
615
616
617
618
619
620
621
622
623
                "deferred bias params (D-22); the toggle template has no "
                "boundary exit hook to re-add them — use the default "
                "parallel mode for plans that defer rowwise biases"
            )
        body = _render_toggle_forward(
            source_text,
            frozen_plan,
            class_name,
            func,
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
            func,
            boundary_classes=boundary_classes,
            module_name=module_name,
        )
        impl = _build_forward_impl_edit(source_text, func, prefix=_TOGGLE_FORM_MARKER_LINE)
        if impl is not None:
            edits.append(impl)
        edits.extend(replace_function_body(source_text, func, body, indent=""))
    if not edits:
        return source_text

    nested = _validate_no_circular(edits)
    if nested:
        raise ValueError(
            "lower_forward_boundaries_toggle: edits overlap (circular class "
            "nesting); cannot safely rewrite: " + ", ".join(nested)
        )
    return apply_edits(source_text, edits)


def _render_toggle_forward(
    source_text: str,
    frozen_plan: Any,
    class_name: str,
    func: FunctionInfo,
656
657
658
659
660
661
662
663
664
665
666
667
668
669
    ``self._hp_tp``; the ``tp_enable`` / ``cp_enable`` / ``ep_enable``
    booleans are bound on the instance by the install path and defer to
    ``mesh_context``.
    """
    inbound, outbound = _per_axis_toggle_ops(
        frozen_plan, class_name, source_text,
        boundary_classes=boundary_classes, module_name=module_name,
    )

    lines = [
        "        # [HYPER TOGGLE] keep the original HF forward as ``_forward_impl``",
        "        # and insert per-dimension slice / collective / merge segments,",
        "        # each guarded by its own {{tp|cp|ep}}_enable switch.",
        f"        # [HYPER INLINE] {_fusion_note_for(class_name)}",
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
        "        # each guarded by its own {{tp|cp|ep}}_enable switch.",
        f"        # [HYPER INLINE] {_fusion_note_for(class_name)}",
        "",
    ]
    for axis in ("tp", "cp", "ep"):
        axis_ops = inbound.get(axis)
        if not axis_ops:
            continue
        lines.append(f"        if self.{axis}_enable:")
        for name, op in sorted(axis_ops.items()):
            expr = _render_tp_call_expr(name, op)
            lines.append(f"            {name} = {expr}")
        lines.append("")
    lines.append(
        "        # [HYPER TOGGLE] original forward body (now ``_forward_impl``)"
    )
    lines.append(_render_forward_call(func))
    lines.append("")
    for axis in reversed(("ep", "cp", "tp")):
        axis_ops = outbound.get(axis)
        if not axis_ops:
            continue
        lines.append(f"        if self.{axis}_enable:")
        for name, op in sorted(axis_ops.items()):
            if op.kind == "all_reduce":
                lines.append("            outputs = self._hp_tp.all_reduce(outputs)")
            elif op.kind == "all_gather":
                lines.append(
                    f"            outputs = self._hp_tp.all_gather(outputs, dim={op.tensor_dim})"
                )
            else:
                lines.append(
                    f"            outputs = self._hp_tp.reduce_scatter(outputs, dim={op.tensor_dim})"
                )
        lines.append("")
    lines.append("        return outputs")
    return "\n".join(lines)


def _per_axis_toggle_ops(
    frozen_plan: Any,
    class_name: str,
    source_text: str,
    *,
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
    axes independently so a boundary can guard ``tp`` and ``cp`` work in separate
    blocks.  A class's forward is shared by every instance, so only the first
    boundary FQN that resolves to ``class_name`` is decomposed.
    """
    param_plan = _plan_field(frozen_plan, "param_plan") or {}
    axes = tuple(_plan_field(frozen_plan, "mesh_dim_names") or ())
    if not axes or not param_plan:
        return {}, {}
    index = build_source_index(source_text)
    for fqn, entry in param_plan.items():
        if not is_boundary_entry(entry):
            continue
        if _class_for_fqn(fqn, boundary_classes, index, module_name) != class_name:
            continue
        inbound = _per_axis_side(entry, "in_src", "in_dst", axes)
        outbound = _per_axis_side(entry, "out_src", "out_dst", axes)
        return inbound, outbound
    return {}, {}


def _per_axis_side(
    entry: dict[str, Any], src_field: str, dst_field: str, axes: tuple[str, ...]
) -> dict[str, dict[str, TransitionOp]]:
    """Per-axis non-identity ops for one side, from the frozen placement strings.
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
    ``torch_npu`` stub in isolated runs).  So a boundary can carry an independent
    ``tp`` and ``cp`` segment.  ``ep`` changes (MoE expert routing) are left for
    the caller to reject via the region/dense guard.
    """
    src_raw = entry.get(src_field) or {}
    dst_raw = entry.get(dst_field) or {}
    result: dict[str, dict[str, TransitionOp]] = {}
    for axis in axes:
        axis_ops: dict[str, TransitionOp] = {}
        for name in sorted(set(src_raw) | set(dst_raw)):
            src_pl = src_raw.get(name) or {}
            dst_pl = dst_raw.get(name) or {}
            src = src_pl.get(axis, "R")
            dst = dst_pl.get(axis, "R")
            if not isinstance(src, str) or not isinstance(dst, str) or src == dst:
                continue
            op = _toggle_placement_op(src, dst)
            if op is not None:
                axis_ops[name] = op
        if axis_ops:
            result[axis] = axis_ops
    return result


def _toggle_placement_op(src: str, dst: str) -> Optional[TransitionOp]:
    """One per-axis transition op from a valid frozen placement pair, or None.

    Delegates the placement semantics to native
    ``classify_tp_transition`` (the single source of truth the boundary-form
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
    native path cannot lower (non-``sum`` partial, unsupported pair) or an
    identity yields ``None`` (left to the boundary's compiled-plan/DTensor
    fallback).
    """
    from hyper_parallel.distributed._builder.tp_collective_lowering import (  # pylint: disable=C0415
        classify_tp_transition,
    )

    result = classify_tp_transition(
        [parse_placement(src)], [parse_placement(dst)], ("tp",)
    )
    if result is None or result["kind"] == "identity":
        return None
    return TransitionOp(kind=result["kind"], tensor_dim=result.get("tensor_dim"))


def _fusion_note_for(class_name: str) -> str:
    """One-line replacement note placed at the top of a switch forward."""
    return f"fused module {class_name}: the switch template keeps the original HF " \
           "forward as its backbone and only adds dimension-guarded segments."


# ---------------------------------------------------------------------------
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
# ---------------------------------------------------------------------------
# forward body construction
# ---------------------------------------------------------------------------

def _require_forward(index: SourceIndex, class_name: str) -> FunctionInfo:
    """Return the ``forward`` ``FunctionInfo`` for a boundary class or raise."""
    func = index.find_forward(class_name)
    if func is None:
        raise ValueError(
            f"lower_forward_boundaries: class {class_name!r} has no forward "
            "method; a boundary forward must exist to be rewritten"
        )
    if func.body_start is None or func.body_end is None:
        raise ValueError(
            f"lower_forward_boundaries: class {class_name!r}.forward has no "
            "replaceable body (single-line/pass); cannot sink a boundary"
        )
    return func


def _build_forward_impl_edit(
    source_text: str,
    func: FunctionInfo,
    prefix: Optional[str] = None,
) -> Optional[TextEdit]:
838
839
840
841
842
843
844
845
846
847
    pass) — there is nothing meaningful to extract, and ``forward`` itself was
    already rejected by ``_require_forward`` for the same reason, so this is a
    defensive fallback.
    """
    if func.body_start is None or func.body_end is None:
        return None
    # The extracted method owns the signature + body; DECORATORS stay attached to
    # the original ``forward`` (which is being rewritten) — copying them onto
    # ``_forward_impl`` too would duplicate a side effect (e.g.
    # ``@torch.no_grad()``) on a method that must be pure.  ``func.def_offset``
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
    # scan forward from it past decorator lines and blanks to the ``def`` keyword
    # — this finds the signature line for both the plain and the decorated case
    # without offset arithmetic (a naive backward scan from ``body_start`` finds
    # the newline that *ends* the ``def`` line, landing on the first body line).
    def_line_start = func.def_offset
    for line in source_text[def_line_start:func.body_start].splitlines(keepends=True):
        stripped = line.lstrip()
        if stripped.startswith("def "):
            break
        def_line_start += len(line)
    span = source_text[def_line_start:func.body_end]
    lines = span.splitlines(keepends=True)
    if not lines:
        return None
    # ``_forward_impl`` is a VERBATIM copy of the original method (signature +
    # body, byte-for-byte, keeping the method indent on the ``def`` and the body
    # nesting) with only the name changed -- so nothing here needs dedenting or
    # re-indenting, and the copy can never drift from the original source.
    for i, line in enumerate(lines):
        stripped = line.lstrip()
        if stripped.startswith(f"def {func.name}("):
            lines[i] = line.replace(f"def {func.name}(", "def _forward_impl(", 1)
            break
    impl = "".join(lines)
    # The copy ends at ``body_end``, which is clamped to EOF for a final
    # statement with no trailing newline — so the extracted method can lose its
    # terminating newline and land joined onto the ``def forward`` that follows.
    # A method must be separated from the next one by a newline, so pad.
    if not impl.endswith("\n"):
        impl += "\n"
    if prefix:
        impl = prefix + impl
    return TextEdit(func.def_offset, func.def_offset, impl)


def _build_forward_body(
    source_text: str,
    injection: Optional[dict[str, Any]],
    func: FunctionInfo,
    form: BoundaryForm,
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
    ``self._hp_deferred_bias(outputs)`` after the output redistribution —
    the runtime binds that hook when it installs the native
    suppression/restore pair.
    """
    if emitted == FORM_TP_COLLECTIVE:
        return _render_tp_collective_forward(source_text, func, form, deferred_bias)

    input_redist = _render_input_redistribute(func)
    out_redist = _render_output_redistribute(deferred_bias)

    local = injection.get("local_compute_fn") if injection else None
    if local is not None:
        return _render_local_compute(local, input_redist, out_redist, form)
    # A generic boundary (including an inner-wrapper one — the CP wrapper is
    # installed at runtime by ``hp_apply_inner_wrapper`` and weaves into
    # this forward) redistributes through the compiled plan, annotated with
    # what each side's transitions lower to.
    return _render_plain_redistribute(input_redist, out_redist, form)


def _render_input_redistribute(func: FunctionInfo) -> str:
    """Render the input-side redistribute call on the compiled boundary plan.

    The generated forward re-binds args/kwargs against its own signature, so a
    positional ``(args, kwargs)`` pair reaches the compiled plan as the
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
    unchanged, so ``self._forward_impl(*args, **kwargs)`` receives every
    argument class the original call site used — a keyword-only ``required``
    or an HF ``**kwargs`` pass-through no longer drops out.
    """
    args = list(func.param_names)
    if func.vararg_name:
        args.append(f"*{func.vararg_name}")
    kwargs_parts = [f'"{name}": {name}' for name in func.kwonly_names]
    if func.kwarg_name:
        kwargs_parts.append(f"**{func.kwarg_name}")
    kwargs_literal = "{" + ", ".join(kwargs_parts) + "}" if kwargs_parts else "{}"
    # The args body is spliced into a tuple literal; a single element needs a
    # trailing comma (also for a lone ``*vararg``) so it stays a tuple rather
    # than parenthesized scalars / a generator expression.
    args_body = ", ".join(args) + ("," if len(args) == 1 else "")
    return (
        "        args, kwargs = self._hp_boundary.redistribute_inputs(\n"
        f"            (({args_body}), {kwargs_literal}),\n"
        "        )"
    )
955
956
957
958
959
960
961
962
963
        "        )"
    )


def _render_output_redistribute(deferred_bias: bool = False) -> str:
    """Render the output-side boundary exit (no signature to bind).

    Mirrors the native ``_wrap_local_region_forward`` exit order exactly:
    Step-3 re-wrap first (local -> DTensor per ``out_src``), then the Step-4
 982
 983
 984
 985
 986
 987
 988
 989
 990
 991
 992
 993
 994
 995
 996
 997
 998
 999
1000
1001
1002
1003
1004
    bound at install time (``module._hp_deferred_bias``); the emitted call
    is conditional on the plan declaring deferred params, so the two sides
    cannot disagree about whether a boundary defers.
    """
    lines = [
        "        outputs = self._hp_boundary.rewrap_outputs(outputs)",
        "        outputs = self._hp_boundary.redistribute_outputs(outputs)",
        "        outputs = hp_to_local_if_dtensor(outputs)",
    ]
    if deferred_bias:
        lines.append("        outputs = self._hp_deferred_bias(outputs)")
    return "\n".join(lines)


def _ordered_notes(notes: dict[str, str]) -> list[str]:
    """Declarative annotation lines for one side, in deterministic name order."""
    return [f"        # {notes[name]}" for name in sorted(notes)]


def _render_plain_redistribute(
    input_redist: str, out_redist: str, form: BoundaryForm
) -> str:
    """Generic boundary: annotated redistribute around the original forward.
1008
1009
1010
1011
1012
1013
1014
1015
1016
    ``==> dtensor redistribute`` for transitions the compiled plan's DTensor
    fallback owns), so a reader sees what the boundary communicates without
    re-deriving the plan.
    """
    return "\n".join([
        "        # [HYPER BOUNDARY] input redistribution",
        *_ordered_notes(form.in_notes),
        input_redist,
        "",
1028
1029
1030
1031
1032
1033
1034
1035
1036
# ---------------------------------------------------------------------------
# static tp_collective template
# ---------------------------------------------------------------------------

def _render_tp_collective_forward(
    source_text: str,
    func: FunctionInfo,
    form: BoundaryForm,
    deferred_bias: bool = False,
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
    after the output ops — the Partial(sum) -> R all_reduce above is exactly
    the exit reduction the deferral waits for, so the bias re-add lands once,
    on the reduced output.
    """
    lines = [
        "        # [HYPER TP-COLLECTIVE] statically lowered boundary — the runtime",
        "        # re-validates this form at install time and replaces the forward",
        "        # with the generic engine on any mismatch.",
    ]
    for name in form.in_declared:
        op = form.in_ops.get(name)
        if op is None:
            note = form.in_notes.get(name) or f"{name}: identity ==> to_local"
            lines.append(f"        # {note}")
            lines.append(f"        {name} = self._hp_tp.to_local({name})")
        else:
            lines.append(f"        # {form.in_notes[name]}")
            lines.append(_render_tp_call(name, op))
    lines.append("")
    lines.append(
        "        # [HYPER TP-COLLECTIVE] original forward body (now ``_forward_impl``)"
    )
    lines.append(_render_forward_call(func))
    tuple_output = not returns_single_value(source_text, func)
    for name, op in form.out_ops.items():
        lines.append(f"        # {form.out_notes[name]}")
        lines.extend(_render_output_tp_call(name, op, form, tuple_output))
    if deferred_bias:
        lines.append("")
        lines.append(
            "        # [HYPER D-22] deferred bias re-added once, after the exit reduction"
        )
        lines.append("        outputs = self._hp_deferred_bias(outputs)")
    lines.append("")
    lines.append("        return outputs")
    return "\n".join(lines)


def _render_tp_call_expr(target: str, op: TransitionOp) -> str:
    """One bare-operator call expression (``self._hp_tp.<kind>(...)``)."""
    if op.kind == "all_reduce":
        return f"self._hp_tp.all_reduce({target})"
    return f"self._hp_tp.{op.kind}({target}, dim={op.tensor_dim})"


def _render_tp_call(name: str, op: TransitionOp) -> str:
    """One input-side bare-operator statement (rebinding the named input)."""
    return f"        {name} = {_render_tp_call_expr(name, op)}"


def _render_output_tp_call(
    name: str, op: TransitionOp, form: BoundaryForm, tuple_output: bool
) -> list[str]:
    """Output-side bare-operator statements.
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
    The common single-tensor case rewrites ``outputs`` directly.  For tuple
    outputs, the frozen plan names the first declared output as index 0;
    rebuild the original container after applying the collective to that item.
    """
    out_names = form.out_names
    if tuple_output and (name not in out_names or out_names.index(name) == 0):
        return [
            f"        _hp_output_0 = {_render_tp_call_expr('outputs[0]', op)}",
            "        outputs = (_hp_output_0, *outputs[1:])",
        ]
    return [f"        outputs = {_render_tp_call_expr('outputs', op)}"]


def _render_forward_call(func: FunctionInfo) -> str:
    """The ``self._forward_impl(...)`` call, every parameter re-passed by name.

    Positional parameters go positionally, keyword-only ones by keyword — the
    call is a pure re-pass of the static forward's own signature, so defaults
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
    Positional parameters go positionally, keyword-only ones by keyword — the
    call is a pure re-pass of the static forward's own signature, so defaults
    and call-site keyword usage both survive the rewrite.
    """
    args = list(func.param_names) + [f"{name}={name}" for name in func.kwonly_names]
    if func.kwarg_name:
        args.append(f"**{func.kwarg_name}")
    if not args:
        return "        outputs = self._forward_impl()"
    single = f"        outputs = self._forward_impl({', '.join(args)})"
    if len(single) <= 100:
        return single
    lines = ["        outputs = self._forward_impl("]
    lines.extend(f"            {arg}," for arg in args)
    lines.append("        )")
    return "\n".join(lines)


def _render_local_compute(
    local: Any, input_redist: str, out_redist: str, form: BoundaryForm
) -> str:
    """Local-region boundary (shape 2, EP MoE): compute fn on local tensors, then exit.
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
    annotate the redistribution steps exactly like the generic and
    tp-collective templates; identity transitions carry no note, so an
    identity in/out region emits no noise.
    """
    compute_name = _callable_name(local)
    return "\n".join([
        "        # [HYPER LOCAL REGION] boundary entry",
        *_ordered_notes(form.in_notes),
        input_redist,
        "        args = tuple(hp_to_local_if_dtensor(arg) for arg in args)",
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
        "        return outputs",
    ])


def _callable_name(value: Any) -> str:
    """Human name for an injection's callable (dict target / string / fn)."""
    if isinstance(value, dict):
        return value.get("name") or value.get("target") or repr(value)
    if isinstance(value, str):
        return value
    return getattr(value, "__name__", repr(value))


# ---------------------------------------------------------------------------
# field reads / validation
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
# ---------------------------------------------------------------------------
# field reads / validation
# ---------------------------------------------------------------------------

def _plan_field(plan: Any, name: str) -> Any:
    """Read a frozen-plan field from a dict or a ``CodegenMeta``."""
    if isinstance(plan, dict):
        return plan.get(name)
    return getattr(plan, name, None)


def _validate_no_circular(edits: list[TextEdit]) -> list[str]:
    """Detect edits authored against different texts (a soft sanity check).

    The lowerer computes all edits against one ``source_text``; the index is
    built once, so a second-pass author (an inner wrapper that itself carries a
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
    built once, so a second-pass author (an inner wrapper that itself carries a
    body rewrite) would need to re-index.  We fail fast rather than splice
    stale offsets.
    """
    problems: list[str] = []
    seen: dict[tuple[int, int], str] = {}
    for edit in edits:
        key = (edit.start, edit.end)
        if key in seen:
            problems.append(
                f"[{edit.start},{edit.end}) (was {seen[key]}, now {edit.replacement[:30]!r})"
            )
        else:
            seen[key] = edit.replacement[:30]
    return problems


__all__ = [
    "TP_FORM_ATTRIBUTE",
    "TP_FORM_MARKER",
    "TOGGLE_FORM_MARKER",
    "iter_emitted_forms",
hyper_parallel/codegen/emit/replacement.py
15
16
17
18
19
20
21
22
23
24
25
The inline pipeline lowers the surviving replacements into the generated
file's module bodies, and ``meta.covered["module_overrides"]`` tells the
trainer to skip its own replacement step.
"""
from __future__ import annotations

from typing import Any, Sequence


# ---------------------------------------------------------------------------
# generation-time compile (fail-fast)
24
25
26
27
28
29
30
31
32
# ---------------------------------------------------------------------------
# generation-time compile (fail-fast)
# ---------------------------------------------------------------------------

def compile_overrides_for_meta(
    meta_model: Any,
    specs: Sequence[Any],
    *,
    factory_paths: Sequence[str | None] = (),
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
    along in ``configs_by_spec`` (keyed by ``id(spec)``) so the runtime can
    re-pre-bind them onto the rebuilt factory instead of dropping to the
    factory's defaults (or failing a required arg).
    """
    if not specs:
        return ()
    if configs_by_spec is None:
        configs_by_spec = {}
    from hyper_parallel.models.replacement import compile_module_replacements

    paths_by_spec = {
        id(spec): path
        for spec, path in zip(specs, factory_paths)
        if path is not None
    }
84
85
86
87
88
89
90
91
92
93
94
95
96
97
        for spec, path in zip(specs, factory_paths)
        if path is not None
    }

    try:
        plan = compile_module_replacements(meta_model, specs)
    except ValueError as exc:
        raise ValueError(
            f"codegen: plan_overrides replace_module: {exc}"
        ) from exc
    return tuple(
        _record_for_target(target, paths_by_spec, configs_by_spec)
        for target in plan.targets
    )
 96
 97
 98
 99
100
101
102
103
104
        for target in plan.targets
    )


def _record_for_target(
    target: Any,
    paths_by_spec: dict[int, str],
    configs_by_spec: dict[int, Any] | None = None,
) -> dict[str, Any]:
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
    paths_by_spec: dict[int, str],
    configs_by_spec: dict[int, Any] | None = None,
) -> dict[str, Any]:
    """Serialize one replacement target into its meta record."""
    spec = target.spec
    factory_path = paths_by_spec.get(id(spec))
    if factory_path is None:
        raise TypeError(
            f"codegen: replacement factory for {spec.match!r} has no serializable "
            "import path — the gen backend needs a YAML Target-backed "
            "@module_replacement factory"
        )
    record = {
        "match": list(spec.match),
        "fqn": target.module_fqns[0],
        "fqns": list(target.module_fqns),
        "module_type": _type_path(spec.module_type),
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
        "module_type": _type_path(spec.module_type),
        "factory": factory_path,
        "exact_type": bool(spec.exact_type),
    }
    config = (configs_by_spec or {}).get(id(spec))
    if config:
        record["target_config"] = config
    return record


def _type_path(module_type: type) -> str:
    return f"{module_type.__module__}.{module_type.__qualname__}"


__all__ = ["compile_overrides_for_meta"]
hyper_parallel/codegen/hash.py
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32


def sha256_file(path: str) -> str:
    """Compute the sha256 of a file's bytes."""
    digest = hashlib.sha256()
    with open(path, "rb") as handle:
        for chunk in iter(lambda: handle.read(1 << 20), b""):
            digest.update(chunk)
    return digest.hexdigest()


def sha256_text(text: str) -> str:
    """Compute the sha256 of a UTF-8 text payload."""
    return hashlib.sha256(text.encode("utf-8")).hexdigest()


def canonical_json(data: object) -> str:
    """Stable JSON serialization (sorted keys, no whitespace).
32
33
34
35
36
37
38
39
40
    """Stable JSON serialization (sorted keys, no whitespace).

    Used for signature inputs — key order must not affect the digest.
    """
    return json.dumps(data, sort_keys=True, separators=(",", ":"), ensure_ascii=True)


def signature_from_spec(spec: object, *, prefix: int = 12) -> str:
    """Compute the artifact signature from a canonical spec, first ``prefix`` hex chars.
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55

    ``spec`` may be any JSON-serializable object (typically a
    :class:`CodegenSpec` ``to_dict()`` result or its canonical projection).
    """
    return sha256_text(canonical_json(spec))[:prefix]


def file_line_count(path: str) -> int:
    """Count lines in a text file."""
    with open(path, "r", encoding="utf-8") as handle:
        return sum(1 for _ in handle)


__all__ = [
    "canonical_json",
hyper_parallel/codegen/inline/attention.py
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
This generator is platform-agnostic ``core`` code: the module type and kernel
entry are handed in, never imported here.
"""

from __future__ import annotations

import ast
import builtins
import inspect
import symtable
import textwrap
from types import ModuleType
from typing import Any, Callable, Iterable

from hyper_parallel.codegen.inline.expansion import ExpandedSource, expand_function


#: The generated forward is the native GQAAttention.forward verbatim — its
#: ``self.attention_interface(...)`` call site is kept unmodified, and neither CP
55
56
57
58
59
60
61
62
63
#: ``hp_install_boundaries``) and the CP inner wrapper is installed by
#: ``hp_apply_inner_wrapper``.


_FUSION_REPLACEMENT_NOTE = (
    "# [HYPER INLINE] fused replacement for the original model's per-head\n"
    "# projections: q_proj/k_proj/v_proj are packed into a single linear_qkv\n"
    "# weight, whose checkpoint layout is converted via InterleaveQKV.\n"
)
62
63
64
65
66
67
68
69
70
    "# weight, whose checkpoint layout is converted via InterleaveQKV.\n"
)


def _fusion_replacement_note(class_name: str) -> str:
    """Annotate that the emitted fused module replaces the original projections.

    The artifact keeps the real fused class (``linear_qkv`` takes over the
    weight the original model spread across ``q_proj``/``k_proj``/``v_proj``),
70
71
72
73
74
75
76
77
78
79
80
81
    weight the original model spread across ``q_proj``/``k_proj``/``v_proj``),
    so a reader of the generated model file sees a genuine class rather than a
    black-box substitution, with a comment naming what the fusion swallowed.
    """
    return f"# [HYPER INLINE] {class_name}: {_FUSION_REPLACEMENT_NOTE}"


def _build_forward(module_type: type) -> ast.FunctionDef:
    """Derive forward from real source verbatim.

    The body is the real ``forward`` exactly as written (its native
    ``self.attention_interface(...)`` call site is left completely unmodified).
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
    redistributes around this forward, and the CP wrapper swaps
    ``attention_interface`` at install time — exactly as native's applier does
    at apply time.
    """
    source = textwrap.dedent(inspect.getsource(module_type.forward))
    tree = ast.parse(source)
    forward = tree.body[0]
    if not isinstance(forward, ast.FunctionDef) or forward.name != "forward":
        raise ValueError(f"codegen attention: {module_type.__name__}.forward is not a plain def")

    _strip_annotations(forward)
    ast.fix_missing_locations(forward)
    return forward


def _strip_annotations(statement: ast.FunctionDef) -> None:
    """Drop type hints so the artifact has no import-time resolution burden.

    Only annotations are cleared; ``*args`` and ``**kwargs`` parameters are kept
    because real forward bodies may reference them.
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130

    Only annotations are cleared; ``*args`` and ``**kwargs`` parameters are kept
    because real forward bodies may reference them.
    """
    statement.returns = None
    for arg in (*statement.args.posonlyargs, *statement.args.args, *statement.args.kwonlyargs):
        arg.annotation = None
    if statement.args.vararg is not None:
        statement.args.vararg.annotation = None
    if statement.args.kwarg is not None:
        statement.args.kwarg.annotation = None


def _drop_docstring(statement: ast.FunctionDef) -> None:
    """Drop a leading string-expression docstring from an emitted definition."""
    if not statement.body:
        return
    first = statement.body[0]
    if (
        isinstance(first, ast.Expr)
        and isinstance(first.value, ast.Constant)
        and isinstance(first.value.value, str)
    ):
        del statement.body[0]


def _referenced_global_names(source: str) -> set[str]:
    """Module-scope names ``source`` reads from every scope it defines.

    ``symtable`` resolves the scopes, so parameters, locals, loop variables,
    comprehension targets and attribute accesses (``x.y`` contributes only
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
    ``symtable`` resolves the scopes, so parameters, locals, loop variables,
    comprehension targets and attribute accesses (``x.y`` contributes only
    ``x``) never show up as global reads.
    """
    referenced: set[str] = set()
    pending = [symtable.symtable(source, "<codegen attention>", "exec")]
    while pending:
        table = pending.pop()
        referenced.update(
            symbol.get_name()
            for symbol in table.get_symbols()
            if symbol.is_global() and symbol.is_referenced()
        )
        pending.extend(table.get_children())
    return referenced


def _module_bound_names(source: str) -> set[str]:
    """Names the module-level statements of ``source`` bind."""
    table = symtable.symtable(source, "<codegen attention>", "exec")
    return {
        symbol.get_name()
        for symbol in table.get_symbols()
        if symbol.is_assigned() or symbol.is_imported() or symbol.is_namespace()
    }
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
        if symbol.is_assigned() or symbol.is_imported() or symbol.is_namespace()
    }


def _import_bound_names(lines: Iterable[str]) -> set[str]:
    """Names a sequence of ``import`` lines binds at module scope."""
    names: set[str] = set()
    for line in lines:
        for alias in ast.parse(line).body[0].names:
            names.add(alias.asname or alias.name.split(".")[0])
    return names


def _is_module_global(name: str) -> bool:
    """Whether ``name`` resolves in any module without an import of ours.

    Builtins do, and so do dunders such as ``__name__``.  A component definition
    of the same name wins over this: it is looked up first.
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197

    Builtins do, and so do dunders such as ``__name__``.  A component definition
    of the same name wins over this: it is looked up first.
    """
    return hasattr(builtins, name) or name.startswith("__")


def _owner_definitions(owner: ModuleType) -> dict[str, ast.stmt]:
    """Module-level definitions of the component's own module, by bound name."""
    definitions: dict[str, ast.stmt] = {}
    for node in ast.parse(inspect.getsource(owner)).body:
        if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef)):
            definitions[node.name] = node
            continue
        if isinstance(node, ast.Assign):
            targets: list[ast.expr] = list(node.targets)
        elif isinstance(node, ast.AnnAssign):
            targets = [node.target]
        else:
            continue
        for target in targets:
            if isinstance(target, ast.Name):
                definitions[target.id] = node
    return definitions


def _owner_import_lines(owner: ModuleType) -> dict[str, str]:
    """Import statement of the component's own module, per name it binds.

    The artifact must import what the component's *module* imports (a re-export
    such as ``WeightConverter`` only exists under the path that module uses), so
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
    such as ``WeightConverter`` only exists under the path that module uses), so
    each line is copied from that module's own source rather than reconstructed
    from the value's ``__module__``.
    """
    lines: dict[str, str] = {}
    for node in ast.parse(inspect.getsource(owner)).body:
        if isinstance(node, ast.Import):
            for alias in node.names:
                bound = alias.asname or alias.name.split(".")[0]
                line = f"import {alias.name}" + (f" as {alias.asname}" if alias.asname else "")
                lines.setdefault(bound, line)
        elif isinstance(node, ast.ImportFrom) and not node.level:
            for alias in node.names:
                bound = alias.asname or alias.name
                line = f"from {node.module} import {alias.name}" + (
                    f" as {alias.asname}" if alias.asname else ""
                )
                lines.setdefault(bound, line)
    return lines


def _definition_source(node: ast.stmt) -> str:
    """Copy one module-level definition, dropping hints and docstrings."""
    if isinstance(node, ast.FunctionDef):
        _strip_annotations(node)
        _drop_docstring(node)
    return ast.unparse(node)


def _component_dependencies(
    module_type: type, code: str, *, provided: Iterable[str]
) -> tuple[str, tuple[str, ...]]:
    """Inline and import every name ``code`` reads from the component's module.
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304

    Returns:
        The inlined definitions and the import lines the copied code needs.
    """
    owner = inspect.getmodule(module_type)
    if owner is None:
        raise ValueError(
            f"codegen attention: cannot resolve the source module of {module_type.__name__!r}"
        )
    definitions = _owner_definitions(owner)
    owner_imports = _owner_import_lines(owner)
    inlined: list[str] = []
    imports: list[str] = []
    resolved = set(provided)
    unresolved: list[str] = []

    def resolve(name: str) -> None:
        """Inline or import ``name``, or record it as unresolvable."""
        if name in resolved:
            return
        node = definitions.get(name)
        if node is not None:
            resolved.add(name)  # before recursing: a helper may reference itself
            for dependency in sorted(_referenced_global_names(ast.unparse(node))):
                resolve(dependency)
            inlined.append(_definition_source(node))
            return
        line = owner_imports.get(name)
        if line is not None:
            resolved.add(name)
            if line not in imports:
                imports.append(line)
            return
        if _is_module_global(name):
            resolved.add(name)
            return
        unresolved.append(name)

    for name in sorted(_referenced_global_names(code)):
        resolve(name)
    if unresolved:
        raise ValueError(
            f"codegen attention: {module_type.__name__} references "
            f"{sorted(set(unresolved))} which neither {owner.__name__!r} defines nor "
            "imports; the generated class would carry an undefined name"
        )
    return "\n\n\n".join(inlined), tuple(imports)


def _class_methods(module_type: type) -> list[ast.stmt]:
    """Copy every real method except ``forward``, dropping the module decorator."""
    source = textwrap.dedent(inspect.getsource(module_type))
    tree = ast.parse(source)
    for node in tree.body:
        if isinstance(node, ast.ClassDef) and node.name == module_type.__name__:
            methods = [
                statement
                for statement in node.body
                if not (
                    isinstance(statement, ast.FunctionDef) and statement.name == "forward"
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
                if not (
                    isinstance(statement, ast.FunctionDef) and statement.name == "forward"
                )
            ]
            for statement in methods:
                statement.decorator_list = []
                if isinstance(statement, ast.FunctionDef):
                    _strip_annotations(statement)
            return methods
    raise ValueError(f"codegen attention: no class {module_type.__name__!r} in its source module")


def render_attention_class(module_type: type, *, interface: Callable[..., Any]) -> ExpandedSource:
    """Render a self-contained attention class with a visible TP boundary.

    The copied class methods and the module-level helpers they call are closed
    over the component's own source module, so the artifact carries everything it
324
325
326
327
328
329
330
331
332
333
334
        interface: The attention kernel entry function to inline (e.g.
            ``run_qwen3_moe_flash_attention``); its closure is expanded into the
            artifact and it must stop at the hardware kernel.
    """
    expanded_interface = expand_function(interface)
    forward = _build_forward(module_type)
    class_node = ast.ClassDef(
        name=module_type.__name__,
        bases=[ast.Name(id="nn.Module", ctx=ast.Load())],
        keywords=[],
        body=_class_methods(module_type) + [forward],
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
        keywords=[],
        body=_class_methods(module_type) + [forward],
        decorator_list=[],
    )
    built_class = _fusion_replacement_note(module_type.__name__) + ast.unparse(class_node)
    shared_defs = expanded_interface.source
    helpers, imports = _component_dependencies(
        module_type,
        built_class,
        provided=(_module_bound_names(shared_defs) | _import_bound_names(expanded_interface.imports)),
    )
    return ExpandedSource(
        (*expanded_interface.imports, *imports),
        "\n\n\n".join(part for part in (helpers, shared_defs, built_class) if part),
    )
hyper_parallel/codegen/inline/expansion.py
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# limitations under the License.
# ============================================================================
"""Expand real orchestration source, stopping at framework and kernel APIs."""

import ast
from dataclasses import dataclass
import inspect
import textwrap
from types import ModuleType
from typing import Any, Callable


_PRIMITIVE_PREFIXES = (
    "hyper_parallel.core",
    "hyper_parallel.collectives",
    "hyper_parallel.components.functional",
    "hyper_parallel.components.modules",
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
    "hyper_parallel.codegen.runtime",
)


@dataclass(frozen=True)
class ExpandedSource:
    """Source and imports required by one expanded dependency closure."""

    imports: tuple[str, ...]
    source: str


def expand_function(function: Callable[..., Any]) -> ExpandedSource:
    """Copy an orchestration function and recursively resolve its globals.

    Constants use their source initializer, so generated cache state never
    captures live device tensors from the generation process. Unsupported
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
    Constants use their source initializer, so generated cache state never
    captures live device tensors from the generation process. Unsupported
    globals fail at generation rather than becoming unresolved artifact names.
    """
    expansion = _Expansion()
    expansion.add_function(function, function.__name__)
    return ExpandedSource(tuple(sorted(expansion.imports)), "\n\n".join(expansion.definitions))


class _Expansion:
    """Resolve one closure with deterministic order and collision checking."""

    def __init__(self) -> None:
        self.imports: set[str] = set()
        self.definitions: list[str] = []
        self.bindings: dict[str, Any] = {}

    def _claim(self, name: str, value: Any) -> bool:
        if name in self.bindings:
            if self.bindings[name] is not value:
                raise ValueError(f"codegen source expansion: conflicting global {name!r}")
            return False
        self.bindings[name] = value
        return True

    def add_function(self, function: Callable[..., Any], name: str) -> None:
        """Resolve dependencies before emitting a function definition."""
        if not self._claim(name, function):
            return
        source = textwrap.dedent(inspect.getsource(function))
        tree = ast.parse(source)
        node = tree.body[0]
        if not isinstance(node, ast.FunctionDef) or node.decorator_list:
            raise ValueError(f"codegen source expansion: unsupported decorated function {name!r}")
        global_names = {item.id for item in ast.walk(node) if isinstance(item, ast.Name)
                        and isinstance(item.ctx, ast.Load)}
        local_names = set(function.__code__.co_varnames)
        for global_name in sorted(global_names - local_names):
            if global_name in function.__globals__:
                self.add_global(global_name, function.__globals__[global_name], inspect.getmodule(function))
        node.name = name
        self.definitions.append(ast.unparse(node))

    def add_global(self, name: str, value: Any, owner: ModuleType) -> None:
        """Emit an import, dependency function, or literal source initializer."""
        if inspect.isfunction(value) and _is_orchestration(value):
            self.add_function(value, name)
            return
        if not self._claim(name, value):
            return
        if isinstance(value, ModuleType):
            self.imports.add(f"import {value.__name__} as {name}")
            return
        module_name = getattr(value, "__module__", None)
        original_name = getattr(value, "__name__", None)
        if module_name and original_name:
            self.imports.add(f"from {module_name} import {original_name} as {name}")
            return
        initializer = _literal_initializer(owner, name)
        self.definitions.append(f"{name} = {initializer}")


def _is_orchestration(function: Callable[..., Any]) -> bool:
    module = function.__module__
    return module.startswith("hyper_parallel.") and not any(
        module == prefix or module.startswith(prefix + ".") for prefix in _PRIMITIVE_PREFIXES
    )


def _literal_initializer(owner: ModuleType, name: str) -> str:
    """Return the source text of the literal assigned to ``name`` in ``owner``."""
    for node in ast.parse(inspect.getsource(owner)).body:
        if isinstance(node, ast.Assign):
            targets, value = node.targets, node.value
        elif isinstance(node, ast.AnnAssign):
            targets, value = [node.target], node.value
        else:
            continue
        if any(isinstance(target, ast.Name) and target.id == name for target in targets):
            try:
                ast.literal_eval(value)
            except (ValueError, TypeError) as exc:
                raise ValueError(f"codegen source expansion: nonliteral global {name!r}") from exc
            return ast.unparse(value)
    raise ValueError(f"codegen source expansion: no literal initializer for {name!r}")
hyper_parallel/codegen/inline/framework_spec.py
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100


def _target_path(value: Any) -> str | None:
    """Return the dotted ``_target_`` of a projected override value."""
    if value is None:
        return None
    if isinstance(value, str):
        return value
    if isinstance(value, Mapping):
        target = value.get("_target_") or value.get("target")
        return str(target) if target else None
    return None


def _family_provider(model_type: str | None, provider: str) -> Any | None:
    """Return one family adapter provider module, or ``None`` if unregistered."""
    if not model_type:
        return None
    from hyper_parallel.models.registry import get_model_adapter  # pylint: disable=C0415

    adapter = get_model_adapter(model_type)
    loader = getattr(adapter, provider, None) if adapter is not None else None
    if loader is None:
        return None
    return loader()


def _family_key(model_type: str | None, target: str | None) -> str | None:
    """Canonical family key for one lookup.
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
    ``models/<family>/adapter`` convention supplies the family when the caller
    did not name one.  An unregistered identity is kept verbatim so resolution
    still fails closed (``None``) instead of falling back to another family.
    """
    if model_type is None:
        model_type = family_from_target(target)
    if not model_type:
        return None
    from hyper_parallel.models.registry import get_model_adapter  # pylint: disable=C0415

    adapter = get_model_adapter(model_type)
    return adapter.model_type if adapter is not None else model_type


def _factory_function(target: str, model_type: str | None):
    """Resolve a YAML replacement ``_target_`` to the family's real factory.
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138

    Returns ``None`` when the target is not a factory exported by this family's
    replacements provider — a target the framework cannot structurally resolve.
    """
    module = _family_provider(model_type, "replacements")
    if module is None:
        return None
    prefix = module.__name__ + "."
    if not target.startswith(prefix):
        return None
    name = target[len(prefix):]
    if "." in name:
        return None
    return getattr(module, name, None)


#: YAML ``replace_module`` targets under this prefix name a generic component
#: class directly (``hyper_parallel.components.modules.engram.EngramModule``)
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
    Fail closed: only a ``hyper_parallel.components.*`` path that resolves to
    a class yields a component.  Any other target (a family factory path, a
    user helper, a typo) is not a component declaration and returns ``None``.
    """
    if not target.startswith(_COMPONENTS_PREFIX):
        return None
    module_name, _, attr = target.rpartition(".")
    if not module_name or not attr:
        return None
    try:
        module = importlib.import_module(module_name)
    except ImportError:
        return None
    component = getattr(module, attr, None)
    return component if isinstance(component, type) else None


def _component_replacement_target(component: type) -> ReplacementTarget | None:
    """Read a wrap-style component's own constructor as its replacement target.
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
    handed over (``wraps_source``), and each remaining framework keyword the
    native factory passes (``module_fqn`` / ``context``) applies the
    component's own constructor default.
    """
    parameters = inspect.signature(component).parameters
    if "module" not in parameters:
        return None
    arguments = tuple(
        ReplacementArgument(name, None)
        for name in ("module_fqn", "context")
        if name in parameters
    )
    return ReplacementTarget(
        component=component.__name__,
        module=component.__module__,
        wraps_source=True,
        arguments=arguments,
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
    Lazy by construction: the component's own module is imported only when a
    rule that constructs it is actually present (``components.modules``
    re-exports lazily, so this pulls in one submodule, never the whole set).
    """
    module = importlib.import_module(target.module)
    component = getattr(module, target.component, None)
    if not isinstance(component, type):
        raise RuntimeError(
            f"codegen: {target.module!r} does not export the class "
            f"{target.component!r} the replacement factory constructs"
        )
    return component


def _render_arguments(
    target: ReplacementTarget, component: type
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
    the framework never restates a component's keyword shape. ``None`` means the
    component does not accept one of the keywords the factory passes, i.e. the
    factory constructs an interface codegen cannot sink.
    """
    parameters = inspect.signature(component).parameters
    rendered: list[str] = []
    for argument in target.arguments:
        if argument.value is not None:
            rendered.append(f"{argument.name}={argument.value}")
            continue
        if argument.name not in parameters:
            return None
        rendered.append(f"{argument.name}={parameters[argument.name].default!r}")
    return tuple(rendered)


def _interface_function(factory: Any, target: ReplacementTarget) -> Any:
    """The family kernel entry the factory hands its component."""
    interface = getattr(factory, "__globals__", {}).get(target.interface)
    if not callable(interface):
        raise RuntimeError(
            f"codegen: replacement factory {getattr(factory, '__name__', factory)!r} "
            f"hands over {target.interface!r}, which its module does not define"
        )
    return interface


def _build_replacement_spec(
    factory: Any, target: ReplacementTarget, old_ctor: str
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
    hands to the component -- which is what makes the component's class rendered
    (visible in the artifact) instead of merely imported. ``None`` means the
    construction is not one codegen can sink into the generated ``__init__``.
    """
    component = _component_class(target)
    keyword_args = _render_arguments(target, component)
    if keyword_args is None:
        return None
    # A component that receives a family interface is rendered into the artifact,
    # so its class is defined there -- importing it as well would be dead weight
    # the rendered definition shadows.
    imports: list[ImportPatch] = (
        [] if target.interface else [ImportPatch(target.module, (target.component,))]
    )
    snippets: tuple[ModuleSnippetPatch, ...] = ()
    if target.interface:
        from hyper_parallel.codegen.inline.attention import (  # pylint: disable=C0415
            render_attention_class,
        )

        expanded = render_attention_class(
            component, interface=_interface_function(factory, target)
        )
        imports.extend(
            ImportPatch(module="", names=(), raw=line) for line in expanded.imports
        )
        snippets = (ModuleSnippetPatch(expanded.source),)
    mode = "wrap_source" if target.wraps_source else "name"
    return ReplacementSpec(
        old_ctor=old_ctor,
        new_ctor=target.component,
        mode=mode,
        keyword_args=keyword_args,
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
    ``module_type`` is the matched source module's dotted type path from the
    YAML rule; the source class name (``old_ctor``) is its class name — a
    property of the matched source module, never a hand-written literal.
    """
    if target is None:
        return None
    if not adapter_path_parts(target):
        return _component_replacement_spec(target, model_type, module_type=module_type)
    model_type = _family_key(model_type, target)
    factory = _factory_function(target, model_type)
    if factory is None:
        return None
    # The spec is derived from the *factory's* structure, so the factory is
    # part of the key: a target re-pointed at a different factory (or a family
    # re-registered with one) must not reuse the previous resolution.
    key = (model_type, factory, module_type)
    if key in _REPLACEMENT_CACHE:
        return _REPLACEMENT_CACHE[key]
    spec = _resolve_replacement_spec(factory, model_type, module_type, target)
    _REPLACEMENT_CACHE[key] = spec
    return spec


def _resolve_replacement_spec(
    factory: Any, model_type: str | None, module_type: str | None, target: str
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
    needs no codegen declaration.  A factory with no constructor call to sink --
    or a rule with no ``module_type`` naming the source class it replaces -- has
    no spec, and the rule is left to the native path.
    """
    del model_type, target
    # The source class the constructor call replaces: without the rule's
    # ``module_type`` there is no call site to rewrite, and guessing one would
    # silently target the wrong class.
    old_ctor = class_name_of(module_type)
    if not old_ctor:
        return None
    replacement = replacement_target(factory)
    if replacement is None:
        return None
    return _build_replacement_spec(factory, replacement, old_ctor)


def _component_replacement_spec(
    target: str, model_type: str | None, *, module_type: str | None
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
    from it exactly as the factory channel derives one from the factory's
    construction call, so a wrap-style component needs no family factory to
    be sunk into the generated ``__init__``.
    """
    component = _direct_component_class(target)
    if component is None:
        return None
    key = (model_type, component, module_type)
    if key in _REPLACEMENT_CACHE:
        return _REPLACEMENT_CACHE[key]
    spec = None
    old_ctor = class_name_of(module_type)
    if old_ctor:
        replacement = _component_replacement_target(component)
        if replacement is not None:
            # No interface: a direct component hands over no family kernel,
            # so the factory argument of _build_replacement_spec is unused.
            spec = _build_replacement_spec(None, replacement, old_ctor)
    _REPLACEMENT_CACHE[key] = spec
    return spec


def strategy_spec_for(
    target: str | None,
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
    CP ``inner_wrapper`` target under the adapter convention resolves to the
    framework CP attention strategy (imports only). Anything else is
    unresolvable and yields ``None`` — recognition never guesses a strategy.
    """
    if target is None:
        return None
    model_type = _family_key(model_type, target)
    key = (target, model_type, inner_wrapper)
    if key in _STRATEGY_CACHE:
        return _STRATEGY_CACHE[key]
    spec = _resolve_strategy_spec(target, model_type, inner_wrapper)
    _STRATEGY_CACHE[key] = spec
    return spec


def _resolve_strategy_spec(
    target: str, model_type: str | None, inner_wrapper: bool
408
409
410
411
412
413
414
415
416
417
418
419
420
421

def _resolve_strategy_spec(
    target: str, model_type: str | None, inner_wrapper: bool
) -> StrategySpec | None:
    archetype = archetype_for_factory(target)
    if archetype is not None:
        return _ep_strategy_spec(archetype)
    if inner_wrapper and adapter_path_parts(target):
        return StrategySpec(kind=CP_ATTENTION_KIND, imports=())
    return None


def _ep_strategy_spec(archetype: Any) -> StrategySpec:
    """Build the EP strategy spec: the inlined forward plus its imports.
423
424
425
426
427
428
429
430
431
432
433
    The inlined EP forward orchestrates through the instance-attribute channel
    (``self.ep_enable`` / ``self._hp_ep_group``) like every other generated
    forward, so it declares no module-level snippet.
    """
    from hyper_parallel.codegen.inline.templates import moe_ep_forward_body  # pylint: disable=C0415

    return StrategySpec(
        kind=archetype.kind,
        target_class=archetype.target_class,
        body_template=moe_ep_forward_body(
            router_kind=archetype.router_kind, shared=archetype.shared
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
    keeps the component's own ``forward`` and therefore takes a *compiled
    boundary* (``hp_install_boundaries``) rather than external state — see
    :func:`external_state_classes_for`.
    """
    module = _family_provider(model_type, "replacements")
    if module is None:
        return None
    for name in dir(module):
        factory = getattr(module, name)
        if not callable(factory) or getattr(factory, "_hp_module_replacement", False) is not True:
            continue
        target = replacement_target(factory)
        if target is not None and target.interface:
            return target.component
    return None


def external_state_classes_for(
    model_type: str | None,
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
    these: its forward is the component's own plus the emitted boundary form,
    so the shared compiled-boundary path
    (``hp_install_boundaries`` / ``hp_apply_inner_wrapper``) owns it.
    """
    inlined: set[str] = set()
    for record in injections or ():
        spec = strategy_spec_for(
            _target_path(record.get("local_compute_fn")), model_type
        )
        if spec is None or spec.body_template is None or spec.target_class is None:
            continue
        inlined.add(spec.target_class)
    return tuple(sorted(inlined))


def declaration_summary(
    model_type: str | None,
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
    resolved content must invalidate cached artifacts. Summarizing exactly the
    YAML targets this override set will inline keeps that invalidation faithful
    without a per-family declaration site.
    """
    replacement_specs: dict[str, Any] = {}
    strategy_specs: dict[str, Any] = {}
    for entry in overrides or ():
        replace_target = _target_path(entry.get("replace_module"))
        if replace_target is not None:
            spec = replacement_spec_for(
                replace_target, model_type, module_type=entry.get("module_type")
            )
            replacement_specs[replace_target] = asdict(spec) if spec is not None else None
        local_target = _target_path(entry.get("local_compute_fn"))
        if local_target is not None:
            spec = strategy_spec_for(local_target, model_type)
            strategy_specs[local_target] = asdict(spec) if spec is not None else None
        inner_target = _target_path(entry.get("inner_wrapper"))
        if inner_target is not None:
            spec = strategy_spec_for(inner_target, model_type, inner_wrapper=True)
            strategy_specs[inner_target] = asdict(spec) if spec is not None else None
    return {"replacement_specs": replacement_specs, "strategy_specs": strategy_specs}


__all__ = [
    "CP_ATTENTION_KIND",
hyper_parallel/codegen/inline/meta_plan.py
48
49
50
51
52
53
54
55
56
57
58
59
    # DeepseekV3MLP used as both dense decoder layers and shared experts
    # inside MoE). Source lowering rewrites class.forward, which ALL
    # instances inherit -- a non-boundary instance would get a boundary
    # body it cannot satisfy (no _hp_boundary installed).
    full_class_map = dict(getattr(meta, "boundary_classes", None) or {})
    _strip_ep_sub_boundaries(meta, rules, model_type)
    _strip_external_state_boundaries(meta)
    _mark_shared_class_boundaries(meta, full_class_map)


def _strip_external_state_boundaries(meta: Any) -> None:
    """Keep parameter sharding without duplicating inline-body-owned collectives.
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
    class would rewrite the very forward the inline patch installed.  A
    rendered component class is deliberately kept — the emitted boundary form
    wraps its ``_forward_impl`` and is compiled at install time.
    """
    external_classes = set(getattr(meta, "external_state_classes", None) or ())
    boundary_classes = getattr(meta, "boundary_classes", None) or {}
    param_plan = getattr(meta, "param_plan", None) or {}
    for fqn, class_name in list(boundary_classes.items()):
        if class_name not in external_classes:
            continue
        entry = param_plan.get(fqn)
        if isinstance(entry, dict):
            entry["is_boundary"] = False
        boundary_classes.pop(fqn)


def _strip_ep_sub_boundaries(
    meta: Any, rules: tuple[InlineRule, ...], model_type: str | None,
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
    ``is_boundary: false`` so ``lower_forward_boundaries`` does not double-wrap
    them.  The param_plan entries are kept so FSDP / EP param sharding still
    applies and the preflight coverage check passes.
    """
    param_plan = getattr(meta, "param_plan", None) or {}
    boundary_classes = getattr(meta, "boundary_classes", None) or {}
    ep_fqns: set[str] = set()
    strip_patterns: tuple[str, ...] = ()
    for rule in rules:
        target = rule.local_compute_target
        if target is None:
            continue
        spec = strategy_spec(target, model_type)
        if spec is None or spec.target_class is None:
            continue
        if spec.strip_boundary_subpatterns:
            strip_patterns = spec.strip_boundary_subpatterns
        for pattern in rule.match:
            for fqn, cls in boundary_classes.items():
                if fnmatch(fqn, pattern) and cls == spec.target_class:
                    ep_fqns.add(fqn)
    if not ep_fqns:
        return
    mark_fqns: set[str] = set()
    for ep_fqn in ep_fqns:
        mark_fqns.add(ep_fqn)
        if not strip_patterns:
            continue
        sub_prefix = ep_fqn + "."
        for fqn in list(param_plan):
            if not fqn.startswith(sub_prefix):
                continue
            local_suffix = fqn[len(sub_prefix):]
            for pat in strip_patterns:
                if fnmatch(local_suffix, pat):
                    mark_fqns.add(fqn)
                    break
    for fqn in mark_fqns:
        entry = param_plan.get(fqn)
        if isinstance(entry, dict):
            entry["is_boundary"] = False
        boundary_classes.pop(fqn, None)


def _mark_shared_class_boundaries(meta: Any, full_class_map: dict[str, str]) -> None:
    """Mark source lowering as skipped for classes shared with a non-boundary instance.
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
    implied by missing data.  ``hp_install_boundaries`` installs the
    per-instance runtime wrapper for the marked boundary, which executes
    the same contract.
    """
    param_plan = getattr(meta, "param_plan", None) or {}
    boundary_classes = getattr(meta, "boundary_classes", None) or {}

    non_boundary_classes: set[str] = set()
    for fqn, entry in param_plan.items():
        if not isinstance(entry, dict):
            continue
        if entry.get("is_boundary") is False:
            cls = full_class_map.get(fqn)
            if cls is not None:
                non_boundary_classes.add(cls)

    if not non_boundary_classes:
        return

    for fqn, class_name in list(boundary_classes.items()):
        if class_name not in non_boundary_classes:
            continue
        entry = param_plan.get(fqn)
        if isinstance(entry, dict):
            entry["skip_source_lowering"] = True
            logger.info(
                "codegen: boundary %s (class %s) marked skip_source_lowering "
                "-- the class is shared with a non-boundary instance; the "
                "runtime per-instance wrapper is used instead",
                fqn, class_name,
hyper_parallel/codegen/inline/patch_engine.py
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
    Returns:
        The patched source text.
    """

    first_pass: list[TextEdit] = []
    index = build_source_index(source_text)
    import_edit = _import_edit(source_text, index.import_end, index.docstring_end, patch_set.imports)
    if import_edit is not None:
        first_pass.append(import_edit)
    snippet_edit = _snippet_edit(source_text, index.import_end, index.docstring_end, patch_set.module_snippets)
    if snippet_edit is not None:
        first_pass.append(snippet_edit)
    first_pass.extend(_constructor_edits(source_text, patch_set.constructor_replaces))
    text = apply_edits(source_text, first_pass) if first_pass else source_text

    if patch_set.class_removals:
        text = _apply_class_removals(text, patch_set.class_removals)

    second_pass: list[TextEdit] = []
    second_pass.extend(_forward_extract_edits(text, patch_set.forward_extracts))
    return apply_edits(text, second_pass) if second_pass else text


def _import_edit(
    source_text: str,
72
73
74
75
76
77
78
79
80
81
82
83
    import_end: int | None,
    docstring_end: int | None,
    imports: Iterable[ImportPatch],
) -> TextEdit | None:
    lines = _render_imports(source_text, imports)
    if not lines:
        return None
    return insert_after_imports(
        source_text,
        import_end,
        "\n" + "\n".join(lines) + "\n",
        default_offset=docstring_end or 0,
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106


def _render_imports(source_text: str, imports: Iterable[ImportPatch]) -> list[str]:
    """Render import patches as deduplicated, grouped import lines."""
    grouped: dict[str, set[str]] = defaultdict(set)
    lines: list[str] = []
    for patch in imports:
        if patch.raw:
            if patch.raw not in source_text:
                lines.append(patch.raw)
            continue
        grouped[patch.module].update(patch.names)
    for module in sorted(grouped):
        names = tuple(sorted(grouped[module]))
        line = f"from {module} import {', '.join(names)}"
        if line not in source_text:
            lines.append(line)
    return lines


def _snippet_edit(
    source_text: str,
107
108
109
110
111
112
113
114
115
116
117
118
    import_end: int | None,
    docstring_end: int | None,
    snippets: Iterable[ModuleSnippetPatch],
) -> TextEdit | None:
    parts = [snippet.text.strip("\n") for snippet in snippets if snippet.text.strip()]
    if not parts:
        return None
    return insert_after_imports(
        source_text,
        import_end,
        "\n\n" + "\n\n".join(parts) + "\n",
        default_offset=docstring_end or 0,
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
    source_text: str,
    patches: Iterable[ConstructorReplacePatch],
) -> list[TextEdit]:
    """Build the edits that swap constructor calls per patch target."""
    patch_list = list(patches)
    patches_by_old: dict[str, list[ConstructorReplacePatch]] = defaultdict(list)
    for patch in patch_list:
        patches_by_old[patch.old_ctor].append(patch)
    if not patches_by_old:
        return []
    scopes = _scopes_by_old_ctor(patch_list)
    tree = ast.parse(source_text)
    offsets = _line_offsets(source_text)
    edits: list[TextEdit] = []
    hits: dict[str, int] = defaultdict(int)
    for node in ast.walk(tree):
        if not isinstance(node, ast.Assign):
            continue
        attr = _assigned_attr(node)
        if attr is None:
            continue
        call = node.value
        if not isinstance(call, ast.Call):
            continue
        name = _call_name(call.func)
        if name is None or name not in patches_by_old:
            continue
        scope = scopes[name]
        if scope is not None and attr not in scope:
            continue
        patch = patches_by_old[name][0]
        hits[name] += 1
        if patch.mode == "name":
            start, end = _node_span(call.func, offsets)
            edits.append(TextEdit(start, end, patch.new_ctor))
        elif patch.mode == "wrap_source":
            start, end = _node_span(call, offsets)
            call_source = source_text[start:end]
            extra = "".join(f", {arg}" for arg in patch.keyword_args)
            replacement = f"{patch.new_ctor}(module={call_source}{extra})"
            edits.append(TextEdit(start, end, replacement))
        else:
            raise ValueError(f"unknown constructor replacement mode: {patch.mode!r}")
    _require_scoped_rewrites(patch_list, hits)
    return _drop_nested_edits(edits)


def _scopes_by_old_ctor(
    patches: Iterable[ConstructorReplacePatch],
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
    patches: Iterable[ConstructorReplacePatch],
) -> dict[str, set[str] | None]:
    """Merge each constructor's rewrite scope; ``None`` means unrestricted."""

    scopes: dict[str, set[str] | None] = {}
    for patch in patches:
        if not patch.scope_attrs:
            scopes[patch.old_ctor] = None
            continue
        merged = scopes.get(patch.old_ctor)
        if merged is None and patch.old_ctor in scopes:
            continue
        if merged is None:
            merged = scopes[patch.old_ctor] = set()
        merged.update(patch.scope_attrs)
    return scopes


def _assigned_attr(node: ast.Assign) -> str | None:
    """Return the stored attribute of an attribute store, else ``None``.
197
198
199
200
201
202
203
204
205
206
207
208
    modules, so any attribute store yields its stored attribute -- the rule's
    scope set keeps the rewrite at the plan's matched-FQN granularity.
    """

    if len(node.targets) != 1:
        return None
    target = node.targets[0]
    return target.attr if isinstance(target, ast.Attribute) else None


def _require_scoped_rewrites(
    patches: Iterable[ConstructorReplacePatch],
215
216
217
218
219
220
221
222
223
224
225
226
227
    generation error: the scope handling needs to learn the new shape rather
    than the rewrite being widened back to the whole file.
    """

    missing = sorted(
        {patch.old_ctor for patch in patches if patch.scope_attrs and not hits.get(patch.old_ctor)}
    )
    if missing:
        raise RuntimeError(
            "codegen: no `self.<attr> = <ctor>(...)` call site found for "
            f"{missing}; the plan replaced modules of this type, so the "
            "replacement cannot be sunk without diverging from the plan"
        )
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243


def _drop_nested_edits(edits: list[TextEdit]) -> list[TextEdit]:
    """Keep outer constructor rewrites when constructor calls are nested."""
    kept: list[TextEdit] = []
    covered_end = -1
    for edit in sorted(edits, key=lambda item: (item.start, -item.end)):
        if edit.start < covered_end:
            continue
        kept.append(edit)
        covered_end = edit.end
    return kept


def drop_shadowed_definitions(text: str, imported_names: Iterable[str]) -> str:
    """Remove definitions that shadow a library import of the same name.
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
    Returns:
        The source with the shadowing definitions removed.
    """

    names = {name for name in imported_names if name}
    if not names:
        return text
    tree = ast.parse(text)
    offsets = _line_offsets(text)
    edits: list[TextEdit] = []
    for node in tree.body:
        if not isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef)):
            continue
        if node.name not in names or not node.decorator_list:
            continue
        start, end = _node_span(node, offsets)
        edits.append(TextEdit(start, _skip_trailing_blank_lines(text, end), ""))
    if not edits:
        return text
    return apply_edits(text, edits)


def _name_is_referenced(tree: ast.AST, name: str) -> bool:
    """Return whether ``name`` still appears as a reference in ``tree``."""
282
283
284
285
286
287
288
289
290
291
292
293
294
295

def _name_is_referenced(tree: ast.AST, name: str) -> bool:
    """Return whether ``name`` still appears as a reference in ``tree``."""

    for node in ast.walk(tree):
        if isinstance(node, ast.Name) and node.id == name:
            return True
        if isinstance(node, ast.Attribute) and node.attr == name:
            return True
    return False


def _apply_class_removals(
    text: str,
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331

    Returns:
        The text with unused classes removed.
    """
    patch_list = list(patches)
    if not patch_list:
        return text
    tree = ast.parse(text)
    offsets = _line_offsets(text)
    names_to_remove, new_name_by_old = _removal_targets(tree, patch_list)
    if not names_to_remove:
        return text
    deletion_ranges, classdef_nodes = _class_deletion_spans(tree, offsets, names_to_remove)
    edits = _reference_edits(
        tree, offsets, names_to_remove, new_name_by_old, deletion_ranges
    )
    edits.extend(_deletion_edits(text, offsets, patch_list, classdef_nodes))
    return apply_edits(text, edits) if edits else text


def _removal_targets(
    tree: ast.AST,
339
340
341
342
343
344
345
346
347
348
349
    that is still called would turn a partially replaced model into a
    ``NameError`` instead of the model the plan described.
    """

    names = {patch.old_name for patch in patch_list}
    unused = {name for name in names if not _name_is_referenced(tree, name)}
    return unused, {patch.old_name: patch.new_name for patch in patch_list}


def _class_deletion_spans(
    tree: ast.AST,
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
    names_to_remove: set[str],
) -> tuple[list[tuple[int, int]], dict[str, ast.ClassDef]]:
    """Return the source spans of the removable classes and their nodes."""

    deletion_ranges: list[tuple[int, int]] = []
    classdef_nodes: dict[str, ast.ClassDef] = {}
    for node in tree.body:
        if isinstance(node, ast.ClassDef) and node.name in names_to_remove:
            classdef_nodes[node.name] = node
            deletion_ranges.append(_node_span(node, offsets))
    return deletion_ranges, classdef_nodes


def _reference_edits(
    tree: ast.AST,
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
    deletion_ranges: list[tuple[int, int]],
) -> list[TextEdit]:
    """Return edits rewriting bare references that survive the deletion."""

    edits: list[TextEdit] = []
    for node in ast.walk(tree):
        if not isinstance(node, ast.Name) or node.id not in names_to_remove:
            continue
        start, end = _node_span(node, offsets)
        if any(span <= start < stop for span, stop in deletion_ranges):
            continue
        edits.append(TextEdit(start, end, new_name_by_old[node.id]))
    return edits


def _deletion_edits(
    text: str,
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
    classdef_nodes: dict[str, ast.ClassDef],
) -> list[TextEdit]:
    """Return edits deleting the removable class blocks and trailing blanks."""

    edits: list[TextEdit] = []
    for patch in patch_list:
        cls_node = classdef_nodes.get(patch.old_name)
        if cls_node is None:
            continue
        start, end = _node_span(cls_node, offsets)
        end = _skip_trailing_blank_lines(text, end)
        edits.append(TextEdit(start, end, ""))
    return edits


def _skip_trailing_blank_lines(text: str, end: int) -> int:
    """Advance ``end`` past spaces and blank lines following a deleted block."""
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416

def _skip_trailing_blank_lines(text: str, end: int) -> int:
    """Advance ``end`` past spaces and blank lines following a deleted block."""

    while end < len(text) and text[end] in " \t":
        end += 1
    while end < len(text) and text[end] == "\n":
        end += 1
        while end < len(text) and text[end] in " \t":
            end += 1
    return end


def _forward_extract_edits(
    source_text: str,
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
    source_text: str,
    patches: Iterable[ForwardExtractPatch],
) -> list[TextEdit]:
    """Build the edits that extract patched forwards into impl functions."""
    if not patches:
        return []
    from hyper_parallel.codegen.emit.parallel import (  # pylint: disable=import-outside-toplevel
        _build_forward_impl_edit,
    )

    index = build_source_index(source_text)
    edits: list[TextEdit] = []
    for patch in patches:
        cls = index.find_class(patch.class_name)
        if cls is None:
            continue
        func = cls.methods.get(patch.method_name)
        if func is None:
            continue
        impl = _build_forward_impl_edit(
            source_text,
            func,
        )
        if impl is not None:
            edits.append(impl)
        edits.extend(replace_function_body(source_text, func, patch.body, indent="        "))
    return edits


def _call_name(node: ast.AST) -> str | None:
    if isinstance(node, ast.Name):
        return node.id
    if isinstance(node, ast.Attribute):
        return node.attr
    return None


def _node_span(node: ast.AST, offsets: tuple[int, ...]) -> tuple[int, int]:
    """Return the ``(start, end)`` source span of ``node``.
457
458
459
460
461
462
463
464
465
    would leave a dangling decorator that then applies to whatever statement
    follows the removed block.
    """

    if (
        getattr(node, "lineno", None) is None
        or getattr(node, "col_offset", None) is None
        or getattr(node, "end_lineno", None) is None
        or getattr(node, "end_col_offset", None) is None
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
        or getattr(node, "col_offset", None) is None
        or getattr(node, "end_lineno", None) is None
        or getattr(node, "end_col_offset", None) is None
    ):
        raise ValueError(f"AST node has no source span: {type(node).__name__}")
    line = node.lineno
    column = node.col_offset
    decorators = getattr(node, "decorator_list", None)
    if decorators:
        first = min(decorators, key=lambda item: item.lineno)
        if first.lineno < line:
            # A decorator node starts at its expression, one column *after* the
            # ``@``; the span must start at the ``@`` itself, otherwise deleting
            # the block leaves a stray ``@`` behind.
            line, column = first.lineno, max(0, first.col_offset - 1)
    start = offsets[line - 1] + column
    end = offsets[node.end_lineno - 1] + node.end_col_offset
    return start, end


def _line_offsets(source_text: str) -> tuple[int, ...]:
    offsets = [0]
    total = 0
    for line in source_text.splitlines(keepends=True):
        total += len(line)
        offsets.append(total)
    return tuple(offsets)
hyper_parallel/codegen/inline/pipeline.py
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
    Returns:
        The rendered modeling source.
    """

    rules = collect_inline_rules(meta)
    if not rules:
        return source_text
    _require_inline_coverage(rules, model_type)
    replacement_patches = build_replacement_patches(rules, model_type)
    strategy_patches = build_strategy_patches(rules, model_type)
    patch_set = _merge_patch_sets(replacement_patches, strategy_patches)
    rendered = apply_patch_set(source_text, patch_set)
    # The copied modeling source brings its own decorated helpers that share
    # names with the library functions the artifact imports, and a local
    # definition wins over the import: the native path calls the library
    # implementation (whose kernel binding is resolved in its own module), so a
61
62
63
64
65
66
67
68
69
70
71
72
73
    # redefined copy makes the generated model run something else.  Only names
    # the base source defines with a decorator are candidates, so the classes and
    # snippets the inline passes render (which share names with library imports
    # on purpose) keep their definitions.
    rendered = drop_shadowed_definitions(
        rendered, _library_import_names(rendered) & _decorated_names(source_text)
    )
    normalize_inline_meta(meta, rules, model_type)
    return rendered


def _library_import_names(text: str) -> set[str]:
    """Names the artifact imports from the library, i.e. the winning bindings.
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
    Returns:
        The set of names bound by its ``hyper_parallel`` imports.
    """

    names: set[str] = set()
    try:
        tree = ast.parse(text)
    except SyntaxError:
        return names
    for node in ast.walk(tree):
        if isinstance(node, ast.ImportFrom) and (node.module or "").startswith("hyper_parallel"):
            names.update(alias.asname or alias.name for alias in node.names)
    return names


def _decorated_names(text: str) -> set[str]:
    """Names of the module-level definitions the base source decorates.
103
104
105
106
107
108
109
110
111
112
113
114
115
    Returns:
        The set of decorated top-level definition names.
    """

    try:
        tree = ast.parse(text)
    except SyntaxError:
        return set()
    return {
        node.name
        for node in tree.body
        if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef))
        and node.decorator_list
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
    body -- its region is bound at install time (the emitted region template
    for an in-source class, the runtime local-region skeleton for an imported
    class), which resolves the factory exactly as the native applier does.
    """
    from hyper_parallel.distributed.recipe_spec import LOCAL_COMPUTE  # pylint: disable=C0415

    module_name, _, attr = target.rpartition(".")
    if not module_name or not attr:
        return False
    try:
        module = importlib.import_module(module_name)
    except ImportError:
        return False
    factory = getattr(module, attr, None)
    if not callable(factory):
        return False
    meta = getattr(factory, "_injection_meta", None)
    return getattr(meta, "kind", None) == LOCAL_COMPUTE


def _require_inline_coverage(rules: tuple[Any, ...], model_type: str | None = None) -> None:
    """Fail when a YAML rule has no structural resolution behind it.
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
    is the coverage proof.  Reporting all gaps at once keeps one fix cycle
    from hiding the next gap.
    """

    uncovered: list[str] = []
    for rule in rules:
        if (
            rule.replace_target is not None
            and replacement_spec(rule.replace_target, model_type, module_type=rule.module_type) is None
        ):
            uncovered.append(rule.replace_target)
        for target in (rule.local_compute_target, rule.inner_wrapper_target):
            if target is None:
                continue
            if strategy_spec(
                target, model_type, inner_wrapper=target == rule.inner_wrapper_target
            ) is not None:
                continue
            # An inner_wrapper target has no runtime channel and stays
            # fail-closed; a local_compute target passes through the
            # @local_compute decoration check.
            if target == rule.local_compute_target and _is_local_compute_factory(target):
                continue
            uncovered.append(target)
    if uncovered:
        raise RuntimeError(
            f"codegen: no inline declaration for {sorted(set(uncovered))} "
            f"(model_type={model_type!r}); the target names no known generic "
            "component or framework strategy, and a local_compute target "
            "names no @local_compute factory"
183
184
185
186
187
188
189
190
191
192
193
194
        )


def _merge_patch_sets(*sets: InlinePatchSet) -> InlinePatchSet:
    merged = InlinePatchSet()
    for patch_set in sets:
        merged.imports.extend(patch_set.imports)
        merged.module_snippets.extend(patch_set.module_snippets)
        merged.constructor_replaces.extend(patch_set.constructor_replaces)
        merged.forward_extracts.extend(patch_set.forward_extracts)
        merged.class_removals.extend(patch_set.class_removals)
    return merged
hyper_parallel/codegen/inline/recognition.py
71
72
73
74
75
76
77
78
79
80
81


def class_name_of(qualified_type: str | None) -> str | None:
    """Return the bare class name of a dotted ``module_type`` path."""
    if not qualified_type:
        return None
    return qualified_type.rsplit(".", 1)[-1] or None


def family_from_target(target: str | None) -> str | None:
    """Infer the adapter family directory from a framework adapter path.
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
    ``hyper_parallel.models.<family>.adapter...`` -> ``<family>``. Returns
    ``None`` for targets that are not model-adapter paths (for example a
    framework recipe), which callers treat as "no family provider".
    """
    parts = (target or "").split(".")
    if len(parts) >= 3 and parts[:2] == ["hyper_parallel", "models"]:
        return parts[2]
    return None


def adapter_path_parts(target: str | None) -> bool:
    """Whether a target lives under the ``models/<family>/adapter`` convention."""
    parts = (target or "").split(".")
    return len(parts) >= 4 and parts[:2] == ["hyper_parallel", "models"] and parts[3] == "adapter"


def replacement_target(factory: Any) -> ReplacementTarget | None:
    """Read the component one ``@module_replacement`` factory constructs.
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
    (``SomeExperts.from_module(...)``) has no constructor call to sink, so the
    rule is left untouched -- the same conservative behaviour a component-name
    table gave, without the table.
    """
    try:
        source = textwrap.dedent(inspect.getsource(factory))
        tree = ast.parse(source)
    except (OSError, TypeError, SyntaxError):
        return None
    imported = _factory_imported_modules(tree)
    parameters = _parameter_names(factory)
    for node in ast.walk(tree):
        if not isinstance(node, ast.Call) or not isinstance(node.func, ast.Name):
            continue
        component = node.func.id
        module = imported.get(component) or _global_class_module(factory, component)
        if module is None:
            continue
        return ReplacementTarget(
            component=component,
            module=module,
            wraps_source=any(
                keyword.arg == "module"
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
                ),
                None,
            ),
        )
    return None


def _factory_imported_modules(tree: ast.AST) -> dict[str, str]:
    """Module path per name the factory source imports (``from X import Y``)."""
    imported: dict[str, str] = {}
    for node in ast.walk(tree):
        if isinstance(node, ast.ImportFrom) and node.module:
            for alias in node.names:
                imported.setdefault(alias.asname or alias.name, node.module)
        elif isinstance(node, ast.Import):
            for alias in node.names:
                imported.setdefault(alias.asname or alias.name.split(".")[0], alias.name)
    return imported


def _global_class_module(factory: Any, name: str) -> str | None:
    """Module path of a class the factory's own module imported at module level."""
    value = getattr(factory, "__globals__", {}).get(name)
    module = getattr(value, "__module__", None)
    return module if isinstance(value, type) and module else None


def _parameter_names(factory: Any) -> set[str]:
    """Parameter names of the factory itself (runtime values, not literals)."""
    try:
        return set(inspect.signature(factory).parameters)
    except (TypeError, ValueError):
        return set()


def _factory_arguments(
    call: ast.Call, parameters: set[str], factory: Any
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
    its text (the component's constructor default applies); a value naming a
    callable in the factory's globals -- a kernel entry handed over -- keeps its
    symbol so the artifact stays readable; anything else keeps its own text.
    """
    arguments: list[ReplacementArgument] = []
    for keyword in call.keywords:
        if keyword.arg is None:
            continue
        if keyword.arg == "module" and isinstance(keyword.value, ast.Name):
            continue
        value: str | None = (
            None
            if isinstance(keyword.value, ast.Name) and keyword.value.id in parameters
            else ast.unparse(keyword.value)
        )
        arguments.append(ReplacementArgument(keyword.arg, value))
    return tuple(arguments)


def archetype_for_factory(factory_path: str | None) -> Any | None:
    """Reverse-lookup a MoE archetype by its compute-factory ``_target_``.
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
    A YAML ``local_compute_fn._target_`` that names a known archetype factory
    resolves to that archetype: the strategy kind / target class / boundary
    sub-patterns / EP body keys are then structural, not declared per family.
    """
    if not factory_path:
        return None
    from hyper_parallel.distributed.expert_parallel.archetypes import (  # pylint: disable=C0415
        moe_archetypes,
    )

    for archetype in moe_archetypes().values():
        if archetype.compute_factory == factory_path:
            return archetype
    return None


__all__ = [
    "ReplacementArgument",
hyper_parallel/codegen/inline/replacement_pass.py
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
    Returns:
        The constructor replacement patches for the matched rules.
    """

    patch_set = InlinePatchSet()
    scopes_by_target: dict[str, set[str]] = defaultdict(set)
    specs_by_target: dict[str, Any] = {}
    for rule in rules:
        target = rule.replace_target
        if target is None:
            continue
        spec = replacement_spec(target, model_type, module_type=rule.module_type)
        if spec is None:
            continue
        specs_by_target.setdefault(target, spec)
        scopes_by_target[target].update(_scope_attrs(rule))
    for target, spec in specs_by_target.items():
        scope_attrs = tuple(sorted(scopes_by_target[target]))
        if not scope_attrs:
            logger.warning(
                "codegen: replace_module %r matched no module and is not expanded",
                target,
            )
            continue
        patch_set.imports.extend(spec.imports)
        if spec.replacement_note:
            patch_set.module_snippets.append(
                ModuleSnippetPatch(f"# Codegen replacement: {spec.replacement_note}")
            )
        patch_set.module_snippets.extend(spec.snippets)
        patch_set.constructor_replaces.append(
            ConstructorReplacePatch(
                old_ctor=spec.old_ctor,
                new_ctor=spec.new_ctor,
                mode=spec.mode,  # type: ignore[arg-type]
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
                keyword_args=spec.keyword_args,
                scope_attrs=scope_attrs,
            )
        )
        if spec.remove_class:
            patch_set.class_removals.append(
                ClassRemovalPatch(old_name=spec.old_ctor, new_name=spec.new_ctor)
            )
    return patch_set


def _scope_attrs(rule: InlineRule) -> tuple[str, ...]:
    """Attribute names of the rule's settled FQNs, i.e. the rewrite scope.
105
106
107
108
109
    granularity native's FQN matching produces.  ``model.norm`` is a sole-name
    FQN, so the whole FQN is the attribute.
    """

    return tuple(sorted({fqn.rsplit(".", 1)[-1] for fqn in rule.match if fqn}))
hyper_parallel/codegen/inline/spec_bundle.py
48
49
50
51
52
53
54
55
56
57
58
59
60
61
    strip_boundary_subpatterns: tuple[str, ...] = ()

    def __post_init__(self) -> None:
        """Require a complete method patch or an imports-only declaration."""
        if (self.target_class is None) != (self.body_template is None):
            raise ValueError("Inline strategy target_class and body_template must be provided together")
        if self.target_class is not None and (not self.target_class or not self.body_template.strip()):
            raise ValueError("Inline strategy class and body must be nonempty")
        if not self.method_name.isidentifier():
            raise ValueError("Inline strategy method_name must be a Python identifier")


__all__ = [
    "ReplacementSpec",
hyper_parallel/codegen/inline/specs.py
40
41
42
43
44
45
46
47
48
    ``module_type`` is the rule's matched source type from the YAML; it names
    the source class the generated artifact must model, so it is passed rather
    than reconstructed.
    """
    return replacement_spec_for(target, model_type, module_type=module_type)


def strategy_spec(
    target: str | None,
55
56
57
58
59
60
61
62
    ``inner_wrapper`` marks the target as a ``plan_overrides.inner_wrapper``
    declaration: only that position makes a CP attention wrapper a strategy
    (a ``local_compute_fn`` in the same slot is not one).
    """
    return strategy_spec_for(target, model_type, inner_wrapper=inner_wrapper)


__all__ = ["replacement_spec", "strategy_spec"]
hyper_parallel/codegen/inline/strategy_pass.py
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
    parallel-state accessor is emitted once per generated file instead of once
    per matched layer.
    """

    patch_set = InlinePatchSet()
    emitted: dict[tuple[str, str], str] = {}
    seen: set[tuple[str, bool]] = set()
    for rule in rules:
        for target in (rule.local_compute_target, rule.inner_wrapper_target):
            if target is None:
                continue
            inner_wrapper = target == rule.inner_wrapper_target
            if (target, inner_wrapper) in seen:
                continue
            spec = strategy_spec(target, model_type, inner_wrapper=inner_wrapper)
            if spec is None:
                continue
            seen.add((target, inner_wrapper))
            patch_set.imports.extend(spec.imports)
            patch_set.module_snippets.extend(spec.snippets)
            if spec.target_class is None:
                continue
            key = (spec.target_class, spec.method_name)
            if key in emitted:
                if emitted[key] != spec.body_template:
                    raise ValueError(f"Conflicting inline strategies for {spec.target_class}.{spec.method_name}")
                continue
            emitted[key] = spec.body_template
            patch_set.forward_extracts.append(
                ForwardExtractPatch(
                    class_name=spec.target_class,
                    method_name=spec.method_name,
                    body=spec.body_template,
61
62
63
64
65
                    method_name=spec.method_name,
                    body=spec.body_template,
                )
            )
    return patch_set
hyper_parallel/codegen/inline/templates.py
13
14
15
16
17
18
19
20
21
# limitations under the License.
# ============================================================================
"""Inline strategy templates rendered into generated modeling files."""

from __future__ import annotations


#: Bare parallel operators for the generated forwards.  The generated file no
#: longer embeds its own operator class or a module-level state accessor:
26
27
28
29
30
31
32
33
34
#: (toggle) forward dispatch through one production collective path.  An inline
#: *strategy* body (the shell below) reads only the EP channel
#: (``self.ep_enable`` / ``self._hp_ep_group``), which
#: ``hp_bind_inline_state`` binds.
MOE_EP_FORWARD_SHELL = '''"""Inline EP routed MoE forward (framework generic)."""
if not self.ep_enable:
    return self._forward_impl(hidden_states)

return moe_ep_forward(
40
41
42
43
44
45
46
47
48
)
'''


def moe_ep_forward_body(router_kind: str, shared: str) -> str:
    """Return the thin inline EP forward body for the given structural keys.

    A single shared body referenced by every MoE render spec. The only
    inputs are the structure-selected router kind and shared merge mode, so
54
55
56
57
58

    Returns:
        The rendered forward body source.
    """
    return MOE_EP_FORWARD_SHELL.format(router_kind=router_kind, shared=shared)
hyper_parallel/codegen/inline/yaml_rules.py
17
18
19
20
21
22
23
24
25
26
27
28
29
30

def collect_inline_rules(meta: Any) -> tuple[InlineRule, ...]:
    """Return normalized inline rules recovered from ``meta``."""

    rules: list[InlineRule] = []
    for record in getattr(meta, "module_overrides", None) or []:
        target = record.get("factory")
        if not target:
            continue
        rules.append(
            InlineRule(
                match=_matches(record),
                module_type=record.get("module_type"),
                replace_target=target,
29
30
31
32
33
34
35
36
37
38
39
40
41
42
                module_type=record.get("module_type"),
                replace_target=target,
            )
        )
    for record in getattr(meta, "injections", None) or []:
        local = _target_path(record.get("local_compute_fn"))
        inner = _target_path(record.get("inner_wrapper"))
        if local is None and inner is None:
            continue
        rules.append(
            InlineRule(
                match=_matches(record),
                inner_wrapper_target=inner,
                local_compute_target=local,
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
                inner_wrapper_target=inner,
                local_compute_target=local,
            )
        )
    return tuple(rules)


def _matches(record: dict[str, Any]) -> tuple[str, ...]:
    value = record.get("fqns") or record.get("match") or record.get("fqn")
    if value is None:
        return ()
    if isinstance(value, str):
        return (value,)
    if isinstance(value, Iterable):
        return tuple(str(item) for item in value)
    return (str(value),)


def _target_path(value: Any) -> Optional[str]:
    if value is None:
        return None
    if isinstance(value, str):
        return value
    if isinstance(value, dict):
        return value.get("_target_") or value.get("target")
    return getattr(value, "__qualname__", None)
hyper_parallel/codegen/loader.py
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
is registered in ``sys.modules`` under a stable synthetic name, which is what
lets ``dataclasses``, ``pickle``, and (importantly) ``torch.load`` resolve
classes defined inside it.
"""
from __future__ import annotations

import hashlib
import importlib.util
import logging
import os
import sys
from types import ModuleType
from typing import Any, Optional

logger = logging.getLogger(__name__)

# Synthetic package prefix for generated modules.  Keyed by artifact dir so two
# YAMLs generating for the same model do not collide in ``sys.modules``.
_GENERATED_MODULE_PREFIX = "hyper_parallel_generated"


def _module_name_for(artifact_dir: str, stem: str) -> str:
    """Build a stable, collision-free ``sys.modules`` name for one bundle.

    The basename alone is not unique — two YAMLs in different directories both
    produce ``generated/train/``, which would map to the same synthetic module
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
    absolute path is hashed instead, so the name is unique per bundle and still
    stable across runs (a prerequisite for ``torch.load``/``pickle`` to resolve
    classes defined in the generated file).
    """
    abs_dir = os.path.abspath(os.path.normpath(artifact_dir))
    digest = hashlib.sha256(abs_dir.encode("utf-8")).hexdigest()[:12]
    marker = os.path.basename(abs_dir)
    safe = "".join(c if c.isalnum() or c == "_" else "_" for c in marker)
    return f"{_GENERATED_MODULE_PREFIX}.{safe}_{digest}.{stem}"


def find_generated_modeling_file(artifact_dir: str) -> str:
    """Locate the single ``modeling_*_gen_npu.py`` in the bundle.

    Raises when absent or ambiguous rather than picking one — a bundle with two
    modeling files means generation left something stale behind, and silently
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96

    Returns:
        str: Absolute path of the generated modeling file.
    """
    if not os.path.isdir(artifact_dir):
        raise FileNotFoundError(
            f"codegen: artifact dir {artifact_dir} does not exist; "
"run generation before building from the generated artifact"
        )
    candidates = sorted(
        name for name in os.listdir(artifact_dir)
        if name.startswith("modeling_") and name.endswith("_gen_npu.py")
    )
    if not candidates:
        raise FileNotFoundError(
            f"codegen: no modeling_*_gen_npu.py in {artifact_dir}; "
            "the bundle is incomplete"
        )
    if len(candidates) > 1:
        raise RuntimeError(
            f"codegen: {len(candidates)} generated modeling files in "
            f"{artifact_dir} ({candidates}); remove the stale bundle and "
            "regenerate"
        )
    return os.path.join(artifact_dir, candidates[0])


def import_generated_module(artifact_dir: str) -> ModuleType:
    """Import the generated modeling module from ``artifact_dir``.

    Repeat calls for the same bundle return the cached module.
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114

    Returns:
        ModuleType: The imported (and cached) generated modeling module.
    """
    modeling_path = find_generated_modeling_file(artifact_dir)
    stem = os.path.splitext(os.path.basename(modeling_path))[0]
    module_name = _module_name_for(artifact_dir, stem)

    cached = sys.modules.get(module_name)
    if cached is not None:
        return cached

    # A ``<remote>`` source uses
    # single-dot relative imports (``from .configuration_deepseek import ...``)
    # that resolve against its own package.  spec_from_file_location compiles
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
    # registering that package + its sibling modules before exec makes those
    # relative imports resolve inside the bundle.  Both are keyed on the same
    # path-derived name as the module, so they stay collision-free per bundle
    # and the registered modules are reused on the next import.
    _register_synthetic_package(module_name, artifact_dir)

    spec = importlib.util.spec_from_file_location(module_name, modeling_path)
    if spec is None or spec.loader is None:
        raise ImportError(f"codegen: cannot build an import spec for {modeling_path}")
    module = importlib.util.module_from_spec(spec)
    module.__package__ = _package_name_for(module_name)
    # Registered before exec so a self-referential import inside the generated
    # file resolves to this partially-initialized module rather than looping.
    sys.modules[module_name] = module
    try:
        spec.loader.exec_module(module)
    except Exception:
        sys.modules.pop(module_name, None)
        raise
    logger.info("codegen: imported generated modeling file %s", modeling_path)
    return module


def _package_name_for(module_name: str) -> str:
    """The synthetic package a generated module lives in (minus the stem).

    ``hyper_parallel_generated.<safe>_<digest>.<stem>`` -> ``...<safe>_<digest>``.
    This is the package the generated file's relative imports resolve against,
144
145
146
147
148
149
150
151
152
153
154
155
    ``hyper_parallel_generated.<safe>_<digest>.<stem>`` -> ``...<safe>_<digest>``.
    This is the package the generated file's relative imports resolve against,
    and it is stable per bundle (the digest is the artifact dir's path hash).
    """
    return module_name.rsplit(".", 1)[0]


def _register_synthetic_package(module_name: str, artifact_dir: str) -> None:
    """Register the synthetic package + its sibling modules in ``sys.modules``.

    A plain ``ModuleType`` package with ``__path__`` set to the bundle dir lets
    ``from .name import x`` resolve against the bundle's own files without any
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
    are loaded on demand by the import machinery (exec'd here) and registered
    under the synthetic package name so ``torch.load``/``pickle`` resolve
    classes defined in them the same way they resolve the generated module.
    """
    package_name = _package_name_for(module_name)
    package = sys.modules.get(package_name)
    if package is None:
        package = ModuleType(package_name)
        package.__package__ = package_name
        package.__path__ = [os.path.abspath(artifact_dir)]
        package.__file__ = "<codegen-synthetic-package>"
        sys.modules[package_name] = package
    elif not hasattr(package, "__path__"):
        package.__path__ = [os.path.abspath(artifact_dir)]

    # Pre-register sibling .py files (the ``<remote>`` relative-import
    # closure, copied into the bundle by ``emit_bundle``) so their own
    # cross-references resolve inside the same package.  The generated
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
    # Pre-register sibling .py files (the ``<remote>`` relative-import
    # closure, copied into the bundle by ``emit_bundle``) so their own
    # cross-references resolve inside the same package.  The generated
    # modeling file is the one being imported — skip it.
    prefix = package_name + "."
    modeling_name = find_generated_modeling_file(artifact_dir)
    for name in sorted(os.listdir(artifact_dir)):
        if not name.endswith(".py") or name == "__init__.py":
            continue
        if name == os.path.basename(modeling_name):
            continue
        sibling_name = prefix + os.path.splitext(name)[0]
        if sibling_name in sys.modules:
            continue
        spec = importlib.util.spec_from_file_location(
            sibling_name, os.path.join(artifact_dir, name),
        )
        if spec is None or spec.loader is None:
            continue
        sibling = importlib.util.module_from_spec(spec)
        sibling.__package__ = package_name
        sys.modules[sibling_name] = sibling
        try:
            spec.loader.exec_module(sibling)
        except Exception:
            sys.modules.pop(sibling_name, None)
            raise


def resolve_generated_model_class(module: ModuleType, hf_config: Any) -> type:
    """Find the model class in ``module`` matching the config's architecture.

    The generated file keeps the original class names, so the architecture name
    from the HF config is the lookup key.
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241

    Returns:
        type: The model class to instantiate.
    """
    architectures = getattr(hf_config, "architectures", None) or []
    if not architectures:
        raise ValueError(
            "codegen: hf_config has no 'architectures'; cannot pick a model "
            f"class from {module.__name__}"
        )
    arch_name = architectures[0]
    model_cls = getattr(module, arch_name, None)
    if model_cls is None:
        available = sorted(
            name for name, value in vars(module).items() if isinstance(value, type)
        )
        raise AttributeError(
            f"codegen: generated module {module.__name__} has no class "
            f"{arch_name!r}; defined classes: {available}"
        )
    if not isinstance(model_cls, type):
        raise TypeError(
            f"codegen: {module.__name__}.{arch_name} is not a class"
        )
    return model_cls


def _register_generated_model_conversions(model: Any) -> None:
    """Let the generated model share the library's native weight conversions.

    The generated modeling file is loaded as custom code (its module name does
    not start with ``transformers.``), so ``PreTrainedModel.is_custom_code()``
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
    ``model_type`` gets no entry and registration is a no-op.  The lookup is by
    ``model_type`` (class-name lookup falls back to it), so registering the
    class name is not required for the root model.
    """
    from transformers.conversion_mapping import (  # pylint: disable=import-outside-toplevel
        USER_REGISTERED_MAPPINGS,
    )

    model_type = getattr(getattr(model, "config", None), "model_type", None)
    if model_type:
        # Guard against API drift across transformers versions: if the set is
        # renamed or removed the whole registration is skipped rather than
        # breaking the generated-model build.
        if getattr(USER_REGISTERED_MAPPINGS, "add", None):
            USER_REGISTERED_MAPPINGS.add(model_type)
            logger.info(
                "codegen: registered generated model_type %r for checkpoint "
                "conversion mapping", model_type,
            )
272
273
274
275
276
277
278
279
280
                "conversion mapping", model_type,
            )


def _load_generated_checkpoint(
    model_cls: type,
    pretrained_path: str,
    hf_config: Any,
    model_args: tuple,
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
    ``make_transforms()`` (scoped to its FQN) and publish the *pre-replacement*
    parameter shapes, then let ``CheckpointManager`` apply the conversions
    through the replacement-conversion route.
    """
    from hyper_parallel.models._transformers.checkpoint_loader import (  # pylint: disable=import-outside-toplevel
        CheckpointManager,
    )

    model = model_cls._from_config(hf_config, *model_args, **kwargs)
    _register_generated_model_conversions(model)

    transforms: list[Any] = []
    for module_fqn, module in model.named_modules():
        make_transforms = getattr(module, "make_transforms", None)
        if make_transforms is None:
            continue
        for transform in make_transforms():
            transform.scope_prefix = module_fqn
            transforms.append(transform)
    if transforms:
        model._hp_checkpoint_source_shapes = _source_model_shapes(hf_config)  # pylint: disable=protected-access
        model._hp_replacement_weight_conversions = transforms  # pylint: disable=protected-access

    # from_pretrained materializes meta tensors internally; _from_config
    # does not. Materialize the meta model before loading so checkpoint
    # copy targets are real tensors (init_device: meta in the training
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
    # from_pretrained materializes meta tensors internally; _from_config
    # does not. Materialize the meta model before loading so checkpoint
    # copy targets are real tensors (init_device: meta in the training
    # config). Downstream infra moves it to the accelerator afterwards.
    if any(p.is_meta for p in model.parameters()):
        model.to_empty(device="cpu")

    CheckpointManager(model).load_checkpoint(pretrained_path, strict=False)
    if torch_dtype not in (None, "auto"):
        _cast_parameters_to(model, torch_dtype)
    return model


def _cast_parameters_to(model: Any, torch_dtype: Any) -> None:
    """Cast the model's parameters to ``torch_dtype`` the way native loading does.

    ``model.to(dtype)`` also down-casts floating-point *buffers*, but the native
    ``from_pretrained(dtype=...)`` path only casts parameters: buffers such as
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
        model: Model whose parameters are cast in place.
        torch_dtype: Configured dtype, either ``"bfloat16"`` or
            ``"torch.bfloat16"``.
    """
    import torch  # pylint: disable=import-outside-toplevel

    dtype = getattr(torch, str(torch_dtype).rsplit(".", 1)[-1])
    keep_fp32 = tuple(getattr(model, "_keep_in_fp32_modules", None) or ())
    for name, parameter in model.named_parameters():
        if keep_fp32 and any(module_name in name for module_name in keep_fp32):
            continue
        if parameter.is_floating_point() and parameter.dtype != dtype:
            parameter.data = parameter.data.to(dtype)


def _source_model_shapes(hf_config: Any) -> dict[str, tuple[int, ...]]:
    """Capture the pre-replacement parameter shapes for the conversion route.

    The native path records ``_named_tensor_shapes(model)`` *before* swapping in
    the fused modules, so its converters are routed by the original parameter
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
    ``v_proj`` exist only as converter source patterns), so rebuild the original
    parameter tree from the config on the meta device -- no storage is
    allocated -- and publish its shapes instead.
    """
    import transformers  # pylint: disable=import-outside-toplevel
    from hyper_parallel import init_empty_weights  # pylint: disable=import-outside-toplevel
    from hyper_parallel.models.replacement import (  # pylint: disable=import-outside-toplevel
        _named_tensor_shapes,
    )

    with init_empty_weights():
        source = transformers.AutoModelForCausalLM.from_config(hf_config)
    return _named_tensor_shapes(source)


def init_generated_model(
    artifact_dir: str,
    pretrained_model_name_or_path: Optional[str],
    hf_config: Any,
    *model_args: Any,
405
406
407
408
409
410
411
412
413
414
415
416
417

    Returns:
        Any: The instantiated model with converted weights loaded.
    """
    module = import_generated_module(artifact_dir)
    model_cls = resolve_generated_model_class(module, hf_config)

    if pretrained_model_name_or_path is not None:
        model = _load_generated_checkpoint(
            model_cls,
            pretrained_model_name_or_path,
            hf_config,
            model_args,
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
        # so ``from_config`` no longer exists on it.  ``_from_config`` is the
        # canonical entry in both 5.14.x and the pinned 5.15.0; the AutoModel
        # family's ``from_config`` (which the native path borrows) is just a
        # resolver that ultimately calls ``_from_config`` anyway.
        model = model_cls._from_config(hf_config, *model_args, **kwargs)
        _register_generated_model_conversions(model)

    from hyper_parallel.codegen.check.preflight import (  # pylint: disable=import-outside-toplevel
        verify_param_plan,
    )
    from hyper_parallel.codegen.meta import (  # pylint: disable=import-outside-toplevel
        load_codegen_meta,
    )

    meta = load_codegen_meta(os.path.join(artifact_dir, "codegen_meta.json"))
    if meta is None:
        raise FileNotFoundError(
            f"codegen: missing meta at {os.path.join(artifact_dir, 'codegen_meta.json')}; "
            "the bundle is incomplete — regenerate the artifact"
        )
    verify_param_plan(meta, model)
    return model


__all__ = [
    "find_generated_modeling_file",
    "import_generated_module",
    "init_generated_model",
    "resolve_generated_model_class",
hyper_parallel/codegen/manager.py
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
    swaps in a fresh bundle (a late-starting rank reads the new meta and takes
    the reuse path while its peers hold the stale one and take the wait path);
    a rank that skips the barrier on reuse deadlocks the whole run.
    """
    if not getattr(config, "codegen", False):
        return None

    if hf_config is None:
        hf_config = _resolve_hf_config_for_layout(config)
    layout = _resolve_layout(config, yaml_path, artifact_dir, hf_config=hf_config)
    ensure_artifact_dir(layout)
    is_rank0 = _is_rank0()
    if is_rank0:
        clean_temp_artifacts(layout)

    signature = _compute_signature(config, layout, hf_config=hf_config)
    existing = load_codegen_meta(layout.meta_path)

    if existing is not None and not should_regenerate(existing, signature):
        if is_rank0:
            logger.info(
                "codegen: reuse artifact %s (signature %s)",
                layout.artifact_dir,
                signature,
            )
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
                signature,
            )
        # Ranks can disagree on reuse while rank0 swaps the bundle in; this
        # path must cross the barrier too, or the peers deadlock inside theirs.
        barrier_after_artifact()
        return layout

    if not is_rank0:
        wait_for_rank0_artifact(layout, signature)
        barrier_after_artifact()
        return layout

    # rank0 generates.
    logger.info(
        "codegen: generating artifact %s (signature %s)", layout.artifact_dir, signature
    )
    meta = _build_meta(config, layout, signature, hf_config=hf_config)
    _fill_plan_fields(meta, config, layout, hf_config=hf_config)
    _record_inline_declarations(meta)
    files = emit_bundle(layout, meta)
    write_bundle_atomic(layout, files, meta)
    barrier_after_artifact()
    return layout


def preflight_integrity_check(
    config: Any,
136
137
138
139
140
141
142
143
144
145
146
    consistency (an empty plan fails — generation always freezes a derived
    plan); full model-side coverage against the live parameter tree runs
    later in :func:`init_generated_model`, where a model handle exists.
    """
    if not getattr(config, "codegen", False):
        return
    from hyper_parallel.codegen.check.preflight import (
        verify_boundary_forms,
        verify_generated_import,
        verify_meta_required_fields,
        verify_output_hashes,
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
        verify_signature,
        warn_skipped_overrides,
    )

    if hf_config is None:
        hf_config = _resolve_hf_config_for_layout(config)
    layout = _resolve_layout(config, yaml_path, artifact_dir, hf_config=hf_config)
    meta = load_codegen_meta(layout.meta_path)
    if meta is None:
        raise FileNotFoundError(
            f"codegen preflight: missing meta at {layout.meta_path}; run generate first"
        )
    verify_meta_required_fields(meta)
    signature = _compute_signature(config, layout, hf_config=hf_config)
    verify_signature(meta, signature)
    verify_output_hashes(meta, layout)
    verify_generated_import(layout)
    verify_boundary_forms(meta, layout)
    verify_param_plan(meta, model=None)
    warn_skipped_overrides(meta)


def should_regenerate(meta: CodegenMeta, signature: str) -> bool:
    """Whether the stored meta no longer matches the current signature."""
    return meta.signature != signature


def wait_for_rank0_artifact(
    layout: ArtifactLayout,
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
    bundle's meta (written last inside the staging dir, then swapped in) lands.
    The meta file is written last, so a matching signature also means the
    modeling/diff/init files are already in place.
    """
    import time

    deadline = time.monotonic() + timeout_s
    while time.monotonic() < deadline:
        meta = load_codegen_meta(layout.meta_path)
        if meta is not None and meta.signature == current_signature:
            return
        time.sleep(0.5)
    raise TimeoutError(
        f"codegen: timed out waiting for rank0 artifact at {layout.artifact_dir}"
    )

219
220
221
222
223
224
225
226
227
228
229
230
    Uses ``torch.distributed`` directly (the process group was initialized in
    ``trainer._setup`` before the codegen manager runs); a no-op without a
    live process group.
    """
    try:
        import torch.distributed as dist

        if dist.is_initialized():
            dist.barrier()
    except Exception:  # pragma: no cover - best-effort without a process group
        pass

240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
    Training config is parsed before the process group is initialized, so a
    torchrun worker must use its ``RANK`` environment value. Other callers use
    the initialized process group when available and otherwise act as rank 0.
    """
    rank = os.environ.get("RANK")
    if rank is not None:
        try:
            return int(rank) == 0
        except ValueError as exc:
            raise ValueError(f"codegen: invalid RANK value {rank!r}") from exc
    try:
        from hyper_parallel.trainer.runtime.distributed import (
            get_global_rank_safe,
        )

        return get_global_rank_safe() == 0
    except Exception:
        return True


def _model_name(config: Any, hf_config: Any = None) -> Optional[str]:
    """Resolve the model name from an HF config or the YAML model target."""
    model_type = getattr(hf_config, "model_type", None)
    if model_type:
        return _model_type_to_modeling_token(str(model_type))
    target = getattr(config, "model", None)
    path = getattr(target, "_target_", None)
    if isinstance(path, str):
        name = path.rsplit(".", 1)[-1]
        if name.startswith(("AutoModel", "Pretrained", "from_")):
            return None
        return name
    return getattr(config, "model_name", None)


def _model_type_to_modeling_token(model_type: str) -> str:
    """Convert HF ``model_type`` into the generated modeling file token."""
    parts = [part for part in model_type.replace("-", "_").split("_") if part]
    if not parts:
        return "model"
    return "_".join(part[:1].upper() + part[1:] for part in parts)


def _model_class(config: Any, hf_config: Any = None) -> Optional[str]:
    """The entry model class name the override rewrite targets in ``__init__``.
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
    neither ``hf_config`` nor ``config`` exposes ``architectures``, resolve the
    model path with the same rule ``spec/project`` uses and load the config —
    lazily, to keep this module importable without ``transformers``.
    """
    for candidate in (
        getattr(hf_config, "architectures", None),
        getattr(config, "architectures", None),
    ):
        if isinstance(candidate, (list, tuple)) and candidate:
            return str(candidate[0])

    model = getattr(config, "model", None)
    path = getattr(model, "pretrained_model_name_or_path", None)
    if path is None:
        path = getattr(model, "model_name_or_path", None)
    if path is None:
        return None
    try:
        from hyper_parallel.models._transformers.config_resolver import get_hf_config
    except ImportError:
        return None
    try:
        arch = getattr(get_hf_config(path), "architectures", None)
    except Exception:
        return None
    if isinstance(arch, (list, tuple)) and arch:
        return str(arch[0])
    return None


def _resolve_layout(
    config: Any,
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
    *,
    hf_config: Any = None,
) -> ArtifactLayout:
    """Resolve either YAML-anchored or model-construction artifact layout."""
    model_name = _model_name(config, hf_config)
    if artifact_dir is not None:
        return resolve_default_artifact_layout(artifact_dir, model_name=model_name)
    model = getattr(config, "model", None)
    configured_dir = getattr(model, "codegen_artifact_dir", None)
    if configured_dir:
        return resolve_default_artifact_layout(str(configured_dir), model_name=model_name)
    if yaml_path is not None:
        return resolve_artifact_layout(str(yaml_path), model_name=model_name)
    configured_yaml_path = getattr(config, "_yaml_path", None)
    if configured_yaml_path:
        return resolve_artifact_layout(str(configured_yaml_path), model_name=model_name)
    return resolve_default_artifact_layout(model_name=model_name)


def _resolve_hf_config_for_layout(config: Any) -> Any:
    """Best-effort HF config lookup used only for artifact naming/layout."""
    model = getattr(config, "model", None)
    if model is None:
        return None
    path = getattr(model, "pretrained_model_name_or_path", None)
    if path is None:
        path = getattr(model, "model_name_or_path", None)
    if path is None:
        return None
    kwargs = _hf_config_kwargs(model)
    try:
        from hyper_parallel.models._transformers.config_resolver import get_hf_config
    except ImportError:
        return None
    try:
        return get_hf_config(
            path,
            getattr(model, "attn_implementation", "sdpa"),
            getattr(model, "torch_dtype", "auto"),
            **kwargs,
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
            getattr(model, "attn_implementation", "sdpa"),
            getattr(model, "torch_dtype", "auto"),
            **kwargs,
        )
    except Exception:
        return None


def _hf_config_kwargs(model: Any) -> dict[str, Any]:
    """Collect model target kwargs that affect ``AutoConfig.from_pretrained``."""
    names = (
        "cache_dir",
        "force_download",
        "local_files_only",
        "proxies",
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
        "token",
        "trust_remote_code",
        "use_auth_token",
    )
    kwargs = {
        name: getattr(model, name)
        for name in names
        if getattr(model, name, None) is not None
    }
    overrides = getattr(model, "config_overrides", None)
    if isinstance(overrides, dict):
        kwargs.update(overrides)
    return kwargs


def _compute_signature(
    config: Any,
407
408
409
410
411
412
413
414
415
416
    The full ``project_codegen_spec`` projection ensures the digest
    covers the same fields the plan/emit layers consume.  The YAML file bytes
    are deliberately excluded (see ``_project_spec``).
    """
    spec = _project_spec(config, layout, hf_config=hf_config)
    return signature_from_spec(spec)


def _project_spec(
    config: Any,
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
    projection already captures every codegen-affecting field, so an unrelated
    YAML edit (comment, logging, optimizer tweak) must not invalidate a
    bundle.
    """
    from hyper_parallel.codegen.spec.project import project_codegen_spec
    from hyper_parallel.codegen.source.resolver import resolve_model_source

    payload = project_codegen_spec(config, layout=layout).to_dict()
    model_id = payload.get("source", {}).get("model_name_or_path") or ""
    is_gen = _gen_backend(config)

    source = resolve_model_source(config, model_id, hf_config=hf_config)
    if source is not None:
        # Replace the SourceSpec projection with the resolved file identity;
        # the sha256 here is what makes a modeling-file change regenerate.
        # ``transformers_version`` is a SourceSpec field the pure projection
        # leaves empty; record the actual env version whenever we resolved a
442
443
444
445
446
447
448
449
450
        # ``transformers_version`` is a SourceSpec field the pure projection
        # leaves empty; record the actual env version whenever we resolved a
        # real file (an environment upgrade that changes the modeling file's
        # behavior must regenerate, not silently reuse).
        resolved = source.to_dict()
        # ``SourceSpec.to_dict`` keeps only architecture / model_type / sha256 —
        # neither the checkpoint identity nor its structural config.  Without
        # them, two checkpoints that share a modeling file but differ in shape
        # (e.g. the same architecture at different layer counts) would produce
450
451
452
453
454
455
456
457
458
459
460
461
462
463
        # (e.g. the same architecture at different layer counts) would produce
        # the same signature and silently reuse a frozen per-layer plan written
        # for the other shape.  Fold the model id and a structural-config digest
        # back in so a shape change invalidates the bundle.
        resolved["model_id"] = model_id
        if hf_config is not None:
            resolved["structure"] = _structure_summary(hf_config)
        payload["source"] = resolved
        payload["source"]["transformers_version"] = _transformers_version()
    elif is_gen:
        # codegen=True resolves to the ``gen`` backend, whose whole point is
        # producing an artifact FROM the original modeling file.  A bundle
        # generated from a guessed/unresolved source would be silently wrong,
        # so fail fast instead of degrading — the user must fix the model id,
461
462
463
464
465
466
467
468
469
        # producing an artifact FROM the original modeling file.  A bundle
        # generated from a guessed/unresolved source would be silently wrong,
        # so fail fast instead of degrading — the user must fix the model id,
        # install transformers, or make the checkpoint reachable.
        raise RuntimeError(
            f"codegen: cannot resolve modeling source for {model_id!r} — no "
            "AutoConfig, no installed transformers module, no remote code.  "
            "The gen backend generates the artifact from the original "
            "modeling file; fix the model id, install transformers, or make "
469
470
471
472
473
474
475
476
477
478
479
480
481
            "modeling file; fix the model id, install transformers, or make "
            "the checkpoint reachable"
        )

    payload["codegen_implementation"] = {
        "version": CODEGEN_VERSION,
        "sha256": _codegen_implementation_digest(),
    }
    identity = payload.get("source", {}).get("architecture")
    # The declarations a target resolves to are derived from structure (the
    # family's runtime factories + the framework archetype table), which live
    # outside the codegen implementation tree; summarizing them here folds
    # their content into the signature so a change to a factory's component or
479
480
481
482
483
484
485
486
487
488
489
490
491
    # family's runtime factories + the framework archetype table), which live
    # outside the codegen implementation tree; summarizing them here folds
    # their content into the signature so a change to a factory's component or
    # a strategy's body still regenerates an artifact.
    payload["inline_declarations"] = {
        "specs": declaration_summary(identity, payload.get("overrides")),
    }

    return payload


_STRUCTURE_FIELDS = (
    # Architectural (shape-defining) config fields.  A change in any of these
511
512
513
514
515
516
517
518
519


def _structure_summary(hf_config: Any) -> dict[str, Any]:
    """A stable digest of the config fields that define the model's structure."""
    return {
        name: getattr(hf_config, name)
        for name in _STRUCTURE_FIELDS
        if getattr(hf_config, name, None) is not None
    }
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
    The relative path is part of the digest so moving content between modules
    is treated as an implementation change. Non-Python files and bytecode
    caches are excluded because they do not define codegen semantics.
    """
    root = os.path.abspath(package_dir or os.path.dirname(__file__))
    digest = hashlib.sha256()
    for dirpath, dirnames, filenames in os.walk(root):
        dirnames[:] = sorted(name for name in dirnames if name != "__pycache__")
        for filename in sorted(filenames):
            if not filename.endswith(".py"):
                continue
            path = os.path.join(dirpath, filename)
            relative = os.path.relpath(path, root).replace(os.sep, "/")
            digest.update(relative.encode("utf-8"))
            digest.update(b"\0")
            with open(path, "r", encoding="utf-8", newline=None) as handle:
                digest.update(handle.read().encode("utf-8"))
            digest.update(b"\0")
    return digest.hexdigest()


def _build_meta(
    config: Any,
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
    (resolved ``ResolvedSource`` dict, or the ``SourceSpec`` projection when
    nothing resolved) so the recorded identity always matches what the
    signature covers.
    """
    spec = _project_spec(config, layout, hf_config=hf_config)
    from hyper_parallel.codegen.meta import (
        COVERED_DEFAULTS,
        NOT_COVERED_DEFAULT,
    )

    return CodegenMeta(
        codegen_version=CODEGEN_VERSION,
        signature=signature,
        yaml_path=layout.yaml_path,
        yaml_sha256=_config_sha256(spec, layout),
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
    artifact that still passes preflight (nothing verifies an empty
    ``param_plan``), so this stage raises and generation fails instead of
    silently continuing.
    """
    from hyper_parallel.codegen.plan.derive import (
        build_meta_model,
        build_plan_overrides,
        derive_sharding_plan,
    )
    from hyper_parallel.codegen.plan.freeze import freeze_plan
    from hyper_parallel.codegen.spec.project import project_codegen_spec

    spec = project_codegen_spec(config, layout=layout)
    model = build_meta_model(spec, config_overrides=_config_overrides(config), hf_config=hf_config)
    if model is None:
        raise RuntimeError(
            "codegen: cannot derive a sharding plan — the spec carries no "
            "model identity to build the meta model from; fix the model "
            "source in the YAML"
        )
    replace_meta = _apply_generation_replacements(meta, config, model, spec)
    plan_overrides = build_plan_overrides(config, spec)
    # S3: a plan override whose matched MoE boundary carries no explicit
    # ``local_compute_fn`` has its EP compute factory inferred from structure
    # (never by model name) and written into the spec so it lands in
    # ``meta.injections``. An explicit ``_target_`` stays the opt-out escape
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
    # ``local_compute_fn`` has its EP compute factory inferred from structure
    # (never by model name) and written into the spec so it lands in
    # ``meta.injections``. An explicit ``_target_`` stays the opt-out escape
    # hatch and is never overwritten.
    inferred_ep_targets = _fill_inferred_ep_targets(model, plan_overrides)
    plan = derive_sharding_plan(
        model,
        spec,
        plan_overrides=plan_overrides,
    )
    _drop_inferred_ep_injections_on_non_moe(plan, model, inferred_ep_targets)
    frozen = freeze_plan(plan, model)
    if not frozen.param_plan:
        raise RuntimeError(
            "codegen: plan derivation produced an empty param plan for "
            f"{spec.source.model_name_or_path!r} — the artifact would carry "
            "no sharding contract and pass preflight; check the generation "
            "logs for the planner failure and regenerate"
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
            f"{spec.source.model_name_or_path!r} — the artifact would carry "
            "no sharding contract and pass preflight; check the generation "
            "logs for the planner failure and regenerate"
        )
    meta.param_plan = frozen.param_plan
    meta.frozen_sharded_params = frozen.frozen_sharded_params
    meta.injections = frozen.injections
    meta.tied_pairs = frozen.tied_pairs
    meta.special_handlers = frozen.special_handlers
    meta.boundary_classes = frozen.boundary_classes
    meta.mesh_dim_names = list(frozen.mesh_dim_names) or None

    meta.module_overrides = replace_meta
    # The inline pipeline lowers the matched replacements into the generated
    # file, so the artifact owns ``replace_module``.  Declare coverage so the
    # trainer's HF replacement step is skipped on the gen path — but only when
    # something was actually matched.  ``covered`` is always a
653
654
655
656
657
658
659
660
661
662
663
664
665
666
    # file, so the artifact owns ``replace_module``.  Declare coverage so the
    # trainer's HF replacement step is skipped on the gen path — but only when
    # something was actually matched.  ``covered`` is always a
    # COVERED_DEFAULTS copy, so the key is present either way.
    meta.covered["module_overrides"] = bool(replace_meta)

    # A non-empty frozen plan is the artifact's full parallel logic; the
    # runtime shards from ``codegen_meta.json``.  ``covered`` is always a
    # COVERED_DEFAULTS copy, so the key is present either way.
    meta.covered["sharding_plan"] = True


def _record_inline_declarations(meta: CodegenMeta) -> None:
    """Record the facts the *runtime* also needs, derived from structure.
670
671
672
673
674
675
676
677
678
    by the injected targets' own resolved specs.  Recording them in ``meta``
    (the file the runtime loads at training time) is what keeps the runtime
    from carrying its own copy that drifts when a family adds a model.
    """
    identity = (meta.source or {}).get("architecture")
    meta.external_state_classes = list(
        external_state_classes_for(identity, getattr(meta, "injections", None))
    )

693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
    pre-replacement parameters (for example ``q_proj``/``k_proj``/``v_proj``)
    that no longer exist after a generated attention module installs fused
    ``qkv_proj`` weights.
    """
    raw_entries = getattr(config, "plan_overrides", None) or []
    if not raw_entries:
        return []

    from hyper_parallel.models.replacement import (
        _apply_module_replacement_actions,
    )
    from hyper_parallel.codegen.emit.replacement import (
        compile_overrides_for_meta,
    )
    from hyper_parallel.trainer.config import (
        entries_to_module_replacements,
    )

    specs = entries_to_module_replacements(raw_entries)
    factory_paths = []
    target_configs = []
    for entry in raw_entries:
        replace_module = getattr(entry, "replace_module", None)
        if replace_module is None:
            continue
        factory_paths.append(_target_path(replace_module))
        # Capture the YAML Target's static args so the runtime can rebuild the
        # same pre-bound factory (see ``_target_replacement_factory``) instead
        # of importing the bare ``@module_replacement`` fn — otherwise a
        # configured arg would fall back to its default or fail to build.
        target_configs.append(
            {
                name: value
                for name, value in replace_module.to_dict().items()
                if name != "_target_"
726
727
728
729
730
731
732
733
734
                for name, value in replace_module.to_dict().items()
                if name != "_target_"
            }
        )
    records = compile_overrides_for_meta(
        model,
        specs,
        factory_paths=factory_paths,
        configs_by_spec={
736
737
738
739
740
741
742
743
744
745
746
        },
    )
    # ``compile_overrides_for_meta`` already raised on any no-match rule (a
    # configuration error); every returned record here matched a live module.
    replace_meta = list(records)
    if replace_meta:
        model, _ = _apply_module_replacement_actions(
            model,
            specs,
            context={
                "low_precision": None,
750
751
752
753
754
755
756
757
                "pp": spec.parallel_dims.pp_size > 1,
            },
            capture_checkpoint_metadata=False,
        )
    return replace_meta


_STRUCTURAL_CONFIG_KEYS = ("num_hidden_layers", "num_layers", "use_cache")
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
    ``config_overrides`` is absent the recognized structural keys are folded in
    — otherwise the frozen plan would be derived at the source checkpoint's
    shape and disagree with the (cropped) model the builder constructs.
    """
    model = getattr(config, "model", None)
    overrides = getattr(model, "config_overrides", None)
    if overrides is None:
        overrides = {
            key: getattr(model, key)
            for key in _STRUCTURAL_CONFIG_KEYS
            if getattr(model, key, None) is not None
        }
    if not isinstance(overrides, dict):
        raise TypeError(
            "codegen: model.config_overrides must be a mapping of "
            f"AutoConfig keyword arguments, got {type(overrides).__name__}"
        )
    return dict(overrides)


def _target_path(target: Any) -> Optional[str]:
    """Return the serialized target path for a YAML Target-like object."""
    if target is None or not hasattr(target, "to_dict"):
        return None
    data = target.to_dict()
    return data.get("_target_") if isinstance(data, dict) else None


#: Structure fingerprint -> model-family MoE EP compute factory.
#:
804
805
806
807
808
809
810
811
812
813
814
815


def _build_ep_factory_table() -> dict[tuple[str, str, str], str]:
    """Project the framework archetype table into a fingerprint->path map."""
    from hyper_parallel.distributed.expert_parallel.archetypes import (  # pylint: disable=C0415
        moe_archetypes,
    )

    return {
        fingerprint: archetype.compute_factory
        for fingerprint, archetype in moe_archetypes().items()
    }
817
818
819
820
821
822
823
824
825
826
827

def _ep_factory_table() -> dict[tuple[str, str, str], str]:
    """Return the cached fingerprint->factory ``_target_`` map."""
    global _EP_FACTORY_BY_STRUCTURE  # pylint: disable=global-statement
    if _EP_FACTORY_BY_STRUCTURE is None:
        _EP_FACTORY_BY_STRUCTURE = _build_ep_factory_table()
    return _EP_FACTORY_BY_STRUCTURE


def _fill_inferred_ep_targets(model: Any, plan_overrides: dict[str, Any]) -> set[str]:
    """S3: fill a missing ``local_compute_fn`` on MoE EP overrides.
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
        framework chose itself, which the plan build must keep off boundaries
        that cannot host experts (see
        ``_drop_inferred_ep_injections_on_non_moe``).
    """
    inferred: set[str] = set()
    for match, spec in plan_overrides.items():
        if getattr(spec, "local_compute_fn", None) is not None:
            continue
        target = _infer_ep_compute_for_match(model, match)
        if target is not None:
            spec.local_compute_fn = target
            path = _target_path(target)
            if path:
                inferred.add(path)
    return inferred


def _infer_ep_compute_for_match(
    model: Any, match: str
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
    ``UnsupportedModuleStructure`` so the artifact never silently picks a wrong
    strategy — the escape-hatch text points the user at writing an explicit
    ``_target_``.
    """
    candidate = None
    candidate_path = None
    for fqn, module in model.named_modules():
        if not fnmatchcase(fqn, match):
            continue
        if not _looks_like_moe_boundary(module):
            continue
        target = _import_ep_target_for(module, match)
        # Compare the serialized factory path, never the Target objects: every
        # ``_build_ep_target`` call wraps a fresh closure and ``Target`` defines no
        # equality, so identity would make any model with two MoE boundaries look
        # ambiguous even when they share one factory.
        path = _target_path(target)
        if candidate is None:
            candidate, candidate_path = target, path
        elif path != candidate_path:
            from hyper_parallel.distributed.expert_parallel.structure import (  # pylint: disable=C0415
                UnsupportedModuleStructure,
            )

            raise UnsupportedModuleStructure(
                f"{match!r} matched multiple MoE boundaries with different "
                "EP compute factories; declare an explicit "
                "local_compute_fn._target_ in the YAML"
            )
    return candidate


def _drop_inferred_ep_injections_on_non_moe(
    plan: Any, model: Any, inferred_targets: set[str]
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939

    Only targets the framework inferred are affected: a user-declared
    ``local_compute_fn`` stays the escape hatch and is never dropped.
    """
    if not inferred_targets:
        return
    for fqn, spec in (getattr(plan, "modules", {}) or {}).items():
        injection = getattr(spec, "local_compute_fn", None)
        if injection is None or _target_path(injection) not in inferred_targets:
            continue
        module = model.get_submodule(fqn) if model is not None else None
        if module is None or _looks_like_moe_boundary(module):
            continue
        logger.info(
            "codegen: inferred EP injection dropped on %s — %s carries no "
            "experts/router, so it cannot be an expert-parallel region",
            fqn, type(module).__name__)
        spec.local_compute_fn = None
        spec.region_dispatch = None


def _looks_like_moe_boundary(module: Any) -> bool:
    """Cheap gate: does the module carry an expert plus a router gate?"""
    return hasattr(module, "experts") and (
        hasattr(module, "gate") or hasattr(module, "router")
    )

939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960


def _import_ep_target_for(module: Any, match: str) -> "Any":
    """Import the delayed target bound to the structure-inferred factory."""
    from hyper_parallel.distributed.expert_parallel.structure import (  # pylint: disable=C0415
        UnsupportedModuleStructure,
        detect_moe_structure,
    )

    structure = detect_moe_structure(module)
    fingerprint = (
        structure.router,
        structure.shared_experts,
        structure.expert_storage,
    )
    factory_path = _ep_factory_table().get(fingerprint)
    if factory_path is None:
        raise UnsupportedModuleStructure(
            f"{match!r} matched {type(module).__name__} with structure "
            f"{fingerprint} that has no EP compute factory. Declare an "
            "explicit local_compute_fn._target_ to opt out of structure "
            "detection."
958
959
960
961
962
963
964
965
966
            f"{fingerprint} that has no EP compute factory. Declare an "
            "explicit local_compute_fn._target_ to opt out of structure "
            "detection."
        )
    return _build_ep_target(factory_path)


def _build_ep_target(factory_path: str) -> "Any":
    """Build a delayed ``Target`` for the EP factory at ``factory_path``.
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
    only the ``_target_`` path into meta, and the runtime re-resolves it from
    that path. Deferring the import keeps codegen free of a hard dependency on
    any concrete model package.
    """
    from hyper_parallel.trainer.config.target import Target  # pylint: disable=C0415

    def _factory(**kwargs: Any) -> Any:
        import importlib  # pylint: disable=C0415

        module_path, _, attr = factory_path.rpartition(".")
        fn = getattr(importlib.import_module(module_path), attr)
        return fn(**kwargs)

    return Target(_factory, target_path=factory_path)


def _gen_backend(config: Any) -> bool:
    """Whether the codegen pipeline is active for this config.
991
992
993
994
995
996
997
998
999
    backend (a ``force_hf`` run still generates), so this is simply the
    codegen switch — and those are exactly the runs that must fail fast
    when the modeling source cannot be resolved.
    """
    return bool(getattr(config, "codegen", False))


def _transformers_version() -> str:
    """Installed transformers version, or ``""`` when not importable.
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020

    Folded into the signature/meta so an environment upgrade that changes the
    modeling file's behavior regenerates instead of silently reusing.
    """
    try:
        import transformers

        return getattr(transformers, "__version__", "")
    except ImportError:
        return ""


def _config_sha256(spec: dict, layout: ArtifactLayout) -> str:
    """Return YAML file hash when available, otherwise hash the projected spec."""
    if layout.yaml_path:
        return sha256_file(layout.yaml_path)
    return hashlib.sha256(canonical_json(spec).encode("utf-8")).hexdigest()


__all__ = [
    "CODEGEN_VERSION",
hyper_parallel/codegen/meta.py
115
116
117
118
119
120
121
122
123
124
125
126
127
128


def meta_to_dict(meta: CodegenMeta) -> dict[str, Any]:
    """Serialize a CodegenMeta to a JSON-friendly dict."""
    return asdict(meta)


def meta_from_dict(data: dict[str, Any]) -> CodegenMeta:
    """Rebuild a CodegenMeta from a JSON dict."""
    validate_meta_schema(data)
    return CodegenMeta(
        **{k: v for k, v in data.items() if k in CodegenMeta.__dataclass_fields__}
    )

128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198


def load_codegen_meta(meta_path: str) -> Optional[CodegenMeta]:
    """Read the meta file; ``None`` if absent or malformed."""
    if not os.path.isfile(meta_path):
        return None
    try:
        with open(meta_path, "r", encoding="utf-8") as handle:
            return meta_from_dict(json.load(handle))
    except (json.JSONDecodeError, TypeError, ValueError, KeyError):
        return None


def write_codegen_meta(meta: CodegenMeta, path: str) -> None:
    """Write the meta as pretty-printed JSON."""
    with open(path, "w", encoding="utf-8") as handle:
        json.dump(meta_to_dict(meta), handle, indent=2, sort_keys=True)


def validate_meta_schema(data: dict[str, Any]) -> None:
    """Fail fast if required meta fields are missing or mistyped."""
    required = ("codegen_version", "signature", "yaml_path", "yaml_sha256")
    for key in required:
        if key not in data:
            raise ValueError(f"codegen_meta missing required field {key!r}")
        if not isinstance(data[key], str):
            raise TypeError(f"codegen_meta field {key!r} must be a string")
    for key in ("source", "parallel_dims", "outputs"):
        if key in data and not isinstance(data[key], dict):
            raise TypeError(f"codegen_meta field {key!r} must be a dict")
    if "entrypoints" in data:
        entrypoints = data["entrypoints"]
        if not isinstance(entrypoints, dict) or not all(
            isinstance(k, str) and isinstance(v, str) for k, v in entrypoints.items()
        ):
            raise TypeError("codegen_meta field 'entrypoints' must be a dict[str, str]")
    if "covered" in data:
        covered = data["covered"]
        if not isinstance(covered, dict):
            raise TypeError("codegen_meta field 'covered' must be a dict")
        if not isinstance(covered.get("sharding_plan"), bool):
            raise TypeError("codegen_meta field 'covered.sharding_plan' must be a bool")
    if "not_covered" in data:
        not_covered = data["not_covered"]
        if not isinstance(not_covered, list) or not all(
            isinstance(item, str) for item in not_covered
        ):
            raise TypeError("codegen_meta field 'not_covered' must be a list[str]")
    if "module_overrides" in data:
        overrides = data["module_overrides"]
        if not isinstance(overrides, list) or not all(
            isinstance(item, dict) for item in overrides
        ):
            raise TypeError(
                "codegen_meta field 'module_overrides' must be a list[dict]"
            )
    if "remote_siblings" in data:
        siblings = data["remote_siblings"]
        if not isinstance(siblings, list) or not all(
            isinstance(item, str) for item in siblings
        ):
            raise TypeError("codegen_meta field 'remote_siblings' must be a list[str]")
    if "external_state_classes" in data:
        class_names = data["external_state_classes"]
        if not isinstance(class_names, list) or not all(
            isinstance(item, str) for item in class_names
        ):
            raise TypeError(
                "codegen_meta field 'external_state_classes' must be a list[str]"
            )

218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
    generation.  The meta file's integrity is covered by the signature check,
    the schema validation, and the hashes of every other bundle file — it does
    not need a self-hash.
    """
    output_dir = base_dir or layout.artifact_dir
    role_by_name = {
        os.path.basename(layout.modeling_path): "modeling",
        os.path.basename(layout.diff_path): "diff",
        os.path.basename(layout.init_path): "init",
    }
    meta.outputs = {}
    for name in sorted(os.listdir(output_dir)):
        target = os.path.join(output_dir, name)
        if name == os.path.basename(layout.meta_path) or not os.path.isfile(target):
            continue
        key = role_by_name.get(name, name)
        meta.outputs[key] = {
            "sha256": sha256_file(target),
            "lines": file_line_count(target),
        }
    return meta


__all__ = [
    "CodegenMeta",
hyper_parallel/codegen/modeling_backend.py
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
    """
    if force_hf:
        resolved = ModelingBackend.HF
    elif codegen_enabled:
        resolved = ModelingBackend.GEN
    elif is_custom_model_available(hf_config):
        resolved = ModelingBackend.CUSTOM
    else:
        resolved = ModelingBackend.HF

    if force_hf and codegen_enabled:
        logger.warning(
            "codegen is enabled but force_hf=True, so the generated modeling "
            "file will not be used; drop force_hf to build from the artifact",
        )
    return resolved
hyper_parallel/codegen/plan/boundary_forms.py
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
    # entry/exit redistributes; the sides are still classified below so the
    # emitter's region template can annotate each transition.  An
    # unlowerable transition does NOT force the generic form there — the
    # compiled plan's redistribute owns it either way.
    is_region = injection is not None and injection.get("local_compute_fn") is not None
    # An inner-wrapper boundary keeps the generic rewrite even when every
    # transition is identity: ``hp_apply_inner_wrapper`` mutates the target
    # forward at runtime, and the P1 contract (wrapper weaves into the
    # rewritten redistribute forward) must not change under pruning.
    inner_wrap = injection is not None and injection.get("inner_wrapper") is not None

    axes = tuple(mesh_dim_names or ())
    if not axes:
        # No active sharding axes: the runtime routes entries whose ``ep``
        # placement actually changes to the expert mesh (a runtime object —
        # not statically decidable) and treats everything else as an exact
        # no-op passthrough.
        if is_region:
            return BoundaryForm(form=FORM_REGION)
        if inner_wrap or _entry_has_ep_placement(entry):
            return BoundaryForm(form=FORM_GENERIC)
        return BoundaryForm(form=FORM_IDENTITY)

    in_ops: dict[str, TransitionOp] = {}
    in_notes: dict[str, str] = {}
    in_declared: list[str] = []
    in_fallback = False
    for name, result, note in _classify_side(entry, "in_src", "in_dst", axes):
        in_declared.append(name)
        if note is not None:
            in_notes[name] = note
        if result is None:
            # An unlowerable transition anywhere on the side forces the
            # generic form (the compiled plan's DTensor fallback owns it).
            in_fallback = True
        elif result["kind"] != "identity":
            in_ops[name] = TransitionOp(
                kind=result["kind"], tensor_dim=result.get("tensor_dim")
            )

    out_ops: dict[str, TransitionOp] = {}
    out_notes: dict[str, str] = {}
    out_fallback = False
    for name, result, note in _classify_side(entry, "out_src", "out_dst", axes):
        if note is not None:
            out_notes[name] = note
        if result is None:
            out_fallback = True
        elif result["kind"] != "identity":
            out_ops[name] = TransitionOp(
                kind=result["kind"], tensor_dim=result.get("tensor_dim")
            )

    entry_out_names = declared_out_names(entry)
    if is_region:
        return BoundaryForm(
            form=FORM_REGION,
            in_ops=in_ops,
            out_ops=out_ops,
            in_notes=in_notes,
186
187
188
189
190
191
192
193
194
195
            out_notes=out_notes,
            in_declared=tuple(in_declared),
            out_names=entry_out_names,
        )
    if in_fallback or out_fallback or inner_wrap:
        return BoundaryForm(
            form=FORM_GENERIC,
            in_ops=in_ops,
            out_ops=out_ops,
            in_notes=in_notes,
196
197
198
199
200
201
202
203
204
205
            out_notes=out_notes,
            in_declared=tuple(in_declared),
            out_names=entry_out_names,
        )
    if not in_ops and not out_ops:
        return BoundaryForm(
            form=FORM_IDENTITY,
            in_ops=in_ops,
            out_ops=out_ops,
            in_notes=in_notes,
206
207
208
209
210
211
212
213
214
            out_notes=out_notes,
            in_declared=tuple(in_declared),
            out_names=entry_out_names,
        )
    return BoundaryForm(
        form=FORM_TP_COLLECTIVE,
        in_ops=in_ops,
        out_ops=out_ops,
        in_notes=in_notes,
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
    line for non-identity transitions (``None`` for identity / no-op names).
    """
    # Lazy imports: plan modules must not pull the distributed package (and
    # its torch-dependent __init__) at codegen import time.
    from hyper_parallel.codegen.plan.freeze import parse_named_placement
    from hyper_parallel.distributed.recipe_spec import resolve_placements
    from hyper_parallel.distributed._builder.tp_collective_lowering import (
        classify_tp_transition,
    )

    src_named = parse_named_placement(entry.get(src_field) or {})
    dst_named = parse_named_placement(entry.get(dst_field) or {})
    src_raw = entry.get(src_field) or {}
    dst_raw = entry.get(dst_field) or {}
    for name in sorted(set(src_named) | set(dst_named)):
        src_p = tuple(resolve_placements(src_named.get(name) or {}, axes))
        dst_p = tuple(resolve_placements(dst_named.get(name) or {}, axes))
        result = classify_tp_transition(src_p, dst_p, axes)
        note = _transition_note(
            name, src_raw.get(name) or {}, dst_raw.get(name) or {}, axes, result
        )
        yield name, result, note


def _transition_note(
    name: str,
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
    dropped by the dense-mesh compile, so claiming it here would lie); the
    raw frozen strings are shown verbatim — the annotation is the plan's own
    declaration, not a re-interpretation.
    """
    diffs = [
        f"{axis} {src_raw.get(axis, 'R')} -> {dst_raw.get(axis, 'R')}"
        for axis in axes
        if src_raw.get(axis, "R") != dst_raw.get(axis, "R")
    ]
    if not diffs:
        return None
    head = f"{name}: " + ", ".join(diffs)
    if result is None:
        return f"{head} ==> dtensor redistribute"
    return f"{head} ==> {_op_text(result)}"


def _op_text(result: dict) -> str:
    """Compact text of one classified op, as it appears in annotations/calls."""
    kind = result["kind"]
    if kind == "all_reduce":
        return "all_reduce(sum)"
    return f"{kind}(dim={result['tensor_dim']})"


def _entry_has_ep_placement(entry: dict[str, Any]) -> bool:
    """Whether any declared name's ``ep`` placement actually changes.
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
    generic form on boundaries whose semantics are an exact no-op, while the
    install path is skipped entirely for such meshes — the rewritten
    forward's ``_hp_boundary`` reference would never be bound.
    """
    for src_field, dst_field in _SIDE_FIELDS:
        src_named = entry.get(src_field) or {}
        dst_named = entry.get(dst_field) or {}
        for name in sorted(set(src_named) | set(dst_named)):
            if _ep_axis(src_named.get(name)) != _ep_axis(dst_named.get(name)):
                return True
    return False


def _ep_axis(placement: Any) -> str:
    """One named placement's canonical ``ep`` value (absent / non-dict = ``R``)."""
    if not isinstance(placement, dict):
        return "R"
    value = placement.get("ep")
    return value if isinstance(value, str) and value else "R"


def declared_out_names(entry: dict[str, Any]) -> tuple[str, ...]:
    """The entry's declared output order (``out_names`` or ``out_src`` keys).
322
323
324
325
326
327
328
329
330
331
332
    Output names are not retained on the frozen entry, so a local-region
    forward wraps its local results per ``out_src`` before the boundary exit
    and reads the declared order off the same place the rewrap does.
    """
    declared = entry.get("out_src") or {}
    out_names = entry.get("out_names") or declared.keys()
    return tuple(out_names)


def is_boundary_entry(entry: Any) -> bool:
    """Whether a frozen param-plan entry is a boundary.
337
338
339
340
341
342
343
344
345
346
347
348
349
    ``is_boundary`` wins over that inference.  Two copies of this predicate
    had drifted apart -- they disagreed on an entry carrying only
    ``out_names`` -- so both consumers read it from here.
    """
    if not isinstance(entry, dict):
        return False
    if entry.get("is_boundary") is not None:
        return bool(entry["is_boundary"])
    return any(entry.get(field) for field in _ENTRY_META_FIELDS)


#: Frozen-entry keys that make an entry a boundary: the in/out placement
#: contracts plus the declared output order a local-region forward wraps by.
hyper_parallel/codegen/plan/derive.py
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62


def _custom_model_cls(spec: CodegenSpec, hf_config: Any) -> Optional[type]:
    """Resolve a custom-registered model class for the spec/config, if any."""
    try:
        from hyper_parallel.models.registry import (  # pylint: disable=C0415
            _resolve_custom_model_cls,
            get_model_adapter,
        )
    except ImportError:
        return None
    candidates = (
        list(getattr(hf_config, "architectures", None) or [])
        if hf_config is not None else []
    )
    spec_arch = getattr(getattr(spec, "source", None), "architecture", None)
    if spec_arch:
        candidates.append(spec_arch)
    for arch in candidates:
        if not arch:
            continue
        if _resolve_custom_model_cls(arch) is None:
            # Lazy family discovery: either spelling may be the key that
            # registers the family (mirrors get_is_hf_model's identity loop).
            identities = [arch]
            if hf_config is not None:
                identities.append(getattr(hf_config, "model_type", "") or "")
            for identity in identities:
                if identity:
                    get_model_adapter(identity)
        cls = _resolve_custom_model_cls(arch)
        if cls is not None:
            return cls
    return None


def build_meta_model(
    spec: CodegenSpec,
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
    then skips plan derivation entirely).  A model that *is* configured but
    cannot be built is a hard failure — swallowing it would freeze an empty
    plan and let an incomplete artifact pass preflight.
    """
    model_id = spec.source.model_name_or_path
    if not model_id:
        return None
    try:
        from hyper_parallel import init_empty_weights

        custom_cls = _custom_model_cls(spec, hf_config)
        if custom_cls is not None:
            # A custom-registered architecture is unknown to transformers
            # AutoModel; build it the way the trainer's CUSTOM/GEN backends
            # do — from the resolved config, which also carries any crop.
            if hf_config is None:
                raise ValueError(
                    "codegen: custom-registered model "
                    f"{spec.source.architecture or model_id!r} needs the "
                    "resolved config (hf_config) to build the meta model; "
                    "AutoConfig cannot load it from the model id"
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
                    f"{spec.source.architecture or model_id!r} needs the "
                    "resolved config (hf_config) to build the meta model; "
                    "AutoConfig cannot load it from the model id"
                )
            with init_empty_weights():
                return custom_cls(hf_config)

        from transformers import AutoConfig, AutoModelForCausalLM

        config = AutoConfig.from_pretrained(model_id, **dict(config_overrides or {}))
        with init_empty_weights():
            return AutoModelForCausalLM.from_config(config)
    except Exception as exc:  # noqa: BLE001 - fail fast: an unbuildable source must not yield a plan-less artifact
        logger.error(
            "codegen: cannot build meta model for %r (%s: %s)",
            model_id, type(exc).__name__, exc,
        )
        raise


def build_plan_overrides(config: Any, spec: CodegenSpec) -> dict[str, Any]:
    """Desugar ``config.plan_overrides`` into the planner's override dict.
117
118
119
120
121
122
123
124
125
126
127
128
129
130
    codegen package must stay importable without the trainer layer).
    ``when``-gated entries are skipped here against the projected dims, the
    same way the trainer's own desugar pass applies them at run time.
    """
    entries = getattr(config, "plan_overrides", None) or []
    if not entries:
        return {}
    from hyper_parallel.trainer.config import entries_to_plan_overrides

    return entries_to_plan_overrides(
        entries,
        cp_size=spec.parallel_dims.cp_size,
        ep_size=spec.parallel_dims.ep_size,
    )
147
148
149
150
151
152
153
154
155
156
157
158
159
160
    live config.  ``allow_uncovered_params`` stays at its default (False):
    the F4b fail-fast guard must fire here, at generation time, not silently
    inside the trainer.
    """
    parallel_dims = spec.parallel_dims
    mesh = build_offline_mesh(parallel_dims)
    from hyper_parallel.distributed._builder.planner import ShardingPlanner

    planner = ShardingPlanner(plan_overrides=dict(plan_overrides or {}))
    return planner.plan(
        model,
        mesh,
        tp_size=parallel_dims.tp_size,
        cp_size=parallel_dims.cp_size,
hyper_parallel/codegen/plan/freeze.py
40
41
42
43
44
45
46
47
48
49

class _StridedShardSerializer:
    @staticmethod
    def to_string(p) -> str:
        dim = p.dim if not p.uneven_shard else -p.dim - 1
        return f"SS({dim},{p.split_factor})"


class _ShardSerializer:
    @staticmethod
47
48
49
50
51
52
53
54
55
56

class _ShardSerializer:
    @staticmethod
    def to_string(p) -> str:
        dim = p.dim if not p.uneven_shard else -p.dim - 1
        return f"S({dim})"


class _ReplicateSerializer:
    @staticmethod
54
55
56
57
58
59
60
61
62

class _ReplicateSerializer:
    @staticmethod
    def to_string(p) -> str:
        return "R"


class _PartialSerializer:
    @staticmethod
60
61
62
63
64
65
66
67
68

class _PartialSerializer:
    @staticmethod
    def to_string(p) -> str:
        return f"P({p.reduce_op})"


def _register_placement_types() -> None:
    """Register the real placement classes against their serializers.
74
75
76
77
78
79
80
81
82
    Order matters: ``StridedShard`` subclasses ``Shard``, so it must be
    registered FIRST or ``isinstance`` dispatch would serialize every
    strided shard as a plain ``Shard``.
    """
    from hyper_parallel.core.dtensor.placement_types import (
        Partial,
        Replicate,
        Shard,
        StridedShard,
81
82
83
84
85
86
87
88
89
        Shard,
        StridedShard,
    )

    for cls, serializer in (
        (StridedShard, _StridedShardSerializer),
        (Shard, _ShardSerializer),
        (Replicate, _ReplicateSerializer),
        (Partial, _PartialSerializer),
87
88
89
90
91
92
93
94
95
        (Shard, _ShardSerializer),
        (Replicate, _ReplicateSerializer),
        (Partial, _PartialSerializer),
    ):
        _PLACEMENT_SERIALIZERS[cls] = serializer


def placement_to_string(placement: Any) -> str:
    """Serialize a ``Placement`` instance to its canonical string form.
 99
100
101
102
103
104
105
106
107
108
109
110
111
    encoded as a negative dim (``Shard(-dim-1)``) so it survives the
    round-trip without an extra field.  Unknown placement types fail fast —
    a silently dropped placement would corrupt the frozen plan.
    """
    if not _PLACEMENT_SERIALIZERS:
        _register_placement_types()
    for cls, serializer in _PLACEMENT_SERIALIZERS.items():
        if isinstance(placement, cls):
            return serializer.to_string(placement)
    raise TypeError(
        f"cannot serialize placement {placement!r} of unknown type "
        f"{type(placement).__name__}"
    )
125
126
127
128
129
130
131
132
133
134
    recursed until a leaf ``Placement`` (or a plain scalar) is reached.
    Axis keys are normalized to strings and sorted for stability; a
    ``None`` value (unset) serializes to an empty dict.
    """
    if not named:
        return {}
    return {
        str(getattr(axis, "value", axis)): _value_to_dict(placement)
        for axis, placement in named.items()
    }
146
147
148
149
150
151
152
153
154
    (``S(-1)`` -> ``Shard(0, uneven_shard=True)``).  Unknown or malformed
    forms fail fast — a silently downgraded placement would corrupt the
    frozen plan (e.g. quietly becoming ``Replicate()``).
    """
    from hyper_parallel.core.dtensor.placement_types import (
        Partial,
        Replicate,
        Shard,
        StridedShard,
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
        Shard,
        StridedShard,
    )

    if not isinstance(text, str) or not text:
        raise ValueError(f"cannot parse placement from {text!r}")

    if text == "R":
        return Replicate()

    match = re.fullmatch(r"([SP]|SS)\((.*)\)", text)
    if match is None:
        raise ValueError(
            f"unknown placement form {text!r} (expected S(dim) / R / "
            f"P(reduce_op) / SS(dim,factor))"
        )
    kind, args = match.groups()

    if kind == "P":
        reduce_op = args
        if not reduce_op:
            raise ValueError(f"malformed partial placement {text!r}: missing reduce_op")
        return Partial(reduce_op)

    parts = [part.strip() for part in args.split(",")]
    try:
        dim = int(parts[0])
    except (IndexError, ValueError) as exc:
        raise ValueError(f"malformed placement {text!r}: invalid dim") from exc
    uneven = dim < 0
    dim = -dim - 1 if uneven else dim

    if kind == "S":
        if len(parts) != 1:
            raise ValueError(f"malformed shard placement {text!r}: too many args")
        return Shard(dim, uneven_shard=uneven)
    if kind == "SS":
        if len(parts) != 2:
            raise ValueError(f"malformed strided shard {text!r}: expected SS(dim,factor)")
        try:
            split_factor = int(parts[1])
        except ValueError as exc:
            raise ValueError(
                f"malformed strided shard {text!r}: invalid split_factor"
            ) from exc
        return StridedShard(dim, split_factor, uneven_shard=uneven)
    raise ValueError(f"unknown placement kind {kind!r} in {text!r}")


def _value_from_dict(value: Any) -> Any:
    """Inverse of :func:`_value_to_dict`: JSON-safe value -> planner-ish value.
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
    serialized via ``to_dict()`` are NOT reconstructed — the frozen boundary
    contract is data, and the generated module resolves runtime targets
    itself.
    """
    if isinstance(value, str):
        return parse_placement(value)
    if isinstance(value, dict):
        return {str(getattr(k, "value", k)): _value_from_dict(v) for k, v in value.items()}
    if isinstance(value, list):
        return [_value_from_dict(v) for v in value]
    return value


def parse_named_placement(data: dict[str, Any]) -> Any:
    """Inverse of :func:`named_placement_to_dict`: JSON dict -> named placement.
222
223
224
225
226
227
228
229
230
231
232

    Round-trips ``named_placement_to_dict()`` output back to a
    ``NamedPlacement`` / output-contract dict with real ``Placement`` leaves.
    """
    if not data:
        return {}
    return _value_from_dict(data)


# ---------------------------------------------------------------------------
# FrozenPlan
275
276
277
278
279
280
281
282
283
284
285
    boundary FQN's owning class (``boundary_classes``). ``model``
    may be ``None`` when only the plan shape is frozen (class names are then
    left unresolvable — the manager passes a live model).
    """
    if plan is None:
        return FrozenPlan()
    return FrozenPlan(
        param_plan=freeze_param_plan(plan),
        injections=freeze_injections(plan),
        tied_pairs=freeze_tied_pairs(plan),
        special_handlers=dict(getattr(plan, "special_handlers", {}) or {}),
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
    every ``plan.modules`` key (``model.layers.0.self_attn``), record the type
    of the submodule at that FQN.  ``get_submodule("")`` returns the root
    module and yields the model class itself.
    """
    if model is None:
        return {}
    modules = getattr(plan, "modules", {}) or {}
    result: dict[str, str] = {}
    for fqn in modules:
        cls = type(model.get_submodule(fqn)).__name__
        result[fqn] = cls
    return result


def freeze_param_plan(plan: Any) -> dict[str, dict[str, Any]]:
    """Freeze the per-boundary parameter sharding plan.
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
    Which fields are frozen (and how) is declared in
    :mod:`hyper_parallel.codegen.plan.spec_fields` — the table both sides
    of the meta boundary walk, so freeze and rebuild cannot drift apart.
    """
    modules = getattr(plan, "modules", {}) or {}
    result: dict[str, dict[str, Any]] = {}
    for fqn, spec in modules.items():
        entry: dict[str, Any] = {}
        for spec_field in PARAM_PLAN_SPEC_FIELDS:
            frozen = _freeze_spec_field(spec_field, getattr(spec, spec_field.name, None))
            if frozen is not _MISSING:
                entry[spec_field.key] = frozen
        result[fqn] = entry
    return result


#: Sentinel distinguishing "field absent from the frozen entry" from a
#: legitimately frozen ``None``/empty value (``{"params": {}}``).
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
    ``params`` uses ``is not None`` so an explicit empty dict ("shards
    nothing") survives — a truthy check here used to drop it and leave the
    rebuilt spec with ``params=None``, which Phase A cannot iterate.
    """
    kind = spec_field.freeze
    if kind in ("placement_map", "named_placements", "scalar",
                "value_to_dict", "plain", "attr_list"):
        if value is None:
            return _MISSING
    elif not value:  # name_list / dict_copy / int_truthy: truthy to write
        return _MISSING
    if kind == "placement_map":
        return {
            name: {
                str(getattr(axis, "value", axis)): placement_to_string(placement)
                for axis, placement in named.items()
            }
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
                for axis, placement in named.items()
            }
            for name, named in value.items()
        }
    if kind == "named_placements":
        return named_placement_to_dict(value)
    if kind in ("name_list", "attr_list"):
        return list(value)
    if kind == "value_to_dict":
        return _value_to_dict(value)
    if kind == "dict_copy":
        return dict(value)
    # scalar / plain / int_truthy: the value is already JSON-safe.
    return value


def freeze_injections(plan: Any) -> list[dict[str, Any]]:
    """Freeze per-boundary injection declarations.
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
    the meta boundary — the generated file re-resolves them from the same
    runtime registry).  Field set and write conditions come from
    :mod:`hyper_parallel.codegen.plan.spec_fields`.
    """
    modules = getattr(plan, "modules", {}) or {}
    injections: list[dict[str, Any]] = []
    for fqn, spec in modules.items():
        entry: dict[str, Any] = {}
        for spec_field in INJECTION_SPEC_FIELDS:
            frozen = _freeze_spec_field(spec_field, getattr(spec, spec_field.name, None))
            if frozen is not _MISSING:
                entry[spec_field.key] = frozen
        if entry:
            injections.append({"match": fqn, **entry})
    return injections


def freeze_tied_pairs(plan: Any) -> list[list[str]]:
    """Freeze tied-weight pairs as stable string lists."""
    pairs = getattr(plan, "tied_pairs", None) or []
    return [list(pair) for pair in pairs]


def expand_frozen_sharded_params(plan: Any) -> list[str]:
    """Expand every sharded parameter in the plan to a concrete FQN.
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
    ``Replicate`` placement (``Shard`` / ``StridedShard`` / ``Partial``).
    The FQN is the module FQN + ``.weight`` / ``.bias`` suffix — the same
    key the generated runtime uses to look up the parameter.
    """
    modules = getattr(plan, "modules", {}) or {}
    fqns: list[str] = []
    seen: set[str] = set()
    for fqn, spec in modules.items():
        for name, named in (getattr(spec, "params", {}) or {}).items():
            if _is_sharded(named):
                fqn_key = f"{fqn}.{name}" if name else fqn
                if fqn_key not in seen:
                    seen.add(fqn_key)
                    fqns.append(fqn_key)
    return sorted(fqns)


# ---------------------------------------------------------------------------
# helpers
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
# ---------------------------------------------------------------------------

def _is_sharded(named: Any) -> bool:
    """Whether any axis of a named placement is non-``Replicate``."""
    if not named:
        return False
    for placement in named.values():
        if placement is None:
            continue
        kind = type(placement).__name__
        if kind in ("Shard", "StridedShard", "Partial"):
            return True
    return False


def _value_to_dict(value: Any) -> Any:
    """Serialize a spec value: Target -> to_dict(), Placement -> string,
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482

def _value_to_dict(value: Any) -> Any:
    """Serialize a spec value: Target -> to_dict(), Placement -> string,
    named placement -> dict, callable -> name."""
    from hyper_parallel.core.dtensor.placement_types import Placement

    if isinstance(value, Placement):
        return placement_to_string(value)
    if hasattr(value, "to_dict"):
        return value.to_dict()
    if isinstance(value, dict):
        return {
            str(getattr(k, "value", k)): _value_to_dict(v)
            for k, v in value.items()
        }
    if isinstance(value, (list, tuple)):
        return [_value_to_dict(v) for v in value]
    if callable(value):
        return getattr(value, "__name__", str(value))
    return value


__all__ = [
    "FrozenPlan",
hyper_parallel/codegen/plan/offline_mesh.py
51
52
53
54
55
56
57
58
59
    mesh_dim_names: tuple[str, ...] = ()
    mesh_shape: tuple[int, ...] = ()

    def __post_init__(self) -> None:
        if len(self.mesh_dim_names) != len(self.mesh_shape):
            raise ValueError(
                f"mesh_dim_names {self.mesh_dim_names!r} and mesh_shape "
                f"{self.mesh_shape!r} must have the same length"
            )
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
      TP key on fused expert weights; ``ep_size`` is passed to ``plan()``
      instead so the planner derives the expert mesh;
    - ``pp`` declared when the config sets ``pp_size > 1``;
    """
    names: list[str] = []
    sizes: list[int] = []
    if parallel_dims.dp_size >= 1:
        names.append("dp")
        sizes.append(parallel_dims.dp_size)
    if parallel_dims.cp_size >= 1:
        names.append("cp")
        sizes.append(parallel_dims.cp_size)
    if parallel_dims.tp_size >= 1:
        names.append("tp")
        sizes.append(parallel_dims.tp_size)
    if parallel_dims.pp_size > 1:
        names.append("pp")
        sizes.append(parallel_dims.pp_size)
    return OfflineMesh(mesh_dim_names=tuple(names), mesh_shape=tuple(sizes))


__all__ = ["OfflineMesh", "build_offline_mesh"]
hyper_parallel/codegen/runtime.py
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45

Helpers fail fast: any unknown placement form, missing entrypoint, or absent
meta errors instead of silently degrading to a ``Replicate()`` no-op.
"""
from __future__ import annotations

import functools
import inspect
import logging
import os
from typing import Any, Optional

from hyper_parallel.codegen.loader import import_generated_module
from hyper_parallel.codegen.meta import (
    CodegenMeta,
    load_codegen_meta as _load_codegen_meta_from_path,
    meta_to_dict,
    validate_meta_schema,
50
51
52
53
54
55
56
57
58
# whose ``ep`` placement actually *changes* routes to the expert mesh; an
# identity ``R -> R`` key (the frozen plan's "not EP-dependent" marker) is not
# a dependency, so a degenerate topology (tp=cp=ep=1) prunes such boundaries
# to the identity form while the install path is skipped entirely.
from hyper_parallel.codegen.plan.boundary_forms import (
    _entry_has_ep_placement,
    declared_out_names,
    is_boundary_entry,
)
57
58
59
60
61
62
63
64
65
    is_boundary_entry,
)
# Pure placement parsing (freeze.py imports no torch), needed by both the
# meta->plan rebuild and its per-field deserializer below.
from hyper_parallel.codegen.plan.freeze import parse_named_placement

# ``sharding/apply`` imports torch at module level, so it is deliberately not
# imported here: runtime.py must stay importable without torch (generation-time
# hosts and the local smoke tests import this module for its pure helpers).  The
64
65
66
67
68
69
70
71
72
73
74
75
# imported here: runtime.py must stay importable without torch (generation-time
# hosts and the local smoke tests import this module for its pure helpers).  The
# apply helpers are imported lazily inside the one function that needs each.

logger = logging.getLogger(__name__)


def load_codegen_meta(artifact_dir: str) -> Optional[CodegenMeta]:
    """Read ``codegen_meta.json`` from an artifact directory.

    Thin wrapper over ``codegen/meta.load_codegen_meta``: the meta module
    takes a full path, the runtime contract is the artifact dir.  ``None``
74
75
76
77
78
79
80
81
82
83
84
85
    Thin wrapper over ``codegen/meta.load_codegen_meta``: the meta module
    takes a full path, the runtime contract is the artifact dir.  ``None``
    when absent or malformed.
    """
    return _load_codegen_meta_from_path(os.path.join(artifact_dir, "codegen_meta.json"))


def verify_codegen_signature(meta: CodegenMeta, hf_config: Any = None) -> None:
    """Validate the generated bundle metadata before model construction.

    The full recompute (canonical signature over YAML + env) is the trainer
    preflight's job — it runs before ``_build_model`` with the trainer config.
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
    - the meta's recorded architecture matches ``hf_config.architectures[0]``,
      when the hf_config is available (catches a bundle generated for a
      different model class).
    """
    validate_meta_schema(meta_to_dict(meta))
    covered = meta.covered or {}
    if not isinstance(covered.get("sharding_plan"), bool):
        raise TypeError(
            "codegen gate: meta.covered.sharding_plan must be a bool; "
            f"got {covered.get('sharding_plan')!r}"
        )
    if not meta.entrypoints.get("parallelize"):
        raise KeyError(
            "codegen gate: meta.entrypoints['parallelize'] is missing — the "
            "generated module cannot be parallelized; regenerate the artifact"
        )
    if hf_config is not None:
        architectures = getattr(hf_config, "architectures", None) or []
        arch_name = architectures[0] if architectures else None
        source_arch = (meta.source or {}).get("architecture")
        if arch_name and source_arch and arch_name != source_arch:
            raise ValueError(
                f"codegen gate: meta records architecture {source_arch!r} but "
                f"hf_config resolves {arch_name!r}; the bundle does not match "
                "the requested model"
            )
117
118
119
120
121
122
123
124
125
                "the requested model"
            )


def publish_codegen_mesh_context(artifact_dir: str, mesh_context: Any) -> None:
    """Publish ``mesh_context`` into the generated module namespace.

    Lowered boundary forwards reference ``mesh_context`` as a module global.
    A pure data-parallel/FSDP run does not need generated parameter sharding,
125
126
127
128
129
130
131
132
133
134
    A pure data-parallel/FSDP run does not need generated parameter sharding,
    but it still executes those lowered forwards; publishing the context keeps
    identity redistributions well-defined without calling ``hp_parallelize``.
    """
    module = import_generated_module(artifact_dir)
    module.__dict__["mesh_context"] = mesh_context


# ---------------------------------------------------------------------------
# Mesh resolution (mirrors apply_sharding_plan's source-mesh selection)
133
134
135
136
137
138
139
140
141
# ---------------------------------------------------------------------------
# Mesh resolution (mirrors apply_sharding_plan's source-mesh selection)
# ---------------------------------------------------------------------------

def _active_mesh(mesh: Any, mesh_dim_names):
    """Slice a full mesh down to the plan's active axes (``_get_active_mesh``).

    Delegates to the native ``_get_active_mesh_with_names`` — the slicing and
    reordering contract lives in one place; this wrapper keeps no mirrored
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
    (``(sub_mesh, active_dim_names)``): the caller must resolve placements
    against the *returned* names, because the sliced sub-mesh carries exactly
    that axis order and a placement tuple must line up with it.
    """
    from hyper_parallel.distributed._builder.applier import (  # pylint: disable=C0415
        _get_active_mesh_with_names,
    )
    return _get_active_mesh_with_names(mesh, mesh_dim_names)


def _resolve_runtime_meshes(mesh: Any, mesh_dim_names=None):
    """Resolve the active (dense) mesh and the FSDP source meshes.

    Mirrors ``apply_sharding_plan`` / ``_resolve_parameter_source_meshes``.
    ``dense_mesh`` is the mesh parameters shard across and boundaries
193
194
195
196
197
198
199
200
201
202
203
204
205
206
    order into the live mesh's declared order).  Callers that resolve
    placements against ``dense_mesh`` must use this returned order, never the
    raw ``mesh_dim_names`` argument.
    """
    device_mesh = getattr(mesh, "device_mesh", mesh)
    dense_mesh, active_dim_names = _active_mesh(device_mesh, mesh_dim_names)

    fsdp_non_moe_mesh = getattr(mesh, "fsdp_non_moe_mesh", None)
    if fsdp_non_moe_mesh is not None:
        dense_source_mesh = (
            fsdp_non_moe_mesh["tp"]
            if "tp" in tuple(getattr(fsdp_non_moe_mesh, "mesh_dim_names", ()) or ())
            else None
        )
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
            if "tp" in tuple(getattr(fsdp_non_moe_mesh, "mesh_dim_names", ()) or ())
            else None
        )
    else:
        dense_source_mesh = (
            device_mesh["tp"]
            if "tp" in tuple(getattr(device_mesh, "mesh_dim_names", ()) or ())
            else None
        )
    fsdp_moe_mesh = getattr(mesh, "fsdp_moe_mesh", None)
    expert_mesh = (
        fsdp_moe_mesh["ep"]
        if fsdp_moe_mesh is not None
        and "ep" in tuple(getattr(fsdp_moe_mesh, "mesh_dim_names", ()) or ())
        else None
216
217
218
219
220
221
222
223
224
225
226
227
228
        if fsdp_moe_mesh is not None
        and "ep" in tuple(getattr(fsdp_moe_mesh, "mesh_dim_names", ()) or ())
        else None
    )
    if expert_mesh is not None:
        # The expert mesh is also sliced to the plan's active axes, so it gets
        # the same mesh-major reordering (and must arrive with it).
        expert_mesh, _ = _active_mesh(expert_mesh, mesh_dim_names)
    return dense_mesh, dense_source_mesh, expert_mesh, active_dim_names


# ---------------------------------------------------------------------------
# TP gradient metadata (delegated to the native builder:
229
230
231
232
233
234
235
236
237
# parameter_sharding._build_runtime_source_shard_info ->
# source_shard.build_source_shard_info)
# ---------------------------------------------------------------------------

def hp_build_tp_grad_info(
    model,
    plan,
    dense_source_mesh,
    expert_source_mesh,
264
265
266
267
268
269
270
271
272
273
274
275
    plain locals, so ``hp_build_tp_grad_info`` must be the LAST sharding
    helper the generated ``hp_parallelize`` runs (the applier's own ordering
    invariant).
    """
    from hyper_parallel.distributed._builder.parameter_sharding import (
        _build_runtime_source_shard_info,
    )

    return _build_runtime_source_shard_info(
        [model], plan, dense_source_mesh, expert_source_mesh, False
    )

277
278
279
280
281
282
283
284
285
# ---------------------------------------------------------------------------
# Boundary redistribution through PrecompiledBoundary
# ---------------------------------------------------------------------------

def _load_target_path(path: str):
    """Import the callable at a dotted ``_target_`` path.

    The frozen injection serializes a ``trainer.config.Target`` to
    ``{"_target_": <path>}``; the runtime re-imports the callable and rebuilds
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
    ``(obj, qualname)`` for classes, but injection targets are module-level
    functions, so the dotted path splits at the last dot into
    (module, attribute).
    """
    from importlib import import_module

    if not isinstance(path, str) or not path:
        raise ValueError(f"frozen injection target path must be a non-empty string, got {path!r}")
    module_name, _, attr = path.rpartition(".")
    if not module_name or not attr:
        raise ValueError(f"frozen injection target path {path!r} is not a dotted path")
    fn = getattr(import_module(module_name), attr, None)
    if fn is None:
        raise ValueError(
            f"frozen injection target path {path!r} does not name a callable "
            f"({module_name!r} has no attribute {attr!r})"
        )
    if not callable(fn):
        raise TypeError(f"frozen injection target path {path!r} resolves to {type(fn).__name__}, not callable")
    return fn


def _injection_target(value: Any) -> Any:
    """Rebuild an injection factory/wrapper from its frozen form.

    ``freeze_injections`` serializes a ``Target`` via ``to_dict()``
    (``{"_target_": path, **kwargs}``) and a plain callable by ``__name__``.
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
    applier's ``_is_delayed_target`` / ``Target.build`` path applies; a string
    is left as the registry name the applier resolves the same way it would
    have at plan time.
    """
    if isinstance(value, dict) and "_target_" in value:
        from hyper_parallel.trainer.config import Target

        path = value["_target_"]
        return Target(
            _load_target_path(path),
            target_path=path,
            **{k: v for k, v in value.items() if k != "_target_"},
        )
    return value


class _FrozenBoundarySpec:
    """Minimal spec view over one frozen ``param_plan`` boundary entry.

    ``PrecompiledBoundary`` reads ``in_src`` / ``in_dst`` / ``out_src`` /
    ``out_dst`` / ``out_names`` off the spec object; the frozen entry stores
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
    re-imported from the frozen form); ``entry`` (the frozen ``param_plan``
    entry) supplies ``region_dispatch`` / ``out_src`` / ``out_names``.
    """

    def __init__(self, entry: dict[str, Any], injection: Optional[dict[str, Any]] = None):
        self.params = parse_named_placement(entry.get("params") or {}) or {}
        self.in_src = parse_named_placement(entry.get("in_src") or {}) or {}
        self.in_dst = parse_named_placement(entry.get("in_dst") or {}) or {}
        self.out_src = (
            parse_named_placement(entry["out_src"]) if entry.get("out_src") else None
        )
        self.out_dst = (
            parse_named_placement(entry["out_dst"]) if entry.get("out_dst") else None
        )
        self.out_names = list(entry["out_names"]) if entry.get("out_names") else None
        self.region_dispatch = entry.get("region_dispatch")
        # D-22: the native suppression/restore pair reads this off the spec
        # object it is handed, so the frozen view mirrors it.
        self._deferred_bias_params = tuple(entry.get("deferred_bias_params") or ())
        for key in ("inner_wrapper", "inner_target", "inner_out_src", "local_compute_fn"):
            if injection is not None and key in injection:
                setattr(self, key, _injection_target(injection[key]))


def _build_rewrap_plan(
    plan_entry: dict[str, Any],
    dim_names: tuple[str, ...],
) -> list[tuple[int, str, tuple]]:
    """Pre-parse one frozen entry's ``out_src`` into executable rewrap ops.
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392

    Returns ``[(output_index, out_name, placements), ...]``.  ``InstalledBoundary``
    resolves once at install time so the per-forward path never re-parses.
    """
    from hyper_parallel.distributed.recipe_spec import resolve_placements

    declared = plan_entry.get("out_src") or {}
    out_names = declared_out_names(plan_entry)
    name_to_idx = {name: index for index, name in enumerate(out_names)}
    plan: list[tuple[int, str, tuple]] = []
    for out_name, named_placement in declared.items():
        index = name_to_idx.get(out_name)
        if index is None:
            raise ValueError(
                f"InstalledBoundary.rewrap_outputs: out_src declares output {out_name!r}, "
                f"but out_names={list(out_names)!r} does not contain it"
            )
        # Out_src is a frozen JSON contract: its placement leaves are
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
        # same step _FrozenBoundarySpec applies for the boundary compile / the
        # native _rewrap_local_outputs runs on already-object contracts); the
        # non-alias path then maps the 2-axis MeshPlacement onto the 3-D
        # tensor, padding the unpredicted dim with Replicate().
        named_placement = parse_named_placement(named_placement)
        plan.append(
            (index, out_name, tuple(resolve_placements(named_placement, dim_names)))
        )
    return plan


def hp_to_local_if_dtensor(output):
    """Convert a local-region boundary output back to a local tensor after exit.

    Named by the emitted local-region forward template (``emit.parallel``), so
    it stays until that template goes away — see the boundary-ownership item in
414
415
416
417
418
419
420
421
422
423
424
425
    Named by the emitted local-region forward template (``emit.parallel``), so
    it stays until that template goes away — see the boundary-ownership item in
    the restructuring plan.
    """
    to_local = getattr(output, "to_local", None)
    if callable(to_local):
        return to_local()
    return output


# ---------------------------------------------------------------------------
# Install-time boundary compilation (one-shot; generated forwards bind these)
424
425
426
427
428
429
430
431
432
# ---------------------------------------------------------------------------
# Install-time boundary compilation (one-shot; generated forwards bind these)
# ---------------------------------------------------------------------------

def _compile_boundary(entry: dict[str, Any], mesh: Any, mesh_dim_names):
    """Compile one frozen entry into a ``PrecompiledBoundary`` (uncached).

    The generated path compiles per module at install time: ``_bind_input_indices``
    mutates ``op.arg_index`` against one module's forward signature, and two classes
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
    The generated path compiles per module at install time: ``_bind_input_indices``
    mutates ``op.arg_index`` against one module's forward signature, and two classes
    with identical contracts but different signatures must not share a plan.
    """
    from hyper_parallel.distributed._builder.precompiled_boundary import (
        PrecompiledBoundary,
    )
    from hyper_parallel.distributed._builder.tp_collective_lowering import (
        create_tp_collective_lowerer,
    )

    dim_names = (
        mesh_dim_names
        or tuple(getattr(mesh, "mesh_dim_names", ()) or ())
    )
    return PrecompiledBoundary(
        _FrozenBoundarySpec(entry),
        mesh,
        dim_names,
        op_lowerer=create_tp_collective_lowerer(mesh, dim_names),
450
451
452
453
454
455
456
457
458
        op_lowerer=create_tp_collective_lowerer(mesh, dim_names),
    )


class InstalledBoundary:
    """One boundary's compiled redistribute plan, bound to a module instance.

    ``hp_install_boundaries`` resolves everything once, at install time
    rather than on every forward call: the dense/expert mesh routing (the
465
466
467
468
469
470
471
472
473
    unwrap on identity ops, local collectives, ``DTensor.redistribute``
    fallback — are the compiled ``PrecompiledBoundary``'s own.
    """

    def __init__(
        self,
        entry: dict[str, Any],
        mesh_context: Any,
        mesh_dim_names: Optional[tuple[str, ...]] = None,
473
474
475
476
477
478
479
480
481
482
483
484
485
        mesh_dim_names: Optional[tuple[str, ...]] = None,
        *,
        module: Any = None,
    ) -> None:
        dense_mesh, _, expert_mesh, active_dim_names = (
            _resolve_runtime_meshes(mesh_context, mesh_dim_names)
        )
        if dense_mesh is None:
            raise ValueError(
                "hp_install_boundaries requires a DeviceMesh / MeshContext "
                "to compile boundaries against"
            )
        # Routing facts the tp_collective re-validation reads: the dense mesh
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
            )
        # Routing facts the tp_collective re-validation reads: the dense mesh
        # and the active axis names this boundary actually compiled against
        # (empty = the no-op / expert-mesh branches below).
        self.dense_mesh = dense_mesh
        self.active_dim_names = tuple(active_dim_names or ())
        self.noop = False
        self._boundary = None
        if not active_dim_names:
            if expert_mesh is not None and _entry_has_ep_placement(entry):
                self._boundary = _compile_boundary(entry, expert_mesh, None)
            else:
                # Boundary-exit semantics for this branch: the payload is
                # returned untouched (no DTensor unwrap either).
                self.noop = True
        else:
            self._boundary = _compile_boundary(entry, dense_mesh, active_dim_names)
        if self._boundary is not None and module is not None:
            from hyper_parallel.distributed._builder.forward_rewriter import (
                _bind_input_indices,
            )

            _bind_input_indices(self._boundary, module)

        self._rewrap_plan: Optional[list[tuple[int, str, tuple]]] = None
        self._rewrap_mesh = None
        if entry.get("out_src"):
            rewrap_dims = (
                tuple(active_dim_names)
                or tuple(getattr(dense_mesh, "mesh_dim_names", ()) or ())
            )
            self._rewrap_plan = _build_rewrap_plan(entry, rewrap_dims)
            self._rewrap_mesh = dense_mesh

    def redistribute_inputs(self, payload):
        """Run the input plan on the ``(args, kwargs)`` pair.

        Accepts the pair the generated forward builds and takes it as one
        positional argument, so the lowered call sites stay uniform across
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
        Accepts the pair the generated forward builds and takes it as one
        positional argument, so the lowered call sites stay uniform across
        boundary forms.
        """
        if self.noop:
            return payload
        args, kwargs = payload
        return self._boundary.redistribute_inputs(args, kwargs)

    def redistribute_outputs(self, outputs):
        """Run the output plan on the forward's tensor output."""
        if self.noop:
            return outputs
        return self._boundary.redistribute_outputs(outputs)

    def rewrap_outputs(self, output):
        """Re-wrap local region outputs into DTensors per ``out_src``.

        ``_build_rewrap_plan`` pre-resolves the placements at install time; the
        execution is the shared native local-region mechanism
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
        execution is the shared native local-region mechanism
        (:func:`~hyper_parallel.distributed._builder.forward_rewriter.rewrap_declared_outputs`),
        so the codegen and native paths cannot drift apart.
        """
        if self._rewrap_plan is None:
            return output
        from hyper_parallel.distributed._builder.forward_rewriter import (  # pylint: disable=import-outside-toplevel
            rewrap_declared_outputs,
        )

        return rewrap_declared_outputs(
            output,
            self._rewrap_plan,
            self._rewrap_mesh,
            label="InstalledBoundary.rewrap_outputs",
554
555
556
557
558
559
560
561
562
            label="InstalledBoundary.rewrap_outputs",
        )


class TPOperators:
    """Bare-operator runtime bound to a statically lowered boundary forward.

    A ``tp_collective`` template calls ``self._hp_tp.<kind>(...)`` directly
    (``emit/parallel``'s static form); ``hp_install_boundaries`` binds this
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
    so a gloo ``reduce_scatter`` remaps to all_reduce + local chunk exactly
    as the placement-driven path does.
    """

    def __init__(self, lowerer: Any) -> None:
        """Capture the install-time TP lowerer every operator dispatches through."""
        self._lowerer = lowerer

    def to_local(self, tensor: Any) -> Any:
        """Identity-transition semantics: a DTensor unwraps to its local shard."""
        from hyper_parallel.core.dtensor.dtensor import DTensor  # pylint: disable=C0415

        if isinstance(tensor, DTensor):
            return tensor.to_local()
        return tensor

    def all_gather(self, tensor: Any, dim: Optional[int] = None) -> Any:
        """Shard -> Replicate on the tp axis: concat-gather along ``dim``."""
        return self._execute("all_gather", tensor, dim)

    def all_reduce(self, tensor: Any) -> Any:
        """Partial -> Replicate on the tp axis: sum-reduce over the tp group."""
        return self._execute("all_reduce", tensor)

    def reduce_scatter(self, tensor: Any, dim: Optional[int] = None) -> Any:
        """Partial -> Shard on the tp axis: sum-reduce, then shard along ``dim``."""
        return self._execute("reduce_scatter", tensor, dim)

    def _execute(self, kind: str, tensor: Any, dim: Optional[int] = None) -> Any:
        """Unwrap, skip ``None``, and run one lowered collective op."""
        from hyper_parallel.core.dtensor.dtensor import DTensor  # pylint: disable=C0415

        if tensor is None:
            return None
        if isinstance(tensor, DTensor):
            tensor = tensor.to_local()
        return self._lowerer.execution_op(kind, tensor_dim=dim).execute(tensor)


#: Generated class names whose ``forward`` an inline strategy body replaced
#: (the MoE EP shell): it orchestrates through parallel state bound on the
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
#: (the MoE EP shell): it orchestrates through parallel state bound on the
#: instance at install time rather than through a compiled boundary.  Filled
#: per generated module from ``meta.external_state_classes`` — the injected
#: strategy's own resolved spec is the only declaration site.
_EXTERNAL_STATE_CLASSES_BY_MODULE: dict[str, frozenset[str]] = {}


def _mesh_group(mesh: Any, name: str) -> Any:
    """Return a named mesh group, tolerating one-dimensional mesh APIs."""
    if mesh is None:
        return None
    try:
        return mesh.get_group(name)
    except TypeError:
        return mesh.get_group()


def _install_inline_state_bindings(module: Any, expert_mesh: Any) -> None:
    """Bind the instance-attribute parallel channel an inlined forward reads.

    The inlined EP MoE shell orchestrates through the same instance attributes
    the boundary templates read: ``self._hp_ep_group`` plus the
637
638
639
640
641
642
643
644
645
646
647
648
649
650
    shared install paths: the compiled plan plus the TP operators
    (``hp_install_boundaries``) and the declared inner wrapper
    (``hp_apply_inner_wrapper``).
    """
    ep_group = _mesh_group(expert_mesh, "ep") if expert_mesh is not None else None
    module._hp_ep_group = ep_group
    module.ep_enable = ep_group is not None and ep_group.size() > 1


def hp_bind_inline_state(
    model: Any,
    mesh_context: Any,
    mesh_dim_names: Optional[tuple[str, ...]] = None,
    *,
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
    The mesh facts are resolved lazily and only when a declared class is
    actually present, so an artifact without inlined components never touches
    the mesh.
    """
    if model is None or generated_module is None:
        return
    targets = [
        module
        for _, module in model.named_modules()
        if _is_external_state_inline_module(module, generated_module)
    ]
    if not targets:
        return
    _, _, expert_mesh, _ = _resolve_runtime_meshes(mesh_context, mesh_dim_names)
    for module in targets:
        _install_inline_state_bindings(module, expert_mesh)


def register_external_state_classes(generated_module: Any, class_names: Any) -> None:
    """Record which generated classes take their parallel state externally."""
    if generated_module is None:
        return
    _EXTERNAL_STATE_CLASSES_BY_MODULE[generated_module.__name__] = frozenset(
        class_names or ()
    )


def _is_external_state_inline_module(module: Any, generated_module: Any = None) -> bool:
    """Whether this generated class gets its state from instance attributes.

    True for an instance of a class the artifact declared in
    ``meta.external_state_classes``: codegen replaced that class's forward with
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
    an inline strategy body and it reads the attributes
    ``hp_bind_inline_state`` binds, so the boundary installers must leave it
    alone.
    """
    if generated_module is None:
        return False
    module_name = getattr(generated_module, "__name__", None)
    class_names = _EXTERNAL_STATE_CLASSES_BY_MODULE.get(module_name)
    if not class_names:
        return False
    cls = type(module)
    return cls.__module__ == module_name and cls.__name__ in class_names


def _install_deferred_bias_pair(module: Any, spec: Any, *, lowered: bool) -> None:
    """Install the D-22 bias-suppression / deferred-re-add pair on one boundary.

    Reuses the native Phase C primitives (``_make_bias_free_forward`` /
    ``_maybe_add_deferred_biases`` from ``forward_rewriter``) so the hiding
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
    ``forward`` would keep the bias hidden while the emitted
    ``self._hp_deferred_bias(outputs)`` reads it at the exit.  Child-owned
    paths and imported-class boundaries keep the native placement exactly.
    """
    from hyper_parallel.distributed._builder.forward_rewriter import (  # pylint: disable=C0415
        _make_bias_free_forward,
        _maybe_add_deferred_biases,
    )

    for param_path in spec._deferred_bias_params:  # pylint: disable=protected-access
        owner_path = param_path.rpartition(".")[0]
        if owner_path:
            owner = module.get_submodule(owner_path)
            owner.forward = _make_bias_free_forward(owner, owner.forward)
        elif lowered:
            module._forward_impl = _make_bias_free_forward(
                module, module._forward_impl
            )
        else:
            module.forward = _make_bias_free_forward(module, module.forward)
    module._hp_deferred_bias = functools.partial(
        _maybe_add_deferred_biases, module, spec
    )


def _wrap_installed_boundary_forward(
    module: Any, installed: InstalledBoundary, spec: Any = None
) -> None:
    """Install native-style boundary entry/exit redistribution on one module.
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
    re-derived on every call.  When ``spec`` carries D-22 deferred bias
    params, the wrapper also re-adds them after the exit redistribution —
    the native Phase C exit order (suppress inside, reduce, add once).
    """
    if getattr(module, "_codegen_boundary_wrapped", False):
        return
    original_forward = module.forward
    deferred_bias = spec is not None and bool(spec._deferred_bias_params)

    @functools.wraps(original_forward)
    def codegen_boundary_forward(*args: Any, **kwargs: Any) -> Any:
        redist_args, redist_kwargs = installed.redistribute_inputs((args, kwargs))
        outputs = original_forward(*redist_args, **redist_kwargs)
        outputs = installed.redistribute_outputs(outputs)
        if deferred_bias:
            outputs = module._hp_deferred_bias(outputs)
        return outputs

    module.forward = codegen_boundary_forward
    module._codegen_boundary_wrapped = True
    module._codegen_original_forward = original_forward


def _wrap_static_fallback_forward(module: Any, installed: InstalledBoundary) -> None:
    """Replace a rejected static ``tp_collective`` forward with the generic engine.

    The static template's body references ``self._hp_tp``, which this
    install path refused to bind; replacing ``module.forward`` wholesale
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
    already bound on the module (``_hp_deferred_bias``, installed before
    this fallback wraps) is re-applied after the exit redistribution, so the
    fallback preserves the deferred-bias semantics the emitted template had.
    """
    impl = module._forward_impl
    deferred_bias = getattr(module, "_hp_deferred_bias", None)

    def generic_boundary_forward(*args: Any, **kwargs: Any) -> Any:
        redist_args, redist_kwargs = installed.redistribute_inputs((args, kwargs))
        outputs = impl(*redist_args, **redist_kwargs)
        outputs = installed.redistribute_outputs(outputs)
        if deferred_bias is not None:
            outputs = deferred_bias(outputs)
        return outputs

    module.forward = generic_boundary_forward


def _install_static_tp_operators(
    module: Any,
    entry: dict[str, Any],
    installed: InstalledBoundary,
    mesh_dim_names: Optional[tuple[str, ...]],
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
    bind ``module._hp_tp`` only when the live verdict reproduces the
    emitted ops; on any mismatch replace the forward with the generic
    engine, so the baked calls never run unvalidated.
    """
    from hyper_parallel.codegen.emit.parallel import (  # pylint: disable=C0415
        TP_FORM_ATTRIBUTE,
        TP_FORM_MARKER,
    )
    from hyper_parallel.codegen.plan.boundary_forms import (  # pylint: disable=C0415
        FORM_TP_COLLECTIVE,
        classify_boundary_form,
    )
    from hyper_parallel.distributed._builder.tp_collective_lowering import (  # pylint: disable=C0415
        create_tp_collective_lowerer,
    )

    if getattr(module, TP_FORM_ATTRIBUTE, None) != TP_FORM_MARKER:
        return
    lowerer = create_tp_collective_lowerer(
        installed.dense_mesh, installed.active_dim_names
    )
    emitted = classify_boundary_form(entry, mesh_dim_names, injection)
    live = classify_boundary_form(entry, installed.active_dim_names, injection)
    if (
        lowerer is not None
        and emitted.form == FORM_TP_COLLECTIVE
        and live.form == FORM_TP_COLLECTIVE
        and live.in_ops == emitted.in_ops
843
844
845
846
847
848
849
850
851
852
853
        and live.form == FORM_TP_COLLECTIVE
        and live.in_ops == emitted.in_ops
        and live.out_ops == emitted.out_ops
    ):
        module._hp_tp = TPOperators(lowerer)
        return
    logger.warning(
        "hp_install_boundaries: static tp_collective forward on %s does not "
        "match the live mesh (emitted=%s live=%s lowerer=%s); falling back to "
        "the generic redistribute engine",
        type(module).__name__, emitted.form, live.form, lowerer is not None,
851
852
853
854
855
856
857
858
859
860
861
862
        "match the live mesh (emitted=%s live=%s lowerer=%s); falling back to "
        "the generic redistribute engine",
        type(module).__name__, emitted.form, live.form, lowerer is not None,
    )
    _wrap_static_fallback_forward(module, installed)


def _install_toggle_bindings(
    module: Any,
    installed: InstalledBoundary,
) -> None:
    """Install the switch/toggle enabler booleans and operators onto one module.
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
      the ``tp_collective`` static form binds, so the toggle body dispatches
      through the production collectives rather than a duplicated path within
      the raw operator.
    """
    from hyper_parallel.codegen.emit.parallel import (  # pylint: disable=C0415
        TOGGLE_FORM_MARKER,
        TP_FORM_ATTRIBUTE,
    )
    from hyper_parallel.distributed._builder.tp_collective_lowering import (  # pylint: disable=C0415
        create_tp_collective_lowerer,
    )

    if getattr(module, TP_FORM_ATTRIBUTE, None) != TOGGLE_FORM_MARKER:
        return
    active = tuple(installed.active_dim_names or ())
    module.tp_enable = bool(active and "tp" in active)
    module.cp_enable = bool(active and "cp" in active)
    module.ep_enable = bool(active and "ep" in active)
    if (
        (module.tp_enable or module.cp_enable)
        and getattr(installed, "dense_mesh", None) is not None
    ):
        lowerer = create_tp_collective_lowerer(installed.dense_mesh, active)
        if lowerer is not None:
            module._hp_tp = TPOperators(lowerer)


def hp_install_boundaries(
    model: Any,
    param_plan: dict[str, Any],
    mesh_context: Any,
    mesh_dim_names: Optional[tuple[str, ...]] = None,
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
    ``_boundary_post_order_key`` — the same D-14 invariant 2 ordering as the
    applier's Phase C driving loop, so inner wrappers exist before an outer
    boundary's local_compute_fn can cache them.
    """
    if not param_plan:
        return
    from hyper_parallel.distributed._builder.applier import (
        _boundary_post_order_key,
        _get_tp_submesh,
    )
    from hyper_parallel.distributed._builder.forward_rewriter import (
        _is_vocab_parallel_embed,
    )
    from hyper_parallel.distributed._builder.parameter_sharding import (
        _resolve_module,
    )

    meshes_resolved = False
    tp_mesh = None

    for fqn, entry in sorted(param_plan.items(), key=_boundary_post_order_key):
        if _is_tp_sequence_parallel_embed_entry(entry):
            parent_fqn = fqn.rpartition(".")[0]
            _wrap_default_position_ids(_resolve_module(model, parent_fqn))
        if not is_boundary_entry(entry):
            continue
        module = _resolve_module(model, fqn)
        if _has_lowered_forward(module, generated_module):
            injection = _injection_spec(injections or [], fqn)
            installed = InstalledBoundary(
                entry, mesh_context, mesh_dim_names, module=module
            )
            if _is_external_state_inline_module(module, generated_module):
                # D-22: an inline-strategy class body never passes through
                # this boundary exit, so a deferred bias would be suppressed
                # but never re-added — fail fast instead of silently
                # dropping the bias from every forward.
                if entry.get("deferred_bias_params"):
                    raise NotImplementedError(
                        f"hp_install_boundaries: boundary {fqn!r} defers bias "
                        f"params {entry['deferred_bias_params']}, but its class is "
                        "an external-state inline strategy the D-22 exit hook "
                        "cannot run in"
 983
 984
 985
 986
 987
 988
 989
 990
 991
 992
 993
 994
 995
 996
 997
 998
 999
1000
                        f"params {entry['deferred_bias_params']}, but its class is "
                        "an external-state inline strategy the D-22 exit hook "
                        "cannot run in"
                    )
                continue
            module._hp_boundary = installed
            if entry.get("deferred_bias_params"):
                # Before _install_static_tp_operators: the static-fallback
                # wrap captures module._forward_impl at wrap time, so the
                # suppression must already be in place there.
                _install_deferred_bias_pair(
                    module, _FrozenBoundarySpec(entry, injection), lowered=True
                )
            _install_static_tp_operators(
                module,
                entry,
                installed,
                mesh_dim_names,
 999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
                installed,
                mesh_dim_names,
                injection,
            )
            _install_toggle_bindings(module, installed)
            continue
        spec = _FrozenBoundarySpec(entry)
        if spec.params:
            if not meshes_resolved:
                dense_mesh, _, _, active_dim_names = _resolve_runtime_meshes(
                    mesh_context, mesh_dim_names
                )
                tp_mesh = _get_tp_submesh(dense_mesh, tuple(active_dim_names))
                meshes_resolved = True
        if _is_vocab_parallel_embed(module, spec, tp_mesh):
            _install_vocab_parallel_embedding(module, tp_mesh)
        installed = InstalledBoundary(
            entry, mesh_context, mesh_dim_names, module=module
        )
        # Every boundary module carries its compiled plan — the imported-class
        # wrapper below closes over it, so introspection and debugging see the
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
        )
        # Every boundary module carries its compiled plan — the imported-class
        # wrapper below closes over it, so introspection and debugging see the
        # same object the generated source-lowered forwards would.
        module._hp_boundary = installed
        if spec._deferred_bias_params:
            # Native order: suppression first, so the wrapper below captures
            # the suppressed forward as its original (D-22, Phase C identity).
            _install_deferred_bias_pair(module, spec, lowered=False)
        injection = _injection_spec(injections or [], fqn)
        if injection is not None and injection.get("local_compute_fn") is not None:
            # A local-region boundary on an imported class: hp_bind_compute
            # installs the native local-region skeleton (boundary entry ->
            # injected compute -> boundary exit) against this compiled
            # boundary.  The plain entry/exit wrapper here would run the
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
            # injected compute -> boundary exit) against this compiled
            # boundary.  The plain entry/exit wrapper here would run the
            # original forward instead of the injected compute and double
            # the redistribution.
            continue
        _wrap_installed_boundary_forward(module, installed, spec=spec)


# ---------------------------------------------------------------------------
# Compute binding and inner-wrapper installation
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
# ---------------------------------------------------------------------------
# Compute binding and inner-wrapper installation
# ---------------------------------------------------------------------------

def _injection_spec(injections: list[dict[str, Any]], fqn: str) -> Optional[dict[str, Any]]:
    """Find one frozen boundary's injection declaration by its FQN."""
    for rule in injections:
        if rule.get("match") == fqn:
            return rule
    return None


def hp_expand_injections(
    rules: Optional[list[dict[str, Any]]],
) -> list[dict[str, Any]]:
    """Expand grouped injection rules into the per-FQN form helpers consume.
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
    A scalar ``match`` (the frozen ``meta.injections`` shape and every
    pre-grouping artifact) passes through untouched; ``None`` / empty becomes
    ``[]``.
    """
    if not rules:
        return []
    expanded: list[dict[str, Any]] = []
    for rule in rules:
        match = rule.get("match") if isinstance(rule, dict) else None
        if isinstance(match, (list, tuple)):
            for fqn in match:
                one = dict(rule)
                one["match"] = fqn
                expanded.append(one)
        else:
            expanded.append(rule)
    return expanded


def _install_runtime_region_forward(
    module: Any,
    spec: Any,
    compute_fn: Any,
    mesh: Any,
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
    injected compute -> boundary exit.  A boundary compiled as a no-op (no
    active axes) degenerates to the compute swap plus the D-22 exit hook --
    its entry/exit are identity, the only remaining skeleton effects.
    """
    from hyper_parallel.distributed._builder.forward_rewriter import (  # pylint: disable=C0415
        _maybe_add_deferred_biases,
        _wrap_local_region_forward,
    )

    installed = getattr(module, "_hp_boundary", None)
    if installed is None:
        raise RuntimeError(
            "codegen: local-region injection on "
            f"{type(module).__name__} has no compiled boundary; "
            "hp_install_boundaries must run before hp_bind_compute"
        )
    if installed.noop:

        @functools.wraps(module.forward)
        def runtime_region_forward(*args: Any, **kwargs: Any) -> Any:
            output = compute_fn(*args, **kwargs)
            return _maybe_add_deferred_biases(module, spec, output)

        module.forward = runtime_region_forward
        return
    _wrap_local_region_forward(
        module,
        installed._boundary,  # pylint: disable=protected-access
        spec,
        mesh,
1126
1127
1128
1129
1130
1131
1132
1133
1134
        compute_fn=compute_fn,
    )


def hp_bind_compute(
    model,
    injections: list[dict[str, Any]],
    mesh_context,
    mesh_dim_names: Optional[tuple[str, ...]] = None,
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
    file) has no emitted forward that could call ``__hp_compute__``; for
    those the native local-region skeleton is installed instead
    (:func:`_install_runtime_region_forward`).
    """
    from hyper_parallel.distributed._builder.rule_resolver import (
        _resolve_local_compute_fn,
    )
    from hyper_parallel.distributed._builder.parameter_sharding import _resolve_module

    dense_mesh, _, expert_mesh, active_dim_names = (
        _resolve_runtime_meshes(mesh_context, mesh_dim_names)
    )
    if dense_mesh is None:
        raise ValueError(
            "hp_bind_compute requires a DeviceMesh / MeshContext to bind compute against"
        )
    if not injections:
        return
    for rule in injections:
        if rule.get("local_compute_fn") is None:
            continue
        fqn = rule.get("match")
        if not fqn:
            raise ValueError(
                f"hp_bind_compute: injection rule {rule!r} has no 'match' FQN"
            )
        entry = (param_plan or {}).get(fqn, {})
        spec = _FrozenBoundarySpec(entry, rule)
        module = _resolve_module(model, fqn)
        compute_fn = _resolve_local_compute_fn(
            module, spec, dense_mesh, active_dim_names, expert_mesh)
        # External-state inline modules (e.g. Qwen3MoeSparseMoeBlock) carry an
        # inlined forward and must not receive __hp_compute__, but the local
        # compute factory above still has to run: it installs companion state
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
        # inlined forward and must not receive __hp_compute__, but the local
        # compute factory above still has to run: it installs companion state
        # such as ``experts.local_expert_count`` and the bound experts.forward
        # that the inlined forward relies on.
        if _is_external_state_inline_module(module, generated_module):
            continue
        # _resolve_local_compute_fn returns module.forward for the
        # region_dispatch=False gate; that path is not a bound compute (the
        # generated forward already calls _forward_impl), so it must not be
        # bound as __hp_compute__.
        if compute_fn is None or compute_fn is module.forward:
            continue
        if _has_lowered_forward(module, generated_module):
            module.__hp_compute__ = compute_fn
            continue
        # An imported-class boundary has no emitted forward that could call
        # ``__hp_compute__``: install the native local-region skeleton
        # instead (boundary entry -> injected compute -> boundary exit), the
        # same code path the native applier runs for this boundary.
        _install_runtime_region_forward(
            module, spec, compute_fn, dense_mesh, active_dim_names
        )


def hp_apply_inner_wrapper(
    model,
    injections: list[dict[str, Any]],
    mesh_context,
    mesh_dim_names: Optional[tuple[str, ...]] = None,
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
    the emitted boundary form (the rendered fused attention), so the companion
    ``attention_interface`` swap and the boundary redistribution compose
    instead of being re-implemented per component.
    """
    from hyper_parallel.distributed._builder.applier import (
        _get_cp_submesh,
        _get_ep_submesh,
        _get_tp_submesh,
    )
    from hyper_parallel.distributed._builder.forward_rewriter import (
        _wrap_inner_attention,
    )
    from hyper_parallel.distributed._builder.parameter_sharding import _resolve_module

    dense_mesh, _, expert_mesh, active_dim_names = (
        _resolve_runtime_meshes(mesh_context, mesh_dim_names)
    )
    if dense_mesh is None:
        raise ValueError(
            "hp_apply_inner_wrapper requires a DeviceMesh / MeshContext to install against"
        )
    if not injections:
        return
    dim_names = tuple(active_dim_names) if active_dim_names else ()
    cp_mesh = _get_cp_submesh(dense_mesh, dim_names)
    tp_mesh = _get_tp_submesh(dense_mesh, dim_names)
    ep_mesh = _get_ep_submesh(dense_mesh, dim_names) or expert_mesh
    for rule in injections:
        if rule.get("inner_wrapper") is None:
            continue
        fqn = rule.get("match")
        if not fqn:
            raise ValueError(
                f"hp_apply_inner_wrapper: injection rule {rule!r} has no "
                "'match' FQN"
            )
        entry = (param_plan or {}).get(fqn, {})
        spec = _FrozenBoundarySpec(entry, rule)
        module = _resolve_module(model, fqn)
        if _is_external_state_inline_module(module, generated_module):
            continue
        # The applier's boundary loop runs _resolve_local_compute_fn even for
        # inner-wrap boundaries, and the preflight fails fast on a missing
        # region_dispatch.  Reuse the exact applier entry here for parity.
        _wrap_inner_attention(
            module,
            cp_mesh,
            spec=spec,
            mesh=dense_mesh,
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
            ep_mesh=ep_mesh,
        )


def _has_lowered_forward(module: Any, generated_module: Any = None) -> bool:
    """Return whether the generated source already owns this boundary wrapper."""
    if _is_external_state_inline_module(module, generated_module):
        return True
    return hasattr(module, "_forward_impl")


def _is_tp_sequence_parallel_embed_entry(entry: dict[str, Any]) -> bool:
    """Return whether an embedding output is reduce-scattered over sequence."""
    out_src = (entry.get("out_src") or {}).get("output") or {}
    out_dst = (entry.get("out_dst") or {}).get("output") or {}
    return (
        str(out_src.get("tp", "")).startswith("P(")
        and out_dst.get("tp") == "S(1)"
    )
1306
1307
1308
1309
1310
1311
1312
1313
1314
        and out_dst.get("tp") == "S(1)"
    )


def _wrap_default_position_ids(module: Any) -> None:
    """Build default position ids before an embedding sequence reduce-scatter.

    Transformers commonly derives missing position ids from ``inputs_embeds``.
    A generated model's vocab-parallel embedding may already have changed that
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
    tensor from TP-replicated sequence length to a TP sequence shard.  Supplying
    the default from the still-replicated ``input_ids`` keeps rotary embeddings
    in the attention boundary's declared TP-replicated layout.
    """
    if getattr(module, "_codegen_position_ids_wrapped", False):
        return
    original_forward = module.forward
    signature = inspect.signature(original_forward)
    if (
        "input_ids" not in signature.parameters
        or "position_ids" not in signature.parameters
    ):
        return
    positional_names = [
        name
        for name, parameter in signature.parameters.items()
        if parameter.kind in (
            inspect.Parameter.POSITIONAL_ONLY,
1332
1333
1334
1335
1336
1337
1338
1339
1340
            inspect.Parameter.POSITIONAL_ONLY,
            inspect.Parameter.POSITIONAL_OR_KEYWORD,
        )
    ]
    position_index = (
        positional_names.index("position_ids")
        if "position_ids" in positional_names
        else None
    )
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
        if "position_ids" in positional_names
        else None
    )

    @functools.wraps(original_forward)
    def codegen_position_ids_forward(*args: Any, **kwargs: Any) -> Any:
        """Supply replicated default position ids to the original model forward."""
        bound = signature.bind_partial(*args, **kwargs)
        if bound.arguments.get("position_ids") is not None:
            return original_forward(*args, **kwargs)
        input_ids = bound.arguments.get("input_ids")
        if input_ids is None:
            return original_forward(*args, **kwargs)

        past_key_values = bound.arguments.get("past_key_values")
        past_seen_tokens = (
            past_key_values.get_seq_length() if past_key_values is not None else 0
        )
        import torch  # pylint: disable=C0415

        position_ids = torch.arange(
            int(input_ids.shape[-1]),
            dtype=input_ids.dtype,
            device="cpu",
        )
        position_ids = (position_ids + past_seen_tokens).unsqueeze(0).to(input_ids.device)
        if position_index is not None and position_index < len(args):
            call_args = list(args)
            call_args[position_index] = position_ids
            return original_forward(*call_args, **kwargs)
        call_kwargs = kwargs.copy()
        call_kwargs["position_ids"] = position_ids
        return original_forward(*args, **call_kwargs)

    module.forward = codegen_position_ids_forward
    module._codegen_position_ids_wrapped = True
    module._codegen_original_position_ids_forward = original_forward


def _install_vocab_parallel_embedding(module: Any, tp_mesh: Any) -> None:
    """Install native's D-02 masked-embedding wrapper, once per module.

    The wrapper itself is native
    (``forward_rewriter._wrap_vocab_parallel_embedding``): codegen installs the
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
    implementation. Only the re-install guard is codegen-local —
    ``hp_install_boundaries`` is documented idempotent, while native Phase C
    runs exactly once.
    """
    if getattr(module, "_codegen_vocab_parallel_wrapped", False):
        return
    from hyper_parallel.distributed._builder.forward_rewriter import (  # pylint: disable=C0415
        _wrap_vocab_parallel_embedding,
    )

    _wrap_vocab_parallel_embedding(module, tp_mesh)
    module._codegen_vocab_parallel_wrapped = True


# ---------------------------------------------------------------------------
# Generated entry-point dispatch
1398
1399
1400
1401
1402
1403
1404
1405
1406
# ---------------------------------------------------------------------------
# Generated entry-point dispatch
# ---------------------------------------------------------------------------

def parallelize_from_generated(
    model,
    mesh_context,
    artifact_dir: str,
    *,
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
    ``apply_sharding_plan()``.  Infrastructure validates the metadata before
    dispatch, so this function repeats the check only when called directly
    with ``hf_config``.
    """
    meta = load_codegen_meta(artifact_dir)
    if meta is None:
        raise FileNotFoundError(
            f"codegen: no meta at {os.path.join(artifact_dir, 'codegen_meta.json')}; "
            "cannot parallelize from the generated module"
        )
    if hf_config is not None:
        verify_codegen_signature(meta, hf_config)
    module = import_generated_module(artifact_dir)
    # The artifact's own record of which generated classes take their parallel
    # state externally — declared once by the adapter render spec and carried
    # here in ``meta``.
    register_external_state_classes(module, meta.external_state_classes)
    entry_name = meta.entrypoints.get("parallelize")
    entry = getattr(module, entry_name, None) if entry_name else None
    if entry is not None:
        return entry(model, mesh_context)
    return _parallelize_inline_from_meta(model, mesh_context, meta, module)


def _parallelize_inline_from_meta(model: Any, mesh_context: Any, meta: CodegenMeta, generated_module: Any) -> dict:
    """Parallelize a clean inline artifact whose model file carries no plan globals.

    Execution is convergent with the native applier (see
    :func:`_parallelize_via_native_apply`): Phase A parameter sharding /
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
    B special handlers / D tied weights run against a live ``ShardingPlan``
    rebuilt from the frozen meta; Phase C forward assembly stays codegen-owned
    because the generated file pre-rewrites forward.
    """
    return _parallelize_via_native_apply(model, mesh_context, meta, generated_module)


def _rebuild_live_plan_from_meta(meta: CodegenMeta):
    """Rebuild a live ``ShardingPlan`` from the frozen meta (meta -> plan).

    Proof-of-concept reconstruction: re-parses the frozen literal forms back
    into real ``Placement``/``Target`` objects so native builder helpers
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
    ``inner_out_src``) are reconstructed with the same :func:`_injection_target`
    the existing ``_FrozenBoundarySpec`` uses, so injection fidelity matches
    the status-quo codegen path.
    """
    from hyper_parallel.distributed.plan import ShardingPlan
    from hyper_parallel.distributed.recipe_spec import ModuleShardingSpec
    from hyper_parallel.codegen.plan.spec_fields import (
        INJECTION_SPEC_FIELDS,
        PARAM_PLAN_SPEC_FIELDS,
        POST_INIT_SPEC_FIELDS,
    )
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
        PARAM_PLAN_SPEC_FIELDS,
        POST_INIT_SPEC_FIELDS,
    )

    by_fqn: dict[str, Any] = {}
    for rule in meta.injections or []:
        if isinstance(rule, dict) and rule.get("match"):
            by_fqn[rule["match"]] = rule

    modules: dict[str, Any] = {}
    for fqn, entry in (meta.param_plan or {}).items():
        inj = by_fqn.get(fqn, {})
        kwargs: dict[str, Any] = {
            field.name: _rebuild_spec_field(field, entry)
            for field in PARAM_PLAN_SPEC_FIELDS
            if not field.post_init
        }
        for field in INJECTION_SPEC_FIELDS:
            kwargs[field.name] = _rebuild_spec_field(field, inj)
        spec = ModuleShardingSpec(**kwargs)
        for field in POST_INIT_SPEC_FIELDS:
            value = _rebuild_spec_field(field, entry)
            if value is not None:
                setattr(spec, field.name, value)
        modules[fqn] = spec

    return ShardingPlan(
        modules=modules,
        special_handlers=dict(meta.special_handlers or {}),
        mesh_dim_names=tuple(meta.mesh_dim_names or ()),
        tied_pairs=[tuple(pair) for pair in (meta.tied_pairs or [])],
1511
1512
1513
1514
1515
1516
1517
1518
1519
        tied_pairs=[tuple(pair) for pair in (meta.tied_pairs or [])],
    )


def _rebuild_spec_field(field: Any, source: dict[str, Any]) -> Any:
    """Deserialize one spec field per its :class:`SpecField` kind contract.

    The inverse of freeze's ``_freeze_spec_field``: reads the field's meta
    key from the frozen entry (param-plan fields) or injection rule
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
    ``is_boundary`` (pre-flag metas), ``{}``/``0`` for the EP internals —
    matching what ``_rebuild_live_plan_from_meta`` passed positionally
    before the table drove both sides.
    """
    kind, key = field.rebuild, field.key
    if kind == "placement_map":
        # Key absent/None -> None (not declared); an explicit {} ("shards
        # nothing") must round-trip as {} — Phase A iterates params.items()
        # and a None there is the crash this rebuild must not reintroduce.
        if source.get(key) is None:
            return None
        return parse_named_placement(source[key])
    if kind == "named_placements":
        return parse_named_placement(source[key]) if source.get(key) else None
    if kind in ("name_list", "attr_list"):
        return list(source[key]) if source.get(key) is not None else None
    if kind == "bool_default_true":
        return bool(source[key]) if source.get(key) is not None else True
    if kind == "injection_target":
        return _injection_target(source[key]) if source.get(key) is not None else None
    if kind == "dict_copy":
        return dict(source[key]) if source.get(key) else {}
    if kind == "int_truthy":
        return source.get(key) or 0
    if kind == "tuple_empty":
        # D-22 param paths: absent/empty restores the dataclass default ()
        # — consumers iterate the tuple, so None would crash.
        return tuple(source[key]) if source.get(key) else ()
    # opt_scalar
    return source.get(key)


def _parallelize_via_native_apply(
    model: Any,
    mesh_context: Any,
    meta: CodegenMeta,
    generated_module: Any,
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
    MoE/EP artifacts are supported: native Phase A applies the same
    ``_stack_moe_experts`` step here as it does for a planner-produced plan, so
    dense and MoE topologies share this one reuse path.
    """
    from hyper_parallel.distributed.mesh import MeshContext  # pylint: disable=C0415
    from hyper_parallel.distributed._builder.applier import (  # pylint: disable=C0415
        _apply_plan_special_handlers,
        _get_active_mesh,
        _get_tp_submesh,
    )
    from hyper_parallel.distributed._builder.parameter_sharding import (  # pylint: disable=C0415
        _replicate_tied_weights,
        _resolve_parameter_source_meshes,
        _shard_planned_parameters,
        detect_tied_weights,
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
        _shard_planned_parameters,
        detect_tied_weights,
    )

    plan = _rebuild_live_plan_from_meta(meta)
    mesh_dim_names = plan.mesh_dim_names
    param_plan = meta.param_plan or {}
    injections = hp_expand_injections(meta.injections or [])

    mc = mesh_context if isinstance(mesh_context, MeshContext) else None
    device_mesh = mc.device_mesh if mc is not None else mesh_context
    if device_mesh is None:
        raise ValueError(
            "native A/B/D reuse requires a DeviceMesh / MeshContext"
        )
    mesh = _get_active_mesh(device_mesh, mesh_dim_names)
    tp_mesh = _get_tp_submesh(mesh, mesh_dim_names)
    expert_mesh, dense_source_mesh, expert_source_mesh = (
        _resolve_parameter_source_meshes(plan, mesh_context, device_mesh, tp_mesh)
    )

    # Native A / B / D are executed against the reconstructed live plan.
    _shard_planned_parameters([model], plan, mesh, expert_mesh, validate_mode=False)
    _apply_plan_special_handlers([model], plan, mesh)
    _replicate_tied_weights(model, list(plan.tied_pairs) or detect_tied_weights(model))

    # Native C is not reusable (see docstring) — keep the codegen forward
    # assembly that matches the pre-rewritten generated file.
    hp_install_boundaries(
        model,
        param_plan,
        mesh_context,
        mesh_dim_names,
1626
1627
1628
1629
1630
1631
1632
1633
1634
        mesh_dim_names,
        injections=injections,
        generated_module=generated_module,
    )
    hp_bind_inline_state(
        model,
        mesh_context,
        mesh_dim_names,
        generated_module=generated_module,
1632
1633
1634
1635
1636
1637
1638
1639
1640
        mesh_context,
        mesh_dim_names,
        generated_module=generated_module,
    )
    hp_bind_compute(
        model,
        injections,
        mesh_context,
        mesh_dim_names,
1640
1641
1642
1643
1644
1645
1646
1647
1648
        mesh_dim_names,
        param_plan=param_plan,
        generated_module=generated_module,
    )
    hp_apply_inner_wrapper(
        model,
        injections,
        mesh_context,
        mesh_dim_names,
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
        mesh_dim_names,
        param_plan=param_plan,
        generated_module=generated_module,
    )
    return hp_build_tp_grad_info(
        model, plan, dense_source_mesh, expert_source_mesh
    )


__all__ = [
    "hp_apply_inner_wrapper",
    "hp_bind_compute",
    "hp_bind_inline_state",
    "hp_build_tp_grad_info",
hyper_parallel/codegen/source/__init__.py
 5
 6
 7
 8
 9
10
11
12
13
14

Locate the real ``modeling_*.py`` (installed transformers or checkpoint remote
code) that a generated modeling file is produced from.
"""
from hyper_parallel.codegen.source.compat import sanitize_source_compat
from hyper_parallel.codegen.source.resolver import (
    ResolvedSource,
    import_module_file,
    resolve_hf_source,
    resolve_model_source,
16
17
18
19
20
21
22
23
24
    resolve_remote_siblings,
    resolve_source_identity,
)

__all__ = [
    "ResolvedSource",
    "import_module_file",
    "resolve_hf_source",
    "resolve_model_source",
hyper_parallel/codegen/source/compat.py
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
that does not import it.  Any other stale-import breakage is reported, not
papered over (a new compat rule is an explicit, documented decision).
"""

from __future__ import annotations

import ast
from dataclasses import dataclass

from hyper_parallel.codegen.astkit.edits import TextEdit, apply_edits
from hyper_parallel.codegen.astkit.index import _line_offsets

# The symbol transformers v5.0.0 removed.
_REMOVED = "is_torch_fx_available"

# Spliced where the import statement was.  The guard CALLS the symbol, so this
# must be a callable returning truthy — not a bare True (``if True():`` would
# raise ``'bool' object is not callable``).
_FALLBACK = (
    "# Codegen source compatibility: transformers >= 5.0 removed\n"
    "# this name from ``transformers.utils.import_utils``; torch >= 2.x imports\n"
    "# ``torch.fx`` unconditionally, so this (4.x-era) source's guard is always\n"
    "# true.  Keep it callable so the guard resolves and stays truthful here.\n"
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
    "    return True\n"
)


@dataclass
class _Stmt:
    """One ``from`` import statement, resolved to byte spans and symbol names."""

    start: int  # byte offset of the ``from`` keyword
    end: int  # byte offset just past the statement's trailing newline
    module: str
    symbols: list[tuple[str, str | None]]


def sanitize_source_compat(text: str) -> str:
    """Drop ``is_torch_fx_available`` imports from a modeling source.

    Returns ``text`` unchanged when the source does not import the symbol (the
    common case — byte-for-byte).  When a statement imports it, that statement is
75
76
77
78
79
80
81
82
83
    common case — byte-for-byte).  When a statement imports it, that statement is
    dropped and a module-scope callable fallback is spliced at its position (so
    the symbol is bound before any guard below).  Other imports are untouched.
    """
    offenders = [
        stmt
        for stmt in _import_statements(text)
        if stmt.module in {"transformers.utils", "transformers.utils.import_utils"}
        and any(name == _REMOVED for name, _ in stmt.symbols)
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
        for stmt in _import_statements(text)
        if stmt.module in {"transformers.utils", "transformers.utils.import_utils"}
        and any(name == _REMOVED for name, _ in stmt.symbols)
    ]
    if not offenders:
        return text

    edits = [TextEdit(stmt.start, stmt.end, _replacement(stmt)) for stmt in offenders]
    return apply_edits(text, edits)


def _replacement(stmt: _Stmt) -> str:
    """Render the surviving names from one affected import plus the fallback."""
    remaining = [(name, alias) for name, alias in stmt.symbols if name != _REMOVED]
    if not remaining:
        return _FALLBACK
    rendered = ", ".join(
        f"{name} as {alias}" if alias is not None else name for name, alias in remaining
    )
    return f"from {stmt.module} import {rendered}\n{_FALLBACK}"


# --- parse + locate offending import statements --------------------------------
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126

# --- parse + locate offending import statements --------------------------------


def _import_statements(text: str) -> list[_Stmt]:
    """Return top-level ``from ... import ...`` statements with source spans."""
    try:
        tree = ast.parse(text)
    except SyntaxError:
        return []

    offsets = _line_offsets(text)
    statements = []
    for node in tree.body:
        if not isinstance(node, ast.ImportFrom) or node.module is None:
            continue
        start = offsets[node.lineno - 1] + node.col_offset
        end = offsets[node.end_lineno - 1] + node.end_col_offset
        if end < len(text) and text[end] == "\n":
            end += 1
        statements.append(
            _Stmt(
                start=start,
                end=end,
                module=node.module,
126
127
128
129
130
                module=node.module,
                symbols=[(alias.name, alias.asname) for alias in node.names],
            )
        )
    return statements
hyper_parallel/codegen/source/resolver.py
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
``transformers`` at module scope: the codegen manager runs before the model is
built, and source resolution must stay functional (degrading) in environments
without torch/transformers.
"""
from __future__ import annotations

import importlib.util
import json
import logging
import os
import re
from dataclasses import dataclass
from types import SimpleNamespace
from typing import Any, Optional

from hyper_parallel.codegen.hash import file_line_count, sha256_file

logger = logging.getLogger(__name__)


@dataclass
class ResolvedSource:
    """A located modeling source file, ready to be copied into a bundle."""

    module_name: str
    file_path: str
    architecture: Optional[str] = None
    model_type: Optional[str] = None
    sha256: Optional[str] = None
    line_count: Optional[int] = None

    def to_dict(self) -> dict[str, Any]:
        """JSON-friendly form for ``CodegenMeta.source``."""
        return {
            "module_name": self.module_name,
            "file_path": self.file_path,
            "architecture": self.architecture,
            "model_type": self.model_type,
62
63
64
65
66
67
68
69
            "line_count": self.line_count,
        }


def resolve_model_source(
    config: Any, model_id: str, *, hf_config: Any = None,
) -> Optional[ResolvedSource]:
    """Resolve the modeling source for a model id, degrading gracefully.
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
    Returns ``None`` when nothing resolved, so the caller decides whether to
    fail (codegen ``gen`` backend must not run without a source) or degrade
    (``hf`` backend records the unresolved ``SourceSpec`` projection).
    """
    if hf_config is not None:
        return resolve_hf_source(config, hf_config)
    cfg = _load_auto_config(model_id, config)
    if cfg is not None:
        try:
            return resolve_hf_source(config, cfg)
        except FileNotFoundError:
            # AutoConfig resolved but no modeling file exists anywhere (not
            # even remote code in the checkpoint) — keep degrading.
            pass
    source = resolve_source_identity(model_id)
    if source is not None:
        return source
    return _resolve_source_from_yaml(config, model_id)


def resolve_hf_source(config: Any, hf_config: Any) -> ResolvedSource:
    """Resolve the modeling source for a resolved HF config.

    Priority mirrors what the hyper trainer actually loads, which is plain
    transformers ``from_pretrained`` semantics (see auto_model._init_model ->
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
    3. Otherwise remote code, if any (a checkpoint whose model_type has no
       installed module; HF would require trust_remote_code to load it, so we
       only reach here to still produce a source for generation).
    """
    arch = _first_architecture(hf_config)
    model_type = getattr(hf_config, "model_type", None)
    has_remote_code = bool(getattr(hf_config, "auto_map", None))
    trust_remote_code = _trust_remote_code(config)
    model_path = _model_path_of(config)

    module_name = _module_for_model_type(model_type)
    installed_path = import_module_file(module_name) if module_name else None

    # 1. Trusted remote code is authoritative — HF loads the checkpoint's own
    #    modeling file over any installed module.
    if has_remote_code and trust_remote_code:
        remote_path = resolve_remote_code_file(model_path, hf_config)
        if remote_path is not None:
            logger.debug("source: resolved to trusted remote code %s", remote_path)
            return _build_resolved_source(remote_path, "<remote>", arch, model_type)
        raise FileNotFoundError(
            f"codegen: trust_remote_code is set and the config for "
            f"{model_path or model_type!r} declares auto_map "
            f"({list(getattr(hf_config, 'auto_map', {}) or {})}), but no "
            "modeling_*.py was found in the checkpoint dir or the loaded "
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
    # 2. Custom-registered model — what hyper_parallel's CUSTOM/GEN backends
    #    load for a registered architecture. The registry entry is
    #    authoritative over an installed transformers module of the same
    #    model_type: backend resolution prefers the registered class.
    custom_module = _custom_registered_module(hf_config)
    if custom_module is not None:
        custom_path = import_module_file(custom_module)
        if custom_path is None:
            raise FileNotFoundError(
                f"codegen: architecture {arch!r} is registered to module "
                f"{custom_module!r} but its file could not be located"
            )
        logger.debug(
            "source: resolved architecture=%r to registered module %s",
            arch, custom_path,
        )
        return _build_resolved_source(custom_path, custom_module, arch, model_type)

    # 3. Installed transformers module — what HF loads when remote code is not
    #    trusted (or not present).
    if installed_path is not None:
        logger.debug(
            "source: resolved model_type=%r to installed module %s",
            model_type, installed_path,
        )
        return _build_resolved_source(installed_path, module_name, arch, model_type)

    # 4. Remote code without an installed module (HF would need
    #    trust_remote_code, but we can still locate the file for generation).
    if has_remote_code:
        remote_path = resolve_remote_code_file(model_path, hf_config)
        if remote_path is not None:
            logger.debug(
                "source: resolved to remote code %s (no installed module)",
                remote_path,
            )
            return _build_resolved_source(remote_path, "<remote>", arch, model_type)

    raise FileNotFoundError(
        f"codegen: cannot resolve modeling source for model_type={model_type!r}, "
        f"architecture={arch!r} (no installed transformers module, no remote code)"
    )
185
186
187
188
189
190
191
192
193
        f"architecture={arch!r} (no installed transformers module, no remote code)"
    )


def _custom_registered_module(hf_config: Any) -> Optional[str]:
    """Consult hyper_parallel's custom-model registry for the modeling module.

    ``MODEL_ARCH_MAPPING`` is filled lazily: a family's ``registration.py``
    self-registers on first import (directory convention, see
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
    self-registers on first import (directory convention, see
    :mod:`hyper_parallel.models.registry`). Call ``get_model_adapter`` first
    so the lookup cannot miss an undiscovered family.
    """
    arch = _first_architecture(hf_config)
    if not arch:
        return None
    try:
        from hyper_parallel.models.registry import (  # pylint: disable=C0415
            MODEL_ARCH_MAPPING,
            get_model_adapter,
        )
    except ImportError:
        return None
    if MODEL_ARCH_MAPPING.get(arch) is None:
        # Either spelling may be the key that discovers the family (mirrors
        # get_is_hf_model's identity loop); stop once the architecture is
        # registered. The architecture goes first: it is the registry key.
        for identity in (arch, getattr(hf_config, "model_type", "")):
            if not identity:
                continue
            get_model_adapter(identity)
            if MODEL_ARCH_MAPPING.get(arch) is not None:
                break
    entry = MODEL_ARCH_MAPPING.get(arch)
    return entry[0] if entry is not None else None


def resolve_source_identity(model_id: str) -> Optional[ResolvedSource]:
    """Best-effort source resolution keyed only on a model id.

    Used as a degradation when no ``hf_config`` / ``AutoConfig`` is available.
    Derives the module name from the model id's basename: model ids usually
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
    carry the repo name the transformers module is keyed on (``DeepSeek-V3``
    -> ``deepseek_v3``).  ``None`` when nothing resolves so the caller falls
    back further (or records the unresolved ``SourceSpec``).
    """
    if not model_id:
        return None
    name = os.path.basename(str(model_id).rstrip("/\\"))
    safe = "".join(c if c.isalnum() or c == "_" else "_" for c in name).lower()
    module_name = _module_for_model_type(safe)
    if module_name is None:
        return None
    file_path = import_module_file(module_name)
    if file_path is None:
        return None
    return ResolvedSource(
        module_name=module_name,
        file_path=file_path,
        architecture=None,
        model_type=safe,
246
247
248
249
250
251
252
253
254
        line_count=file_line_count(file_path),
    )


def resolve_remote_code_file(model_path: str, hf_config: Any) -> Optional[str]:
    """Locate the remote-code modeling file for a checkpoint.

    Remote-code checkpoints carry ``modeling_*.py`` + ``configuration_*.py``
    next to the weights.  Two layouts are handled:
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
    3. The single ``modeling_*.py`` when there is exactly one.

    Returns None when no remote code is reachable.
    """
    for directory in _remote_code_dirs(model_path, hf_config):
        found = _scan_remote_code_dir(directory, hf_config)
        if found is not None:
            return found
    return None


def _remote_code_dirs(model_path: str, hf_config: Any) -> list[str]:
    """Directories that may hold this checkpoint's remote code, best first."""
    dirs: list[str] = []
    if model_path and os.path.isdir(model_path):
        dirs.append(model_path)
    # A remote-code config instance is defined in the cached configuration
    # module (``.../modules/transformers_modules/<repo>/configuration_x.py``),
    # which sits next to the cached modeling file.  This is how a Hub id
    # resolves without downloading anything extra.
    module_name = getattr(type(hf_config), "__module__", "") or ""
    if module_name and module_name not in ("builtins", "types"):
        origin = import_module_file(module_name)
        if origin is None:
            module = __import__("sys").modules.get(module_name)
            origin = getattr(module, "__file__", None) if module else None
        if origin:
            cached_dir = os.path.dirname(origin)
            if cached_dir and cached_dir not in dirs and os.path.isdir(cached_dir):
                dirs.append(cached_dir)
    return dirs


def _scan_remote_code_dir(model_path: str, hf_config: Any) -> Optional[str]:
    """Pick this checkpoint's modeling file out of one directory."""
    try:
        candidates = sorted(
            name for name in os.listdir(model_path)
            if name.startswith("modeling_") and name.endswith(".py")
        )
    except OSError:
        return None
    if not candidates:
        return None

    auto_map = getattr(hf_config, "auto_map", None) or {}
    for value in auto_map.values():
        if not isinstance(value, str) or "." not in value:
            continue
        # "modeling_foo.FooForCausalLM" -> "modeling_foo.py".  Hub auto_map
        # values can be "<repo>--modeling_foo.FooForCausalLM"; keep the tail.
        module_ref = value.split("--")[-1]
        module_file = module_ref.split(".", 1)[0] + ".py"
        if module_file in candidates:
            return os.path.join(model_path, module_file)

    arch = _first_architecture(hf_config)
    if arch:
        for name in candidates:
            if arch in name:
                return os.path.join(model_path, name)
    if len(candidates) == 1:
        return os.path.join(model_path, candidates[0])
    return None


def import_module_file(module_name: str) -> Optional[str]:
    """Resolve a Python module's ``__file__`` without importing it."""
    try:
        spec = importlib.util.find_spec(module_name)
    except (ImportError, ModuleNotFoundError, ValueError, AttributeError):
        return None
    if spec is None or spec.origin is None:
        return None
    if not spec.origin.endswith(".py"):
        return None
    return spec.origin


def _load_auto_config(model_id: str, config: Any) -> Optional[Any]:
    """``AutoConfig.from_pretrained(model_id)`` with lazy import; None on failure.

    ``trust_remote_code`` is forwarded so remote-code checkpoints can load
    their own config classes — mirroring what ``HyperAutoModel.from_pretrained``
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
    their own config classes — mirroring what ``HyperAutoModel.from_pretrained``
    will do at build time.  Any failure (no transformers installed, no network,
    unknown id, remote config raising) returns ``None``; the caller degrades.
    """
    if not model_id:
        return None
    try:
        from transformers import AutoConfig
    except ImportError:
        return None
    kwargs: dict[str, Any] = {}
    if _trust_remote_code(config):
        kwargs["trust_remote_code"] = True
    try:
        return AutoConfig.from_pretrained(model_id, **kwargs)
    except Exception:
        return None


def _resolve_source_from_yaml(config: Any, model_id: str) -> Optional[ResolvedSource]:
    """Best-effort source resolution from a local checkpoint's ``config.json``.

    Used when ``AutoConfig`` and the basename guess both came up empty: for a
    local checkpoint directory, read its ``config.json`` and resolve against
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
    it.  ``SimpleNamespace`` attribute access mirrors what
    ``AutoConfig.from_pretrained`` produces, without importing transformers.
    Returns ``None`` on any failure so the caller degrades.
    """
    if not model_id or not os.path.isdir(model_id):
        return None
    config_path = os.path.join(model_id, "config.json")
    if not os.path.isfile(config_path):
        return None
    try:
        with open(config_path, "r", encoding="utf-8") as handle:
            raw = json.load(handle)
        hf_config = SimpleNamespace(**raw)
        return resolve_hf_source(config, hf_config)
    except (OSError, ValueError, FileNotFoundError, ImportError):
        return None


def _module_for_model_type(model_type: Optional[str]) -> Optional[str]:
    """Map ``config.model_type`` to a transformers ``modeling_*`` module name."""
    if not model_type:
        return None
    safe = "".join(c if c.isalnum() or c == "_" else "_" for c in model_type)
    return f"transformers.models.{safe}.modeling_{safe}"


def _first_architecture(hf_config: Any) -> Optional[str]:
    architectures = getattr(hf_config, "architectures", None) or []
    return architectures[0] if architectures else None


def _model_path_of(config: Any) -> str:
    """Extract the checkpoint dir / HF id from the model target.

    Same key order as ``project_source_spec``: most YAMLs and
    ``HyperAutoModel.from_pretrained`` configure the id under
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
    Same key order as ``project_source_spec``: most YAMLs and
    ``HyperAutoModel.from_pretrained`` configure the id under
    ``pretrained_model_name_or_path``, with ``model_name_or_path`` as fallback.
    """
    model = getattr(config, "model", None)
    if model is None:
        return ""
    path = getattr(model, "pretrained_model_name_or_path", None)
    if path is None:
        path = getattr(model, "model_name_or_path", None)
    if path is None:
        path = getattr(model, "_kwargs", {}).get("pretrained_model_name_or_path", "")
    return path or ""


def _trust_remote_code(config: Any) -> bool:
    """Whether this model is loaded with trusted remote code.

    The truth is the YAML's ``model.trust_remote_code`` — it is what the
    trainer forwards into ``AutoConfig.from_pretrained`` / the HF
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
    the checkpoint's own modeling file or an installed transformers module.
    The ``TrainerConfig`` object itself has no such field, so this must read
    through the model target.
    """
    model = getattr(config, "model", None)
    if model is None:
        return False
    value = getattr(model, "trust_remote_code", None)
    if value is None:
        value = getattr(model, "_kwargs", {}).get("trust_remote_code", False)
    return bool(value)


def _relative_imports_of(path: str) -> list[str]:
    """Names a file relatively imports from its own package (one level).

    Mirrors ``transformers.dynamic_module_utils.get_relative_imports`` without
    importing transformers (source resolution must stay dependency-free).  Only
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
    ``import .name`` resolves within the same package directory, which is what
    a ``<remote>`` modeling source ships.  Multi-dot relatives (``..x``) do not
    resolve against a flat remote code dir and are ignored.
    """
    try:
        with open(path, "r", encoding="utf-8", errors="replace") as handle:
            content = handle.read()
    except OSError:
        return []
    names = re.findall(r"(?m)^\s*import\s+\.([A-Za-z_][A-Za-z0-9_]*)\s*$", content)
    names += re.findall(r"(?m)^\s*from\s+\.([A-Za-z_][A-Za-z0-9_]*)\s+import", content)
    return sorted(set(names))


def resolve_remote_siblings(source: ResolvedSource) -> dict[str, str]:
    """Transitive closure of a ``<remote>`` source's package siblings.

    A trust-remote-code modeling file is compiled as the top-level module of a
    synthetic package (loader.import_generated_module), so its single-dot
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
    that ``modeling_deepseek.py`` imports (and any module *it* relatively
    imports).  The caller copies these into the bundle so the artifact is
    self-contained; the loader registers them under the synthetic package.
    """
    if isinstance(source, dict):
        module_name = source.get("module_name")
        file_path = source.get("file_path")
    else:
        module_name = getattr(source, "module_name", None)
        file_path = getattr(source, "file_path", None)
    if module_name != "<remote>" or not file_path:
        return {}
    base_dir = os.path.dirname(os.path.abspath(file_path))
    seen: dict[str, str] = {}
    queue: list[str] = [os.path.abspath(file_path)]
    while queue:
        module_file = queue.pop(0)
        for name in _relative_imports_of(module_file):
            sibling = os.path.join(base_dir, f"{name}.py")
            if not os.path.isfile(sibling):
                continue
            if sibling in seen:
                continue
            seen[sibling] = sibling
            queue.append(sibling)
    return seen


def _build_resolved_source(
    file_path: str, module_name: str, arch: Optional[str], model_type: Optional[str],
) -> ResolvedSource:
    return ResolvedSource(
        module_name=module_name,
        file_path=file_path,
        architecture=arch,
        model_type=model_type,
512
513
514
515
516
517
518
519
520
        line_count=file_line_count(file_path),
    )


__all__ = [
    "ResolvedSource",
    "import_module_file",
    "resolve_hf_source",
    "resolve_model_source",
hyper_parallel/codegen/spec/project.py
48
49
50
51
52
53
54
55
56
57
58
59
60
61

    ``config`` may be a ``TrainerConfig`` (or any object exposing the same
    attributes) so the manager can call this without importing trainer.
    """
    if layout is None:
        if yaml_path is None:
            raise ValueError("project_codegen_spec requires yaml_path or layout")
        layout = resolve_artifact_layout(yaml_path, model_name=_model_name(config))
    source_spec = project_source_spec(config)
    return CodegenSpec(
        source=source_spec,
        parallel_dims=project_parallel_dims(config),
        overrides=project_overrides(config),
        transform_specs=project_transform_specs(config),
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
    Target holds its id under the key the caller actually used — most YAMLs and
    ``HyperAutoModel.from_pretrained`` use ``pretrained_model_name_or_path`` —
    so read that first and fall back to ``model_name_or_path``.
    """
    model = getattr(config, "model", None)
    model_name_or_path = getattr(model, "pretrained_model_name_or_path", None)
    if model_name_or_path is None:
        model_name_or_path = getattr(model, "model_name_or_path", None)
    if model_name_or_path is None:
        # Target configured without a positional model id — the HF path will
        # derive it from from_pretrained's first arg at load time.
        model_name_or_path = ""
    return SourceSpec(
        model_name_or_path=model_name_or_path,
        architecture=None,
        model_type=None,
        sha256=None,
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111


def project_parallel_dims(config: Any) -> ParallelDimsSpec:
    """Project ``accelerator.tp_size/cp_size/ep_size/pp_size`` etc."""
    accelerator = getattr(config, "accelerator", None)
    if accelerator is None:
        return ParallelDimsSpec()
    kwargs: dict[str, Any] = {}
    for key in _PARALLEL_DIM_KEYS:
        if key == "dp_size":
            kwargs[key] = _project_dp_size(accelerator)
        else:
            kwargs[key] = getattr(accelerator, key, ParallelDimsSpec().__dataclass_fields__[key].default)
    return ParallelDimsSpec(**kwargs)


def project_overrides(config: Any) -> list[dict[str, Any]]:
    """Project ``plan_overrides`` to stable canonical dicts.
113
114
115
116
117
118
119
120
121
122
    The canonicalization lives here: keep the declarative fields, resolve
    ``Target`` values to their ``to_dict()`` form, and drop ``None`` fields so
    the canonical form is stable under YAML default omissions.
    """
    entries = getattr(config, "plan_overrides", None) or []
    return [_canonicalize_override(entry) for entry in entries]


def project_transform_specs(config: Any) -> TransformSpec:
    """Project codegen-affecting model-rewrite config.
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165

    TODO: Populate transform specs when operator, weight, and checkpoint
    transforms are supported.
    """
    model = getattr(config, "model", None)
    config_overrides = getattr(model, "config_overrides", None)
    extra = {}
    if config_overrides:
        extra["config_overrides"] = _canonicalize_value(config_overrides)
    return TransformSpec(extra_codegen_options=extra)


def _model_name(config: Any) -> Optional[str]:
    """Best-effort model name for artifact file naming."""
    model = getattr(config, "model", None)
    path = getattr(model, "_target_", None)
    if isinstance(path, str):
        name = path.rsplit(".", 1)[-1]
        if name.startswith(("AutoModel", "Pretrained", "from_")):
            return None
        return name
    return getattr(config, "model_name", None)


def _project_dp_size(accelerator: Any) -> int:
    """Derive runtime DP size from ``WORLD_SIZE`` and non-DP topology."""
    explicit = getattr(accelerator, "dp_size", None)
    if explicit:
        return max(1, int(explicit))
    world_size = int(os.environ.get("WORLD_SIZE") or os.environ.get("OMPI_COMM_WORLD_SIZE") or 1)
    tp_size = max(1, int(getattr(accelerator, "tp_size", 1)))
    cp_size = max(1, int(getattr(accelerator, "cp_size", 1)))
    pp_size = max(1, int(getattr(accelerator, "pp_size", 1)))
    non_dp_size = tp_size * cp_size * pp_size
    if world_size % non_dp_size != 0:
        raise ValueError(
            f"world_size {world_size} is not divisible by non-DP size {non_dp_size}"
        )
    return max(1, world_size // non_dp_size)


def _canonicalize_override(entry: Any) -> dict[str, Any]:
    """Stable dict form of one PlanOverride entry.
166
167
168
169
170
171
172
173
174
175

    Resolves Target values instead of stashing raw ``repr()``.
    ``replace_module`` is projected so changes invalidate the signature.
    """
    result: dict[str, Any] = {"match": entry.match}
    for attr in (
        "when",
        "module_type",
        "exact_type",
        "inner_target",
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
        "inner_target",
        "region_dispatch",
        "tp_divide_attrs",
    ):
        value = getattr(entry, attr, None)
        if value is not None:
            result[attr] = value
    for attr in ("inner_wrapper", "replace_module", "local_compute_fn"):
        value = getattr(entry, attr, None)
        if value is not None:
            result[attr] = _canonicalize_value(value)
    for attr in ("params", "in_src", "in_dst", "out_src", "out_dst"):
        value = getattr(entry, attr, None)
        if value is not None:
            result[attr] = _canonicalize_value(value)
    return result


def _canonicalize_value(value: Any) -> Any:
    """Resolve Target objects to dict form; recurse into containers."""
    if hasattr(value, "to_dict"):
        return value.to_dict()
    if isinstance(value, dict):
        return {key: _canonicalize_value(item) for key, item in value.items()}
    if isinstance(value, (list, tuple)):
        return [_canonicalize_value(item) for item in value]
    return value


__all__ = [
    "project_codegen_spec",
hyper_parallel/codegen/spec/types.py
72
73
74
75
76
77
78
79
80
    target: TargetSpec = field(default_factory=TargetSpec)

    def to_dict(self) -> dict[str, Any]:
        """Serialize to a JSON-friendly dict (for signatures and meta)."""
        return {
            "source": asdict(self.source),
            "parallel_dims": asdict(self.parallel_dims),
            "overrides": list(self.overrides),
            "transform_specs": asdict(self.transform_specs),
hyper_parallel/components/modules/__init__.py
74
75
76
77
78


def __dir__() -> list[str]:
    """Include lazy public exports in ``dir()``."""
    return sorted(set(globals()) | set(__all__))
hyper_parallel/components/modules/grouped_experts.py
280
281
282
283
284
285
286
287
288
            raise ValueError("GroupedExperts requires concatenated gate/up expert weights")
        self.is_transposed = True
        # Keep HF native initialization order stable; this replacement module
        # resets its packed expert parameters after materialization instead.
        self._hp_reset_after_materialization = True
        self.train(module.training)

    def _initialize_projection_layout(
        self, source_gate_up, source_down, source_is_transposed, gated_linear_unit,
hyper_parallel/distributed/_builder/applier.py
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
    ``ep``) returns itself with the intersecting names; a mesh that HAS the
    axes but cannot be sliced raises — never silently returns the unsliced
    mesh (that would shard along a wrong axis).
    """
    if not mesh_dim_names:
        return mesh, ()
    names = tuple(getattr(mesh, "mesh_dim_names", ()) or ())
    if not names:
        return mesh, tuple(mesh_dim_names)
    # Mesh-major order: keep only the axes the plan shards on, reordered to
    # the mesh's own declared order (so `mesh[tuple(...)]` indices ascend).
    active = tuple(n for n in names if n in mesh_dim_names)
    if not active:
        return mesh, ()
    # Only skip slicing when the mesh declares EXACTLY the plan's axes (in the
    # same order).  Comparing against ``names`` (not the filtered ``active``)
    # prevents a mesh that also carries non-plan axes -- e.g. a ('dp', 'tp')
    # mesh vs a ('tp',) frozen plan -- from returning the unsliced 2-D mesh
325
326
327
328
329
330
331
332
333
334
    # mesh vs a ('tp',) frozen plan -- from returning the unsliced 2-D mesh
    # alongside a 1-D placement axis.  When the mesh carries any extra axis the
    # plan does not shard on, it must be sliced down to ``active`` so the dense
    # mesh dimension count always matches the placement tuple.
    if tuple(names) == tuple(mesh_dim_names):
        return mesh, tuple(mesh_dim_names)
    # The plan may name an axis the mesh does not carry (e.g. a plan frozen for
    # an ``ep`` axis on a mesh that derives EP from the dense region).  Never
    # silently return the unsliced mesh with a dim name that does not exist on
    # it — that would resolve placements against a phantom axis and corrupt
333
334
335
336
337
338
339
340
341
    # silently return the unsliced mesh with a dim name that does not exist on
    # it — that would resolve placements against a phantom axis and corrupt
    # every downstream DTensor layout.  Slice to the overlapping active axes
    # and keep the same mesh-major names.
    return mesh[active], active


def _get_tp_submesh(mesh, mesh_dim_names):
    if "tp" not in mesh_dim_names:
hyper_parallel/distributed/_builder/forward_rewriter.py
605
606
607
608
609
610
611
612
613
def _rewrap_local_outputs(output, spec, mesh, mesh_dim_names, module_name):
    """Wrap every declared local Tensor output with its ``out_src`` layout."""
    declared = spec.out_src or {}
    if not declared:
        return output

    out_names = list(getattr(spec, "out_names", None) or declared.keys())
    name_to_idx = {name: index for index, name in enumerate(out_names)}
    entries = []
613
614
615
616
617
618
619
620
621
    entries = []
    for out_name, named_placement in declared.items():
        index = name_to_idx.get(out_name)
        if index is None:
            raise ValueError(
                f"{module_name}: out_src declares output {out_name!r}, but "
                f"out_names={out_names!r} does not contain it"
            )
        entries.append((index, out_name, named_placement))
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
    is still a DTensor -- ``DTensor.shape`` is the global shape and would give
    a wrong interval -- so the local shard is read explicitly.
    """
    original_forward = module.forward
    weight = module.weight
    to_local = getattr(weight, "to_local", None)
    local_weight = to_local() if callable(to_local) else weight
    v_local = local_weight.shape[0]
    lo = tp_mesh.get_local_rank() * v_local
    hi = lo + v_local

    @functools.wraps(original_forward)
hyper_parallel/distributed/_builder/tp_collective_lowering.py
56
57
58
59
60
61
62
63
64
    """
    if tuple(src) == tuple(dst):
        return {"kind": "identity"}
    if "tp" not in mesh_dim_names:
        return None
    tp_axis = tuple(mesh_dim_names).index("tp")
    if any(
        axis != tp_axis and src_placement != dst_placement
        for axis, (src_placement, dst_placement) in enumerate(zip(src, dst))
70
71
72
73
74
75
76
77
78
79
80
81
82
    if isinstance(src_tp, Shard) and isinstance(dst_tp, Replicate):
        return {"kind": "all_gather", "tensor_dim": src_tp.dim}
    if isinstance(src_tp, Partial) and isinstance(dst_tp, Replicate):
        if src_tp.reduce_op != "sum":
            return None
        return {"kind": "all_reduce", "reduce_op": "sum"}
    if isinstance(src_tp, Partial) and isinstance(dst_tp, Shard):
        if src_tp.reduce_op != "sum":
            return None
        return {
            "kind": "reduce_scatter",
            "tensor_dim": dst_tp.dim,
            "reduce_op": "sum",
hyper_parallel/distributed/expert_parallel/archetypes.py
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
literals; the manager reuses the same single source for the compute-factory
``_target_``.
"""

from __future__ import annotations

from dataclasses import dataclass

#: Structural fingerprint fields, mirroring ``MoeStructure``.
FINGERPRINT_ROUTER = 0
FINGERPRINT_SHARED = 1
FINGERPRINT_STORAGE = 2


@dataclass(frozen=True)
class MoeArchetype:
    """Inline EP declarations that structure detection proves for a MoE."""

    #: Structure-keyed strategy ``kind`` (not a model-family dispatch literal).
    kind: str
    #: The MoE block class name (a structural result, not a family anchor).
    #: Its forward is fully inlined, so it is also an external-state class.
    target_class: str
    strip_boundary_subpatterns: tuple[str, ...]
    #: Runtime router-adapter key the generic ``moe_ep_forward`` resolves
    #: through ``MOE_ROUTER_ADAPTERS``. This is the *merge contract* key and
    #: may differ from the detection fingerprint's router field (for example a
    #: grouped-sigmoid family whose gate module is detected as a top-k router
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
    #: through ``MOE_ROUTER_ADAPTERS``. This is the *merge contract* key and
    #: may differ from the detection fingerprint's router field (for example a
    #: grouped-sigmoid family whose gate module is detected as a top-k router
    #: module still merges with the ``sigmoid_group`` adapter).
    router_kind: str
    shared: str
    #: The importable compute-factory ``_target_`` the YAML/native path anchors.
    compute_factory: str


_MOE_ARCHETYPE_BY_STRUCTURE = {
    ("topk_router_module", "none", "batched_parameters"): MoeArchetype(
        kind="moe_ep_routed",
        target_class="Qwen3MoeSparseMoeBlock",
        strip_boundary_subpatterns=(),
79
80
81
82
83
84
85
86
87
    ),
}


def moe_archetype(
    router: str,
    shared_experts: str,
    expert_storage: str,
    *,
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
    *,
    default: MoeArchetype | None = None,
) -> MoeArchetype | None:
    """Look up the EP archetype for a MoE structural fingerprint."""
    return _MOE_ARCHETYPE_BY_STRUCTURE.get(
        (router, shared_experts, expert_storage), default
    )


def moe_archetypes() -> dict[tuple[str, str, str], MoeArchetype]:
    """Return the framework structure->archetype table (read-only by convention)."""
    return dict(_MOE_ARCHETYPE_BY_STRUCTURE)


def ep_compute_factory_for(router: str, shared_experts: str, expert_storage: str) -> str | None:
    """Return the importable compute-factory ``_target_`` for a fingerprint."""
    archetype = moe_archetype(router, shared_experts, expert_storage)
    return archetype.compute_factory if archetype is not None else None


def moe_external_state_classes(router: str, shared_experts: str, expert_storage: str) -> tuple[str, ...]:
    """Return the external-state classes structure proves for a MoE fingerprint.

    A MoE whose forward is fully inlined takes its parallel state externally,
    so its own block class is an external-state class. Any additional classes
111
112
113
114
115
116
117
118
119
120
121
122
123
    so its own block class is an external-state class. Any additional classes
    the same inline pipeline generates (for example a replaced attention
    component) are contributed by their own declarations, not here.
    """
    archetype = moe_archetype(router, shared_experts, expert_storage)
    return (archetype.target_class,) if archetype is not None else ()


__all__ = [
    "MoeArchetype",
    "ep_compute_factory_for",
    "moe_archetype",
    "moe_archetypes",
hyper_parallel/distributed/expert_parallel/experts.py
1401
1402
1403
1404
1405
1406
1407
1408
1409
    ``module.shared_experts(...)`` is a planned nested TP boundary: its exit
    already performs the TP reduction (nested-boundary call contract) — do NOT
    all-reduce its return value.
    """
    return routed + module.shared_experts(hidden_states)


def _merge_shared_gated(
    module: Any,
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473

    Keeps the same numeric contract as ``ep_routed_forward`` plus the
    ``combine`` step of the runtime EP recipes.
    """
    merge = SHARED_MERGE_MODES.get(shared)
    if merge is None:
        raise ValueError(
            f"moe_ep_forward: unknown shared merge mode {shared!r} "
            f"(expected one of {sorted(SHARED_MERGE_MODES)})"
        )
    routed = ep_routed_forward(
        module,
        hidden_states,
        router_fn=MOE_ROUTER_ADAPTERS[router_kind],
        ep_group=ep_group,
1471
1472
1473
1474
1475
1476
1477
1478
1479
        hidden_states,
        router_fn=MOE_ROUTER_ADAPTERS[router_kind],
        ep_group=ep_group,
    )
    return merge(module, hidden_states, routed)


def require_attrs(module: Any, *names: str, owner: str = "") -> None:
    """Assert that ``module`` has every attribute in ``names``; raise a
hyper_parallel/distributed/expert_parallel/structure.py
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
# limitations under the License.
# ============================================================================
"""Conservative generation-time recognition of supported MoE structures."""

import ast
from dataclasses import dataclass
import inspect
import textwrap
from typing import Any

from torch import nn


class UnsupportedModuleStructure(ValueError):
    """A matched module has no proven parallel recipe."""


@dataclass(frozen=True)
class MoeStructure:
    """Serializable structural decisions frozen before distributed execution."""

    router: str
    shared_experts: str
    expert_storage: str
    jitter: bool = False


def _unsupported(module: Any, detail: str) -> UnsupportedModuleStructure:
    return UnsupportedModuleStructure(
        f"{type(module).__name__}: {detail}. Declare an explicit "
        "local_compute_fn._target_ to opt out of structure detection."
    )
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
        "local_compute_fn._target_ to opt out of structure detection."
    )


def _forward_tree(module: Any) -> ast.FunctionDef:
    try:
        source = textwrap.dedent(inspect.getsource(type(module).forward))
        tree = ast.parse(source)
    except (AttributeError, OSError, TypeError, SyntaxError) as exc:
        raise _unsupported(module, "forward source is unavailable") from exc
    functions = [node for node in tree.body if isinstance(node, ast.FunctionDef)]
    if len(functions) != 1:
        raise _unsupported(module, "forward source is ambiguous")
    return functions[0]


def detect_router_kind(module: Any) -> str:
    """Recognize a router without invoking it or consulting a model identity.

    Tuple routers keep their own scoring algorithm, including grouped sigmoid
    routing. Recomputing scores from their first return value would lose gate
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
    Tuple routers keep their own scoring algorithm, including grouped sigmoid
    routing. Recomputing scores from their first return value would lose gate
    configuration and risks applying scaling twice.
    """
    gate = getattr(module, "gate", None)
    if gate is None:
        gate = getattr(module, "router", None)
    if gate is None:
        raise _unsupported(module, "missing gate/router")
    if type(gate) is nn.Linear:  # pylint: disable=C0123  # exact match: subclasses take the AST path
        config = getattr(module, "config", None)
        if not any(getattr(owner, name, None) is not None
                   for owner in (module, config) for name in ("top_k", "num_experts_per_tok")):
            raise _unsupported(module, "linear router has no explicit top-k setting")
        # A bare-linear gate feeds either softmax top-k routing or
        # group-limited sigmoid routing; both gates are literally nn.Linear.
        # The merge contract is told apart by the sigmoid/group markers the
        # MOE declares on itself or its config (DeepSeek-V3 / GLM-4-MoE keep
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
        # MOE declares on itself or its config (DeepSeek-V3 / GLM-4-MoE keep
        # routed_scaling_factor + n_group + topk_group; plain softmax routers
        # carry none). This mirrors the runtime router adapters, which key on
        # the same attributes.
        if any(getattr(owner, name, None) is not None
               for owner in (module, config) for name in ("routed_scaling_factor", "n_group", "topk_group")):
            return "sigmoid_group"
        return "softmax_topk"
    forward = _forward_tree(gate)
    returns = [node.value for node in ast.walk(forward) if isinstance(node, ast.Return)]
    calls = {node.func.attr for node in ast.walk(forward)
             if isinstance(node, ast.Call) and isinstance(node.func, ast.Attribute)}
    index_names = _topk_index_names(forward)
    if (returns and all(isinstance(value, ast.Tuple) and len(value.elts) == 3
                        and isinstance(value.elts[2], ast.Name)
                        and value.elts[2].id in index_names for value in returns)
            and "topk" in calls and "linear" in calls):
        return "topk_router_module"
    raise _unsupported(module, "router does not expose a supported logits/scores/indices contract")


def _topk_index_names(forward: ast.FunctionDef) -> set[str]:
    """Prove the final tuple item comes from a top-k index result."""
    names = set()
    for node in ast.walk(forward):
        if not isinstance(node, ast.Assign) or len(node.targets) != 1:
            continue
        target, value = node.targets[0], node.value
        if (isinstance(target, (ast.Tuple, ast.List)) and len(target.elts) == 2
                and isinstance(target.elts[1], ast.Name) and _is_topk_call(value)):
            names.add(target.elts[1].id)
        elif (isinstance(target, ast.Name) and isinstance(value, ast.Subscript)
              and isinstance(value.slice, ast.Constant) and value.slice.value == 1
              and _is_topk_call(value.value)):
            names.add(target.id)
    return names


def _is_topk_call(value: ast.AST) -> bool:
    return (isinstance(value, ast.Call) and isinstance(value.func, ast.Attribute)
            and value.func.attr == "topk")


def detect_moe_structure(module: Any) -> MoeStructure:
    """Freeze the router, shared branch and expert parameter storage contract."""
    experts = getattr(module, "experts", None)
    if experts is None:
        raise _unsupported(module, "missing experts")
    if isinstance(experts, nn.ModuleList):
        if not experts or not all(all(hasattr(expert, name) for name in
                                     ("gate_proj", "up_proj", "down_proj")) for expert in experts):
            raise _unsupported(module, "expert list is not a SwiGLU projection list")
        storage = "module_list"
    elif all(isinstance(getattr(experts, name, None), nn.Parameter)
             and getattr(experts, name).ndim == 3 for name in ("gate_up_proj", "down_proj")):
        storage = "batched_parameters"
    else:
        raise _unsupported(module, "unsupported expert parameter storage")
    shared_names = {name for name in ("shared_expert", "shared_expert_gate", "shared_experts")
                    if getattr(module, name, None) is not None}
    if not shared_names:
        shared = "none"
    elif shared_names == {"shared_expert", "shared_expert_gate"}:
        shared = "gated"
    elif shared_names == {"shared_experts"}:
        shared = "additive"
    else:
        raise _unsupported(module, f"ambiguous shared expert branches {sorted(shared_names)}")
    return MoeStructure(detect_router_kind(module), shared, storage, hasattr(module, "jitter_noise"))