diff --git a/src/twinkle/kernel/npu_impls/rms_norm.py b/src/twinkle/kernel/npu_impls/rms_norm.py index 7fd7a5f7..7996db95 100644 --- a/src/twinkle/kernel/npu_impls/rms_norm.py +++ b/src/twinkle/kernel/npu_impls/rms_norm.py @@ -15,6 +15,10 @@ logger = get_logger() +# Resolved once at import: matches the legacy "patch-time, process-wide" invariant. +# Mid-process env mutation will not retroactively change behavior. +_FORCE_FP32 = os.environ.get('TWINKLE_NPU_GATED_RMSNorm_FP32', '0').lower() in ('1', 'true', 'on', 'yes') + class NpuRMSNorm(nn.Module): """Class-replacement impl for HF RMSNorm variants. @@ -28,7 +32,7 @@ def _twinkle_residual_param(self) -> bool: """Lazily detect residual parameterization (e.g. Qwen3.5: scale = 1 + weight).""" cached = getattr(self, '_twinkle_residual_cached', None) if cached is None: - cached = abs(self.weight.data.mean().item()) < 0.3 + cached = not hasattr(self, 'variance_epsilon') self._twinkle_residual_cached = cached if cached: logger.debug('[NPU] NpuRMSNorm using residual parameterization (1.0 + weight)') @@ -39,17 +43,16 @@ def _twinkle_eps(self) -> float: def forward(self, hidden_states: torch.Tensor) -> torch.Tensor: import torch_npu - target_dtype = hidden_states.dtype + input_dtype = hidden_states.dtype + target_dtype = torch.float32 if _FORCE_FP32 else input_dtype + if _FORCE_FP32: + hidden_states = hidden_states.to(torch.float32) if self._twinkle_residual_param(): - scale = (1.0 + self.weight).to(target_dtype) + scale = 1.0 + self.weight.to(target_dtype) else: scale = self.weight.to(target_dtype) - return torch_npu.npu_rms_norm(hidden_states, scale, epsilon=self._twinkle_eps())[0] - - -# Resolved once at import: matches the legacy "patch-time, process-wide" invariant. -# Mid-process env mutation will not retroactively change behavior. -_FORCE_FP32 = os.environ.get('TWINKLE_NPU_GATED_RMSNorm_FP32', '0').lower() in ('1', 'true', 'on', 'yes') + out = torch_npu.npu_rms_norm(hidden_states, scale, epsilon=self._twinkle_eps())[0] + return out.to(input_dtype) if _FORCE_FP32 else out def npu_gated_rms_norm_forward(self, hidden_states, gate=None): @@ -61,7 +64,7 @@ def npu_gated_rms_norm_forward(self, hidden_states, gate=None): if _FORCE_FP32: hidden_states = hidden_states.to(torch.float32) - weight = self.weight.float() + weight = self.weight.to(torch.float32) gate = gate.to(torch.float32) if gate is not None else None else: weight = self.weight @@ -69,4 +72,4 @@ def npu_gated_rms_norm_forward(self, hidden_states, gate=None): hidden_states = torch_npu.npu_rms_norm(hidden_states, weight, epsilon=_eps)[0] if gate is not None: hidden_states = hidden_states * F.silu(gate) - return hidden_states.to(input_dtype) + return hidden_states.to(input_dtype) if _FORCE_FP32 else hidden_states