Coverage for / home / jenkins / .local / lib / python3.10 / site-packages / hyper_parallel / codegen / manager.py: 92%
48 statements
« prev ^ index » next coverage.py v7.13.1, created at 2026-11-15 07:18 +0800
« prev ^ index » next coverage.py v7.13.1, created at 2026-11-15 07:18 +0800
1# Copyright 2026 Huawei Technologies Co., Ltd
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14# ============================================================================
15"""Generate and atomically publish single-process codegen bundles.
17The manager is the only entry point the trainer / CLI talks to. It does not
18perform AST patching or source emitting itself; those operations belong to
19the ``emit`` layer. Every invocation emits and atomically replaces the bundle
20in the current process.
21"""
23from __future__ import annotations
25import logging
26from typing import Optional
28from hyper_parallel.codegen.artifact import (
29 ArtifactLayout,
30 artifact_generation_lock,
31 clean_temp_artifacts,
32 ensure_artifact_dir,
33 resolve_default_artifact_layout,
34 resolve_model_artifact_layout,
35 write_bundle_atomic,
36)
37from hyper_parallel.codegen.emit import emit_bundle
38from hyper_parallel.codegen.emit.replacement import replacement_records_from_snapshot
39from hyper_parallel.codegen.emit.snapshot_patches import (
40 emission_patches_from_snapshot_payload,
41)
42from hyper_parallel.codegen.emit.context import EmissionContext
43from hyper_parallel.codegen.snapshot import CodegenSnapshot, CodegenStatus
44from hyper_parallel.models.registry import (
45 artifact_model_type,
46 family_dir_for_model_type,
47)
49logger = logging.getLogger(__name__)
52def ensure_codegen_artifact_from_snapshot(
53 snapshot: CodegenSnapshot,
54 artifact_dir: Optional[str] = None,
55 *,
56 timeout_s: float = 600.0,
57) -> ArtifactLayout:
58 """Generate and replace a bundle in the current single process.
60 This entry must remain independent of config projection, meta-model
61 construction, offline meshes, replacement matching, and planner calls.
62 Distributed launchers must use a dedicated sidecar instead of invoking
63 this function independently on every training rank.
64 """
65 if not isinstance(snapshot, CodegenSnapshot):
66 raise TypeError("snapshot must be a CodegenSnapshot")
67 if snapshot.status not in (CodegenStatus.DERIVED, CodegenStatus.NOT_REQUIRED):
68 raise ValueError(
69 "snapshot-driven generation requires status 'derived' or 'not_required', "
70 f"got {snapshot.status.value!r}"
71 )
72 source = snapshot.source
73 if not source.get("file_path"):
74 raise ValueError("snapshot source must include a modeling file_path")
75 model_type = source.get("model_type")
76 public_model_type = artifact_model_type(str(model_type)) if model_type else None
77 model_name = (
78 _model_type_to_modeling_token(str(public_model_type))
79 if model_type
80 else source.get("architecture")
81 )
82 if artifact_dir is None and source.get("model_type"):
83 layout = resolve_model_artifact_layout(
84 family_dir_for_model_type(str(source["model_type"])),
85 model_name=str(model_name) if model_name else None,
86 )
87 else:
88 layout = resolve_default_artifact_layout(
89 artifact_dir,
90 model_name=str(model_name) if model_name else None,
91 )
92 ensure_artifact_dir(layout)
94 with artifact_generation_lock(layout, timeout_s=timeout_s):
95 clean_temp_artifacts(layout)
96 logger.info("codegen: generating artifact %s", layout.artifact_dir)
97 context = _build_emission_context(snapshot)
98 files = emit_bundle(layout, context)
99 write_bundle_atomic(layout, files)
100 return layout
103def _build_emission_context(snapshot: CodegenSnapshot) -> EmissionContext:
104 """Map one immutable observation into an in-memory emission context."""
105 payload = snapshot._emission_view() # pylint: disable=protected-access
106 source = dict(payload["source"])
107 status = snapshot.status
108 frozen_plan = dict(payload["plan"] or {})
109 module_overrides = list(replacement_records_from_snapshot(payload))
110 is_derived = status is CodegenStatus.DERIVED
111 emission_patches = (
112 emission_patches_from_snapshot_payload(payload) if is_derived else {}
113 )
114 return EmissionContext(
115 source=source,
116 parallel_dims=dict(frozen_plan.get("parallel_dims") or {}),
117 status=payload["status"],
118 injections=list(frozen_plan.get("injections") or ()),
119 param_plan=dict(frozen_plan.get("param_plan") or {}),
120 boundary_classes=dict(frozen_plan.get("boundary_classes") or {}),
121 nested_components=list(frozen_plan.get("nested_components") or ()),
122 mesh_dim_names=list(frozen_plan.get("mesh_dim_names") or ()) or None,
123 module_overrides=(module_overrides if is_derived else []),
124 emission_patches=emission_patches,
125 )
128def _model_type_to_modeling_token(model_type: str) -> str:
129 """Convert HF ``model_type`` into the generated modeling file token."""
130 parts = [part for part in model_type.replace("-", "_").split("_") if part]
131 if not parts:
132 return "model"
133 return "_".join(part[:1].upper() + part[1:] for part in parts)
136__all__ = [
137 "ensure_codegen_artifact_from_snapshot",
138]