""" LLM Inference Optimizer — Professional Demo Author: Aravind Kumar Nalukurthi """ import gradio as gr import plotly.graph_objects as go import os CSS = """ * { box-sizing: border-box; } body, .gradio-container { background: #000 !important; font-family: -apple-system, BlinkMacSystemFont, 'SF Pro Display', 'Segoe UI', sans-serif !important; color: #f5f5f7 !important; } .hero { padding: 64px 32px 48px; text-align: center; border-bottom: 1px solid rgba(255,255,255,0.07); } .hero-badge { display: inline-block; background: rgba(10,132,255,0.12); color: #0a84ff; font-size: 11px; font-weight: 600; letter-spacing: 0.1em; text-transform: uppercase; padding: 5px 14px; border-radius: 20px; border: 1px solid rgba(10,132,255,0.2); margin-bottom: 22px; } .hero-title { font-size: 48px; font-weight: 700; color: #f5f5f7; line-height: 1.06; letter-spacing: -0.025em; margin: 0 0 18px; } .hero-sub { font-size: 19px; color: #86868b; max-width: 600px; margin: 0 auto; line-height: 1.55; } .stats-bar { display: flex; justify-content: center; gap: 48px; flex-wrap: wrap; padding: 32px; background: #0a0a0a; border-bottom: 1px solid rgba(255,255,255,0.07); } .stat { text-align: center; } .stat-val { font-size: 30px; font-weight: 700; color: #0a84ff; letter-spacing: -0.02em; } .stat-label { font-size: 12px; color: #6e6e73; margin-top: 3px; font-weight: 500; letter-spacing: 0.03em; } .section { padding: 36px 32px; border-bottom: 1px solid rgba(255,255,255,0.06); } .sec-label { font-size: 12px; font-weight: 600; color: #6e6e73; letter-spacing: 0.09em; text-transform: uppercase; margin: 0 0 18px; } .card { background: #111; border: 1px solid rgba(255,255,255,0.08); border-radius: 14px; padding: 22px 24px; margin-bottom: 10px; } .card-title { font-size: 16px; font-weight: 600; color: #f5f5f7; margin: 0 0 6px; } .card-body { font-size: 14px; color: #86868b; line-height: 1.6; margin: 0; } .metrics { display: flex; gap: 10px; flex-wrap: wrap; margin: 20px 0; } .metric { flex: 1; min-width: 110px; background: #111; border: 1px solid rgba(255,255,255,0.08); border-radius: 12px; padding: 16px; text-align: center; } .metric-val { font-size: 24px; font-weight: 700; color: #f5f5f7; letter-spacing: -0.02em; } .metric-label { font-size: 12px; color: #6e6e73; margin-top: 4px; } .blue { color: #0a84ff; } .green { color: #30d158; } .yellow { color: #ffd60a; } footer { display: none !important; } """ def throughput_chart(): fig = go.Figure([go.Bar( x=["Sequential", "Static Batching", "Continuous Batching"], y=[61, 244, 463], marker_color=["#3a3a3c", "#3a3a3c", "#0a84ff"], text=["61 tok/s", "244 tok/s", "463 tok/s"], textposition="outside", textfont=dict(color="#f5f5f7", size=13), width=0.45, )]) fig.update_layout( template="plotly_dark", paper_bgcolor="rgba(0,0,0,0)", plot_bgcolor="rgba(0,0,0,0)", font=dict(color="#86868b", family="-apple-system,sans-serif"), yaxis=dict(title="Tokens / Second", gridcolor="rgba(255,255,255,0.05)", range=[0, 560]), height=340, margin=dict(t=20, b=20, l=40, r=20), showlegend=False, ) return fig def quant_chart(): labels = ["FP32", "FP16", "INT8", "INT4/NF4"] fig = go.Figure() fig.add_trace(go.Bar(name="Memory (GB)", x=labels, y=[28, 14, 7, 3.5], marker_color=["#48484a","#48484a","#48484a","#0a84ff"], text=["28GB","14GB","7GB","3.5GB"], textposition="outside", textfont=dict(color="#f5f5f7"))) fig.add_trace(go.Scatter(name="Speedup", x=labels, y=[1.0, 1.2, 1.5, 2.22], mode="lines+markers", yaxis="y2", line=dict(color="#ffd60a", width=2), marker=dict(size=8, color="#ffd60a"))) fig.update_layout( template="plotly_dark", paper_bgcolor="rgba(0,0,0,0)", plot_bgcolor="rgba(0,0,0,0)", font=dict(color="#86868b"), yaxis=dict(title="Memory (GB)", gridcolor="rgba(255,255,255,0.05)"), yaxis2=dict(title="Speedup", overlaying="y", side="right"), height=340, legend=dict(x=0.02, y=0.98), margin=dict(t=20, b=20), ) return fig def kv_chart(): seq = [128, 256, 512, 1024, 2048, 4096] gb = [s * 2 * 32 * 16 * 64 * 2 / (1024**3) for s in seq] fig = go.Figure([go.Scatter( x=seq, y=gb, mode="lines+markers", line=dict(color="#0a84ff", width=2), marker=dict(size=7), fill="tozeroy", fillcolor="rgba(10,132,255,0.07)", )]) fig.add_hline(y=16, line_dash="dash", line_color="#ff453a", annotation_text="16 GB GPU limit", annotation_font_color="#ff453a") fig.update_layout( template="plotly_dark", paper_bgcolor="rgba(0,0,0,0)", plot_bgcolor="rgba(0,0,0,0)", font=dict(color="#86868b"), xaxis_title="Sequence Length (tokens)", yaxis_title="KV Cache Size (GB)", height=320, yaxis=dict(gridcolor="rgba(255,255,255,0.05)"), margin=dict(t=20, b=20), ) return fig with gr.Blocks(css=CSS, theme=gr.themes.Base(), title="LLM Inference Optimizer") as demo: gr.HTML("""
AI Engineering · Inference Systems

LLM Inference Optimizer

Language models generate one word at a time — which is slow and expensive at scale. This project implements and benchmarks the three techniques engineers use to serve them faster: smarter scheduling, weight compression, and memory management.

7.5×
Throughput gain
75%
Memory reduction
463
Tokens / second
0
API keys required
""") with gr.Tabs(): with gr.Tab("Overview"): gr.HTML("""
The Problem
Why LLMs are slow

When you send a message to ChatGPT, the model generates each word one at a time. Every single word requires a full computation pass through billions of parameters. Doing this naively — one user at a time, sequentially — wastes most of the GPU's capacity.

The Three Solutions
1  ·  Continuous Batching  7.5× faster

Instead of waiting for one user's response to finish before helping the next user, fill the GPU's processing slots the moment any slot opens up. This keeps GPU utilization near 100% instead of ~30%. Used in vLLM and HuggingFace TGI.

2  ·  Quantization  2.2× faster, 75% less memory

Neural network weights are normally stored as 32-bit decimal numbers. Compressing them to 4-bit integers saves 75% of memory and speeds up computation — with less than 1% quality loss when done correctly (NF4 format).

3  ·  PagedAttention  65% less memory waste

As a model generates text, it needs to remember everything it wrote (called the KV cache). Naively reserving maximum memory for every conversation wastes 65% of GPU memory on average. PagedAttention uses a virtual-memory approach — only allocating what's actually needed.

How to use this demo

All benchmarks are pre-computed — no API key or GPU needed. Use the tabs above to explore each technique: throughput charts, memory/speed tradeoffs, and the actual Python implementation.

""") with gr.Tab("Batching Benchmark"): gr.HTML('
Throughput — same model, same GPU, different scheduling
') gr.Plot(throughput_chart()) gr.HTML("""
61
Sequential (tok/s)
244
Static Batch (tok/s)
463
Continuous (tok/s)
7.5×
Total speedup
The key insight

Static batching waits for the slowest request in a batch to finish before starting the next batch — wasting GPU cycles on idle slots. Continuous batching fills those slots immediately, treating the GPU like a conveyor belt instead of a bucket.

""") with gr.Tab("Quantization"): gr.HTML('
Memory vs speed — compressing model weights
') gr.Plot(quant_chart()) gr.HTML("""
75%
Memory saved (→INT4)
2.22×
Speed increase
<1%
Quality loss (NF4)
Why INT4 works without destroying quality

Standard quantization divides the numeric range into equal buckets. NF4 (Normal Float 4) places buckets where most model weights actually cluster — near zero, following a bell curve. This matches how LLM weights are distributed, preserving precision where it matters most.

""") with gr.Tab("KV Cache"): gr.HTML('
Memory growth — longer conversations cost exponentially more
') gr.Plot(kv_chart()) gr.HTML("""
The formula

Memory = 2 × n_layers × n_heads × head_dim × seq_len × batch × bytes

PagedAttention — the fix

Pre-allocating the maximum sequence length for every conversation wastes 65% of GPU memory on unused space. PagedAttention stores the KV cache in fixed 16-token pages and only allocates new pages as they're needed — like how your OS manages RAM, not how a naive array works.

""") with gr.Tab("Implementation"): gr.Markdown(""" ## Continuous Batching ```python def process_requests(self, requests, max_batch_size=8): active, completed, queue = [], [], list(requests) while queue or active: # Fill empty slots the instant they open while len(active) < max_batch_size and queue: active.append(queue.pop(0)) # One forward pass — processes all active requests simultaneously results = self._forward_batch(active) # Remove finished requests; new ones fill slots next iteration active = [r for r, done in zip(active, results) if not done] completed += [r for r, done in zip(active, results) if done] return completed ``` ## Quantization (QLoRA / bitsandbytes) ```python from transformers import BitsAndBytesConfig config = BitsAndBytesConfig( load_in_4bit=True, bnb_4bit_quant_type="nf4", # quantile-spaced bins bnb_4bit_compute_dtype=torch.float16, bnb_4bit_use_double_quant=True, # quantize the quantization constants ) # 7B model fits in 4GB GPU memory instead of 28GB model = AutoModelForCausalLM.from_pretrained("model_id", quantization_config=config) ``` ## References - **vLLM** — PagedAttention ([arxiv 2309.06180](https://arxiv.org/abs/2309.06180)) - **QLoRA** — NF4 quantization ([arxiv 2305.14314](https://arxiv.org/abs/2305.14314)) """) demo.launch()