The project combined infrastructure operations, GPU platform validation, Linux administration, AI runtime integration, documentation, and recovery planning. The success criterion was a trustworthy platform state, not just a working demo.
Engineering Case Study - AI Infrastructure
Building a Local AI Infrastructure Platform
This was not simply an AI installation project. It was an infrastructure engineering effort involving architecture decisions, operational troubleshooting, platform validation, recovery planning, and the creation of a repeatable AI infrastructure baseline.
- VirtualizationProxmox
- GPURTX 3090
- DriverNVIDIA 595.80
- RuntimeCUDA 13.2
- ContainerLXC
- InterfaceOpen WebUI
- ServingOllama
- Validated ModelGPT-OSS 20B
Operating Objective
Build a controlled local AI platform that could be validated, recovered, documented, and extended.
The rebuilt environment successfully hosted and executed Qwen 7B, Qwen 14B, and GPT-OSS 20B with GPU acceleration, then established a recoverable baseline through snapshots, NAS backups, and GitHub documentation.
Executive Context
The business value was creating a trustworthy platform state, not just getting a model to answer.
Platform confidence had dropped
GPU driver conflicts, runtime failures, and accumulated troubleshooting changes made the AI platform hard to trust as a baseline for future work.
Rebuild from a clean baseline
Rebuild on Proxmox, resolve GPU ownership issues, validate NVIDIA/CUDA/Open WebUI/Ollama/model execution, then protect the working state.
Recoverable AI platform baseline
Established a validated local AI platform with RTX 3090 acceleration, model execution, documentation, snapshots, and NAS backup.
Less drift and recovery uncertainty
Reduced platform drift, GPU/runtime troubleshooting risk, and uncertainty around whether the environment could be restored or repeated.
From demo to managed infrastructure
Added root cause analysis, validation steps, documentation, snapshots, backups, benchmarking, and observability work around the AI platform.
Architecture Overview
The platform separates infrastructure, GPU enablement, runtime services, application access, and model validation into clear operating layers.
- Proxmox
- RTX 3090
- NVIDIA 595.80
- CUDA 13.2
- LXC Container
- Open WebUI
- Ollama
- Qwen Models
- GPT-OSS 20B
Engineering Timeline
The build progressed from initial deployment through driver investigation, rebuild, validation, recovery, and portfolio integration.
- 01Initial AI Platform Deployment
- 02GPU Validation Challenges
- 03VFIO Investigation
- 04Ollama Runtime Failures
- 05Platform State Confidence Review
- 06Decision to Rebuild
- 07Fresh Proxmox Deployment
- 08NovaCore Conflict Discovery
- 09NVIDIA 595.80 Installation
- 10CUDA 13.2 Validation
- 11Open WebUI Validation
- 12Ollama Validation
- 13Qwen Model Validation
- 14GPT-OSS 20B Validation
- 15Snapshot Creation
- 16NAS Backup Creation
- 17GitHub Documentation
- 18Portfolio Integration
Key Decisions
The project hinged on engineering judgment: when to keep troubleshooting, when to challenge assumptions, and when to protect the validated state.
Continue troubleshooting versus rebuild from a trusted baseline
Outcome: Fresh rebuild selected.
Reason: Confidence in platform state had become too low.
Assume AI runtime issue versus validate GPU ownership
Outcome: NovaCore conflict identified.
Reason: Root cause analysis revealed a driver ownership conflict.
Move on after validation versus establish recovery strategy
Outcome: Snapshot and NAS backup created.
Reason: A stable platform is only valuable if it can be recovered.
Root Cause Analysis
The investigation treated runtime failures as symptoms of a deeper platform state and GPU ownership problem.
VFIO Investigation
GPU behavior was reviewed at the host and passthrough layers to determine whether the device was being held by the wrong subsystem.
GPU Ownership Analysis
The RTX 3090 needed a clear ownership path before CUDA and model execution could be trusted.
NovaCore Conflict Discovery
A conflicting driver stack involving NovaCore was identified as a blocker for clean NVIDIA driver attachment.
Driver Attachment Issues
NVIDIA installation failures and inconsistent module behavior were traced back to the underlying ownership conflict.
Platform State Drift
Accumulated troubleshooting changes reduced confidence in the environment, making a clean rebuild the stronger engineering path.
Key Outcomes Dashboard
The final platform state was validated across compute, driver, runtime, application, models, and recovery controls.
Benchmarking & Observability
The project moved beyond running a local model into measuring runtime behavior under load.
The next phase of the lab added vLLM serving, Prometheus metrics, Grafana dashboards, and Locust load testing so the platform could be judged by throughput, latency, queue depth, and GPU behavior rather than a single successful response.
Benchmark results were captured using Locust, Prometheus, Grafana, and NVIDIA GPU telemetry while serving Qwen 2.5 7B through vLLM on an EVGA RTX 3090 FTW3 Ultra.
| Test | Users | Generation Tokens/sec | Prompt Tokens/sec | Avg Latency | P95 Latency | Waiting Requests | Failures |
|---|---|---|---|---|---|---|---|
| Baseline | 1 | ~33.07 | ~6.58 | ~4.18s | ~4.20-4.88s | 0 | 0 |
| Concurrency Test | 5 | ~159.31 | ~29.6 | ~4.22s | ~4.30-4.88s | 0 | 0 |
| Load Test | 25 | ~751.76 | ~141 | ~4.54s | ~4.60-4.88s | 0 | 0 |
| Sustained Load Test | 50 | ~1439.93 | ~262 | ~4.76-4.92s | ~4.89-5.0s | 0 | 0 |
Understanding the Dashboard
Three layers: observability health, runtime behavior, and saturation signals.
The dashboard is meant to show whether the monitoring box is healthy, how the AI runtime is behaving, and whether requests are starting to queue.
CT203 CPU Usage %
Shows whether Prometheus, Grafana, Node Exporter, and Locust are using enough CPU to become part of the benchmark problem.
CT203 Memory Usage %
Shows whether the observability and load-testing container has enough RAM headroom during benchmark runs.
CT203 Disk Usage %
Tracks local disk pressure on the observability server, which matters because Prometheus stores time-series data locally.
Generation Tokens/sec
Measures output throughput: how many response tokens Qwen 2.5 7B generates per second.
Prompt Tokens/sec
Measures input throughput: how many prompt tokens are processed before responses are generated.
Requests Running
Shows active inference requests currently being processed by vLLM. It is the current concurrency view.
Requests Waiting
Shows queued requests waiting for GPU/runtime resources. Zero means requests are not backing up.
Average Request Latency
Shows average request completion time. It is one of the main user-facing performance metrics.
P95 Request Latency
Shows the response time experienced by 95% of requests, which helps expose slow outliers.
KV Cache Usage %
Shows how much of the runtime's short-term memory workspace is being used. 0-30% is plenty of headroom, 30-70% is normal, 70-90% needs attention, and 90%+ can become bottleneck territory.
GPU Validation
RTX 3090 Runtime Validation
During the 50-user benchmark, the EVGA RTX 3090 FTW3 Ultra reached 100% utilization and 249W power draw while staying around 56 degrees C. VRAM usage was approximately 21.9GB out of 24GB. There were zero failed requests and zero queued requests.
Initial baseline validation showing approximately 33 generation tokens/sec, stable latency, and zero failures.
Sustained load test showing approximately 1440 generation tokens/sec, 100% GPU utilization, 249W power draw, and zero queued requests.
Implementation Notes
Dashboard v1.1 added host separation and custom GPU telemetry.
These notes document what was actually configured: the vLLM runtime command, Prometheus scrape targets, Node Exporter textfile collection, the GPU metrics script, and the systemd timer that keeps the dashboard current.
vLLM startup command
CT202 serves Qwen/Qwen2.5-7B-Instruct through vLLM on port 8000.
source ~/vllm-env/bin/activate
vllm serve Qwen/Qwen2.5-7B-Instruct \
--host 0.0.0.0 \
--port 8000 \
--tensor-parallel-size 1 \
--gpu-memory-utilization 0.90 \
--max-model-len 8192
Prometheus scrape jobs
CT203 runs Prometheus and Grafana. Prometheus scrapes the vLLM metrics endpoint and the CT202 Node Exporter endpoint.
vLLM scrape target:
192.168.50.104:8000
CT202 node exporter scrape target:
192.168.50.104:9100
Textfile collector
CT202 exposes custom GPU metrics by enabling the Node Exporter textfile collector.
/etc/default/prometheus-node-exporter
ARGS="--collector.textfile.directory=/var/lib/node_exporter/textfile_collector"
Custom exporter script
The gpu-metrics.sh script uses nvidia-smi and writes gpu.prom for Node Exporter. It collects utilization, temperature, power draw, and VRAM usage.
#!/usr/bin/env bash
set -euo pipefail
OUT="/var/lib/node_exporter/textfile_collector/gpu.prom"
TMP="${OUT}.$$"
read -r GPU_UTIL GPU_TEMP GPU_POWER GPU_MEM_USED < <(
nvidia-smi --query-gpu=utilization.gpu,temperature.gpu,power.draw,memory.used \
--format=csv,noheader,nounits | head -n 1 | tr -d ','
)
cat > "$TMP" <<EOF
# HELP gpu_utilization_percent GPU utilization percentage from nvidia-smi.
# TYPE gpu_utilization_percent gauge
gpu_utilization_percent $GPU_UTIL
# HELP gpu_temperature_celsius GPU temperature in Celsius from nvidia-smi.
# TYPE gpu_temperature_celsius gauge
gpu_temperature_celsius $GPU_TEMP
# HELP gpu_power_watts GPU power draw in watts from nvidia-smi.
# TYPE gpu_power_watts gauge
gpu_power_watts $GPU_POWER
# HELP gpu_memory_used_mb GPU memory used in MB from nvidia-smi.
# TYPE gpu_memory_used_mb gauge
gpu_memory_used_mb $GPU_MEM_USED
EOF
mv "$TMP" "$OUT"
systemd service and timer
gpu-metrics.service runs the exporter, and gpu-metrics.timer runs it every 15 seconds so Grafana has a current GPU view without Prometheus shelling out to nvidia-smi.
# gpu-metrics.service
[Unit]
Description=Collect NVIDIA GPU metrics for Node Exporter
[Service]
Type=oneshot
ExecStart=/usr/local/bin/gpu-metrics.sh
# gpu-metrics.timer
[Unit]
Description=Run GPU metrics collection every 15 seconds
[Timer]
OnBootSec=15s
OnUnitActiveSec=15s
Unit=gpu-metrics.service
[Install]
WantedBy=timers.target
Separated host and GPU views
The dashboard now separates CT203 observability host metrics from CT202 AI runtime host metrics. GPU panels include utilization, temperature, power draw, and VRAM usage.
With Qwen 2.5 7B loaded and idle/ready, the RTX 3090 showed about 21,996 MB of VRAM used.
Model Evaluation
Qwen3-14B-AWQ vLLM Evaluation
This test evaluated whether Qwen3-14B-AWQ could run reliably on a single RTX 3090 24GB using vLLM, Open WebUI, Prometheus, Grafana, and GPU telemetry.
The goal was not just to get a model process running. The real check was whether the runtime, API, frontend, and telemetry stack could make the model usable and understandable on homelab hardware.
Qwen3-14B-AWQ is successfully running under vLLM on the RTX 3090. Open WebUI can connect to it, API testing works, and Grafana captures GPU/runtime telemetry.
Qwen3-14B failed with CUDA out-of-memory
The non-quantized Qwen3-14B attempt failed when vLLM tried to allocate additional GPU memory while the RTX 3090 was already near full VRAM usage.
torch.OutOfMemoryError: CUDA out of memory
GPU had ~23.55 GiB total capacity
Only a small amount of VRAM was free
That confirmed full-precision or larger model loading was not practical on a single 24GB card without quantization or model parallelism.
24GB VRAM is usable, but format matters
The RTX 3090 is still a very useful local AI card, but model format and quantization matter more than parameter count alone. The same class of model can fail or run depending on how it is packaged.
Why Quantization Matters
Quantization reduces model weight precision, which lowers VRAM requirements. AWQ allowed the 14B model to fit where the non-quantized model failed.
The tradeoff is possible small quality loss, but the deployability improvement on consumer GPUs is huge. For homelab AI infrastructure, quantization turns "the model theoretically exists" into "the model actually runs."
Qwen/Qwen3-14B-AWQ served through vLLM
- ModelQwen/Qwen3-14B-AWQ
- RuntimevLLM 0.22.0
- APIOpenAI-compatible :8000
- FrontendOpen WebUI
- GPURTX 3090 24GB
vLLM served /v1/models and /v1/chat/completions successfully. Open WebUI displayed and used the Qwen3-14B-AWQ model.
Runtime, API, and telemetry checks
ps -ef | grep vllm
ss -tulpn | grep 8000
curl http://localhost:8000/health
curl http://localhost:8000/v1/models
nvidia-smi
cat /var/lib/node_exporter/textfile_collector/gpu.prom
curl http://localhost:8000/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"model": "Qwen/Qwen3-14B-AWQ",
"messages": [
{
"role": "user",
"content": "Explain Terraform state locking in plain English."
}
],
"max_tokens": 200
}'
3090 VRAM Utilization
Qwen3-14B-AWQ ran close to the card's practical VRAM ceiling.
Qwen3-14B-AWQ used roughly 20.9GB to 22GB of RTX 3090 VRAM while loaded. nvidia-smi showed around 20902MiB to 21996MiB used during testing. GPU power ranged from low idle values around 23W up to roughly 249W during active generation, utilization reached 100% during prompt generation, and temperature stayed reasonable at around 29C to 36C in the screenshots.
Grafana Observability
The dashboard connected model behavior to GPU pressure, latency, and request activity.
Grafana made it possible to correlate model behavior with GPU load, VRAM pressure, latency, and request activity instead of guessing from a terminal window.
Architecture Diagram
Open WebUI talks to vLLM, vLLM runs the AWQ model, and Prometheus/Grafana watch the runtime.
- Open WebUI
- vLLM OpenAI-compatible API :8000
- Qwen3-14B-AWQ
- RTX 3090 24GB
qwen3-14b-awq-grafana.png
Final Grafana dashboard screenshot during a controlled Qwen3-14B-AWQ test.
qwen3-14b-awq-openwebui.png
Open WebUI showing the Qwen3-14B-AWQ model connected through the vLLM endpoint.
qwen3-14b-awq-nvidia-smi.png
nvidia-smi output showing RTX 3090 VRAM, utilization, power, and temperature during testing.
Model Comparison Notes
The AWQ model was the practical 14B path on a single RTX 3090.
| Model | Result | Notes |
|---|---|---|
| Qwen3-14B non-quantized | Failed | CUDA OOM on a single RTX 3090 |
| Qwen3-14B-AWQ | Successful | Successful on a single RTX 3090 |
| Qwen2.5-7B-Instruct | Successful baseline | Lower VRAM, faster/smaller baseline |
| GPT-OSS 20B | Not yet fully evaluated in vLLM | Previously liked in Ollama; needs compatible vLLM model format review |
Matt's Notes
Quantization was the difference between "almost fits" and "actually runs."
What surprised me
- A 14B model can still fail on a 24GB RTX 3090 depending on format and precision.
- The quantized AWQ version worked where the non-quantized version failed.
- Grafana made the VRAM and GPU utilization story obvious.
What broke
- The non-quantized Qwen3-14B run failed with CUDA out-of-memory.
- The RTX 3090 had almost no free VRAM left during the failed load.
- The first assumption that "14B should fit" was too simplistic.
What I learned
- Quantization matters as much as parameter count.
- AWQ can make larger models practical on consumer GPUs.
- Observability is not optional when building local AI infrastructure.
What I would do differently
- Check quantized model options first.
- Capture nvidia-smi and Grafana screenshots during every test.
- Compare models with the same prompts and load profile.
- Treat VRAM like a budget, not a suggestion.
Current Status
Qwen3-14B-AWQ is successfully running under vLLM on the RTX 3090.
Open WebUI can connect to it, API testing works, and Grafana captures GPU/runtime telemetry. The non-quantized 14B model failed with CUDA OOM, making AWQ quantization the practical path for this GPU.
Next Steps
Turn the successful single-model test into a measured runtime comparison.
- Capture final Grafana screenshots
- Run a small Locust load test
- Compare against Qwen2.5-7B
- Evaluate GPT-OSS 20B in vLLM
- Add RAG as the next phase
Primary Lesson
"Trusting a clean baseline can be more valuable than endlessly troubleshooting an unknown environment."
Symptoms are not always root causes.
Ollama failures and slow model responses pointed toward runtime issues, but the deeper issue was GPU ownership.
Documentation accelerates troubleshooting.
Notes, screenshots, configuration details, and validation results reduced repeated work and improved decision quality.
Recovery planning should be part of the build process.
Snapshots and NAS backups converted a working state into a recoverable platform baseline.
AI infrastructure is still infrastructure.
Architecture, reliability, security, monitoring, recovery, and documentation remain core operating concerns.
Business Value
The project demonstrates enterprise-relevant infrastructure capability across operations, platform engineering, AI infrastructure, and recovery discipline.
Infrastructure Operations
Validated host state, service behavior, backup posture, and operational reliability.
GPU Infrastructure
Managed driver installation, CUDA validation, GPU visibility, and workload execution.
Linux Administration
Worked through host-level behavior, kernel-level ownership, runtime services, and containerized execution.
Platform Engineering
Created a repeatable baseline with documented decisions, validation steps, and recovery controls.
AI Infrastructure
Integrated Open WebUI, Ollama, local model execution, and future-ready platform patterns.
Disaster Recovery
Established snapshot and NAS backup strategy immediately after successful validation.
Documentation and Repeatability
Converted troubleshooting and implementation work into artifacts that support future rebuilds.
Operational Decision Making
Selected rebuild over continued troubleshooting when confidence in platform state became too low.
Future Roadmap
Future initiatives extend the validated baseline into broader AI platform engineering capabilities.
- llama.cpp
- vLLM
- Vector Database Platform
- RAG Knowledge Platform
- Secret Management
- AI Operations Agent
- AI Platform Engineering Lab