Deploying LLMs Locally: Ollama, llama.cpp, vLLM & Text Generation Inference
Complete guide to running LLMs locally — from quick experiments with Ollama to production-grade serving with vLLM and TGI.
Introduction
Local LLM deployment offers privacy, cost savings, and offline capability. This guide covers the four main approaches with production configurations.
Quick Comparison
| Tool | Best For | Ease of Use | Performance | Hardware |
|---|---|---|---|---|
| Ollama | Quick start, Mac/Win/Linux | ⭐⭐⭐⭐⭐ | Good | CPU/GPU |
| llama.cpp | CPU inference, quantization | ⭐⭐⭐ | Excellent (CPU) | CPU/GPU |
| vLLM | High-throughput serving | ⭐⭐⭐ | Best (GPU) | GPU required |
| TGI | Enterprise, OpenAI-compatible | ⭐⭐⭐ | Excellent | GPU required |
Part 1: Ollama — Easiest Start
Installation & Basics
# macOS
brew install ollama
# Linux
curl -fsSL https://ollama.com/install.sh | sh
# Windows
# Download from https://ollama.com/download
# Start service
ollama serve
# Pull model
ollama pull llama3.1:8b
ollama pull codellama:13b
ollama pull nemotron-mini:4b
Python Integration
# ollama/client.py
import ollama
from typing import List, Dict, Optional
class OllamaClient:
def __init__(self, host: str = "http://localhost:11434"):
self.client = ollama.Client(host=host)
def list_models(self) -> List[Dict]:
return self.client.list()["models"]
def pull(self, model: str) -> None:
self.client.pull(model)
def generate(
self,
model: str,
prompt: str,
system: str = None,
temperature: float = 0.7,
top_p: float = 0.9,
num_predict: int = -1,
stream: bool = False
) -> str:
response = self.client.generate(
model=model,
prompt=prompt,
system=system,
options={
"temperature": temperature,
"top_p": top_p,
"num_predict": num_predict,
},
stream=stream
)
return response["response"]
def chat(
self,
model: str,
messages: List[Dict],
stream: bool = False
):
return self.client.chat(model=model, messages=messages, stream=stream)
def embeddings(self, model: str, prompt: str) -> List[float]:
return self.client.embeddings(model=model, prompt=prompt)["embedding"]
# Usage
client = OllamaClient()
response = client.generate("llama3.1:8b", "Explain LoRA in 3 sentences")
print(response)
# Streaming chat
for chunk in client.chat("llama3.1:8b", [
{"role": "user", "content": "Write a haiku about GPUs"}
], stream=True):
print(chunk["message"]["content"], end="", flush=True)
Custom Modelfiles
# Modelfile for custom model
FROM llama3.1:8b
# System prompt
SYSTEM """You are a senior Python developer. Write clean, typed, documented code."""
# Parameters
PARAMETER temperature 0.3
PARAMETER top_p 0.9
PARAMETER num_ctx 8192
PARAMETER num_predict 2048
# Template (optional)
TEMPLATE """{{ .System }}User: {{ .Prompt }}Assistant: """
# Save and create
# ollama create python-expert -f Modelfile
Ollama API Server
# ollama/api_server.py
from fastapi import FastAPI
from pydantic import BaseModel
import ollama
app = FastAPI()
class GenerateRequest(BaseModel):
model: str
prompt: str
system: str = None
temperature: float = 0.7
stream: bool = False
@app.post("/generate")
async def generate(req: GenerateRequest):
if req.stream:
return StreamingResponse(
stream_generate(req),
media_type="text/event-stream"
)
response = ollama.generate(
model=req.model,
prompt=req.prompt,
system=req.system,
options={"temperature": req.temperature}
)
return {"response": response["response"]}
async def stream_generate(req: GenerateRequest):
stream = ollama.generate(
model=req.model,
prompt=req.prompt,
system=req.system,
options={"temperature": req.temperature},
stream=True
)
for chunk in stream:
yield f"data: {chunk['response']}\n\n"
# Run: uvicorn api_server:app --host 0.0.0.0 --port 8000
Part 2: llama.cpp — CPU Optimization
Building from Source
# Ubuntu/Debian
sudo apt update && sudo apt install -y build-essential cmake git
# Clone
git clone https://github.com/ggerganov/llama.cpp
cd llama.cpp
# CPU-only build (AVX2)
make LLAMA_CUBLAS=0
# With CUDA (NVIDIA GPU)
make LLAMA_CUBLAS=1
# With Metal (Apple Silicon)
make LLAMA_METAL=1
# With Vulkan (AMD/Intel GPU)
make LLAMA_VULKAN=1
Model Conversion & Quantization
# Download HF model
huggingface-cli download meta-llama/Meta-Llama-3.1-8B --local-dir llama3.1-8b
# Convert to GGUF
python convert_hf_to_gguf.py llama3.1-8b --outfile llama3.1-8b.f16.gguf --outtype f16
# Quantize (smaller, faster, slight quality loss)
# Q4_K_M = best balance (4-bit)
./llama-quantize llama3.1-8b.f16.gguf llama3.1-8b.Q4_K_M.gguf Q4_K_M
# Other quantization levels
# Q2_K - smallest, lowest quality
# Q3_K_M - small, decent
# Q4_K_M - recommended default
# Q5_K_M - higher quality
# Q6_K - near original
# Q8_0 - almost lossless
Running llama.cpp Server
# Basic server
./llama-server -m llama3.1-8b.Q4_K_M.gguf -c 8192 --host 0.0.0.0 --port 8080
# With GPU offloading (Metal)
./llama-server -m llama3.1-8b.Q4_K_M.gguf -c 8192 --host 0.0.0.0 --port 8080 -ngl 99
# With CUDA
./llama-server -m llama3.1-8b.Q4_K_M.gguf -c 8192 --host 0.0.0.0 --port 8080 -ngl 99 -mgpu 0
# Performance options
./llama-server \
-m model.gguf \
-c 8192 \ # Context size
-b 512 \ # Batch size
-ub 512 \ # UBatch size
-ngl 99 \ # GPU layers (-1 = all)
-t 8 \ # Threads
--flash-attn \ # Flash attention
--mlock \ # Lock in RAM
--no-mmap \ # Don't mmap (for containers)
Python Client
# llama_cpp/client.py
from llama_cpp import Llama
from typing: List, Dict
import json
class LlamaCppClient:
def __init__(
self,
model_path: str,
n_ctx: int = 8192,
n_gpu_layers: int = -1,
n_threads: int = 8,
verbose: bool = False
):
self.llm = Llama(
model_path=model_path,
n_ctx=n_ctx,
n_gpu_layers=n_gpu_layers,
n_threads=n_threads,
verbose=verbose,
chat_format="llama-3", # or "chatml", "mistral-instruct"
)
def generate(
self,
prompt: str,
max_tokens: int = 512,
temperature: float = 0.7,
top_p: float = 0.9,
stop: List[str] = None,
stream: bool = False
):
return self.llm(
prompt=prompt,
max_tokens=max_tokens,
temperature=temperature,
top_p=top_p,
stop=stop,
stream=stream
)
def chat(
self,
messages: List[Dict],
max_tokens: int = 512,
temperature: float = 0.7,
stream: bool = False
):
return self.llm.create_chat_completion(
messages=messages,
max_tokens=max_tokens,
temperature=temperature,
stream=stream
)
def embeddings(self, texts: List[str]) -> List[List[float]]:
return [self.llm.embed(text) for text in texts]
# Usage
client = LlamaCppClient("./llama3.1-8b.Q4_K_M.gguf", n_gpu_layers=99)
response = client.generate("Explain quantization:")
print(response["choices"][0]["text"])
Part 3: vLLM — High-Throughput Serving
Installation
# Requires NVIDIA GPU with CUDA 11.8+
pip install vllm
# Or with specific CUDA
pip install vllm --extra-index-url https://download.pytorch.org/whl/cu118
# For AMD ROCm
pip install vllm --extra-index-url https://download.pytorch.org/whl/rocm5.6
Basic Server
# OpenAI-compatible server
vllm serve meta-llama/Meta-Llama-3.1-8B-Instruct \
--host 0.0.0.0 \
--port 8000 \
--dtype auto \
--max-model-len 8192 \
--gpu-memory-utilization 0.9 \
--tensor-parallel-size 1 \
--enforce-eager
# With quantization (AWQ/GPTQ)
vllm serve TheBloke/Llama-3.1-8B-Instruct-AWQ \
--quantization awq \
--max-model-len 8192
# Multi-GPU tensor parallel
vllm serve meta-llama/Meta-Llama-3.1-70B-Instruct \
--tensor-parallel-size 4 \
--gpu-memory-utilization 0.9
Python Client (OpenAI Compatible)
# vllm/client.py
from openai import OpenAI
from typing: List, Dict
import asyncio
class VLLMClient:
def __init__(self, base_url: str = "http://localhost:8000/v1"):
self.client = OpenAI(
base_url=base_url,
api_key="dummy" # Not used by vLLM
)
def list_models(self):
return self.client.models.list()
def generate(
self,
model: str,
prompt: str,
max_tokens: int = 512,
temperature: float = 0.7,
top_p: float = 0.9,
stop: List[str] = None,
stream: bool = False
):
return self.client.completions.create(
model=model,
prompt=prompt,
max_tokens=max_tokens,
temperature=temperature,
top_p=top_p,
stop=stop,
stream=stream
)
def chat(
self,
model: str,
messages: List[Dict],
max_tokens: int = 512,
temperature: float = 0.7,
stream: bool = False
):
return self.client.chat.completions.create(
model=model,
messages=messages,
max_tokens=max_tokens,
temperature=temperature,
stream=stream
)
async def achat(self, *args, **kwargs):
return await self.client.chat.completions.create(*args, **kwargs)
# Usage
client = VLLMClient()
# Chat completion
response = client.chat("meta-llama/Meta-Llama-3.1-8B-Instruct", [
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "Write a Python decorator for retry logic"}
])
print(response.choices[0].message.content)
# Streaming
for chunk in client.chat(model, messages, stream=True):
if chunk.choices[0].delta.content:
print(chunk.choices[0].delta.content, end="", flush=True)
Advanced vLLM Features
# vllm/advanced.py
from vllm import LLM, SamplingParams
from vllm.lora.request import LoRARequest
# Offline inference (no server)
llm = LLM(
model="meta-llama/Meta-Llama-3.1-8B-Instruct",
tensor_parallel_size=1,
gpu_memory_utilization=0.9,
max_model_len=8192,
enable_lora=True,
max_lora_rank=64,
)
sampling_params = SamplingParams(
temperature=0.7,
top_p=0.9,
max_tokens=512,
stop=["</s>", "<|eot_id|>"],
)
# Batch generation
prompts = [
"Write a function for: ",
"Explain the concept of: ",
"Debug this code: ",
]
outputs = llm.generate(prompts, sampling_params)
# With LoRA adapter
lora_outputs = llm.generate(
prompts,
sampling_params,
lora_request=LoRARequest("sql_adapter", 1, "./sql-lora-adapter")
)
# Structured output (guided decoding)
from vllm import SamplingParams
from pydantic import BaseModel
class CodeResponse(BaseModel):
explanation: str
code: str
language: str
guided_params = SamplingParams(
temperature=0.3,
max_tokens=1024,
guided_json=CodeResponse.model_json_schema()
)
output = llm.generate(["Write a binary search in Python"], guided_params)
print(output[0].outputs[0].text) # Valid JSON matching schema
vLLM Performance Tuning
# vllm/tuning.sh
# Key parameters:
# --max-model-len: Context window (affects KV cache memory)
# --gpu-memory-utilization: Fraction of GPU memory for KV cache (0.8-0.95)
# --block-size: KV cache block size (16 or 32)
# --max-num-seqs: Max concurrent sequences
# --max-num-batched-tokens: Max tokens per batch
# --enable-chunked-prefill: For long contexts
# --disable-log-stats: Reduce logging overhead
# High-throughput config
vllm serve meta-llama/Meta-Llama-3.1-8B-Instruct \
--max-model-len 4096 \
--gpu-memory-utilization 0.95 \
--max-num-seqs 256 \
--max-num-batched-tokens 8192 \
--enable-chunked-prefill \
--disable-log-stats \
--block-size 16
# Low-latency config
vllm serve meta-llama/Meta-Llama-3.1-8B-Instruct \
--max-model-len 8192 \
--gpu-memory-utilization 0.8 \
--max-num-seqs 16 \
--enforce-eager \
--disable-log-stats
Part 4: Text Generation Inference (TGI)
Installation
# Docker (recommended)
docker run --gpus all --shm-size 1g \
-p 8080:80 \
-v $PWD/data:/data \
ghcr.io/huggingface/text-generation-inference:latest \
--model-id meta-llama/Meta-Llama-3.1-8B-Instruct \
--max-input-length 4096 \
--max-total-tokens 8192 \
--max-batch-prefill-tokens 4096
# With quantization
docker run --gpus all --shm-size 1g \
-p 8080:80 \
ghcr.io/huggingface/text-generation-inference:latest \
--model-id TheBloke/Llama-3.1-8B-Instruct-AWQ \
--quantize awq
# Multi-GPU
docker run --gpus all --shm-size 1g \
-p 8080:80 \
ghcr.io/huggingface/text-generation-inference:latest \
--model-id meta-llama/Meta-Llama-3.1-70B-Instruct \
--num-shard 4
Python Client
# tgi/client.py
from text_generation import Client
from typing: List, Dict
import asyncio
class TGIClient:
def __init__(self, url: str = "http://localhost:8080"):
self.client = Client(url, timeout=60)
def generate(
self,
prompt: str,
max_new_tokens: int = 512,
temperature: float = 0.7,
top_p: float = 0.9,
stop_sequences: List[str] = None,
stream: bool = False
):
return self.client.generate(
prompt,
max_new_tokens=max_new_tokens,
temperature=temperature,
top_p=top_p,
stop_sequences=stop_sequences,
stream=stream
)
async def agenerate(self, *args, **kwargs):
return await self.client.generate_async(*args, **kwargs)
# Usage
client = TGIClient()
response = client.generate("Explain RAG:")
print(response.generated_text)
Part 5: Model Selection Guide
Recommended Models by Use Case
# models/recommendations.py
RECOMMENDED_MODELS = {
"general_chat": [
"meta-llama/Meta-Llama-3.1-8B-Instruct",
"mistralai/Mistral-7B-Instruct-v0.3",
"google/gemma-2-9b-it",
"Qwen/Qwen2.5-7B-Instruct",
],
"coding": [
"deepseek-ai/deepseek-coder-6.7b-instruct",
"Qwen/Qwen2.5-Coder-7B-Instruct",
"bigcode/starcoder2-7b",
"codellama/CodeLlama-13b-Instruct-hf",
],
"reasoning": [
"deepseek-ai/DeepSeek-R1-Distill-Llama-8B",
"Qwen/QwQ-32B-Preview",
],
"multilingual": [
"Qwen/Qwen2.5-7B-Instruct",
"google/gemma-2-9b-it",
"CohereForAI/aya-23-8B",
],
"small_efficient": [
"microsoft/Phi-3.5-mini-instruct",
"google/gemma-2-2b-it",
"Qwen/Qwen2.5-1.5B-Instruct",
],
"long_context": [
"gradientai/Llama-3-8B-Instruct-Gradient-1048k",
"Qwen/Qwen2.5-7B-Instruct-1M",
],
}
# Quantization recommendations
QUANTIZATION_GUIDE = {
"4bit": "Q4_K_M (llama.cpp) / AWQ (vLLM/TGI) — best balance",
"8bit": "Q8_0 / GPTQ — near lossless",
"2bit": "Q2_K — only for extreme constraints",
}
Hardware Requirements
# models/hardware.py
MODEL_REQUIREMENTS = {
"7B_f16": {"vram_gb": 14, "ram_gb": 16, "disk_gb": 14},
"7B_q4": {"vram_gb": 6, "ram_gb": 8, "disk_gb": 4.5},
"7B_q8": {"vram_gb": 10, "ram_gb": 12, "disk_gb": 8},
"13B_f16": {"vram_gb": 26, "ram_gb": 32, "disk_gb": 26},
"13B_q4": {"vram_gb": 10, "ram_gb": 16, "disk_gb": 8},
"70B_f16": {"vram_gb": 140, "ram_gb": 160, "disk_gb": 140},
"70B_q4": {"vram_gb": 48, "ram_gb": 64, "disk_gb": 40},
}
def estimate_requirements(model_size: str, quantization: str) -> dict:
"""Estimate hardware needs."""
key = f"{model_size}_{quantization}"
return MODEL_REQUIREMENTS.get(key, {"error": "Unknown combination"})
# Apple Silicon unified memory
# M1/M2/M3 Max (96GB) → 70B q4
# M1/M2/M3 Pro (36GB) → 13B q4 or 7B f16
# M1/M2/M3 Base (8-24GB) → 7B q4
Part 6: Production Deployment
Docker Compose Stack
# deployment/docker-compose.yml
version: '3.8'
services:
# vLLM for high throughput
vllm:
image: vllm/vllm-openai:latest
runtime: nvidia
environment:
- HF_TOKEN=${HF_TOKEN}
command: >
--model meta-llama/Meta-Llama-3.1-8B-Instruct
--max-model-len 8192
--gpu-memory-utilization 0.9
--tensor-parallel-size 1
--host 0.0.0.0
--port 8000
ports:
- "8000:8000"
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: 1
capabilities: [gpu]
# TGI for OpenAI compatibility
tgi:
image: ghcr.io/huggingface/text-generation-inference:latest
runtime: nvidia
command: >
--model-id meta-llama/Meta-Llama-3.1-8B-Instruct
--max-input-length 4096
--max-total-tokens 8192
ports:
- "8080:80"
shm_size: 1g
# Ollama for local development
ollama:
image: ollama/ollama:latest
ports:
- "11434:11434"
volumes:
- ollama_data:/root/.ollama
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: 1
capabilities: [gpu]
volumes:
ollama_data:
Kubernetes Deployment
# deployment/k8s-vllm.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: vllm-llama3-8b
spec:
replicas: 2
selector:
matchLabels:
app: vllm
template:
metadata:
labels:
app: vllm
spec:
containers:
- name: vllm
image: vllm/vllm-openai:latest
command: ["python", "-m", "vllm.entrypoints.openai.api_server"]
args:
- "--model=meta-llama/Meta-Llama-3.1-8B-Instruct"
- "--max-model-len=8192"
- "--gpu-memory-utilization=0.9"
- "--host=0.0.0.0"
- "--port=8000"
ports:
- containerPort: 8000
resources:
limits:
nvidia.com/gpu: 1
requests:
nvidia.com/gpu: 1
memory: "16Gi"
env:
- name: HF_TOKEN
valueFrom:
secretKeyRef:
name: hf-token
key: token
---
apiVersion: v1
kind: Service
metadata:
name: vllm-service
spec:
selector:
app: vllm
ports:
- port: 8000
targetPort: 8000
type: LoadBalancer
Load Balancing & Autoscaling
# deployment/load_balancer.py
import httpx
from typing: List
import random
class LLMLoadBalancer:
"""Round-robin load balancer with health checks."""
def __init__(self, endpoints: List[str]):
self.endpoints = endpoints
self.healthy = set(endpoints)
self.current = 0
async def health_check(self):
"""Periodic health check."""
async with httpx.AsyncClient(timeout=5) as client:
for ep in self.endpoints:
try:
resp = await client.get(f"{ep}/health")
if resp.status_code == 200:
self.healthy.add(ep)
else:
self.healthy.discard(ep)
except:
self.healthy.discard(ep)
def get_endpoint(self) -> str:
healthy = list(self.healthy)
if not healthy:
raise Exception("No healthy endpoints")
ep = healthy[self.current % len(healthy)]
self.current += 1
return ep
async def generate(self, *args, **kwargs):
ep = self.get_endpoint()
async with httpx.AsyncClient(base_url=ep, timeout=60) as client:
return await client.post("/v1/completions", json=kwargs)
Part 7: Monitoring & Observability
Prometheus Metrics
# monitoring/prometheus_metrics.py
from prometheus_client import Counter, Histogram, Gauge
import time
LLM_REQUESTS = Counter(
"llm_requests_total",
"Total LLM requests",
["model", "status"]
)
LLM_LATENCY = Histogram(
"llm_latency_seconds",
"Request latency",
["model"],
buckets=[0.1, 0.5, 1, 2, 5, 10, 30, 60]
)
LLM_TOKENS = Counter(
"llm_tokens_total",
"Tokens generated",
["model", "type"] # prompt, completion
)
LLM_GPU_MEMORY = Gauge(
"llm_gpu_memory_bytes",
"GPU memory usage",
["model", "gpu_id"]
)
LLM_QUEUE_SIZE = Gauge(
"llm_queue_size",
"Request queue size",
["model"]
)
class MonitoredLLM:
def __init__(self, client, model_name: str):
self.client = client
self.model_name = model_name
def generate(self, prompt: str, **kwargs):
start = time.time()
try:
result = self.client.generate(prompt, **kwargs)
LLM_REQUESTS.labels(model=self.model_name, status="success").inc()
return result
except Exception as e:
LLM_REQUESTS.labels(model=self.model_name, status="error").inc()
raise
finally:
LLM_LATENCY.labels(model=self.model_name).observe(time.time() - start)
Part 8: Benchmarking
Throughput Test
# benchmark/throughput.py
import asyncio
import time
from statistics import mean, stdev
from vllm import LLM, SamplingParams
async def benchmark_throughput(
model: str,
num_requests: int = 100,
prompt_len: int = 512,
output_len: int = 256,
concurrency: int = 10
):
"""Benchmark throughput and latency."""
llm = LLM(model=model, gpu_memory_utilization=0.9)
sampling_params = SamplingParams(
max_tokens=output_len,
temperature=0.7
)
prompt = "x" * prompt_len
prompts = [prompt] * num_requests
# Warmup
llm.generate([prompt], sampling_params)
# Benchmark
start = time.time()
outputs = llm.generate(prompts, sampling_params)
total_time = time.time() - start
total_tokens = sum(len(o.outputs[0].token_ids) for o in outputs)
return {
"total_time_sec": total_time,
"requests_per_sec": num_requests / total_time,
"tokens_per_sec": total_tokens / total_time,
"avg_latency_ms": (total_time / num_requests) * 1000,
"total_tokens": total_tokens,
}
# Run
results = asyncio.run(benchmark_throughput("meta-llama/Meta-Llama-3.1-8B-Instruct"))
print(f"Throughput: {results['requests_per_sec']:.1f} req/s")
print(f"Tokens/sec: {results['tokens_per_sec']:.0f}")
Part 9: Cost Analysis
Local vs Cloud
# benchmark/cost_analysis.py
CLOUD_PRICING = {
"gpt-4o": {"input": 2.50, "output": 10.00}, # per 1M tokens
"gpt-4o-mini": {"input": 0.15, "output": 0.60},
"claude-3.5-sonnet": {"input": 3.00, "output": 15.00},
}
GPU_COSTS = {
"RTX 4090 (24GB)": {"purchase": 1600, "power_w": 450, "vram": 24},
"RTX 3090 (24GB)": {"purchase": 800, "power_w": 350, "vram": 24},
"A100 40GB": {"hourly": 1.50, "power_w": 400, "vram": 40},
"A100 80GB": {"hourly": 3.00, "power_w": 400, "vram": 80},
"H100 80GB": {"hourly": 8.00, "power_w": 700, "vram": 80},
}
def calculate_break_even(
model: str,
requests_per_day: int,
avg_input_tokens: int,
avg_output_tokens: int,
gpu: str = "RTX 4090"
) -> dict:
"""Calculate when local becomes cheaper than cloud."""
cloud = CLOUD_PRICING.get(model, {"input": 0, "output": 0})
daily_cloud_cost = (
requests_per_day * avg_input_tokens * cloud["input"] +
requests_per_day * avg_output_tokens * cloud["output"]
) / 1_000_000
gpu_info = GPU_COSTS[gpu]
if "purchase" in gpu_info:
# Electricity cost (assume $0.12/kWh)
daily_power_cost = (gpu_info["power_w"] * 24 / 1000) * 0.12
daily_local_cost = daily_power_cost
break_even_days = gpu_info["purchase"] / (daily_cloud_cost - daily_local_cost)
else:
daily_local_cost = gpu_info["hourly"] * 24
break_even_days = float('inf') if daily_local_cost >= daily_cloud_cost else 0
return {
"daily_cloud_cost": daily_cloud_cost,
"daily_local_cost": daily_local_cost,
"monthly_savings": (daily_cloud_cost - daily_local_cost) * 30,
"break_even_days": max(0, break_even_days),
}
# Example: 1000 requests/day, 1k in / 500 out tokens
print(calculate_break_even("gpt-4o-mini", 1000, 1000, 500, "RTX 4090"))
# {'daily_cloud_cost': 0.45, 'daily_local_cost': 1.30, 'monthly_savings': -25.5, 'break_even_days': 0}
# Local cheaper at scale, cloud cheaper for low volume
Part 10: Troubleshooting
Common Issues
# troubleshooting/common.py
TROUBLESHOOTING = {
"OOM": {
"vllm": [
"Reduce --gpu-memory-utilization (0.8 → 0.7)",
"Reduce --max-model-len",
"Enable --enable-chunked-prefill",
"Use quantization (AWQ/GPTQ)",
"Reduce --max-num-seqs",
],
"llama.cpp": [
"Reduce -c (context size)",
"Reduce -ngl (GPU layers)",
"Use smaller quantization (Q4 → Q3)",
"Add --mlock to prevent swap",
],
"tgi": [
"Reduce --max-total-tokens",
"Reduce --max-batch-prefill-tokens",
"Enable --quantize",
],
},
"Slow_Inference": {
"vllm": [
"Increase --gpu-memory-utilization",
"Disable --enforce-eager",
"Increase --max-num-batched-tokens",
"Use tensor parallel for large models",
],
"llama.cpp": [
"Increase -ngl (more GPU layers)",
"Use -ngl 99 (all layers GPU)",
"Enable --flash-attn",
"Increase -t (threads) for CPU",
],
},
"Quality_Loss": {
"quantization": [
"Try Q4_K_M instead of Q4_0",
"Use Q5_K_M or Q6_K for better quality",
"Avoid Q2_K/Q3_K for reasoning tasks",
"Test with your specific prompts",
],
},
}
Resources
- Ollama Documentation
- llama.cpp GitHub
- vLLM Documentation
- TGI Documentation
- GGUF Models
- AWQ Models
- Local LLM Leaderboard
Start with Ollama for development, graduate to vLLM/TGI for production. The best local LLM setup is the one that runs on your hardware.
Top comments (0)