Coverage for / home / jenkins / .local / lib / python3.10 / site-packages / hyper_parallel / core / optimizer / __init__.py: 97%
62 statements
« prev ^ index » next coverage.py v7.13.1, created at 2026-10-25 05:45 +0800
« prev ^ index » next coverage.py v7.13.1, created at 2026-10-25 05:45 +0800
1# Copyright 2026 Huawei Technologies Co., Ltd
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14# ============================================================================
15"""HyperParallel optimizer module."""
16from importlib import import_module as _import_module # pylint: disable=invalid-name
18import inspect
19import logging
20from typing import Any, Dict, List, Optional
22from hyper_parallel.core.optimizer.swap_optimizer import (
23 SwapOptimizer,
24 SwapOptimizerConfig,
25 swap_optimizer,
26)
28logger = logging.getLogger(__name__)
29logger.setLevel(logging.INFO)
31# Optimizer implementations import torch at module load. Keep them off the
32# eager path so importing the package does not pull in torch.
33_LAZY_EXPORTS = {
34 "AdamW": ".adamw",
35 "Muon": ".muon",
36 "ChainedOptimizer": ".optimizer",
37 "detect_dtensor_backend": ".dtensor_compat",
38}
41def __getattr__(name): # pylint: disable=invalid-name
42 """Lazily import torch-only optimizer symbols."""
43 if name not in _LAZY_EXPORTS:
44 raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
45 module = _import_module(_LAZY_EXPORTS[name], __name__)
46 value = getattr(module, name)
47 globals()[name] = value
48 return value
51def __dir__(): # pylint: disable=invalid-name
52 """Include lazy torch-only optimizer exports in ``dir()``."""
53 return sorted(set(globals()) | set(_LAZY_EXPORTS))
56def _load_torch_optimizer_runtime():
57 """Import torch-only optimizer helpers used by the factory APIs."""
58 # pylint: disable=import-outside-toplevel,unused-import
59 import hyper_parallel.core.optimizer.utils # noqa: F401 - install rank0 logging helpers
61 from hyper_parallel.core.optimizer.adamw import AdamW
62 from hyper_parallel.core.optimizer.dtensor_compat import detect_dtensor_backend
63 from hyper_parallel.core.optimizer.muon import Muon
64 from hyper_parallel.core.optimizer.optimizer import ChainedOptimizer
66 return AdamW, Muon, ChainedOptimizer, detect_dtensor_backend
69def _effective_optimizer_config(
70 optimizer_class: Any,
71 configured_values: Dict[str, Any],
72 runtime_optimizer: Any,
73) -> Dict[str, Any]:
74 """Merge constructor defaults, user values, and resolved runtime defaults."""
75 signature = inspect.signature(optimizer_class.__init__)
76 effective_config = {}
77 for name, parameter in signature.parameters.items():
78 if name not in {"self", "params"} and parameter.default is not inspect.Parameter.empty:
79 effective_config[name] = parameter.default
80 effective_config.update(configured_values)
81 effective_config.update(runtime_optimizer.defaults)
82 if hasattr(runtime_optimizer, "hsdp_replica_count"):
83 effective_config["hsdp_replica_count"] = runtime_optimizer.hsdp_replica_count
84 return effective_config
87def _filter_optimizer_config(
88 optimizer_name: str,
89 optimizer_class: Any,
90 configured_values: Optional[Dict[str, Any]],
91) -> Dict[str, Any]:
92 """Normalize prefixed YAML keys and remove unsupported constructor args."""
93 prefix = f"{optimizer_name}_"
94 normalized_config = {
95 key[len(prefix):] if key.startswith(prefix) else key: value
96 for key, value in (configured_values or {}).items()
97 }
98 allowed_keys = (
99 inspect.signature(optimizer_class.__init__).parameters.keys()
100 - {"self", "params"}
101 )
102 allowed_keys = set(allowed_keys) | set(getattr(optimizer_class, "ADDITIONAL_CONFIG_KEYS", ()))
103 filtered_config = {
104 key: value
105 for key, value in normalized_config.items()
106 if key in allowed_keys
107 }
108 excluded_keys = normalized_config.keys() - allowed_keys
109 if excluded_keys:
110 logger.info_rank0(
111 "Excluded %s config: %s",
112 optimizer_name,
113 list(excluded_keys),
114 )
115 return filtered_config
118def _build_configured_optimizer(
119 optimizer_name: str,
120 optimizer_class: Any,
121 param_groups: Any,
122 configured_values: Dict[str, Any],
123) -> Any:
124 """Construct one leaf optimizer and log its effective configuration."""
125 optimizer = optimizer_class(param_groups, **configured_values)
126 logger.info_rank0(
127 f"Effective {optimizer_name} config: %s",
128 _effective_optimizer_config(
129 optimizer_class,
130 configured_values,
131 optimizer,
132 ),
133 )
134 return optimizer
137def get_hyper_optimizer(
138 model: Any,
139 muon_params: List[Dict[str, Any]],
140 adamw_params: List[Dict[str, Any]],
141 muon_kwargs: Optional[Dict[str, Any]] = None,
142 adamw_kwargs: Optional[Dict[str, Any]] = None,
143) -> Any:
144 """Create a chained Muon + AdamW optimizer.
146 Args:
147 model: The neural network model.
148 muon_params: Param groups for Muon. Empty list disables Muon.
149 adamw_params: Param groups for AdamW. Empty list disables AdamW.
150 muon_kwargs: Dedicated configurations dict for Muon.
151 adamw_kwargs: Dedicated configurations dict for AdamW.
153 Example:
154 from hyper_parallel.core.optimizer import get_hyper_optimizer
156 _adamw_legacy = {
157 'adamw_lr': 1e-3,
158 'adamw_weight_decay': 1e-2,
159 'adamw_betas': (0.9, 0.95),
160 'adamw_eps': 1e-8,
161 'fused': True
162 }
163 _muon_legacy = {
164 'muon_lr': 2e-2,
165 'muon_weight_decay': 0.1,
166 'muon_momentum': 0.95,
167 'muon_ns_steps': 5,
168 'muon_ns_variant': 'asym5',
169 'muon_nesterov': True,
170 'muon_hsdp_replica_count': 2
171 }
173 optimizer = get_hyper_optimizer(
174 model=model,
175 muon_params=muon_groups,
176 adamw_params=adamw_groups,
177 adamw_kwargs=_adamw_legacy,
178 muon_kwargs=_muon_legacy,
179 )
181 optimizer.step()
182 """
183 AdamW, Muon, ChainedOptimizer, detect_dtensor_backend = _load_torch_optimizer_runtime()
185 filtered_adamw_config = _filter_optimizer_config(
186 "adamw",
187 AdamW,
188 adamw_kwargs,
189 )
190 filtered_muon_config = _filter_optimizer_config(
191 "muon",
192 Muon,
193 muon_kwargs,
194 )
195 optimizers = {}
196 detect_dtensor_backend(adamw_params, muon_params)
198 if adamw_params:
199 optimizers["adamw"] = _build_configured_optimizer(
200 "adamw",
201 AdamW,
202 adamw_params,
203 filtered_adamw_config,
204 )
206 if muon_params:
207 optimizers["muon"] = _build_configured_optimizer(
208 "muon",
209 Muon,
210 muon_params,
211 filtered_muon_config,
212 )
214 flatten = bool(adamw_params and muon_params)
216 return ChainedOptimizer(
217 model,
218 optimizers=optimizers,
219 flatten=flatten,
220 )
223__all__ = [
224 'SwapOptimizer',
225 'SwapOptimizerConfig',
226 'get_hyper_optimizer',
227 'swap_optimizer',
228]