Individually controlling two 4 pin PWM Fans 25KHz

If the internal circuitry of your fans transforms the pwm-signal to an analog voltage
the pwm-frequency can be varied in some range. I guess most pwm-controlled devices do so.

You can test this by varying the pwm-frequency of your one-channel pwmcode

Under this pre-condition a different approach can be used:
using one timer-interrupt and create the pwm-signals in software.
Though on a Arduino Uno the maximum-frequency for this is limited.
When using timer2 I tested it to a maximum-frequency of 30 kHz as the base-frequency for the timer-interrupt.
Depending on the resolution you want to have for the pwm-signal the pwm-frequency is
base-frequency / resolution
example

basefrequency 30 kHz
resolution 10 (steps from 0% to 100% = 0,10,20,30,...100%)
pwm-frequency = 30 kHz / 10 = 3 kHz

basefrequency 30 kHz
resolution 100 (steps from 0% to 100% = 0,1,2,3,...100%)
pwm-frequency = 30 kHz / 100 = 300 Hz

I'm not very familiar with the counters inside arduinos
For other microcontrollers the reachable frequency will be higher but as they have a different hardware setting up the frequency is done differently.

Anyway I guess for any microcontroller like Seeeduino XIAO, all kinds of SMT-boards, SAMD21 SAMD91, nRF, ESP8266, ESP32 you will find demo-codes that show how to use a timer-interrupt.

So here is the demo-code for a 4 channel PWM created by a single timer-interrupt using timer2 on Arduino = microcontroller-type Atmega328P

// this demo-code belongs to the public domain
// this code demonstrates how to blink the  onboard-LED
// of an Arduino-Uno (connected to IO-pin 13)
// in a nonblocking way using timing based on function millis()
// and creating a pwm-signal "in the backround"
// through setting up a timer-interrupt through configuring timer2

// A T T E N T I O N !    R E M A R K
// some other libraries that make use of timer2 may conflict with this


// start of macros dbg and dbgi
#define dbg(myFixedText, variableName) \
  Serial.print( F(#myFixedText " "  #variableName"=") ); \
  Serial.println(variableName);
// usage: dbg("1:my fixed text",myVariable);
// myVariable can be any variable or expression that is defined in scope

#define dbgi(myFixedText, variableName,timeInterval) \
  do { \
    static unsigned long intervalStartTime; \
    if ( millis() - intervalStartTime >= timeInterval ){ \
      intervalStartTime = millis(); \
      Serial.print( F(#myFixedText " "  #variableName"=") ); \
      Serial.println(variableName); \
    } \
  } while (false);
// usage: dbgi("2:my fixed text",myVariable,1000);
// myVariable can be any variable or expression that is defined in scope
// third parameter is the time in milliseconds that must pass by until the next time a
// Serial.print is executed
// end of macros dbg and dbgi

void PrintFileNameDateTime() {
  Serial.println( F("Code running comes from file ") );
  Serial.println( F(__FILE__));
  Serial.print( F("  compiled ") );
  Serial.print(F(__DATE__));
  Serial.print( F(" ") );
  Serial.println(F(__TIME__));
}


boolean TimePeriodIsOver (unsigned long &periodStartTime, unsigned long TimePeriod) {
  unsigned long currentMillis  = millis();
  if ( currentMillis - periodStartTime >= TimePeriod ) {
    periodStartTime = currentMillis; // set new expireTime
    return true;                // more time than TimePeriod) has elapsed since last time if-condition was true
  }
  else return false;            // not expired
}

unsigned long MyTestTimer =  0;                   // variables MUST be of type unsigned long
const byte    OnBoard_LED = 13;


void BlinkHeartBeatLED(int IO_Pin, int BlinkPeriod) {
  static unsigned long MyBlinkTimer;
  pinMode(IO_Pin, OUTPUT);

  if ( TimePeriodIsOver(MyBlinkTimer, BlinkPeriod) ) {
    digitalWrite(IO_Pin, !digitalRead(IO_Pin) );
  }
}


const unsigned long pwmBaseFrequency = 30000;
const unsigned long resolution = 10;
const unsigned long pulseFreq = pwmBaseFrequency / resolution;

volatile unsigned long pwmCounter   = 0;
volatile unsigned long periodCount  = pwmBaseFrequency / pulseFreq;
const byte maxChannels = 4;
volatile byte dutyCh[maxChannels];
volatile unsigned long pwmOnTimeCnt[maxChannels];

const byte pwmPin[maxChannels] = {4, 5, 6, 7};

byte duty = resolution * 2;

void setupTimerInterrupt(unsigned long ISR_call_frequency) {
  const byte Prescaler___8 = (1 << CS21);
  const byte Prescaler__32 = (1 << CS21) + (1 << CS20);
  const byte Prescaler__64 = (1 << CS22);
  const byte Prescaler_128 = (1 << CS22) + (1 << CS20);
  const byte Prescaler_256 = (1 << CS22) + (1 << CS21);
  const byte Prescaler1024 = (1 << CS22) + (1 << CS21) + (1 << CS20);

  const unsigned long CPU_Clock = 16000000;
  const byte toggleFactor = 1;

  unsigned long OCR2A_value;

  cli();//stop interrupts

  TCCR2A = 0;// set entire TCCR2A register to 0
  TCCR2B = 0;// same for TCCR2B
  TCNT2  = 0;//initialize counter value to 0

  TCCR2A |= (1 << WGM21); // turn on CTC mode
  TIMSK2 |= (1 << OCIE2A); // enable timer compare interrupt

  // the prescaler must be setup to a value that the calculation
  // of the value for OCR2A is below 256
  TCCR2B = Prescaler___8;
  OCR2A_value = (CPU_Clock / ( 8 * ISR_call_frequency * toggleFactor) )  - 1;
  dbg("1 setup: timer ", OCR2A_value);

  if (OCR2A_value > 256) {  // if value too big
    TCCR2B = Prescaler__32; // set higher prescaler
    OCR2A_value = (CPU_Clock / ( 32 * ISR_call_frequency * toggleFactor) )  - 1;
    dbg("setup: prescaler 32", OCR2A_value);
  }

  if (OCR2A_value > 256) { // if value too big
    TCCR2B = Prescaler__64;// set higher prescaler
    OCR2A_value = (CPU_Clock / ( 64 * ISR_call_frequency * toggleFactor) )  - 1;
    dbg("setup: prescaler 64", OCR2A_value);
  }

  if (OCR2A_value > 256) { // if value too big
    TCCR2B = Prescaler_128;// set higher prescaler
    OCR2A_value = (CPU_Clock / ( 128 * ISR_call_frequency * toggleFactor) )  - 1;
    dbg("setup: prescaler 128", OCR2A_value);
  }

  if (OCR2A_value > 256) {  // if value too big
    TCCR2B = Prescaler_256; // set higher prescaler
    OCR2A_value = (CPU_Clock / ( 256 * ISR_call_frequency * toggleFactor) )  - 1;
    dbg("setup: prescaler 256", OCR2A_value);
  }

  if (OCR2A_value > 256) {   // if value too big
    TCCR2B = Prescaler1024;  // set higher prescaler
    OCR2A_value = (CPU_Clock / ( 1024 * ISR_call_frequency * toggleFactor) )  - 1;
    dbg("setup: prescaler 1024", OCR2A_value);
  }

  OCR2A = OCR2A_value; // finally set the value of OCR2A

  sei();//allow interrupts
  dbg("setup: timer done", OCR2A_value);

  dbg("setup", periodCount);
}

unsigned long pwmCount = 0;

const byte Clock_Pin   = 9;


void setup() {
  Serial.begin(115200);
  Serial.println( F("Setup-Start") );
  PrintFileNameDateTime();
  for (int i = 0; i < maxChannels; i++) {
    pinMode(pwmPin[i], OUTPUT);    
  }
  /*
  pinMode(pwmPin[0], OUTPUT);    
  pinMode(pwmPin[1], OUTPUT);    
  pinMode(pwmPin[2], OUTPUT);    
  pinMode(pwmPin[3], OUTPUT);    
  */
  pinMode(Clock_Pin, OUTPUT);

  setupTimerInterrupt(pwmBaseFrequency);
  pwmCount = 0;
  SetDuty(0,10);
  SetDuty(1,25);
  SetDuty(2,80);
  SetDuty(3,100);  
}


void createPWM() {
  pwmCount++;

  // for debugging purposes create a clock-signal
  digitalWrite(Clock_Pin, !digitalRead(Clock_Pin) );

  if (pwmCount == periodCount) { // if new period starts
    pwmCount = 0; // reset counter
    for (int i = 0; i < maxChannels; i++) {
      if (dutyCh[i] > 0) { // if duty is above 0%
        digitalWrite(pwmPin[i], HIGH); // switch channel ON
      }
    }
  }

  for (int i = 0; i < maxChannels; i++) {
    if (pwmCount == pwmOnTimeCnt[i]) { // if ON-time is reached
      digitalWrite(pwmPin[i], LOW); // switch channel OFF
    }
  }
}


void SetDuty(byte channel, byte duty) {

  if (duty > 100) {
    duty = 100;
  }
  if (channel < maxChannels) {
    dutyCh[channel] = duty;
    pwmOnTimeCnt[channel] = (periodCount * duty) / 100;     
  }
  
  dbg("1:",channel);
  dbg("2:",dutyCh[channel]);
  dbg("3:",pwmOnTimeCnt[channel]);
  dbg("IO-pin Nr.:",pwmPin[channel]);
}


ISR(TIMER2_COMPA_vect) {
  createPWM();
}


void loop() {
  BlinkHeartBeatLED(OnBoard_LED, 200);

  if (TimePeriodIsOver(MyTestTimer,1000) ) {
    // if 3000 milliseconds have passed by
    duty = duty + resolution;
    if (duty > 100) {
      duty = 0;
    }
    SetDuty( 0,duty ); 
  }

}

best regards Stefan