fix(sdk): stop serializing concurrent LLM calls through modify_params lock - #4447
Draft
santhiprakash wants to merge 1 commit into
Draft
fix(sdk): stop serializing concurrent LLM calls through modify_params lock#4447santhiprakash wants to merge 1 commit into
santhiprakash wants to merge 1 commit into
Conversation
… lock The class-level threading.Lock guarding litellm.modify_params was held for the entire duration of each LLM transport call. Because it was a ClassVar, the lock was shared across all LLM instances in the process, serializing all concurrent sync conversations. With local models (vLLM, Ollama, llama.cpp) where one call takes 30-120s, N parallel conversations took N×30-120s instead of 30-120s — the GPU sat idle while other conversations waited on the lock. litellm.modify_params is a process-wide global that LiteLLM reads synchronously during request transformation (before the HTTP call), so the lock only needs to guard the set/restore of that global — not the full HTTP round-trip. Replace the hold-for-full-duration lock with a reference-counted set/restore: the lock is held only for the brief counter update and global set/restore, then released so concurrent calls overlap. When the last in-flight call finishes, the global is restored to its pre-call value. The async path is simplified correspondingly: the previous run_in_executor / shield / cancellation-callback machinery is no longer needed because the lock is held for microseconds, not the full call duration. Race note: if two concurrent calls request different modify_params values, the last entrant wins the global during transformation. This is inherent to LiteLLM's process-wide global and cannot be solved without an upstream LiteLLM change; in practice concurrent calls in the same process almost always share the same value, and local models (where the serialization hurts most) use modify_params=False. Fixes #16459.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
HUMAN:
AGENT:
Why
A
ClassVar[threading.Lock]inopenhands/sdk/llm/llm.pywas held for the entire duration of each LLM transport call to guard the process-widelitellm.modify_paramsboolean. Because it was aClassVar, the lock was shared across allLLMinstances in the process, serializing all concurrent sync conversations. With local models (vLLM, Ollama, llama.cpp) where a single call takes 30-120s, 3 parallel conversations took 90-360s instead of 30-120s — the GPU sat idle while other conversations waited on the lock.litellm.modify_paramsis read by LiteLLM synchronously during request transformation (before the HTTP call) — inprompt_templates/factory.py,utils.py, and provider-specific transformation modules. Once the HTTP request is sent, the flag is no longer consulted. The lock therefore only needs to guard the set/restore of the global, not the full HTTP round-trip.Summary
_alitellm_modify_params_ctx): the previousrun_in_executor/asyncio.shield/ cancellation done-callback machinery (added in fix: don't freeze agent-server's event loop while waiting on litellm's global modify_params lock #4012 to avoid event-loop freezes) is no longer needed because the lock is no longer held across theawait. A plain blockingwithis safe when the lock is held for microseconds._litellm_modify_params_lock_executorThreadPoolExecutor.Race note: if two concurrent calls request different
modify_paramsvalues, the last entrant wins the global during transformation. This is inherent to LiteLLM's process-wide global and cannot be solved without an upstream LiteLLM change. In practice, concurrent calls in the same process almost always share the samemodify_paramsvalue, and local models (where the serialization hurts most) usemodify_params=False.Issue Number
Fixes #16459.
How to Test
Verification results
Key new test:
test_litellm_modify_params_ctx_concurrent_overlapTwo concurrent sync calls each sleep for 0.3s inside the context manager. If serialized (old behavior), total wall time ≈ 0.6s. With the fix, both overlap → total ≈ 0.3s. The test asserts
elapsed < 2 × call_durationto verify non-serialization.Video/Screenshots
N/A — internal concurrency fix, no UI change.
Type
Notes
release-note-requiredlabel on the issue is about API breakage detection. This PR does not change any public API —_litellm_modify_params_ctxand_alitellm_modify_params_ctxare private methods. The ClassVar_litellm_modify_params_lock_executoris removed, but it was also private.run_in_executor. This PR supersedes that approach by eliminating the long-held lock entirely, making therun_in_executorworkaround unnecessary.