Using a proper password hashing algorithm mostly addresses this concern... and standard cryptographic hashes like MD-5, SHA-1, SHA-256, etc. are not appropriate. They're designed to be as time and space-efficient as possible while still achieving their security goals. Password hashing functions (more precisely, password-based key derivation functions) are designed specifically to be time and space-hungry, efficient enough that you can execute them in half-second or so for user authentication, but slow enough that brute forcing even moderately-good passwords is intractible.
The best widely-available algorithm is Argon2id. The modern algorithms don't focus so much on requiring lots of CPU cycles because GPUs. Instead, they focus on requiring significant amounts of RAM, in ways that provably cannot be reduced. The most-recommended Argon2id configuration requires 2GB RAM. This makes it feasible for most servers to handle fairly easily, as long as they don't have to verify too many passwords in parallel, but it means that GPUs don't help the attacker, and it's also slow enough that while you can get some traction by using a large botnet, it's really not very much. If a PC requires 500ms per attempt, and you have a million-machine botnet, you can still only try 2M passwords per second. If user passwords have, say, 30 bits of entropy, your massive botnet can find one every five minutes on average. If they have 40 bits, your botnet can find a password every ~3 days, on average. That's not nothing, but if you have control of a million machines, you can definitely find better uses for them.
Of course, even better is to use passkeys or similar, but as a practical matter you probably have to have a password to fall back on.