foucault / nvfancontrol

NVidia dynamic fan control for Linux and Windows

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

RTX Fan Control Windows

MaynardMiner opened this issue · comments

Hello,

I was trying your controller in windows. I have multiple GPUs.

Found 8 available GPU(s)
GPU #0: GeForce GTX 1070
 COOLER-0

It doesn't seem like they all show up.

So I tried to set a fan speed:

nvfan -g 1 -l 40,50
WARN - No config file found; using default curve
thread 'main' panicked at 'index out of bounds: the len is 1 but the index is 1', /rustc/a53f9df32fbb0b5f4382caaad8f1a46f36ea887c\src\libcore\slice\mod.rs:2695:10
note: Run with `RUST_BACKTRACE=1` environment variable to display a backtrace.

So I tried -g 0 (but instructions say must be > 0 )

nvfan -g 0 -l 40,50
WARN - No config file found; using default curve
INFO - NVIDIA driver version: 430.86
INFO - NVIDIA graphics adapter #0: GeForce GTX 1070
INFO -   GPU #0 coolers: COOLER-0
thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: "NvAPI_GPU_GetFullName() failed; error -101"', src\libcore\result.rs:999:5
note: Run with `RUST_BACKTRACE=1` environment variable to display a backtrace.

Am I using it wrong? I placed the recent nvapi .dll's in source directory, just like instructions.

Setup:

Mixture of RTX & GTX cards (8 total). I am trying to find a command line tool that will set fan speed for both. 64-bit Windows 10

Oh- Gpu 0 in the example is a gtx card. #1 on the bus is an rtx.

However, if I am reading your script right, it looks like you came up with a way to handle dual fan control of rtx cards, which if that is the case, if I can get this to work- You would ne my hero

I have been spending days trying to write c++ script that will work for rtx cards, with no luck.

Hi! Unfortunately testing is lacking on Windows and things tend to break. I only have one card and most people use nvfancontrol on Linux. There is room for improvement if you're willing to test!!

It is a bit troubling than only one device is enumerated although all of them are detected. This is probably some bug I need to identify.

Coolers being individually controllable depends both on the card, and the VBIOS of the card. Most RTX cards should allow that though.

Would it be possible to give me an overview of your system setup? For instance what kind of cards you have and in what sequence, ie. how they appear on the windows device manager. Can you also repeat the above scenarios with the debug flag (-d) enabled? It might give us some more insight.

image

It's more just finding a command line tool that works in Windows, that supports RTX cards. I have went through many. It seems no one has been able to figure out how the hidden NVAPI methods for the cards, the only thing I have found is that the reason is likely because RTX cards have dual fan control.

image

C:\Users\Mayna\Documents\GitHub\nvfancontrol\target\release>nvfan.exe -p -d
Found 8 available GPU(s)
GPU #0: GeForce GTX 1070
 COOLER-0
C:\Users\Mayna\Documents\GitHub\nvfancontrol\target\release>nvfan.exe -g 0 -l 40,50 -d
WARN - No config file found; using default curve
DEBUG - Default configuration loaded
DEBUG - [[gpus]]
id = 0
enabled = true
points = [[41, 20], [49, 30], [57, 45], [66, 55], [75, 63], [78, 72], [80, 80]]
DEBUG - Curve points: [(41, 20), (49, 30), (57, 45), (66, 55), (75, 63), (78, 72), (80, 80)]
INFO - NVIDIA driver version: 430.86
INFO - NVIDIA graphics adapter #0: GeForce GTX 1070
INFO -   GPU #0 coolers: COOLER-0
thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: "NvAPI_GPU_GetFullName() failed; error -101"', src\libcore\result.rs:999:5
note: Run with `RUST_BACKTRACE=1` environment variable to display a backtrace.
DEBUG - Resetting fan control
C:\Users\Mayna\Documents\GitHub\nvfancontrol\target\release>nvfan.exe -g 1 -l 40,50 -d
WARN - No config file found; using default curve
DEBUG - Default configuration loaded
DEBUG - [[gpus]]
id = 1
enabled = true
points = [[41, 20], [49, 30], [57, 45], [66, 55], [75, 63], [78, 72], [80, 80]]
thread 'main' panicked at 'index out of bounds: the len is 1 but the index is 1', /rustc/a53f9df32fbb0b5f4382caaad8f1a46f36ea887c\src\libcore\slice\mod.rs:2695:10
note: Run with `RUST_BACKTRACE=1` environment variable to display a backtrace.

I am a programmer myself, I wrote a C++ command line script that works for AMD, that controls fans. I just am not hugely familiar with rust, but if there is something you need me to try to or look at code-wise, I am familiar with how NVAPI works at this point, and have been picking apart the script enough to understand the basics of it.

I believe I found where the error is. It seems like GPU handles are not properly allocated here so NvAPI_GPU_GetFullName fails because it cannot find a correct handle for any GPU past 0, hence the -101 error which is exactly that: NVAPI_EXPECTED_PHYSICAL_GPU_HANDLE. This will probably be the same for all functions that access the Physical GPU handles. I will try to put out a debug build for you to test hopefully within the weekend!

No problem. I found the C# NVAPIwrapper github, and build a simple program to control fans. It seems like it worked fine for GTX, but the dual fans in RTX cards were not responding. I'm pretty sure they are using the same methods as your NVAPI wrapper.

https://github.com/falahati/NvAPIWrapper

GPU.PhysicalGPU.CoolerInformation

I don't think your rust wrapper will work with RTX series cards, regardless of it is working or not. NVIDIA has the methods for RTX fan control in Windows locked down/hidden good. Its frustrating.

NVAPI is a mess of semi-hidden stuff with minimal documentation. However per-GPU coolers are essentially an array of length NVAPI_MAX_COOLERS_PER_GPU; presently 3 so technically information is retrieved (and hopefully applied on a per-cooler basis).

internal const int MaxNumberOfCoolersPerGPU = 3;

Nvapiwrapper has it set.

What I get when running their method on RTX cards is NVAPI_NOT_SUPPORTED, and it looks like it is very similar to your function.

It seems that its not that its not an issue of targeting coolers, but that the method is not available for RTX GPU entirely. Or maybe NVAPIWrapper coded it wrong, but theirs works on GTX cards no problem. I think NVIDIA may have a new method to set coolers for RTX cards. I am going to consult linux nvctrl.h, and see if they have a separate function for it, with a different address or something as homework.

Address=00A54220

ClientFanCoolers_SetControl( struct NvPhysicalGpuHandle__ *, unsigned long *,unsigned long *, int *, unsigned long *, int *)

This is what I see I when I set fan with a manufacturer controller.

Just thought I would share.

One thing at a time!
I think I fixed the issue where not all of the GPUs are enumerated. Can you please try this build and let me know of the output?

If you want to build from source here's the patch to master.

C:\Users\Mayna\Desktop\nvfancontrol> nvfancontrol.exe -g 0 -l 40,50 -d
WARN - No config file found; using default curve
DEBUG - Default configuration loaded
DEBUG - [[gpus]]
id = 0
enabled = true
points = [[41, 20], [49, 30], [57, 45], [66, 55], [75, 63], [78, 72], [80, 80]]
DEBUG - Curve points: [(41, 20), (49, 30), (57, 45), (66, 55), (75, 63), (78, 72), (80, 80)]
INFO - NVIDIA driver version: 430.86
INFO - NVIDIA graphics adapter #0: GeForce GTX 1070
INFO -   GPU #0 coolers: COOLER-0
INFO - NVIDIA graphics adapter #1: GeForce RTX 2070
WARN - Could not get GPU cooler indices or unsupported OS
INFO - NVIDIA graphics adapter #2: GeForce GTX 1070
INFO -   GPU #2 coolers: COOLER-0
INFO - NVIDIA graphics adapter #3: GeForce GTX 1050 Ti
INFO -   GPU #3 coolers: COOLER-0
INFO - NVIDIA graphics adapter #4: GeForce RTX 2070
WARN - Could not get GPU cooler indices or unsupported OS
INFO - NVIDIA graphics adapter #5: GeForce RTX 2070
WARN - Could not get GPU cooler indices or unsupported OS
INFO - NVIDIA graphics adapter #6: GeForce RTX 2070
WARN - Could not get GPU cooler indices or unsupported OS
INFO - NVIDIA graphics adapter #7: GeForce RTX 2070
WARN - Could not get GPU cooler indices or unsupported OS
DEBUG - Temp: 38; Speed: [0] RPM ([0]%); Load: 0%; Mode: Auto
DEBUG - Temp: 38; Speed: [0] RPM ([0]%); Load: 0%; Mode: Auto
DEBUG - Temp: 38; Speed: [0] RPM ([0]%); Load: 0%; Mode: Auto
DEBUG - Temp: 38; Speed: [0] RPM ([0]%); Load: 0%; Mode: Auto
DEBUG - Temp: 38; Speed: [0] RPM ([0]%); Load: 0%; Mode: Auto
DEBUG - Temp: 38; Speed: [0] RPM ([0]%); Load: 0%; Mode: Auto
DEBUG - Temp: 38; Speed: [0] RPM ([0]%); Load: 0%; Mode: Auto
DEBUG - Temp: 38; Speed: [0] RPM ([0]%); Load: 0%; Mode: Auto
DEBUG - Temp: 38; Speed: [0] RPM ([0]%); Load: 0%; Mode: Auto
DEBUG - Temp: 38; Speed: [0] RPM ([0]%); Load: 0%; Mode: Auto
DEBUG - Temp: 38; Speed: [0] RPM ([0]%); Load: 0%; Mode: Auto
DEBUG - Temp: 38; Speed: [0] RPM ([0]%); Load: 0%; Mode: Auto
DEBUG - Interrupt signal
DEBUG - Exiting
DEBUG - Resetting fan control
C:\Users\Mayna\Desktop\nvfancontrol> nvfancontrol.exe -g 1 -l 40,50 -d
WARN - No config file found; using default curve
DEBUG - Default configuration loaded
DEBUG - [[gpus]]
id = 1
enabled = true
points = [[41, 20], [49, 30], [57, 45], [66, 55], [75, 63], [78, 72], [80, 80]]
thread 'main' panicked at 'index out of bounds: the len is 1 but the index is 1', /rustc/a53f9df32fbb0b5f4382caaad8f1a46f36ea887c\src\libcore\slice\mod.rs:2695:10
note: Run with `RUST_BACKTRACE=1` environment variable to display a backtrace.

First one is gpu 0, which is GTX card. It doesn't change speed, but no errors.
Second is GPU 1, which is an RTX card- Fails with error.

May I also have the output of nvfancontrol -p ?

For the GTX 1070 is normal that the fan is at 0 since by default it will kick in once T ≥ 41C. Otherwise is a bit strange that no fans are found for the RTXs. I'm wondering if this is problem with my code or NVAPI. Need to investigate further...

C:\Users\Mayna\Desktop\nvfancontrol>nvfancontrol.exe -p -d
Found 8 available GPU(s)
GPU #0: GeForce GTX 1070
 COOLER-0
GPU #1: GeForce RTX 2070

What I have found from poking at manufacturer software, is that there is two different calls for NVIDIA fans. GTX uses a call like yours:

SetFanSpeed(int,unsigned long)

While RTX uses a different call:

ClientFanCoolers_SetControl( struct NvPhysicalGpuHandle__ *, unsigned long *,unsigned long *, int *, unsigned long *, int *)

Both of them proceed:

IsMultiGpuFanControlLockingEnabled(void)

and

IsFanControLocked(void)

Likely to determine fan control state needs to be set to manual, and which function to use (old or new).

It is probably the case that the handling of per-fan speed is different for RTX. However we should still be able to enumerate the fans regardless but listing of fans fails (that's why the output of -p fails after the first RTX). I should try to fix that first before anything else.

That being said the signature ClientFanCooler_SetControl is slightly more complicated than I would've expected. I guess one is the cooler id and the other the actual speed but it's not clear what the rest of the arguments are.

I was wrong

For GTX the function call is this:

	GpuGetCoolerSettings(struct NvPhysicalGpuHandle__ *,unsigned long,struct NV_GPU_GETCOOLER_SETTINGS_V4 *)
	GpuSetCoolerLevels(struct NvPhysicalGpuHandle__ *,unsigned long,struct NV_GPU_SETCOOLER_LEVEL_V2 *)

For RTX, it goes straight to this:

	ClientFanCoolers_SetControl(struct NvPhysicalGpuHandle__ *,unsigned long *,unsigned long *,int *,unsigned long *,int *)

I assume the unsigned long are meant to simulate NVU32 values. I'm not sure what the rest are.

falahati/NvAPIWrapper#10

I have also been talking about it here, if it interests you.

I give up. Stupid NVIDIA ...We can't control our own fans in Windows, thought its as simple as pie in linux.

The only thing I have figured out I believe is 100% is:

ClientFanCoolers_SetControl( [gpu handle] , ?, ?, [fan speed %] , ?, ? )

NvAPI_QueryInterface(0x814B209F)

I spent waaaay too much time on this at this point. Its frustrating that NVIDIA has to be so annoying and not release methods. Most people want to keep their cards cooler than their factory settings.

falahati/NvAPIWrapper#10

We figured out new fan methods in windows. He solved in his wrapper, and I confirmed it works for him.

Just letting you know, if you wanted to make a Rust version. If you write something and need testing, just tag me.

Yes I was monitoring the other thread. There are indeed different functions for RTX cards. I will try to code that path in once I have some time over the next few days. I'm definitely going to need some help testing.

Following from #23 a test binary should print all fans for the card and their current levels and RPMs.

Paging @Gordin
I'm attaching a build with initial support for the new client API functions. I've done some light testing and it seems like it's working but I'd appreciate a couple more tests before merging in the changes. If you're compiling from source please use the rtx branch and compile with cargo build --features=rtx.

Ideally I'd like to have the following

  • The output of nvfancontrol -p: this should print GPUs and their coolers
  • The output of nvfancontrol -d -m: this should periodically print the temperatures, load and fan settings as well as control mode (manual or auto)
  • The output of nvfancontrol -d -f: this should do what the previous command does and additionally manage the fans according to the specified curve. Please make sure there isn't any other program trying to access the fans because funny things may happen!
  • If you have access to older GPUs (say 8xx, 9xx, 10xx) please try to run this build of nvfancontrol and check if it behaves as expected.

Again thanks for helping me debug this!!

nvfancontrol.zip

I compiled it myself and it seems to be working. The first -m and -f was with afterburner still running, the second ones are without afterburner. The values all seem to be correct. The alternating 0 and 1XXX values are because the card has flickering issue when going from 0 to anything below ~38%.
Should the anti-flickering stuff work with this build? That's what I wanted to use nvfancontrol for in the first place ^^

PS C:\Users\Gordin\Documents\code\nvfancontrol> .\target\debug\nvfancontrol.exe -d -p
Found 1 available GPU(s)
GPU #0: GeForce RTX 2070 SUPER
 COOLER-0
PS C:\Users\Gordin\Documents\code\nvfancontrol> .\target\debug\nvfancontrol.exe -d -m
WARN - No config file found; using default curve
DEBUG - Default configuration loaded
DEBUG - [[gpu]]
id = 0
enabled = true
points = [[41, 20], [49, 30], [57, 45], [66, 55], [75, 63], [78, 72], [80, 80]]
INFO - NVIDIA driver version: 445.87
INFO - NVIDIA graphics adapter #0: GeForce RTX 2070 SUPER
INFO -   GPU #0 coolers: COOLER-0
INFO - Option "-m" is present; curve will have no actual effect
DEBUG - Temp: 30; Speed: [1240] RPM ([28]%); Load: 2%; Mode: Manual
DEBUG - Temp: 30; Speed: [1189] RPM ([28]%); Load: 2%; Mode: Manual
DEBUG - Temp: 30; Speed: [1005] RPM ([28]%); Load: 1%; Mode: Manual
DEBUG - Temp: 30; Speed: [1197] RPM ([28]%); Load: 1%; Mode: Manual
DEBUG - Temp: 30; Speed: [0] RPM ([28]%); Load: 1%; Mode: Manual
DEBUG - Temp: 30; Speed: [1288] RPM ([28]%); Load: 1%; Mode: Manual
DEBUG - Temp: 30; Speed: [0] RPM ([28]%); Load: 1%; Mode: Manual
DEBUG - Temp: 30; Speed: [1441] RPM ([28]%); Load: 1%; Mode: Manual
DEBUG - Temp: 30; Speed: [0] RPM ([28]%); Load: 2%; Mode: Manual
DEBUG - Temp: 30; Speed: [1486] RPM ([28]%); Load: 2%; Mode: Manual
DEBUG - Temp: 30; Speed: [0] RPM ([28]%); Load: 7%; Mode: Manual
DEBUG - Temp: 30; Speed: [1249] RPM ([28]%); Load: 3%; Mode: Manual
DEBUG - Temp: 30; Speed: [1032] RPM ([28]%); Load: 3%; Mode: Manual
DEBUG - Interrupt signal
DEBUG - Exiting
DEBUG - Resetting fan control
PS C:\Users\Gordin\Documents\code\nvfancontrol> .\target\debug\nvfancontrol.exe -d -f
DEBUG - Default configuration loaded
DEBUG - [[gpu]]
id = 0
enabled = true
points = [[41, 20], [49, 30], [57, 45], [66, 55], [75, 63], [78, 72], [80, 80]]
DEBUG - Curve points: [(41, 20), (49, 30), (57, 45), (66, 55), (75, 63), (78, 72), (80, 80)]
INFO - NVIDIA driver version: 445.87
INFO - NVIDIA graphics adapter #0: GeForce RTX 2070 SUPER
INFO -   GPU #0 coolers: COOLER-0
DEBUG - Temp: 30; Speed: [1381] RPM ([28]%); Load: 2%; Mode: Auto
DEBUG - Temp: 30; Speed: [1309] RPM ([28]%); Load: 1%; Mode: Auto
DEBUG - Temp: 30; Speed: [0] RPM ([28]%); Load: 1%; Mode: Auto
DEBUG - Temp: 30; Speed: [1215] RPM ([28]%); Load: 1%; Mode: Auto
DEBUG - Temp: 30; Speed: [1278] RPM ([28]%); Load: 1%; Mode: Auto
DEBUG - Temp: 30; Speed: [1283] RPM ([28]%); Load: 1%; Mode: Auto
DEBUG - Interrupt signal
DEBUG - Exiting
DEBUG - Resetting fan control
PS C:\Users\Gordin\Documents\code\nvfancontrol> .\target\debug\nvfancontrol.exe -d -f
WARN - No config file found; using default curve
DEBUG - Default configuration loaded
DEBUG - [[gpu]]
id = 0
enabled = true
points = [[41, 20], [49, 30], [57, 45], [66, 55], [75, 63], [78, 72], [80, 80]]
DEBUG - Curve points: [(41, 20), (49, 30), (57, 45), (66, 55), (75, 63), (78, 72), (80, 80)]
INFO - NVIDIA driver version: 445.87
INFO - NVIDIA graphics adapter #0: GeForce RTX 2070 SUPER
INFO -   GPU #0 coolers: COOLER-0
DEBUG - Temp: 30; Speed: [0] RPM ([0]%); Load: 2%; Mode: Auto
DEBUG - Temp: 30; Speed: [0] RPM ([0]%); Load: 1%; Mode: Auto
DEBUG - Temp: 30; Speed: [0] RPM ([0]%); Load: 1%; Mode: Auto
DEBUG - Temp: 30; Speed: [0] RPM ([0]%); Load: 1%; Mode: Auto
DEBUG - Temp: 30; Speed: [0] RPM ([0]%); Load: 2%; Mode: Auto
DEBUG - Temp: 30; Speed: [0] RPM ([0]%); Load: 1%; Mode: Auto
DEBUG - Temp: 30; Speed: [0] RPM ([0]%); Load: 1%; Mode: Auto
DEBUG - Temp: 30; Speed: [0] RPM ([0]%); Load: 1%; Mode: Auto
DEBUG - Interrupt signal
DEBUG - Exiting
DEBUG - Resetting fan control
PS C:\Users\Gordin\Documents\code\nvfancontrol> .\target\debug\nvfancontrol.exe -d -m
WARN - No config file found; using default curve
DEBUG - Default configuration loaded
DEBUG - [[gpu]]
id = 0
enabled = true
points = [[41, 20], [49, 30], [57, 45], [66, 55], [75, 63], [78, 72], [80, 80]]
DEBUG - Curve points: [(41, 20), (49, 30), (57, 45), (66, 55), (75, 63), (78, 72), (80, 80)]
INFO - NVIDIA driver version: 445.87
INFO - NVIDIA graphics adapter #0: GeForce RTX 2070 SUPER
INFO -   GPU #0 coolers: COOLER-0
INFO - Option "-m" is present; curve will have no actual effect
DEBUG - Temp: 31; Speed: [0] RPM ([0]%); Load: 4%; Mode: Auto
DEBUG - Temp: 31; Speed: [0] RPM ([0]%); Load: 1%; Mode: Auto
DEBUG - Temp: 31; Speed: [0] RPM ([0]%); Load: 1%; Mode: Auto
DEBUG - Temp: 31; Speed: [0] RPM ([0]%); Load: 1%; Mode: Auto
DEBUG - Temp: 31; Speed: [0] RPM ([0]%); Load: 2%; Mode: Auto
DEBUG - Temp: 31; Speed: [0] RPM ([0]%); Load: 2%; Mode: Auto
DEBUG - Interrupt signal
DEBUG - Exiting
DEBUG - Resetting fan control

I have access to a 970 and a 1070, but no second PC and I'd rather not swap around the cards all day unless you think you're close to having everything work ^^

The cards had no problem Afterburner though. I looked at the SetFanSpeed call in Afterburner in a x32dbg, and it looks like (in the Graph) the only thing they are checking to decide whether to use the new API or not is the result from ClientFanCoolers_IsSupported. For me it goes to the right to the ClientFanCoolers_SetControl part, while for other cards it will use GpuSetCoolerLevels.
SetFanSpeed

You can probably search for all calls of *Supported to figure out what calls to make. I don't use debuggers much but it looks like it could be easy for someone with experience. All the calls to nvapi have wrappers that look very similiar. There seem to be 10 different calls with "Supported" (that Afterburner uses):

Address   Type    Ordinal  Symbol                                                                           Symbol (undecorated)                                                                                                                                                                                                                                                                         
008141C0  Export  498      ?ClientFanCoolers_IsSupported@CNVAPIWrapper@@QAEHXZ                              public: int __thiscall CNVAPIWrapper::ClientFanCoolers_IsSupported(void)
00813520  Export  1881     ?IsCoreClockControlSupported@CNVAPIWrapper@@QAEHXZ                               public: int __thiscall CNVAPIWrapper::IsCoreClockControlSupported(void)
00813760  Export  1937     ?IsFramerateLimitControlSupported@CNVAPIWrapper@@QAEHXZ                          public: int __thiscall CNVAPIWrapper::IsFramerateLimitControlSupported(void)
00813520  Export  2035     ?IsMemoryClockControlSupported@CNVAPIWrapper@@QAEHXZ                             public: int __thiscall CNVAPIWrapper::IsMemoryClockControlSupported(void)
00816AF0  Export  2072     ?IsPerfClocksTestSupported@CNVAPIWrapper@@QAEHXZ                                 public: int __thiscall CNVAPIWrapper::IsPerfClocksTestSupported(void)
00813720  Export  2082     ?IsPerfPolicySupported@CNVAPIWrapper@@QAEHK@Z                                    public: int __thiscall CNVAPIWrapper::IsPerfPolicySupported(unsigned long)
00816560  Export  2131     ?IsShaderClockControlSupported@CNVAPIWrapper@@QAEHK@Z                            public: int __thiscall CNVAPIWrapper::IsShaderClockControlSupported(unsigned long)
00814C70  Export  2354     ?PS20_IsRelVoltageSupported@CNVAPIWrapper@@QAEHXZ                                public: int __thiscall CNVAPIWrapper::PS20_IsRelVoltageSupported(void)
00813510  Export  2355     ?PS20_IsSupported@CNVAPIWrapper@@QAEHXZ                                          public: int __thiscall CNVAPIWrapper::PS20_IsSupported(void)
00813D80  Export  2868     ?VFCurve_IsSupported@CNVAPIWrapper@@QAEHXZ                                       public: int __thiscall CNVAPIWrapper::VFCurve_IsSupported(void)

OK so it looks like it's working but could you please actually produce some load on the GPU so that the curve kicks in? That's the only way to check if the *Set command is actually working and if it's working as intended.

The IsSupported should check for the new API. Need to find out what's its signature.

As for the flicker bit: please check. I don't have this issue with my card (fan is always ON) so I can't test it. All the lower level bits are abstracted away so if the curve is applied I can't see any reason why it won't work.

OK, I tested with load and Reading the Coolers works, but Setting does not. When I first tested it, it looked like nvfancontrol was competing with Afterburner in setting the fan speed to 0, so I assumed it was working.
As soon as it tries to set a value I'm getting

ERROR - Could not update fan speed: NvAPI_GPU_SetCoolerLevels() failed; error -104

but that's expected I guess because my card wants the ClientFanCoolers_SetControl
This is with -d -f

Yes, obviously because I did not update set_fanspeed! Will post an updated binary soon.

OK this one looks like it's working without crashing. Give it a shot :-)

nvfancontrol.zip

It works now :)
The anti fan flickering also works but I guess that doesn't have anything to do with the actual nvapi calls anyway. Although, (this is really just a not very important feature request...), it would be nice to be able to configure the steps it uses, I'm ramping up to 38% and then down to 25%, so right now it goes down by 1% each tick which takes 13 seconds but when I do it manually I can just go down to 32% and then to 25% with a second apart and it will still work.
This is from -d -f -r 25,35

DEBUG - Temp: 38; Speed: [774] RPM ([25]%); Load: 36%; Mode: Manual
DEBUG - FanFlickerFix: preventing fan-off
DEBUG - FanFlickerFix [25, 35]: requested change from InRange(25) to InRange(25) (decrease), staying at 25%
DEBUG - Temp: 38; Speed: [776] RPM ([25]%); Load: 35%; Mode: Manual
DEBUG - FanFlickerFix: preventing fan-off
DEBUG - FanFlickerFix [25, 35]: requested change from InRange(25) to InRange(25) (decrease), staying at 25%
DEBUG - Temp: 36; Speed: [777] RPM ([25]%); Load: 4%; Mode: Manual
DEBUG - FanFlickerFix [25, 35]: requested change from InRange(25) to Below(23) (decrease), staying at 25%
DEBUG - Temp: 44; Speed: [775] RPM ([25]%); Load: 98%; Mode: Manual
DEBUG - FanFlickerFix [25, 35]: requested change from InRange(25) to InRange(25) (decrease), staying at 25%
DEBUG - Temp: 45; Speed: [776] RPM ([25]%); Load: 98%; Mode: Manual
DEBUG - FanFlickerFix [25, 35]: requested change from InRange(25) to InRange(26) (increase), setting 26%
DEBUG - Temp: 46; Speed: [774] RPM ([25]%); Load: 95%; Mode: Manual
DEBUG - FanFlickerFix [25, 35]: requested change from InRange(26) to InRange(27) (increase), setting 27%
DEBUG - Temp: 47; Speed: [803] RPM ([26]%); Load: 98%; Mode: Manual
DEBUG - FanFlickerFix [25, 35]: requested change from InRange(27) to InRange(28) (increase), setting 28%
DEBUG - Temp: 48; Speed: [838] RPM ([27]%); Load: 98%; Mode: Manual
DEBUG - FanFlickerFix [25, 35]: requested change from InRange(28) to InRange(28) (decrease), staying at 28%
DEBUG - Temp: 48; Speed: [871] RPM ([28]%); Load: 98%; Mode: Manual
DEBUG - FanFlickerFix [25, 35]: requested change from InRange(28) to InRange(30) (increase), setting 30%
DEBUG - Temp: 49; Speed: [872] RPM ([28]%); Load: 98%; Mode: Manual
DEBUG - FanFlickerFix [25, 35]: requested change from InRange(30) to InRange(31) (increase), setting 31%
DEBUG - Temp: 50; Speed: [923] RPM ([30]%); Load: 98%; Mode: Manual
DEBUG - FanFlickerFix [25, 35]: requested change from InRange(31) to InRange(31) (decrease), staying at 31%
DEBUG - Temp: 50; Speed: [960] RPM ([31]%); Load: 98%; Mode: Manual
DEBUG - FanFlickerFix [25, 35]: requested change from InRange(31) to InRange(33) (increase), setting 33%
DEBUG - Temp: 50; Speed: [966] RPM ([31]%); Load: 98%; Mode: Manual
DEBUG - FanFlickerFix [25, 35]: requested change from InRange(33) to InRange(33) (decrease), staying at 33%
DEBUG - Temp: 51; Speed: [1022] RPM ([33]%); Load: 98%; Mode: Manual
DEBUG - FanFlickerFix [25, 35]: requested change from InRange(33) to InRange(35) (increase), setting 35%
DEBUG - Temp: 51; Speed: [1026] RPM ([33]%); Load: 98%; Mode: Manual
DEBUG - FanFlickerFix [25, 35]: requested change from InRange(35) to InRange(35) (decrease), staying at 35%
DEBUG - Temp: 52; Speed: [1080] RPM ([35]%); Load: 98%; Mode: Manual
DEBUG - FanFlickerFix [25, 35]: requested change from InRange(35) to Above(37) (increase), setting 37%
DEBUG - Temp: 53; Speed: [1088] RPM ([35]%); Load: 98%; Mode: Manual
DEBUG - FanFlickerFix [25, 35]: requested change from Above(37) to Above(37) (decrease), staying at 37%
DEBUG - Temp: 53; Speed: [1147] RPM ([37]%); Load: 98%; Mode: Manual
DEBUG - FanFlickerFix [25, 35]: requested change from Above(37) to Above(37) (decrease), staying at 37%
DEBUG - Temp: 53; Speed: [1151] RPM ([37]%); Load: 98%; Mode: Manual
DEBUG - FanFlickerFix [25, 35]: requested change from Above(37) to Above(39) (increase), setting 39%
DEBUG - Temp: 54; Speed: [1147] RPM ([37]%); Load: 98%; Mode: Manual
DEBUG - FanFlickerFix [25, 35]: requested change from Above(39) to Above(39) (decrease), staying at 39%
DEBUG - Temp: 54; Speed: [1214] RPM ([39]%); Load: 98%; Mode: Manual
DEBUG - FanFlickerFix [25, 35]: requested change from Above(39) to Above(41) (increase), setting 41%
DEBUG - Temp: 55; Speed: [1214] RPM ([39]%); Load: 98%; Mode: Manual
DEBUG - FanFlickerFix [25, 35]: requested change from Above(41) to Above(41) (decrease), staying at 41%
DEBUG - Temp: 55; Speed: [1267] RPM ([41]%); Load: 98%; Mode: Manual
DEBUG - FanFlickerFix [25, 35]: requested change from Above(41) to Above(41) (decrease), staying at 41%
DEBUG - Temp: 55; Speed: [1274] RPM ([41]%); Load: 98%; Mode: Manual
DEBUG - FanFlickerFix [25, 35]: requested change from Above(41) to Above(43) (increase), setting 43%
DEBUG - Temp: 56; Speed: [1274] RPM ([41]%); Load: 98%; Mode: Manual
DEBUG - FanFlickerFix [25, 35]: requested change from Above(43) to Above(43) (decrease), staying at 43%
DEBUG - Temp: 56; Speed: [1334] RPM ([43]%); Load: 98%; Mode: Manual
DEBUG - FanFlickerFix [25, 35]: requested change from Above(43) to Above(43) (decrease), staying at 43%
DEBUG - Temp: 56; Speed: [1335] RPM ([43]%); Load: 98%; Mode: Manual
DEBUG - FanFlickerFix [25, 35]: requested change from Above(43) to Above(43) (decrease), staying at 43%
DEBUG - Temp: 56; Speed: [1336] RPM ([43]%); Load: 98%; Mode: Manual
DEBUG - FanFlickerFix [25, 35]: requested change from Above(43) to Above(43) (decrease), staying at 43%
DEBUG - Temp: 56; Speed: [1336] RPM ([43]%); Load: 98%; Mode: Manual
DEBUG - FanFlickerFix [25, 35]: requested change from Above(43) to Above(45) (increase), setting 45%
DEBUG - Temp: 57; Speed: [1333] RPM ([43]%); Load: 98%; Mode: Manual
DEBUG - FanFlickerFix [25, 35]: requested change from Above(45) to Above(45) (decrease), staying at 45%
DEBUG - Temp: 57; Speed: [1398] RPM ([45]%); Load: 98%; Mode: Manual
DEBUG - FanFlickerFix [25, 35]: requested change from Above(45) to Above(45) (decrease), staying at 45%

That's good! We're getting closer. It's still unclear whether there is indeed a NVAPI call for the IsSupported function or Afterburner uses some logic to determine it. In any case if there is a function for that it's going to take a while to debug afterburner to find its magic code. Unfortunately everything in the NVAPI is hidden behind the nvapi_QueryInterface function which takes a magic number and returns a pointer to a function. For anything that's not on the public API you will have to go through debuggers and I'm not really familiar with them especially on Windows. In any case I'll try to provide a unified solution and then merge the changes so there might be another binary for you to test soon.

For fanflicker we should probably involve @tzh1043 as the original author of that code, but this is not relevant to this issue; open a new issue if you want to pursue it further.

Hm, from what I've seen in the debugger I think it is an NVAPI call. All the NVAPI calls from afterburner have the same structure, and they all have the magic number value hardcoded in them. I'll see if I have time today to look at this again, I can probably find the magic value and at least the number of arguments.

Nevermind, ClientFanCoolers_IsSupported is not n NVAPI call it's just (X + 0x1538) >> 6 & 1. Not sure what X is yet...
Edit: (X + 0x1538) is obviously a pointer to something, I'm bad at this (:

OK, this points to a bitmask. For me it's 0110 1011. The first 1 seems to indicate that my card supports the new API. I have no idea how yet, but maybe I can find out where this value comes from or how the other bits are used. Maybe those are for the other IsSupported calls.

Yup :)
PS20_IsSupported points to the same address. It's just (X) & 1
VFCurve_IsSupported is (X >> 3) & 1
VFCurve_IsAdjustedClock is `(X >> 5) & 1

I've been digging around and I think the gpuFeatures that Afterburner knows actually come from a call to ClientFanCoolers_GetInfo, which uses ClientFanCoolers_GetInfoQuery, which actually makes an NVAPI call.
Magic code for this is 0xfb85b01e, so it's already implemented in nvfancontrol.

Still have to figure out how exactly the response ends up in the bitmap

Throttling down below 32% still does not have any effect on my GeForce RTX 2060 at WIN10. Using nvfancontrol from here.

D:\home\brgrvrm\apps\nvfancontrol>nvfancontrol.exe -d -l 0 -f -r 11,38
INFO - Loading configuration file: "D:\\Users\\brgrvrm\\AppData\\Roaming\\nvfancontrol.conf"
DEBUG - Curve points: [(30, 10), (40, 20), (50, 30), (60, 40), (70, 50), (80, 60)]
INFO - Trying to prevent fan flickering in range [11, 38]
DEBUG - FanFlickerFix: setting previous speed to 38%
INFO - NVIDIA driver version: 445.87
INFO - NVIDIA graphics adapter #0: GeForce RTX 2060
INFO -   GPU #0 coolers: COOLER-0, COOLER-1
DEBUG - FanFlickerFix [11, 38]: requested change from InRange(38) to InRange(12) (decrease), setting 37%
DEBUG - Temp: 31; Speed: [1200, 1199] RPM ([32, 32]%); Load: 0%; Mode: Manual
DEBUG - FanFlickerFix [11, 38]: requested change from InRange(37) to InRange(11) (decrease), setting 36%
DEBUG - Temp: 32; Speed: [1405, 1421] RPM ([37, 37]%); Load: 1%; Mode: Manual
DEBUG - FanFlickerFix [11, 38]: requested change from InRange(36) to InRange(12) (decrease), setting 35%
DEBUG - Temp: 32; Speed: [1325, 1320] RPM ([36, 36]%); Load: 0%; Mode: Manual
DEBUG - FanFlickerFix [11, 38]: requested change from InRange(35) to InRange(11) (decrease), setting 34%
DEBUG - Temp: 31; Speed: [1284, 1282] RPM ([35, 35]%); Load: 0%; Mode: Manual
DEBUG - FanFlickerFix [11, 38]: requested change from InRange(34) to InRange(12) (decrease), setting 33%
DEBUG - Temp: 32; Speed: [1253, 1253] RPM ([34, 34]%); Load: 0%; Mode: Manual
DEBUG - FanFlickerFix [11, 38]: requested change from InRange(33) to InRange(12) (decrease), setting 32%
DEBUG - Temp: 31; Speed: [1212, 1212] RPM ([33, 33]%); Load: 0%; Mode: Manual
DEBUG - FanFlickerFix [11, 38]: requested change from InRange(32) to InRange(12) (decrease), setting 31%
DEBUG - Temp: 32; Speed: [1195, 1194] RPM ([32, 32]%); Load: 0%; Mode: Manual
DEBUG - FanFlickerFix [11, 38]: requested change from InRange(31) to InRange(12) (decrease), setting 30%
DEBUG - Temp: 32; Speed: [1202, 1200] RPM ([32, 32]%); Load: 0%; Mode: Manual
DEBUG - FanFlickerFix [11, 38]: requested change from InRange(30) to InRange(12) (decrease), setting 29%
DEBUG - Temp: 32; Speed: [1199, 1203] RPM ([32, 32]%); Load: 0%; Mode: Manual
DEBUG - FanFlickerFix [11, 38]: requested change from InRange(29) to InRange(12) (decrease), setting 28%
DEBUG - Temp: 32; Speed: [1199, 1202] RPM ([32, 32]%); Load: 0%; Mode: Manual
DEBUG - FanFlickerFix [11, 38]: requested change from InRange(28) to InRange(12) (decrease), setting 27%
DEBUG - Temp: 32; Speed: [1203, 1201] RPM ([32, 32]%); Load: 1%; Mode: Manual
DEBUG - FanFlickerFix [11, 38]: requested change from InRange(27) to InRange(12) (decrease), setting 26%
DEBUG - Temp: 32; Speed: [1200, 1201] RPM ([32, 32]%); Load: 0%; Mode: Manual
DEBUG - FanFlickerFix [11, 38]: requested change from InRange(26) to InRange(12) (decrease), setting 25%
DEBUG - Temp: 32; Speed: [1203, 1202] RPM ([32, 32]%); Load: 0%; Mode: Manual
DEBUG - FanFlickerFix [11, 38]: requested change from InRange(25) to InRange(11) (decrease), setting 24%
DEBUG - Temp: 32; Speed: [1201, 1202] RPM ([32, 32]%); Load: 0%; Mode: Manual
DEBUG - FanFlickerFix [11, 38]: requested change from InRange(24) to InRange(12) (decrease), setting 23%
DEBUG - Temp: 32; Speed: [1200, 1200] RPM ([32, 32]%); Load: 0%; Mode: Manual
DEBUG - FanFlickerFix [11, 38]: requested change from InRange(23) to InRange(12) (decrease), setting 22%
DEBUG - Temp: 32; Speed: [1199, 1197] RPM ([32, 32]%); Load: 0%; Mode: Manual
DEBUG - FanFlickerFix [11, 38]: requested change from InRange(22) to InRange(11) (decrease), setting 21%
DEBUG - Temp: 31; Speed: [1199, 1200] RPM ([32, 32]%); Load: 0%; Mode: Manual
DEBUG - FanFlickerFix [11, 38]: requested change from InRange(21) to InRange(11) (decrease), setting 20%
DEBUG - Temp: 31; Speed: [1199, 1200] RPM ([32, 32]%); Load: 0%; Mode: Manual
DEBUG - FanFlickerFix [11, 38]: requested change from InRange(20) to InRange(11) (decrease), setting 19%
DEBUG - Temp: 32; Speed: [1200, 1199] RPM ([32, 32]%); Load: 0%; Mode: Manual
DEBUG - FanFlickerFix [11, 38]: requested change from InRange(19) to InRange(12) (decrease), setting 18%
DEBUG - Temp: 32; Speed: [1199, 1202] RPM ([32, 32]%); Load: 0%; Mode: Manual
DEBUG - FanFlickerFix [11, 38]: requested change from InRange(18) to InRange(12) (decrease), setting 17%
DEBUG - Temp: 32; Speed: [1198, 1198] RPM ([32, 32]%); Load: 0%; Mode: Manual
DEBUG - FanFlickerFix [11, 38]: requested change from InRange(17) to InRange(12) (decrease), setting 16%
DEBUG - Temp: 32; Speed: [1202, 1201] RPM ([32, 32]%); Load: 0%; Mode: Manual
DEBUG - FanFlickerFix [11, 38]: requested change from InRange(16) to InRange(12) (decrease), setting 15%
DEBUG - Temp: 32; Speed: [1196, 1202] RPM ([32, 32]%); Load: 0%; Mode: Manual
DEBUG - FanFlickerFix [11, 38]: requested change from InRange(15) to InRange(11) (decrease), setting 14%
DEBUG - Temp: 31; Speed: [1200, 1201] RPM ([32, 32]%); Load: 0%; Mode: Manual
DEBUG - FanFlickerFix [11, 38]: requested change from InRange(14) to InRange(11) (decrease), setting 13%
DEBUG - Temp: 32; Speed: [1202, 1199] RPM ([32, 32]%); Load: 0%; Mode: Manual
DEBUG - FanFlickerFix [11, 38]: requested change from InRange(13) to InRange(11) (decrease), setting 12%
DEBUG - Temp: 32; Speed: [1199, 1201] RPM ([32, 32]%); Load: 0%; Mode: Manual
DEBUG - FanFlickerFix [11, 38]: requested change from InRange(12) to InRange(12) (decrease), staying at 12%
DEBUG - Temp: 32; Speed: [1201, 1199] RPM ([32, 32]%); Load: 4%; Mode: Manual
DEBUG - FanFlickerFix [11, 38]: requested change from InRange(12) to InRange(12) (decrease), staying at 12%
DEBUG - Temp: 32; Speed: [1200, 1200] RPM ([32, 32]%); Load: 2%; Mode: Manual
DEBUG - FanFlickerFix [11, 38]: requested change from InRange(12) to InRange(12) (decrease), staying at 12%
DEBUG - Temp: 32; Speed: [1199, 1199] RPM ([32, 32]%); Load: 0%; Mode: Manual
DEBUG - FanFlickerFix [11, 38]: requested change from InRange(12) to InRange(12) (decrease), staying at 12%
DEBUG - Temp: 32; Speed: [1199, 1199] RPM ([32, 32]%); Load: 0%; Mode: Manual
DEBUG - FanFlickerFix [11, 38]: requested change from InRange(12) to InRange(12) (decrease), staying at 12%
DEBUG - Temp: 32; Speed: [1201, 1200] RPM ([32, 32]%); Load: 0%; Mode: Manual
DEBUG - FanFlickerFix [11, 38]: requested change from InRange(12) to InRange(12) (decrease), staying at 12%
DEBUG - Temp: 32; Speed: [1199, 1203] RPM ([32, 32]%); Load: 10%; Mode: Manual
DEBUG - FanFlickerFix [11, 38]: requested change from InRange(12) to InRange(12) (decrease), staying at 12%
DEBUG - Temp: 32; Speed: [1198, 1201] RPM ([32, 32]%); Load: 1%; Mode: Manual
DEBUG - FanFlickerFix [11, 38]: requested change from InRange(12) to InRange(12) (decrease), staying at 12%
DEBUG - Temp: 32; Speed: [1199, 1201] RPM ([32, 32]%); Load: 0%; Mode: Manual
DEBUG - FanFlickerFix [11, 38]: requested change from InRange(12) to InRange(12) (decrease), staying at 12%
DEBUG - Temp: 32; Speed: [1201, 1200] RPM ([32, 32]%); Load: 0%; Mode: Manual
DEBUG - FanFlickerFix [11, 38]: requested change from InRange(12) to InRange(11) (decrease), setting 11%
DEBUG - Temp: 31; Speed: [1199, 1200] RPM ([32, 32]%); Load: 14%; Mode: Manual
DEBUG - FanFlickerFix [11, 38]: requested change from InRange(11) to InRange(12) (increase), setting 12%
DEBUG - Temp: 32; Speed: [1200, 1198] RPM ([32, 32]%); Load: 0%; Mode: Manual
DEBUG - FanFlickerFix [11, 38]: requested change from InRange(12) to InRange(12) (decrease), staying at 12%
DEBUG - Temp: 32; Speed: [1200, 1198] RPM ([32, 32]%); Load: 10%; Mode: Manual
DEBUG - FanFlickerFix [11, 38]: requested change from InRange(12) to InRange(12) (decrease), staying at 12%
DEBUG - Temp: 32; Speed: [1200, 1201] RPM ([32, 32]%); Load: 1%; Mode: Manual
DEBUG - FanFlickerFix [11, 38]: requested change from InRange(12) to InRange(12) (decrease), staying at 12%
DEBUG - Temp: 31; Speed: [1201, 1200] RPM ([32, 32]%); Load: 0%; Mode: Manual
DEBUG - FanFlickerFix [11, 38]: requested change from InRange(12) to InRange(12) (decrease), staying at 12%
DEBUG - Temp: 32; Speed: [1200, 1198] RPM ([32, 32]%); Load: 0%; Mode: Manual
DEBUG - FanFlickerFix [11, 38]: requested change from InRange(12) to InRange(12) (decrease), staying at 12%
DEBUG - Temp: 32; Speed: [1203, 1200] RPM ([32, 32]%); Load: 1%; Mode: Manual
DEBUG - FanFlickerFix [11, 38]: requested change from InRange(12) to InRange(11) (decrease), setting 11%
DEBUG - Temp: 32; Speed: [1200, 1199] RPM ([32, 32]%); Load: 0%; Mode: Manual
DEBUG - FanFlickerFix [11, 38]: requested change from InRange(11) to InRange(11) (decrease), staying at 11%
DEBUG - Temp: 31; Speed: [1204, 1203] RPM ([32, 32]%); Load: 4%; Mode: Manual
DEBUG - FanFlickerFix [11, 38]: requested change from InRange(11) to InRange(12) (increase), setting 12%
DEBUG - Temp: 32; Speed: [1199, 1199] RPM ([32, 32]%); Load: 0%; Mode: Manual
DEBUG - FanFlickerFix [11, 38]: requested change from InRange(12) to InRange(12) (decrease), staying at 12%
DEBUG - Temp: 31; Speed: [1197, 1202] RPM ([32, 32]%); Load: 0%; Mode: Manual
DEBUG - FanFlickerFix [11, 38]: requested change from InRange(12) to InRange(12) (decrease), staying at 12%
DEBUG - Temp: 31; Speed: [1201, 1198] RPM ([32, 32]%); Load: 0%; Mode: Manual
DEBUG - FanFlickerFix [11, 38]: requested change from InRange(12) to InRange(12) (decrease), staying at 12%
DEBUG - Temp: 31; Speed: [1201, 1203] RPM ([32, 32]%); Load: 0%; Mode: Manual
DEBUG - FanFlickerFix [11, 38]: requested change from InRange(12) to InRange(12) (decrease), staying at 12%
DEBUG - Temp: 32; Speed: [1201, 1201] RPM ([32, 32]%); Load: 0%; Mode: Manual
DEBUG - FanFlickerFix [11, 38]: requested change from InRange(12) to InRange(11) (decrease), setting 11%
DEBUG - Temp: 31; Speed: [1200, 1197] RPM ([32, 32]%); Load: 0%; Mode: Manual
DEBUG - FanFlickerFix [11, 38]: requested change from InRange(11) to InRange(12) (increase), setting 12%
DEBUG - Temp: 31; Speed: [1200, 1197] RPM ([32, 32]%); Load: 0%; Mode: Manual
DEBUG - FanFlickerFix [11, 38]: requested change from InRange(12) to InRange(12) (decrease), staying at 12%
DEBUG - Temp: 32; Speed: [1198, 1202] RPM ([32, 32]%); Load: 0%; Mode: Manual
DEBUG - FanFlickerFix [11, 38]: requested change from InRange(12) to InRange(11) (decrease), setting 11%
DEBUG - Temp: 31; Speed: [1201, 1201] RPM ([32, 32]%); Load: 0%; Mode: Manual
DEBUG - FanFlickerFix [11, 38]: requested change from InRange(11) to InRange(12) (increase), setting 12%
DEBUG - Temp: 31; Speed: [1201, 1199] RPM ([32, 32]%); Load: 0%; Mode: Manual
DEBUG - Interrupt signal
DEBUG - Exiting
DEBUG - Resetting fan control

If you can't get it lower that a threshold you're most probably firmware limited. I can't get my card under 30-ish% either. On most cards there is a lower limit you can get the fan before it turns off (if it switches off). It will be either 0 or 30-80%. Some cards can do 20-80, or even 15-80 but that's really up to the manufacturer of the card.

Ok, thank you for your comment. Interesting fact is that as long as my PC is in mainboard BIOS the fans are at lower speed. Rotation speed is about 500-600rpm.

My 2070 Super I've tested this with seems to be unlocked, I can get the fans to spin as low as ~24%, but if I go lower the fans just turn off and don't get enough power to start back up again and are stuck in a loop of trying to start and failing. When that happens they don't spin up correctly anymore until I set them at least to ~40%.
Some manufacturers probably lock the fans to a minimum of around 30% to prevent that from happening. Maybe your card is still able to spin lower in the BIOS because that is probably using a different way to control the GPU as the API that nvfancontrol uses.
Personally I'm keeping mine at 30% with FanFlickerFix (930RPM).