Problem – TGI Node Out‑of‑Memory (OOM) During Large Batch Inference with Autoscaling Disabled
In a production Kubernetes deployment of Text Generation Inference (TGI), a batch job that aggregates thousands of text‑generation requests into a single inference call caused the pod to be killed with an OOM error. Autoscaling was deliberately disabled, so the node could not acquire additional memory resources, leading to repeated job failures.
Root Cause – Unbounded Batch Size Combined with Fixed Memory Limits
The core issue is a mismatch between the max_batch_size used by the client and the memory limits configured on the TGI pod:
- Batch aggregation logic concatenates incoming requests until a configurable threshold is reached. The threshold was set to
max_batch_size=1024tokens per request, but the client sentbatch_size=2048requests in a single call. - Pod memory limits were set to
requests: 8Gi,limits: 12Gi. The TGI container’s internal memory allocator (torch.cuda) does not enforce a hard cap; it allocates as needed until the cgroup limit is hit. - Autoscaling disabled (both HPA and cluster‑autoscaler) prevented the scheduler from provisioning a larger node when the pod exceeded its limit.
- The combination caused the Linux OOM killer to terminate the container, producing the following log snippet:
[2024-09-12T14:32:07.842Z] INFO tgi.server: Received batch request: size=2048, max_new_tokens=256 [2024-09-12T14:32:08.001Z] WARN torch.cuda: Allocating 13.2 GiB on device 0 (available: 12.0 GiB) [2024-09-12T14:32:08.015Z] ERROR kernel: Out of memory: Kill process 31245 (python) score 987 or sacrifice child [2024-09-12T14:32:08.016Z] FATAL tgi.server: Process killed due to OOMDebug – Investigation Steps
1. Verify Pod Resource Configuration
kubectl get pod tgi-0 -n inference -o yaml | grep -A4 resourcesOutput:
resources: limits: cpu: "8" memory: 12Gi requests: cpu: "4" memory: 8Gi2. Inspect TGI Server Logs for Batch Parameters
kubectl logs tgi-0 -n inference | grep "Received batch request"Shows multiple entries with
size=2048, confirming the client is sending a batch larger than the pod can handle.3. Capture Runtime Memory Usage
kubectl exec -it tgi-0 -n inference -- bash -c "cat /sys/fs/cgroup/memory/memory.usage_in_bytes"Typical peak usage:
13,215,874,560(≈12.3 GiB), exceeding the limit.4. Validate Autoscaling Configuration
kubectl get hpa -n inference tgi-hpa -o yamlResult:
spec.minReplicas: 3,spec.maxReplicas: 3(effectively disabled).5. Reproduce Locally with Controlled Batch Size
python - <<'PY' import torch from transformers import AutoModelForCausalLM, AutoTokenizer model = AutoModelForCausalLM.from_pretrained("bigscience/bloom-560m", device_map="auto") tokenizer = AutoTokenizer.from_pretrained("bigscience/bloom-560m") inputs = tokenizer(["Hello"]*2048, return_tensors="pt", padding=True).to('cuda') with torch.no_grad(): outputs = model.generate(**inputs, max_new_tokens=256) print("generated", len(outputs)) PYLocal run fails with
CUDA out of memory, confirming the batch size exceeds GPU memory.Solution – Align Batch Size with Pod Memory Limits and Enable Reactive Scaling
1. Enforce a Safe
max_batch_sizein the TGI ClientUpdate the client configuration to cap the batch size at a value that fits within the pod’s memory budget. For a 12 GiB limit on an A100 (40 GiB total), a safe batch size is ~
1024tokens per request.Before (client code)
batch_size = 2048After
batch_size = min(requested_batch, 1024) # enforce limit2. Adjust TGI Container Memory Requests/Limits
Increase the memory limit to provide a buffer for occasional spikes, and add a
memory.swaplimit to prevent silent swapping.Before (deployment snippet)
resources: requests: cpu: "4" memory: 8Gi limits: cpu: "8" memory: 12GiAfter
resources: requests: cpu: "4" memory: 12Gi limits: cpu: "8" memory: 16Gi # optional: prevent swapping oomScoreAdj: -10003. Enable Horizontal Pod Autoscaling (HPA) for Memory‑Based Scaling
Add an HPA that reacts to memory usage, allowing the system to spin up additional TGI pods when a single pod approaches its limit.
apiVersion: autoscaling/v2 kind: HorizontalPodAutoscaler metadata: name: tgi-hpa namespace: inference spec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: tgi-deployment minReplicas: 3 maxReplicas: 10 metrics: - type: Resource resource: name: memory target: type: Utilization averageUtilization: 704. Add Runtime Guardrails in TGI Server
Configure the TGI server to reject batches that would exceed a configurable memory budget.
# tgi_server.yaml max_batch_memory_gb: 10 # abort batches that would allocate >10 GiB max_batch_size: 10245. Deploy Updated Manifests
kubectl apply -f tgi-deployment.yaml kubectl apply -f tgi-hpa.yamlVerify – Confirm the Fix Prevents OOM
1. Load Test with Targeted Batch Size
hey -c 50 -n 5000 -m POST http://tgi-service.inference.svc.cluster.local/generate \ -d '{"inputs":"Hello world","parameters":{"max_new_tokens":256}}'Observe that the maximum observed memory stays below the limit:
kubectl top pod -n inference | grep tgi tgi-0 8Mi 10Gi tgi-1 7Mi 9.8Gi2. Check HPA Scaling Activity
kubectl get hpa -n inference tgi-hpaDuring peak load, the replica count should increase (e.g., from 3 to 6) and later scale back down.
3. Verify No OOM Events in System Logs
kubectl logs tgi-0 -n inference | grep -i "out of memory"Output should be empty.
Prevent – Operational Guardrails
- Monitoring: Export
container_memory_working_set_bytesand set an alert at 80 % of the limit. - Admission Control: Use a validating webhook that rejects Deployments where
max_batch_size * avg_input_length * 4 bytesexceedsmemory.requests. - Capacity Planning: Periodically run a synthetic batch that gradually increases size until memory usage plateaus; record the safe ceiling.
- Documentation: Encode the safe batch size in the client SDK as a constant derived from the deployment’s resource profile.
- Fail‑Fast Policy: Enable TGI’s
max_batch_memory_gbguardrail to return HTTP 429 instead of OOM when a request would exceed the budget.
Related Topic Hub: Model Serving Troubleshooting Hub
FAQ
- Why does the OOM happen only when autoscaling is disabled?
Without autoscaling, the scheduler cannot place additional pods on larger nodes. The single pod must absorb the entire batch, exceeding its cgroup memory limit and triggering the OOM killer. - Can I keep autoscaling disabled and still avoid OOM?
Yes, by enforcing a batch size that fits within the pod’s memory limits and by adding server‑side guards (e.g.,max_batch_memory_gb) to reject oversized batches. - How do I determine a safe
max_batch_sizefor a given GPU?
Run a local benchmark that increments the batch size untiltorch.cuda.memory_allocated()approaches 85 % of the GPU’s total memory, then use that value (rounded down) as the production limit. - What metric should I alert on to catch future memory pressure early?
Alert oncontainer_memory_working_set_bytes / container_spec_memory_limit_bytes > 0.75for the TGI pods, and on the HPA’sdesiredReplicas > maxReplicascondition. - Is there a way to make TGI automatically split large batches?
TGI itself does not split batches, but you can wrap the client call with a helper that partitions the input list into chunks ofmax_batch_sizeand issues multiple parallel requests.
- Monitoring: Export