Manual

Load gpu_unlock before nvidia on Bazzite for an RTX 3090

This installs the gpu_unlock kernel module on Bazzite so that it runs from the initramfs, before nvidia binds the card, and leaves an RTX 3090 with full-rate FP16/BF16 (FP32-accumulate) and TF32 tensor math on every boot that carries the kernel option gpu_unlock.targets=all.

Tested on
Bazzite 44.20261023 (bazzite-nvidia-open image, kernel 7.2.9-ogc3.1.fc44), NVIDIA open driver 615.78.08, RTX 3090 (10de:2204) with VBIOS 94.02.26.80.8C, Secure Boot off; gpu-unlock at upstream commit af28cbb, NVIDIA firmware pin 610.43.03 (2026-10-26).
Not verified
the redo after an image update brings a new kernel (none has arrived yet); the file-removal half of Undo; the login check on a real failed boot (tested with its override variables only); Secure Boot; any card other than this one RTX 3090; the boot-menu keys in Recovery come from Bazzite's docs.
Needs
a login user that can read the kernel log with journalctl -k (on the tested box: a member of wheel); an rpm-ostree image that puts nvidia in its initramfs (Bazzite's nvidia and nvidia-open images do); a GA102/GA103/GA104/GA106 GeForce card from the upstream list; sudo; about 340 MB of downloads; a keyboard on the box to reach the hidden boot menu.
Time
30 min (plus one full power off).
Risk
if the unlock fails partway, the card can stay unusable until a full power off; on a single-GPU box that is a black screen, recovered from the boot menu (Recovery below). Reversible: the option lives in one deployment, the speed selectors do not survive a power off. Step 11 (optional) removes the rollback entry for good.

The steps replace upstream's make install (DKMS plus packages), which cannot work on rpm-ostree: /usr is read-only, and the image's prebuilt initramfs loads nvidia before anything on the root file system runs. Here the module, its firmware and a loader script live in /etc, rpm-ostree initramfs-etc adds them to the initramfs as an overlay, and a modprobe.d install rule runs the loader before every load of nvidia.

Before you start#

  • Secure Boot must be off: this path does not sign the module.

    $ mokutil --sb-state
    SecureBoot disabled
    

    Expected: SecureBoot disabled.

  • The headers for the booted kernel must be in the image (Bazzite ships them as kernel-devel-matched):

    $ uname -r; ls -d /usr/src/kernels/$(uname -r)
    7.2.9-ogc3.1.fc44.x86_64
    /usr/src/kernels/7.2.9-ogc3.1.fc44.x86_64
    

    Expected: the directory exists and its name equals uname -r.

  • Firmware generation needs Python 3.14 or newer:

    $ python3 --version
    Python 3.14.8
    
  • Check that the image forces nvidia into the initramfs. This is why the module has to be in the initramfs too:

    $ cat /usr/lib/dracut/dracut.conf.d/99-nvidia.conf
    # Omit the i915 amdgpu nvidia driver from the ramdisk, to avoid needing to regenerate
    # the ramdisk on updates.
    force_drivers+=" i915 amdgpu nvidia nvidia_drm nvidia_modeset nvidia_uvm "
    
  • Note the deployments. rpm-ostree initramfs-etc and rpm-ostree kargs below each build a new deployment on top of the newest one, including an update that is already staged:

    $ rpm-ostree status | grep -E 'ostree-image|Version:'
    ● ostree-image-signed:docker://ghcr.io/ublue-os/bazzite-nvidia-open:stable
                      Version: 44.20261023 (2026-10-23T16:36:04Z)
    

    Expected: one line with ● (the booted deployment). A deployment listed above it is staged and is what you will boot next.

    GotchaA staged update with another kernel

    The module is built for the kernel you are running. If a staged deployment brings a different kernel, the unlock silently does nothing after the power off. Boot the staged deployment first, then start at step 1.

  • Backup: none to take by hand. The deployment you are booted into stays as the rollback entry until you run step 11. Record the current options, to compare by eye after an Undo (the ostree= entry names the deployment and will differ):

    rpm-ostree kargs > ~/kargs-before.txt
    

Steps#

  1. Clone upstream and check out the pinned commit.

    $ cd ~ && git clone -q https://github.com/Zagitta/gpu-unlock.git && cd gpu-unlock && git checkout -q af28cbb && git log -1 --format='%h %ad' --date=short
    af28cbb 2026-10-24
    

    Expected: af28cbb 2026-10-24. The remaining commands run in ~/gpu-unlock.

    GotchaDo not build a 3090 unlock from a newer upstream without checking

    Upstream commit fc83321 broke the unlock on several RTX 3090s (issue #20, poll timeout then -110; issue #23). 4189b17 reverted it together with 291f7fa, back to the tree of c274e74, and af28cbb has that same kernel/ code. On 2026-10-26 #20 was still open and main was nine commits past af28cbb, including a kernel refactor ("Refactor driver to be recipe driven"). Before you build from a later commit, read #20 and the newer 3090 reports.

  2. Build the module against the booted kernel.

    $ make -C kernel/gpu_unlock KCFLAGS=-Werror
    make: Entering directory '/var/home/user/gpu-unlock/kernel/gpu_unlock'
    make -C "/lib/modules/7.2.9-ogc3.1.fc44.x86_64/build" M="/var/home/user/gpu-unlock/kernel/gpu_unlock" modules
    make[1]: Entering directory '/usr/src/kernels/7.2.9-ogc3.1.fc44.x86_64'
    make[2]: Entering directory '/var/home/user/gpu-unlock/kernel/gpu_unlock'
    warning: pahole version differs from the one used to build the kernel
      The kernel was built with: 131
      You are using:             0
      CC [M]  main.o
      CC [M]  gfw_eval.o
      CC [M]  prep_610.o
      CC [M]  layout_610.o
      CC [M]  vbios_610.o
      CC [M]  unlock_610.o
      LD [M]  gpu_unlock.o
      MODPOST Module.symvers
      CC [M]  gpu_unlock.mod.o
      CC [M]  .module-common.o
      LD [M]  gpu_unlock.ko
      BTF [M] gpu_unlock.ko
    Skipping BTF generation for gpu_unlock.ko due to unavailability of vmlinux
    make[2]: Leaving directory '/var/home/user/gpu-unlock/kernel/gpu_unlock'
    make[1]: Leaving directory '/usr/src/kernels/7.2.9-ogc3.1.fc44.x86_64'
    make: Leaving directory '/var/home/user/gpu-unlock/kernel/gpu_unlock'
    

    Expected: exit code 0 and LD [M] gpu_unlock.ko. The pahole warning and Skipping BTF generation are not errors. Check the build kernel:

    $ modinfo -F vermagic kernel/gpu_unlock/gpu_unlock.ko
    7.2.9-ogc3.1.fc44.x86_64 SMP preempt mod_unload
    

    Expected: the first word equals uname -r from Before you start.

  3. Download the pinned NVIDIA inputs and generate the firmware.

    $ make assets
    python3 firmware/fetch_artifacts.py /home/user/.cache/gpu-unlock/inputs
    /home/user/.cache/gpu-unlock/inputs/NVIDIA-kernel-module-source-610.43.03.tar.xz
    /home/user/.cache/gpu-unlock/inputs/nvidia-utils-610.43.03-1-x86_64_v3.pkg.tar.zst
    make firmware
    make[1]: Entering directory '/var/home/user/gpu-unlock'
    rm -rf /home/user/.cache/gpu-unlock/assets.src
    mkdir -p /home/user/.cache/gpu-unlock/assets.src
    tar -xf /home/user/.cache/gpu-unlock/inputs/NVIDIA-kernel-module-source-610.43.03.tar.xz -C /home/user/.cache/gpu-unlock/assets.src
    python3 firmware/generate_v67.py "$(realpath /home/user/.cache/gpu-unlock/inputs/nvidia-utils-610.43.03-1-x86_64_v3.pkg.tar.zst)" /home/user/.cache/gpu-unlock/assets
    python3 firmware/extract_boot_assets.py /home/user/.cache/gpu-unlock/assets.src/NVIDIA-kernel-module-source-610.43.03 /home/user/.cache/gpu-unlock/assets
    make[1]: Leaving directory '/var/home/user/gpu-unlock'
    
    $ ls -l ~/.cache/gpu-unlock/assets
    total 82480
    -rw-r--r--. 1 user user    61256 Oct 26 21:41 booter_load_ga102.bin
    -rw-r--r--. 1 user user       84 Oct 26 21:41 gsp_boot_desc_ga102.bin
    -rw-r--r--. 1 user user    24576 Oct 26 21:41 gsp_boot_ga102.bin
    -rw-r--r--. 1 user user 84277400 Oct 26 21:41 gsp_ga10x.bin
    -rw-r--r--. 1 user user    22653 Oct 26 21:41 NVIDIA-LICENSE
    -rw-r--r--. 1 user user    64000 Oct 26 21:41 v67-ga102-610.43.03.bin
    

    Expected: the five .bin files and NVIDIA-LICENSE. The generators check every input and output against a pinned SHA-256 and stop on a mismatch.

  4. Save the loader script and the modprobe.d rule. Both are GPL-2.0-only, written for this setup; they are not in upstream.

    load.sh runs before every nvidia load, in the initramfs and later. It reads the gpu_unlock.* options from the kernel command line (insmod does not, unlike modprobe), points the firmware loader at /etc/gpu-unlock/firmware, loads the module, and always exits 0 so that nvidia loads whatever happened. It uses only shell builtins and absolute paths: the initramfs need not have cat, and PATH may not be set there.

    mkdir -p bazzite
    cat > bazzite/load.sh <<'EOF'
    #!/bin/sh
    # SPDX-License-Identifier: GPL-2.0-only
    # Run by the modprobe.d install rule for nvidia, in the initramfs and on the
    # root filesystem: load gpu_unlock from /etc just before nvidia. Bazzite's
    # /usr is read-only, so the module and its firmware live in /etc/gpu-unlock
    # and reach the initramfs through rpm-ostree initramfs-etc. Never fails, so
    # nvidia always loads.
    dir=/etc/gpu-unlock
    [ -d /sys/module/gpu_unlock ] && exit 0
    # insmod does not read gpu_unlock.* from the command line as modprobe would.
    # Builtins only: the initramfs need not have cat or PATH set.
    read -r cmdline < /proc/cmdline
    params=
    for word in $cmdline; do
    	case "$word" in
    	gpu_unlock.*=*) params="$params ${word#gpu_unlock.}" ;;
    	esac
    done
    [ -n "$params" ] || exit 0
    # The firmware is under /etc too; resume loads it again, so the path stays set.
    fw=/sys/module/firmware_class/parameters/path
    current=
    [ -w "$fw" ] && read -r current < "$fw"
    if [ -w "$fw" ] && [ -z "$current" ]; then
    	printf %s "$dir/firmware" > "$fw"
    fi
    /usr/bin/insmod "$dir/gpu_unlock.ko" $params
    exit 0
    EOF
    

    The rule runs load.sh through /bin/sh -c, then loads nvidia with its usual options. A path in it that does not exist in the initramfs stops nvidia from loading at all.

    cat > bazzite/modprobe.conf <<'EOF'
    # SPDX-License-Identifier: GPL-2.0-only
    # Bazzite: gpu_unlock is not under /usr/lib/modules, so no softdep can name it.
    install nvidia /bin/sh /etc/gpu-unlock/load.sh; /sbin/modprobe --ignore-install nvidia $CMDLINE_OPTS
    EOF
    

    Check what you saved:

    $ sha256sum bazzite/load.sh bazzite/modprobe.conf
    453dfef73e601ecbea06f43eff208fc07d633452fe8d5fdb03b81789c12801ba  bazzite/load.sh
    fee0e6f424735d531eeb8a1c05cea70c6329f874e2159f9d1796913b20ab44b9  bazzite/modprobe.conf
    

    Expected: these two hashes. A different hash means the paste changed the file.

  5. Install the module, firmware, loader and rule into /etc.

    sudo install -Dm644 kernel/gpu_unlock/gpu_unlock.ko /etc/gpu-unlock/gpu_unlock.ko
    sudo install -Dm755 bazzite/load.sh /etc/gpu-unlock/load.sh
    sudo install -Dm644 -t /etc/gpu-unlock/firmware/gpu-unlock/610.43.03 \
      ~/.cache/gpu-unlock/assets/booter_load_ga102.bin \
      ~/.cache/gpu-unlock/assets/gsp_ga10x.bin \
      ~/.cache/gpu-unlock/assets/gsp_boot_ga102.bin \
      ~/.cache/gpu-unlock/assets/gsp_boot_desc_ga102.bin \
      ~/.cache/gpu-unlock/assets/v67-ga102-610.43.03.bin
    sudo install -Dm644 bazzite/modprobe.conf /etc/modprobe.d/gpu-unlock.conf
    

    Expected: no output. cmp kernel/gpu_unlock/gpu_unlock.ko /etc/gpu-unlock/gpu_unlock.ko prints nothing.

  6. Add the files to the initramfs as an overlay.

    F=/etc/gpu-unlock/firmware/gpu-unlock/610.43.03
    sudo rpm-ostree initramfs-etc --force-sync \
      --track=/etc/gpu-unlock/gpu_unlock.ko --track=/etc/gpu-unlock/load.sh \
      --track=/etc/modprobe.d/gpu-unlock.conf \
      --track=$F/booter_load_ga102.bin --track=$F/gsp_ga10x.bin --track=$F/gsp_boot_ga102.bin \
      --track=$F/gsp_boot_desc_ga102.bin --track=$F/v67-ga102-610.43.03.bin
    

    Output on the tested box (same command, run as root; repository lines cut):

    Checking out tree 278edb6...done
    …
    Resolving dependencies...done
    Checking out packages...done
    Running systemd-sysusers...done
    Running pre scripts...done
    Running post scripts...done
    Running posttrans scripts...done
    Writing rpmdb...done
    Writing OSTree commit...done
    Staging deployment...done
    Freed: 138.7 MB (pkgcache branches: 0)
    Run "systemctl reboot" to start a reboot
    

    Expected: Staging deployment...done. No dracut run: the image's initramfs stays as it is and the overlay is a second initrd in the new boot entry.

  7. Opt in on the kernel command line.

    sudo rpm-ostree kargs --append-if-missing=gpu_unlock.targets=all
    

    Output on the tested box, cut (an image update was staged, so it listed that update's package diff):

    Checking out tree 278edb6...done
    Resolving dependencies...done
    Checking out packages...done
    Running systemd-sysusers...done
    Running pre scripts...done
    Running post scripts...done
    Running posttrans scripts...done
    Writing rpmdb...done
    Writing OSTree commit...done
    Staging deployment...done
    Freed: 138.7 MB (pkgcache branches: 0)
    …
    Changes queued for next boot. Run "systemctl reboot" to start a reboot
    

    Expected: Changes queued for next boot.

    NoteOptions are per deployment

    rpm-ostree kargs writes the option only into the new deployment. The entry you booted from keeps its old options and is now the rollback: booting it gives a locked card. Image updates copy the option and the overlay from the deployment they build on.

  8. Power off fully, then power on.

    systemctl poweroff
    

    Do not use reboot for the first unlock. The module refuses a card that a driver has already used since power on (card already initialised since power on (WPR2 enabled); power off first). A full power off clears that state. I saw a warm reboot clear it once (Field notes); that is not a guarantee.

    WarningSingle-GPU box: a failed unlock is a black screen

    The screen on the card goes blank from the card's reset until nvidia-drm takes over. If the card's firmware does not stop during the unlock, the module keeps the DMA buffers that firmware may still use until the next power off, on purpose. Separately, with the broken fc83321, issue #23 reports that nvidia could not use either card after a failed unlock until the machine was fully restarted. On a single-GPU box that means no picture: follow Recovery below.

  9. Check that this boot unlocked the card.

    $ journalctl -k -b -o cat | grep 'gpu_unlock: unlocked'
    pci 0000:10:00.0: gpu_unlock: unlocked
    

    Expected: one line ending in gpu_unlock: unlocked. Nothing printed: see Failure modes before going on. The full checks are under Verify.

  10. Optional: install a login warning for boots that did not unlock. An image update that brings a new kernel leaves the old module in /etc. The kernel's version check is expected to make insmod refuse it (not yet seen on this box); load.sh ignores the failure, nvidia loads as usual and the card stays locked with no visible sign. This script, started at login, sends a desktop notification when the option is set but this boot did not unlock: module missing, module built for another kernel, or no gpu_unlock: unlocked line. On a headless box, check the journal after each update instead.

    mkdir -p bazzite
    cat > bazzite/check.sh <<'EOF'
    #!/bin/sh
    # SPDX-License-Identifier: GPL-2.0-only
    # Run at login (XDG autostart): warn on the desktop when the unlock is opted in but did not happen,
    # most often because an image update brought a kernel the module was not built for.
    # GU_CMDLINE, GU_KVER and GU_NOTIFY override the inputs for testing.
    cmdline=$(cat "${GU_CMDLINE:-/proc/cmdline}")
    case " $cmdline " in
    *" gpu_unlock.targets="*) ;;
    *) exit 0 ;;
    esac
    ko=/etc/gpu-unlock/gpu_unlock.ko
    kver=${GU_KVER:-$(uname -r)}
    notify=${GU_NOTIFY:-notify-send}
    built=$(modinfo -F vermagic "$ko" 2>/dev/null | cut -d' ' -f1)
    if [ -z "$built" ]; then
    	msg="$ko is missing: the unlock is not installed."
    elif [ "$built" != "$kver" ]; then
    	msg="Module built for $built, running $kver: the card is locked. Rebuild the module for this kernel, reinstall it, then power off fully."
    elif ! journalctl -k -b 2>/dev/null | grep -q 'gpu_unlock: unlocked'; then
    	msg="No 'gpu_unlock: unlocked' this boot. Check: journalctl -k -b | grep gpu_unlock"
    else
    	exit 0
    fi
    "$notify" -a gpu-unlock -u critical "gpu-unlock: not unlocked" "$msg"
    EOF
    
    cat > bazzite/gpu-unlock-check.desktop <<'EOF'
    [Desktop Entry]
    Type=Application
    Name=gpu-unlock check
    Comment=Warn when the GPU unlock did not happen this boot
    Exec=/bin/sh /etc/gpu-unlock/check.sh
    NoDisplay=true
    X-KDE-autostart-phase=2
    EOF
    
    $ sha256sum bazzite/check.sh bazzite/gpu-unlock-check.desktop
    98e136a627ad306b10ea98359351043d37dce0e7547547d3d3d8ade4429b8ef5  bazzite/check.sh
    aac408322d320c6679521e3d15490c903e1ad4ff1c78e73c49c29d342c2bf580  bazzite/gpu-unlock-check.desktop
    

    Test it before installing. On an unlocked boot it is silent; with GU_KVER set to another kernel version and GU_NOTIFY=echo, it prints the notification it would send:

    $ sh bazzite/check.sh; echo "rc=$?"
    rc=0
    
    $ GU_KVER=0.0.0-test GU_NOTIFY=echo sh bazzite/check.sh
    -a gpu-unlock -u critical gpu-unlock: not unlocked Module built for 7.2.9-ogc3.1.fc44.x86_64, running 0.0.0-test: the card is locked. Rebuild the module for this kernel, reinstall it, then power off fully.
    

    Install it:

    sudo install -Dm755 bazzite/check.sh /etc/gpu-unlock/check.sh
    install -Dm644 bazzite/gpu-unlock-check.desktop ~/.config/autostart/gpu-unlock-check.desktop
    

    check.sh is not needed in the initramfs, so it is not tracked in step 6.

  11. Optional: make every boot entry unlocked. Only after Verify passed:

    sudo rpm-ostree cleanup -r
    

    This deletes the rollback deployment, so the boot menu no longer offers a locked entry or the state before step 6. Recovery then goes through the boot menu's edit screen.

Verify#

  • The kernel log of this boot has the unlock line (cut: the first of two command-line lines, and the ROM and FWSEC parse lines):

    $ journalctl -k -b -o short-monotonic | grep -E 'gpu_unlock|NVRM: loading'
    …
    [    2.654241] host kernel: Kernel command line: BOOT_IMAGE=(hd0,gpt2)/ostree/default-1b68a7f6d69d47119f25bf65e33a39241f28a815750d5d3417b702029a82d98d/vmlinuz-7.2.9-ogc3.1.fc44.x86_64 rhgb quiet root=UUID=<ROOT_UUID> rootflags=subvol=root rw ostree=/ostree/boot.0/default/1b68a7f6d69d47119f25bf65e33a39241f28a815750d5d3417b702029a82d98d/0 bluetooth.disable_ertm=1 gpu_unlock.targets=all
    …
    [    7.012680] host kernel: pci 0000:10:00.0: gpu_unlock: unlocked
    [    8.947048] host kernel: NVRM: loading NVIDIA UNIX Open Kernel Module for x86_64  615.78.08  Release Build  (akmods@)  Thu Oct 15 08:44:23 UTC 2026
    

    Expected: gpu_unlock: unlocked on your card's PCI address, about two seconds before NVRM: loading. No gpu_unlock lines at all means the overlay or the rule did not reach the initramfs; see Failure modes.

  • The overlay is tracked and the boot entry carries it and the option:

    $ rpm-ostree initramfs-etc
    Tracked files:
      /etc/gpu-unlock/firmware/gpu-unlock/610.43.03/booter_load_ga102.bin
      /etc/gpu-unlock/firmware/gpu-unlock/610.43.03/gsp_boot_desc_ga102.bin
      /etc/gpu-unlock/firmware/gpu-unlock/610.43.03/gsp_boot_ga102.bin
      /etc/gpu-unlock/firmware/gpu-unlock/610.43.03/gsp_ga10x.bin
      /etc/gpu-unlock/firmware/gpu-unlock/610.43.03/v67-ga102-610.43.03.bin
      /etc/gpu-unlock/gpu_unlock.ko
      /etc/gpu-unlock/load.sh
      /etc/modprobe.d/gpu-unlock.conf
    
    $ grep -E '^(options|initrd)' /boot/loader/entries/*.conf
    options rhgb quiet root=UUID=<ROOT_UUID> rootflags=subvol=root rw ostree=/ostree/boot.0/default/1b68a7f6d69d47119f25bf65e33a39241f28a815750d5d3417b702029a82d98d/0 bluetooth.disable_ertm=1 gpu_unlock.targets=all
    initrd /ostree/default-1b68a7f6d69d47119f25bf65e33a39241f28a815750d5d3417b702029a82d98d/initramfs-7.2.9-ogc3.1.fc44.x86_64.img
    initrd /ostree/initramfs-overlays/478bc1e8a230630f6402e2852948414ca0f13a0851599482029df49fb4a1c741.img
    

    Expected: the eight tracked paths; a second initrd line from /ostree/initramfs-overlays/; gpu_unlock.targets=all in options.

  • nvidia drives the card as usual:

    $ nvidia-smi --query-gpu=name,pci.bus_id,driver_version,vbios_version --format=csv
    name, pci.bus_id, driver_version, vbios_version
    NVIDIA GeForce RTX 3090, 00000000:10:00.0, 615.78.08, 94.02.26.80.8C
    

The speed-up itself shows only in FP16/BF16 math with FP32 accumulation and in TF32. On this card a cuBLAS 8192³ BF16 GEMM at a 280 W power limit went from 74.9 to 128 TFLOPS; the measurements are in the RTX 3090 unlock post.

Redo after a kernel update#

[UNVERIFIED: not exercised; no image update with a new kernel has arrived on the tested box since the install]

After an image update, compare the module's kernel with the running one:

$ modinfo -F vermagic /etc/gpu-unlock/gpu_unlock.ko; uname -r
7.2.9-ogc3.1.fc44.x86_64 SMP preempt mod_unload 
7.2.9-ogc3.1.fc44.x86_64

If the first word differs from the second line, you are booted into the new kernel: repeat step 2 in ~/gpu-unlock (git -C ~/gpu-unlock status must show no changes). The firmware is pinned and already in /etc. Then:

sudo install -Dm644 kernel/gpu_unlock/gpu_unlock.ko /etc/gpu-unlock/gpu_unlock.ko

then step 6 (--force-sync builds a new deployment with the current tracked files) and step 8. The option is already set; step 7 is not needed.

Recovery: black screen after an unlock attempt#

  1. Hold the power button until the machine is off, or pull the plug. Upstream says to power off before trying again; do not count on a reboot.
  2. Power on and open the GRUB menu. Bazzite's docs: if the menu does not show, press Esc once and tap Down; if the menu timeout is set to "Skip Menu", hold Shift or F8. Select the entry, press e, delete gpu_unlock.targets=all from the line that holds the kernel options, and boot with Ctrl+X. If step 11 was not run, the ostree:1 entry is the deployment from before step 6 and boots without the option.
  3. Once logged in, remove the option for good: sudo rpm-ostree kargs --delete-if-present=gpu_unlock.targets=all, then power off fully.

Undo / rollback#

Each line stands alone; stop when the box is where you want it.

WhatHow
Skip the unlock for one bootboot menu → e → delete gpu_unlock.targets=all → Ctrl+X
Go back to the deployment before step 6boot menu → the previous entry, or sudo rpm-ostree rollback (only before step 11; also drops anything else that came with the newer deployment, an image update included)
Stop unlocking, keep the filessudo rpm-ostree kargs --delete-if-present=gpu_unlock.targets=all, then power off fully
Remove the files and the overlaythe block below [UNVERIFIED: rollback not exercised]
F=/etc/gpu-unlock/firmware/gpu-unlock/610.43.03
sudo rpm-ostree initramfs-etc \
  --untrack=/etc/gpu-unlock/gpu_unlock.ko --untrack=/etc/gpu-unlock/load.sh \
  --untrack=/etc/modprobe.d/gpu-unlock.conf \
  --untrack=$F/booter_load_ga102.bin --untrack=$F/gsp_ga10x.bin --untrack=$F/gsp_boot_ga102.bin \
  --untrack=$F/gsp_boot_desc_ga102.bin --untrack=$F/v67-ga102-610.43.03.bin
sudo rm -f /etc/modprobe.d/gpu-unlock.conf
sudo rm -rf /etc/gpu-unlock
rm -f ~/.config/autostart/gpu-unlock-check.desktop
sudo rpm-ostree kargs --delete-if-present=gpu_unlock.targets=all
systemctl poweroff

Expected after the power off: rpm-ostree initramfs-etc lists no gpu-unlock path, ls /etc/gpu-unlock /etc/modprobe.d/gpu-unlock.conf fails, cat /proc/cmdline has no gpu_unlock. Once the option is gone the module does nothing, even if it still loads; after a full power off the card runs at stock rate again. The clone ~/gpu-unlock and the download cache ~/.cache/gpu-unlock (about 340 MB of inputs plus the generated files) are left in place; delete them by hand if you want the space back.

Failure modes#

Symptom (exact message)CauseFix
poll timeout: reg=0x110100 last=0x10 mask=0x10 expected=0x0 budget_us=1000, then failure unlock_610.c:735 unlock_transaction_fwsec error=-110 (issue #20; the first line also in #80)Module built from fc83321 on an RTX 3090Power off fully, rebuild from af28cbb (steps 1–2), reinstall the module (step 5), step 6, power off.
gpu_unlock: unlock failed (-110) on each card, then nvidia cannot use the cards (issue #23)Same fc83321 regressionAs above; recover the screen first (Recovery).
gpu_unlock: unlock failed (-16) (PR #79, layered-RPM path)nvidia bound the card before gpu_unlock ran: the dracut drop-in gpu-unlock.conf sorts after Bazzite's 99-nvidia.conf. -16 is -EBUSY, which the module also returns for other reasons, so read the lines before it.Not hit with these steps: the install nvidia rule runs the loader before any nvidia load. On the RPM path, name the drop-in 00-gpu-unlock.conf.
initramfs regeneration and /etc overlay not compatible; use dracut arg -I instead (PR #79; rpm-ostree source)rpm-ostree initramfs --enable while files are tracked with initramfs-etc, i.e. mixing this manual with the RPM pathRun the file-removal block in Undo (it untracks the files) before switching paths.
sudo lsinitrd <image> | grep -E '…insmod$' prints nothing although insmod is in the image (my run)lsinitrd lists symlinks as usr/bin/insmod -> kmod; a pattern anchored at $ misses themDrop the $ anchor.

Field notes#

  • 2026-10-26 (evening), same box: Bazzite 44.20261023 bazzite-nvidia-open, kernel 7.2.9-ogc3.1.fc44, NVIDIA 615.78.08, RTX 3090 VBIOS 94.02.26.80.8C, gpu-unlock af28cbb, firmware pin 610.43.03: I re-ran the unprivileged half of this manual from a fresh clone in a scratch home directory, in the order above: Before you start, steps 1–4, step 9, the tests in step 10, Verify and the vermagic check. Clone, module build and make assets took under a minute together. The clone block above comes from a second clone a few minutes later, with a shorter git log format. The five generated firmware files were byte-identical to the ones installed that morning; the freshly built gpu_unlock.ko was not (cmp differs from byte 41), which is why step 5 compares your own build with your own copy. The pasted /etc, boot entry and journal output is from the boot running at the time, which had unlocked. On this box the RTC keeps local time, so early kernel lines in plain journalctl -k carry timestamps two hours ahead; the blocks above use -o cat and -o short-monotonic for that reason.
  • 2026-10-26, Bazzite 44.20261023 bazzite-nvidia-open, kernel 7.2.9-ogc3.1.fc44, NVIDIA 615.78.08, RTX 3090 VBIOS 94.02.26.80.8C, gpu-unlock af28cbb, firmware pin 610.43.03, Secure Boot off:
    • I built from af28cbb on purpose after reading the 3090 regression reports (#20, #23).
    • I installed around 13:00 while still booted into the previous deployment, with the 44.20261023 update already staged. Both rpm-ostree initramfs-etc and rpm-ostree kargs built on top of the staged tree; the kargs output listed the whole update's package diff. Both deployments carried the same kernel build, so the module matched. With a different kernel in the staged update it would be expected not to load.
    • First boot of the new deployment: gpu_unlock: unlocked at 6.9 s on the journal's monotonic clock (the kernel's first line is at 2.6 s), NVRM: loading at 8.8 s.
    • While checking the initramfs I grepped lsinitrd output with a pattern anchored at $ for insmod and got nothing, although insmod is there: lsinitrd prints symlinks as usr/bin/insmod -> kmod. Without the anchor it shows up.
    • Seen once: the first systemctl poweroff from the unlocked boot hung at the very end (power LED blinking) and needed the power button. The one later power-off, from a locked boot, and the later reboots, one of them from an unlocked boot, finished normally. I have not powered off from an unlocked boot since and did not investigate.
    • For a locked comparison I removed the option with rpm-ostree kargs --delete=gpu_unlock.targets=all (new deployment, overlay kept), powered off (the hang above) and booted: no gpu_unlock lines, and a BF16 GEMM ran at 74.9 TFLOPS against 128 unlocked (280 W). I put the option back with --append-if-missing, got gpu_unlock: unlocked again, then ran rpm-ostree cleanup -r; one deployment is left and its entry has the option.
    • Later I skipped the unlock for one boot from the boot menu (e, deleted the option). The plain reboot after that locked boot, in which nvidia had used the card, unlocked again: on this board a warm reboot cleared the state the module checks for, that one time. I still power off before a first unlock.
    • The login check (my installed copy differs from step 10 only in two message texts) stayed silent at login on the unlocked boot.

Notes#

  • Upstream make install (DKMS and distro packages) does not work on rpm-ostree images; these steps replace it. The upstream README covers the module's behaviour: the targets syntax (all, or PCI addresses as lspci -D prints them), suspend and resume, and the display card.
  • gpu_unlock.targets=<PCI address> instead of all limits the unlock to one card; not tested here (one card).
  • After suspend to RAM the module unlocks the card again before nvidia resumes it (README). load.sh leaves firmware_class.path pointing at /etc/gpu-unlock/firmware for that reason. Not tested on this box.
  • The other Bazzite route, upstream PR #131 (packaging/atomic/install.sh, open and unmerged on 2026-10-26) and its manual twin PR #79: a layered local RPM, rpm-ostree initramfs --enable (the initramfs is then rebuilt locally on every update), depmod in %post (rpm-ostree runs depmod only when the kernel changes, so dracut skipped the module without it), and a drop-in named 00-gpu-unlock.conf. It can sign the module for Secure Boot; #131 itself was tested with Secure Boot off, the signed path on hardware in #79 and in issue #80 (a ZOTAC 3090 on Bazzite 44.20261023, unlocked from af28cbb). The two routes do not mix (Failure modes, row 4).
  • Why the measured gain is uneven across local LLM engines: the RTX 3090 unlock post.

Filed under: nvidia, gpu, kernel, bazzite, rpm-ostree, local-llm

Sources#