It's for this task that I wrote
Tie::Scalar::Decay, which implements scalars whose values change over time. For each failed attempt, I would increment a scalar. Every N amount of time the value would decrease by F. But if the value went above a particular value, I would assume an attack was underway and take action. Those options can be tweaked so that a real user who's forgotten his password won't be locked out, but an automated password-guessing bot will be locked out.
Depending on your implementation, you may be able to use this module, but if you can't, then the basic idea of it is simple and should be easy to implement some other way. If you do have to reimplement it, I'd be glad to help or to accept suggestions or patches.