Batch Inference for Throughput

AILLMMachine LearningBackend
Share on LinkedIn

Running 50,000 product descriptions through an LLM for classification at $0.003 per call in real-time costs $150 and ties up rate limits your support bot needs during business hours. The same job via batch API costs $75, completes overnight, and never competes with production traffic. Batch inference is boring infrastructure — and it's how teams afford LLM features at scale.

Provider batch APIs

OpenAI and Anthropic both offer async batch endpoints: upload a JSONL file of requests, get results hours later at reduced price.

{"custom_id": "prod-001", "method": "POST", "url": "/v1/chat/completions", "body": {"model": "gpt-4o-mini", "messages": [{"role": "user", "content": "Classify: ..."}]}}
{"custom_id": "prod-002", "method": "POST", "url": "/v1/chat/completions", "body": {"model": "gpt-4o-mini", "messages": [{"role": "user", "content": "Classify: ..."}]}}
async def submit_batch(requests: list[BatchRequest]) -> str:
    file_id = await client.files.create(
        file=to_jsonl(requests),
        purpose="batch",
    )
    batch = await client.batches.create(
        input_file_id=file_id,
        endpoint="/v1/chat/completions",
        completion_window="24h",
    )
    return batch.id

Poll or webhook on completion. Map results back via custom_id. Handle partial failures — batches can succeed 98% with individual line errors.

Best for: embeddings at ingest time, nightly report generation, eval dataset runs, reprocessing after prompt changes.

Self-hosted continuous batching

When provider batch isn't an option (private models, data residency, cost at volume), run vLLM or TGI with continuous batching:

python -m vllm.entrypoints.openai.api_server \
  --model meta-llama/Llama-3.1-8B-Instruct \
  --max-num-seqs 256 \
  --gpu-memory-utilization 0.90

Continuous batching (also called in-flight batching) adds new requests to the GPU batch as others finish — unlike static batching where you wait for N requests before starting.

Throughput gains come from:

Queue architecture for batch workloads

Separate batch from real-time at the queue level:

                    ┌──────────────┐
Producers ─────────→│  SQS/Kafka   │
                    └──────┬───────┘
                           │
              ┌────────────┼────────────┐
              ▼                         ▼
     [Real-time workers]        [Batch aggregator]
     latency SLO: 2s            flush every 30s OR 64 items
              │                         │
              ▼                         ▼
     [vLLM priority queue]      [vLLM batch queue]

Batch aggregator collects requests until size or time threshold, submits as a group. Real-time requests never wait behind batch jobs.

Sizing and backpressure

Monitor:

Apply backpressure when queue depth exceeds threshold:

if queue.depth > MAX_DEPTH:
    raise ServiceUnavailable(retry_after=30)

For batch jobs, delay is acceptable. For real-time, shed load early.

Chunking large batch jobs

A 500K-row job shouldn't be one batch file:

for chunk in chunks(all_rows, size=5000):
    batch_id = await submit_batch(chunk)
    await db.save_checkpoint(job_id, batch_id, chunk.ids)

Cost comparison framework

Workload Real-time cost Batch cost Latency tradeoff
100K classifications/day $300/day $150/day 24h vs 2s
Embedding 1M docs (one-time) $2000 $1000 + queue time Hours vs days
Nightly summaries Blocks prod quota Isolated Acceptable

Run the math before building real-time pipelines for inherently async work.

OpenAI Batch API workflow

Complete batch processing pipeline:

import openai, json, time

# 1. Prepare JSONL input file
requests = [
    {"custom_id": f"req-{i}", "method": "POST",
     "url": "/v1/chat/completions",
     "body": {"model": "gpt-4o-mini", "messages": [{"role": "user", "content": q}]}}
    for i, q in enumerate(queries)
]
with open("batch_input.jsonl", "w") as f:
    for req in requests:
        f.write(json.dumps(req) + "\n")

# 2. Upload and create batch
file = openai.files.create(file=open("batch_input.jsonl", "rb"), purpose="batch")
batch = openai.batches.create(input_file_id=file.id, endpoint="/v1/chat/completions", completion_window="24h")

# 3. Poll until complete
while batch.status not in ("completed", "failed"):
    time.sleep(60)
    batch = openai.batches.retrieve(batch.id)

# 4. Download results
results = openai.files.content(batch.output_file_id)

50% cost discount vs real-time. 24-hour completion window — not for latency-sensitive work.

Self-hosted batch inference with vLLM

For on-prem or cost control at scale:

from vllm import LLM, SamplingParams

llm = LLM(model="meta-llama/Llama-3.1-8B", max_num_seqs=256)
prompts = load_prompts("batch_job.csv")  # 100k prompts

sampling = SamplingParams(temperature=0, max_tokens=512)
outputs = llm.generate(prompts, sampling)  # continuous batching internally

save_results(outputs, "batch_output.parquet")

vLLM continuous batching processes requests as they complete — no fixed batch size. Throughput scales with GPU count linearly.

Batch vs real-time decision matrix

Factor Batch Real-time
Latency tolerance Hours acceptable Seconds required
Volume >10k requests/day Any
Cost sensitivity High (50% discount) Lower priority
Retry complexity Simple (resubmit batch) Complex (streaming)
User waiting No Yes

Default to batch for: nightly summaries, bulk classification, embedding generation, eval runs. Default to real-time for: chat, search, agent tools.

Failure modes

Production checklist

Resources

Frequently asked questions

When should I use batch inference instead of real-time API calls?

Use batch for workloads with latency tolerance over 15 minutes to 24 hours: overnight summarization, bulk classification, embedding generation, eval runs, and content moderation backlogs. Interactive chat and user-facing agents need real-time endpoints.

How much cheaper is batch inference?

Provider batch APIs (OpenAI, Anthropic) typically offer 50% off standard rates with 24-hour completion windows. Self-hosted continuous batching improves GPU utilization 3–5x over static batching but requires you to operate inference servers.

What batch size should I target on self-hosted models?

Let the inference engine decide. vLLM and TensorRT-LLM use continuous batching that dynamically adds/removes requests. Target GPU memory utilization of 85–90% and tune max_num_seqs based on your average sequence length — shorter prompts allow larger batches.

Hiring a senior Android / Flutter engineer?

I architect and ship production mobile software — Kotlin, Jetpack Compose, Flutter — for robotics, EV infrastructure, fintech, and real-time systems. Open to remote roles in Europe and the US.

Get in touch →