Coverage for  / home / jenkins / .local / lib / python3.10 / site-packages / hyper_parallel / core / optimizer / __init__.py: 97%

66 statements  

« prev     ^ index     » next       coverage.py v7.13.1, created at 2026-11-15 07:18 +0800

1# Copyright 2026 Huawei Technologies Co., Ltd 

2# 

3# Licensed under the Apache License, Version 2.0 (the "License"); 

4# you may not use this file except in compliance with the License. 

5# You may obtain a copy of the License at 

6# 

7# http://www.apache.org/licenses/LICENSE-2.0 

8# 

9# Unless required by applicable law or agreed to in writing, software 

10# distributed under the License is distributed on an "AS IS" BASIS, 

11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 

12# See the License for the specific language governing permissions and 

13# limitations under the License. 

14# ============================================================================ 

15"""HyperParallel optimizer module.""" 

16from importlib import import_module as _import_module # pylint: disable=invalid-name 

17 

18import inspect 

19import logging 

20from typing import Any, Dict, List, Optional 

21 

22from hyper_parallel.core.optimizer.swap_optimizer import ( 

23 SwapOptimizer, 

24 SwapOptimizerConfig, 

25 swap_optimizer, 

26) 

27 

28logger = logging.getLogger(__name__) 

29logger.setLevel(logging.INFO) 

30 

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 "Sinkhorn": ".sinkhorn", 

37 "ChainedOptimizer": ".optimizer", 

38 "detect_dtensor_backend": ".dtensor_compat", 

39} 

40 

41 

42def __getattr__(name): # pylint: disable=invalid-name 

43 """Lazily import torch-only optimizer symbols.""" 

44 if name not in _LAZY_EXPORTS: 

45 raise AttributeError(f"module {__name__!r} has no attribute {name!r}") 

46 module = _import_module(_LAZY_EXPORTS[name], __name__) 

47 value = getattr(module, name) 

48 globals()[name] = value 

49 return value 

50 

51 

52def __dir__(): # pylint: disable=invalid-name 

53 """Include lazy torch-only optimizer exports in ``dir()``.""" 

54 return sorted(set(globals()) | set(_LAZY_EXPORTS)) 

55 

56 

57def _load_torch_optimizer_runtime(): 

58 """Import torch-only optimizer helpers used by the factory APIs.""" 

59 # pylint: disable=import-outside-toplevel,unused-import 

60 import hyper_parallel.core.optimizer.utils # noqa: F401 - install rank0 logging helpers 

61 

62 from hyper_parallel.core.optimizer.adamw import AdamW 

63 from hyper_parallel.core.optimizer.dtensor_compat import detect_dtensor_backend 

64 from hyper_parallel.core.optimizer.muon import Muon 

65 from hyper_parallel.core.optimizer.optimizer import ChainedOptimizer 

66 

67 return AdamW, Muon, ChainedOptimizer, detect_dtensor_backend 

68 

69 

70def _effective_optimizer_config( 

71 optimizer_class: Any, 

72 configured_values: Dict[str, Any], 

73 runtime_optimizer: Any, 

74) -> Dict[str, Any]: 

75 """Merge constructor defaults, user values, and resolved runtime defaults.""" 

76 signature = inspect.signature(optimizer_class.__init__) 

77 effective_config = {} 

78 for name, parameter in signature.parameters.items(): 

79 if name not in {"self", "params"} and parameter.default is not inspect.Parameter.empty: 

80 effective_config[name] = parameter.default 

81 effective_config.update(configured_values) 

82 effective_config.update(runtime_optimizer.defaults) 

83 if hasattr(runtime_optimizer, "hsdp_replica_count"): 

84 effective_config["hsdp_replica_count"] = runtime_optimizer.hsdp_replica_count 

85 return effective_config 

86 

87 

88def _filter_optimizer_config( 

89 optimizer_name: str, 

90 optimizer_class: Any, 

91 configured_values: Optional[Dict[str, Any]], 

92) -> Dict[str, Any]: 

93 """Normalize prefixed YAML keys and remove unsupported constructor args.""" 

94 prefix = f"{optimizer_name}_" 

95 normalized_config = { 

96 key[len(prefix):] if key.startswith(prefix) else key: value 

97 for key, value in (configured_values or {}).items() 

98 } 

99 allowed_keys = ( 

100 inspect.signature(optimizer_class.__init__).parameters.keys() 

101 - {"self", "params"} 

102 ) 

103 allowed_keys = set(allowed_keys) | set(getattr(optimizer_class, "ADDITIONAL_CONFIG_KEYS", ())) 

104 filtered_config = { 

105 key: value 

106 for key, value in normalized_config.items() 

107 if key in allowed_keys 

108 } 

109 excluded_keys = normalized_config.keys() - allowed_keys 

110 if excluded_keys: 

111 logger.info_rank0( 

112 "Excluded %s config: %s", 

113 optimizer_name, 

114 list(excluded_keys), 

115 ) 

116 return filtered_config 

117 

118 

119def _build_configured_optimizer( 

120 optimizer_name: str, 

121 optimizer_class: Any, 

122 param_groups: Any, 

123 configured_values: Dict[str, Any], 

124) -> Any: 

125 """Construct one leaf optimizer and log its effective configuration.""" 

126 optimizer = optimizer_class(param_groups, **configured_values) 

127 logger.info_rank0( 

128 f"Effective {optimizer_name} config: %s", 

129 _effective_optimizer_config( 

130 optimizer_class, 

131 configured_values, 

132 optimizer, 

133 ), 

134 ) 

135 return optimizer 

136 

137 

138def get_hyper_optimizer( 

139 model: Any, 

140 muon_params: List[Dict[str, Any]], 

141 adamw_params: List[Dict[str, Any]], 

142 muon_kwargs: Optional[Dict[str, Any]] = None, 

143 adamw_kwargs: Optional[Dict[str, Any]] = None, 

144 *, 

145 sinkhorn_params: Optional[List[Dict[str, Any]]] = None, 

146 sinkhorn_kwargs: Optional[Dict[str, Any]] = None, 

147) -> Any: 

148 """Create a chained Muon + AdamW optimizer with optional Sinkhorn groups. 

149 

150 Args: 

151 model: The neural network model. 

152 muon_params: Param groups for Muon. Empty list disables Muon. 

153 adamw_params: Param groups for AdamW. Empty list disables AdamW. 

154 muon_kwargs: Dedicated configurations dict for Muon. 

155 adamw_kwargs: Dedicated configurations dict for AdamW. 

156 sinkhorn_params: Optional Sinkhorn groups. Empty or None disables Sinkhorn. 

157 sinkhorn_kwargs: Dedicated configurations dict for Sinkhorn. 

158 

159 Example: 

160 from hyper_parallel.core.optimizer import get_hyper_optimizer 

161 

162 _adamw_legacy = { 

163 'adamw_lr': 1e-3, 

164 'adamw_weight_decay': 1e-2, 

165 'adamw_betas': (0.9, 0.95), 

166 'adamw_eps': 1e-8, 

167 'fused': True 

168 } 

169 _muon_legacy = { 

170 'muon_lr': 2e-2, 

171 'muon_weight_decay': 0.1, 

172 'muon_momentum': 0.95, 

173 'muon_ns_steps': 5, 

174 'muon_ns_variant': 'asym5', 

175 'muon_nesterov': True, 

176 'muon_hsdp_replica_count': 2 

177 } 

178 

179 optimizer = get_hyper_optimizer( 

180 model=model, 

181 muon_params=muon_groups, 

182 adamw_params=adamw_groups, 

183 adamw_kwargs=_adamw_legacy, 

184 muon_kwargs=_muon_legacy, 

185 ) 

186 

187 optimizer.step() 

188 """ 

189 AdamW, Muon, ChainedOptimizer, detect_dtensor_backend = _load_torch_optimizer_runtime() 

190 

191 filtered_adamw_config = _filter_optimizer_config( 

192 "adamw", 

193 AdamW, 

194 adamw_kwargs, 

195 ) 

196 filtered_muon_config = _filter_optimizer_config( 

197 "muon", 

198 Muon, 

199 muon_kwargs, 

200 ) 

201 optimizers = {} 

202 detect_dtensor_backend(adamw_params or sinkhorn_params or [], muon_params) 

203 

204 if adamw_params: 

205 optimizers["adamw"] = _build_configured_optimizer( 

206 "adamw", 

207 AdamW, 

208 adamw_params, 

209 filtered_adamw_config, 

210 ) 

211 

212 if muon_params: 

213 optimizers["muon"] = _build_configured_optimizer( 

214 "muon", 

215 Muon, 

216 muon_params, 

217 filtered_muon_config, 

218 ) 

219 

220 if sinkhorn_params: 

221 sinkhorn_class = __getattr__("Sinkhorn") 

222 filtered_sinkhorn_config = _filter_optimizer_config("sinkhorn", sinkhorn_class, sinkhorn_kwargs) 

223 optimizers["sinkhorn"] = _build_configured_optimizer( 

224 "sinkhorn", sinkhorn_class, sinkhorn_params, filtered_sinkhorn_config, 

225 ) 

226 

227 flatten = len(optimizers) > 1 

228 

229 return ChainedOptimizer( 

230 model, 

231 optimizers=optimizers, 

232 flatten=flatten, 

233 ) 

234 

235 

236__all__ = [ 

237 'SwapOptimizer', 

238 'SwapOptimizerConfig', 

239 'get_hyper_optimizer', 

240 'swap_optimizer', 

241]