Skip to content

feat(fallback): per-account RPM cap, default 40 for NVIDIA - #3203

Open
MuhammadUsamaMX wants to merge 3 commits into
decolua:masterfrom
MuhammadUsamaMX:feat/per-account-rpm-cap
Open

MuhammadUsamaMX wants to merge 3 commits into
decolua:masterfrom
MuhammadUsamaMX:feat/per-account-rpm-cap

Conversation

@MuhammadUsamaMX

Copy link
Copy Markdown
Contributor

Stacked on #2895 (which is stacked on #2879). The diff below is only the last commit; it collapses to 6 files once those merge.

Problem

NVIDIA NIM enforces a per-API-key requests-per-minute limit. Crossing it returns 429, which parks that account on a cooldown — so a short burst can take the whole pool out for minutes even though every account is healthy. The limit is known up front, but nothing counts against it, so it is always discovered by tripping it.

Change

Count our own requests per account and skip an account that has already spent its minute budget, so the request goes to the next account instead of earning a 429.

  • src/sse/services/rpmLimiter.js — sliding 60s window per connection id: recordRequest, isOverLimit, retryAfterMs.
  • getAuth filters out capped accounts alongside model-locked/excluded ones, and records a request against whichever account it hands out.
  • When every account for a provider is capped, it returns the usual allRateLimited shape with the time capacity comes back, rather than failing hard.
  • Editable per provider from Providers → [id] (RPM / account, beside Retry delay), stored in settings.rpmByProvider. Blank = DEFAULT_PROVIDER_RPM (40 for nvidia, unset elsewhere), 0 = unlimited.

Counters are in-process. That is the right scope for a single gateway; a multi-instance deployment behind a load balancer would need shared state, and there is a ponytail: note in the file saying so.

Verification

Unit test in tests/unit/rpmLimiter.test.js covers unlimited-when-0, capping at the limit, the window sliding open again, per-account isolation, and retryAfterMs.

End to end against a live instance with 7 NVIDIA accounts, cap temporarily set to 1:

req 1..6 -> 200 ok                     (one account each)
req 7    -> 503 Local 1 RPM cap reached for every nvidia account (reset after 30s)
req 8,9  -> 503 (same)

Log line: [AUTH] nvidia | all 7 accounts at the 1 RPM cap (30s). No 429 was sent upstream. After the window elapsed, requests succeeded again, and with the override removed the default of 40 applies.

429 fallback currently locks an account for a fixed exponential backoff
(getQuotaCooldown: base 2s … max 5m), ignoring the reset window the
provider actually reports. So an account whose quota resets in 30m is
retried every 5m (wasted 429s and fallback churn), while one that resets
in seconds is held out far too long.

Parse the provider's own reset signal from the 429 body/headers and use
it as the cooldown instead:

  - Google/Antigravity RetryInfo   "retryDelay": "1976.365s"  (relative s)
  - OpenAI/Codex                    "resets_at" / "reset_at"  (epoch s|ms)
  - OpenRouter / RateLimit headers  "X-RateLimit-Reset"       (epoch s|ms)
  - Retry-After: N                  (seconds)

resolveCooldownMs() prefers the parsed reset, floors it at the static
backoff (never retry sooner than before) and caps it at
MAX_RATE_LIMIT_COOLDOWN_MS so a bogus/huge value can't wedge an account.
Cap raised 30m -> 6h to accommodate real multi-hour resets (e.g. Codex).
No signal present -> unchanged static backoff.
Add a per-provider retry-delay option so operators can set the cooldown
applied when an account hits a rate-limit/error and the provider reports
no reset window of its own.

- Dashboard: a "Retry delay" selector on each provider page
  (Auto / 15s / 30s / 1m / 2m / 5m / 10m / 30m / 1h), saved to the new
  `retryDelayByProvider` setting via the existing /api/settings PATCH.
- markAccountUnavailable honors it as a fallback only: a provider-reported
  reset (retryDelay / Retry-After / resets_at / X-RateLimit-Reset) is always
  used and never shortened. When the provider reports nothing, a configured
  value sets the lock duration; with "auto", free/free-tier provider pools
  cap the static backoff at 60s so a small account set recovers within its
  per-minute window instead of all locking for ~5m at once.

Builds on the provider-reset-aware cooldown (uses parseProviderResetMs).
NVIDIA NIM enforces a per-API-key requests-per-minute limit. Crossing it
returns 429, which parks that account on a cooldown, so a burst can knock the
whole pool out for minutes at a time even though the accounts are healthy.

Count our own requests per account and skip an account that has used its
minute budget, so the provider never sees the 429 in the first place. When
every account for a provider is at its cap the request reports the wait like
any other rate-limit instead of failing hard.

The cap is per provider and editable from Providers -> [id] ("RPM / account",
next to Retry delay): blank uses DEFAULT_PROVIDER_RPM (40 for nvidia, unset
elsewhere) and 0 means unlimited, stored in settings.rpmByProvider.

Counters are in-process, which is the right scope for a single gateway; a
multi-instance deployment would need shared state.
@MuhammadUsamaMX
MuhammadUsamaMX force-pushed the feat/per-account-rpm-cap branch from d9580ba to 5c91639 Compare August 10, 2026 14:40
golamrabbi696 added a commit to golamrabbi696/EzRouter that referenced this pull request Aug 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant