-
Notifications
You must be signed in to change notification settings - Fork 4.8k
Fix memory leak in AsyncCompletions.parse() with dynamically created models #2148
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 6 commits
f1e3865
d595725
3e19420
bcb75a8
35376a5
abcb863
36ed579
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let’s remove this file too—the test doesn’t seem to work. Plus, memory leak tests in pytest are super flaky: memory can grow because of pytest itself. So we can also consider not adding a test at all
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Makes sense, removed |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,50 @@ | ||
| import gc | ||
| from typing import List | ||
|
|
||
| import pytest | ||
| from pydantic import Field, create_model | ||
|
|
||
| from openai.lib._parsing import type_to_response_format_param | ||
| from openai.lib._parsing._completions import _schema_cache | ||
|
|
||
|
|
||
| @pytest.mark.asyncio | ||
| async def test_async_completions_parse_memory() -> None: | ||
| """Test if AsyncCompletions.parse() doesn't leak memory with dynamic models""" | ||
| # Create a base step model | ||
| StepModel = create_model( | ||
| "Step", | ||
| explanation=(str, Field()), | ||
| output=(str, Field()), | ||
| ) | ||
|
|
||
| # Clear the cache before testing | ||
| _schema_cache.clear() | ||
|
|
||
| # Simulate the issue by creating multiple models and making calls | ||
| models: list[type] = [] | ||
| for i in range(10): | ||
| # Create a new dynamic model each time | ||
| new_model = create_model( | ||
| f"MathResponse{i}", | ||
| steps=(List[StepModel], Field()), # type: ignore[valid-type] | ||
| final_answer=(str, Field()), | ||
| ) | ||
| models.append(new_model) | ||
|
|
||
| # Convert to response format and check if it's in the cache | ||
| type_to_response_format_param(new_model) | ||
| assert new_model in _schema_cache | ||
|
|
||
| # Record cache size with all models referenced | ||
| cache_size_with_references = len(_schema_cache) | ||
|
|
||
| # Let the models go out of scope and trigger garbage collection | ||
| del models | ||
| gc.collect() | ||
|
|
||
| # After garbage collection, the cache should be significantly reduced | ||
| cache_size_after_gc = len(_schema_cache) | ||
| assert cache_size_after_gc < cache_size_with_references | ||
| # The cache size should be close to the initial size (with some tolerance) | ||
| assert cache_size_after_gc < cache_size_with_references / 2 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's consider adding this in a separate PR, and in this one, only resolve the memory leak
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good call, removed