Get proper battery life on Linux laptops with NVIDIA RTX GPUs.
TL;DR: By default, NVIDIA GPUs stay awake and drain ~6W constantly. This guide enables D3cold power states so the GPU sleeps when idle, doubling battery life on hybrid graphics laptops (tested: 20W → 10W idle).
Linux hybrid graphics laptops with NVIDIA RTX GPUs have terrible battery life out of the box. The GPU never enters deep sleep (D3cold) because:
-
GNOME Shell uses NVIDIA for EGL rendering - libglvnd loads
10_nvidia.jsonbefore50_mesa.json, so the desktop compositor keeps the GPU awake permanently. -
The NVIDIA driver has bugs - Sleep/shutdown hangs, CPU spin bugs, and missing power management features require workarounds.
-
System services keep the GPU awake -
nvidia-persistenced,nvidia-settings, and even Chrome probe the GPU at startup. -
TLP fights against you - Default
RUNTIME_PM_ON_AC=onforces all devices to stay awake on AC power.
git clone https://github.com/jvdillon/rtx-laptop-linux.git
cd rtx-laptop-linux
sudo ./setup.sh
# Reboot requiredThe script will:
- Install NVIDIA drivers (or prompt to reinstall if present)
- Apply all power management fixes
- Configure systemd services for proper sleep/wake handling
libglvnd loads EGL vendors by filename order; 10_nvidia.json wins over
50_mesa.json, so the desktop renders on (and wakes) the dGPU. A plain rename
is not durable: libnvidia-gl upgrades re-create 10_nvidia.json, silently
undoing the demotion. Use dpkg-divert so the package's own file is permanently
redirected and never reappears at the winning filename across upgrades.
sudo dpkg-divert --add --rename \
--divert /usr/share/glvnd/egl_vendor.d/90_nvidia.json \
/usr/share/glvnd/egl_vendor.d/10_nvidia.jsonEven with the priority fix, some apps probe NVIDIA. This environment variable forces Mesa for all desktop rendering:
# /etc/environment
__EGL_VENDOR_LIBRARY_FILENAMES=/usr/share/glvnd/egl_vendor.d/50_mesa.jsonThe dGPU drives no display connectors (all eDP/DP/HDMI are on the Intel iGPU),
so KMS only exposes /dev/dri/card0+renderD129 for the desktop to probe.
CUDA uses /dev/nvidia* directly and is unaffected by modeset.
Gotcha: ubuntu-drivers ships /usr/lib/modprobe.d/nvidia-kms.conf with
modeset=1. modprobe merges all *.conf across /etc and /usr/lib, sorts by
filename, and for a repeated option last-wins. A differently-named
file in /etc does not override it — nvidia-graphics-drivers-kms.conf ("g")
sorts before nvidia-kms.conf ("k"), so the /usr/lib modeset=1 wins. The
only deterministic override is a same-basename file in /etc, which fully
shadows the /usr/lib copy.
# Same basename as /usr/lib/modprobe.d/nvidia-kms.conf -> /etc copy replaces it
echo 'options nvidia-drm modeset=0' | sudo tee /etc/modprobe.d/nvidia-kms.conf
sudo update-initramfs -uVerify after reboot: cat /sys/module/nvidia_drm/parameters/modeset -> N.
Driver bug: the nv_open_q kernel thread spins at 20-70% CPU when nonblocking
open is enabled (default).
# /etc/modprobe.d/nvidia-graphics-drivers.conf
options nvidia NVreg_EnableNonblockingOpen=0Reference: NVIDIA/open-gpu-kernel-modules#615
The GPU needs power/control=auto to enter D3cold. This udev rule sets it for
all NVIDIA 3D controllers:
# /etc/udev/rules.d/80-nvidia-pm.rules
ACTION=="add|change|bind", SUBSYSTEM=="pci", ATTR{vendor}=="0x10de", ATTR{class}=="0x030200", ATTR{power/control}="auto"Driver bug: when the GPU is in D3cold, the driver can't cleanly unload or prepare for system sleep. This causes hangs after "Reached target Reboot" or sleep failures with error -5.
A systemd service wakes the GPU before any shutdown/sleep target:
# /etc/systemd/system/nvidia-wake.service
[Unit]
Description=Wake NVIDIA GPU before shutdown/sleep to prevent hangs
DefaultDependencies=no
Before=shutdown.target reboot.target halt.target suspend.target hibernate.target hybrid-sleep.target
[Service]
Type=oneshot
ExecStart=/bin/bash -c 'echo on > /sys/bus/pci/devices/0000:01:00.0/power/control; sleep 1'
[Install]
WantedBy=halt.target reboot.target shutdown.target suspend.target hibernate.target hybrid-sleep.targetDriver bug: when nvidia modules are in initramfs, they load BEFORE hibernate
resume. The kernel then tries to freeze a freshly-loaded driver that was never
properly initialized, causing pci_pm_freeze(): nv_pmops_freeze returns -5.
Fix: exclude nvidia from initramfs so modules load AFTER hibernate resume.
Ubuntu 25.04+ (dracut):
# /etc/dracut.conf.d/nvidia-exclude.conf
omit_drivers+=" nvidia nvidia-drm nvidia-modeset nvidia-uvm "Ubuntu 24.04 LTS (initramfs-tools):
# /etc/initramfs-tools/hooks/exclude-nvidia
# Hook script that removes nvidia modules from initramfsAlso adds early i915 KMS for external display during LUKS decrypt.
References:
- https://bbs.archlinux.org/viewtopic.php?id=285508
- https://forums.developer.nvidia.com/t/preservevideomemoryallocations-systemd-services-causes-resume-from-hibernate-to-fail/233643
The nvidia driver sets power/control=on when it initializes, and our wake
service sets it before sleep. Both prevent D3cold after boot/resume.
Two systemd units restore power/control=auto:
# /etc/systemd/system/nvidia-power-control.service (after boot)
[Unit]
Description=Enable NVIDIA GPU runtime power management
After=multi-user.target tlp.service
[Service]
Type=oneshot
ExecStart=/bin/bash -c 'echo auto > /sys/bus/pci/devices/0000:01:00.0/power/control'
[Install]
WantedBy=multi-user.target# /etc/systemd/system/nvidia-resume.service.d/restore-pm.conf (after resume)
[Service]
ExecStartPost=/bin/bash -c 'echo auto > /sys/bus/pci/devices/0000:01:00.0/power/control'This service keeps the driver loaded even when the GPU is unused, preventing D3cold. Disabling it enables on-demand loading.
Trade-off: first CUDA call has ~1s latency while the driver loads.
sudo systemctl disable nvidia-persistencednvidia-powerd is the Dynamic Boost daemon: while the GPU is active it
rebalances the shared CPU/GPU power budget. It depends on the SBIOS exposing the
GPU power rail via the NVPCF ACPI interface. This laptop's SBIOS does not,
and actively requests Dynamic Boost be disabled — powerd logs:
ERROR! Client (presumably SBIOS) has requested to disable Dynamic Boost DC controller
With powerd running, it polls this dead interface and floods the journal every
~22s with PRH failed to update thermal limit. Dynamic Boost is useless for
sustained compute (the GPU wants its full budget) and unavailable here
regardless.
Masking powerd removes the every-22s flood. It does not eliminate PRH
messages entirely: the driver itself probes the SBIOS thermal interface at init
and on each D3cold wake, so a few PRH lines per boot remain (clustered at
boot/wake, not the periodic flood). These are benign — there is no driver
flag to disable the PlatformRequestHandler (modinfo nvidia exposes none),
and the real fix would be an LG BIOS that exposes NVPCF (none exists for the
16Z90TR as of this writing). D3cold/battery and suspend use separate subsystems
and are unaffected.
Use mask (not disable): the driver package's systemd preset re-enables
powerd on every upgrade; mask survives presets.
sudo systemctl mask --now nvidia-powerdThese official NVIDIA services handle saving/restoring GPU state across suspend and hibernate:
sudo systemctl enable nvidia-suspend nvidia-hibernate nvidia-resumenvidia-settings spawns at login and keeps /dev/nvidia* open, preventing
D3cold.
# ~/.config/autostart/nvidia-settings-autostart.desktop
[Desktop Entry]
Type=Application
Hidden=trueChrome probes /dev/nvidiactl at startup even when not using GPU acceleration.
Force it to use Mesa:
sudo sed -i 's|^Exec=/usr/bin/google-chrome-stable|Exec=env __NV_PRIME_RENDER_OFFLOAD=0 __GLX_VENDOR_LIBRARY_NAME=mesa /usr/bin/google-chrome-stable|' \
/usr/share/applications/google-chrome.desktopTLP defaults to RUNTIME_PM_ON_AC=on which forces all devices awake on AC
power:
sudo sed -i '/^#*RUNTIME_PM_ON_AC/d; $ a RUNTIME_PM_ON_AC="auto"' /etc/tlp.confApply the initramfs changes (nvidia exclusion, early i915):
sudo update-initramfs -u # Ubuntu 24.04
sudo dracut --force # Ubuntu 25.04+After rebooting, verify the GPU enters D3cold when idle:
# Should show "suspended" when GPU is idle
cat /sys/bus/pci/devices/0000:01:00.0/power/runtime_status
# nv_open_q should be ~0% CPU (or not running)
ps aux | grep '\[nv_open_q\]'
# Power draw should be 8-12W idle (without heavy apps)
cat /sys/class/power_supply/BAT*/power_now | awk '{print $1/1000000 " W"}'To wake the GPU for CUDA work:
nvidia-smi # GPU wakes on-demandCheck what's keeping it awake:
# List processes using nvidia devices
sudo fuser -v /dev/nvidia*
# Check if any GL apps are running
lsof /dev/dri/card*Common culprits: Discord, Slack, VS Code (Electron apps), OBS.
The wake service may not be running:
sudo systemctl status nvidia-wake.service
sudo systemctl enable nvidia-wake.serviceCheck if nvidia modules are in initramfs:
lsinitramfs /boot/initrd.img-$(uname -r) | grep nvidiaIf they appear, the dracut config didn't take effect:
sudo update-initramfs -uTry adding to kernel command line:
nvidia.NVreg_PreserveVideoMemoryAllocations=1
Known NVIDIA driver bug (nv_restore_user_channels fails). Use suspend or
hibernate instead - hybrid-sleep is fundamentally broken with these drivers.
The setup script auto-detects your GPU PCI address. The examples in this
README use 0000:01:00.0 for illustration.
To find yours manually:
lspci -D | grep -i nvidia
# Example output: 0000:01:00.0 3D controller: NVIDIA Corporation...If you need to update the systemd services manually, edit:
/etc/systemd/system/nvidia-wake.service/etc/systemd/system/nvidia-power-control.service/etc/systemd/system/nvidia-resume.service.d/restore-pm.conf
For reference, here's how to install the NVIDIA driver and CUDA toolkit on Ubuntu 24.04+:
# Remove any existing nvidia packages
sudo apt purge -y '^nvidia-.*' '^libnvidia-.*' '^cuda-.*' '^libcuda-.*'
sudo apt autoremove -y
# Add NVIDIA CUDA repository
wget https://developer.download.nvidia.com/compute/cuda/repos/ubuntu2404/x86_64/cuda-keyring_1.1-1_all.deb
sudo dpkg -i cuda-keyring_1.1-1_all.deb
sudo apt update
# Install open driver + CUDA toolkit
sudo apt install -y nvidia-open cuda-toolkit
# Add CUDA to PATH (~/.bashrc)
export PATH="/usr/local/cuda/bin${PATH:+:${PATH}}"
export LD_LIBRARY_PATH="/usr/local/cuda/lib64${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}}"To use nsys and other NVIDIA profiling tools without root:
# Allow profiling for all users
echo "options nvidia NVreg_RestrictProfilingToAdminUsers=0" | \
sudo tee /etc/modprobe.d/nvidia-profiling.conf
sudo update-initramfs -u
# Set perf_event_paranoid
echo "kernel.perf_event_paranoid=2" | sudo tee /etc/sysctl.d/99-perf.conf
sudo sysctl -p /etc/sysctl.d/99-perf.conf- NVIDIA Dynamic Power Management
- Arch Wiki: NVIDIA Tips
- Kernel PCI Power Management
- nv_open_q CPU spin bug
- LG Gram Pro 16" 2025 (RTX 5050 Mobile) - Ubuntu 25.10, 25.04
- Ubuntu 24.04 LTS supported (uses initramfs-tools instead of dracut)
- Should work on any hybrid graphics laptop with RTX 20-series or newer
Apache 2.0