# Copyright (c) 2022-2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# Copyright (c) 2026, Huawei Technologies Co., Ltd. All rights reserved.
#
# See LICENSE for license information.

"""RMSNorm API for TransformerEngineNPU PyTorch"""

from typing import Optional, Union, Iterable, Any
import warnings

import torch
import torch_npu

import transformer_engine.pytorch.ops as ops

class RMSNorm(ops.RMSNorm):
    r"""Root Mean Square Layer Normalization

    Applies Root Mean Square Layer Normalization over a mini-batch of
    inputs as described in the paper
    `Root Mean Square Layer Normalization <https://arxiv.org/abs/1910.07467>`__

    .. math::
        y = \frac{x}{\sqrt{\mathrm{Var}[x] + \varepsilon}} * \gamma

    where

    .. math::
        \mathrm{Var}[x] = \frac{1}{n}\sum_{i=0}^n x_i^2

    :math:`\gamma` is a learnable affine transform parameter that
    matches the inner-most dimensions of the input tensor.

    Parameters
    ----------
    normalized_shape : int or iterable of int
        Inner dimensions of input tensor
    eps : float, default = 1e-5
        A value added to the denominator for numerical stability
    sequence_parallel : bool, default = False
        If ``True``, the weight parameter is marked for sequence parallel
        all-reduce. This is custom logic for Megatron-LM integration.
    zero_centered_gamma : bool, default = False
        If ``True``, the :math:`\gamma` parameter is initialized to zero
        and the calculation changes to

            .. math::
                y = \frac{x}{\sqrt{\mathrm{Var}[x] + \varepsilon}} * (1 + \gamma)

    hidden_size : int, optional
        **Deprecated.** Use ``normalized_shape`` instead.
    params_dtype : torch.dtype, optional
        **Deprecated.** Use ``dtype`` kwarg instead.

    """

    def __init__(
        self,
        normalized_shape: Union[Iterable[int], int, None] = None,
        eps: float = 1e-5,
        sequence_parallel: bool = False,
        zero_centered_gamma: bool = False,
        hidden_size: Optional[int] = None,
        params_dtype: Optional[torch.dtype] = None,
        **kwargs,
    ) -> None:
        super().__init__(
            normalized_shape,
            eps=eps,
            zero_centered_gamma=zero_centered_gamma,
            **kwargs,
        )

        if normalized_shape is None:
            if hidden_size is None:
                raise RuntimeError(
                    "Neither `normalized_shape` nor `hidden_size` (deprecated) args are provided"
                )
            warnings.warn(
                "`hidden_size` arg has been renamed to `normalized_shape` "
                "for compatibility with `torch.nn.LayerNorm`.",
                DeprecationWarning,
                stacklevel=2,
            )
            normalized_shape = hidden_size
        elif hidden_size is not None:
            raise RuntimeError(
                "Both `normalized_shape` and `hidden_size` (deprecated) args are provided"
            )

        if params_dtype is not None:
            if "dtype" in kwargs:
                raise RuntimeError(
                    "Both `dtype` and `params_dtype` (deprecated) kwargs are provided"
                )
            kwargs["dtype"] = params_dtype

        if not isinstance(normalized_shape, Iterable):
            normalized_shape = (normalized_shape,)
        else:
            normalized_shape = tuple(normalized_shape)

        self.normalized_shape = normalized_shape
        self.eps = eps
        self.sequence_parallel = sequence_parallel
        self.zero_centered_gamma = zero_centered_gamma

        dtype = kwargs.get("dtype", torch.get_default_dtype())
        device = kwargs.get("device", torch_npu.npu.current_device() if torch_npu.npu.is_available() else "cpu")

        self.weight = torch.nn.Parameter(torch.ones(normalized_shape, dtype=dtype, device=device))
        setattr(self.weight, "sequence_parallel", sequence_parallel)

        if zero_centered_gamma:
            torch.nn.init.zeros_(self.weight)

    def reset_parameters(self) -> None:
        """Reset parameters to initial values."""
        if self.zero_centered_gamma:
            torch.nn.init.zeros_(self.weight)
        else:
            torch.nn.init.ones_(self.weight)

    def extra_repr(self) -> str:
        return (
            f"{self.normalized_shape}, eps={self.eps}, "
            f"sequence_parallel={self.sequence_parallel}, "
            f"zero_centered_gamma={self.zero_centered_gamma}"
        )


__all__ = ["RMSNorm"]