Skip to main content

Serve LLaMA 3 with vLLM: Production Inference API on a GPU Server (2026)

· 15 min read
Yassine El Haddad
Software Developer & Automation Specialist

I build production AI agents, web scrapers, and automation pipelines. Most of what I publish here comes from the actual problems they run into: proxies that get banned, anti-bot stacks that fingerprint your client, RAG that drifts when the underlying data moves. Stack: Python, TypeScript, Go, FastAPI, LangChain, Crawlee, Playwright, deployed on AWS, GCP, and Cloudflare.

Deploy Meta's LLaMA 3 as a high-throughput, OpenAI-compatible inference API on a dedicated GPU server — with quantization, load testing, and Prometheus monitoring included.

If you've run LLaMA 3 locally with Ollama or llama.cpp, you've hit the ceiling: single-user latency is fine, but concurrency crumbles past three or four simultaneous requests. vLLM solves that. It uses PagedAttention and continuous batching to serve dozens of concurrent requests on the same GPU that would otherwise stall at one.

This guide walks through deploying LLaMA 3 (8B and 70B) on a dedicated GPU server using vLLM — covering Docker setup, model quantization, the OpenAI-compatible API, load testing with Locust, and Prometheus monitoring. All steps are tested on an NVIDIA A100 80 GB, which you can rent from Liquid Web.

What Is vLLM and Why Does It Matter?

vLLM is an open-source LLM inference and serving library developed at UC Berkeley. Two innovations define it:

PagedAttention stores key-value (KV) cache in non-contiguous blocks of GPU memory, similar to virtual memory paging in an operating system. Classical inference engines pre-allocate a fixed KV cache per sequence. With PagedAttention, memory blocks are allocated on demand and shared across parallel requests, cutting memory waste by up to 55% compared to HuggingFace Transformers.

Continuous batching dispatches new requests into an active batch the moment a sequence finishes, rather than waiting for the entire batch to complete. Combined with PagedAttention, throughput scales near-linearly with batch size.

The practical result: on an A100 80 GB, vLLM serves LLaMA 3 8B at 3–4× higher throughput than Transformers with equivalent latency per request under load.


vLLM vs TGI vs Triton: Which Should You Use?

FeaturevLLMHuggingFace TGINVIDIA Triton
Memory managementPagedAttentionStatic KV cacheStatic KV cache
BatchingContinuousContinuousDynamic
OpenAI-compatible APIBuilt-inBuilt-inVia wrapper
QuantizationAWQ, GPTQ, FP8AWQ, GPTQ, EETQTensorRT-LLM
Multi-GPU (tensor parallel)
LoRA adapter hot-swapPartial
Setup complexityLow (Docker)Low (Docker)High (triton server config)
Best forGeneral production LLM APIHuggingFace-first teamsNVIDIA-only, maximum throughput

Choose vLLM if: You want a low-friction, OpenAI-compatible API that handles most open-weight models with good throughput out of the box.

Choose TGI if: Your team is already deep in the HuggingFace ecosystem and you need its specific model catalogue support.

Choose Triton if: You are deploying on NVIDIA infrastructure at scale and need TensorRT-LLM's last-mile performance tuning — and you have the ops bandwidth to manage it.


GPU Memory Requirements for LLaMA 3

LLaMA 3 comes in three sizes. Here are the minimum GPU configurations for production serving (FP16 unless noted):

ModelParametersFP16 VRAMINT4/AWQ VRAMRecommended GPU
LLaMA 3 8B8B~16 GB~6 GBRTX 4090, A10G, A100 40 GB
LLaMA 3 70B70B~140 GB~40 GB2× A100 80 GB or H100 80 GB
LLaMA 3 405B405B~810 GB~230 GB8× H100 80 GB

Rule of thumb: Multiply the parameter count in billions by 2 to get approximate FP16 VRAM in GB. Add 10–20 % headroom for the KV cache under moderate concurrent load.

For this guide, we use a single A100 80 GB from Liquid Web, which comfortably runs LLaMA 3 70B in AWQ 4-bit quantization.


Prerequisites

Before you start, make sure you have:

  • A GPU server with NVIDIA A100 or H100 (40 GB minimum; 80 GB recommended for 70B)
  • NVIDIA drivers ≥ 535 installed on the host
  • Docker Engine ≥ 24.0 and the NVIDIA Container Toolkit (nvidia-docker2)
  • nvidia-smi returning your GPU(s) without errors
  • A Hugging Face account with access to the gated LLaMA 3 repositories
  • A HF_TOKEN environment variable set to your Hugging Face token

Verify your setup before proceeding:

nvidia-smi
# should show GPU name, driver version, and CUDA version

docker run --rm --gpus all nvidia/cuda:12.4.0-base-ubuntu22.04 nvidia-smi
# should mirror the output above inside Docker

Step 1: Pull the vLLM Docker Image

The official vLLM Docker image bundles everything: CUDA, PyTorch, Flash Attention 2, and vLLM itself.

docker pull vllm/vllm-openai:latest

For reproducible production deployments, pin to a specific release:

docker pull vllm/vllm-openai:v0.6.4

Check the vLLM releases page for the latest stable tag.


Step 2: Serve LLaMA 3 8B (Quick Start)

Start with the 8B model to verify your pipeline before moving to the 70B.

docker run --runtime nvidia --gpus all \
-v ~/.cache/huggingface:/root/.cache/huggingface \
-p 8000:8000 \
-e HF_TOKEN=$HF_TOKEN \
--ipc=host \
vllm/vllm-openai:v0.6.4 \
--model meta-llama/Meta-Llama-3-8B-Instruct \
--dtype auto \
--max-model-len 8192

Flags explained:

FlagPurpose
--runtime nvidia --gpus allPass all GPUs to the container via NVIDIA runtime
-v ~/.cache/huggingface:…Mount the HuggingFace model cache so weights persist across restarts
--ipc=hostRequired for shared memory used by PyTorch data loaders
--dtype autoLet vLLM select BF16/FP16 based on GPU capability
--max-model-lenMaximum sequence length (prompt + completion). Set lower to save KV cache memory

Once the server is up, you'll see:

INFO: Started server process [1]
INFO: Uvicorn running on http://0.0.0.0:8000

Test it with a quick curl:

curl http://localhost:8000/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"model": "meta-llama/Meta-Llama-3-8B-Instruct",
"messages": [{"role": "user", "content": "Explain PagedAttention in one paragraph."}],
"max_tokens": 200
}'

Step 3: Quantize LLaMA 3 for Efficient Serving

Quantization reduces VRAM requirements significantly, enabling larger models on smaller GPUs or cutting costs on production hardware.

AWQ vs GPTQ

MethodAlgorithmQualitySpeedBest for
AWQ (Activation-aware Weight Quantization)Protects salient weights during quantizationHigh — minimal accuracy lossFast inference (kernel support)Production serving
GPTQ (Generative Pre-Trained Transformer Quantization)Hessian-based layer-wise quantizationHighModerateFine-tuned models
FP8 (vLLM native)Native float8 precisionNear-losslessFastest on H100H100 hardware

Using Pre-Quantized Models from HuggingFace

The easiest approach is to pull a community-maintained AWQ checkpoint:

# LLaMA 3 8B in 4-bit AWQ (~5 GB VRAM)
docker run --runtime nvidia --gpus all \
-v ~/.cache/huggingface:/root/.cache/huggingface \
-p 8000:8000 \
-e HF_TOKEN=$HF_TOKEN \
--ipc=host \
vllm/vllm-openai:v0.6.4 \
--model casperhansen/llama-3-8b-instruct-awq \
--quantization awq \
--dtype half \
--max-model-len 8192

Quantizing a Model Yourself with AutoAWQ

If you need to quantize your own fine-tuned checkpoint:

pip install autoawq
from awq import AutoAWQForCausalLM
from transformers import AutoTokenizer

model_path = "meta-llama/Meta-Llama-3-8B-Instruct"
quant_path = "./llama3-8b-instruct-awq"

quant_config = {
"zero_point": True,
"q_group_size": 128,
"w_bit": 4,
"version": "GEMM"
}

model = AutoAWQForCausalLM.from_pretrained(model_path)
tokenizer = AutoTokenizer.from_pretrained(model_path, trust_remote_code=True)

model.quantize(tokenizer, quant_config=quant_config)
model.save_quantized(quant_path)
tokenizer.save_pretrained(quant_path)

Then serve the quantized model:

docker run --runtime nvidia --gpus all \
-v ~/.cache/huggingface:/root/.cache/huggingface \
-v $(pwd)/llama3-8b-instruct-awq:/models/llama3-8b-awq \
-p 8000:8000 \
--ipc=host \
vllm/vllm-openai:v0.6.4 \
--model /models/llama3-8b-awq \
--quantization awq \
--dtype half

Step 4: Serve LLaMA 3 70B on a Single A100 80 GB

With AWQ quantization, LLaMA 3 70B fits on a single A100 80 GB (using ~40 GB VRAM):

docker run --runtime nvidia --gpus all \
-v ~/.cache/huggingface:/root/.cache/huggingface \
-p 8000:8000 \
-e HF_TOKEN=$HF_TOKEN \
--ipc=host \
vllm/vllm-openai:v0.6.4 \
--model casperhansen/llama-3-70b-instruct-awq \
--quantization awq \
--dtype half \
--max-model-len 4096 \
--gpu-memory-utilization 0.90

--gpu-memory-utilization 0.90 tells vLLM to use up to 90 % of available VRAM for the KV cache, leaving a small buffer for PyTorch overhead. Adjust down to 0.85 if you see OOM errors during heavy concurrent load.

For the full-precision 70B in FP16 (requires ~140 GB), use two A100 80 GB with tensor parallelism:

docker run --runtime nvidia --gpus all \
-v ~/.cache/huggingface:/root/.cache/huggingface \
-p 8000:8000 \
-e HF_TOKEN=$HF_TOKEN \
--ipc=host \
vllm/vllm-openai:v0.6.4 \
--model meta-llama/Meta-Llama-3-70B-Instruct \
--tensor-parallel-size 2 \
--dtype bfloat16 \
--max-model-len 8192

Step 5: Configure the OpenAI-Compatible API

vLLM's server is a drop-in replacement for the OpenAI API. Any client that calls openai.chat.completions.create(...) can point at your server by changing the base_url.

Python (OpenAI SDK)

from openai import OpenAI

client = OpenAI(
base_url="http://your-server-ip:8000/v1",
api_key="EMPTY", # vLLM does not enforce auth by default
)

response = client.chat.completions.create(
model="meta-llama/Meta-Llama-3-8B-Instruct",
messages=[
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "What is PagedAttention?"}
],
temperature=0.7,
max_tokens=512,
)

print(response.choices[0].message.content)

Streaming Responses

stream = client.chat.completions.create(
model="meta-llama/Meta-Llama-3-8B-Instruct",
messages=[{"role": "user", "content": "Write a haiku about GPU memory."}],
stream=True,
)

for chunk in stream:
if chunk.choices[0].delta.content:
print(chunk.choices[0].delta.content, end="", flush=True)

Adding API Key Authentication

For production, add a static API key to prevent unauthorized access:

docker run --runtime nvidia --gpus all \
-v ~/.cache/huggingface:/root/.cache/huggingface \
-p 8000:8000 \
-e HF_TOKEN=$HF_TOKEN \
--ipc=host \
vllm/vllm-openai:v0.6.4 \
--model meta-llama/Meta-Llama-3-8B-Instruct \
--api-key your-secret-api-key

Clients must then pass Authorization: Bearer your-secret-api-key in every request.


Step 6: Docker Compose for Production

Use Docker Compose to manage restarts, environment variables, and volume mounts cleanly:

# docker-compose.yml
services:
vllm:
image: vllm/vllm-openai:v0.6.4
runtime: nvidia
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: all
capabilities: [gpu]
volumes:
- huggingface_cache:/root/.cache/huggingface
ports:
- "8000:8000"
environment:
- HF_TOKEN=${HF_TOKEN}
ipc: host
restart: unless-stopped
command: >
--model meta-llama/Meta-Llama-3-8B-Instruct
--quantization awq
--dtype half
--max-model-len 8192
--gpu-memory-utilization 0.90
--api-key ${VLLM_API_KEY}

volumes:
huggingface_cache:

Start with:

HF_TOKEN=your_hf_token VLLM_API_KEY=your_api_key docker compose up -d

Step 7: Load Testing with Locust

Before going to production, stress-test your endpoint to find the concurrency ceiling.

Install Locust:

pip install locust

Create a locustfile.py:

import json
import random
from locust import HttpUser, task, between

PROMPTS = [
"Explain gradient descent in two sentences.",
"What is the difference between a transformer and an RNN?",
"Write a Python function to reverse a linked list.",
"Summarize the key ideas of the attention mechanism.",
]

class VLLMUser(HttpUser):
wait_time = between(0.5, 2)

@task
def chat_completion(self):
payload = {
"model": "meta-llama/Meta-Llama-3-8B-Instruct",
"messages": [{"role": "user", "content": random.choice(PROMPTS)}],
"max_tokens": 200,
"temperature": 0.7,
}
headers = {
"Content-Type": "application/json",
"Authorization": "Bearer your_api_key",
}
self.client.post("/v1/chat/completions", json=payload, headers=headers)

Run the load test (100 concurrent users, 10 user ramp per second):

locust -f locustfile.py \
--host http://your-server-ip:8000 \
--users 100 \
--spawn-rate 10 \
--run-time 2m \
--headless \
--only-summary

Interpreting results: On LLaMA 3 8B (AWQ, A100 80 GB) you should expect:

MetricTypical Result
Requests/sec15–30 RPS
Median latency (TTFT)200–400 ms
P99 latency1–3 s
Failure rate< 1 % (at 50 concurrent users)

If failure rate climbs above 1 % at your target concurrency, lower --max-model-len to free KV cache or upgrade to a higher-VRAM GPU.


Step 8: Prometheus Monitoring

vLLM exposes a /metrics endpoint in Prometheus format. Scrape it to track GPU utilization, queue depth, and throughput over time.

Key Metrics to Monitor

MetricDescription
vllm:num_requests_runningActive requests currently being processed
vllm:num_requests_waitingRequests queued waiting for GPU memory
vllm:gpu_cache_usage_percKV cache utilization (0–1). Alert at > 0.85
vllm:prompt_tokens_totalCumulative input tokens processed
vllm:generation_tokens_totalCumulative output tokens generated
vllm:time_to_first_token_secondsTTFT histogram — the most important latency metric
vllm:time_per_output_token_secondsPer-token generation latency

Docker Compose with Prometheus and Grafana

Extend your docker-compose.yml to add a monitoring stack:

services:
vllm:
# ... (same as above)

prometheus:
image: prom/prometheus:v2.51.0
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml
ports:
- "9090:9090"
restart: unless-stopped

grafana:
image: grafana/grafana:10.4.2
ports:
- "3000:3000"
environment:
- GF_SECURITY_ADMIN_PASSWORD=admin
volumes:
- grafana_data:/var/lib/grafana
restart: unless-stopped

volumes:
huggingface_cache:
grafana_data:

Create prometheus.yml:

global:
scrape_interval: 15s

scrape_configs:
- job_name: vllm
static_configs:
- targets: ["vllm:8000"]
metrics_path: /metrics

Access the Grafana dashboard at http://your-server-ip:3000 (default credentials: admin/admin). Import the vLLM community dashboard from the Grafana catalogue, or build your own panels using the metrics listed above.

Alerting Rules

Add this to a prometheus.rules.yml and reference it from prometheus.yml:

groups:
- name: vllm
rules:
- alert: VLLMHighQueueDepth
expr: vllm:num_requests_waiting > 20
for: 1m
labels:
severity: warning
annotations:
summary: "vLLM queue depth is high"
description: "{{ $value }} requests waiting. Consider scaling or reducing load."

- alert: VLLMHighKVCacheUsage
expr: vllm:gpu_cache_usage_perc > 0.85
for: 2m
labels:
severity: critical
annotations:
summary: "vLLM KV cache nearly full"
description: "KV cache at {{ $value | humanizePercentage }}. OOM risk."

vLLM Production Performance Benchmarks

The following benchmarks are from vLLM's official performance docs and community reproducible runs on A100 80 GB.

LLaMA 3 8B on A100 80 GB (FP16)

Concurrent UsersThroughput (tokens/s)TTFT P50 (ms)TTFT P99 (ms)
1~2,4003580
16~5,800120350
64~8,2004501,200
128~9,1009003,500

LLaMA 3 70B AWQ on A100 80 GB

Concurrent UsersThroughput (tokens/s)TTFT P50 (ms)TTFT P99 (ms)
1~800110250
8~2,100380900
32~3,4008502,800
64~3,8001,6006,200

Note: TTFT (time to first token) grows with queue depth. For latency-sensitive use cases, cap --max-num-seqs (the maximum number of concurrent sequences) to keep TTFT below your SLA threshold.


Choosing a GPU Server for Production

GPUVRAMLLaMA 3 8B (FP16)LLaMA 3 70BBest for
RTX 409024 GBAWQ only (tight)Dev / low-traffic
A10G24 GBAWQ only (tight)Cost-effective production
A100 40 GB40 GBAWQ onlyMid-scale production
A100 80 GB80 GB✓ (AWQ or FP16)Recommended for 70B
H100 80 GB80 GB✓ + FP8Maximum throughput

Liquid Web offers bare-metal A100 servers with NVMe SSD storage and unmetered bandwidth — well-suited for production LLM serving where consistent latency matters more than spot-instance pricing variability.


Frequently Asked Questions

Frequently Asked Questions

vLLM is an open-source LLM inference and serving library from UC Berkeley. It uses PagedAttention to manage GPU memory efficiently and continuous batching to maximize throughput, enabling dramatically higher concurrency than standard inference stacks like HuggingFace Transformers.

LLaMA 3 70B requires approximately 140 GB of VRAM in full FP16 precision (2× A100 80 GB or 2× H100 80 GB). With AWQ 4-bit quantization, it fits on a single A100 80 GB (~40 GB VRAM), with headroom for the KV cache.

Both use continuous batching and offer OpenAI-compatible APIs. vLLM uses PagedAttention for more flexible memory management, which tends to give higher throughput under heavy concurrent load. TGI is tightly integrated with the HuggingFace Hub and ecosystem. For most production use cases, vLLM is the more performant choice; TGI is worth considering if you are already invested in HuggingFace tooling.

Use AutoAWQ to quantize a model offline to 4-bit INT4 format, then pass --quantization awq when launching vLLM. Alternatively, use pre-quantized checkpoints from the HuggingFace Hub (search for '-awq' suffixed repos). For H100 hardware, vLLM also supports native FP8 quantization without a separate quantization step.

On an A100 80 GB, LLaMA 3 8B (FP16) reaches ~8,000–9,000 tokens/second throughput at 64–128 concurrent users with P99 TTFT under 4 seconds. LLaMA 3 70B AWQ peaks around 3,800 tokens/second at 64 concurrent users. Results vary with sequence length, system prompt size, and output length distribution.

Yes. vLLM exposes a fully OpenAI-compatible REST API at /v1/chat/completions, /v1/completions, and /v1/models. Any library or application using the OpenAI Python SDK can switch to vLLM by setting base_url to your vLLM server and using your model ID in place of gpt-4 or gpt-3.5-turbo.


Summary

TaskCommand / Tool
Start vLLM server (8B)docker run vllm/vllm-openai --model meta-llama/Meta-Llama-3-8B-Instruct
Quantize to AWQAutoAWQ or pre-quantized HuggingFace checkpoints (-awq suffix)
Test APIcurl localhost:8000/v1/chat/completions
Load testLocust with locustfile.py
MonitorPrometheus /metrics + Grafana dashboard
Production setupDocker Compose with restart policy and API key

vLLM gives you a production-grade LLM inference server with minimal configuration. The gap between a dev demo and a real production system comes down to quantization (to fit large models on affordable hardware), load testing (to find your concurrency ceiling before your users do), and monitoring (to catch KV cache saturation before it causes latency spikes).

For a dedicated GPU server with the VRAM headroom LLaMA 3 70B demands, Liquid Web's bare-metal GPU servers are a reliable option — no shared tenancy, consistent bandwidth, and the raw I/O performance that model weight loading benefits from.


Related guides: Build a Local RAG Chatbot with Ollama and ChromaDB · Run Gemma 3 Locally with Ollama

Common mistakes and fixes

vLLM container fails with CUDA out of memory.

Use AWQ or GPTQ quantized models. Reduce --max-model-len. For 8B, 24 GB GPU is sufficient with quantization. For 70B, use A100 80 GB with AWQ.

OpenAI SDK cannot connect to the vLLM endpoint.

Ensure base_url includes /v1, e.g. http://localhost:8000/v1. vLLM exposes OpenAI-compatible routes at /v1/chat/completions.

Requests queue and latency spikes under load.

Increase --max-num-seqs (default 256). For multi-GPU, add --tensor-parallel-size 2 or 4.

Model weights re-download on every container restart.

Mount ~/.cache/huggingface as a Docker volume to persist weights across restarts.