Automationintermediate

Fix NVIDIA Cursor and Video Stutter on Linux: GPU Clock Thrash

Months of micro-stutter on my GTX 1660 Ubuntu box was not the compositor. It was the GPU dropping its clocks.

By··7 min read
nvidia-smi dmon output showing the graphics clock thrashing between 810 and 1530 MHz

For months my mouse cursor stuttered. Not a freeze, not lag, just a tiny hitch every four or five seconds, like the desktop skipped a heartbeat. Video did the same: a clean 1080p clip in Chrome would micro-stall, drop a couple of frames, then carry on. Worse whenever I had Citrix open. I did what everyone does and blamed the compositor.

I tried the usual suspects. Switched Mutter settings. Suspected Xwayland. Raised my mouse polling rate. Chased Citrix's H.264 codec and its input-device grabbing. Each one felt plausible and each one was wrong.

The actual cause had nothing to do with the desktop drawing. It was the GPU itself, and once I saw it on nvidia-smi I could not unsee it.

The real cause: GPU clock thrash

The NVIDIA proprietary driver runs aggressive dynamic power management. At idle it drops the GPU clocks to save a few watts. The moment something needs the GPU, it ramps back up. On a desktop under real load that ramp happens constantly, and every transition costs a millisecond or two. At a 60Hz refresh rate that lands as visible jitter.

You can watch it happen. Run this and move your mouse around for a few seconds:

nvidia-smi dmon -s pucvmet -c 15

On my box the pclk column (the graphics clock) was bouncing between about 810 MHz and 1530 MHz, over and over. There is your tell. Every cursor render, every small repaint, dragged the clock up and let it fall back. Anything that touches the GPU more often makes it worse, which is exactly why Citrix's wfica, Chrome, and any Xwayland app turned a faint hitch into an obvious one. More work means more transitions means more stutter.

There are two separate clocks doing this, and the distinction matters later. The graphics clock thrashes and you feel it as cursor jitter. The memory clock thrashes and you feel it as video stutter, because video decode is memory-bandwidth-bound and every VRAM transition micro-stalls a frame. I had both. Most people have at least one.

Before you touch anything, confirm it is the clock. If pclk and mclk sit steady in that dmon output while you move the mouse, your stutter is something else and this fix will not help. Mine varied hard, so I had my answer.

My stack, for reference, since this behaviour is driver-specific:

Component Version
GPU NVIDIA GTX 1660
OS Ubuntu (kernel 6.17)
Driver NVIDIA 580.159.03
Display Wayland, Mutter 46

The fix that held

The cheap test first, because it works instantly with no reboot and you can revert it by rebooting if you hate it.

Turn on persistence mode, then lock the graphics clock to its top value:

sudo nvidia-smi -pm 1
sudo nvidia-smi -lgc 1545,1545

-pm 1 keeps the driver state resident. -lgc 1545,1545 locks the graphics clock to a fixed floor and ceiling, both set to the card's max, so it stops dropping. The instant I ran this the cursor stutter was gone. Not reduced. Gone. After months of poking at the compositor it was almost insulting.

But that only fixed the cursor. My video was still stuttering, and here is the part that cost me extra time. -lgc locks the graphics clock only. At low GPU utilisation the driver will still drop the memory clock down to 810 MHz, which is what kills video. So I reached for the obvious next command:

sudo nvidia-smi -lmc 5001,5001

It printed "All done" and did absolutely nothing. This is the GTX 1660 gotcha you need to know: -lmc and -ac are fused off on consumer Turing cards. The command reports success, the memory clock ignores it, and the applications-clock readout shows N/A. There is no point retrying it with different numbers. On this class of card it simply does not work.

The lever that does work is PowerMizer, set through nvidia-settings:

nvidia-settings -a [gpu:0]/GPUPowerMizerMode=1

Mode 1 is "Prefer Maximum Performance". The default is 2, which is Auto and exactly the thing dropping your clocks. Setting it to 1 pins the top P-state, and now the VRAM holds its full clock even at four percent utilisation. I confirmed it live: memory clock parked high, video stutter gone.

So the full fix is two commands for the graphics clock and one for memory, and they live in different places because of what they need:

Setting Command Fixes Needs
Persistence nvidia-smi -pm 1 keeps state resident root
Graphics clock lock nvidia-smi -lgc 1545,1545 cursor stutter root
Max performance P-state nvidia-settings -a [gpu:0]/GPUPowerMizerMode=1 video stutter a running X11 display

That nvidia-settings call needs $DISPLAY because GPUPowerMizerMode is an X11 attribute. That single fact decides how you make this survive a reboot, which is the next problem.

Making it survive reboot

Every one of those commands resets on reboot. You need two persistence mechanisms because one half needs root with no display and the other half needs a display with no root.

The root half goes in a systemd unit. Create /etc/systemd/system/nvidia-clock-lock.service:

[Unit]
Description=Lock NVIDIA clocks to stop cursor stutter
After=multi-user.target

[Service]
Type=oneshot
ExecStart=/usr/bin/nvidia-smi -pm 1
ExecStart=/usr/bin/nvidia-smi -lgc 1545,1545
RemainAfterExit=yes

[Install]
WantedBy=multi-user.target

Type=oneshot with two ExecStart= lines runs both commands once at boot. Enable it:

sudo systemctl enable --now nvidia-clock-lock.service

The PowerMizer half cannot go here, because at boot there is no $DISPLAY for it to attach to. It has to run after you log in. So it goes in an XDG autostart entry instead. Create ~/.config/autostart/nvidia-powermizer-max.desktop:

[Desktop Entry]
Type=Application
Name=NVIDIA Max Performance
Exec=nvidia-settings -a [gpu:0]/GPUPowerMizerMode=1
X-GNOME-Autostart-enabled=true

Now the graphics clock locks at boot via systemd, and the memory clock pins at login via autostart. Both halves survive a reboot, each through the mechanism that fits its requirements. I have run this setup daily since and the stutter has not come back.

What broke for me

This is the part I want you to read twice, because I nearly bricked my display chasing extra polish.

While tuning the box I wanted to add a few NVIDIA module options. The obvious move is to write them to /etc/modprobe.d/nvidia-graphics-drivers.conf. So a tuning script wrote three options lines into that file and rebuilt the initramfs.

Reboot. Black. nvidia-smi failed. Chrome would not render windows. dmesg told the story:

NVRM: GPU 0000:2d:00.0 is already bound to nouveau

Here is the trap. There is already a file by that exact name shipped under /usr/lib/modprobe.d/nvidia-graphics-drivers.conf, and it carries the stock blacklist nouveau lines that stop the open-source driver from grabbing the card. When you write /etc/modprobe.d/<name>.conf with the same filename, the /etc copy does not merge with the /usr/lib one. It shadows it completely. My three tuning lines silently wiped eight blacklist and alias ... off lines I never meant to touch. On reboot nouveau loaded first, claimed the GTX 1660, and the NVIDIA driver could not bind.

The fix was to restore the blacklist content, but the lesson is the rule. Never truncate a file in /etc/modprobe.d/ that shares a name with one in /usr/lib/modprobe.d/. Two safe ways to add options:

# Safe: use a different filename so nothing gets shadowed.
# modprobe reads every *.conf in alphabetical order.
echo 'options nvidia NVreg_UsePageAttributeTable=1' | sudo tee /etc/modprobe.d/zz-nvidia-tuning.conf

A zz- prefix sorts last and never collides with a stock filename, so the shipped blacklist stays intact. If you must reuse the original filename, inline the stock content first:

cat /usr/lib/modprobe.d/nvidia-graphics-drivers.conf > /tmp/new
echo 'options nvidia NVreg_UsePageAttributeTable=1' >> /tmp/new
sudo mv /tmp/new /etc/modprobe.d/nvidia-graphics-drivers.conf

The same shadowing hazard lives in /etc/sysctl.d/, /etc/udev/rules.d/, and /etc/systemd/system/*.d/. Any /etc directory that overlays a /usr/lib one will shadow per filename, not merge. And any module change that only takes effect after a reboot deserves a rule of its own: the first thing you do on next boot is check nvidia-smi works. If it does not, run lsmod | grep nouveau and dmesg | grep NVRM and you will see this exact failure immediately.

Does this hurt performance

Honestly, a little, and it does not matter on a desktop.

Locking the clocks high means the GPU stops idling down, so it draws more power and runs warmer. On my box idle power went from about 10W to 15W, and the temperature rose from roughly 54C to 58C. Five watts and five degrees. On a desktop plugged into the wall, nothing, and I will pay it every day to be rid of the stutter.

The one place to stop and think is a laptop on battery. There, pinning the GPU at max performance all the time is a real cost to runtime and heat. I would not enable this blindly on a laptop. On a desktop, apply it and move on.

When this is NOT your problem

This fix is narrow on purpose. It only helps when the GPU clocks are actually thrashing. Run the diagnostic first:

nvidia-smi dmon -s pucvmet -c 15

If pclk and mclk hold steady while you use the machine, your stutter is something else and locking the clocks will only cost you watts for nothing. Look at the compositor, your display cable or refresh rate, or thermal throttling instead.

A few things I ruled out during this debug, so you do not waste the hours I did. Mouse polling rate was not it, though raising it is a fine orthogonal tweak. Citrix grabbing input devices was a red herring. Chrome's H.264 codec settings were innocent. And separately, do not bother forcing GPU video decode in Chrome on the NVIDIA proprietary stack. I A/B tested it against a 4K60 clip and NVDEC stayed at zero percent through every flag and backend combination. Chrome will not engage hardware decode there. If you genuinely need GPU decode for heavy 4K streams, Firefox does engage NVDEC on this same driver where Chrome refuses. For everything else the clock lock is the whole fix.

If you are setting up an NVIDIA Linux box from scratch, get the driver and tooling right before you ever hit this. My Claude Code on Linux install walkthrough and the Gemma on Ollama Linux setup both run on this same GTX 1660 box, and a stable GPU clock keeps local inference and the desktop smooth at once.

Related