pytorch - 💡(How to fix) Fix Numerical discrepancy between CUDA FP32 eager and CPU FP32 eager for isolated LSTM layer [4 comments, 3 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
pytorch/pytorch#178263Fetched 2026-04-08 01:20:50
View on GitHub
Comments
4
Participants
3
Timeline
7
Reactions
0
Author
Timeline (top)
commented ×4labeled ×2closed ×1

Fix Action

Fix / Workaround

CPU: Architecture: x86_64 CPU op-mode(s): 32-bit, 64-bit Address sizes: 40 bits physical, 48 bits virtual Byte Order: Little Endian CPU(s): 48 On-line CPU(s) list: 0-47 Vendor ID: GenuineIntel Model name: QEMU Virtual CPU version 2.5+ CPU family: 15 Model: 107 Thread(s) per core: 1 Core(s) per socket: 48 Socket(s): 1 Stepping: 1 BogoMIPS: 4190.15 Flags: fpu de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ht syscall nx lm constant_tsc nopl xtopology cpuid tsc_known_freq pni ssse3 fma cx16 sse4_1 sse4_2 x2apic movbe popcnt aes xsave avx f16c hypervisor lahf_lm abm cpuid_fault pti bmi1 avx2 bmi2 avx512f avx512dq avx512cd avx512bw avx512vl Hypervisor vendor: KVM Virtualization type: full L1d cache: 1.5 MiB (48 instances) L1i cache: 1.5 MiB (48 instances) L2 cache: 192 MiB (48 instances) L3 cache: 16 MiB (1 instance) NUMA node(s): 1 NUMA node0 CPU(s): 0-47 Vulnerability Gather data sampling: Not affected Vulnerability Itlb multihit: KVM: Mitigation: VMX unsupported Vulnerability L1tf: Mitigation; PTE Inversion Vulnerability Mds: Vulnerable: Clear CPU buffers attempted, no microcode; SMT Host state unknown Vulnerability Meltdown: Mitigation; PTI Vulnerability Mmio stale data: Unknown: No mitigations Vulnerability Reg file data sampling: Not affected Vulnerability Retbleed: Not affected Vulnerability Spec rstack overflow: Not affected Vulnerability Spec store bypass: Vulnerable Vulnerability Spectre v1: Mitigation; usercopy/swapgs barriers and __user pointer sanitization Vulnerability Spectre v2: Mitigation; Retpolines; STIBP disabled; RSB filling; PBRSB-eIBRS Not affected; BHI Retpoline Vulnerability Srbds: Not affected Vulnerability Tsx async abort: Not affected Vulnerability Vmscape: Not affected

Code Example

import torch
import torch.nn as nn
import os

BASELINE = "eager(cuda+float32)"
TARGET = "eager(cpu+float32)"
EPS = 1e-12

BASE_DIR = os.path.dirname(os.path.abspath(__file__))
INPUT_PATH = os.path.join(BASE_DIR, "input.pt")
SD_PATH = os.path.join(BASE_DIR, "sd.pt")


def build_lstm1(device: str, sd: dict) -> nn.Module:
    lstm = nn.LSTM(
        input_size=50,
        hidden_size=50,
        num_layers=1,
        batch_first=True,
        bidirectional=False,
    ).to(torch.device(device)).to(torch.float32).eval()
    lstm.load_state_dict(sd, strict=True)
    return lstm


def run_mode(device: str, x_cpu_fp32: torch.Tensor, sd: dict):
    lstm = build_lstm1(device, sd)
    with torch.no_grad():
        x = x_cpu_fp32.to(device=device, dtype=torch.float32)
        y, _ = lstm(x)
    return y.detach().float().cpu()


def compare_tensors(ref: torch.Tensor, cur: torch.Tensor):
    ref_f = ref.reshape(ref.shape[0], -1).float()
    cur_f = cur.reshape(cur.shape[0], -1).float()
    valid = torch.isfinite(ref_f) & torch.isfinite(cur_f)
    abs_err = (cur_f - ref_f).abs()
    rel_err = torch.where(valid, abs_err / (ref_f.abs() + EPS), torch.zeros_like(abs_err))
    return float(abs_err.max().item()), float(rel_err.max().item())


def has_nan_inf(t: torch.Tensor):
    return bool(torch.isnan(t).any().item()), bool(torch.isinf(t).any().item())


def main():
    x = torch.load(INPUT_PATH, map_location="cpu").float()
    sd = torch.load(SD_PATH, map_location="cpu")
    print(f"input min={x.min().item():.6g} max={x.max().item():.6g}")
    print(
        f"weight_ih_l0 min={sd['weight_ih_l0'].min().item():.6g} max={sd['weight_ih_l0'].max().item():.6g}"
    )
    print(
        f"weight_hh_l0 min={sd['weight_hh_l0'].min().item():.6g} max={sd['weight_hh_l0'].max().item():.6g}"
    )
    print(
        f"bias_ih_l0 min={sd['bias_ih_l0'].min().item():.6g} max={sd['bias_ih_l0'].max().item():.6g}"
    )
    print(
        f"bias_hh_l0 min={sd['bias_hh_l0'].min().item():.6g} max={sd['bias_hh_l0'].max().item():.6g}"
    )
    print(f"\ncompare: {BASELINE} vs {TARGET}")

    out_base = run_mode("cuda", x, sd)
    out_cur = run_mode("cpu", x, sd)
    abs_c, rel_c = compare_tensors(out_base, out_cur)
    print(f"final abs_max={abs_c:.6e}")
    print(f"final rel_max={rel_c:.6e}")


if __name__ == "__main__":
    main()

---

input min=1.00001 max=131358
weight_ih_l0 min=-0.154915 max=0.154858
weight_hh_l0 min=-0.259587 max=0.242374
bias_ih_l0 min=0 max=0
bias_hh_l0 min=0 max=0

compare: eager(cuda+float32) vs eager(cpu+float32)
final abs_max=1.102686e-05
final rel_max=1.999983e+00
RAW_BUFFERClick to expand / collapse

🐛 Describe the bug

I observed a reproducible numerical discrepancy between CUDA FP32 eager and CPU FP32 eager using a minimal, isolated single-layer nn.LSTM reproduction.

Using identical input and identical layer parameters, the two eager backends produce different outputs:

abs_max = 1.102686e-05 rel_max = 1.999983e+00 No NaN/Inf is present in either output.

Could the maintainers confirm whether this discrepancy level is expected for LSTM across CPU and CUDA eager FP32, or if this should be treated as an accuracy issue?

import torch
import torch.nn as nn
import os

BASELINE = "eager(cuda+float32)"
TARGET = "eager(cpu+float32)"
EPS = 1e-12

BASE_DIR = os.path.dirname(os.path.abspath(__file__))
INPUT_PATH = os.path.join(BASE_DIR, "input.pt")
SD_PATH = os.path.join(BASE_DIR, "sd.pt")


def build_lstm1(device: str, sd: dict) -> nn.Module:
    lstm = nn.LSTM(
        input_size=50,
        hidden_size=50,
        num_layers=1,
        batch_first=True,
        bidirectional=False,
    ).to(torch.device(device)).to(torch.float32).eval()
    lstm.load_state_dict(sd, strict=True)
    return lstm


def run_mode(device: str, x_cpu_fp32: torch.Tensor, sd: dict):
    lstm = build_lstm1(device, sd)
    with torch.no_grad():
        x = x_cpu_fp32.to(device=device, dtype=torch.float32)
        y, _ = lstm(x)
    return y.detach().float().cpu()


def compare_tensors(ref: torch.Tensor, cur: torch.Tensor):
    ref_f = ref.reshape(ref.shape[0], -1).float()
    cur_f = cur.reshape(cur.shape[0], -1).float()
    valid = torch.isfinite(ref_f) & torch.isfinite(cur_f)
    abs_err = (cur_f - ref_f).abs()
    rel_err = torch.where(valid, abs_err / (ref_f.abs() + EPS), torch.zeros_like(abs_err))
    return float(abs_err.max().item()), float(rel_err.max().item())


def has_nan_inf(t: torch.Tensor):
    return bool(torch.isnan(t).any().item()), bool(torch.isinf(t).any().item())


def main():
    x = torch.load(INPUT_PATH, map_location="cpu").float()
    sd = torch.load(SD_PATH, map_location="cpu")
    print(f"input min={x.min().item():.6g} max={x.max().item():.6g}")
    print(
        f"weight_ih_l0 min={sd['weight_ih_l0'].min().item():.6g} max={sd['weight_ih_l0'].max().item():.6g}"
    )
    print(
        f"weight_hh_l0 min={sd['weight_hh_l0'].min().item():.6g} max={sd['weight_hh_l0'].max().item():.6g}"
    )
    print(
        f"bias_ih_l0 min={sd['bias_ih_l0'].min().item():.6g} max={sd['bias_ih_l0'].max().item():.6g}"
    )
    print(
        f"bias_hh_l0 min={sd['bias_hh_l0'].min().item():.6g} max={sd['bias_hh_l0'].max().item():.6g}"
    )
    print(f"\ncompare: {BASELINE} vs {TARGET}")

    out_base = run_mode("cuda", x, sd)
    out_cur = run_mode("cpu", x, sd)
    abs_c, rel_c = compare_tensors(out_base, out_cur)
    print(f"final abs_max={abs_c:.6e}")
    print(f"final rel_max={rel_c:.6e}")


if __name__ == "__main__":
    main()
input min=1.00001 max=131358
weight_ih_l0 min=-0.154915 max=0.154858
weight_hh_l0 min=-0.259587 max=0.242374
bias_ih_l0 min=0 max=0
bias_hh_l0 min=0 max=0

compare: eager(cuda+float32) vs eager(cpu+float32)
final abs_max=1.102686e-05
final rel_max=1.999983e+00

Versions

PyTorch version: 2.10.0+cu126 Is debug build: False CUDA used to build PyTorch: 12.6 ROCm used to build PyTorch: N/A

OS: Ubuntu 24.04.3 LTS (x86_64) GCC version: (Ubuntu 13.3.0-6ubuntu2~24.04) 13.3.0 Clang version: Could not collect CMake version: Could not collect Libc version: glibc-2.39

Python version: 3.10.19 | packaged by conda-forge | (main, Jan 26 2026, 23:45:08) [GCC 14.3.0] (64-bit runtime) Python platform: Linux-6.8.0-90-generic-x86_64-with-glibc2.39 Is CUDA available: True CUDA runtime version: 12.6.20 CUDA_MODULE_LOADING set to: GPU models and configuration: GPU 0: NVIDIA GeForce RTX 3090 GPU 1: NVIDIA GeForce RTX 3090

Nvidia driver version: 560.35.03 cuDNN version: Could not collect Is XPU available: False HIP runtime version: N/A MIOpen runtime version: N/A Is XNNPACK available: True Caching allocator config: N/A

CPU: Architecture: x86_64 CPU op-mode(s): 32-bit, 64-bit Address sizes: 40 bits physical, 48 bits virtual Byte Order: Little Endian CPU(s): 48 On-line CPU(s) list: 0-47 Vendor ID: GenuineIntel Model name: QEMU Virtual CPU version 2.5+ CPU family: 15 Model: 107 Thread(s) per core: 1 Core(s) per socket: 48 Socket(s): 1 Stepping: 1 BogoMIPS: 4190.15 Flags: fpu de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ht syscall nx lm constant_tsc nopl xtopology cpuid tsc_known_freq pni ssse3 fma cx16 sse4_1 sse4_2 x2apic movbe popcnt aes xsave avx f16c hypervisor lahf_lm abm cpuid_fault pti bmi1 avx2 bmi2 avx512f avx512dq avx512cd avx512bw avx512vl Hypervisor vendor: KVM Virtualization type: full L1d cache: 1.5 MiB (48 instances) L1i cache: 1.5 MiB (48 instances) L2 cache: 192 MiB (48 instances) L3 cache: 16 MiB (1 instance) NUMA node(s): 1 NUMA node0 CPU(s): 0-47 Vulnerability Gather data sampling: Not affected Vulnerability Itlb multihit: KVM: Mitigation: VMX unsupported Vulnerability L1tf: Mitigation; PTE Inversion Vulnerability Mds: Vulnerable: Clear CPU buffers attempted, no microcode; SMT Host state unknown Vulnerability Meltdown: Mitigation; PTI Vulnerability Mmio stale data: Unknown: No mitigations Vulnerability Reg file data sampling: Not affected Vulnerability Retbleed: Not affected Vulnerability Spec rstack overflow: Not affected Vulnerability Spec store bypass: Vulnerable Vulnerability Spectre v1: Mitigation; usercopy/swapgs barriers and __user pointer sanitization Vulnerability Spectre v2: Mitigation; Retpolines; STIBP disabled; RSB filling; PBRSB-eIBRS Not affected; BHI Retpoline Vulnerability Srbds: Not affected Vulnerability Tsx async abort: Not affected Vulnerability Vmscape: Not affected

Versions of relevant libraries: [pip3] numpy==1.26.4 [pip3] nvidia-cublas-cu12==12.6.4.1 [pip3] nvidia-cuda-cupti-cu12==12.6.80 [pip3] nvidia-cuda-nvrtc-cu12==12.6.77 [pip3] nvidia-cuda-runtime-cu12==12.6.77 [pip3] nvidia-cudnn-cu12==9.10.2.21 [pip3] nvidia-cufft-cu12==11.3.0.4 [pip3] nvidia-curand-cu12==10.3.7.77 [pip3] nvidia-cusolver-cu12==11.7.1.2 [pip3] nvidia-cusparse-cu12==12.5.4.2 [pip3] nvidia-cusparselt-cu12==0.7.1 [pip3] nvidia-nccl-cu12==2.27.5 [pip3] nvidia-nvjitlink-cu12==12.6.85 [pip3] nvidia-nvtx-cu12==12.6.77 [pip3] onnxruntime-gpu==1.23.2 [pip3] optree==0.18.0 [pip3] pytorch-triton==3.2.0+git4b3bb1f8 [pip3] torch==2.10.0+cu126 [pip3] torchaudio==2.11.0.dev20260127+cu126 [pip3] torchvision==0.25.0+cu126 [pip3] triton==3.6.0+git9844da95 [conda] numpy 1.26.4 pypi_0 pypi [conda] nvidia-cublas-cu12 12.6.4.1 pypi_0 pypi [conda] nvidia-cuda-cupti-cu12 12.6.80 pypi_0 pypi [conda] nvidia-cuda-nvrtc-cu12 12.6.77 pypi_0 pypi [conda] nvidia-cuda-runtime-cu12 12.6.77 pypi_0 pypi [conda] nvidia-cudnn-cu12 9.10.2.21 pypi_0 pypi [conda] nvidia-cufft-cu12 11.3.0.4 pypi_0 pypi [conda] nvidia-curand-cu12 10.3.7.77 pypi_0 pypi [conda] nvidia-cusolver-cu12 11.7.1.2 pypi_0 pypi [conda] nvidia-cusparse-cu12 12.5.4.2 pypi_0 pypi [conda] nvidia-cusparselt-cu12 0.7.1 pypi_0 pypi [conda] nvidia-nccl-cu12 2.27.5 pypi_0 pypi [conda] nvidia-nvjitlink-cu12 12.6.85 pypi_0 pypi [conda] nvidia-nvtx-cu12 12.6.77 pypi_0 pypi [conda] optree 0.18.0 pypi_0 pypi [conda] pytorch-triton 3.2.0+git4b3bb1f8 pypi_0 pypi [conda] torch 2.10.0+cu126 pypi_0 pypi [conda] torchaudio 2.11.0.dev20260127+cu126 pypi_0 pypi [conda] torchvision 0.25.0+cu126 pypi_0 pypi [conda] triton 3.6.0+git9844da95 pypi_0 pypi

extent analysis

Fix Plan

The issue seems to be related to the numerical discrepancy between CUDA FP32 eager and CPU FP32 eager using a minimal, isolated single-layer nn.LSTM reproduction.

To fix this issue, we can try to set the seed for the random number generator to ensure reproducibility. We can also try to use the torch.backends.cudnn.deterministic flag to ensure that the CUDA operations are deterministic.

Here are the steps to fix the issue:

  • Set the seed for the random number generator:
torch.manual_seed(0)
  • Set the torch.backends.cudnn.deterministic flag:
torch.backends.cudnn.deterministic = True
  • Set the torch.backends.cudnn.benchmark flag to False:
torch.backends.cudnn.benchmark = False

We can add these lines of code at the beginning of the main function to ensure that the random number generator is seeded and the CUDA operations are deterministic.

Example Code

import torch
import torch.nn as nn
import os

# ... (rest of the code remains the same)

def main():
    torch.manual_seed(0)
    torch.backends.cudnn.deterministic = True
    torch.backends.cudnn.benchmark = False
    
    # ... (rest of the code remains the same)

if __name__ == "__main__":
    main()

Verification

To verify that the fix worked, we can run the code again and check if the numerical discrepancy between CUDA FP32 eager and CPU FP32 eager is reduced. We can also try to run the code multiple times to ensure that the results are reproducible.

Extra Tips

  • Make sure to set the seed for the random number generator and the torch.backends.cudnn.deterministic flag at the beginning of the code to ensure reproducibility.
  • Try to use the torch.backends.cudnn.benchmark flag to False to ensure that the CUDA operations are deterministic.
  • If the issue persists, try to update the CUDA and cuDNN versions to the latest ones.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

pytorch - 💡(How to fix) Fix Numerical discrepancy between CUDA FP32 eager and CPU FP32 eager for isolated LSTM layer [4 comments, 3 participants]