Skip to main content

Deploying Open-Source LLMs on Resource-Constrained Infrastructure

Published: July 1, 2025 Updated: June 24, 2026 Larry Qu 19 min read
Table of Contents

Startups and small dev teams are facing high per-token costs from commercial LLM APIs during R&D. Self-hosting open-source LLMs on local or small cloud infrastructure can dramatically reduce cost while giving you privacy and control.


Introduction

This guide helps engineers and technical decision-makers choose, configure, and deploy open-source LLMs on constrained hardware — from CPU-only laptops or servers to single small GPUs (8–16GB). It catalogs practical options and trade-offs, shows quick-start commands, and provides a decision framework so you can pick the right stack for your team.

Key goals:

  • Minimize API costs during experimentation and internal usage
  • Keep latency and throughput acceptable for R&D and low-volume production
  • Preserve data privacy and avoid vendor lock-in

Hardware profiles and expectations 🔧

Pick the right target class first. Here are the common scenarios and what to expect:

  • Localhost / CPU-only (8–32 vCPU, 16–64 GB RAM)
    • Best for prototyping, batch jobs, and low-concurrency tooling
    • Can run 1–4B parameter models with quantization (GGUF / GPTQ / AWQ)
  • Small GPU (8–16 GB VRAM, consumer RTX or equivalent)
    • Good for interactive apps and small team usage
    • Ideal for 3–13B models with 4-bit/8-bit quantization and optimized runtimes
  • Cloud micro GPU (T4, A10, etc.)
    • Elastic option for short bursts or staging environments
    • Consider spot instances for cost savings

Hardware decision quick rules:

  • If you need sub-second latency across multiple users, go small-GPU + vLLM/TGI.
  • If you mostly do offline batch processing or experimentation, CPU-only + llama.cpp/OpenVINO is fine.

Deployment methods and frameworks (what to use and when)

This section catalogs the most practical, battle-tested tools and where they fit.

Inference engines and runtimes

  • llama.cpp (GGUF) — Extremely portable, C/C++ runtime optimized for CPU and lightweight GPU offload. Great for local testing and CPU-only servers. Works well with quantized GGUF models.

  • vLLM — Designed for low-latency, high-concurrency serving on GPUs. Implements memory-efficient attention and batching (PagedAttention) to maximize throughput on limited VRAM.

  • TextGenerationInference (TGI) — NVIDIA-backed server optimized for GPU inference with support for multi-instance GPU serving and model optimizations. Good when you can run NVIDIA drivers and want a production-focused server.

  • Ollama — Developer-friendly, OpenAI-compatible API wrapper that simplifies running many GGUF models locally or on a single host.

  • LocalAI — Lightweight model server with built-in support for multiple backends (llama.cpp, ggml, etc.) and an OpenAI-compatible API, useful for self-hosting with minimal ops.

  • OpenVINO / OVMS (OpenVINO Model Server) — Intel-optimized inference and model server for CPU-first deployments, often outperforming generic runtimes on Intel hardware.

Rust ecosystem & native inference

Rust is increasingly a practical choice for deploying LLMs, especially when you value low-overhead, single-binary distribution, memory safety, and predictable performance on CPU-first hosts. The Rust ecosystem now offers native bindings and runtimes that can load quantized GGUF/ggml models for CPU inference, run LibTorch-backed models for GPU inference, or consume ONNX artifacts for optimized CPU paths.

  • Key crates & tools

    • llm / llama-rs / ggml-rs — Native GGUF / ggml loaders for CPU-optimized inference and quantized models.
    • tch-rs (LibTorch bindings) — Use when you need CUDA-backed GPU inference inside a Rust binary.
    • rust-bert — Transformer utilities and examples built on tch-rs.
    • onnxruntime / tract — Stable ONNX runtimes for CPU and (where supported) CUDA/ONNX-TRT acceleration.
    • huggingface-tokenizers — High-performance tokenizers in Rust for low-latency preprocessing.
  • Deployment patterns

    • CPU-only service: Convert / quantize your model with Python tools (GPTQ / AWQ → GGUF) then load it in a Rust service (e.g., llm / llama-rs) and expose an HTTP API with axum/hyper.
    • GPU-enabled Rust binary: Use tch-rs with LibTorch to run models on CUDA if you prefer a single Rust process (note: introduces LibTorch/CUDA dependencies).
    • ONNX route: Convert model to ONNX and use onnxruntime/tract for CPU-optimized inference on server CPUs.
    • Hybrid architecture: Keep GPU-optimized Python services (vLLM/TGI) for heavy lifting and use a Rust gateway for routing, caching, authentication, and low-overhead pre/post-processing.
  • Caveats & practical notes

    • Most model conversion and cutting-edge quantization tooling still lives in the Python ecosystem; a common workflow is: convert/quantize in Python → ship GGUF/ONNX → load in Rust.
    • GPU-specific, advanced quantization (e.g., bitsandbytes INT8 workflows) is more mature in Python; use Rust where operational simplicity and small binary size matter.
    • The Rust ML ecosystem is rapidly maturing; expect more direct tooling and wrappers over the next year.

Example: high-level Rust server sketch (conceptual)

Below is an illustrative sketch showing the pieces (tokenizer + GGUF model loader + HTTP endpoint). Refer to the specific crate docs for exact APIs — this is intentionally high-level.

Cargo.toml (deps):

[dependencies]
axum = "0.7"
tokio = { version = "1", features = ["rt-multi-thread", "macros"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
huggingface-tokenizers = "0.*"
llm = "*" # or use the specific crate you choose (llama-rs / ggml-rs)

main.rs (sketch):

use axum::{routing::post, Json, Router};
use serde::{Deserialize, Serialize};
use tokenizers::Tokenizer;
// use llm::Model; // pseudo import — consult crate docs for exact types

#[derive(Deserialize)]
struct InferenceRequest { prompt: String }

#[derive(Serialize)]
struct InferenceResponse { output: String }

#[tokio::main]
async fn main() {
    // Load tokenizer and model (pseudo-code)
    let tokenizer = Tokenizer::from_file("./tokenizer.json").unwrap();
    // let model = llm::load_model("./model.gguf").unwrap();

    let app = Router::new().route("/v1/generate", post(move |Json(req): Json<InferenceRequest>| async move {
        // Tokenize, run model, decode – pseudocode
        let tokens = tokenizer.encode(req.prompt, true).unwrap();
        // let out = model.generate(&tokens);
        Json(InferenceResponse { output: "<model output>".to_string() })
    }));

    axum::Server::bind(&"0.0.0.0:8080".parse().unwrap())
        .serve(app.into_make_service())
        .await
        .unwrap();
}

This pattern gives you a compact, production-ready artifact: a multi-threaded Rust binary with minimal runtime overhead. For deployment, use a multi-stage Docker build (compile on a builder, copy the static binary into a minimal runtime image) to keep images small and secure.

Quantization and compression techniques

  • GGUF — A model file format often used with llama.cpp for compact CPU-friendly inference. Usually combined with 4-bit or 8-bit quantization.

  • GPTQ — Post-training quantization that produces high-quality 4-bit-aware models; commonly used to fit larger models into small GPUs.

  • AWQ (Approximate Weight Quantization) — A newer quantization technique that often improves quality for 3–13B models when using 4-bit formats.

  • bitsandbytes (bnb) — A PyTorch extension that enables 8-bit / 4-bit model loading and training on GPUs. Frequently used with Transformers-based stacks (load_in_8bit=True, device_map='auto').

Notes on quality vs size: 8-bit quantization tends to preserve model quality better than aggressive 4-bit methods, but 4-bit gains you more memory savings. Test your downstream tasks (LLM reasoning, instruction following) — quantization effects often vary by model and task.


Memory requirements & optimization strategies 🧠

  • Model size baseline (floating point)

    • 3B model (FP16): ~6–8 GB
    • 7B model (FP16): ~12–16 GB
    • 13B model (FP16): ~24–30 GB
  • After quantization (rough expectations)

    • 4-bit (GPTQ/AWQ): ~25–35% of FP16 size (very rough)
    • 8-bit / 16-bit: intermediate savings
  • Optimization techniques

    • Offload / CPU+GPU hybrid: Keep hot layers on GPU, offload embeddings or blocks to RAM
    • Sharding: Split model across devices if you have multiple small GPUs
    • KV cache management: Use runtimes that support streaming/eviction for long contexts to avoid blowing VRAM
    • Batching / dynamic batching: Aggregate requests to increase throughput but watch latency

Practical trade-offs: speed, memory, and quality

  • The smaller the model, the faster and cheaper it is, but with diminishing returns for complex reasoning tasks.
  • 4-bit quantization reduces memory the most but can introduce subtle quality regressions; evaluate per-task.
  • CPU deployments save cost but will be slower; use OpenVINO or llama.cpp with GGUF to get reasonable latency.

Quick-starts & example commands (actionable) ⚡

1) CPU-only: Running a GGUF model with llama.cpp

  1. Convert an HF model to GGUF (check llama.cpp repo conversions for your model format).
# Example: run a local gguf file with llama.cpp
git clone https://github.com/ggerganov/llama.cpp
cd llama.cpp
make
./main -m /path/to/model.gguf -p "Write a short summary of deployment options."
  1. Expose an HTTP API with a small wrapper or use LocalAI / Ollama for an OpenAI-compatible server.

2) Small GPU: vLLM (Docker)

# Pull image and run (example, verify image/tags in docs)
docker run --gpus all -p 8000:8000 ghcr.io/vllm/vllm:latest \
  vllm serve --model /models/your-quantized-model

vLLM provides a fast HTTP inference endpoint and can manage batching and multiple concurrent sessions efficiently.

3) OpenVINO CPU optimization

# Convert with Optimum-Intel and run the OpenVINO Model Server
python -m optimum.intel.openvino.convert --model_name <HF_MODEL> --output_dir ./ov_model
# Then configure OVMS to serve the model

(OpenVINO tool names and flags change over time—consult the Optimum-Intel docs for up-to-date commands.)

4) Using bitsandbytes in a Transformers app (GPU INT8)

from transformers import AutoModelForCausalLM, AutoTokenizer
model = AutoModelForCausalLM.from_pretrained(
    'your-model',
    load_in_8bit=True,
    device_map='auto'
)

5) Quick LocalAPI: Ollama / LocalAI

  • Ollama: ollama run <model> then call http://localhost:11434/v1/chat/completions
  • LocalAI: run its binary or Docker image and point at your GGUF models; it exposes an OpenAI-compatible API

Decision Tree for Runtime Selection

Hardware available?
├── GPU (NVIDIA)
│   ├── >100 concurrent users → vLLM or SGLang
│   ├── 10-100 users → vLLM (default)
│   └── <10 users / prototyping → Ollama
├── CPU (Intel)
│   ├── Intel Xeon with AMX → OpenVINO
│   └── AMD/ARM → llama.cpp (GGUF)
├── Apple Silicon
│   └── llama.cpp (via Ollama or MLX)
└── Edge device
    └── llama.cpp (GGUF Q4_K_M or smaller)

Comparison matrix — method suitability

Method / Stack CPU-only Small GPU (8–16GB) Multi-user Ease-of-use Best for
llama.cpp (GGUF) ✅ Good ✅ Ok (with offload) ⚠️ Limited ⭐️⭐️ Local prototyping, CPU servers
OpenVINO + OVMS ✅ Very good (Intel) ⚠️ ⚠️ ⭐️⭐️ CPU-first production on Intel servers
vLLM ⚠️ ✅ Excellent ✅ Excellent ⭐️⭐️⭐️ Small GPU production, multi-user APIs
TGI (NVIDIA) ⚠️ ✅ Excellent (NVIDIA) ⭐️⭐️ GPU production with NVIDIA stack
Ollama / LocalAI ✅ Good ✅ Good ⚠️ Depends ⭐️⭐️⭐️ Rapid prototyping & internal APIs

Legend: ✅ Good, ⚠️ Possible but constrained


LLM Serving Engine Comparison (2026)

Engine Quantization Throughput GPU Memory Multi-GPU Best For
vLLM AWQ, GPTQ, FP8, NVFP4 Highest Efficient (PagedAttention) Yes Production, 100+ concurrent users
SGLang FP8, AWQ Higher than vLLM (~29%) Efficient Yes Multi-turn agent workflows
TGI INT8, FP8 Moderate Moderate Yes HuggingFace ecosystem
TensorRT-LLM INT8, FP8, INT4, NVFP4 Highest Efficient Yes Enterprise, Blackwell optimized
llama.cpp GGUF (2-8 bit) Moderate CPU + GPU offload Limited CPU, Mac, edge devices
Ollama GGUF (via llama.cpp) Low-Moderate CPU + GPU No Local dev, single user

Production Decision Flow

Need production serving (100+ users)?
├── Yes → GPU available?
│   ├── Yes → vLLM (default) or SGLang (multi-turn agents)
│   └── No → OpenVINO (Intel) or llama.cpp (AMD/ARM)
└── No → Local/single user?
    ├── Ollama (simplest)
    └── llama.cpp (maximum control)

vLLM Production Configuration

# vLLM production config (config.yaml)
model: /models/llama-3.3-70b-awq
max_model_len: 131072
gpu_memory_utilization: 0.90
tensor_parallel_size: 4          # 4 GPUs for 70B
pipeline_parallel_size: 1
enable_prefix_caching: true
max_num_seqs: 256
max_num_batched_tokens: 65536
quantization: awq

# Performance settings
block_size: 16
swap_space: 4                    # GB for KV cache offloading
enable_chunked_prefill: true

# Serving
host: 0.0.0.0
port: 8000
served_model_name: llama-70b
response_role: assistant

Startup command

python3 -m vllm.entrypoints.openai.api_server \
    --config config.yaml

Multi-GPU Deployment Patterns

Pattern Description Best For
Tensor Parallelism Split model layers across GPUs Fitting large models
Pipeline Parallelism Split layers by stage Deep models, reduced communication
Data Parallelism Replicate model, split requests Higher throughput
Context Parallelism Split sequence across GPUs Long-context (>100K tokens)

For most deployments, tensor parallelism with 2-4 GPUs provides the best balance of model capacity and throughput.

# Launch vLLM with tensor parallelism
docker run --gpus all -p 8000:8000 \
    -v /models:/models \
    vllm/vllm:latest \
    vllm serve /models/llama-70b-awq \
    --tensor-parallel-size 4 \
    --quantization awq \
    --max-model-len 32768

Serving Engine Benchmarks (Qwen2.5-32B on H200)

Engine Throughput (tok/s) TTFT (p50) TTFT (p95) Memory Efficiency
vLLM 461 0.8s 2.1s Best (PagedAttention)
SGLang 595 0.6s 1.5s Good (RadixAttention)
TGI 210 0.4s 1.1s Moderate
TensorRT-LLM 520 0.5s 1.3s Best (optimized kernels)
llama.cpp 85 1.5s 3.5s Good (CPU+GPU)
Ollama 65 2.0s 4.0s Moderate

SGLang offers the highest throughput for multi-turn agent workflows. vLLM is the best general-purpose choice. TGI excels at long-context prompts (200K+ tokens) with 13x faster prefill.

Performance benchmarks (realistic ranges)

Actual numbers depend heavily on model architecture, quantization, batch size, prompts, and runtime. Expect this order of magnitude for interactive workloads:

  • CPU-only (8–16 cores)
    • 1–3B model: tens to low hundreds of tokens/second (very dependent on quantization and SIMD optimizations)
    • 7B model: often single-digit to low tens of tokens/second
  • Small GPU (8–16GB) + 4-bit quantization
    • 3B: hundreds of tokens/second
    • 7B: tens to low hundreds tps
    • 13B: tens of tps (if properly quantized and with efficient runtime)

Note: These are broad ranges. Run microbenchmarks for your model and workload; measuring real-world prompt/response cycles is essential.


Memory Optimization Techniques

Technique Memory Savings Quality Impact Implementation Complexity
PagedAttention (vLLM) 80-95% KV cache None Built-in (vLLM)
Prefix caching 50-90% on repeated prefixes None Built-in (vLLM)
KV cache quantization (NVFP4) 50% KV cache Minimal B200+ GPUs
Continuous batching 2-4x throughput None Built-in (vLLM)
Chunked prefill 30% peak memory None Built-in (vLLM)
CPU offloading 50-80% GPU memory Latency increase llama.cpp
FlashAttention-3 2-3x attention speed None H100+ GPUs

Decision framework — how to choose

  1. Start with the use case: prototyping, internal tooling, or low-volume production?
  2. Identify latency tolerance and concurrency requirements.
  3. Choose a model size that fits your memory constraints after quantization.
  4. Pick a runtime aligned with your hardware (llama.cpp/OpenVINO for CPU, vLLM/TGI for GPU).
  5. Validate with a small benchmark and an A/B test between quantization settings (4-bit vs 8-bit).
  6. Automate CI for model conversion, test prompts, and integration tests before deploying.

Auto-Scaling Production Deployments

# Kubernetes auto-scaling for LLM serving
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: llm-server
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: vllm-server
  minReplicas: 2
  maxReplicas: 10
  metrics:
  - type: Pods
    pods:
      metric:
        name: vllm_gpu_cache_usage
      target:
        type: AverageValue
        averageValue: 85
  - type: Resource
    resource:
      name: memory
      target:
        type: Utilization
        averageUtilization: 80

Monitoring and Observability

Metric Tool Warning Critical
GPU memory usage nvidia-smi, Prometheus >85% >95%
P50/P95 latency vLLM metrics, Grafana P95 > 5s P95 > 10s
Throughput (tok/s) vLLM metrics <100 <50
Error rate Application logs >1% >5%
Queue depth vLLM metrics >50 >200
KV cache usage vLLM metrics >80% >95%
Model loading time Startup logs >5 min >15 min

Prometheus Metrics from vLLM

# Prometheus scrape config for vLLM
scrape_configs:
  - job_name: 'vllm'
    static_configs:
      - targets: ['localhost:8000']
    metrics_path: /metrics
    scrape_interval: 15s

Cost Analysis

Self-Hosted vs API Cost Comparison

Volume Model Self-Hosted (Monthly) API (Monthly) Break-Even
100K queries Llama-3.1-8B $1,200 (1x A100) $2,400 (GPT-4.1 Mini) 6 months
1M queries Llama-3.1-70B $12,000 (4x A100) $85,000 (GPT-4.1) 2 months
10M queries Llama-3.3-70B $45,000 (8x A100) $850,000 (GPT-4.1) 1 month

Self-hosting becomes cost-effective at >1M queries/month for 70B-class models.

Hardware Requirements by Model Size

Model Size Min GPU Memory Recommended GPU Quantization Throughput (tok/s)
1-3B 4GB RTX 3060, T4 GGUF Q4_K_M 100-500
7-8B 8GB RTX 4090, A10 AWQ INT4 50-200
13B 12GB A100 40GB AWQ INT4 30-100
34B 24GB 2x A100 AWQ INT4 20-60
70B 48GB 4x A100 AWQ INT4 10-40
120B+ 80GB 8x A100 FP8/INT4 5-20

Prompt Engineering for Self-Hosted Models

Self-hosted models often need different prompting than API-based models:

Aspect API Model (GPT-4, Claude) Self-Hosted (Llama, Qwen)
System prompt Strongly followed May need reinforcement in user message
Few-shot examples Effective with 1-2 May need 3-5 examples
Output format Reliable JSON mode May need schema in prompt
Temperature 0.7 typical 0.3-0.5 for deterministic
Max tokens Set generously Set conservatively (speed)

Troubleshooting Common Issues

Symptom Cause Solution
CUDA OOM Model too large for GPU Use smaller quant (INT4), enable swap, reduce batch size
Slow first token Cold start, no prefix cache Enable prefix caching, warm up with dummy requests
Low throughput Poor batching Increase max_num_seqs, enable continuous batching
High latency CPU offloading Move more layers to GPU, reduce CPU offload
Model fails to load Wrong quantization format Verify format compatibility with serving engine
Memory leak vLLM version bug Upgrade to latest vLLM, check GitHub issues
Inconsistent responses Temperature too high Reduce temperature to 0.1-0.3 for deterministic output

Final notes and best practices ✅

  • Always evaluate quality after quantization on real tasks — not just loss numbers
  • Use batch inference where latency allows to benefit from GPU throughput
  • Automate model conversions and deploy reproducible images (Docker) for predictable behavior
  • Monitor memory, latency, and error rates; define fallback behavior for out-of-memory situations
  • Implement prefix caching for repeated prompts to reduce time-to-first-token
  • Start with Ollama for prototyping, migrate to vLLM for production
  • Use AWQ quantization for best quality on NVIDIA GPUs
  • Test with realistic concurrency before going live

Security Considerations

Concern Mitigation
Model access control Use API keys, network policies, and authentication middleware
Data privacy Self-hosting keeps data on-premise — no data sent to third parties
Model theft Store models on encrypted volumes, restrict file system access
Prompt injection Implement input validation and output filtering
Rate limiting Protect against abuse with per-user rate limits
Audit logging Log all inference requests and responses for compliance
Container security Run with minimal privileges, scan images for vulnerabilities

Decision Framework: When to Self-Host vs. Use API

Factor Self-Host API Provider
Monthly volume >1M queries <100K queries
Latency requirement <100ms p50 <500ms p50 acceptable
Data privacy Sensitive data, compliance Public or anonymized data
Model customization Fine-tuning, custom architectures Standard models only
Team expertise DevOps/MLOps team available Minimal ops overhead
Cost sensitivity Predictable costs at high volume Variable, pay-per-use
Time to market Longer (infrastructure setup) Immediate

Tipping Points

Model Size Self-Host Cost (Monthly) API Cost (Monthly) Tipping Point
7B $1,200 $2,400 at 100K queries ~50K queries/month
13B $3,500 $6,000 at 100K queries ~60K queries/month
34B $7,000 $15,000 at 100K queries ~50K queries/month
70B $12,000 $85,000 at 1M queries ~150K queries/month

GPU Memory Planning

Calculate memory requirements for deployment:

def estimate_gpu_memory(model_size_b: float, quantization: str,
                        context_length: int, batch_size: int) -> dict:
    """Estimate GPU memory for LLM serving."""
    weight_bytes = {"fp16": 2, "int8": 1, "int4": 0.5, "fp8": 1}
    kv_bytes = 2 * 80 * context_length * 8192 / 64 * 128  # 70B model assumption

    weight_gb = model_size_b * weight_bytes.get(quantization, 2)
    kv_cache_gb = kv_bytes * batch_size / (1024**3)
    overhead_gb = 2  # CUDA context, activations

    total = weight_gb + kv_cache_gb + overhead_gb
    return {
        "weights_gb": round(weight_gb, 1),
        "kv_cache_gb": round(kv_cache_gb, 1),
        "overhead_gb": overhead_gb,
        "total_gb": round(total, 1),
        "recommended_gpu": f"{int(total * 1.2)}+ GB"
    }

print(estimate_gpu_memory(70, "int4", 32768, 16))
# Output: {'weights_gb': 35.0, 'kv_cache_gb': 10.5, 'overhead_gb': 2, 'total_gb': 47.5, 'recommended_gpu': '57+ GB'}

Getting Started: 5-Minute Quickstart

# Option 1: Ollama (simplest)
curl -fsSL https://ollama.com/install.sh | sh
ollama run llama3.2:3b
curl http://localhost:11434/api/chat -d '{"model":"llama3.2:3b","messages":[{"role":"user","content":"Hello"}]}'

# Option 2: vLLM Docker (production)
docker run --gpus all -p 8000:8000 \
    vllm/vllm:latest \
    vllm serve Qwen/Qwen2.5-7B-Instruct \
    --quantization awq

# Test
curl http://localhost:8000/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{"model":"Qwen/Qwen2.5-7B-Instruct","messages":[{"role":"user","content":"Hello"}]}'

Model Recommendation by Use Case

Use Case Recommended Model Size Quantization Runtime
Chatbot, general QA Llama 3.2 8B 8B AWQ INT4 vLLM
Code generation Qwen2.5-Coder 7B 7B AWQ INT4 vLLM
Document analysis Llama 3.3 70B 70B AWQ INT4 vLLM, 4x GPU
RAG embedding BGE-M3 568M FP16 FastEmbed
Classification Llama 3.2 3B 3B GGUF Q4 llama.cpp
Edge/on-device Phi-3.5 mini 3.8B GGUF Q4 Ollama

Production Operational Tasks

Frequency Task Impact
Daily Check GPU memory usage, latency p95 Prevent OOM, detect degradation
Weekly Review error rates, update models Maintain reliability
Monthly Rotate API keys, audit access logs Security compliance
Quarterly Upgrade serving engine, re-evaluate model choice Stay current
On-deploy Run load test, verify quantization quality, check compatibility Prevent regressions

Conclusion

Self-hosting open-source LLMs can dramatically reduce the per-token costs of R&D and internal tooling while giving you full data control. For most startups:

  • Start with Ollama or llama.cpp on CPU for rapid prototyping
  • Move to vLLM or SGLang when you need interactive latency and multi-user throughput
  • Use AWQ INT4 quantization for best quality on NVIDIA GPUs
  • Implement prefix caching and continuous batching for production throughput
  • Monitor GPU memory, latency, and error rates continuously

With careful model selection, quantization, and the right runtime, you can run capable LLMs with predictable costs and acceptable performance. The self-hosted ecosystem in 2026 is mature enough for production workloads — the key is matching the serving engine to your hardware and concurrency requirements.

Deployment Architecture Patterns

Single GPU (7B-13B models)

Client → Load Balancer → vLLM (1x GPU) → Model (7B/13B AWQ)
                         └── Redis Cache (prefix caching)

Multi-GPU (34B-70B models)

Client → Load Balancer → vLLM (4x GPU, TP=4) → Model (70B AWQ)
                         ├── Redis Cache
                         └── Prometheus + Grafana (monitoring)

High-Availability (production)

Client → Load Balancer → vLLM Replica 1 (2x GPU)
                         vLLM Replica 2 (2x GPU)
                         vLLM Replica 3 (2x GPU)
                         └── Shared Redis Cache + PostgreSQL (request log)

Batch Inference for Throughput

For non-real-time workloads, batch inference maximizes GPU utilization:

import asyncio
from vllm import AsyncLLMEngine, SamplingParams

async def batch_inference(engine, prompts: list[str]) -> list[str]:
    """Run batch inference for maximum throughput."""
    sampling_params = SamplingParams(
        temperature=0.1,
        max_tokens=512,
        top_p=0.9
    )
    requests = [engine.generate(p, sampling_params) for p in prompts]
    results = await asyncio.gather(*requests)
    return [r.outputs[0].text for r in results]

# Usage: process 100 prompts in a single batch
engine = AsyncLLMEngine.from_vllm_config(vllm_config)
responses = asyncio.run(batch_inference(engine, prompts))

Batch inference can achieve 3-5x higher throughput than sequential processing for batch workloads like document classification, summarization, and data extraction.

Upgrade Path: Ollama → vLLM

Stage Tool Users GPUs Setup Time
1: Prototype Ollama 1-5 0-1 Minutes
2: Internal tool Ollama + Docker 5-50 1 Hours
3: Production vLLM 50-1000 1-8 Days
4: Scale vLLM + K8s 1000+ 8-64 Weeks

Migrate from Ollama to vLLM when you exceed 50 concurrent users or need >100 tok/s throughput.

Serving Engine Configuration Reference

# vLLM: Production configuration
# Hardware: 4x A100 80GB, Llama-3.3-70B AWQ
engine:
  model: meta-llama/Llama-3.3-70B-Instruct-AWQ-INT4
  quantization: awq
  tensor_parallel_size: 4
  max_model_len: 32768
  gpu_memory_utilization: 0.90
  enable_prefix_caching: true
  max_num_seqs: 256
  block_size: 16
  swap_space: 4

# SGLang: Multi-turn agent configuration
engine:
  model: Qwen/Qwen2.5-32B-Instruct-AWQ
  quantization: awq
  tensor_parallel_size: 2
  max_model_len: 65536
  enable_mixed: true
  radix_cache_size: 16384

# llama.cpp: CPU inference configuration
engine:
  model: /models/llama-3.2-3b-q4_k_m.gguf
  n_gpu_layers: -1  # All on GPU if available
  n_ctx: 8192
  n_batch: 512
  n_threads: 8
  no_kv_offload: false

Open-Source Model Recommendations

Category Best Model (2026) Size License Why
General purpose Llama 3.3 70B 70B Llama 3 Best overall quality
General purpose (small) Qwen2.5 32B 32B Apache 2.0 Strong quality, fits 2x GPU
Code generation Qwen2.5-Coder 32B 32B Apache 2.0 Best open code model
Reasoning DeepSeek-R1 distill 7B 7B MIT Strong reasoning in small package
Edge/mobile Phi-3.5 Mini 3.8B MIT Tiny, runs on phone NPU
Embeddings BGE-M3 568M MIT Best general embedding quality
Vision LLaVA-NeXT 34B 34B Apache 2.0 Strong multimodal

Migration Checklist: Ollama → vLLM

  1. Convert model to AWQ format (or download pre-quantized)
  2. Set up vLLM server with tensor parallelism if multi-GPU
  3. Configure prefix caching and continuous batching
  4. Update client code to use OpenAI-compatible API
  5. Set up Prometheus metrics and Grafana dashboards
  6. Implement load testing with realistic concurrency
  7. Configure auto-scaling based on GPU memory utilization
  8. Set up Redis cache layer for repeated prompt prefixes
  9. Deploy with Docker/Kubernetes for reproducibility
  10. Monitor for 48 hours before cutting over production traffic

Frequently Asked Questions

Q: Can I run Llama 3 70B on a single GPU? A: Yes, with AWQ INT4 quantization a 70B model requires ~35GB — it fits on a single H100 (80GB) or A100 (80GB). For production throughput, 4x GPUs are recommended.

Q: Is self-hosting cheaper than API? A: At >1M queries/month, yes. Below that, API may be more cost-effective when accounting for engineering time and infrastructure management.

Q: What’s the best quantization for self-hosting? A: AWQ INT4 for NVIDIA GPUs (best quality-to-speed). GGUF Q6_K for CPU inference (best quality on CPU). GGUF Q4_K_M for edge deployment (memory-constrained).

Q: How do I handle concurrent users? A: Use vLLM with continuous batching. A single A100 serving a 7B AWQ model can handle 50-100 concurrent users with good latency.

Q: Can I serve multiple models from one server? A: Yes, vLLM supports model multiplexing. Set --serve-model-name and use separate endpoints per model. TensorRT-LLM also supports multi-model serving.

Resources

Comments

👍 Was this article helpful?