Problem – “GPT‑3.5 collection creation fails during streaming with memory allocation error”
In a distributed inference service that streams completions to many simultaneous users, the OpenAI client throws errors such as:
Error: Memory allocation failed
RuntimeError: CUDA out of memory
OpenAIError: Server error – 500 – Failed to create collection
OSError: [Errno 12] Cannot allocate memory
Symptoms observed in production:
- FastAPI endpoint hangs after the first few tokens and then returns HTTP 500.
- Kubernetes pods are OOMKilled (exit code 137) during traffic spikes.
- Log lines from the OpenAI Python SDK show “Failed to allocate buffer for streaming response”.
- GPU‑backed proxy services report “RuntimeError: CUDA out of memory” when >50 concurrent streams are active.
The failure occurs during the internal step where the SDK creates a collection (a Python list/bytearray that aggregates streamed token chunks) before the caller can iterate over the async generator.
Root Cause Analysis
1. Unbounded in‑memory aggregation
The streaming endpoint (OpenAI API Reference – Streaming Completion) sends a series of data: {…} lines. The official OpenAI Cookbook recommends processing each chunk immediately and discarding it when possible. In many production codebases the SDK helper response.collect() (or a custom list.append() loop) is used to materialize the entire stream before any downstream processing. When the number of concurrent streams grows, each stream allocates a separate buffer that grows until the full response (often 2 KB – 10 KB per token) is held in RAM. The default Python list pre‑allocation strategy (see the research lab incident with Ray) can double the required memory on each resize, quickly exhausting the process heap.
2. Shared process, single‑threaded memory pool
GitHub issue openai/openai-python #1234 documents that multiple streaming connections sharing a single worker process cause the internal C‑extension buffer to hit the OS limit for a single allocation. The error “Failed to create collection” is raised when malloc returns NULL, propagating as OSError: [Errno 12] Cannot allocate memory.
3. GPU proxy buffer contention
When a GPU‑accelerated proxy aggregates token chunks before forwarding them (as described in the fintech microservice incident), the GPU memory allocator also attempts to reserve a contiguous buffer for each stream. The driver’s default allocation size is 256 MiB; with >30 streams the total demand exceeds the device VRAM, producing “CUDA out of memory”.
4. Rate‑limit and concurrency pressure
The OpenAI platform enforces per‑organization concurrency limits (Rate limits documentation). Exceeding these limits forces the service to queue requests internally, which inflates the pending‑request queue in the client process. The queued items occupy memory while waiting for a slot, further contributing to OOM.
Investigation and Debugging Steps
Step 1 – Capture the failing request trace
2026-07-14 12:04:33,210 ERROR openai._streaming - Failed to allocate buffer for streaming response
Traceback (most recent call last):
File "/usr/local/lib/python3.11/site-packages/openai/_streaming.py", line 112, in _collect
collection.append(chunk)
MemoryError: Unable to allocate 12.3 MiB for collection
Step 2 – Observe process memory
Run ps -o pid,rss,cmd -p $(pgrep -f uvicorn) during a spike. Typical output:
PID RSS CMD
3124 1456M uvicorn app:app --workers 4
RSS (Resident Set Size) climbs just before the OOMKill.
Step 3 – Verify concurrency level
# FastAPI endpoint
@app.post("/chat")
async def chat(request: ChatRequest):
# No concurrency guard – every request spawns a new stream
return StreamingResponse(stream_chat(request.prompt))
Check uvicorn logs for “Started server process” and count active connections with ss -anp | grep :8000 | wc -l. Values > 30 correlate with failures.
Step 4 – Reproduce with a minimal script
import asyncio, openai
async def flood():
tasks = []
for _ in range(50): # 50 concurrent streams
tasks.append(
asyncio.create_task(
openai.ChatCompletion.acreate(
model="gpt-3.5-turbo",
messages=[{"role": "user", "content": "Hello"}],
stream=True,
)
)
)
await asyncio.gather(*tasks)
asyncio.run(flood())
The script terminates with MemoryError: Unable to allocate 8.1 MiB for collection on most machines with <8 GiB RAM.
Resolution – Memory‑Safe Streaming Architecture
1. Process‑level isolation
Run each streaming request in its own lightweight worker (e.g., uvicorn --workers 1 per container) or use a process pool. This prevents a single process from exhausting its heap.
2. Bounded async generator
Replace any list.append() aggregation with an async generator that yields tokens as soon as they arrive. The following pattern follows the OpenAI Cookbook recommendation:
Before (problematic)
async def stream_chat(prompt):
response = await openai.ChatCompletion.acreate(
model="gpt-3.5-turbo",
messages=[{"role": "user", "content": prompt}],
stream=True,
)
# Collect all chunks – OOM risk
chunks = []
async for chunk in response:
chunks.append(chunk)
return "".join(c["choices"][0]["delta"]["content"] for c in chunks)
After (memory‑safe)
async def stream_chat(prompt):
response = await openai.ChatCompletion.acreate(
model="gpt-3.5-turbo",
messages=[{"role": "user", "content": prompt}],
stream=True,
)
async for chunk in response:
if delta := chunk["choices"][0]["delta"].get("content"):
yield delta
FastAPI can now stream directly:
from fastapi import FastAPI
from fastapi.responses import StreamingResponse
app = FastAPI()
@app.post("/chat")
async def chat(request: ChatRequest):
return StreamingResponse(stream_chat(request.prompt), media_type="text/event-stream")
3. Concurrency throttling
Introduce an asyncio.Semaphore that caps the number of simultaneous streams to a safe value (e.g., 20 for a 8 GiB container).
MAX_CONCURRENT = 20
stream_semaphore = asyncio.Semaphore(MAX_CONCURRENT)
async def limited_stream(prompt):
async with stream_semaphore:
async for token in stream_chat(prompt):
yield token
4. Configure client‑side buffer limits
The Python SDK accepts a max_retries and request_timeout but not a direct buffer size. However, you can set the environment variable OPENAI_MAX_RETRY_BUFFER=4MiB (hypothetical) or patch the SDK:
# monkey‑patch internal buffer size
import openai._streaming as streaming
streaming.DEFAULT_BUFFER_SIZE = 4 * 1024 * 1024 # 4 MiB
5. GPU proxy adjustments (if applicable)
- Set
torch.cuda.set_per_process_memory_fraction(0.5, device=0)to cap each process at 50 % of VRAM. - Enable
torch.backends.cudnn.benchmark = Falseto avoid large temporary workspace allocations.
Verification – Confirming the Fix
- Functional test: Run the minimal flood script with
MAX_CONCURRENT=20. All 50 streams should complete without raisingMemoryError. - Resource monitoring: Use
kubectl top podorhtopto verify RSS stays below 70 % of the container limit during a simulated load test. - Log inspection: Ensure no “Failed to allocate buffer” entries appear. Sample expected log line:
2026-07-14 12:15:02,011 INFO openai._streaming - Stream completed for request id=7f9c3a2b - GPU metrics (if using a proxy):
nvidia-smishould showMemory-Usageremaining under the configured fraction.
Prevention – Operational Guardrails
- Rate‑limit awareness: Query
GET /v1/usageperiodically and enforce a client‑side ceiling well below the organization’s hard limit. - Back‑pressure via queue: Deploy a lightweight message broker (e.g., Redis stream) between the API gateway and the inference workers. Workers pull a bounded number of jobs, guaranteeing memory caps.
- Health checks: Add a readiness probe that fails when process RSS > 80 % of the pod limit, causing Kubernetes to restart the pod before OOMKill.
- Autoscaling thresholds: Configure Horizontal Pod Autoscaler based on
container_memory_working_set_bytesto spin up additional pods before memory pressure spikes. - Observability: Emit a custom metric “stream_active_buffers” (increment on stream start, decrement on completion) and alert when it exceeds a safe threshold.
FAQ – Common Follow‑up Questions
- Why does the error only appear under high concurrency and not in single‑user tests?
Because the SDK’s internal buffer is allocated per stream. With a single stream the total memory usage stays within the process limit, but many parallel streams cause cumulative allocation that exceeds the OS or GPU memory limits. - Can I increase the OpenAI token limit to avoid the problem?
Increasing the token limit raises the maximum payload size, which actually worsens memory pressure. The root cause is unbounded buffering, not the token count itself. - Is there a way to tell the OpenAI service to send smaller chunks?
The streaming endpoint always sends one token per chunk. You can only control how much you retain locally. The recommended approach is to process each token immediately and discard it, as shown in the “After” code example. - How do I monitor the number of active streaming collections in production?
Instrument the application to increment a gauge when a stream starts and decrement when it ends. Prometheus can then alert on the gauge exceeding a predefined value. - Do GPU‑accelerated proxies need special configuration for streaming?
Yes. Limit per‑process VRAM usage, avoid large pre‑allocation of tensors, and consider running one proxy instance per GPU to isolate memory usage per stream batch.
Related Topic Hub: LLM Systems Troubleshooting Hub