Solving the fan-curve problem

• Created by: Domonkos Poór

Hero image for Solving the fan-curve problem

The age-old question: How on earth do you control an NVIDIA GPU fan on Linux under Wayland? The usual answer is a bad one: nvidia-settings needs an X server, and Wayland does not have one. The default driver curve is not adjustable. For a long time the practical options were to accept the noise or to keep an X session around for a settings tool.

Why the existing tools did not fit

Several tools solve this problem, and most of them work. The problem is the curve shape. Almost every NVIDIA tool interpolates linearly between temperature and speed points. The fan speed tracks the temperature continuously, so the pitch drifts up and down as the card warms and cools.

The ear is good at detecting a changing pitch, and bad at filtering it. A fan that holds one speed is easier to ignore than a fan that slides between speeds, even when the average noise is the same.

I wanted a different curve shape.

The idea: stair-step with hysteresis

The daemon holds a constant fan speed until the temperature crosses the next threshold by a configurable margin. That margin is the hysteresis. It stops the fan from oscillating when the temperature sits on a boundary.

Four points define the curve:

Point Meaning
temp_min Below this, use speed_min
temp_low The next step up
temp_high The step above that
temp_max Above this, use speed_max

This way the fan will stay at a constant speed for the given temperature range, eliminating the constant WOOSHH-fsssh-WOOSHH-fsssh that all of us are all too familiar with.

The implementation

The daemon is written in Go. It uses the NVIDIA Management Library directly through the official Go bindings, so it does not need nvidia-settings, an X server, or a pipe from nvidia-smi. It works on X11, on Wayland, and on a headless machine.

The main properties:

  • Multi-GPU by default. It enumerates every device at startup and keeps independent state per GPU.
  • TOML configuration. The default path is /etc/coolyourcard.conf. A --config flag overrides it.
  • Two curve functions. step for the stair-step curve, linear for the classic interpolation. The function is a config value.
  • Silent mode. Below temp_min, the daemon returns the fans to driver control instead of holding a minimum speed.
  • Graceful shutdown. On SIGTERM, SIGINT, SIGQUIT, SIGTSTP, or SIGPWR, the daemon restores driver fan control before it exits. A deferred call does this, so it runs on any exit path, including a panic.
  • Live reload. SIGHUP re-reads the configuration without a restart, and keeps the current fan state, so the transition is smooth.
  • Fault tolerance. If one GPU fails a temperature query or a fan call, the daemon logs a warning and continues with the others.

A systemd unit runs it as a service. Restart=on-failure covers the signals that cannot be caught, such as SIGKILL, where the fans stay at their last speed until the service restarts.

temp_min = 30
temp_low = 45
temp_high = 60
temp_max = 75
speed_min = 25
speed_low = 40
speed_high = 60
speed_max = 85
interval = 2
threshold = 4
silent = true
function = "step"

Where it sits in the landscape

Tool Curve Hysteresis Vendor Language
CoolYourCard Stair-step Yes NVIDIA Go
nvfd Linear No NVIDIA C
nvfancontrol Linear No NVIDIA Rust
GreenWithEnvy Linear No NVIDIA Python
NVML-GPU-Control Stair-step No NVIDIA Python
amdgpu-fancontrol Stair-step Yes AMD Bash

No NVIDIA tool implements stair-step with hysteresis. The closest matches are NVML-GPU-Control for Nvidia, without hysteresis, and amdgpu-fancontrol, which is AMD only. That is the whole reason this project exists.

I considered adding the feature to nvfd instead. It is mature and well-engineered. I built CoolYourCard separately for four reasons.

  • nvfd is about 3,000 lines of C across 15 or more files, and Go removes a class of memory bugs.
  • The stair-step logic is stateful, because it remembers the current speed level per GPU, and nvfd is built around a stateless linear model, so the change is more invasive than it looks.
  • I wanted control of the release cycle and the config format.
  • And a working reference makes a future contribution to nvfd less risky.