-
Notifications
You must be signed in to change notification settings - Fork 4.4k
Add --muon-coefficient-type argument for Muon optimizer #3927
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
444f17e
e357a41
bcac481
17597ac
bf834c1
8a025d3
678c832
7b8b042
02bad44
c4d9c4a
07ee6fa
4e929d2
6628801
3de021e
420ebbd
b945ada
99e8ad4
8d9f7c5
8d39d23
a1dd63d
b2936b2
a8e82d0
0bc8f48
0d613ec
10bf5eb
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,7 +3,7 @@ | |
| """Megatron muon optimizer wrapper to handle tensor-parallel.""" | ||
|
|
||
| import logging | ||
| from typing import Any, Callable, Dict, List, Literal, Optional | ||
| from typing import Any, Callable, Dict, List, Literal, Optional, get_args | ||
|
|
||
| import torch | ||
| from torch.optim.optimizer import ParamsT | ||
|
|
@@ -13,7 +13,7 @@ | |
| from megatron.core.transformer.module import MegatronModule | ||
| from megatron.core.utils import get_pg_size, log_single_rank | ||
|
|
||
| from . import _get_param_groups, get_megatron_optimizer | ||
| from . import HAVE_EMERGING_OPTIMIZERS, HAVE_EO_V02, _get_param_groups, get_megatron_optimizer | ||
| from .layer_wise_optimizer import LayerWiseDistributedOptimizer | ||
| from .optimizer import ( | ||
| ChainedOptimizer, | ||
|
|
@@ -23,31 +23,44 @@ | |
| ) | ||
| from .optimizer_config import OptimizerConfig, ParamKey | ||
|
|
||
| try: | ||
| if HAVE_EMERGING_OPTIMIZERS: | ||
| from emerging_optimizers.orthogonalized_optimizers import ( | ||
| OrthogonalizedOptimizer, | ||
| get_muon_scale_factor, | ||
| ) | ||
| from emerging_optimizers.orthogonalized_optimizers.muon_utils import newton_schulz_tp | ||
|
|
||
| HAVE_EMERGING_OPTIMIZERS = True | ||
| except ImportError: | ||
| HAVE_EMERGING_OPTIMIZERS = False | ||
| else: | ||
| OrthogonalizedOptimizer = object | ||
|
|
||
| # TODO: Remove this separate try/except once the next version of emerging_optimizers | ||
| # (which includes Lion) is released. Then Lion can be imported in the block above. | ||
| try: | ||
| from emerging_optimizers.scalar_optimizers import Lion # pylint: disable=unused-import | ||
|
|
||
| HAVE_LION = True | ||
| except ImportError: | ||
| HAVE_LION = False | ||
| if HAVE_EO_V02: | ||
| from emerging_optimizers.orthogonalized_optimizers.muon_utils import NSCoeffT | ||
|
|
||
|
|
||
| logger = logging.getLogger(__name__) | ||
|
|
||
|
|
||
| def get_supported_coefficient_types() -> tuple[str, ...]: | ||
| """Return the coefficient types supported by the installed emerging_optimizers. | ||
|
|
||
| Reads the members of the ``NSCoeffT`` Literal type so that new types | ||
| added upstream are automatically available without code changes here. | ||
| """ | ||
| assert ( | ||
| HAVE_EO_V02 | ||
| ), "emerging_optimizers >= 0.2 is required for NSCoeffT. Please install or upgrade it." | ||
| return get_args(NSCoeffT) # pylint: disable=possibly-used-before-assignment | ||
|
|
||
|
|
||
| def validate_coefficient_type(coefficient_type: str) -> None: | ||
| """Raise ``ValueError`` if *coefficient_type* is not supported.""" | ||
| supported = get_supported_coefficient_types() if HAVE_EO_V02 else ("quintic",) | ||
| if coefficient_type not in supported: | ||
| raise ValueError( | ||
| f"Unsupported muon coefficient type '{coefficient_type}'. " | ||
| f"Supported types: {supported}" | ||
| ) | ||
|
|
||
|
|
||
| class TensorParallelMuon(OrthogonalizedOptimizer): | ||
| """Tensor Parallel Muon optimizer.""" | ||
|
|
||
|
|
@@ -72,6 +85,7 @@ def __init__( | |
| ) -> None: | ||
| if num_ns_steps < 1: | ||
| raise ValueError(f"num_ns_steps must be at least 1, got {num_ns_steps}") | ||
| validate_coefficient_type(coefficient_type) | ||
|
|
||
| def scaled_orthogonalize_fn( | ||
| grad: torch.Tensor, | ||
|
|
@@ -87,14 +101,15 @@ def scaled_orthogonalize_fn( | |
| size = [grad.size(-2), grad.size(-1)] | ||
| if partition_dim is not None: | ||
| size[partition_dim] *= get_pg_size(tp_group) | ||
| orth_grad = newton_schulz_tp( | ||
| grad, | ||
| steps=num_ns_steps, | ||
| coefficient_type=coefficient_type, | ||
| tp_group=tp_group, | ||
| partition_dim=partition_dim, | ||
| mode="duplicated" if mode == "blockwise" else mode, | ||
| mode_value = "duplicated" if mode == "blockwise" else mode | ||
| mode_kwarg = {"tp_mode": mode_value} if HAVE_EO_V02 else {"mode": mode_value} | ||
| ns_kwargs = dict( | ||
| steps=num_ns_steps, tp_group=tp_group, partition_dim=partition_dim, **mode_kwarg | ||
| ) | ||
| ns_kwargs["coefficient_type"] = coefficient_type | ||
| # pylint: disable-next=possibly-used-before-assignment | ||
| orth_grad = newton_schulz_tp(grad, **ns_kwargs) | ||
| # pylint: disable-next=possibly-used-before-assignment | ||
| scale_factor = get_muon_scale_factor(size[0], size[1], mode=scale_mode) | ||
| return orth_grad * scale_factor * extra_scale_factor | ||
|
|
||
|
|
@@ -105,11 +120,14 @@ def scaled_orthogonalize_fn( | |
| self.qkv_split_shapes = qkv_split_shapes | ||
|
|
||
| weight_decay_method = "decoupled" if use_decoupled_weight_decay else "l2" | ||
| nesterov_kwarg = ( | ||
| {"nesterov": use_nesterov} if HAVE_EO_V02 else {"use_nesterov": use_nesterov} | ||
| ) | ||
| super().__init__( | ||
| params, | ||
| lr, | ||
| momentum_beta, | ||
| use_nesterov=use_nesterov, | ||
| **nesterov_kwarg, | ||
| weight_decay=weight_decay, | ||
| weight_decay_method=weight_decay_method, | ||
| fp32_matmul_prec=fp32_matmul_prec, | ||
|
|
@@ -195,12 +213,13 @@ def get_megatron_muon_optimizer( | |
| # Set the nonlinear optimizer for muon (used for embeddings, biases, norms). | ||
| config.optimizer = config.muon_scalar_optimizer | ||
|
|
||
| assert HAVE_EMERGING_OPTIMIZERS, "Emerging Optimizers is not installed." | ||
| if config.muon_scalar_optimizer == 'lion': | ||
| assert HAVE_LION, ( | ||
| "Lion optimizer requires a version of 'emerging_optimizers' that includes Lion. " | ||
| assert HAVE_EO_V02, ( | ||
| "Lion optimizer requires emerging_optimizers >= 0.2. " | ||
| "Please upgrade to use --muon-scalar-optimizer lion." | ||
| ) | ||
| else: | ||
| assert HAVE_EMERGING_OPTIMIZERS, "Emerging Optimizers is not installed." | ||
|
|
||
| # Dist-opt is not supported due to strong coupling with how DDP init grad buffer | ||
| # In theory we can change DDP to enable use muon and dist-opt-adam together | ||
|
|
@@ -288,6 +307,7 @@ def lion_init_state_fn(opt, config=None): | |
| "use_nesterov": config.muon_use_nesterov, | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. BTW, should we check for certain versions of
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I feel it is better to keep the code only support one version. And I think having that version be the tagged in pyproject is reasonable. Optionally we can add a global check. |
||
| "weight_decay": config.weight_decay, | ||
| "fp32_matmul_prec": config.muon_fp32_matmul_prec, | ||
| "coefficient_type": config.muon_coefficient_type, | ||
| "num_ns_steps": config.muon_num_ns_steps, | ||
| "scale_mode": config.muon_scale_mode, | ||
| "split_qkv": config.muon_split_qkv, | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.