llm-inference-optimizer / inference /naive_batching.py
Darkweb007's picture
Initial commit: llm-inference-optimizer
f99ebff
Raw
History Blame Contribute Delete
3.69 kB
"""
Naive Batching: process one request at a time, no concurrency.
This is the baseline — every LLM serving system starts here.
"""
import time
import torch
import numpy as np
from dataclasses import dataclass
from typing import List
from transformers import AutoTokenizer, AutoModelForCausalLM
@dataclass
class InferenceResult:
prompt: str
output: str
input_tokens: int
output_tokens: int
latency_ms: float
tokens_per_second: float
class NaiveBatchingEngine:
"""
Sequential inference: each request waits for the previous to complete.
Problems:
- GPU sits idle between requests
- No sharing of KV cache computation
- Latency scales linearly with queue depth
"""
def __init__(self, model_name: str, device: str = "auto"):
print(f"[NaiveBatching] Loading {model_name}...")
self.tokenizer = AutoTokenizer.from_pretrained(model_name)
self.tokenizer.pad_token = self.tokenizer.eos_token
self.model = AutoModelForCausalLM.from_pretrained(
model_name,
torch_dtype=torch.float16 if torch.cuda.is_available() else torch.float32,
device_map=device,
)
self.model.eval()
self.device = next(self.model.parameters()).device
print(f"[NaiveBatching] Model loaded on {self.device}")
@torch.no_grad()
def generate_single(self, prompt: str, max_new_tokens: int = 50) -> InferenceResult:
"""Generate for a single prompt, sequentially."""
inputs = self.tokenizer(prompt, return_tensors="pt").to(self.device)
input_len = inputs["input_ids"].shape[1]
start = time.perf_counter()
output_ids = self.model.generate(
**inputs,
max_new_tokens=max_new_tokens,
do_sample=False,
pad_token_id=self.tokenizer.eos_token_id,
)
elapsed_ms = (time.perf_counter() - start) * 1000
output_len = output_ids.shape[1] - input_len
output_text = self.tokenizer.decode(
output_ids[0][input_len:], skip_special_tokens=True
)
tps = (output_len / elapsed_ms) * 1000
return InferenceResult(
prompt=prompt,
output=output_text,
input_tokens=input_len,
output_tokens=output_len,
latency_ms=elapsed_ms,
tokens_per_second=tps,
)
def benchmark(
self, prompts: List[str], max_new_tokens: int = 50
) -> dict:
"""Run prompts sequentially and collect latency statistics."""
results = []
for i, prompt in enumerate(prompts):
result = self.generate_single(prompt, max_new_tokens)
results.append(result)
print(f" [{i+1}/{len(prompts)}] {result.latency_ms:.1f}ms, "
f"{result.tokens_per_second:.1f} tok/s")
latencies = [r.latency_ms for r in results]
tps_values = [r.tokens_per_second for r in results]
total_time = sum(latencies)
return {
"method": "naive_sequential",
"n_requests": len(prompts),
"total_time_ms": total_time,
"throughput_requests_per_sec": len(prompts) / (total_time / 1000),
"throughput_tokens_per_sec": sum(r.output_tokens for r in results) / (total_time / 1000),
"latency_p50_ms": float(np.percentile(latencies, 50)),
"latency_p95_ms": float(np.percentile(latencies, 95)),
"latency_p99_ms": float(np.percentile(latencies, 99)),
"latency_mean_ms": float(np.mean(latencies)),
"tokens_per_second_mean": float(np.mean(tps_values)),
"results": results,
}