Diff Coverage

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

Source File Diff Coverage (%) Missing Lines
hyper_parallel/codegen/build.py 0.0% 108-109
hyper_parallel/codegen/model_build_overlay.py 8.5% 98-99,101-104,106,108-109,119,132-134,136-141,149,151-158,160-161,165-166,190,193,271,280,282-285,287,291,316
hyper_parallel/components/modules/__init__.py 100%  
hyper_parallel/components/modules/family.py 26.1% 113,115-119,128-130,229-231,233,235-238,241-246,264-267,283-289
hyper_parallel/components/modules/module_family.py 28.6% 62-63,74-78,95,132-143,148-162,167-187,200-208,217-228,233-242,255-266,295-306,308,310-312,321-322,327-335,341-344,353-358,368-375,383-388,393-403,405,407,412-417,419,424,430,432-436
hyper_parallel/core/fully_shard/state_dict_utils.py 9.1% 300,516,682,832,880,882,888,1482,1518,1612
hyper_parallel/distributed/_builder/applier.py 10.0% 139-140,166-167,377-378,382-384
hyper_parallel/distributed/_builder/family_layout.py 15.0% 51,55,59-65,69-77,82-102,106,111-124,129-139,144-157,159-161,166-172,174-188,199-210,212-228,232,234-237,249-279,285-286,288-292,294-296,299-306
hyper_parallel/distributed/_builder/fsdp_adapter.py 11.8% 85-95,367-368,384-385
hyper_parallel/distributed/_builder/parameter_sharding.py 7.1% 217,229,266-269,327,330-331,334-335,340-341
hyper_parallel/distributed/_builder/planner.py 22.2% 297,307,427,432,434,469-473,485,527,677,1502
hyper_parallel/distributed/_builder/source_shard.py 12.5% 155,283-288
hyper_parallel/distributed/apply.py 16.7% 68,123-127,151-153,164,167-169,171-172
hyper_parallel/distributed/context_parallel/cp_context.py 61.0% 39,44,56,64-68,76-78,87-88,97,107,117
hyper_parallel/distributed/expert_parallel/ep_context.py 38.2% 57-66,74-82,85-87,97-103,105-112,122-131
hyper_parallel/distributed/mesh.py 100%  
hyper_parallel/distributed/module_binding.py 35.0% 47-54,66-70,85-92,113-132,137-141,143-148,159-169,171-174,176-180,189-192
hyper_parallel/distributed/plan.py 100%  
hyper_parallel/trainer/config/parallelism.py 8.9% 375-376,381-387,397,411,413,415-416,436,655-665,667-669,671-672,679,805,808,812,816,852,899,906-907,912
hyper_parallel/trainer/config/replacement.py 25.0% 85-90
hyper_parallel/codegen/build.py
104
105
106
107
108
109
110
111
112
113
        mesh_shape=(layout.dp_size, topology_spec.cp_size, topology_spec.tp_size),
    )
    mesh_context = MeshContext.from_layout(layout)
    mesh_context.device_mesh = topology
    if topology_spec.ep_size > 1:
        mesh_context.fsdp_moe_mesh = LogicalDeviceMesh(
            mesh_dim_names=("edp", "ep"),
            mesh_shape=(topology.size() // topology_spec.ep_size, topology_spec.ep_size),
        )
    return DistributedSetup.from_config(
hyper_parallel/codegen/model_build_overlay.py
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
    load_base_model: bool,
    session: CodegenBuildSession,
) -> Any:
    """Apply native replacement declarations while recording their result."""
    specs = list(getattr(distributed_setup, "module_replacements", None) or ())
    original_compile = module_build.compile_module_replacements

    def _observe_compile(*args: Any, **kwargs: Any) -> Any:
        plan = original_compile(*args, **kwargs)
        specs[:] = plan.specs
        return plan

    module_build.compile_module_replacements = _observe_compile
    try:
        with _observe_replacement_installations(specs) as applications:
            model, _ = model_builder._maybe_apply_module_replacements(  # pylint: disable=protected-access
                model, distributed_setup, low_precision_config, load_base_model,
            )
    except replacement_api.ModuleReplacementMatchError as exc:
        session.record_replacements(
115
116
117
118
119
120
121
122
123
            applications=(), unmatched=exc.unmatched,
        )
        raise
    finally:
        module_build.compile_module_replacements = original_compile
    session.record_replacements(
        declarations=replacement_api.describe_replacement_declarations(specs),
        applications=applications, unmatched=(),
    )
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
def _observe_replacement_installations(specs: Sequence[Any]) -> Iterator[list[dict[str, Any]]]:
    """Record metadata only after the shared installer commits a prepared candidate."""
    applications: list[dict[str, Any]] = []
    spec_indexes = {id(spec): index for index, spec in enumerate(specs)}
    pending = {}
    original_prepare = replacement_api._prepare_replacement_target  # pylint: disable=protected-access
    original_install = replacement_api._install_module_candidates  # pylint: disable=protected-access

    def _observe_prepare(target: Any, *args: Any, **kwargs: Any) -> Any:
        candidate, transforms = original_prepare(target, *args, **kwargs)
        if id(target.spec) not in spec_indexes:
            spec_indexes.update({id(spec): index for index, spec in enumerate(specs)})
        index = spec_indexes.setdefault(id(target.spec), len(spec_indexes))
        pending[(target.module_fqns, id(candidate))] = {
            "rule_id": replacement_api._replacement_rule_id(target.spec, index),  # pylint: disable=protected-access
            "module_fqns": list(target.module_fqns),
            "primary_fqn": target.module_fqns[0],
            "source_type": replacement_api._symbol_path(type(target.source)),
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
            "source_type": replacement_api._symbol_path(type(target.source)),
            "replacement_type": replacement_api._symbol_path(type(candidate)),
            "weight_transforms": [replacement_api._symbol_path(type(transform)) for transform in transforms],
        }
        return candidate, transforms

    def _observe_install(root: Any, candidates: Any) -> Any:
        candidates = tuple(candidates)
        result = original_install(root, candidates)
        for fqns, _, candidate in candidates:
            record = pending.pop((fqns, id(candidate)), None)
            if record is not None:
                applications.append(record)
        return result

    replacement_api._prepare_replacement_target = _observe_prepare  # pylint: disable=protected-access
    replacement_api._install_module_candidates = _observe_install  # pylint: disable=protected-access
    try:
        yield applications
    finally:
        replacement_api._prepare_replacement_target = original_prepare  # pylint: disable=protected-access
        replacement_api._install_module_candidates = original_install  # pylint: disable=protected-access


def _observe_plan(
    model: Any,
186
187
188
189
190
191
192
193
194
195
196
197
        return
    if mesh.device_mesh is None:
        session.mark_not_required("device_mesh_unavailable")
        return
    if (is_hf_model and not model_sharding_requested
            and not model_builder._has_module_owned_layouts(model)):  # pylint: disable=protected-access
        if session.has_replacement_applications:
            session.observe_plan(None, None, mesh)
        else:
            session.mark_not_required("pure_data_parallel")
        return
    plan = sharding_planner.plan(
267
268
269
270
271
272
273
274
275
            swap_inputs,
            allow_cache_entry_mutation,
            activation_swap,
        )
        factory, context = auto_model._resolve_module_construction(  # pylint: disable=protected-access
            kwargs, hf_config, distributed_setup, model_init_dtype or torch_dtype,
        )
        init_kwargs = auto_model._model_init_kwargs(  # pylint: disable=protected-access
            dict(kwargs),
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
            load_base_model=False,
        )
        if "trust_remote_code" in kwargs:
            init_kwargs["trust_remote_code"] = kwargs["trust_remote_code"]
        _prepare_model_source_compat(context["model_spec"])
        with _codegen_model_init_context():
            if factory is not None:
                if model_args:
                    raise ValueError("model_factory uses config and context, not additional positional model arguments")
                model = auto_model.construct_family_model(factory, hf_config, context=context)
            else:
                _, model = auto_model._init_model(  # pylint: disable=protected-access
                    cls, None, hf_config, attn_implementation, torch_dtype, is_hf_model,
                    *model_args, **init_kwargs,
                )
                auto_model.attach_build_context(model, context)
        _record_source(
            session,
            model,
            hf_config,
312
313
314
315
316
317
318
319
320
            model_builder._apply_parameter_freezing(  # pylint: disable=protected-access
                model,
                freeze_config,
            )
        model_builder._apply_model_method_replacements(  # pylint: disable=protected-access
            model, distributed_setup, getattr(distributed_setup, "low_precision_config", None),
        )
        _observe_plan(model, mesh, sharding_planner, is_hf_model, session)
        snapshot = _publish_session(session, artifact_dir, result)
hyper_parallel/components/modules/family.py
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
    """Check complete, unique whitelist entries before registration."""
    defaults = _validate_selector_specs(family)
    _validate_variant_paths(family)
    if family.parallel_axis not in (None, "cp", "ep"):
        raise ValueError("parallel_axis must be cp, ep or None")
    if family.default_variant is not None:
        if family.default_variant not in family.variants:
            raise ValueError("default_variant must name a declared concrete variant")
        if "parallel" in family.selector_specs:
            raise ValueError("parallel is derived from context, not a public selector")
        defaults = {**defaults, "parallel": False}
    conditions = [defaults]  # The default combination is an explicit baseline allowance.
    for rule in family.variant_rules:
        if not isinstance(rule, VariantRule) or not isinstance(rule.when, Mapping):
            raise TypeError(f"ModuleFamily {family.name}: expected VariantRule with a mapping condition")
124
125
126
127
128
129
130
131
132
133
134
        if set(rule.when) != set(defaults):
            raise ValueError(f"ModuleFamily {family.name}: each rule must contain exactly {sorted(defaults)}")
        for name, value in rule.when.items():
            if name == "parallel" and family.default_variant is not None:
                actual_type = type(value)
                if actual_type is not bool:
                    raise TypeError("derived parallel conditions must be bool")
            else:
                _validate_selector_value(family, name, value)
        if rule.variant not in family.variants:
            raise ValueError(f"ModuleFamily {family.name}: undeclared variant {rule.variant!r}")
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
    selectors = {name: args.pop(name, spec.default) for name, spec in family.selector_specs.items()}
    for name, value in selectors.items():
        _validate_selector_value(family, name, value)
    defaults = {name: spec.default for name, spec in family.selector_specs.items()}
    if family.default_variant is not None:
        selectors["parallel"] = parallel
        defaults["parallel"] = False
    if selectors == defaults:
        variant = family.default_variant
    else:
        matches = [rule.variant for rule in family.variant_rules if selectors == dict(rule.when)]
        if len(matches) != 1:
            legal = [defaults, *(dict(rule.when) for rule in family.variant_rules)]
            raise ValueError(
                f"ModuleFamily {family.name}: unsupported selector combination {selectors}; legal combinations: {legal}"
            )
        variant = matches[0]
    constructor = family.baseline
    if variant is not None:
        module_path, _, name = family.variants[variant].rpartition(".")
        constructor = getattr(import_module(module_path), name)
    return constructor, variant, selectors


def resolve_module_family_target(target: Callable, arguments: Mapping) -> tuple[Callable, dict]:
    """Select a registered implementation and validate its ordinary keywords.
260
261
262
263
264
265
266
267
268
269
270
271
    args = normalize_family_selectors(target, arguments)
    family = get_module_family(target)
    if family is None:
        return target, args
    if family.default_variant is not None:
        _validate_deferred_arguments(family, args)
        return target, args
    target, _, _ = _select_variant(family, args)
    target_signature = signature(target)
    allowed = {
        name
        for name, parameter in target_signature.parameters.items()
279
280
281
282
283
284
285
286
287
288
289


def _validate_deferred_arguments(family: ModuleFamily, args: Mapping[str, Any]) -> None:
    """Validate a new-family request without choosing a topology-dependent member."""
    allowed = set(signature(family.baseline).parameters) - {"module", "module_fqn", "context", "mesh"}
    unknown = set(args) - allowed
    if unknown:
        raise TypeError(f"ModuleFamily {family.name}: unknown replacement arguments: {sorted(unknown)}")
    for name in family.selector_specs:
        if name in args:
            _validate_selector_value(family, name, args[name])
hyper_parallel/components/modules/module_family.py
58
59
60
61
62
63
64
65
66
67
    sequence_parallel: bool = False
    loss_parallel: bool = False

    def __post_init__(self) -> None:
        object.__setattr__(self, "axis_sizes", MappingProxyType(dict(self.axis_sizes)))
        object.__setattr__(self, "parameter_shapes", MappingProxyType(dict(self.parameter_shapes)))


@dataclass(frozen=True)
class FamilyRequest:
70
71
72
73
74
75
76
77
78
79
80
81
82
    target: Callable
    kwargs: Mapping[str, Any]

    def __post_init__(self) -> None:
        family = get_module_family(self.target)
        if family is None or family.default_variant is None:
            raise ValueError("FamilyRequest requires a family with a concrete default_variant")
        _validate_deferred_arguments(family, self.kwargs)
        object.__setattr__(self, "kwargs", MappingProxyType(deepcopy(dict(self.kwargs))))


@dataclass(frozen=True)
class ResolvedFamily:
91
92
93
94
95
96
97
98
99

    @property
    def sharding(self) -> Callable | None:
        """Return the selected family's layout provider."""
        return self.family.sharding


@dataclass(frozen=True)
class _FamilyLayout:
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


def _axis_sizes(context: Mapping[str, Any]) -> Mapping[str, int]:
    """Read and validate logical parallel degrees from the construction context."""
    result = {}
    for axis in ("tp", "cp", "ep", "pp"):
        size = context.get(f"{axis}_size")
        if size is None:
            if context.get(axis):
                raise ValueError(f"{axis}_size is required when {axis} is active")
            size = 1
        actual_type = type(size)
        if actual_type is not int or size < 1:
            raise ValueError(f"{axis}_size must be a positive integer")
        result[axis] = size
    return MappingProxyType(result)


def _mesh_axis_size(mesh: Any, axis: str) -> int:
    """Validate declared sizes against the actual borrowed axis views."""
    device_mesh = mesh.device_mesh
    region = getattr(mesh, "fsdp_moe_mesh", None) if axis == "ep" else None
    if region is None:
        region = device_mesh
    names = tuple(getattr(region, "mesh_dim_names", ()) or ())
    actual = region[axis].size() if axis in names else 1
    size = getattr(mesh, f"{axis}_size", actual)
    actual_type = type(size)
    if actual_type is not int or size < 1:
        raise ValueError(f"{axis}_size must be a positive integer")
    if axis in names and size != actual:
        raise ValueError(f"{axis}_size={size} disagrees with the mesh size {actual}")
    if axis not in names and size > 1 and axis != "pp":
        raise ValueError(f"{axis}_size={size} requires an axis named {axis!r}; available mesh axes: {names!r}")
    return size


def _context_from_mesh(mesh: Any, context: Mapping[str, Any] | None) -> Mapping[str, Any]:
    """Read actual job axes without copying mesh resources into a request."""
    result = dict(context or {})
    if mesh is None:
        mesh = result.get("mesh")
        if getattr(mesh, "device_mesh", None) is None:
            return result
    device_mesh = getattr(mesh, "device_mesh", None)
    if device_mesh is None:
        raise ValueError("mesh requires a prebuilt device_mesh")
    previous = result.get("mesh")
    if previous is not None and not _same_mesh(previous, mesh):
        raise ValueError("context and mesh belong to different jobs")
    for axis in ("tp", "cp", "ep", "pp"):
        size = _mesh_axis_size(mesh, axis)
        if f"{axis}_size" in result and result[f"{axis}_size"] != size:
            raise ValueError(f"context and mesh disagree on {axis}_size")
        result[f"{axis}_size"] = size
        result[axis] = size > 1
    result["mesh"] = mesh
    result["sequence_parallel"] = bool(getattr(mesh, "sequence_parallel", False))
    result["loss_parallel"] = bool(getattr(mesh, "loss_parallel", False))
    return result


def resolve_family(request: FamilyRequest, *, context: Mapping[str, Any] | None = None) -> ResolvedFamily:
    """Resolve exactly one allowlisted member using the current job context.
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211

    Returns:
        Concrete construction information without allocating model resources.
    """
    family = get_module_family(request.target)
    context = context or {}
    axes = _axis_sizes(context)
    args = deepcopy(dict(request.kwargs))
    parallel = family.parallel_axis is not None and axes[family.parallel_axis] > 1
    constructor, variant, selectors = _select_variant(family, args, parallel=parallel)
    if constructor is family.baseline:
        raise ValueError("a concrete family member cannot be its public factory")
    return ResolvedFamily(
        family, variant, constructor, MappingProxyType(args), MappingProxyType(selectors), axes,
    )

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
242
243
244
245
246
def _constructor_arguments(
    member: ResolvedFamily, config: Any, module: nn.Module | None, fqn: str, context: Mapping[str, Any],
) -> dict[str, Any]:
    """Bind source or config construction arguments accepted by the selected member."""
    signature = inspect.signature(member.constructor)
    required = "config" if module is None else "module"
    if required not in signature.parameters:
        raise TypeError(f"member {member.variant!r} must explicitly accept {required}")
    kwargs = dict(member.arguments)
    unknown = set(kwargs) - set(signature.parameters)
    if unknown:
        raise TypeError(f"member {member.variant!r} does not accept {sorted(unknown)}")
    runtime = {"config": config, "module": module, "module_fqn": fqn, "context": context}
    kwargs.update({name: value for name, value in runtime.items() if name in signature.parameters})
    signature.bind(**kwargs)
    return kwargs


def _normalize_family_request(target, kwargs):
    """Resolve a public name without selecting a topology-dependent member."""
    if isinstance(target, str):
        matches = [family.baseline for family in _FAMILIES.values() if family.name == target]
        if len(matches) != 1:
            raise ValueError(f"family name {target!r} must identify exactly one registration")
        target = matches[0]
    if isinstance(target, FamilyRequest):
        if kwargs:
            raise ValueError("request arguments cannot be combined with additional keywords")
        return target
    return FamilyRequest(target, kwargs)


def _build_family_member(
    request: FamilyRequest,
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
    module_fqn: str = "",
    context: Mapping[str, Any],
) -> nn.Module:
    """Construct an already resolved choice without parsing the same request again."""
    context = MappingProxyType(dict(context))
    if member.family.validate is not None:
        member.family.validate(config=config, module=module, context=context, member=member)
    config = deepcopy(config)
    result = member.constructor(**_constructor_arguments(member, config, module, module_fqn, context))
    if not isinstance(result, nn.Module):
        raise TypeError("a family constructor must return nn.Module")
    info = FamilyBuildInfo(request, member, deepcopy(config), "direct" if module is None else "replacement")
    result._hp_family_build_info = info  # pylint: disable=protected-access
    if config is not None and callable(getattr(result, "reset_parameters", None)):
        result._hp_reset_after_materialization = True  # pylint: disable=protected-access
    return result


def build_family(
    target: Callable | str | FamilyRequest,
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

    Returns:
        The concrete member with construction provenance. Logical/meta builds finish physical work at runtime.
    """
    request = _normalize_family_request(target, kwargs)
    arguments = dict(request.kwargs)
    if "config" in arguments:
        if config is not None:
            raise ValueError("config was supplied twice")
        config = arguments.pop("config")
        request = FamilyRequest(request.target, arguments)
    if (config is None) == (module is None):
        raise ValueError("provide exactly one of config or module")
    context = _context_from_mesh(mesh, context)
    member = resolve_family(request, context=context)
    result = _build_family_member(request, member, config=config, module=module,
                                 module_fqn=module_fqn, context=context)
    if mesh is not None:
        # Layout discovery imports this module; defer its runtime dependency to avoid a cycle.
        from hyper_parallel.distributed._builder.family_layout import _prepare_family_layout  # pylint: disable=C0415,R0401
        _prepare_family_layout(result, mesh)
    return result


def get_family_build_info(module: nn.Module) -> FamilyBuildInfo | None:
    """Return only framework-created family provenance, or None for ordinary modules.
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339

    Args:
        module: Module whose construction provenance is queried.
    """
    value = getattr(module, "_hp_family_build_info", None)
    return value if isinstance(value, FamilyBuildInfo) else None


def _family_parameter_shapes(model: nn.Module) -> dict[str, tuple[int, ...]]:
    """Return logical parameter shapes even after component construction localized weights."""
    by_identity = {}
    for boundary in collect_family_modules(model):
        layout = boundary.info.layout
        if layout is not None:
            for name, shape in layout.parameter_shapes.items():
                identity = id(boundary.module.get_parameter(name))
                if by_identity.setdefault(identity, shape) != shape:
                    raise ValueError(f"shared family parameter {name!r} has conflicting logical shapes")
    return {name: by_identity.get(id(value), tuple(value.shape))
            for name, value in model.named_parameters(remove_duplicate=False)}


def _module_aliases(model: nn.Module) -> dict[int, tuple[nn.Module, tuple[str, ...]]]:
337
338
339
340
341
342
343
344
345
346
347
348


def _module_aliases(model: nn.Module) -> dict[int, tuple[nn.Module, tuple[str, ...]]]:
    """Inventory registered module identities and all paths, including the root."""
    found: dict[int, tuple[nn.Module, list[str]]] = {}
    for fqn, module in model.named_modules(remove_duplicate=False):
        found.setdefault(id(module), (module, []))[1].append(fqn)
    return {identity: (module, tuple(paths)) for identity, (module, paths) in found.items()}


def collect_family_modules(model: nn.Module) -> tuple[FamilyBoundary, ...]:
    """Discover final family boundaries by object identity, including root and aliases.
349
350
351
352
353
354
355
356
357
358
359
360
361
362

    Args:
        model: Final or source model tree for this build.
    """
    boundaries = []
    for module, paths in _module_aliases(model).values():
        info = get_family_build_info(module)
        if info is not None:
            boundaries.append(FamilyBoundary(module, paths, info))
    return tuple(boundaries)


def validate_family_context(model: nn.Module, context: Mapping[str, Any]) -> None:
    """Validate that model preparation has finalized members for the job context.
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
    Args:
        model: Final or source model tree for this build.
        context: Read-only job context and effective model facts.
    """
    axes = _axis_sizes(_context_from_mesh(None, context))
    for boundary in collect_family_modules(model):
        if boundary.info.layout is not None and boundary.info.layout.applied:
            _context_from_mesh(boundary.info.layout.mesh, context)
        member = boundary.info.resolved
        for axis in ("cp", "ep"):
            if member.axis_sizes[axis] != axes[axis]:
                raise ValueError(
                    f"family at {boundary.fqns} was constructed with {axis}={member.axis_sizes[axis]}, "
                    f"job requires {axes[axis]}; call prepare_model_components with the job context before layout"
                )
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
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428


def _validate_applied_layout(cached: _FamilyLayout, spec: Any, fqn: str) -> None:
    """Reject post-construction changes to a component's declared computation layout."""
    original = cached.sharding
    if any(name not in original.params or named != original.params[name] for name, named in spec.params.items()):
        raise ValueError(f"layout at {fqn!r} conflicts with its constructed family layout")
    for field_name in ("in_src", "in_dst", "out_src", "out_dst", "out_names", "is_boundary"):
        if getattr(spec, field_name) != getattr(original, field_name):
            raise ValueError(f"{field_name} at {fqn!r} conflicts with its constructed family layout")


def _record_family_layouts(model, plan, mesh, shapes, source_shard_info, *, applied=True):
    """Retain relative declarations once for constructor and model-level application."""
    for boundary in collect_family_modules(model):
        fqn = boundary.fqns[0]
        if fqn not in plan.modules or boundary.info.resolved.sharding is None:
            continue
        existing = boundary.info.layout
        if existing is not None and existing.applied:
            continue
        prefix = fqn + "." if fqn else ""
        names = plan.modules[fqn].params
        relative_shapes = {name: shapes[prefix + name] for name in names}
        source = {name: source_shard_info[prefix + name] for name in names
                  if source_shard_info is not None and prefix + name in source_shard_info}
        layout = _FamilyLayout(mesh, deepcopy(plan.modules[fqn]), plan.parallel_modules.get(fqn),
                              MappingProxyType(relative_shapes), MappingProxyType(source), applied)
        boundary.module._hp_family_build_info = replace(boundary.info, layout=layout)  # pylint: disable=protected-access


def _family_source_shard_info(model: nn.Module) -> dict[str, Any]:
    """Gather constructed component source layouts under their final model paths."""
    result = {}
    for boundary in collect_family_modules(model):
        layout = boundary.info.layout
        if layout is not None and layout.applied:
            for fqn in boundary.fqns:
                result.update({f"{fqn}.{name}" if fqn else name: value
                               for name, value in layout.source_shard_info.items()})
    return result


def _same_mesh(first: Any, second: Any) -> bool:
    """Compare borrowed job views without copying or reconstructing their groups."""
    return (getattr(first, "device_mesh", first) is getattr(second, "device_mesh", second)
            and getattr(first, "fsdp_moe_mesh", None) is getattr(second, "fsdp_moe_mesh", None))


def _family_mesh(model: nn.Module, mesh: Any) -> Any:
426
427
428
429
430
431
432
433
434
435
436


def _family_mesh(model: nn.Module, mesh: Any) -> Any:
    """Reuse the complete context when a caller passes its dense DeviceMesh view."""
    layouts = [boundary.info.layout for boundary in collect_family_modules(model)
               if boundary.info.layout is not None and boundary.info.layout.applied]
    if not hasattr(mesh, "device_mesh"):
        mesh = next((layout.mesh for layout in layouts if layout.mesh.device_mesh is mesh), mesh)
    if any(not _same_mesh(layout.mesh, mesh) for layout in layouts):
        raise ValueError("constructed families cannot be applied with a different job mesh")
    return mesh
hyper_parallel/core/fully_shard/state_dict_utils.py
296
297
298
299
300
301
302
303
304
        )
    else:
        scattered = state_dict

    model.load_state_dict(scattered, strict=options.strict, assign=assign)


# =====================================================================
# Optimizer state dict helpers
512
513
514
515
516
517
518
519
520
    raw_sd: Dict[str, Any],
    saved_id_to_fqn: Dict[int, str],
) -> List[Dict[str, Any]]:
    """Build result param_groups with FQN keys from saved id-based groups."""
    del optimizer
    result_param_groups: List[Dict[str, Any]] = []
    for saved_group in raw_sd["param_groups"]:
        pg: Dict[str, Any] = {}
        for k, v in saved_group.items():
678
679
680
681
682
683
684
685
686

    Returns:
        Tuple of (fqn_to_saved_id, dtensor_state_ids).
    """
    del param_by_fqn
    target_raw_sd = optimizer.state_dict()

    target_id_to_param: Dict[int, nn.Parameter] = {}
    for runtime_group, saved_group in zip(optimizer.param_groups, target_raw_sd["param_groups"]):
828
829
830
831
832
833
834
835
836


def _optimizer_tensor_result(tensor: DTensor, stores_dtensor: bool) -> Union[torch.Tensor, DTensor]:
    """Keep global layout only when the target optimizer stores DTensor values."""
    return tensor if stores_dtensor else tensor.to_local()


def _convert_input_tensor_to_target(
    tensor: torch.Tensor,
876
877
878
879
880
881
882
883
884
885
886
    target_device = param.to_local().device

    if isinstance(tensor, DTensor):
        if tensor.device_mesh == mesh and tensor.placements == placements:
            return _optimizer_tensor_result(tensor, stores_dtensor)
        redistributed = tensor.redistribute(mesh, placements)
        return _optimizer_tensor_result(redistributed, stores_dtensor)

    if full_state_dict:
        if cpu_offload:
            tensor = tensor.to(target_device)
884
885
886
887
888
889
890
891
    if full_state_dict:
        if cpu_offload:
            tensor = tensor.to(target_device)
        dt = distribute_tensor(tensor, mesh, placements)
        return _optimizer_tensor_result(dt, stores_dtensor)

    if cpu_offload and tensor.device != target_device:
        tensor = tensor.to(target_device)
1478
1479
1480
1481
1482
1483
1484
1485
1486
    cpu_offload: bool,
) -> Optional[Tuple[str, str, torch.Tensor]]:
    """Parse a state.* metadata entry and return (fqn, state_key, tensor) or None."""
    # DCP re-exports these state helpers; defer metadata to avoid that package import cycle.
    from hyper_parallel.core.distributed_checkpoint.metadata import (  # pylint: disable=import-outside-toplevel
        TensorStorageMetadata,
    )

    remainder = meta_key[len("state."):]
1514
1515
1516
1517
1518
1519
1520
1521
1522
    meta_val: Any,
) -> Optional[Tuple[int, str, torch.Tensor]]:
    """Parse a param_group.* metadata entry and return (group_idx, field_name, tensor) or None."""
    # DCP re-exports these state helpers; defer metadata to avoid that package import cycle.
    from hyper_parallel.core.distributed_checkpoint.metadata import (  # pylint: disable=import-outside-toplevel
        TensorStorageMetadata,
    )

    remainder = meta_key[len("param_group."):]
1608
1609
1610
1611
1612
1613
1614
1615
1616
        state.<FQN>.<state_key>  -> TensorStorageMetadata
        param_group.<FQN>.<field> -> TensorStorageMetadata or BytesStorageMetadata
    """
    # DCP re-exports these state helpers; defer metadata to avoid that package import cycle.
    from hyper_parallel.core.distributed_checkpoint.metadata import (  # pylint: disable=import-outside-toplevel
        TensorStorageMetadata,
    )

    flat: Dict[str, Any] = {}
hyper_parallel/distributed/_builder/applier.py
135
136
137
138
139
140
141
142
143
144
    """Require an explicit forward replacement for CP attention boundaries."""
    if cp_mesh is None or cp_mesh.size() <= 1:
        return
    for fqn, spec in plan.modules.items():
        if fqn in plan.parallel_modules:
            continue
        if not spec.is_boundary or not getattr(spec, "_needs_cp_attn", False):
            continue
        if spec.inner_wrapper is not None or spec.local_compute_fn is not None:
            continue
162
163
164
165
166
167
168
169
170
171
    from hyper_parallel.distributed.expert_parallel.recipes import (  # pylint: disable=C0415
        EP_ARCHETYPE_SUGGESTIONS,
    )
    for fqn, spec in plan.modules.items():
        if fqn in plan.parallel_modules:
            continue
        if not spec.is_boundary or not getattr(spec, "_ep_size", 0):
            continue
        if spec.local_compute_fn is not None or spec.region_dispatch is False:
            continue
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
        else create_tp_collective_lowerer(mesh, mesh_dim_names)
    )
    for module_fqn, spec in sorted(
            plan.modules.items(), key=_boundary_post_order_key):
        if module_fqn in plan.parallel_modules:
            continue
        if not spec.is_boundary:
            continue
        module = _resolve_module(model, module_fqn)
        family = get_family_build_info(module)
        if family is not None and family.layout is not None:
            continue
        boundary = PrecompiledBoundary(
            spec,
            mesh,
            mesh_dim_names,
hyper_parallel/distributed/_builder/family_layout.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
 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
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
    aliases: list[tuple[str, str]] = field(default_factory=list)


def _full(fqn, name):
    return f"{fqn}.{name}" if fqn else name


def _is_child(fqn, parent):
    return fqn.startswith(parent + ".") if parent else bool(fqn)


def _module_parallel(module, member):
    provider = getattr(module, "parallel_module_spec", None)
    declaration = provider() if provider is not None else None
    if declaration is not None and not isinstance(declaration, ParallelModuleSpec):
        raise TypeError("parallel_module_spec() must return ParallelModuleSpec")
    if member is not None and member.selectors["parallel"] and declaration is None:
        raise ValueError("a parallel family member must declare its explicit execution protocol")
    return declaration


def _add_provider(entries, model, fqn, sharding, parallel):
    module = model.get_submodule(fqn)
    identity = id(module)
    if identity in entries:
        raise ValueError(f"multiple layout/communication owners for module {fqn!r}")
    if not isinstance(sharding, ModuleShardingSpec):
        raise TypeError("layout providers must return ModuleShardingSpec")
    sharding = copy.deepcopy(sharding)
    _normalize_out_fields(sharding)
    entries[identity] = (fqn, module, sharding, parallel)


def _native_providers(entries, model, spec, context, boundaries):
    """Locate model-owned sharding providers for native parallel components."""
    if spec is None or spec.native_parallel is None:
        return
    requests = spec.native_parallel(model=model, context=context)
    all_modules = tuple(model.named_modules(remove_duplicate=False))
    family_subtrees = {id(child) for boundary in boundaries for child in boundary.module.modules()}
    for request in requests:
        if not isinstance(request, NativeParallelBindingRequest):
            raise TypeError("native_parallel must return NativeParallelBindingRequest objects")
        matched = set()
        for pattern in request.match:
            hits = [(fqn, module) for fqn, module in all_modules if fnmatch.fnmatchcase(fqn, pattern)]
            if not hits:
                raise ValueError(f"native binding pattern {pattern!r} matched no module")
            for fqn, module in hits:
                if id(module) in matched:
                    continue
                if _overlaps_family(module, family_subtrees):
                    raise ValueError(f"native binding at {fqn!r} overlaps a family-owned subtree")
                matched.add(id(module))
                layout = request.sharding(module=module, module_fqn=fqn, member=None, context=context)
                _add_provider(entries, model, fqn, layout, request.parallel)


def _overlaps_family(module, family_subtrees):
    return any(id(child) in family_subtrees for child in module.modules())


def _validate_ports(module, spec):
    """Validate named input and output layout declarations against the module interface."""
    injection_fields = ("local_compute_fn", "inner_wrapper", "inner_target", "region_dispatch")
    if any(getattr(spec, name) is not None for name in injection_fields):
        raise ValueError("module-owned layouts cannot install legacy compute injection")
    if spec.params is None or spec.in_src is None or spec.in_dst is None:
        raise ValueError("module-owned providers must declare params, in_src and in_dst explicitly")
    if set(spec.in_src) != set(spec.in_dst):
        raise ValueError("module-owned in_src and in_dst must declare the same input names")
    signature = inspect.signature(module.forward)
    accepts_kwargs = any(item.kind == inspect.Parameter.VAR_KEYWORD for item in signature.parameters.values())
    unknown = set(spec.in_src) - set(signature.parameters)
    if unknown and not accepts_kwargs:
        raise ValueError(f"layout declares unknown forward inputs {sorted(unknown)}")
    if spec.out_names is not None and len(set(spec.out_names)) != len(spec.out_names):
        raise ValueError("output names must be unique")


def _validate_placements(spec, context):
    """Reject unknown mesh axes and incompatible parameter placement declarations."""
    mappings = [*spec.params.values(), *spec.in_src.values(), *spec.in_dst.values()]
    mappings.extend((spec.out_src or {}).values())
    mappings.extend((spec.out_dst or {}).values())
    for named in mappings:
        for axis, placement in named.items():
            if axis not in context.axis_sizes:
                raise ValueError(f"module layouts cannot declare storage/unknown axis {axis!r}")
            if not hasattr(placement, "is_shard"):
                raise TypeError("module providers must return concrete Placement objects")
    if any(isinstance(placement, Partial) for named in spec.params.values() for placement in named.values()):
        raise ValueError("parameter source layouts cannot be Partial")


def _claim_layout_parameters(fqn, parameters, spec, children, owner_by_id, canonical_by_id):
    """Assign each Parameter identity once and verify every alias has the same layout."""
    declared = set()
    for name, placement in tuple(spec.params.items()):
        parameter = parameters.get(name)
        if parameter is None:
            raise ValueError(f"layout parameter {_full(fqn, name)!r} does not exist")
        full_name = _full(fqn, name)
        if any(full_name.startswith(child + ".") for child in children):
            raise ValueError(f"parent layout duplicates nested module ownership at {full_name!r}")
        identity = id(parameter)
        declared.add(identity)
        if identity in owner_by_id:
            if owner_by_id[identity] != placement:
                raise ValueError(f"shared parameter {full_name!r} has conflicting layouts")
            del spec.params[name]
        else:
            owner_by_id[identity] = placement
            canonical_by_id[identity] = full_name
    return declared


def _assign_parameters(result, entries, model):
    """Assign one owned layout per logical parameter while retaining shared aliases."""
    all_parameters = dict(model.named_parameters(remove_duplicate=False))
    owner_by_id = {}
    canonical_by_id = {}
    for fqn, module, spec, _ in entries.values():
        parameters = dict(module.named_parameters(remove_duplicate=False))
        children = [name for name, *_ in entries.values() if _is_child(name, fqn)]
        expected = {id(parameter) for name, parameter in parameters.items()
                    if not any(_full(fqn, name).startswith(child + ".") for child in children)}
        declared = _claim_layout_parameters(fqn, parameters, spec, children, owner_by_id, canonical_by_id)
        if expected - declared:
            missing = [name for name, parameter in parameters.items() if id(parameter) in expected - declared]
            raise ValueError(f"module-owned layout {fqn!r} omits parameters {missing}")
        result.expected_ids.update(expected)
        result.specs[fqn] = spec
        result.required_by_fqn[fqn] = {id(parameters[name]) for name in spec.params}
    for name, parameter in all_parameters.items():
        identity = id(parameter)
        if identity not in owner_by_id:
            continue
        result.owned_names.add(name)
        canonical = canonical_by_id[identity]
        if canonical != name:
            result.aliases.append((canonical, name))


def collect_owned_layouts(model: nn.Module, context: Any, spec: Any = None) -> OwnedLayouts:
    """Discover model/native/family providers independently of generic TP templates.
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
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
        model: Final logical model, before any parameter sharding.
        context: FamilyShardingContext with logical axes and global parameter shapes.
        spec: Effective per-build model specification, if any.
    """
    entries = {}
    if spec is not None and spec.sharding is not None:
        for fqn, layout in spec.sharding(model=model, context=context).items():
            _add_provider(entries, model, fqn, layout, _module_parallel(model.get_submodule(fqn), None))
    boundaries = collect_family_modules(model)
    for boundary in boundaries:
        member = boundary.info.resolved
        if member.sharding is None:
            continue
        fqn = boundary.fqns[0]
        cached = boundary.info.layout
        layout = (cached.sharding if cached is not None else
                  member.sharding(module=boundary.module, module_fqn=fqn, member=member, context=context))
        entries.pop(id(boundary.module), None)
        parallel = cached.parallel if cached is not None else _module_parallel(boundary.module, member)
        _add_provider(entries, model, fqn, layout, parallel)
    _native_providers(entries, model, spec, context, boundaries)
    _validate_parallel_nesting(entries)
    result = OwnedLayouts()
    for fqn, module, layout, parallel in entries.values():
        _validate_ports(module, layout)
        _validate_placements(layout, context)
        info = get_family_build_info(module)
        cached = info.layout if info is not None else None
        if parallel is not None and cached is None:
            validate_parallel_module(module, parallel, layout, context)
        if parallel is not None:
            result.parallel[fqn] = parallel
    _assign_parameters(result, entries, model)
    return result


def _validate_parallel_nesting(entries):
    active = [(fqn, parallel.binding_kind) for fqn, _, _, parallel in entries.values()
              if parallel is not None and parallel.binding_kind != "none"]
    for fqn, kind in active:
        for parent, parent_kind in active:
            if _is_child(fqn, parent) and (kind == parent_kind or "mesh" in (kind, parent_kind)):
                raise ValueError(f"overlapping communication owners at {parent!r} and {fqn!r}")


def validate_owned_layouts(model: nn.Module, plan: Any, owned: OwnedLayouts, context: Any) -> None:
    """Check ownership and member capabilities after the final explicit overrides.
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
        plan: Plan after user layout overrides.
        owned: Ownership captured before templates.
        context: Shared immutable logical layout context.
    """
    if not owned.specs:
        return
    covered = set()
    declarations = {}
    for fqn, spec in plan.modules.items():
        module = model.get_submodule(fqn)
        parameters = dict(module.named_parameters(remove_duplicate=False))
        for name, placement in spec.params.items():
            parameter = parameters.get(name)
            if parameter is None:
                continue
            identity = id(parameter)
            if identity in declarations and declarations[identity] != placement:
                raise ValueError(f"explicit overrides give shared parameter {_full(fqn, name)!r} conflicting layouts")
            declarations[identity] = placement
            covered.add(identity)
        if fqn in owned.specs:
            local_coverage = {id(parameters[name]) for name in spec.params if name in parameters}
            if owned.required_by_fqn[fqn] - local_coverage:
                raise ValueError(f"final override removed required parameter ownership from {fqn!r}")
            _validate_ports(module, spec)
            _validate_placements(spec, context)
            parallel = owned.parallel.get(fqn)
            info = get_family_build_info(module)
            cached = info.layout if info is not None else None
            if cached is not None:
                _validate_applied_layout(cached, spec, fqn)
            elif parallel is not None:
                validate_parallel_module(module, parallel, spec, context)
    if owned.expected_ids - covered:
        raise ValueError("final layout overrides removed required module-owned parameter coverage")


def _prepare_family_layout(module: nn.Module, mesh: Any) -> None:
    """Apply a self-described component through the existing planner and executor."""
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

def _prepare_family_layout(module: nn.Module, mesh: Any) -> None:
    """Apply a self-described component through the existing planner and executor."""
    # Planner and apply import layout discovery; defer these cyclic runtime dependencies.
    from hyper_parallel.distributed._builder.planner import ShardingPlanner  # pylint: disable=C0415,R0401
    from hyper_parallel.distributed.apply import apply_sharding_plan  # pylint: disable=C0415,R0401

    info = get_family_build_info(module)
    if info.resolved.sharding is None:
        if info.resolved.selectors["parallel"]:
            raise ValueError("a mesh-constructed parallel family must declare sharding")
        return
    # A component's config identifies its containing model, whose providers do not apply here.
    module._hp_model_adapter_spec = None  # pylint: disable=protected-access
    planner = ShardingPlanner()
    plan = planner.plan(module, mesh.device_mesh, tp_size=mesh.tp_size, cp_size=mesh.cp_size,
                        ep_size=mesh.ep_size, sequence_parallel=mesh.sequence_parallel,
                        loss_parallel=mesh.loss_parallel)
    for fqn, spec in plan.modules.items():
        if fqn not in plan.parallel_modules:
            validate_parallel_module(module.get_submodule(fqn), ParallelModuleSpec("family.local"), spec, None)
    _record_family_layouts(module, plan, mesh, _family_parameter_shapes(module), None, applied=False)
    if callable(getattr(mesh.device_mesh, "get_group", None)):
        apply_sharding_plan(module, plan, mesh)
        if not any(parameter.is_meta for parameter in module.parameters()):
            validate_materialized_modules(module)
hyper_parallel/distributed/_builder/fsdp_adapter.py
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99


def _declared_expert_unit_fqns(model, expert_parameters):
    """Find family/native execution units by identity rather than an experts path suffix."""
    expert_module_fqns = set()
    for boundary in collect_family_modules(model):
        parameters = set(boundary.module.parameters())
        if parameters and parameters <= expert_parameters:
            expert_module_fqns.add(boundary.fqns[0])
    for fqn, module in model.named_modules():
        binding = getattr(module, "_hp_parallel_binding", None)
        if binding is not None and binding.ep_context is not None:
            expert_module_fqns.add(fqn)
    expert_module_fqns.discard("")
    return expert_module_fqns


class FSDP2Manager:
    """Configure and apply nested FSDP2 wrapping for a dual-mode model."""
363
364
365
366
367
368
369
370
371
372
            parameter_fqn.split(".experts.", 1)[0] + ".experts"
            for parameter_fqn, parameter in model.named_parameters(remove_duplicate=False)
            if parameter in expert_parameters and ".experts." in parameter_fqn
        }
        expert_module_fqns.update(_declared_expert_unit_fqns(model, expert_parameters))
        expert_module_fqns.discard("")
        module_by_fqn = dict(model.named_modules())
        expert_wrap_modules = []
        for expert_module_fqn in sorted(expert_module_fqns):
            expert_module = module_by_fqn.get(expert_module_fqn)
380
381
382
383
384
385
386
387
388
389

    @staticmethod
    def _get_model_adapter_spec(model: ModuleClass) -> Any | None:
        """Resolve a model adapter from the final model configuration."""
        if hasattr(model, "_hp_model_adapter_spec"):
            return model._hp_model_adapter_spec  # pylint: disable=protected-access
        config = getattr(model, "config", None)
        if config is None:
            return None
        identities = (
hyper_parallel/distributed/_builder/parameter_sharding.py
213
214
215
216
217
218
219
220
221
def _resolve_parameter_source_meshes(plan, mesh_context, full_mesh, tp_mesh):
    """Resolve dense TP and virtual-EP source meshes for one sharding plan."""
    # Lazy import: applier imports parameter_sharding (_resolve_module) at
    # module level — importing applier here at module level would cycle.
    from hyper_parallel.distributed._builder.applier import (  # pylint: disable=C0415,R0401
        _build_expert_mesh,
    )
    ep_size = next(
        (
225
226
227
228
229
230
231
232
233
        ),
        0,
    )
    if mesh_context is not None:
        expert_mesh = getattr(mesh_context, "fsdp_moe_mesh", None)
    elif ep_size > 0:
        expert_mesh = _build_expert_mesh(
            full_mesh,
            full_mesh.mesh_dim_names,
262
263
264
265
266
267
268
269
270
271
272
273
    """Shard dense and virtual-EP parameters, then update local metadata."""
    for model in models:
        for module_fqn, spec in plan.modules.items():
            module = _resolve_module(model, module_fqn)
            info = get_family_build_info(module)
            if info is not None and info.layout is not None and info.layout.applied:
                _validate_applied_layout(info.layout, spec, module_fqn)
                continue
            # Runtime planner metadata is internal to the sharding pipeline.
            if spec._ep_stack:  # pylint: disable=protected-access
                _stack_moe_experts(
                    module,
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
    """Unwrap production parameters and build FSDP source-layout metadata."""
    if validate_mode:
        return None
    source_shard_records = {}
    existing = {}
    for model in models:
        source_shard_records.update(_local_params_context(model))
        existing.update(_family_source_shard_info(model))
    if not source_shard_records and not existing:
        return None
    if dense_source_mesh is None and expert_source_mesh is None:
        return existing or None
    result = build_source_shard_info(
        plan,
        dense_source_mesh,
        expert_source_mesh=expert_source_mesh,
    )
    result.update(existing)
    return result


def _shard_module_params(module, param_specs, mesh, mesh_dim_names):
    """distribute_tensor() converts parameters into DTensors.
hyper_parallel/distributed/_builder/planner.py
293
294
295
296
297
298
299
300
301
    def _resolve_isolated_roots(model: Any, arch: str) -> tuple[str, ...]:
        """Find subtrees whose activation layout is owned by a separate runtime."""
        from hyper_parallel.models.registry import get_model_adapter  # pylint: disable=C0415

        adapter = (model._hp_model_adapter_spec if hasattr(model, "_hp_model_adapter_spec")  # pylint: disable=protected-access
                   else get_model_adapter(arch))
        if adapter is None or adapter.encoder_parallel is None:
            return ()
        spec = adapter.encoder_parallel()
303
304
305
306
307
308
309
310
311
        return tuple(root for root in spec.isolated_subtrees if root in names)

    def _classify_boundary_groups(self, model: Any, arch: str, isolated_roots=(), excluded_names=()):
        """Classify model parameters and group them into candidate boundaries."""
        param_roles = {name: role for name, role in self._classify_all_params(model, arch).items()
                       if name not in excluded_names}
        regular_roles = {
            name: role for name, role in param_roles.items()
            if not any(name.startswith(root + ".") for root in isolated_roots)
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
        # happens when _mark_hf_native_moe actually matches an HF-native
        # MoE (pre-stacked EP-aware modules use their own dispatcher and
        # are not subject to this constraint)
        # Phase 1: parameter role classification
        layout_context = FamilyShardingContext(
            mesh_dim_names, {"tp": tp_size, "cp": cp_size, "ep": ep_size},
            _family_parameter_shapes(model),
            sequence_parallel, loss_parallel,
        )
        owned = collect_owned_layouts(model, layout_context, resolve_model_adapter(model))
        isolated_roots = self._resolve_isolated_roots(model, arch) if self._derive else ()
        param_roles, boundary_groups = self._classify_boundary_groups(
            model, arch, isolated_roots, excluded_names=owned.owned_names,
        )

        # Phase 3+4: semantic inference + template-fills I/O
465
466
467
468
469
470
471
472
473
474
475
476
477
            params = {name: {TP: Replicate()} for name, parameter in module.named_parameters()
                      if parameter.requires_grad}
            plan.modules[root] = ModuleShardingSpec(params=params, in_src={}, in_dst={}, is_boundary=False)

        plan.modules.update(owned.specs)
        plan.parallel_modules.update(owned.parallel)
        plan.owned_parameter_names.update(owned.owned_names)
        plan.tied_pairs.extend(owned.aliases)
        plan.parameter_aliases.extend(owned.aliases)

        # Phase 4.5: merge derived matches and expand fully declared globs
        # against the final model tree to insert new boundaries.
        self._finalize_boundary_specs(
481
482
483
484
485
486
487
488
489
            ep_size=ep_size,
            mesh=mesh,
            mesh_dim_names=mesh_dim_names,
        )
        validate_owned_layouts(model, plan, owned, layout_context)
        return plan, param_roles, param_metadata

    def _finalize_plan(
        self,
523
524
525
526
527
528
529
530
        # without any boundary communication — warn instead of silently passing
        self._warn_uncovered_function_modules(model, plan)

        # tied-weight detection (embed <-> lm_head sharing storage)
        plan.tied_pairs = list(dict.fromkeys([*plan.tied_pairs, *self._detect_tied_pairs(model)]))
        return plan

    # ── Architecture detection ──────────────────────────────────────────
673
674
675
676
677
678
679
680
681
        knowledge)."""
        from hyper_parallel.models.registry import (  # pylint: disable=import-outside-toplevel
            get_model_adapter,
        )
        spec = (model._hp_model_adapter_spec if hasattr(model, "_hp_model_adapter_spec")  # pylint: disable=protected-access
                else get_model_adapter(arch))
        rules = []
        if spec is not None and spec.sharding_rules is not None:
            rules = list(spec.sharding_rules())
1498
1499
1500
1501
1502
1503
1504
1505
1506
        """
        if not self._derive:
            return
        covered = self._declared_parameter_names(plan)
        covered.update(plan.owned_parameter_names)
        uncovered = [
            name for name, p in model.named_parameters()
            if p.requires_grad and name not in covered
        ]
hyper_parallel/distributed/_builder/source_shard.py
151
152
153
154
155
156
157
158
159

    info = {}
    for fqn, spec in plan.modules.items():
        for param_name, named_placement in spec.params.items():
            full_fqn = f"{fqn}.{param_name}" if fqn else param_name
            _check_fsdp_owned_axes(full_fqn, named_placement)
            if (spec._ep_size > 0  # pylint: disable=protected-access
                    and isinstance((named_placement or {}).get(EP), Shard)):
                if expert_source_mesh is None:
279
280
281
282
283
284
285
286
287
288
289
290
291
292
    Raises:
        ValueError: If TP metadata is missing, references an unknown FQN,
            contains ``Partial``, or gives tied aliases conflicting layouts.
    """
    constructed = _family_source_shard_info(model)
    if constructed:
        for name, layout in constructed.items():
            if name in (source_shard_info or {}) and source_shard_info[name] != layout:
                raise ValueError(f"source_shard_info conflicts with the constructed family layout at {name!r}")
        source_shard_info = {**constructed, **(source_shard_info or {})}
    if source_shard_info is None:
        return _build_dtensor_source_shard_info(manager, model)
    if manager.mesh_context.tp_size > 1 and not source_shard_info:
        raise ValueError("TP is enabled but source_shard_info is empty")
hyper_parallel/distributed/apply.py
64
65
66
67
68
69
70
71
72
    plan: ShardingPlan,
    mesh: Any,
) -> Tuple[Any, Any, Any, Any, Any]:
    """Resolve active and parameter source meshes before plan application."""
    mesh_context = mesh if hasattr(mesh, "device_mesh") else None
    if mesh_context is None:
        device_mesh = mesh
    else:
        device_mesh = mesh_context.device_mesh
119
120
121
122
123
124
125
126
127
128
129
130
131
      non-FSDP source axis (dense entries derive from the dense-FSDP mesh, routed
      experts from the expert mesh);
    - validate: no unwrap (parameters remain DTensors); source_shard_info is None.
    """
    original_mesh = mesh
    for part in model if isinstance(model, list) else [model]:
        original_mesh = _family_mesh(part, original_mesh)
    mesh = original_mesh
    shapes = {id(part): _family_parameter_shapes(part)
              for part in (model if isinstance(model, list) else [model])}
    mesh, models, expert_mesh, dense_source_mesh, expert_source_mesh = (
        _prepare_sharding_meshes(model, plan, mesh)
    )
147
148
149
150
151
152
153
154
155
156
157
        dense_source_mesh,
        expert_source_mesh,
        validate_mode,
    )
    for part in models:
        for canonical, alias in plan.parameter_aliases:
            _set_param_by_path(part, alias, part.get_parameter(canonical))

    # ====== Phase C: wrap forward ======
    for part in models:
        _apply_phase_c(part, plan, mesh, validate_mode, expert_mesh=expert_mesh)
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
    tied_pairs = list(plan.tied_pairs) or detect_tied_weights(models[0])
    for part in models:
        _replicate_tied_weights(part, tied_pairs)

    runtime_mesh = original_mesh if hasattr(original_mesh, "device_mesh") else SimpleNamespace(
        device_mesh=original_mesh, fsdp_moe_mesh=expert_mesh,
    )
    if plan.parallel_modules:
        for part in models:
            bind_parallel_modules(part, plan, runtime_mesh)

    for part in models:
        _record_family_layouts(part, plan, runtime_mesh, shapes[id(part)], source_shard_info)

    return model, source_shard_info
hyper_parallel/distributed/context_parallel/cp_context.py
35
36
37
38
39
40
41
42
43
44
45
46
47
48

    @property
    def offset(self) -> int:
        """Global sequence coordinate of the first local token."""
        return sum(self.lengths[:self.rank])

    @property
    def global_length(self) -> int:
        """Number of tokens represented by this partition."""
        return sum(self.lengths)


@dataclass
class PendingSequence:
52
53
54
55
56
57
58
59
60
    partition: SequencePartition

    def wait(self) -> tuple[Tensor, SequencePartition]:
        """Wait before exposing the gathered tensor and its coordinate receipt."""
        return self.collective.wait(), self.partition


class CPContext:
    """Transport only: model families retain projection, mask and attention math."""
60
61
62
63
64
65
66
67
68
69
70
71
    """Transport only: model families retain projection, mask and attention math."""

    def __init__(self, mesh: Any) -> None:
        """Borrow a one-dimensional CP mesh without creating process groups."""
        if mesh is None or mesh.ndim != 1:
            raise ValueError("CPContext requires an existing one-dimensional HP mesh")
        self.mesh = mesh
        self.size = mesh.size()
        self.rank = mesh.get_local_rank()

    def partition(self, local_length: int) -> SequencePartition:
        """Describe equal contiguous source shards without reading activations.
72
73
74
75
76
77
78
79
80
81

        Args:
            local_length: Sequence length on each rank of the CP group.
        """
        if local_length < 0:
            raise ValueError("local sequence length cannot be negative")
        return SequencePartition((local_length,) * self.size, self.rank)

    def launch_gather_sequence(self, tensor: Tensor, *, dim: int = 1) -> PendingSequence:
        """Launch differentiable sequence gathering with explicit readiness.
83
84
85
86
87
88
89
90
91
        Args:
            tensor: Local activation shard, with equal lengths on all CP ranks.
            dim: Sequence dimension; rank order defines global token coordinates.
        """
        partition = self.partition(tensor.shape[dim])
        return PendingSequence(async_cp_allgather_launch(tensor, dim, self.mesh), partition)

    def gather_sequence(self, tensor: Tensor, *, dim: int = 1) -> tuple[Tensor, SequencePartition]:
        """Gather and wait synchronously; backward sums consumers into source shards.
 93
 94
 95
 96
 97
 98
 99
100
        Args:
            tensor: Local sequence shard.
            dim: Sequence dimension.
        """
        return self.launch_gather_sequence(tensor, dim=dim).wait()

    def sequence_to_head(self, tensor: Tensor, *, seq_dim: int = 1, head_dim: int = 2) -> Tensor:
        """Exchange sequence-sharded activations into head-sharded activations.
103
104
105
106
107
108
109
110
            tensor: Local sequence shard with divisible head count.
            seq_dim: Sequence dimension.
            head_dim: Head dimension.
        """
        return ulysses_seq_to_head(tensor, seq_dim=seq_dim, head_dim=head_dim, cp_mesh=self.mesh)

    def head_to_sequence(self, tensor: Tensor, *, seq_dim: int = 1, head_dim: int = 2) -> Tensor:
        """Apply the inverse head-to-sequence exchange.
113
114
115
116
117
            tensor: Local head shard with divisible sequence length.
            seq_dim: Sequence dimension.
            head_dim: Head dimension.
        """
        return ulysses_head_to_seq(tensor, seq_dim=seq_dim, head_dim=head_dim, cp_mesh=self.mesh)
hyper_parallel/distributed/expert_parallel/ep_context.py
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70


def _validate_slots(hidden: Tensor, indices: Tensor, weights: Tensor, experts: int) -> None:
    """Validate routed token slots, expert indices and optional routing probabilities."""
    if hidden.ndim != 2 or indices.ndim != 2 or weights.shape != indices.shape:
        raise ValueError("expected hidden[T,H], expert_indices[T,K], routing_weights[T,K]")
    if hidden.shape[0] != indices.shape[0] or indices.shape[1] < 1:
        raise ValueError("token dimensions must agree and top-k must be positive")
    if indices.dtype not in (torch.int32, torch.int64) or not weights.is_floating_point():
        raise ValueError("expert IDs must be integers and routing weights floating point")
    if hidden.device != indices.device or hidden.device != weights.device:
        raise ValueError("all routing inputs must be on the same device")
    if bool(((indices < 0) | (indices >= experts)).any()):
        raise ValueError("every supplied slot must have an expert ID in [0, num_experts); drop is unsupported")


class EPContext:
    """Balanced contiguous expert ownership from HP mesh and Shard(0) layout."""
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
    """Balanced contiguous expert ownership from HP mesh and Shard(0) layout."""

    def __init__(self, mesh: Any, *, num_experts: int) -> None:
        """Borrow the EP mesh; never create a group or reduce parameter gradients."""
        if mesh is None or mesh.ndim != 1:
            raise ValueError("EPContext requires an existing one-dimensional HP mesh")
        if num_experts < 1 or num_experts % mesh.size():
            raise ValueError("num_experts must be positive and divisible by EP size")
        self.mesh = mesh
        self.num_experts = num_experts
        self.local_experts = num_experts // mesh.size()
        self.rank = mesh.get_local_rank()
        self._identity = object()

    def _exchange(self, tensor, send, receive):
        if self.mesh.size() == 1:
            return tensor
        return ep_all_to_all(tensor, send, receive, self.mesh.get_group())

    def dispatch(self, hidden_states: Tensor, expert_indices: Tensor, routing_weights: Tensor) -> DispatchedExperts:
        """Transport every already-selected slot, including zero-probability slots.
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
            hidden_states: Source tokens [T,H].
            expert_indices: Global selected expert IDs [T,K]; no capacity/drop mode.
            routing_weights: Already normalized/scaled probabilities [T,K], with gradients.
        """
        _validate_slots(hidden_states, expert_indices, routing_weights, self.num_experts)
        tokens, top_k = expert_indices.shape
        indices = expert_indices.reshape(-1).to(torch.int64)
        destinations = indices // self.local_experts
        send_order = torch.argsort(destinations, stable=True)
        counts = torch.bincount(destinations, minlength=self.mesh.size())
        received_counts = (ep_exchange_counts(counts, self.mesh.get_group())
                           if self.mesh.size() > 1 else counts)
        send, receive = ep_split_sizes(counts), ep_split_sizes(received_counts)
        expanded = hidden_states[:, None, :].expand(-1, top_k, -1).reshape(-1, hidden_states.shape[-1])
        values = self._exchange(expanded[send_order], send, receive)
        weights = self._exchange(routing_weights.reshape(-1, 1)[send_order], send, receive).reshape(-1)
        ids = self._exchange(indices[send_order, None], send, receive).reshape(-1) - self.rank * self.local_experts
        expert_order = torch.argsort(ids, stable=True)
        receipt = DispatchReceipt(self._identity, send, receive, send_order, expert_order, tokens, top_k)
        return DispatchedExperts(values[expert_order], weights[expert_order], ids[expert_order],
                                 torch.bincount(ids, minlength=self.local_experts), receipt)

    def combine(self, values: Tensor, receipt: DispatchReceipt) -> Tensor:
        """Return computed slots to their source tokens and sum top-k contributions.
118
119
120
121
122
123
124
125
126
127
128
129
130
131
        Args:
            values: Expert-major weighted slot outputs [received_slots,H].
            receipt: Receipt from exactly one dispatch on this context.
        """
        if receipt.owner is not self._identity or receipt.consumed:
            raise ValueError("dispatch receipt belongs to another context or was already consumed")
        if values.ndim != 2 or values.shape[0] != sum(receipt.recv_counts):
            raise ValueError("expert output must have one row per received slot")
        peer_major = values[torch.argsort(receipt.expert_order)]
        source_slots = self._exchange(peer_major, receipt.recv_counts, receipt.send_counts)
        source_slots = source_slots[torch.argsort(receipt.send_order)]
        output = source_slots.reshape(receipt.tokens, receipt.top_k, values.shape[-1]).sum(dim=1)
        receipt.consumed = True
        return output
hyper_parallel/distributed/module_binding.py
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
    version: str = "1"
    validate_materialized: Callable | None = None

    def __post_init__(self) -> None:
        if not self.protocol or not self.version:
            raise ValueError("parallel protocol and version must be non-empty")
        if self.binding_kind not in ("none", "cp", "ep", "mesh"):
            raise ValueError("binding_kind must be none, cp, ep or mesh")
        if self.binding_kind != "none" and (not self.stages or self.validate is None):
            raise ValueError("parallel members must declare stage signatures and a layout validator")
        object.__setattr__(self, "stages", MappingProxyType({name: tuple(args) for name, args in self.stages.items()}))
        object.__setattr__(self, "options", MappingProxyType(deepcopy(dict(self.options))))


@dataclass(frozen=True)
class NativeParallelBindingRequest:
62
63
64
65
66
67
68
69
70
71
72
73
74
    parallel: ParallelModuleSpec
    sharding: Callable

    def __post_init__(self) -> None:
        if isinstance(self.match, str) or not self.match or not all(isinstance(name, str) for name in self.match):
            raise ValueError("native matches must be a non-empty tuple of module FQN patterns")
        if not callable(self.sharding) or not isinstance(self.parallel, ParallelModuleSpec):
            raise ValueError("native binding requires a complete layout provider and ParallelModuleSpec")
        object.__setattr__(self, "match", tuple(self.match))


@dataclass(frozen=True)
class MeshBinding:
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96

        Args:
            axis: cp or ep, in the runtime's established rank order.
        """
        if axis == "ep":
            region = self.mesh_context.fsdp_moe_mesh
            if region is not None:
                return region["ep"]
        region = self.mesh_context.device_mesh
        if region is None or axis not in tuple(region.mesh_dim_names or ()):
            raise ValueError(f"no prebuilt {axis!r} mesh is available for parallel binding")
        return region[axis]


@dataclass(frozen=True)
class ParallelBinding:
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
        parallel: Versioned stage declaration from the class/model spec.
        sharding: Final ModuleShardingSpec after user overrides.
        context: Immutable logical layout context.
    """
    for name, arguments in parallel.stages.items():
        implementation = inspect.getattr_static(type(module), name, None)
        if not callable(implementation):
            raise ValueError(f"parallel stage {name!r} must be implemented in the module class")
        inspect.signature(getattr(module, name)).bind(**dict.fromkeys(arguments))
    if parallel.binding_kind != "none":
        implementation = inspect.getattr_static(type(module), "bind_parallel", None)
        if not callable(implementation):
            raise ValueError("parallel modules must define bind_parallel(binding) in their class")
        inspect.signature(module.bind_parallel).bind(None)
    if getattr(sharding, "_ep_stack", None):
        raise ValueError("explicit expert stages cannot inherit legacy whole-MoE stacking")
    if sharding.in_src != sharding.in_dst:
        raise ValueError("explicit stage modules must receive their declared computation layout directly")
    if sharding.out_dst is not None and sharding.out_src != sharding.out_dst:
        raise ValueError("explicit stage modules must restore their declared output layout themselves")
    if parallel.binding_kind == "ep":
        _validate_expert_ownership(module, parallel, sharding, context)
    if parallel.validate is not None:
        parallel.validate(module=module, sharding=sharding, context=context)


def _validate_expert_ownership(module, parallel, sharding, context):
    """Check the bound EP mesh against local and global expert ownership."""
    experts = parallel.options.get("num_experts")
    expert_type = type(experts)
    if expert_type is not int or experts < 1 or experts % context.axis_sizes["ep"]:
        raise ValueError("EP protocols must declare a divisible global num_experts")
    sharded = {name: named["ep"] for name, named in sharding.params.items()
               if isinstance(named.get("ep"), Shard)}
    if not sharded:
        raise ValueError("EP transport requires explicit DTensor expert ownership")
    for name, placement in sharded.items():
        parameter = module.get_parameter(name)
        if placement.dim != 0 or parameter.shape[0] != experts:
            raise ValueError("EPContext supports balanced contiguous expert Shard(0) ownership")


def bind_parallel_modules(model: nn.Module, plan: Any, mesh_context: Any) -> None:
    """Inject shared resources after layout and before activation wrappers/FSDP2.
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
        model: Final model tree with computation-sharded parameters.
        plan: Plan carrying validated family/native protocol declarations.
        mesh_context: Existing HyperParallel MeshContext; never a new topology.
    """
    mesh_binding = MeshBinding(mesh_context)
    prepared = []
    for fqn, parallel in plan.parallel_modules.items():
        if parallel.binding_kind == "none":
            continue
        module = model.get_submodule(fqn)
        existing = getattr(module, "_hp_parallel_binding", None)
        if existing is not None:
            info = get_family_build_info(module)
            cached = info.layout if info is not None else None
            if (cached is None or not cached.applied or cached.parallel != parallel
                    or not _same_mesh(existing.mesh_binding.mesh_context, mesh_context)):
                raise ValueError(f"module {fqn!r} already has a communication owner")
            continue
        cp = CPContext(mesh_binding.axis_mesh("cp")) if parallel.binding_kind == "cp" else None
        ep = (EPContext(mesh_binding.axis_mesh("ep"), num_experts=parallel.options["num_experts"])
              if parallel.binding_kind == "ep" else None)
        prepared.append((module, ParallelBinding(mesh_binding, cp, ep)))
    for module, binding in prepared:
        module.bind_parallel(binding)
        module._hp_parallel_binding = binding  # pylint: disable=protected-access
    model._hp_parallel_module_specs = dict(plan.parallel_modules)  # pylint: disable=protected-access


def validate_materialized_modules(model: nn.Module) -> None:
    """Check final device/kernel resources after materialization and before compile.
185
186
187
188
189
190
191
192

    Args:
        model: Final loaded model whose class protocols may validate physical resources.
    """
    for fqn, parallel in getattr(model, "_hp_parallel_module_specs", {}).items():
        module = model.get_submodule(fqn)
        if parallel.validate_materialized is not None:
            parallel.validate_materialized(module=module, binding=getattr(module, "_hp_parallel_binding", None))
hyper_parallel/trainer/config/parallelism.py
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
            raise TypeError(f"unsupported forward target metadata kind {meta.kind!r}")

    def _serialized_replacement(self):
        """Serialize only the replacement action, independently of layout fields."""
        implementation_config = _serialize_config_value(self.replace.implementation)
        implementation_value = (
            implementation_config["_target_"]
            if isinstance(implementation_config, dict) and set(implementation_config) == {"_target_"}
            else implementation_config
        )
        action = {"kind": self.replace.kind, "implementation": implementation_value}
        if self.replace.kind == "module":
            if self.replace.source_type is not None:
                action["source_type"] = self.replace.source_type
            if self.replace.exact_type:
                action["exact_type"] = True
        return action

    def to_dict(self) -> dict[str, Any]:
        """Serialize the public declaration."""
        result: dict[str, Any] = {"match": self.match}
393
394
395
396
397
398
399
400
401
            result["when"] = self.when
        if self.low_precision_dtype_scheme is not None:
            result["low_precision_dtype_scheme"] = self.low_precision_dtype_scheme
        if self.replace is not None:
            result["replace"] = self._serialized_replacement()
        for name in ("region_dispatch", "params", "in_src", "in_dst", "out_src", "out_dst", "tp_divide_attrs"):
            value = getattr(self, name)
            default_forward_dispatch = (
                name == "region_dispatch" and value is False
407
408
409
410
411
412
413
414
415
416
417
418
419
420
        return result

    def _validate_override_match(self):
        """Reject method-only and empty declarations before creating a boundary."""
        if self.replace is not None and self.replace.kind == "method" and (
                self.match.rpartition(".")[2] != "forward"):
            raise ValueError("non-forward method replacements do not create planner boundaries")

        if not self.match:
            raise ValueError(
                "plan_overrides entry is missing 'match' (an fqn or "
                "fqn glob such as '*.self_attn' or '*.mlp')")

    def to_override(self) -> "tuple[str, Any]":
432
433
434
435
436
437
438
439
440
        from hyper_parallel.distributed.recipe_spec import (
            ModuleShardingSpec,
        )

        self._validate_override_match()
        implementation = self.replace.implementation if self.replace is not None else None
        meta = getattr(implementation.callable, "_injection_meta", None) if implementation is not None else None
        spec = ModuleShardingSpec(
            local_compute_fn=implementation if meta is not None and meta.kind == "local_compute" else None,
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676

    Args:
        entries: Value passed to this operation.
    """
    effective = low_precision_config if low_precision_enabled else None
    result = []
    for entry_index, entry in enumerate(entries):
        if entry.replace is None or entry.replace.kind != "module":
            continue
        if entry.when == "low_precision" and not low_precision_enabled:
            continue
        _validate_replacement_entry(entry, effective)
        target = entry.replace.implementation
        baseline = target.requested_target if isinstance(target, ReplacementTarget) else target.callable
        arguments = {name: value for name, value in target._kwargs.items()  # pylint: disable=protected-access
                     if name not in {"module", "module_fqn", "context"}}
        if isinstance(target, ReplacementTarget):
            arguments.update(target.selectors)
        if entry.when == "low_precision":
            # Legacy policy-bearing closures remain on the explicit compatibility path.
            continue
        result.append(ModuleReplacementRequest(
            match=(entry.match,) if isinstance(entry.match, str) else tuple(entry.match),
            target=baseline, kwargs=arguments, origin="user",
            declaration_id=f"plan_override:{entry_index}:replace",
            module_type=_import_module_type(entry.replace.source_type) if entry.replace.source_type else None,
675
676
677
678
679
680
681
682
683
            declaration_id=f"plan_override:{entry_index}:replace",
            module_type=_import_module_type(entry.replace.source_type) if entry.replace.source_type else None,
            exact_type=entry.replace.exact_type,
        ))
    return tuple(result)


def entries_to_module_replacements(
    entries: List[PlanOverride],
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820


def _validate_override_transport(entry):
    """Validate YAML module-selection transport separately from placement lowering."""
    if isinstance(entry.match, list) and (
        entry.replace is None or entry.replace.kind != "module" or _has_sharding_action(entry)
    ):
        raise ValueError(
            "plan_overrides match lists are supported only for replace.kind='module' "
            "entries; use separate string matches for sharding actions"
        )
    if entry.replace is not None and entry.replace.kind == "module" and entry.when not in (
        None,
        "low_precision",
    ):
        raise ValueError(
            f"plan_overrides match={entry.match!r} uses replace.kind='module' and "
            "does not support 'when' values other than 'low_precision' "
            "(module replacements are not conditioned on parallel topology)"
        )
848
849
850
851
852
853
854
855
856
        if entry.replace is not None and entry.replace.kind == "method" and (
                entry.match.rpartition(".")[2] != "forward"):
            _validate_when(entry)
            continue
        _validate_override_transport(entry)
        _validate_when(entry)
        if entry.when is not None:
            active = {
                "cp": cp_size > 1,
895
896
897
898
899
900
901
902
903
            "enabled",
            False,
        )
    )
    distributed_setup._hp_legacy_low_precision_targets = any(  # pylint: disable=protected-access
        entry.replace is not None and entry.replace.kind == "module" and (
            entry.when == "low_precision"
            or getattr(entry.replace.implementation, "low_precision_dtype_scheme", None) is not None
        )
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
            or getattr(entry.replace.implementation, "low_precision_dtype_scheme", None) is not None
        )
        for entry in entries
    )
    if not distributed_setup._hp_legacy_low_precision_targets:  # pylint: disable=protected-access
        distributed_setup.module_replacement_requests = entries_to_module_replacement_requests(
            entries, low_precision_enabled=low_precision_enabled,
            low_precision_config=getattr(distributed_setup, "low_precision_config", None),
        )
    else:
        distributed_setup.module_replacement_requests = None
    distributed_setup.module_replacements = entries_to_module_replacements(
        entries,
        low_precision_enabled=low_precision_enabled,
        low_precision_config=getattr(distributed_setup, "low_precision_config", None),
hyper_parallel/trainer/config/replacement.py
81
82
83
84
85
86
87
88
89
90
91
92
93
94
    def build(self, **runtime_kwargs: Any) -> Any:
        """Build only after a named scheme has been bound by the replacement planner."""
        if self.low_precision_dtype_scheme is not None:
            raise ValueError("Bind low_precision_dtype_scheme through the replacement plan before building")
        family = get_module_family(self.requested_target)
        if family is None or family.default_variant is None:
            return super().build(**runtime_kwargs)
        arguments = {name: _build_nested_targets(value) for name, value in self._kwargs.items()}
        arguments.update(self.selectors)
        return build_family(
            FamilyRequest(self.requested_target, arguments),
            module=runtime_kwargs.get("module"),
            module_fqn=runtime_kwargs.get("module_fqn", ""),
            context=runtime_kwargs.get("context"),