Repository: pytorch
Issue: Unexpected behavior of `torch.nn.init.trunc_normal` with bf16 tensors
Labels: module: distributions,module: nn,triaged

### 🐛 Describe the bug

Hi, I found that `torch.nn.init.trunc_normal` produces unexpected output for bf16 tensors due to the precision issue. When running the following code,

```python
import torch

fp32_tensor = torch.empty(100000)
bf16_tensor = torch.empty(100000).bfloat16()

torch.nn.init.trunc_normal_(fp32_tensor, std=0.01)
torch.nn.init.trunc_normal_(bf16_tensor, std=0.01)
casted_bf16_tensor = fp32_tensor.bfloat16()

print("# mean=0, std=0.01, a=-2, b=2")
print("fp32")
print(fp32_tensor.mean())
print(fp32_tensor.std())

print("bf16")
print(bf16_tensor.mean())
print(bf16_tensor.std())

print("casted bf16")
print(casted_bf16_tensor.mean())
print(casted_bf16_tensor.std())
```
it prints as follows.
```
# mean=0, std=0.01, a=-2, b=2
fp32
tensor(-6.6384e-07)
tensor(0.0100)
bf16
tensor(-0.0081, dtype=torch.bfloat16)
tensor(0.1270, dtype=torch.bfloat16)
casted bf16
tensor(-6.8918e-07, dtype=torch.bfloat16)
tensor(0.0100, dtype=torch.bfloat16)
```

https://github.com/pytorch/pytorch/blob/bf4f8919df8ee88e356b407bb84ed818ebfb407b/torch/nn/init.py#L38-L59
I found that this issue originates from the bf16 uniform sampling. For sufficiently small stds (which is typical for recent large models), `l` and `u` become 0 and 1. In this case, `trunc_normal_` draws bf16 numbers from a uniform distribution (0, 1). However, because bf16 has only 7 mantissa bits, a nontrivial portion of them becomes zero, produces -inf after `erfinv` and leading to truncation to `a`, which shifts the distribution. You can confirm this through the following code. When using a large negative `a`, the shift becomes significantly problematic.
```python
import torch

fp32_tensor = torch.empty(100000)
bf16_tensor = torch.empty(100000).bfloat16()

torch.nn.init.trunc_normal_(fp32_tensor, std=0.01, a=-100, b=100)
torch.nn.init.trunc_normal_(bf16_tensor, std=0.01, a=-100, b=100)
casted_bf16_tensor = fp32_tensor.bfloat16()

print("# mean=0, std=0.01, a=-100, b=100")
print("fp32")
print(fp32_tensor.mean())
print(fp32_tensor.std())

print("bf16")
print(bf16_tensor.mean())
print(bf16_tensor.std())

print("casted bf16")
print(casted_bf16_tensor.mean())
print(casted_bf16_tensor.std())

torch.nn.init.uniform_(bf16_tensor)
print("\n# ratio of zeros for bf16 tensor w/ uniform sampling")
print(bf16_tensor.eq(0).sum() / bf16_tensor.numel())
```
```
# mean=0, std=0.01, a=-100, b=100
fp32
tensor(7.5750e-05)
tensor(0.0100)
bf16
tensor(-0.3789, dtype=torch.bfloat16)
tensor(6.1250, dtype=torch.bfloat16)
casted bf16
tensor(7.5817e-05, dtype=torch.bfloat16)
tensor(0.0100, dtype=torch.bfloat16)

# ratio of zeros for bf16 tensor w/ uniform sampling
tensor(0.0040)
```

Note that CUDA implementation of bf16 `erfinv` is removed in https://github.com/pytorch/pytorch/issues/57707 due to precision issue. See also https://github.com/jax-ml/jax/discussions/13798.

### Versions

PyTorch version: 2.4.1+cu124
Is debug build: False
CUDA used to build PyTorch: 12.4
ROCM used to build PyTorch: N/A

OS: Ubuntu 22.04.5 LTS (x86_64)
GCC version: (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0
Clang version: Could not collect
CMake version: Could not collect
Libc version: glibc-2.35

Python version: 3.11.10 (main, Sep  7 2024, 18:35:41) [GCC 11.4.0] (64-bit runtime)
Python platform: Linux-6.8.0-49-generic-x86_64-with-glibc2.35
Is CUDA available: True
CUDA runtime version: 12.4.131
CUDA_MODULE_LOADING set to: LAZY
GPU models and configuration: GPU 0: NVIDIA GeForce RTX 4090
Nvidia driver version: 565.57.01
cuDNN version: Could not collect
HIP runtime version: N/A
MIOpen runtime version: N/A
Is XNNPACK available: True

CPU:
Architecture:                         x86_64
CPU op-mode(s):                       32-bit, 64-bit
Address sizes:                        48 bits physical, 48 bits virtual
Byte Order:                           Little Endian
CPU(s):                               32
On-line CPU(s) list:                  0-31
Vendor ID:                            AuthenticAMD
Model name:                           AMD Ryzen 9 7950X 16-Core Processor
CPU family:                           25
Model:                                97
Thread(s) per core:                   2
Core(s) per socket:                   16
Socket(s):                            1
Stepping:                             2
CPU max MHz:                          5881.0000
CPU min MHz:                          400.0000
BogoMIPS:                             8982.90
Flags:                                fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ht syscall nx mmxext fxsr_opt pdpe1gb rdtscp lm constant_tsc rep_good amd_lbr_v2 nopl nonstop_tsc cpuid extd_apicid aperfmperf rapl pni pclmulqdq monitor ssse3 fma cx16 sse4_1 sse4_2 movbe popcnt aes xsave avx f16c rdrand lahf_lm cmp_legacy svm extapic cr8_legacy abm sse4a misalignsse 3dnowprefetch osvw ibs skinit wdt tce topoext perfctr_core perfctr_nb bpext perfctr_llc mwaitx cpb cat_l3 cdp_l3 hw_pstate ssbd mba perfmon_v2 ibrs ibpb stibp ibrs_enhanced vmmcall fsgsbase bmi1 avx2 smep bmi2 erms invpcid cqm rdt_a avx512f avx512dq rdseed adx smap avx512ifma clflushopt clwb avx512cd sha_ni avx512bw avx512vl xsaveopt xsavec xgetbv1 xsaves cqm_llc cqm_occup_llc cqm_mbm_total cqm_mbm_local user_shstk avx512_bf16 clzero irperf xsaveerptr rdpru wbnoinvd cppc arat npt lbrv svm_lock nrip_save tsc_scale vmcb_clean flushbyasid decodeassists pausefilter pfthreshold avic v_vmsave_vmload vgif x2avic v_spec_ctrl vnmi avx512vbmi umip pku ospke avx512_vbmi2 gfni vaes vpclmulqdq avx512_vnni avx512_bitalg avx512_vpopcntdq rdpid overflow_recov succor smca fsrm flush_l1d
Virtualization:                       AMD-V
L1d cache:                            512 KiB (16 instances)
L1i cache:                            512 KiB (16 instances)
L2 cache:                             16 MiB (16 instances)
L3 cache:                             64 MiB (2 instances)
NUMA node(s):                         1
NUMA node0 CPU(s):                    0-31
Vulnerability Gather data sampling:   Not affected
Vulnerability Itlb multihit:          Not affected
Vulnerability L1tf:                   Not affected
Vulnerability Mds:                    Not affected
Vulnerability Meltdown:               Not affected
Vulnerability Mmio stale data:        Not affected
Vulnerability Reg file data sampling: Not affected
Vulnerability Retbleed:               Not affected
Vulnerability Spec rstack overflow:   Mitigation; Safe RET
Vulnerability Spec store bypass:      Mitigation; Speculative Store Bypass disabled via prctl
Vulnerability Spectre v1:             Mitigation; usercopy/swapgs barriers and __user pointer sanitization
Vulnerability Spectre v2:             Mitigation; Enhanced / Automatic IBRS; IBPB conditional; STIBP always-on; RSB filling; PBRSB-eIBRS Not affected; BHI Not affected
Vulnerability Srbds:                  Not affected
Vulnerability Tsx async abort:        Not affected

Versions of relevant libraries:
[pip3] numpy==1.26.3
[pip3] nvidia-cublas-cu12==12.4.2.65
[pip3] nvidia-cuda-cupti-cu12==12.4.99
[pip3] nvidia-cuda-nvrtc-cu12==12.4.99
[pip3] nvidia-cuda-runtime-cu12==12.4.99
[pip3] nvidia-cudnn-cu12==9.1.0.70
[pip3] nvidia-cufft-cu12==11.2.0.44
[pip3] nvidia-curand-cu12==10.3.5.119
[pip3] nvidia-cusolver-cu12==11.6.0.99
[pip3] nvidia-cusparse-cu12==12.3.0.142
[pip3] nvidia-nccl-cu12==2.20.5
[pip3] nvidia-nvjitlink-cu12==12.4.99
[pip3] nvidia-nvtx-cu12==12.4.99
[pip3] torch==2.4.1+cu124
[pip3] torchaudio==2.4.1+cu124
[pip3] torchvision==0.19.1+cu124
[pip3] triton==3.0.0
[conda] Could not collect

cc @fritzo @neerajprad @alicanb @nikitaved @albanD @mruberry @jbschlosser @walterddr @mikaylagawarecki