Comprehensive Local LLM Investigation Plan
Goal: Find the optimal local inference stack for pai-primary, anticipating local becoming primary source for all AI activities.
Hardware: RTX 3060 Laptop 6GB + RTX 4060 8GB, 47GB RAM, CUDA 12.0
Success Criteria:
- Maximum throughput (tokens/sec)
- Minimum latency (time to first token)
- Best memory efficiency (largest model that fits)
- Stability (24/7 operation)
- API compatibility (OpenAI-compatible preferred)
- Ease of management (systemd integration, monitoring)
Phase 1: Baseline Measurement (Ollama)
Current State - Measure Before Changing
Tests to Run
-
Memory efficiency
- GPU memory per model size
- How many models can load simultaneously
- Memory overhead vs raw model size
-
Performance benchmarks
- Time to first token (TTFT)
- Tokens per second (generation speed)
- Concurrent request handling
- Context length impact on performance
-
Model quality at different quants
- Q4_K_M vs Q6_K vs Q8_0
- Quality degradation measurement
- Speed vs quality tradeoff
-
Configuration tuning
- OLLAMA_NUM_PARALLEL impact
- OLLAMA_MAX_LOADED_MODELS impact
- OLLAMA_FLASH_ATTENTION impact
- Context caching effectiveness
Deliverable: OLLAMA_BASELINE_BENCHMARK.md with hard numbers
Phase 2: llama.cpp Direct (Ollama’s Backend)
Hypothesis: Ollama adds overhead, direct llama.cpp might be faster
Installation
git clone https://github.com/ggerganov/llama.cpp
cd llama.cpp
make LLAMA_CUDA=1Tests
-
Same models, direct comparison
- Load qwen2.5:7b GGUF directly
- Measure TTFT, tokens/sec
- Compare to Ollama’s performance
-
Advanced quantization
- Try quantization formats Ollama doesn’t expose
- IQ4_XS, IQ3_M, etc.
- Measure quality vs speed
-
Server mode
./llama-server --model qwen2.5-7b.gguf --port 8080 --n-gpu-layers 99- OpenAI-compatible API
- Performance vs Ollama API
- Concurrent request handling
-
Memory optimization flags
- Flash attention
- KV cache optimization
- Batch size tuning
Deliverable: LLAMACPP_DIRECT_BENCHMARK.md
Decision Point: If llama.cpp direct is significantly faster, consider replacing Ollama
Phase 3: Alternative Serving Engines
3A. LocalAI (OpenAI-compatible, consumer-focused)
Why: Designed specifically for consumer hardware, multiple backend support
Installation:
docker run -d --name localai \
--gpus all \
-v ~/.cache/huggingface:/models \
-p 8080:8080 \
localai/localai:latest-gpu-nvidia-cuda-12Tests:
- Same model (Qwen2.5-7B)
- Performance comparison
- Memory efficiency
- Multi-model support
- API compatibility
Deliverable: LOCALAI_BENCHMARK.md
3B. SGLang (Claims 5x speedup over vLLM)
Why: Structured Generation Language, optimized for efficiency
Installation:
pip install "sglang[all]"Tests:
python -m sglang.launch_server \
--model Qwen/Qwen2.5-7B-Instruct \
--port 30000 \
--mem-fraction-static 0.8- Benchmark vs vLLM’s failed attempts
- Check if memory management better
- RadixAttention effectiveness
- Structured output performance
Deliverable: SGLANG_BENCHMARK.md
3C. Aphrodite Engine (vLLM fork, optimized)
Why: vLLM fork focused on consumer GPUs, better quantization support
Installation:
pip install aphrodite-engineTests:
- AWQ/GPTQ/EXL2 support
- Memory efficiency improvements
- API compatibility
- Speed comparison
Deliverable: APHRODITE_BENCHMARK.md
3D. Text Generation Inference (Hugging Face TGI)
Why: Production-grade, used by HF internally
Installation:
docker run -d --name tgi \
--gpus all \
-v ~/.cache/huggingface:/data \
-p 8081:80 \
ghcr.io/huggingface/text-generation-inference:latest \
--model-id Qwen/Qwen2.5-7B-Instruct \
--quantize awqTests:
- Quantization support (AWQ, GPTQ, bitsandbytes)
- Continuous batching effectiveness
- Tensor parallelism across 2 GPUs
- Production stability
Deliverable: TGI_BENCHMARK.md
3E. MLC LLM (Apache TVM-based)
Why: Compiles models to optimized kernels, claims best memory efficiency
Installation:
pip install mlc-llm mlc-ai-nightlyTests:
- Model compilation to optimized format
- Memory usage vs raw PyTorch
- Speed after compilation
- Quantization options (4-bit, 3-bit)
Deliverable: MLC_LLM_BENCHMARK.md
3F. ExLlamaV2 (GPTQ-focused)
Why: Fastest GPTQ inference engine
Installation:
git clone https://github.com/turboderp/exllamav2
pip install -e exllamav2Tests:
- EXL2 quantization (better than AWQ?)
- Speed claims (2x+ faster than transformers)
- Memory efficiency
- Multi-GPU support
Deliverable: EXLLAMAV2_BENCHMARK.md
Phase 4: Multi-Engine Architecture
Hypothesis: Different engines excel at different tasks
Strategy: Route by Task Type
┌─────────────────────────────────────┐
│ PAI Inference Router │
└─────────────────────────────────────┘
│
┌─────────┴─────────┐─────────┐
│ │ │
┌────▼────┐ ┌──────▼──┐ ┌──▼─────┐
│ llama.cpp│ │ SGLang │ │ TGI │
│ (fast) │ │(struct) │ │(batch) │
└──────────┘ └─────────┘ └────────┘
Tests:
-
Task classification
- Fast single queries → llama.cpp
- Structured output → SGLang
- Batch processing → TGI
-
Routing overhead
- Decision latency
- Load balancing
- Failure handling
-
Combined throughput
- Can we saturate both GPUs?
- Better utilization than single engine?
Deliverable: MULTI_ENGINE_ARCHITECTURE.md
Phase 5: Quantization Deep Dive
Goal: Find optimal quality/speed/memory balance
Formats to Test
-
GGUF variants (llama.cpp)
- Q4_K_M, Q4_K_S, Q5_K_M, Q6_K, Q8_0
- IQ4_XS, IQ3_M (imatrix quants)
-
AWQ (Activation-aware Weight Quantization)
- 4-bit, group size 128
- Tried with vLLM, test with others
-
GPTQ (Post-training quantization)
- 4-bit, 3-bit
- Group size impact
-
EXL2 (ExLlamaV2 format)
- Variable bitrate
- Quality claims
-
bitsandbytes (Runtime quantization)
- INT8, FP4, NF4
- Dynamic vs static
Quality Evaluation
- MMLU benchmark subset
- Human eval on 50 test prompts
- Task-specific accuracy (code, reasoning, facts)
Deliverable: QUANTIZATION_ANALYSIS.md
Phase 6: Hardware Optimization
6A. GPU Allocation Strategies
Test Configurations:
-
Single GPU (RTX 4060 only)
- Larger model on 8GB
- Compare to split approach
-
Split by task
- GPU 0: Fast small model (3B)
- GPU 1: Slow large model (7B)
-
Tensor parallelism (if engine supports mismatched sizes)
- Custom sharding
- Load balancing
-
Pipeline parallelism
- Layers split across GPUs
- Test with TGI/SGLang
Deliverable: GPU_ALLOCATION_STRATEGY.md
6B. CPU Offloading
Hypothesis: 47GB RAM underutilized, offload to CPU
Tests:
-
Partial offloading
- Layers on CPU vs GPU
- Performance vs memory tradeoff
-
KV cache on CPU
- Large context on CPU memory
- Speed impact measurement
-
Hybrid inference
- Prefill on GPU
- Decode on CPU
- Latency analysis
Deliverable: CPU_OFFLOAD_ANALYSIS.md
6C. System-Level Tuning
OS/Driver Optimizations:
-
CUDA optimizations
export CUDA_LAUNCH_BLOCKING=0 export CUDA_VISIBLE_DEVICES=0,1 export CUDA_DEVICE_ORDER=PCI_BUS_ID -
CPU governor
sudo cpupower frequency-set -g performance -
Memory settings
# Huge pages for faster memory access sudo sysctl -w vm.nr_hugepages=1024 -
PCIe optimization
- Check PCIe lanes (lspci -vv)
- Ensure x16 for GPUs
Deliverable: SYSTEM_TUNING_GUIDE.md
Phase 7: Production Architecture Design
Input: Results from Phases 1-6
Design Decisions
-
Primary engine selection
- Based on benchmark winner
- Fallback engine choice
-
Model strategy
- Which sizes for which tasks
- Quantization levels
- Preload vs on-demand
-
API design
/v1/chat/completions (OpenAI-compatible) /v1/generate (Raw generation) /v1/embed (Embeddings) /health (Monitoring) /metrics (Prometheus) -
High availability
- Health checks
- Auto-restart
- Graceful degradation
- Cloud fallback triggers
-
Monitoring
- GPU utilization
- Request latency
- Queue depth
- Error rates
Deliverable: PRODUCTION_ARCHITECTURE.md
Phase 8: Integration with PAI
8A. Update PAI Routing Logic
Current: PAI/Tools/Inference.ts
New: Multi-engine dispatcher
interface EngineCapability {
name: string;
endpoint: string;
strengths: TaskType[];
maxContext: number;
tokensPerSec: number;
costPerToken: number; // $0 for local
}
const engines: EngineCapability[] = [
{
name: "llama.cpp-fast",
endpoint: "http://localhost:8080",
strengths: ["classification", "fast_generation"],
maxContext: 8192,
tokensPerSec: 45,
costPerToken: 0
},
{
name: "sglang-structured",
endpoint: "http://localhost:30000",
strengths: ["json_extraction", "structured_output"],
maxContext: 4096,
tokensPerSec: 38,
costPerToken: 0
},
// ... Claude, Gemini fallbacks
];8B. Skill Updates
Update skills to leverage local capabilities:
- OllamaSkill → LocalInferenceSkill
- Route by capability, not by engine
- Automatic fallback to cloud
8C. Cost Tracking
Even though local is “free”:
- Track GPU hours
- Electricity cost estimation
- Compare to cloud equivalent cost
Deliverable: PAI_INTEGRATION_PLAN.md
Phase 9: Benchmark Harness
Create standardized testing framework
Test Suite
interface BenchmarkTest {
name: string;
prompt: string;
expectedTokens: number;
taskType: "generation" | "classification" | "json" | "reasoning";
warmup: boolean;
}
const standardTests: BenchmarkTest[] = [
{
name: "fast-classification",
prompt: "Classify sentiment: 'This is great!' Response: ",
expectedTokens: 5,
taskType: "classification",
warmup: true
},
{
name: "medium-generation",
prompt: "Write a haiku about coding",
expectedTokens: 30,
taskType: "generation",
warmup: false
},
// ... 20+ tests across task types
];Metrics Collected
For each engine × model × quantization:
- Time to first token (TTFT)
- Tokens per second (TPS)
- GPU memory used
- System memory used
- GPU utilization %
- Power consumption (if measurable)
- Quality score (vs reference)
- Concurrent request handling (1, 2, 4, 8 parallel)
Output Format
{
"engine": "llama.cpp",
"model": "qwen2.5-7b-q4_k_m",
"gpu": "RTX 4060",
"results": {
"fast-classification": {
"ttft_ms": 45,
"tps": 52.3,
"memory_mb": 4200,
"quality_score": 0.95
}
}
}Deliverable: BenchmarkHarness.ts + results database
Timeline Estimate
| Phase | Estimated Time | Priority |
|---|---|---|
| 1. Ollama Baseline | 2 hours | HIGH |
| 2. llama.cpp Direct | 3 hours | HIGH |
| 3A. LocalAI | 2 hours | MEDIUM |
| 3B. SGLang | 3 hours | HIGH |
| 3C. Aphrodite | 2 hours | LOW |
| 3D. TGI | 2 hours | MEDIUM |
| 3E. MLC LLM | 4 hours | LOW |
| 3F. ExLlamaV2 | 2 hours | MEDIUM |
| 4. Multi-Engine | 4 hours | HIGH |
| 5. Quantization | 6 hours | HIGH |
| 6. Hardware Opt | 4 hours | MEDIUM |
| 7. Architecture | 3 hours | HIGH |
| 8. PAI Integration | 4 hours | HIGH |
| 9. Benchmark Harness | 4 hours | HIGH |
| TOTAL | 45 hours |
Aggressive: 1 week full-time
Realistic: 2-3 weeks part-time
Thorough: 1 month with quality evaluation
Decision Framework
After each phase, decide:
Stop Conditions (Found Winner)
- Engine is 2x+ faster than Ollama
- Uses 30%+ less memory
- Same or better quality
- Production-stable
Continue Conditions
- Results inconclusive
- Multiple engines competitive
- Trade-offs need quantification
Pivot Conditions
- All local options inferior to cloud
- Hardware upgrade more cost-effective
- Hybrid approach optimal
Expected Outcomes
Likely Scenarios
Scenario A: llama.cpp Direct Wins
- Ollama adds overhead
- Replace with llama.cpp server mode
- 20-30% performance gain
Scenario B: Multi-Engine Optimal
- llama.cpp for fast queries
- SGLang for structured output
- TGI for batch processing
- Router coordinates
Scenario C: Specialized by Model Size
- 3B on GPU 0 for fast tasks
- 7B on GPU 1 for complex tasks
- Route by task complexity
Scenario D: Hybrid is Best
- Local for 80% of requests
- Cloud for 20% complex/long-context
- Optimize the 80%, accept cloud cost for 20%
Success Metrics (Final)
After full investigation:
-
Performance
- 2x throughput increase vs baseline Ollama
- <100ms TTFT for simple queries
- Support 10+ concurrent requests
-
Cost
- 90%+ requests on local (vs current 60%)
- $200+/month additional savings
- ROI on time invested positive
-
Quality
- No degradation vs baseline
- Quantization finds optimal point
- Production-stable (99%+ uptime)
-
Usability
- OpenAI-compatible API
- Systemd integration
- Monitoring/alerting
- Auto-recovery
Risk Mitigation
If Investigation Fails
- Keep Ollama (baseline works)
- Document what doesn’t work
- Re-evaluate when hardware/software changes
If Time Exceeds Estimate
- Prioritize HIGH items only
- Accept “good enough” over perfect
- Timebox each phase (hard stop at 2x estimate)
If Nothing Beats Ollama
- Document Ollama as optimal for this hardware
- Focus on tuning Ollama configuration
- Plan for future when hardware upgrades
Next Steps
Ready to start?
- Run Phase 1 (Ollama baseline) - 2 hours
- Run Phase 2 (llama.cpp direct) - 3 hours
- Evaluate and decide on Phase 3 scope
Or want to adjust the plan first?
I can:
- Add/remove engines to test
- Change priority order
- Adjust timeline
- Add specific test cases
- Focus on particular aspect (speed vs memory vs quality)
What’s your preference?