NVIDIA / NVIDIA/TransformerEngine
RMSNorm precision different from HF implementation
Nobody has claimed this yet.
- Dominant language
- Python
- Stars
- 3.5k
- Forks
- 831
- Avg merge
- 3d 11h
- Merged PRs (30d)
- 65
Description
We noticed there's a tiny implementation difference that makes transformer_engine.pytorch.module.rmsnorm and also TELayerNormColumnParallelLinear generate results from HF implementation.
And the tiny difference is when the hidden_states are converted back to bfloat16. Here's the gap:
- the red line is native HF implementation, and converts hidden_states to bfloat16 before multiply weight, and TENorm's result is different form this implementation
- the green line implementation matches the TENorm's implementation, and converts hidden_states to bfloat16 after multiply weights.
We wonder if TE could provide an other to match the HF's implementation, which converts hidden_states to bfloat16 before multiply the weights. Thanks.
How to reproduce
Version: transformer-engine 1.7.0+4e7caa1
Code to reproduce:
import unittest
import torch
import torch.nn as nn
from transformer_engine.pytorch.module.rmsnorm import RMSNorm as TELayerNorm
from copy_from_hf import HFRMSNorm
class TestLayerNormComparison(unittest.TestCase):
def setUp(self):
self.hidden_size = 4096
self.batch_size = 1
self.seq_length = 1024
self.eps = 1e-5
self.shared_weight = nn.Parameter(torch.randn(self.hidden_size, dtype=torch.bfloat16))
self.te_layernorm = TELayerNorm(self.hidden_size, eps=self.eps, zero_centered_gamma=False).to(torch.bfloat16)
self.hf_rmsnorm = HFRMSNorm(self.hidden_size, eps=self.eps).to(torch.bfloat16)
self.te_layernorm.weight = self.shared_weight
self.hf_rmsnorm.weight = self.shared_weight
self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
self.te_layernorm.to(self.device)
self.hf_rmsnorm.to(self.device)
def test_layernorm_comparison(self):
input_tensor = torch.randn(self.batch_size, self.seq_length, self.hidden_size,
dtype=torch.bfloat16, device=self.device)
with torch.no_grad():
te_output = self.te_layernorm(input_tensor)
hf_output = self.hf_rmsnorm(input_tensor)
assert torch.allclose(te_output, hf_output, atol=1e-2)
if __name__ == '__main__':
unittest.main()
First define HFRMSNorm with native implementation:
import torch
from torch import nn
class HFRMSNorm(nn.Module):
def __init__(self, hidden_size, eps=1e-6, config=None):
super().__init__()
self.weight = nn.Parameter(torch.ones(hidden_size))
self.variance_epsilon = eps
def forward(self, hidden_states):
input_dtype = hidden_states.dtype
hidden_states = hidden_states.to(torch.float32)
variance = hidden_states.pow(2).mean(-1, keepdim=True)
hidden_states = hidden_states * torch.rsqrt(variance + self.variance_epsilon)
return self.weight * hidden_states.to(input_dtype)
The assertion should fail when we run the code with this implementation.
Now, let's change the last line from return self.weight * hidden_states.to(input_dtype) to return (self.weight.to(torch.float32) * hidden_states).to(input_dtype), the assertion should pass.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start by running the supplied comparison against transformer_engine.pytorch.module.rmsnorm.RMSNorm and the TELayerNormColumnParallelLinear path. Read the RMSNorm implementation and compare its dtype conversion order with the HFRMSNorm example. Done means an option or behavior that matches the HF result when hidden_states are converted to bfloat16 before multiplying by the weight, with the reproduction passing.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python, pytorch
- Domain
- machine-learning, performance
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 45/100