Course outline▾
Week 1 · Bare-Metal Infrastructure & Advanced Containerization
- Day 1The AI Infrastructure Shift — Why the Model is Only 10% of the Production System
- Day 2Advanced Docker for AI — Multi-Stage Builds for Heavy Python Runtimes
- Day 3Hardware Acceleration in Docker — GPU Passthrough and Limits for Constrained Hardware
- Day 4Optimizing Model Weights — Quantization Basics (GGUF, AWQ) for Efficient Inference
- Day 5Local AI Runtimes — Deploying Ollama and vLLM for Bare-Metal Execution
- Day 6Containerizing AI Microservices — Building and Packaging APIs with FastAPI
- Day 7Docker Compose: Architecting a Multi-Container Database and Inference Stack Locally
Week 2 · Kubernetes Foundation for MLOps
- Day 8K3d & Lightweight Kubernetes: Running Multi-Node Clusters on 16GB of RAM
- Day 9Kubernetes Core for Machine Learning: Pods, Deployments, and Headless Services
- Day 10Persistent Volumes (PV/PVC): Managing Heavy Model Weights and Vector Stores
- Day 11StatefulSets for Vector Databases: Deploying Milvus or Qdrant in Kubernetes
- Day 12Auto-Scaling AI Inference: Using Horizontal Pod Autoscalers (HPA) for Spiky API Traffic
- Day 13Helm Charts: Packaging, Versioning, and Updating AI Deployments
- Day 14Week 2 Capstone: Deploying a Highly Available Local AI Application on K3d
Week 3 · High-Performance Networking & Security Mesh
- Day 15AI Network Bottlenecks: Transitioning from iptables to eBPF
- Day 16Cilium & eBPF: Sub-Millisecond Routing for Heavy AI Microservices
- Day 17Service Mesh 101: Installing Istio over Your Local Cluster
- Day 18Envoy Proxy as an AI Gateway: Load Balancing and Terminating LLM Prompts
- Day 19Zero-Trust mTLS: Encrypting Traffic Between Autonomous Agents
- Day 20API Rate Limiting & Egress Control: Preventing Agents from Exfiltrating Data
- Day 21Distributed Tracing: Debugging Agent Workflows with Jaeger and OpenTelemetry
Week 4 · Enterprise Agents & CI/CD Workflows
- Day 22Introduction to MLOps: Bridging AI Development with Continuous Integration
- Day 23GitOps with ArgoCD: Automated, Self-Healing AI Deployments
- Day 24Building Ephemeral AI Sandboxes: Secure Execution Environments for Agent Code
- Day 25Multi-Agent Orchestration Frameworks: Exploring Mastra, LangGraph, and CrewAI
- Day 26Tool-Calling in Production: Connecting Agents Securely to Internal Databases
- Day 27MLOps Guardrails: Evaluating Agents and Preventing Hallucinations in Real-Time
- Day 28Continuous Training Pipelines: Connecting Data Ingestion to Automated Model Retraining
- Day 29Managing Technical Debt and Infrastructure Drift in Enterprise AI
- Day 30The AI-Native Platform: Scaling from a Sandbox Environment to Global Cloud Architecture
Day 5: Local AI Runtimes — Deploying Ollama and vLLM for Bare-Metal Execution
2026-11-11 · 8 min read
Loading a model with a native Hugging Face pipeline (AutoModelForCausalLM) inside a web server is simple, and it is inefficient: you carry a heavy Python interpreter, get no real hardware scheduling and tend to fall over under concurrent traffic. Production uses dedicated inference engines instead. This lesson covers the two that matter: Ollama for development and edge compute, and vLLM for datacentre concurrency.
Ollama: development and edge
Ollama wraps llama.cpp and GGUF weights behind a small daemon.
- Memory efficiency. It can split a model's layers between CPU RAM and GPU memory, so models larger than your VRAM still run.
- A Modelfile describes a reproducible model setup.
- OpenAI-compatible endpoints, such as
/v1/chat/completions, so existing clients work by changing the base URL.
# Modelfile
FROM gemma2:2b
PARAMETER temperature 0.2
PARAMETER num_ctx 4096
SYSTEM """You are an enterprise infrastructure diagnostic assistant. Output structured JSON only."""
ollama create infra-diag -f Modelfile
ollama run infra-diag "Summarise: link flap on port 12, CRC errors rising"
vLLM: datacentre concurrency
vLLM is built to serve many users at once from a GPU.
PagedAttention. A standard engine allocates the KV-cache in one contiguous block per request, sized for the maximum context, and much of it stays empty. The vLLM authors measured 60 to 80% of KV-cache memory wasted in earlier systems. vLLM instead stores the cache in non-contiguous blocks, like virtual-memory paging in an operating system, and reports under 4% waste.
Continuous batching. Rather than waiting for a whole batch to finish, vLLM interleaves incoming prompts into the running batch at the token level, which keeps the GPU busy and raises throughput.
Deploying vLLM
docker run -d --name enterprise-vllm \
--gpus all \
--shm-size="4g" \
-p 8000:8000 \
-v /opt/models/gemma-2-9b-awq:/model \
vllm/vllm-openai:latest \
--model /model \
--quantization awq \
--max-model-len 4096 \
--gpu-memory-utilization 0.90
--gpu-memory-utilization 0.90lets vLLM claim 90% of VRAM for the weights and the PagedAttention cache pool. It is a starting point, so leave headroom if other processes share the GPU.--max-model-len 4096caps the context per request, so the cache pool can serve more users at once.--quantization awqmatches the AWQ weights from Day 4.
vLLM exposes the same OpenAI-compatible API, so your client code does not change.
from openai import OpenAI
client = OpenAI(base_url="http://localhost:8000/v1", api_key="not-needed")
reply = client.chat.completions.create(
model="/model", # the name vLLM was started with
messages=[{"role": "user", "content": "List three causes of CRC errors."}],
)
print(reply.choices[0].message.content)
Pick Ollama while you build and for small or edge machines. Move to vLLM when concurrency, not convenience, is the problem.
Run it yourself
This is the exact example from the video. A NumPy simulation of the idea, not vLLM itself, so use it to understand the effect and measure your own engine for real numbers.
import numpy as np
rng = np.random.default_rng(0)
MAX, BLOCK = 4096, 16
used = rng.integers(100, 1500, 1000) # tokens really used
contig = np.full(used.size, MAX) # reserve the max
paged = np.ceil(used / BLOCK) * BLOCK # whole blocks
def waste(reserved):
return 100 * (1 - used.sum() / reserved.sum())
print(f"contiguous waste: {waste(contig):.0f}%")
print(f"paged waste: {waste(paged):.1f}%")
slots = 10**6
print("fit in 1M:", slots // MAX, "vs", int(slots // paged.mean()))
Run it:
python paged.py
Output I got while recording the video:
contiguous waste: 80%
paged waste: 0.9%
fit in 1M: 244 vs 1205
Coming Up Next
Day 6: Containerizing AI microservices: building and packaging APIs with FastAPI.
#LocalAI #vLLM #Ollama #ModelServing #DevOps #DataSovereignty #HighPerformanceComputing #OpenSource