Spaces:
Sleeping
Sleeping
Commit ·
db40dfa
1
Parent(s): d0962ae
redesign: professional UI + fix HF color metadata
Browse files
app.py
CHANGED
|
@@ -1,529 +1,246 @@
|
|
| 1 |
"""
|
| 2 |
-
LLM Inference Optimizer —
|
| 3 |
-
=========================================================
|
| 4 |
-
Demonstrates the engineering tradeoffs behind modern LLM serving:
|
| 5 |
-
- Naive sequential inference (baseline)
|
| 6 |
-
- Continuous batching (the vLLM innovation)
|
| 7 |
-
- INT8 / INT4 quantization
|
| 8 |
-
- KV cache memory analysis and PagedAttention
|
| 9 |
-
|
| 10 |
Author: Aravind Kumar Nalukurthi
|
| 11 |
-
GitHub: https://github.com/data-geek-astronomy/llm-inference-optimizer
|
| 12 |
"""
|
| 13 |
|
| 14 |
import gradio as gr
|
| 15 |
-
import torch
|
| 16 |
-
import json
|
| 17 |
-
import time
|
| 18 |
import plotly.graph_objects as go
|
| 19 |
-
import plotly.express as px
|
| 20 |
-
from plotly.subplots import make_subplots
|
| 21 |
-
import numpy as np
|
| 22 |
import os
|
| 23 |
|
| 24 |
-
# Lazy-load engines only when GPU is available
|
| 25 |
-
LIVE_MODE = torch.cuda.is_available() and os.getenv("ENABLE_LIVE_BENCHMARK", "0") == "1"
|
| 26 |
-
MODEL_NAME = os.getenv("MODEL_NAME", "gpt2")
|
| 27 |
-
|
| 28 |
-
# Pre-computed benchmark data (always available as fallback)
|
| 29 |
-
PRECOMPUTED = {
|
| 30 |
-
"batching_comparison": {
|
| 31 |
-
"methods": ["Naive Sequential", "Static Batch (8)", "Continuous Batch (8)"],
|
| 32 |
-
"throughput_rps": [1.2, 4.8, 9.1],
|
| 33 |
-
"throughput_tps": [61, 244, 463],
|
| 34 |
-
"latency_p50": [812, 203, 109],
|
| 35 |
-
"latency_p95": [1041, 261, 187],
|
| 36 |
-
"latency_p99": [1189, 298, 251],
|
| 37 |
-
"latency_mean": [856, 214, 118],
|
| 38 |
-
"colors": ["#ef4444", "#f59e0b", "#22c55e"],
|
| 39 |
-
"annotations": [
|
| 40 |
-
"Baseline: GPU idles between requests",
|
| 41 |
-
"Better: batches requests but waits for slowest",
|
| 42 |
-
"Best: slots filled continuously, no idle GPU",
|
| 43 |
-
],
|
| 44 |
-
},
|
| 45 |
-
"quantization_comparison": {
|
| 46 |
-
"configs": ["FP16 (14.0 GB)", "INT8 (7.0 GB)", "INT4 NF4 (3.5 GB)"],
|
| 47 |
-
"memory_gb": [14.0, 7.0, 3.5],
|
| 48 |
-
"throughput_tps": [89, 134, 198],
|
| 49 |
-
"latency_p50": [224, 149, 101],
|
| 50 |
-
"perplexity": [11.2, 11.6, 12.4],
|
| 51 |
-
"colors": ["#6366f1", "#f59e0b", "#22c55e"],
|
| 52 |
-
"speedup": [1.0, 1.51, 2.22],
|
| 53 |
-
"memory_reduction": ["0%", "50%", "75%"],
|
| 54 |
-
},
|
| 55 |
-
"kv_cache": {
|
| 56 |
-
"seq_lengths": [128, 256, 512, 1024, 2048, 4096, 8192],
|
| 57 |
-
"model_weights_gb": 14.0,
|
| 58 |
-
"kv_batch1_gb": [0.13, 0.25, 0.5, 1.0, 2.0, 4.0, 8.0],
|
| 59 |
-
"kv_batch4_gb": [0.5, 1.0, 2.0, 4.0, 8.0, 16.0, 32.0],
|
| 60 |
-
"kv_batch8_gb": [1.0, 2.0, 4.0, 8.0, 16.0, 32.0, 64.0],
|
| 61 |
-
"t4_limit_gb": 16.0,
|
| 62 |
-
},
|
| 63 |
-
}
|
| 64 |
-
|
| 65 |
CSS = """
|
| 66 |
-
|
| 67 |
-
.
|
| 68 |
-
background:
|
| 69 |
-
|
| 70 |
-
|
| 71 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 72 |
footer { display: none !important; }
|
| 73 |
"""
|
| 74 |
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
fig = make_subplots(
|
| 82 |
-
rows=1, cols=2,
|
| 83 |
-
subplot_titles=("Throughput (tokens/sec) ↑ higher is better",
|
| 84 |
-
"Latency P50/P95/P99 (ms) ↓ lower is better"),
|
| 85 |
-
horizontal_spacing=0.12,
|
| 86 |
-
)
|
| 87 |
-
|
| 88 |
-
fig.add_trace(go.Bar(
|
| 89 |
-
x=d["methods"], y=d["throughput_tps"],
|
| 90 |
-
marker_color=d["colors"], showlegend=False,
|
| 91 |
-
text=[f"{v} tok/s" for v in d["throughput_tps"]],
|
| 92 |
textposition="outside",
|
| 93 |
-
|
| 94 |
-
|
| 95 |
-
|
| 96 |
-
("P50", "latency_p50", "#22c55e"),
|
| 97 |
-
("P95", "latency_p95", "#f59e0b"),
|
| 98 |
-
("P99", "latency_p99", "#ef4444"),
|
| 99 |
-
]:
|
| 100 |
-
fig.add_trace(go.Bar(
|
| 101 |
-
name=label, x=d["methods"], y=d[key],
|
| 102 |
-
marker_color=color,
|
| 103 |
-
text=[f"{v}ms" for v in d[key]],
|
| 104 |
-
textposition="outside",
|
| 105 |
-
), row=1, col=2)
|
| 106 |
-
|
| 107 |
fig.update_layout(
|
| 108 |
-
template="plotly_dark",
|
| 109 |
-
|
| 110 |
-
|
| 111 |
-
|
| 112 |
-
height=420,
|
| 113 |
-
margin=dict(t=60, b=20, l=20, r=20),
|
| 114 |
-
legend=dict(orientation="h", y=-0.15),
|
| 115 |
)
|
| 116 |
return fig
|
| 117 |
|
| 118 |
-
|
| 119 |
-
|
| 120 |
-
|
| 121 |
-
fig
|
| 122 |
-
|
| 123 |
-
|
| 124 |
-
|
| 125 |
-
|
| 126 |
-
|
| 127 |
-
),
|
| 128 |
-
horizontal_spacing=0.1,
|
| 129 |
-
)
|
| 130 |
-
|
| 131 |
-
fig.add_trace(go.Bar(
|
| 132 |
-
x=d["configs"], y=d["memory_gb"], marker_color=d["colors"],
|
| 133 |
-
showlegend=False, text=[f"{v}GB" for v in d["memory_gb"]],
|
| 134 |
-
textposition="outside",
|
| 135 |
-
), row=1, col=1)
|
| 136 |
-
|
| 137 |
-
fig.add_trace(go.Bar(
|
| 138 |
-
x=d["configs"], y=d["throughput_tps"], marker_color=d["colors"],
|
| 139 |
-
showlegend=False, text=[f"{v} tok/s" for v in d["throughput_tps"]],
|
| 140 |
-
textposition="outside",
|
| 141 |
-
), row=1, col=2)
|
| 142 |
-
|
| 143 |
-
fig.add_trace(go.Bar(
|
| 144 |
-
x=d["configs"], y=d["perplexity"], marker_color=d["colors"],
|
| 145 |
-
showlegend=False, text=[f"{v:.1f}" for v in d["perplexity"]],
|
| 146 |
-
textposition="outside",
|
| 147 |
-
), row=1, col=3)
|
| 148 |
-
|
| 149 |
fig.update_layout(
|
| 150 |
-
template="plotly_dark",
|
| 151 |
-
|
| 152 |
-
|
| 153 |
-
|
| 154 |
-
height=
|
| 155 |
-
margin=dict(t=60, b=20, l=20, r=20),
|
| 156 |
)
|
| 157 |
return fig
|
| 158 |
|
| 159 |
-
|
| 160 |
-
|
| 161 |
-
|
| 162 |
-
fig = go.Figure(
|
| 163 |
-
|
| 164 |
-
|
| 165 |
-
(
|
| 166 |
-
|
| 167 |
-
|
| 168 |
-
|
| 169 |
-
|
| 170 |
-
total = [d["model_weights_gb"] + v for v in d[key]]
|
| 171 |
-
fig.add_trace(go.Scatter(
|
| 172 |
-
x=d["seq_lengths"], y=total, name=label,
|
| 173 |
-
mode="lines+markers", line=dict(color=color, width=2.5),
|
| 174 |
-
marker=dict(size=7),
|
| 175 |
-
))
|
| 176 |
-
|
| 177 |
-
# T4 VRAM limit
|
| 178 |
-
fig.add_hline(
|
| 179 |
-
y=d["t4_limit_gb"], line_dash="dot",
|
| 180 |
-
line_color="#a78bfa", line_width=2,
|
| 181 |
-
annotation_text="T4 VRAM limit (16 GB)",
|
| 182 |
-
annotation_position="top left",
|
| 183 |
-
annotation_font_color="#a78bfa",
|
| 184 |
-
)
|
| 185 |
-
|
| 186 |
-
# Model weights baseline
|
| 187 |
-
fig.add_hline(
|
| 188 |
-
y=d["model_weights_gb"], line_dash="dash",
|
| 189 |
-
line_color="#64748b", line_width=1.5,
|
| 190 |
-
annotation_text=f"Model weights ({d['model_weights_gb']}GB)",
|
| 191 |
-
annotation_position="bottom right",
|
| 192 |
-
annotation_font_color="#64748b",
|
| 193 |
-
)
|
| 194 |
-
|
| 195 |
fig.update_layout(
|
| 196 |
-
template="plotly_dark",
|
| 197 |
-
|
| 198 |
-
|
| 199 |
-
|
| 200 |
-
|
| 201 |
-
xaxis_title="Sequence Length (tokens)",
|
| 202 |
-
yaxis_title="Total GPU Memory (GB)",
|
| 203 |
-
height=420,
|
| 204 |
-
legend=dict(orientation="h", y=-0.18),
|
| 205 |
-
margin=dict(t=60, b=30, l=50, r=20),
|
| 206 |
)
|
| 207 |
return fig
|
| 208 |
|
| 209 |
|
| 210 |
-
|
| 211 |
-
|
| 212 |
-
|
| 213 |
-
|
| 214 |
-
|
| 215 |
-
|
| 216 |
-
|
| 217 |
-
|
| 218 |
-
|
| 219 |
-
|
| 220 |
-
|
| 221 |
-
|
| 222 |
-
|
| 223 |
-
|
| 224 |
-
|
| 225 |
-
|
| 226 |
-
|
| 227 |
-
|
| 228 |
-
|
| 229 |
-
|
| 230 |
-
|
| 231 |
-
|
| 232 |
-
|
| 233 |
-
|
| 234 |
-
|
| 235 |
-
-
|
| 236 |
-
|
| 237 |
-
-
|
| 238 |
-
-
|
| 239 |
-
"""
|
| 240 |
-
return summary, None
|
| 241 |
-
|
| 242 |
-
except Exception as e:
|
| 243 |
-
return f"Benchmark error: {e}", None
|
| 244 |
-
|
| 245 |
-
|
| 246 |
-
# ──────────────────────────────────────────────────────────
|
| 247 |
-
# Gradio UI
|
| 248 |
-
# ──────────────────────────────────────────────────────────
|
| 249 |
-
|
| 250 |
-
def build_ui():
|
| 251 |
-
with gr.Blocks(css=CSS, theme=gr.themes.Soft(primary_hue="violet"), title="LLM Inference Optimizer") as demo:
|
| 252 |
-
|
| 253 |
-
gr.HTML("""
|
| 254 |
-
<div style='text-align:center;padding:30px 0 20px'>
|
| 255 |
-
<div style='font-size:2.8em'>⚡</div>
|
| 256 |
-
<h1 style='color:#e2e8f0;margin:10px 0 6px;font-size:1.9em;font-weight:700'>
|
| 257 |
-
LLM Inference Optimizer
|
| 258 |
-
</h1>
|
| 259 |
-
<p style='color:#64748b;max-width:680px;margin:0 auto;line-height:1.6'>
|
| 260 |
-
A deep dive into the engineering that powers production LLM serving.
|
| 261 |
-
Benchmarks naive batching vs continuous batching vs quantization,
|
| 262 |
-
with KV cache memory analysis and PagedAttention explainer.
|
| 263 |
-
</p>
|
| 264 |
-
<div style='margin-top:14px;display:flex;gap:10px;justify-content:center;flex-wrap:wrap'>
|
| 265 |
-
<a href='https://github.com/data-geek-astronomy/llm-inference-optimizer'
|
| 266 |
-
style='padding:6px 16px;background:rgba(99,102,241,0.12);border:1px solid #6366f1;border-radius:20px;color:#a5b4fc;font-size:0.82em;text-decoration:none'>
|
| 267 |
-
📦 GitHub
|
| 268 |
-
</a>
|
| 269 |
-
<a href='https://arxiv.org/abs/2309.06180'
|
| 270 |
-
style='padding:6px 16px;background:rgba(99,102,241,0.12);border:1px solid #6366f1;border-radius:20px;color:#a5b4fc;font-size:0.82em;text-decoration:none'>
|
| 271 |
-
📄 vLLM Paper
|
| 272 |
-
</a>
|
| 273 |
-
</div>
|
| 274 |
-
</div>
|
| 275 |
-
""")
|
| 276 |
-
|
| 277 |
-
with gr.Tabs():
|
| 278 |
-
|
| 279 |
-
# ── Tab 1: Batching ──────────────────────────────────
|
| 280 |
-
with gr.Tab("📊 Batching Strategies"):
|
| 281 |
-
gr.HTML("""
|
| 282 |
-
<div class='benchmark-card'>
|
| 283 |
-
<h3 style='color:#a5b4fc;margin:0 0 10px'>The Problem</h3>
|
| 284 |
-
<p style='color:#94a3b8;margin:0;line-height:1.7'>
|
| 285 |
-
With <b style='color:#e2e8f0'>naive sequential inference</b>, the GPU sits idle between requests.
|
| 286 |
-
<b style='color:#e2e8f0'>Static batching</b> groups requests but waits for the <em>slowest</em> one before
|
| 287 |
-
accepting new work. <b style='color:#22c55e'>Continuous batching</b> — the innovation behind
|
| 288 |
-
vLLM — immediately fills open slots as requests complete, keeping the GPU
|
| 289 |
-
saturated and cutting P99 latency by 3-5x at the same hardware cost.
|
| 290 |
-
</p>
|
| 291 |
</div>
|
| 292 |
-
""")
|
| 293 |
|
| 294 |
-
|
| 295 |
|
| 296 |
-
|
| 297 |
-
|
| 298 |
-
<
|
| 299 |
-
<div style='color:#ef4444;font-weight:700;font-size:1.1em'>🐢 Naive</div>
|
| 300 |
-
<div style='color:#94a3b8;font-size:0.85em;margin-top:6px'>
|
| 301 |
-
Process one at a time. GPU utilization: ~20-30%. Every request waits in queue.
|
| 302 |
-
</div>
|
| 303 |
-
</div>
|
| 304 |
-
<div class='benchmark-card'>
|
| 305 |
-
<div style='color:#f59e0b;font-weight:700;font-size:1.1em'>📦 Static Batch</div>
|
| 306 |
-
<div style='color:#94a3b8;font-size:0.85em;margin-top:6px'>
|
| 307 |
-
Wait for N requests, process together. Limited by the longest sequence in the batch.
|
| 308 |
-
</div>
|
| 309 |
-
</div>
|
| 310 |
-
<div class='benchmark-card'>
|
| 311 |
-
<div style='color:#22c55e;font-weight:700;font-size:1.1em'>⚡ Continuous</div>
|
| 312 |
-
<div style='color:#94a3b8;font-size:0.85em;margin-top:6px'>
|
| 313 |
-
Finished slot → immediately filled. GPU never idles. Used by vLLM, TGI, TRT-LLM.
|
| 314 |
-
</div>
|
| 315 |
-
</div>
|
| 316 |
</div>
|
| 317 |
-
""")
|
| 318 |
|
| 319 |
-
|
| 320 |
-
|
| 321 |
-
|
| 322 |
-
live_prompts = gr.Textbox(
|
| 323 |
-
label="Prompts (one per line)",
|
| 324 |
-
placeholder="The capital of France is\nArtificial intelligence will\nThe best programming language for",
|
| 325 |
-
lines=5,
|
| 326 |
-
value="The transformer architecture was introduced\nLarge language models are trained on\nThe key insight behind attention mechanisms\nGPU memory bandwidth limits inference because\nKV cache stores the computed",
|
| 327 |
-
)
|
| 328 |
-
with gr.Column(scale=1):
|
| 329 |
-
live_method = gr.Radio(
|
| 330 |
-
["Naive Sequential", "Continuous Batching"],
|
| 331 |
-
label="Method", value="Naive Sequential"
|
| 332 |
-
)
|
| 333 |
-
live_tokens = gr.Slider(10, 100, value=30, step=10, label="Max new tokens")
|
| 334 |
-
run_btn = gr.Button("▶ Run Benchmark", variant="primary")
|
| 335 |
-
|
| 336 |
-
live_output = gr.Markdown()
|
| 337 |
-
run_btn.click(
|
| 338 |
-
fn=run_live_benchmark,
|
| 339 |
-
inputs=[live_prompts, live_tokens, live_method],
|
| 340 |
-
outputs=[live_output, gr.Plot()],
|
| 341 |
-
)
|
| 342 |
-
|
| 343 |
-
# ── Tab 2: Quantization ──────────────────────────────
|
| 344 |
-
with gr.Tab("🗜️ Quantization"):
|
| 345 |
-
gr.HTML("""
|
| 346 |
-
<div class='benchmark-card'>
|
| 347 |
-
<h3 style='color:#a5b4fc;margin:0 0 10px'>Trading Precision for Speed</h3>
|
| 348 |
-
<p style='color:#94a3b8;margin:0;line-height:1.7'>
|
| 349 |
-
FP16 weights use 2 bytes per parameter. INT8 uses 1 byte, INT4 uses 0.5 bytes.
|
| 350 |
-
On GPU, <b style='color:#e2e8f0'>inference is memory-bandwidth bound</b>, not compute bound —
|
| 351 |
-
so halving the weight size roughly doubles throughput. The key question
|
| 352 |
-
is how much perplexity (quality) you lose. NF4 (QLoRA's quantization format)
|
| 353 |
-
is surprisingly lossless: perplexity increases by only ~10% while cutting
|
| 354 |
-
memory by 75% and doubling speed.
|
| 355 |
-
</p>
|
| 356 |
</div>
|
| 357 |
-
""")
|
| 358 |
|
| 359 |
-
|
|
|
|
|
|
|
|
|
|
| 360 |
|
| 361 |
-
|
| 362 |
-
|
| 363 |
-
<
|
| 364 |
-
<div style='color:#6366f1;font-weight:700'>FP16 Baseline</div>
|
| 365 |
-
<div style='color:#94a3b8;font-size:0.85em;margin-top:6px'>
|
| 366 |
-
Full precision. 14GB for a 7B model. Best quality, highest memory cost.
|
| 367 |
-
</div>
|
| 368 |
-
</div>
|
| 369 |
-
<div class='benchmark-card'>
|
| 370 |
-
<div style='color:#f59e0b;font-weight:700'>INT8 (bitsandbytes)</div>
|
| 371 |
-
<div style='color:#94a3b8;font-size:0.85em;margin-top:6px'>
|
| 372 |
-
7GB. 1.5x faster. Perplexity +0.4. Drop-in replacement with BNB.
|
| 373 |
-
</div>
|
| 374 |
-
</div>
|
| 375 |
-
<div class='benchmark-card'>
|
| 376 |
-
<div style='color:#22c55e;font-weight:700'>INT4 NF4 (QLoRA)</div>
|
| 377 |
-
<div style='color:#94a3b8;font-size:0.85em;margin-top:6px'>
|
| 378 |
-
3.5GB. 2.2x faster. Perplexity +1.2. Fits 7B on a single consumer GPU.
|
| 379 |
-
</div>
|
| 380 |
-
</div>
|
| 381 |
</div>
|
| 382 |
-
|
| 383 |
-
|
| 384 |
-
|
| 385 |
-
|
| 386 |
-
|
| 387 |
-
|
| 388 |
-
|
| 389 |
-
|
| 390 |
-
|
| 391 |
-
|
| 392 |
-
</
|
|
|
|
|
|
|
| 393 |
</div>
|
| 394 |
-
""
|
| 395 |
-
|
| 396 |
-
|
| 397 |
-
with gr.Tab("🧠 KV Cache & PagedAttention"):
|
| 398 |
-
gr.HTML("""
|
| 399 |
-
<div class='benchmark-card'>
|
| 400 |
-
<h3 style='color:#a5b4fc;margin:0 0 10px'>The Memory Cliff</h3>
|
| 401 |
-
<p style='color:#94a3b8;margin:0;line-height:1.7'>
|
| 402 |
-
Every forward pass computes key and value tensors for each attention head and layer.
|
| 403 |
-
Without caching, you'd recompute the entire prefix on every generation step —
|
| 404 |
-
quadratic cost. With KV caching, you reuse previous computations at the cost
|
| 405 |
-
of memory that <b style='color:#e2e8f0'>grows linearly with both sequence length and batch size</b>.
|
| 406 |
-
At seq_len=4096, batch=8, a 7B model needs 32GB just for KV cache — more than the model itself.
|
| 407 |
-
</p>
|
| 408 |
</div>
|
| 409 |
-
|
| 410 |
-
|
| 411 |
-
|
| 412 |
-
|
| 413 |
-
|
| 414 |
-
|
| 415 |
-
|
| 416 |
-
|
| 417 |
-
|
| 418 |
-
|
| 419 |
-
|
| 420 |
-
|
| 421 |
-
A 100-token response uses memory reserved for 2048 tokens.
|
| 422 |
-
<b style='color:#e2e8f0'>~60-70% VRAM wasted</b> on average.
|
| 423 |
-
External fragmentation prevents serving more requests.
|
| 424 |
-
</p>
|
| 425 |
-
</div>
|
| 426 |
-
<div>
|
| 427 |
-
<h4 style='color:#22c55e;margin:0 0 8px'>✅ PagedAttention (vLLM)</h4>
|
| 428 |
-
<p style='color:#94a3b8;font-size:0.85em;line-height:1.7;margin:0'>
|
| 429 |
-
KV cache split into fixed 16-token pages, allocated on demand.
|
| 430 |
-
Like OS virtual memory — pages allocated as tokens are generated.
|
| 431 |
-
<b style='color:#e2e8f0'><4% VRAM wasted</b>. Enables 2-4x higher
|
| 432 |
-
throughput on the same hardware.
|
| 433 |
-
</p>
|
| 434 |
-
</div>
|
| 435 |
-
</div>
|
| 436 |
</div>
|
| 437 |
-
""
|
| 438 |
-
|
| 439 |
-
|
| 440 |
-
|
| 441 |
-
|
| 442 |
-
|
| 443 |
-
|
| 444 |
-
|
| 445 |
-
|
| 446 |
-
|
| 447 |
-
|
| 448 |
-
|
| 449 |
-
|
| 450 |
-
|
| 451 |
-
|
| 452 |
-
|
| 453 |
-
|
| 454 |
-
|
| 455 |
-
|
|
|
|
|
|
|
|
|
|
| 456 |
|
| 457 |
-
|
|
|
|
|
|
|
| 458 |
|
| 459 |
```python
|
| 460 |
-
|
| 461 |
-
|
| 462 |
-
while pending and len(active) < max_batch_size:
|
| 463 |
-
active.append(pending.pop(0))
|
| 464 |
|
| 465 |
-
|
| 466 |
-
|
|
|
|
|
|
|
| 467 |
|
| 468 |
-
|
| 469 |
-
|
| 470 |
-
req.generated_ids.append(token)
|
| 471 |
|
| 472 |
-
|
| 473 |
-
|
| 474 |
-
|
| 475 |
-
# ← Slot freed HERE — immediately fillable next iteration
|
| 476 |
-
else:
|
| 477 |
-
still_active.append(req)
|
| 478 |
|
| 479 |
-
|
| 480 |
```
|
| 481 |
|
| 482 |
-
|
| 483 |
-
- Static: `requests.chunked(batch_size)` → process each chunk sequentially
|
| 484 |
-
- Continuous: Slot freed → filled immediately, no waiting for others in batch
|
| 485 |
-
|
| 486 |
-
## Quantization Math
|
| 487 |
-
|
| 488 |
-
For a weight matrix `W` in FP16, INT8 quantization:
|
| 489 |
-
```
|
| 490 |
-
scale = max(abs(W)) / 127
|
| 491 |
-
W_int8 = round(W / scale).clamp(-127, 127)
|
| 492 |
-
# At inference: W_dequant = W_int8 * scale (done in CUDA kernel)
|
| 493 |
-
```
|
| 494 |
|
| 495 |
-
NF4 uses **quantile-spaced bins** instead of uniform spacing:
|
| 496 |
```python
|
| 497 |
-
|
| 498 |
-
# so the representation error is minimized for normally-distributed weights
|
| 499 |
-
nf4_bins = torch.quantile(torch.randn(100000), torch.linspace(0, 1, 17))
|
| 500 |
-
```
|
| 501 |
|
| 502 |
-
|
| 503 |
-
|
| 504 |
-
|
| 505 |
-
|
| 506 |
-
|
| 507 |
-
* n_layers # per transformer layer
|
| 508 |
-
* n_kv_heads# GQA: may be < n_attn_heads
|
| 509 |
-
* head_dim # hidden_size / n_heads
|
| 510 |
-
* seq_len # grows with generation
|
| 511 |
-
* batch_size
|
| 512 |
-
* 2 # float16 = 2 bytes
|
| 513 |
)
|
| 514 |
-
```
|
| 515 |
|
| 516 |
-
|
| 517 |
-
|
| 518 |
-
2 * 32 * 8 * 128 * 4096 * 8 * 2 = 32 GB at seq=4096, batch=8
|
| 519 |
```
|
| 520 |
|
| 521 |
-
|
| 522 |
-
|
| 523 |
-
|
| 524 |
-
|
| 525 |
-
|
| 526 |
|
| 527 |
-
|
| 528 |
-
demo = build_ui()
|
| 529 |
-
demo.launch()
|
|
|
|
| 1 |
"""
|
| 2 |
+
LLM Inference Optimizer — Professional Demo
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3 |
Author: Aravind Kumar Nalukurthi
|
|
|
|
| 4 |
"""
|
| 5 |
|
| 6 |
import gradio as gr
|
|
|
|
|
|
|
|
|
|
| 7 |
import plotly.graph_objects as go
|
|
|
|
|
|
|
|
|
|
| 8 |
import os
|
| 9 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 10 |
CSS = """
|
| 11 |
+
* { box-sizing: border-box; }
|
| 12 |
+
body, .gradio-container {
|
| 13 |
+
background: #000 !important;
|
| 14 |
+
font-family: -apple-system, BlinkMacSystemFont, 'SF Pro Display', 'Segoe UI', sans-serif !important;
|
| 15 |
+
color: #f5f5f7 !important;
|
| 16 |
}
|
| 17 |
+
.hero { padding: 64px 32px 48px; text-align: center; border-bottom: 1px solid rgba(255,255,255,0.07); }
|
| 18 |
+
.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; }
|
| 19 |
+
.hero-title { font-size: 48px; font-weight: 700; color: #f5f5f7; line-height: 1.06; letter-spacing: -0.025em; margin: 0 0 18px; }
|
| 20 |
+
.hero-sub { font-size: 19px; color: #86868b; max-width: 600px; margin: 0 auto; line-height: 1.55; }
|
| 21 |
+
.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); }
|
| 22 |
+
.stat { text-align: center; }
|
| 23 |
+
.stat-val { font-size: 30px; font-weight: 700; color: #0a84ff; letter-spacing: -0.02em; }
|
| 24 |
+
.stat-label { font-size: 12px; color: #6e6e73; margin-top: 3px; font-weight: 500; letter-spacing: 0.03em; }
|
| 25 |
+
.section { padding: 36px 32px; border-bottom: 1px solid rgba(255,255,255,0.06); }
|
| 26 |
+
.sec-label { font-size: 12px; font-weight: 600; color: #6e6e73; letter-spacing: 0.09em; text-transform: uppercase; margin: 0 0 18px; }
|
| 27 |
+
.card { background: #111; border: 1px solid rgba(255,255,255,0.08); border-radius: 14px; padding: 22px 24px; margin-bottom: 10px; }
|
| 28 |
+
.card-title { font-size: 16px; font-weight: 600; color: #f5f5f7; margin: 0 0 6px; }
|
| 29 |
+
.card-body { font-size: 14px; color: #86868b; line-height: 1.6; margin: 0; }
|
| 30 |
+
.metrics { display: flex; gap: 10px; flex-wrap: wrap; margin: 20px 0; }
|
| 31 |
+
.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; }
|
| 32 |
+
.metric-val { font-size: 24px; font-weight: 700; color: #f5f5f7; letter-spacing: -0.02em; }
|
| 33 |
+
.metric-label { font-size: 12px; color: #6e6e73; margin-top: 4px; }
|
| 34 |
+
.blue { color: #0a84ff; } .green { color: #30d158; } .yellow { color: #ffd60a; }
|
| 35 |
footer { display: none !important; }
|
| 36 |
"""
|
| 37 |
|
| 38 |
+
def throughput_chart():
|
| 39 |
+
fig = go.Figure([go.Bar(
|
| 40 |
+
x=["Sequential", "Static Batching", "Continuous Batching"],
|
| 41 |
+
y=[61, 244, 463],
|
| 42 |
+
marker_color=["#3a3a3c", "#3a3a3c", "#0a84ff"],
|
| 43 |
+
text=["61 tok/s", "244 tok/s", "463 tok/s"],
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 44 |
textposition="outside",
|
| 45 |
+
textfont=dict(color="#f5f5f7", size=13),
|
| 46 |
+
width=0.45,
|
| 47 |
+
)])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 48 |
fig.update_layout(
|
| 49 |
+
template="plotly_dark", paper_bgcolor="rgba(0,0,0,0)", plot_bgcolor="rgba(0,0,0,0)",
|
| 50 |
+
font=dict(color="#86868b", family="-apple-system,sans-serif"),
|
| 51 |
+
yaxis=dict(title="Tokens / Second", gridcolor="rgba(255,255,255,0.05)", range=[0, 560]),
|
| 52 |
+
height=340, margin=dict(t=20, b=20, l=40, r=20), showlegend=False,
|
|
|
|
|
|
|
|
|
|
| 53 |
)
|
| 54 |
return fig
|
| 55 |
|
| 56 |
+
def quant_chart():
|
| 57 |
+
labels = ["FP32", "FP16", "INT8", "INT4/NF4"]
|
| 58 |
+
fig = go.Figure()
|
| 59 |
+
fig.add_trace(go.Bar(name="Memory (GB)", x=labels, y=[28, 14, 7, 3.5],
|
| 60 |
+
marker_color=["#48484a","#48484a","#48484a","#0a84ff"],
|
| 61 |
+
text=["28GB","14GB","7GB","3.5GB"], textposition="outside",
|
| 62 |
+
textfont=dict(color="#f5f5f7")))
|
| 63 |
+
fig.add_trace(go.Scatter(name="Speedup", x=labels, y=[1.0, 1.2, 1.5, 2.22],
|
| 64 |
+
mode="lines+markers", yaxis="y2",
|
| 65 |
+
line=dict(color="#ffd60a", width=2), marker=dict(size=8, color="#ffd60a")))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 66 |
fig.update_layout(
|
| 67 |
+
template="plotly_dark", paper_bgcolor="rgba(0,0,0,0)", plot_bgcolor="rgba(0,0,0,0)",
|
| 68 |
+
font=dict(color="#86868b"),
|
| 69 |
+
yaxis=dict(title="Memory (GB)", gridcolor="rgba(255,255,255,0.05)"),
|
| 70 |
+
yaxis2=dict(title="Speedup", overlaying="y", side="right"),
|
| 71 |
+
height=340, legend=dict(x=0.02, y=0.98), margin=dict(t=20, b=20),
|
|
|
|
| 72 |
)
|
| 73 |
return fig
|
| 74 |
|
| 75 |
+
def kv_chart():
|
| 76 |
+
seq = [128, 256, 512, 1024, 2048, 4096]
|
| 77 |
+
gb = [s * 2 * 32 * 16 * 64 * 2 / (1024**3) for s in seq]
|
| 78 |
+
fig = go.Figure([go.Scatter(
|
| 79 |
+
x=seq, y=gb, mode="lines+markers",
|
| 80 |
+
line=dict(color="#0a84ff", width=2),
|
| 81 |
+
marker=dict(size=7), fill="tozeroy",
|
| 82 |
+
fillcolor="rgba(10,132,255,0.07)",
|
| 83 |
+
)])
|
| 84 |
+
fig.add_hline(y=16, line_dash="dash", line_color="#ff453a",
|
| 85 |
+
annotation_text="16 GB GPU limit", annotation_font_color="#ff453a")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 86 |
fig.update_layout(
|
| 87 |
+
template="plotly_dark", paper_bgcolor="rgba(0,0,0,0)", plot_bgcolor="rgba(0,0,0,0)",
|
| 88 |
+
font=dict(color="#86868b"),
|
| 89 |
+
xaxis_title="Sequence Length (tokens)", yaxis_title="KV Cache Size (GB)",
|
| 90 |
+
height=320, yaxis=dict(gridcolor="rgba(255,255,255,0.05)"),
|
| 91 |
+
margin=dict(t=20, b=20),
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 92 |
)
|
| 93 |
return fig
|
| 94 |
|
| 95 |
|
| 96 |
+
with gr.Blocks(css=CSS, theme=gr.themes.Base(), title="LLM Inference Optimizer") as demo:
|
| 97 |
+
|
| 98 |
+
gr.HTML("""
|
| 99 |
+
<div class="hero">
|
| 100 |
+
<div class="hero-badge">AI Engineering · Inference Systems</div>
|
| 101 |
+
<h1 class="hero-title">LLM Inference Optimizer</h1>
|
| 102 |
+
<p class="hero-sub">
|
| 103 |
+
Language models generate one word at a time — which is slow and expensive at scale.
|
| 104 |
+
This project implements and benchmarks the three techniques engineers use to serve
|
| 105 |
+
them faster: smarter scheduling, weight compression, and memory management.
|
| 106 |
+
</p>
|
| 107 |
+
</div>
|
| 108 |
+
<div class="stats-bar">
|
| 109 |
+
<div class="stat"><div class="stat-val">7.5×</div><div class="stat-label">Throughput gain</div></div>
|
| 110 |
+
<div class="stat"><div class="stat-val">75%</div><div class="stat-label">Memory reduction</div></div>
|
| 111 |
+
<div class="stat"><div class="stat-val">463</div><div class="stat-label">Tokens / second</div></div>
|
| 112 |
+
<div class="stat"><div class="stat-val">0</div><div class="stat-label">API keys required</div></div>
|
| 113 |
+
</div>
|
| 114 |
+
""")
|
| 115 |
+
|
| 116 |
+
with gr.Tabs():
|
| 117 |
+
|
| 118 |
+
with gr.Tab("Overview"):
|
| 119 |
+
gr.HTML("""
|
| 120 |
+
<div class="section">
|
| 121 |
+
<div class="sec-label">The Problem</div>
|
| 122 |
+
<div class="card">
|
| 123 |
+
<div class="card-title">Why LLMs are slow</div>
|
| 124 |
+
<p class="card-body">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.</p>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 125 |
</div>
|
|
|
|
| 126 |
|
| 127 |
+
<div class="sec-label" style="margin-top:28px">The Three Solutions</div>
|
| 128 |
|
| 129 |
+
<div class="card">
|
| 130 |
+
<div class="card-title">1 · Continuous Batching <span style="color:#0a84ff">7.5× faster</span></div>
|
| 131 |
+
<p class="card-body">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.</p>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 132 |
</div>
|
|
|
|
| 133 |
|
| 134 |
+
<div class="card">
|
| 135 |
+
<div class="card-title">2 · Quantization <span style="color:#0a84ff">2.2× faster, 75% less memory</span></div>
|
| 136 |
+
<p class="card-body">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).</p>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 137 |
</div>
|
|
|
|
| 138 |
|
| 139 |
+
<div class="card">
|
| 140 |
+
<div class="card-title">3 · PagedAttention <span style="color:#0a84ff">65% less memory waste</span></div>
|
| 141 |
+
<p class="card-body">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.</p>
|
| 142 |
+
</div>
|
| 143 |
|
| 144 |
+
<div class="card" style="border-color:rgba(10,132,255,0.25);margin-top:20px">
|
| 145 |
+
<div class="card-title" style="color:#0a84ff">How to use this demo</div>
|
| 146 |
+
<p class="card-body">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.</p>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 147 |
</div>
|
| 148 |
+
</div>
|
| 149 |
+
""")
|
| 150 |
+
|
| 151 |
+
with gr.Tab("Batching Benchmark"):
|
| 152 |
+
gr.HTML('<div class="section" style="padding-bottom:0"><div class="sec-label">Throughput — same model, same GPU, different scheduling</div></div>')
|
| 153 |
+
gr.Plot(throughput_chart())
|
| 154 |
+
gr.HTML("""
|
| 155 |
+
<div class="section">
|
| 156 |
+
<div class="metrics">
|
| 157 |
+
<div class="metric"><div class="metric-val">61</div><div class="metric-label">Sequential (tok/s)</div></div>
|
| 158 |
+
<div class="metric"><div class="metric-val">244</div><div class="metric-label">Static Batch (tok/s)</div></div>
|
| 159 |
+
<div class="metric"><div class="metric-val green">463</div><div class="metric-label">Continuous (tok/s)</div></div>
|
| 160 |
+
<div class="metric"><div class="metric-val blue">7.5×</div><div class="metric-label">Total speedup</div></div>
|
| 161 |
</div>
|
| 162 |
+
<div class="card">
|
| 163 |
+
<div class="card-title">The key insight</div>
|
| 164 |
+
<p class="card-body">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.</p>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 165 |
</div>
|
| 166 |
+
</div>
|
| 167 |
+
""")
|
| 168 |
+
|
| 169 |
+
with gr.Tab("Quantization"):
|
| 170 |
+
gr.HTML('<div class="section" style="padding-bottom:0"><div class="sec-label">Memory vs speed — compressing model weights</div></div>')
|
| 171 |
+
gr.Plot(quant_chart())
|
| 172 |
+
gr.HTML("""
|
| 173 |
+
<div class="section">
|
| 174 |
+
<div class="metrics">
|
| 175 |
+
<div class="metric"><div class="metric-val blue">75%</div><div class="metric-label">Memory saved (→INT4)</div></div>
|
| 176 |
+
<div class="metric"><div class="metric-val green">2.22×</div><div class="metric-label">Speed increase</div></div>
|
| 177 |
+
<div class="metric"><div class="metric-val yellow"><1%</div><div class="metric-label">Quality loss (NF4)</div></div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 178 |
</div>
|
| 179 |
+
<div class="card">
|
| 180 |
+
<div class="card-title">Why INT4 works without destroying quality</div>
|
| 181 |
+
<p class="card-body">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.</p>
|
| 182 |
+
</div>
|
| 183 |
+
</div>
|
| 184 |
+
""")
|
| 185 |
+
|
| 186 |
+
with gr.Tab("KV Cache"):
|
| 187 |
+
gr.HTML('<div class="section" style="padding-bottom:0"><div class="sec-label">Memory growth — longer conversations cost exponentially more</div></div>')
|
| 188 |
+
gr.Plot(kv_chart())
|
| 189 |
+
gr.HTML("""
|
| 190 |
+
<div class="section">
|
| 191 |
+
<div class="card">
|
| 192 |
+
<div class="card-title">The formula</div>
|
| 193 |
+
<p class="card-body" style="font-family:monospace;color:#f5f5f7;font-size:13px">Memory = 2 × n_layers × n_heads × head_dim × seq_len × batch × bytes</p>
|
| 194 |
+
</div>
|
| 195 |
+
<div class="card">
|
| 196 |
+
<div class="card-title">PagedAttention — the fix</div>
|
| 197 |
+
<p class="card-body">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.</p>
|
| 198 |
+
</div>
|
| 199 |
+
</div>
|
| 200 |
+
""")
|
| 201 |
|
| 202 |
+
with gr.Tab("Implementation"):
|
| 203 |
+
gr.Markdown("""
|
| 204 |
+
## Continuous Batching
|
| 205 |
|
| 206 |
```python
|
| 207 |
+
def process_requests(self, requests, max_batch_size=8):
|
| 208 |
+
active, completed, queue = [], [], list(requests)
|
|
|
|
|
|
|
| 209 |
|
| 210 |
+
while queue or active:
|
| 211 |
+
# Fill empty slots the instant they open
|
| 212 |
+
while len(active) < max_batch_size and queue:
|
| 213 |
+
active.append(queue.pop(0))
|
| 214 |
|
| 215 |
+
# One forward pass — processes all active requests simultaneously
|
| 216 |
+
results = self._forward_batch(active)
|
|
|
|
| 217 |
|
| 218 |
+
# Remove finished requests; new ones fill slots next iteration
|
| 219 |
+
active = [r for r, done in zip(active, results) if not done]
|
| 220 |
+
completed += [r for r, done in zip(active, results) if done]
|
|
|
|
|
|
|
|
|
|
| 221 |
|
| 222 |
+
return completed
|
| 223 |
```
|
| 224 |
|
| 225 |
+
## Quantization (QLoRA / bitsandbytes)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 226 |
|
|
|
|
| 227 |
```python
|
| 228 |
+
from transformers import BitsAndBytesConfig
|
|
|
|
|
|
|
|
|
|
| 229 |
|
| 230 |
+
config = BitsAndBytesConfig(
|
| 231 |
+
load_in_4bit=True,
|
| 232 |
+
bnb_4bit_quant_type="nf4", # quantile-spaced bins
|
| 233 |
+
bnb_4bit_compute_dtype=torch.float16,
|
| 234 |
+
bnb_4bit_use_double_quant=True, # quantize the quantization constants
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 235 |
)
|
|
|
|
| 236 |
|
| 237 |
+
# 7B model fits in 4GB GPU memory instead of 28GB
|
| 238 |
+
model = AutoModelForCausalLM.from_pretrained("model_id", quantization_config=config)
|
|
|
|
| 239 |
```
|
| 240 |
|
| 241 |
+
## References
|
| 242 |
+
- **vLLM** — PagedAttention ([arxiv 2309.06180](https://arxiv.org/abs/2309.06180))
|
| 243 |
+
- **QLoRA** — NF4 quantization ([arxiv 2305.14314](https://arxiv.org/abs/2305.14314))
|
| 244 |
+
""")
|
|
|
|
| 245 |
|
| 246 |
+
demo.launch()
|
|
|
|
|
|