Repository navigation
feat(proxy): fairness under load controls with fair queueing and admin UI - #42029
devin-ai-integration[bot] wants to merge 27 commits into
Conversation
…n UI Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…es and read-only mappings Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
|
I'll fix CI failures and address comments from users with write access. I'll skip comments containing "(aside)".
|
|
|
|
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
…ise-branch test Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…r_load Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> # Conflicts: # litellm/proxy/common_request_processing.py # litellm/proxy/hooks/parallel_request_limiter_v3.py # tests/test_litellm/proxy/test_proxy_server.py # ui/litellm-dashboard/src/lib/http/schema.d.ts
…tings, partial usage and share totals Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
|
@greptileai review |
…iew under the lint size cap Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
…abled Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
|
@greptileai review |
|
bugbot run |
…es are active Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Comments Outside DiffThese findings sit on lines the diff does not cover, so they could not be posted inline. Each one leaves this list once its file changes.
|
…ion was refunded Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
|
@greptileai review |
|
bugbot run |
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
|
@greptileai review |
|
bugbot run |
…cing success bills only the remainder Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
|
@greptileai review |
|
bugbot run |
…te fails Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
|
@greptileai review |
|
bugbot run |
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
|
@greptileai review |
|
bugbot run |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit 511e689. Configure here.
Runtime verification on 511e689Passed persistence, validation, key/team assignment, live counters, and one-second deadlines with real completions: two 200s, two 429s |
Description
TLDR
Problem this solves:
How it solves it:
PUT /fairness/settings, applied live on every workerx-litellm-max-queue-wait, cancelled on disconnectGET /fairness/statusand the page show saturation, per-class usage, queue depth, waits, rejectionsUser Flow
Before: a proxy admin has no way to keep a support app responsive while a nightly batch job shares
gpt-4o-mini, and there is no page or endpoint for it404 {"detail":"Not Found"}rpm: 4. All eight go straight to the provider; nothing protects the production share or tells the batch caller to slow downAfter: the admin sets reserved shares and deadlines on a page, production keeps its share, batch waits its deadline and then gets a clear 429
production(50%, max wait 30s) and classbatch(25%, max wait 6s), leave the default pool at 25% with max wait 0s, threshold 50%, and click Save settings. A toast confirms and the same values come back from GET http://localhost:4000/fairness/settingsproductionin the new Workload class dropdown, and another withbatch. The Teams create and edit forms have the same dropdownRequest for gpt-4o-mini could not be admitted within 6.0s (queue_deadline_exceeded). Priority: batch, waited 6.0sandfairness_reasonplusqueue_wait_secondsin the error bodyfairness_reason: capacity_exhaustedx-litellm-max-queue-wait: 2and gets its 429 after 2s instead of 6sdisconnected_total: 1gpt-4o-miniat 100% saturation with reservations enforced, and per class the reserved rpm, in-window usage, queue depth, queued and admitted counts, average wait, and rejections split by capacity, queue full, deadline and disconnectLinear ticket
Resolves LIT-8648
Pre-Submission checklist
Please complete all items before asking a LiteLLM maintainer to review your PR
uv run pytest tests/test_litellm/<your_test_file>.py -v. Leave the suites (make test-unit-*,make test-unit) to CI: it finishes in ~15 minutes where a laptop takes an hour or more@greptileaito re-request a review after pushing changes)Delays in PR merge?
If you're seeing a delay in your PR being merged, ping the LiteLLM Team on Slack (#pr-review).
Screenshots / Proof of Fix
Setup shared by both runs: Postgres, real OpenAI calls, this config, master key
sk-1234Keys used by the load cases (created through
POST /key/generate,metadata.priorityset to the class name; the unclassified key has no metadata):Load script used in both runs (
$BATCH,$PRODhold the keys above):Before (974e4f1)
Fairness endpoints
curl -s -w '\nHTTP %{http_code}\n' localhost:4000/fairness/status -H 'Authorization: Bearer sk-1234'{"detail":"Not Found"}/HTTP 404curl -s -w '\nHTTP %{http_code}\n' -X PUT localhost:4000/fairness/settings -H 'Authorization: Bearer sk-1234' -H 'Content-Type: application/json' -d '{"enabled": true}'{"detail":"Not Found"}/HTTP 404Six batch plus two production requests on a 4 rpm model
Admin UI
After (207989e)
The curl cases below were rerun against a proxy started at 207989e. The only commit after it, 31119bd, changes the disable-mid-wait and paused-admission paths in the limiter and queue, which the curl legs above do not exercise; their behavior is covered by the regression tests named under CI state. The UI screenshots were taken at a0d2e7f; the commits after it touch only the limiter registration, config-load order, the queue store, the one-token reservation floor and the disable lifecycle, not the dashboard
Fairness endpoints
persisted: truemeans the row landed in the DB):{ "settings": { "enabled": true, "workload_classes": [ {"name": "production", "reserved_share": 0.5, "max_queue_wait_seconds": 30.0, "description": null}, {"name": "batch", "reserved_share": 0.25, "max_queue_wait_seconds": 6.0, "description": null} ], "default_reserved_share": 0.25, "default_max_queue_wait_seconds": 0.0, "saturation_threshold": 0.5, "saturation_check_cache_ttl": 60, "max_queue_depth_per_class": 100, "queue_poll_interval_seconds": 0.1 }, "persisted": true }Six batch plus two production requests on a 4 rpm model
curl -s localhost:4000/fairness/status -H 'Authorization: Bearer sk-1234'right after the run:{ "enabled": true, "limiter_active": true, "saturation_threshold": 0.5, "window_size_seconds": 60, "stats_window_seconds": 300, "models": [ { "model_group": "gpt-4o-mini", "rpm": 4, "tpm": null, "saturation": 1.0, "enforcing_reservations": true, "current_requests": 4, "current_tokens": 59, "classes": [ {"name": "production", "reserved_share": 0.5, "reserved_rpm": 2, "reserved_tpm": null, "current_requests": 2, "current_tokens": 29, "queue_depth": 0, "max_queue_wait_seconds": 30.0, "queued_total": 0, "admitted_after_wait_total": 0, "rejected_capacity_total": 0, "rejected_queue_full_total": 0, "rejected_deadline_total": 0, "disconnected_total": 0, "avg_queue_wait_seconds": 0.0}, {"name": "batch", "reserved_share": 0.25, "reserved_rpm": 1, "reserved_tpm": null, "current_requests": 2, "current_tokens": 30, "queue_depth": 0, "max_queue_wait_seconds": 6.0, "queued_total": 4, "admitted_after_wait_total": 0, "rejected_capacity_total": 0, "rejected_queue_full_total": 0, "rejected_deadline_total": 4, "disconnected_total": 0, "avg_queue_wait_seconds": 6.001119019749694}, {"name": "default", "reserved_share": 0.25, "reserved_rpm": 1, "reserved_tpm": null, "current_requests": 0, "current_tokens": 0, "queue_depth": 0, "max_queue_wait_seconds": 0.0, "queued_total": 0, "admitted_after_wait_total": 0, "rejected_capacity_total": 0, "rejected_queue_full_total": 0, "rejected_deadline_total": 0, "disconnected_total": 0, "avg_queue_wait_seconds": 0.0} ] } ] }Immediate rejection, per-request deadline override, client disconnect
Turning it off live
PUT /fairness/settingswith{"enabled": false}and repeat the six calls:bash 06_disable.sh # same hit() loop as above with the unclassified key, run before and after the PUTAdmin UI
gpt-4o-miniwith its saturation badge and one row per class with reserved rpm, in-window usage, queue depth, waits and the rejection breakdown from the run aboveproduction50% with max wait 30s andbatch25% with max wait 6s, click Save settings. A toast confirms, and after a reload the same values are still therebatch, save, reopen the key:batchis still selected. Teams create and edit have the same dropdownType
🆕 New Feature
Caveats (if any)
Severe
rpmortpmset: over-limit requests wait up to the class deadline instead of failing fastLITELLM_LICENSEis set (existing priority reservation behavior); without it every class gets the default weightMedium
x-litellm-max-queue-waitcan only shorten the wait; a longer value is clamped to the class deadlineLow
max_tokens, so a class with largemax_tokensvalues reserves more than it will usually spend until the response reconciles itdefault, and bothdefaultanddefault_poolare rejected as workload class names because the default pool's counter key uses the latterCI state
QueueReleasedwithout taking a reservation instead of sitting until its deadline, and a request admitted while the limiter was paused is markeddynamic_admission_bypassedon its request stash so the success callback never bills it even if fairness is re-enabled before it finishes. Both have regression tests, and reverting either fix makes its test failmcp-integration / Run testsfailed on 207989e ontest_call_toolandtest_sse_mcp_handler_mock, which also fail on main at 636eb4c (run 36181414493) and on the merge base; it passes on the current head and this branch does not touch MCP code or testsmake lintpass on itasync_post_call_failure_hook, a request that held a reservation never falls through to the legacy token increment after settlement, anddefault_poolis rejected as a workload class name alongsidedefault. Each has a regression test in the mapped test file that fails with the fix revertedAnalyze (python)(CodeQL) stops at its 2 GiB result-set limit inCWE-117/LogInjection.qlwith zero alerts, as on recent main pushes; it is not a required checkoauth(MCP OAuth happy path) stays in the waiting state; it does the same on recently merged main PRs and is not a required checkFinal Attestation
Link to Devin session: https://app.devin.ai/sessions/594d6f8da0b942c6a1a05db3a3974388
Open in Devin Desktop: https://app.devin.ai/desktop/session/594d6f8da0b942c6a1a05db3a3974388?variant=devin
Requested by: @yassin-berriai
Note
High Risk
Changes core request admission, token reservation, and rate-limit behavior for every model with RPM/TPM when fairness is enabled; misconfiguration or queue bugs could delay or reject production traffic cluster-wide.
Overview
Adds fairness under load for the proxy: admins configure workload classes (reserved RPM/TPM shares, queue deadlines, saturation threshold) via
GET/PUT /fairness/settingsandGET /fairness/status, persisted asfairness_settingsand synced across workers like otherlitellm_settings.When enabled,
apply_fairness_settingsmirrors classes intopriority_reservation, registers or pausesdynamic_rate_limiter_v3, and the limiter reserves estimated tokens at admission, settles/refunds on success or failure, and on capacity pressure queues over-limit traffic in a weighted per-modelFairQueueinstead of an immediate 429. Rejections exposefairness_reason, queue wait, and optionalx-litellm-max-queue-wait;ActiveRequestMiddlewarelets waiters abort on client disconnect.The dashboard gains a Fairness settings form and live status table; the v3 parallel limiter stash and several helpers are extended/refactored to support dynamic reservations and shared token accounting.
Reviewed by Cursor Bugbot for commit 511e689. Bugbot is set up for automated code reviews on this repo. Configure here.