Enterprise β 3 Documents
AI Deployment, Fine-Tuning, Infrastructure
π Enterprise AI Deployment: Production Infrastructure and Operations β 1012 lines
1. Introduction to Enterprise AI
Enterprise AI deployment encompasses the full lifecycle of taking machine learning models from research and development into production environments where they deliver business value at scale. Unlike experimental or proof-of-concept deployments, enterprise AI requires:
- Reliability: 99.9%+ uptime with automated failover
- Scalability: Handle thousands to millions of requests per second
- Security: Data isolation, encryption, access control, audit trails
- Compliance: Meet regulatory requirements (GDPR, HIPAA, SOC2, EU AI Act)
- Cost Efficiency: Optimized inference with caching, routing, and tiered models
- Observability: Full traceability of model inputs, outputs, drift, and performance
- Governance: Approval workflows, model registries, versioning, rollback capability
The enterprise AI stack typically has six layers:
ββββββββββββββββββββββββββββββββββββββββββββββββ
β Application Layer β
β Chatbots / Copilots / Automation / Search β
ββββββββββββββββββββββββββββββββββββββββββββββββ€
β Orchestration Layer β
β Prompt Pipelines / Agent Frameworks / RAG β
ββββββββββββββββββββββββββββββββββββββββββββββββ€
β Model Serving Layer β
β vLLM / TGI / Triton / Ollama / Bedrock β
ββββββββββββββββββββββββββββββββββββββββββββββββ€
β Infrastructure Layer β
β Kubernetes / Docker / Serverless / Edge β
ββββββββββββββββββββββββββββββββββββββββββββββββ€
β Data & Feature Layer β
β Vector DB / Feature Store / Data Lake β
ββββββββββββββββββββββββββββββββββββββββββββββββ€
β Governance & Observability β
β Monitoring / Logging / Audit / Compliance β
ββββββββββββββββββββββββββββββββββββββββββββββββ
1.1 Key Enterprise Considerations
Latency Requirements:
- Real-time (chat): <500ms p95
- Near-real-time (assistant): <3s p95
- Batch: minutes to hours
Throughput:
- Small deployments: 100-1,000 RPM (requests per minute)
- Medium deployments: 1,000-10,000 RPM
- Large deployments: 10,000-100,000+ RPM
Reliability Targets:
- Development: 99% uptime (3.65 days/year downtime)
- Staging: 99.9% uptime (8.76 hours/year downtime)
- Production critical: 99.99% uptime (52.56 minutes/year downtime)
- Mission-critical: 99.999% uptime (5.26 minutes/year downtime)
2. Deployment Architectures
2.1 Cloud Deployment
The most common enterprise pattern. Models run on cloud infrastructure with auto-scaling.
Pros: Elastic scaling, pay-per-use, managed services, global availability
Cons: Data egress costs, latency for on-premise integration, vendor lock-in
Cloud Providers:
- AWS: Bedrock (managed), SageMaker (custom), EC2 (DIY)
- Azure: OpenAI Service, AI Studio, ML Studio
- GCP: Vertex AI (managed), GKE (custom), Cloud Run (serverless)
2.2 On-Premises Deployment
Models run entirely within the organization's data center.
Pros: Complete data sovereignty, low latency for internal services, predictable cost
Cons: Fixed capacity, hardware procurement, maintenance overhead, GPU scarcity
Typical Hardware:
- Inference: NVIDIA A100/H100/H200/B200, AMD MI300X, Intel Gaudi
- Small: 1-4 GPUs per model (7B-70B params)
- Medium: 8-16 GPUs per model (70B-180B params)
- Large: 32-128 GPUs for high-throughput serving
- Training clusters: 256-16,000+ GPUs
2.3 Edge Deployment
Models run on user devices or edge servers.
Pros: Zero latency, offline capability, privacy by design
Cons: Hardware constraints, limited model size, update distribution
Common Edge Targets:
- Mobile: Apple Neural Engine, Qualcomm Hexagon, Samsung NPU
- Laptop: Apple M-series, Intel NPU, NVIDIA RTX (local)
- IoT: Raspberry Pi, Jetson Orin/Nano, Intel Movidius
- Browser: WebGPU, ONNX Runtime Web, Transformers.js
Model Sizes for Edge:
- Quantized 1-3B models (Qwen2.5-1.5B, Phi-3-mini, Gemma-2B)
- Multimodal: MobileCLIP, EfficientViT-SAM
- Speech: Whisper tiny/small, Silero VAD
- 4-bit quantized 7B for high-end devices
2.4 Hybrid Deployment
Combines cloud, on-prem, and edge based on workload requirements.
Strategy:
- Simple queries β Edge (fast, free, private)
- Standard queries β On-prem (low latency, controlled)
- Complex queries β Cloud (unlimited compute, latest models)
- Fine-tuning/Training β Cloud/on-prem GPU clusters
Edge On-Premise Cloud
βββββββββββββββ ββββββββββββββββ ββββββββββββββββ
β Small Models βββββββββββββββΆβ Medium ModelsββββββββββββΆβ Large Models β
β (<3B, 4bit) β escalation β (7B-70B) β escalationβ (70B-400B+) β
β β β β β β
β ~1ms latencyβ β ~50ms lat. β β ~200ms lat. β
β ~$0 cost β β ~fixed cost β β ~per-token β
βββββββββββββββ ββββββββββββββββ ββββββββββββββββ
3. MLOps Pipelines
3.1 Full MLOps Lifecycle
Data Collection β Data Validation β Feature Engineering β Model Training
β β
Model Registry β Model Evaluation β Model Validation β Hyperparameter Tuning
β
Model Deployment β Prediction Serving β Monitoring β Feedback Loop
β
Retraining Trigger
3.2 Key MLOps Components
Feature Store (Feast, Tecton, SageMaker Feature Store):
- Centralized feature computation and storage
- Online (low-latency) and offline (batch) serving
- Feature sharing across teams
- Point-in-time correct feature retrieval for training
Model Registry (MLflow Model Registry, Hugging Face Hub, S3/GCS):
- Versioned model artifacts with metadata
- Model lineage tracking (data β training β deployment)
- Stage transitions (staging β production β archived)
- Model signatures and input/output schemas
Pipeline Orchestration (Kubeflow, Airflow, Prefect, Dagster):
- DAG-based pipeline definition
- Retry and error handling
- Artifact passing between steps
- Scheduled and event-driven execution
Experiment Tracking (MLflow, Weights & Biases, Neptune, Comet):
- Hyperparameter logging
- Metrics visualization
- Artifact storage (model weights, plots, configs)
- Run comparison and search
3.3 CI/CD for ML
βββββββββββββββββββββββ
β Source Control β
β (code + configs) β
ββββββββββββ¬βββββββββββ
β push/PR
βΌ
βββββββββββββββββββββββ
β CI Pipeline β
β - Lint code β
β - Unit tests β
β - Integration tests β
β - Model validation β
ββββββββββββ¬βββββββββββ
β
ββββββββββββΌβββββββββββ
β Model Training β
β (triggered by CI) β
ββββββββββββ¬βββββββββββ
β
ββββββββββββΌβββββββββββ
β Model Evaluation β
β - Benchmark scores β
β - Offline metrics β
β - Bias detection β
ββββββββββββ¬βββββββββββ
β pass?
ββββββββββββΌβββββββββββ
β CD Pipeline β
β - Canary deploy β
β - Shadow testing β
β - Blue-green deploy β
ββββββββββββ¬βββββββββββ
β
ββββββββββββΌβββββββββββ
β Monitoring β
β - Performance β
β - Drift detection β
β - Alerting β
βββββββββββββββββββββββ
4. Model Serving Infrastructure
4.1 Model Serving Frameworks
vLLM:
- High-throughput LLM serving with PagedAttention
- Continuous batching, prefix caching, tensor parallelism
- OpenAI-compatible API
- Supports: LLaMA, Mistral, Qwen, DeepSeek, Falcon, Gemma, GPT-NeoX
- Best for: high-throughput, production LLM serving
Text Generation Inference (TGI) by Hugging Face:
- Production-focused LLM serving
- Tensor parallelism, continuous batching, quantization (bitsandbytes, GPTQ, AWQ)
- Message streaming via Server-Sent Events
- Best for: Hugging Face ecosystem integration
NVIDIA Triton Inference Server:
- Multi-framework support (TensorRT, ONNX, PyTorch, TensorFlow)
- Concurrent model execution, dynamic batching
- Ensemble and BLS (Business Logic Scripting)
- GPU optimization with TensorRT
- Best for: heterogeneous model serving at scale
Ollama:
- Simplified local model serving
- Model management (pull, run, stop)
- OpenAI-compatible API
- GPU acceleration (CUDA, Metal, Vulkan)
- Best for: development, testing, personal use
TensorRT-LLM:
- NVIDIA's optimized LLM serving
- In-flight batching, paged attention, multi-GPU
- FP8, INT4, INT8 quantization
- Best for: maximum GPU utilization on NVIDIA hardware
Comparison Table:
| Framework | Throughput | Latency | GPU Util | Ease | Features |
|---|---|---|---|---|---|
| vLLM | β β β β β | β β β β | β β β β β | β β β β | PagedAttention, prefix caching |
| TGI | β β β β | β β β β | β β β β | β β β β | HuggingFace native |
| Triton | β β β β β | β β β β β | β β β β β | β β β | Multi-framework, ensemble |
| Ollama | β β β | β β β | β β β | β β β β β | Simplest setup |
| TensorRT-LLM | β β β β β | β β β β β | β β β β β | β β | NVIDIA-only, complex |
4.2 Containerization and Orchestration
Docker:
- Standard container format for ML models
- Multi-stage builds for size optimization
- Health checks, resource limits, environment configuration
Kubernetes (K8s):
- Standard orchestration for ML workloads
- Deployments for stateless serving
- StatefulSets for stateful services (vector DBs)
- Horizontal Pod Autoscaler (HPA) for scaling
- Custom metrics (requests per second, queue depth, GPU utilization)
- Node pools for GPU vs CPU workloads
- Affinity/anti-affinity for GPU pod placement
- Service mesh (Istio, Linkerd) for traffic management
Example Kubernetes Deployment:
apiVersion: apps/v1
kind: Deployment
metadata:
name: llm-server
spec:
replicas: 4
selector:
matchLabels:
app: llm-server
template:
metadata:
labels:
app: llm-server
spec:
containers:
- name: vllm
image: vllm/vllm-openai:latest
args: ["--model", "mistralai/Mistral-7B-Instruct-v0.3",
"--tensor-parallel-size", "1",
"--max-model-len", "8192",
"--gpu-memory-utilization", "0.90"]
resources:
limits:
nvidia.com/gpu: 1
memory: "32Gi"
cpu: "8"
nodeSelector:
cloud.google.com/gke-accelerator: nvidia-l4
4.3 Serverless ML
AWS SageMaker Serverless, GCP Cloud Run, Azure Container Apps:
- Scale to zero when not in use
- Pay-per-invocation (no idle cost)
- Cold start latency (5-30 seconds)
- Best for: bursty, low-volume, or spiky workloads
Emerging: serverless GPU (Modal, RunPod, Banana, Replicate):
- GPU-backed serverless inference
- Automatic scaling, no infrastructure management
- Pay per second of GPU usage
- Best for: prototyping, demos, low-volume production
5. Monitoring and Observability
5.1 Metrics Categories
Infrastructure Metrics:
- GPU utilization, memory, temperature
- Request latency (avg, p50, p95, p99, p999)
- Request throughput (RPS / TPS / RPM)
- Error rate (4xx, 5xx, timeout)
- Queue depth and wait time
Model Performance Metrics:
- First-token latency (TTFT β Time to First Token)
- Inter-token latency (ITL)
- Tokens per second (throughput)
- Context window utilization
- Token generation distribution
Quality Metrics:
- Response relevance (human eval, LLM-as-judge)
- Factual accuracy (grounding checks)
- Toxicity / safety score
- Response length distribution
- User satisfaction (thumbs up/down, ratings)
Drift Metrics:
- Input distribution drift (embedding distance over time)
- Output distribution drift
- Performance drift (quality metrics over time)
- Data drift detection (statistical tests: KS, PSI, MMD)
5.2 Monitoring Tools
Infrastructure:
- Prometheus + Grafana (standard open-source stack)
- Datadog (SaaS, full-stack monitoring)
- New Relic (APM-focused)
- Grafana Cloud / Grafana Loki (logs + metrics)
ML-Specific:
- LangSmith (LLM observability, traces, evaluations)
- Weights & Biases Prompts (prompt monitoring)
- Arize AI (ML observability, drift detection)
- LangFuse (open-source LLM monitoring)
- Helicone (LLM proxy with logging)
- MLflow (model monitoring)
Alerting:
- PagerDuty / OpsGenie for on-call
- Slack / Teams notifications for warnings
- Email for daily/weekly reports
5.3 Observability Tracing
Distributed tracing for LLM applications provides end-to-end visibility:
User Request
ββ API Gateway
ββ Orchestrator
ββ Prompt Template (5ms)
ββ RAG Retrieval (150ms)
β ββ Query Embedding (20ms)
β ββ Vector DB Search (80ms)
β ββ Re-ranking (50ms)
ββ Context Assembly (10ms)
ββ LLM Inference (800ms)
β ββ Time-to-First-Token (400ms)
β ββ Token Generation (400ms)
ββ Post-processing (15ms)
Total: ~980ms
6. A/B Testing for LLMs
6.1 Testing Strategies
Shadow Testing:
- New model runs in parallel with production model
- Outputs compared but only production served to users
- Safe: no user impact, collect comparison data
- Duration: 1-7 days
Canary Deployment:
- Gradually shift traffic to new model (5% β 25% β 50% β 100%)
- Monitor for regressions at each step
- Auto-rollback if error rate or quality drops
- Duration: hours to days
Champion/Challenger:
- Old model (champion) vs new model (challenger)
- Controlled experiment with statistical significance testing
- Measure: user satisfaction, task completion, latency, cost
- Requires sufficient traffic for statistical power
Interleaved A/B:
- Each user sees responses from both models (side-by-side or interleaved)
- Blind comparison (user doesn't know which is which)
- Faster convergence, requires less traffic
- More complex to implement
6.2 Evaluation Metrics
| Metric | What It Measures | How to Measure |
|---|---|---|
| Win Rate | User preference | Blind A/B comparison |
| Task Completion | Did user get answer? | Button click, follow-up rate |
| User Satisfaction | How did user feel? | Thumbs up/down, rating |
| Latency | Speed | p50/p95/p99 response time |
| Cost | Economics | Tokens per request, model cost |
| Safety Score | Content safety | Automated guardrails, human review |
| Grounding | Factual accuracy | LLM-as-judge, human fact-check |
7. Cost Optimization
7.1 Cost Components
Total Cost = Compute + API + Storage + Network + Human
- Compute: GPU instances, CPU instances, serverless
- API: Third-party model APIs (OpenAI, Anthropic, etc.)
- Storage: Vector databases, model storage, embeddings cache
- Network: Data transfer, cross-region, egress
- Human: Labeling, monitoring, prompt engineering
7.2 Optimization Strategies
Prompt Optimization:
- Shorter prompts reduce token costs
- Fewer few-shot examples (use dynamic retrieval instead)
- System prompt compression
- Prompt caching (Anthropic's prompt caching, Gemini context caching)
Output Length Control:
- Use max_tokens constraints
- Set stop sequences early
- Use structured output formatting
- Compress or truncate when possible
Model Tiering:
Query Type β Model to Use β Cost/1M tokens
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Simple Q&A β GPT-4o-mini / Mistral-7B β $0.15-0.60
Content extraction β Claude 3 Haiku / Gemini Flash β $0.25-1.00
Code generation β Claude 3.5 Sonnet / GPT-4o β $3.00-10.00
Complex reasoning β Claude 4 Opus / DeepSeek-R1 β $15.00-60.00
Research / analysis β Full o3 / Gemini 2.5 Pro β $15.00-75.00
Semantic Cache:
- Cache results for semantically similar queries
- Reduce duplicate computation by 20-60%
- Memory cost of cache vs token cost savings
- Typical: Redis + embedding-based similarity search
Batching:
- Batch non-realtime requests for higher throughput
- vLLM server batching reduces per-request overhead
- Combine multiple prompts into single inference call
Provider Cost Comparison (as of 2026):
| Provider | Model | Input (per 1M tokens) | Output (per 1M tokens) |
|---|---|---|---|
| OpenAI | GPT-4o | $2.50 | $10.00 |
| OpenAI | GPT-4o-mini | $0.15 | $0.60 |
| Anthropic | Claude 4 Sonnet | $3.00 | $15.00 |
| Anthropic | Claude 3 Haiku | $0.25 | $1.25 |
| Gemini 2.0 Flash | $0.10 | $0.40 | |
| Gemini 2.5 Pro | $1.25 | $10.00 | |
| DeepSeek | V3 | $0.27 | $1.10 |
| Self-hosted | LLaMA 3 70B (vLLM) | ~$0.12 | ~$0.48 |
| Self-hosted | Qwen2.5-7B (Ollama) | ~$0.015 | ~$0.06 |
8. Compliance and Governance
8.1 Regulatory Landscape
EU AI Act (2025+):
- Risk categories: minimal, limited, high, unacceptable
- High-risk AI systems require: risk assessment, data governance, transparency, human oversight, accuracy/robustness
- GPAI (General Purpose AI) models: transparency, copyright, systemic risk assessment
- Fines: up to 7% of global annual turnover or β¬35M
US Executive Orders on AI (2023-2026):
- Safety testing of powerful AI systems (red-teaming results)
- Watermarking (C2PA content provenance standards)
- Privacy-preserving techniques research
- Equity and civil rights guidance
China AI Regulations (2023-2026):
- Algorithm registration requirements
- Content safety reviews for generative AI
- Data localization requirements
- Synthetic data labeling mandates
Other Jurisdictions:
- UK: AI Safety Institute, pro-innovation framework
- Canada: AIDA (Artificial Intelligence and Data Act)
- Japan: AI Guidelines (soft law approach)
- Singapore: Model AI Governance Framework
- Brazil: Bill 2338/2023 (LGPDP + AI specific provisions)
- India: NITI Aayog AI principles
8.2 Governance Framework
Model Governance Lifecycle:
1. Model Intake
- Business case approval
- Risk assessment
- Regulatory mapping
2. Model Development
- Data provenance documentation
- Fairness / bias evaluation
- Explainability analysis
- Documentation (model cards)
3. Model Validation
- Independent validation team
- Performance benchmarking
- Stress testing / edge cases
- Compliance review
4. Model Approval
- Governance committee review
- Approval for deployment
- Conditions / limitations documented
5. Model Deployment
- Staged rollout
- Monitoring thresholds set
- Human-in-the-loop requirements
6. Model Monitoring
- Ongoing performance tracking
- Drift detection
- Periodic re-validation
- Incident response plan
7. Model Retirement
- Decommissioning plan
- Data cleanup
- Lessons learned
8.3 Model Documentation
Model Cards (Standardized by Hugging Face, now widely adopted):
- Model description
- Intended use and limitations
- Training data and methodology
- Evaluation results (segmented by group)
- Ethical considerations
- Caveats and recommendations
Datasheets for Datasets:
- Motivation (why was dataset created?)
- Composition (what fields, how much data?)
- Collection process (how was data gathered?)
- Preprocessing (cleaning, filtering, labeling)
- Uses (what are appropriate/forbidden uses?)
- Distribution (licenses, access restrictions)
- Maintenance (who updates, how?)
9. Enterprise LLM Platforms
9.1 Azure OpenAI Service
Capabilities:
- GPT-4o, GPT-4 Turbo, GPT-4o-mini, GPT-4.1 models
- GPT-4o Realtime API (speech-to-speech)
- DALL-E 3, Whisper, Embeddings models
- Azure AI Search integration (built-in RAG)
- Content filtering and safety system
- Private endpoints and managed VNet
- Role-based access control (RBAC)
- Data residency (regional deployments)
- Responsible AI dashboard
- SLA: 99.9% for provisioned throughput
Pricing Model:
- Pay-as-you-go per token
- Provisioned throughput (reserved capacity)
- Batch processing (50% discount)
9.2 AWS Bedrock
Capabilities:
- Multiple foundation models (Claude, LLaMA, Mistral, Cohere, AI21, Amazon Titan)
- Knowledge Bases (serverless RAG with OpenSearch)
- Agents (multi-step task orchestration)
- Guardrails (content filtering, topic denial, PII redaction)
- Model evaluation (automated and human)
- Provisioned throughput
- Integration with AWS ecosystem (Lambda, S3, Kendra, DynamoDB)
9.3 GCP Vertex AI
Capabilities:
- Gemini 1.5/2.0/2.5 series, Claude, LLaMA, Gemma
- Agent Builder (no-code agent creation)
- Model Garden (curated model hub)
- Vector Search (scaled ANN search)
- Evaluation Service (automated metrics)
- Vertex AI Studio (prompt design and testing)
- Model Registry and Model Monitoring
9.4 Databricks
Capabilities:
- Unity Catalog (unified data governance)
- MLflow for model lifecycle
- Model Serving (serverless GPU endpoints)
- Foundation Model APIs (Llama, Mixtral, DBRX)
- Vector Search for RAG
- AI/BI dashboards and Genie
- Lakehouse AI platform
9.5 Enterprise Platform Comparison
| Feature | Azure OpenAI | AWS Bedrock | GCP Vertex | Databricks |
|---|---|---|---|---|
| Model Variety | OpenAI + select open | Broad (multi-provider) | Google + select open | Open models |
| RAG Built-in | Yes (AI Search) | Yes (Knowledge Bases) | Yes (Vector Search) | Yes (Vector Search) |
| Guardrails | Yes | Yes | Yes | Custom |
| Private Network | Yes (VNet) | Yes (VPC) | Yes (VPC) | Yes |
| Data Sovereignty | Yes (regions) | Yes (regions) | Yes (regions) | Yes |
| SLA | 99.9% | 99.9% | 99.9% | 99.9% |
| Custom Models | Fine-tuning only | Fine-tuning | Fine-tuning | Full training pipeline |
| Ease of Use | β β β β | β β β β | β β β β | β β β |
10. Security and Privacy
10.1 Threat Model for LLM Applications
Common Attack Vectors:
- Prompt Injection: Crafting inputs that override system instructions
- Jailbreaking: Bypassing safety guardrails
- Data Extraction: Extracting training data or system prompts
- Indirect Injection: Malicious content in retrieved documents
- Denial of Service: Inputs that cause excessive token generation
- Model Inversion: Reconstructing training examples from model output
- Supply Chain: Compromised model weights, plugins, or libraries
10.2 Defense Layers
Input Layer:
ββ Input Validation (length limits, pattern filtering)
ββ PII Detection and Redaction
ββ Prompt Injection Detection (Guardrails AI, NeMo Guardrails)
ββ Rate Limiting and Quota Management
Orchestration Layer:
ββ Context Sanitization (clean retrieved documents)
ββ Permission Checks (user access to specific data)
ββ Tool Access Control (which tools per user role)
ββ Output Verification (check for info leakage)
Model Layer:
ββ Model-Level Guardrails (system prompt hardening)
ββ Content Filter (toxicity, hate, violence, sexual)
ββ Output Constraint (JSON schema enforcement, regex)
ββ Hallucination Detection (grounding checks)
Output Layer:
ββ PII Re-check on output
ββ Output Length Limiting
ββ Response Approval (human-in-the-loop for critical actions)
ββ Audit Logging (full request/response trace)
10.3 Data Isolation
Multi-Tenant Architectures:
- Database-per-tenant: Strongest isolation, highest cost
- Schema-per-tenant: Good isolation, shared infrastructure
- Row-level security: Weakest isolation, most efficient
- Document-level filtering: Via vector DB metadata filters
Privacy Techniques:
- Differential Privacy: Add calibrated noise to training data
- Federated Learning: Train across institutions without data sharing
- Data Masking: Replace sensitive values with masked tokens
- On-Device Processing: Process sensitive data offline on user devices
11. Multi-Model Routing
11.1 Router Architecture
The multi-model router directs each query to the optimal model based on complexity, cost, latency requirements, and capability needs.
ββββββββββββββββββββββββ
β Query Classifier β
β (small fast LLM) β
ββββββββ¬ββββββββ¬ββββββββ
β β
βββββββββββββββ βββββββββββββββ
βΌ βΌ
ββββββββββββββββββββ ββββββββββββββββββββββββ
β Simple Router β β Complex Router β
β β β β
β 10% β GPT-4o-miniβ β 60% β Claude 4 Sonnetβ
β 30% β Gemini Flashβ β 25% β GPT-4o β
β 60% β Mistral-7B β β 15% β DeepSeek-R1 β
ββββββββββββββββββββ ββββββββββββββββββββββββ
β β
ββββββββββββ¬ββββββββββββββββββββββ
βΌ
βββββββββββββββββββββββ
β Response Validator β
β - Check quality β
β - Check safety β
β - Escalate if poor β
βββββββββββββββββββββββ
11.2 Routing Strategies
Rule-Based Routing:
- Query length < 50 tokens β cheap model
- Contains code β code-capable model
- Contains PII β local model
- Contains "error"/"exception" β reasoning model
ML-Based Routing:
- Train a classifier to predict model success
- Features: query length, domain, complexity score
- Label: which model produced best result
- Continuous learning from user feedback
Cost-Aware Routing:
- Budget per query/quota per user
- Route to cheaper model when budget near limit
- Fallback chain: try cheap β escalate if needed
Latency-Aware Routing:
- Critical path queries β fast model
- Responses under SLA β balanced routing
- Background/batch β cheapest possible
- Peak load β degrade model tier
11.3 Model Cascade
Level 1: Local (Ollama / ONNX)
- Model: Qwen2.5-7B-Q4 / Phi-4-mini
- Latency: ~50ms
- Cost: ~$0
- Use: Simple FAQ, autocomplete, quick Q&A
- Fallback threshold: confidence < 0.8 or "I don't know"
Level 2: Fast API (GPT-4o-mini / Claude Haiku / Gemini Flash)
- Model: gpt-4o-mini / claude-3-haiku
- Latency: ~200ms
- Cost: $0.15-0.60/1M
- Use: Standard queries, classification, extraction
- Fallback threshold: uncertain, user dissatisfied
Level 3: Premium API (GPT-4o / Claude Sonnet / Gemini Pro)
- Model: gpt-4o / claude-4-sonnet
- Latency: ~800ms
- Cost: $2.50-15.00/1M
- Use: Complex reasoning, code generation, analysis
- Fallback threshold: task failed or high complexity detected
Level 4: Expert (DeepSeek-R1 / Claude Opus / o4-mini)
- Model: deepseek-r1 / claude-4-opus
- Latency: ~3s
- Cost: $15.00-60.00/1M
- Use: Research, math, PhD-level reasoning, safety-critical
- No fallback (this is the ceiling)
12. Scaling Strategies
12.1 Horizontal Scaling
Scale model serving by adding more replicas:
Stateless (Easy): LLM serving pods behind a load balancer
- Just add more pods
- Requires shared model weight storage (NFS, S3, PVC)
- Watch for GPU memory pressure
Stateful (Hard): Vector databases, caches
- Shard data across pods
- Replication for read availability
- Distributed consensus for writes
12.2 Vertical Scaling
Use larger GPU instances:
Trade-offs:
- Bigger GPUs: higher throughput per instance, but lower availability
- Multiple GPUs: tensor parallelism for large models, pipeline parallelism for throughput
- Instance types: A10G (24GB), L4 (24GB), A100 (40/80GB), H100 (80GB), H200 (141GB), B200 (192GB)
12.3 Model Parallelism Strategies
Tensor Parallelism (TP): Split layers across GPUs
- Requires NVLink/NVSwitch for fast GPU-to-GPU communication
- Common for models > 30B parameters
- Minimally: 1 GPU for 7B, 2 for 13B, 4 for 34B, 8 for 70B
Pipeline Parallelism (PP): Split layers into stages
- Each GPU has a subset of consecutive layers
- Lower communication requirement than TP
- Suffers from bubble overhead
Sequence Parallelism (SP): Split sequence dimension across GPUs
- Useful for very long sequences
- Often combined with TP
Expert Parallelism (EP): For MoE models, split experts across GPUs
- Each GPU handles subset of experts
- All-to-all communication for expert routing
- Used by Mixtral 8x7B, DeepSeek-V2/V3, DBRX
12.4 Autoscaling Configuration
# Horizontal Pod Autoscaler for LLM serving
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: llm-server-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: llm-server
minReplicas: 2
maxReplicas: 50
metrics:
- type: Pods
pods:
metric:
name: llm_queue_depth
target:
type: AverageValue
averageValue: 5
- type: Pods
pods:
metric:
name: llm_gpu_utilization
target:
type: AverageValue
averageValue: 80
behavior:
scaleDown:
stabilizationWindowSeconds: 300
policies:
- type: Percent
value: 50
periodSeconds: 60
scaleUp:
stabilizationWindowSeconds: 0
policies:
- type: Percent
value: 200
periodSeconds: 15
π Enterprise Fine-Tuning β 3378 lines
A comprehensive guide to fine-tuning large language models in enterprise environments, covering decision frameworks, PEFT methods, data pipelines, training infrastructure, and production deployment.
1. When to Fine-Tune vs RAG vs Prompting
Decision Matrix
Choosing the right paradigm β fine-tuning, Retrieval-Augmented Generation (RAG), or prompting β is the most consequential architectural decision in any LLM project. The wrong choice burns GPU budget on unnecessary training, couples you to fragile prompt engineering, or locks you into a retrieval pipeline with latency you cannot sustain. This matrix provides a structured framework.
| Decision Factor | Prompting (Few-shot / Zero-shot) | RAG | Fine-Tuning |
|---|---|---|---|
| Knowledge recency | Stale at cutoff | Fresh via retrieval index | Frozen at training time |
| Data volume | ~10 examples max | Unlimited (index scale) | 1Kβ100K+ examples |
| Task specificity | General-purpose | Factual recall tasks | Specialized behavior / style |
| Latency budget | ~200msβ2s | +50β500ms retrieval overhead | Same as base model |
| Cost per inference | Lowest | Medium (storage + retrieval) | Highest (GPU hour amortized) |
| Data privacy | No data retention | Documents indexed externally | Weights memorize data |
| Domain jargon | Poor (no memorization) | Good (if in retrieved docs) | Excellent (deep assimilation) |
| Output format control | Fragile (prompt engineering) | Moderate | Strong (format baked into weights) |
| Model size constraints | None | Must fit context + retrieved docs | Must fit batch on GPU |
| Iteration speed | Instant prompt edits | Re-index documents (hours) | Re-train (hoursβdays) |
| Hallucination risk | Highest | Lowest (grounded in retrieved text) | Moderate (memorized patterns) |
| Maintenance burden | Low | Medium (pipeline monitoring) | High (retraining cycles) |
Decision Flow
Start with prompting. If the task can be accomplished with 0β10 examples in the prompt and the required knowledge fits within the model's training cutoff, prompting is free and infinitely fast to iterate. Use structured output (JSON mode, function calling) to enforce format.
Add RAG when the model needs access to information outside its weights. If the answer depends on specific documents, proprietary knowledge bases, or frequently updating data, RAG provides grounding without retraining. RAG is also the correct choice when data volumes exceed what fine-tuning can absorb (100K+ docs) or when data sources change daily.
Fine-tune when the model needs to internalize a behavior, style, or domain. Fine-tuning shines when:
- The output format must be strictly controlled (e.g., legal document templates, clinical note styles)
- The model must deeply understand domain-specific jargon and concepts (e.g., medical terminology, legal citations, financial reporting standards)
- The model needs to exhibit a specific persona or tone consistently
- RAG retrieval quality is inadequate (low precision/recall on domain corpus)
- Inference latency cannot tolerate retrieval overhead
Hybrid Patterns
The most successful enterprise deployments combine all three:
Prompting (system prompt + instructions)
β
RAG (retrieved context injected into prompt)
β
Fine-Tuned Model (domain-adapted base)
Example: Enterprise Legal Assistant
- Prompting: System prompt defines lawyer persona, jurisdiction rules, and output format
- RAG: Retrieves relevant case law, statutes, and contract clauses from vector database
- Fine-Tuning: Model fine-tuned on legal document corpus for citation format, legal reasoning, and jurisdiction-specific terminology
Anti-Patterns to Avoid
| Anti-Pattern | Why It Fails |
|---|---|
| Fine-tuning on facts that change quarterly | Model memorizes stale data; retraining needed every quarter |
| RAG for tasks requiring deep reasoning | Retrieved context is fragmented; model cannot synthesize across docs |
| Prompting for complex multi-step workflows | Prompt becomes fragile, long, and expensive; context window overflow |
| Fine-tuning to "add knowledge" | Fine-tuning teaches behavior/shape, not facts; RAG is better for knowledge |
| No fallback strategy | All three should degrade gracefully; RAG fallback from fine-tuning |
2. Domain Adaptation Strategies
2.1 Legal Domain
Legal NLP requires precision in citation format, jurisdiction awareness, and understanding of specialized terminology spanning centuries of precedent.
Key Adaptation Approaches:
- Continued pre-training (CPT) on legal corpora (CourtListener, CaseLaw, PACER, legal textbooks, statutes) β 10Bβ50B tokens for base model assimilation
- Instruction fine-tuning on legal QA pairs, contract analysis, and legal reasoning chains
- RLHF/DPO for alignment with legal ethics, confidentiality rules, and jurisdiction-specific output formats
Critical Capabilities:
- Citation extraction and formatting (Bluebook, ALWD, OSCOLA)
- Legal entity recognition (statutes, case names, court names, judges, parties)
- Jurisdiction classification (federal vs state, common law vs civil law)
- Temporal reasoning (overruled precedents, statutory amendments)
Dataset Sources:
- Contracts: EDGAR (SEC filings), US Contract datasets (200K+ contracts), Atticus (contract review)
- Case Law: CourtListener bulk data, Caselaw Access Project (Harvard Law), Free Law Project
- Statutes: US Code XML, State legislative databases, EUR-Lex (EU law)
- Legal QA: LexGLUE, CaseHOLD, LegalBench, CUAD (Contract Understanding Atticus Dataset)
Notable Domain-specific Models:
- LLaMA-based: SaulLM-7B, SaulLM-141B (legal)
- BERT-based: Legal-BERT, CaseLaw-BERT, RoBERTa-Legal
- Encoder-only: LexNLP-based models
Evaluation Benchmarks:
- LegalBench (162 tasks across 6 categories: reasoning, interpretation, drafting, analysis, entity, citation)
- LexGLUE (7 tasks: ECHR, casehold, unfair_tos, etc.)
- CUAD (41 contract question types)
- CaseHOLD (citation prediction)
2.2 Medical Domain
Medical adaptation requires handling of clinical abbreviations, drug names, anatomical terminology, and strict adherence to medical formatting standards (ICD-10, SNOMED CT, RxNorm).
Key Adaptation Approaches:
- Continued pre-training on PubMed Central, MIMIC-III/IV clinical notes, Medscape, textbooks
- Multi-task fine-tuning: NER for medical entities, relation extraction for drug interactions, summarization for clinical notes
- Reinforcement learning from patient outcome proxies (not recommended without rigorous oversight)
Critical Capabilities:
- Medical entity extraction (symptoms, diagnoses, medications, procedures, lab values)
- ICD-10/SNOMED/CPT code assignment
- Clinical note summarization and structured data extraction
- Radiology report generation
- Drug-drug interaction detection
Dataset Sources:
- Clinical Notes: MIMIC-III/IV (requires credentialed access), i2b2/n2c2 datasets, eICU
- Biomedical Literature: PubMed Central Open Access (3M+ full-text articles), PubMed abstracts
- Medical QA: MedQA (USMLE), MedMCQA, PubMedQA, BioASQ
- Drug Data: DrugBank, RxNorm, DailyMed (FDA labels)
Privacy Considerations:
- HIPAA compliance required for training on protected health information (PHI)
- De-identification before any training (MIMIC is pre-de-identified)
- Differential privacy during training when using clinical data
- Model weights may memorize PHI β avoid deploying on raw clinical text
Evaluation Benchmarks:
- MedQA (USMLE step questions)
- MedMCQA (multi-choice medical questions)
- PubMedQA (biomedical QA)
- BioBERT-based NER benchmarks (NCBI Disease, BC5CDR, JNLPBA)
2.3 Finance Domain
Financial models need to understand SEC filings, market terminology, financial ratios, and temporal market context.
Key Adaptation Approaches:
- CPT on financial corpus: SEC filings (10-K, 10-Q, 8-K, S-1), earnings call transcripts, analyst reports, financial news
- Instruction tuning for financial QA, sentiment analysis, numerical reasoning, and report generation
- Numerical tokenization considerations (some domains benefit from digit tokenization or specialized number embeddings)
Critical Capabilities:
- Financial entity recognition (ticker symbols, company names, currency amounts, financial ratios)
- Numerical reasoning (percentage change, CAGR, EPS, P/E ratio calculation)
- Financial sentiment analysis (bullish/bearish/neutral on financial texts)
- SEC filing structure understanding (Item numbers, MD&A, Risk Factors, Financial Statements)
- Multi-lingual support (global markets)
Dataset Sources:
- SEC Filings: EDGAR (free access via SEC API), thousands of new filings daily
- Earnings Calls: Transcripts from SeekingAlpha, Motley Fool, manual scraping
- Financial News: Reuters, Bloomberg (licensed), Yahoo Finance
- Financial QA: FinQA, ConvFinQA, TAT-QA (table-based), FinanceBench
Numerical Capabilities:
Financial NLP places extreme demands on numerical reasoning. Evaluate:
- Calculation of financial ratios given raw financial data
- Trend identification (YoY growth, QoQ changes)
- Table understanding (financial statements in tabular format)
- Currency conversion and unit normalization
Evaluation Benchmarks:
- FinQA (numerical reasoning over financial reports)
- ConvFinQA (conversational financial QA)
- FinanceBench (real-world financial analyst questions)
- TAT-QA (table-and-text hybrid QA)
- FiQA SA (financial sentiment analysis)
2.4 Code Domain
Code-adapted models power developer productivity tools, automated documentation, code review, and bug fixing.
Key Adaptation Approaches:
- CPT on code corpora: GitHub repositories (permissively licensed), Stack Overflow, technical documentation
- Fill-in-the-middle (FIM) training: predict masked code spans, not just left-to-right generation
- Multi-language training: Python, JavaScript, TypeScript, Java, Go, Rust, C++, SQL, Bash
Critical Capabilities:
- Code completion (single-line, multi-line, whole-function)
- Code explanation and documentation generation
- Bug detection and fix suggestion
- Code translation (e.g., Python to JavaScript)
- SQL query generation from natural language
- Shell command generation
Dataset Sources:
- GitHub: The Stack (3TB+), CodeParrot, GitHub Archive
- QA: Stack Overflow data dump, CodeSearchNet, StaQC
- Documentation: Official docs (Python, JavaScript, Rust, etc.)
Training Considerations:
- FIM training: Use a FIM transformation that converts code into <PREFIX><SUFFIX><HOLE> format. Common implementations: PSM (Prefix-Suffix-Middle), SPM (Suffix-Prefix-Middle)
- Language balancing: Weight smaller languages (Rust, Haskell) higher to prevent catastrophic forgetting
- File-level context: Code files have inter-dependencies; consider whole-file or cross-file context windows
- Syntax validation: Add AST validation to training data filters
Evaluation Benchmarks:
- HumanEval (function synthesis from docstrings)
- MBPP (mostly basic Python programming)
- SWE-bench (real-world GitHub issue resolution)
- BigCodeBench (multi-language, multi-task)
- RepoBench (repository-level code completion)
2.5 Customer Support Domain
Customer support models need tone control, brand voice adherence, escalation logic, and multi-turn conversation management.
Key Adaptation Approaches:
- Conversational fine-tuning on chat logs, ticket histories, and email threads
- Supervised fine-tuning with agent-written responses as targets
- RLHF/DPO with customer satisfaction scores as reward signal
Critical Capabilities:
- Intent classification and routing
- Sentiment detection and empathy generation
- Knowledge base grounding (hybrid with RAG)
- Multi-turn conversation coherence
- Escalation detection and handoff
- Brand voice and tone consistency
Dataset Sources:
- Internal ticketing systems: Zendesk, Freshdesk, Salesforce Service Cloud, Intercom
- Chat logs: Live chat transcripts (anonymized)
- Email: Customer support email threads (PII-redacted)
- Knowledge base: Internal FAQ and documentation
Privacy and Compliance:
- GDPR: Support data often contains PII; ensure retention policies
- CCPA: California consumer privacy rights apply
- PCI DSS: Payment information must never enter training data
- HIPAA: If healthcare support, clinical data protection required
Quality Metrics:
- CSAT (Customer Satisfaction Score) improvement
- First Contact Resolution (FCR) rate
- Average Handle Time (AHT) change
- Escalation rate reduction
- Agent acceptance rate of AI-suggested responses
3. PEFT Methods β Enterprise Focus
Parameter-Efficient Fine-Tuning (PEFT) methods are the backbone of enterprise LLM adaptation. They make fine-tuning economically viable by updating only a tiny fraction of model parameters while retaining the base model's general capabilities.
3.1 LoRA (Low-Rank Adaptation)
Core Concept:
Given a pre-trained weight matrix W β β^{dΓk}, LoRA constrains its update ΞW to be low-rank:
ΞW = BA, where B β β^{dΓr}, A β β^{rΓk}, and r βͺ min(d, k)
The forward pass becomes:
h = Wx + BAx
Only A and B are trained; W is frozen.
Hyperparameters in Detail:
Rank (r)
The rank r controls the representational capacity of the adapter. Enterprise practitioners should think of r as a budget parameter: more rank = more expressivity, more parameters, more VRAM, more regularization needed.
| Rank | Parameters (7B, q_proj+v_proj) | Typical Use Case |
|---|---|---|
| 1 | ~0.5M | Simple domain adaptation, very sparse data (<100 examples) |
| 8 | ~4M | Default starting point for most tasks |
| 16 | ~8M | Complex instruction following, multi-task |
| 32 | ~16M | High-difficulty domain adaptation (legal, medical) |
| 64 | ~32M | Near full-fine-tune quality, large datasets (50K+) |
| 128 | ~64M | Overkill for most tasks; consider full fine-tuning |
| 256 | ~128M | Rarely needed; full fine-tuning often better |
Empirical rule: Start with r=8 for data <10K examples, r=16 for 10Kβ50K, r=32+ for 50K+. In practice, r=16 is the "sweet spot" for most enterprise use cases.
Alpha (Ξ±)
LoRA scaling factor determines how much the adaptation affects the forward pass:
h = Wx + (Ξ±/r) * BAx
- Ξ± = r: Default (1:1 scaling). No amplification.
- Ξ± = 2r: Doubles the effective learning rate of the adapter.
- Ξ± = 16, r=8: Scale factor of 2.
- Ξ± = 32, r=8: Scale factor of 4.
Best practices:
- Keep Ξ± proportional to r (Ξ± = r, 2r, or 4r)
- Higher Ξ±/r ratio = stronger regularization effect (smaller effective updates)
- Lower Ξ±/r ratio = faster convergence but potential instability
- Common enterprise setting: Ξ± = 16, r = 16
Dropout
LoRA dropout applies dropout to the adapter output:
h = Wx + dropout(BAx, p)
| Dropout Rate | Effect |
|---|---|
| 0.0 | Fast convergence, overfitting risk on small data |
| 0.05 | Mild regularization, good for 10K+ examples |
| 0.1 | Default for most cases |
| 0.2 | Heavy regularization, small datasets (<1K) |
| 0.3+ | Very aggressive; rarely needed |
Target Modules
Which weight matrices to adapt determines both cost and quality.
Common choices for transformer architectures:
# Llama 2/3 architecture
target_modules = [
"q_proj", # Query projection β most impactful
"v_proj", # Value projection β important for output
"k_proj", # Key projection β less critical than Q/V
"o_proj", # Output projection β subtle improvements
"gate_proj", # MLP gate β significant for reasoning
"up_proj", # MLP up projection
"down_proj", # MLP down projection
]
| Module Set | Parameters Added (7B, r=16) | Quality Impact |
|---|---|---|
| q_proj + v_proj | ~8M | Good baseline |
| q_proj + v_proj + o_proj | ~12M | Better |
| All attention (q,k,v,o) | ~16M | Standard choice |
| All attention + MLP (gate,up,down) | ~48M | Full adapter (near full-FT quality) |
| Every linear layer | ~80M+ | Aggressive; marginal gains |
Enterprise recommendation: Start with q_proj, v_proj and add o_proj, k_proj if quality is insufficient. Add MLP modules (gate_proj, up_proj, down_proj) only for complex reasoning tasks where attention-only adaptation plateaus.
3.2 QLoRA (Quantized LoRA)
QLoRA enables fine-tuning of large models (65B, 70B, 140B+) on a single consumer GPU by quantizing the base model to 4-bit while keeping LoRA adapters in full precision.
4-bit NormalFloat (NF4)
NF4 is a quantization data type designed for normally distributed weights. Unlike uniform quantization (INT4), NF4 allocates more quantization levels near zero where weights concentrate:
NF4 codebook: [-1.0, -0.696, -0.525, -0.393, -0.278, -0.174, -0.078, 0.0,
0.078, 0.174, 0.278, 0.393, 0.525, 0.696, 1.0]
Double Quantization
QLoRA introduces double quantization: the quantization constants (scale factors) themselves are quantized to 8-bit, saving another ~0.5 bits per parameter:
- First quantization: FP16 weights β NF4 (4-bit) β scale factors in FP32
- Second quantization: FP32 scale factors β INT8 (8-bit) β second-level scale factors in FP32
Total savings: ~4.5 bits per parameter vs 16-bit (FP16) baseline.
Memory Savings Breakdown
| Model | FP16 | LoRA (r=16) | QLoRA (4-bit NF4) |
|---|---|---|---|
| 7B | 14 GB | ~16 GB | ~6.5 GB |
| 13B | 26 GB | ~28 GB | ~10.5 GB |
| 34B | 68 GB | ~70 GB | ~24 GB |
| 70B | 140 GB | ~142 GB | ~48 GB |
| 180B | 360 GB | ~362 GB | ~120 GB |
QLoRA typically achieves within 0.5β2% of LoRA quality on most benchmarks while using ~4x less memory.
Nested Quantization
For extreme memory savings, the base model can be quantized to 3-bit or 2-bit with modest quality degradation:
- NF3 (3-bit): ~3.5 bits/param after double quantization
- NF2 (2-bit): ~2.5 bits/param after double quantization
These are experimental and recommended only for large models (70B+) where 4-bit still exceeds available VRAM.
When to Use QLoRA vs LoRA
| Scenario | Recommended |
|---|---|
| Consumer GPU (24 GB or less) | QLoRA |
| Enterprise GPU (40 GB A100, 80 GB H100) | LoRA (for quality) or QLoRA (for throughput) |
| Multiple models on one GPU | QLoRA |
| Maximum quality required | LoRA |
| 70B+ models on limited hardware | QLoRA |
| Production training pipeline | LoRA (less complexity) |
3.3 DoRA (Weight-Decomposed Low-Rank Adaptation)
DoRA decomposes pretrained weights into magnitude and direction components and applies LoRA only to the directional component:
W = m * V / ||V|| where V = W + ΞW (LoRA update)
Key Advantages:
- Directional updates: LoRA changes both magnitude and direction; DoRA isolates directional learning
- Faster convergence: 1.5β2x faster than LoRA on some benchmarks
- Better quality: Consistently matches or exceeds LoRA across NLU and NLG tasks
- Parameter count: Same as LoRA (magnitude vector adds negligible parameters, one per row)
Enterprise Recommendation:
DoRA is the default choice over LoRA when available. The additional training stability and faster convergence reduce GPU hours directly. Supported in libraries like PEFT (via use_dora=True), Axolotl (via lora_dora: true).
Implementation in PEFT:
from peft import LoraConfig, get_peft_model
lora_config = LoraConfig(
r=16,
lora_alpha=32,
target_modules=["q_proj", "v_proj"],
lora_dropout=0.1,
use_dora=True, # Enable DoRA
)
3.4 AdaLoRA (Adaptive Budget Allocation)
AdaLoRA parametrizes weight updates through SVD and dynamically allocates the rank budget across layers based on importance:
W = Wβ + PΞQ, where Ξ is a diagonal matrix of singular values
Key Mechanisms:
- Regularization: L0 penalty on singular values encourages sparsity
- Dynamic budget: Layers with higher importance get higher effective rank
- SVD-based: Maintains orthogonality constraints on P and Q
Empirical Results:
- Often outperforms LoRA with the same total rank budget
- Automatically assigns more rank to early layers and attention layers
- Less sensitive to rank choice (use r=16 or r=32 and let it prune)
Trade-offs:
- +20-30% training overhead over LoRA
- More hyperparameters (L0 regularization coefficient, target rank)
- Not supported in all frameworks
Enterprise Recommendation:
Best for tasks where the optimal rank is unknown and you have budget to run AdaLoRA experiments. For well-understood tasks, standard LoRA/DoRA is simpler.
3.5 IA3 (Infused Adapter by Inhibiting and Amplifying Activations)
IA3 learns scaling vectors that modulate key activations:
h = Wx β l, where l is a learned scaling vector
Compared to LoRA:
- Fewer parameters: ~10,000x fewer than full fine-tuning (vs 1000x for LoRA)
- Faster: No matrix multiplication overhead for adapters
- Weaker on complex tasks: Struggles with multi-task reasoning
When to Use IA3:
- Extremely limited storage (thousands of adapters)
- Simple domain adaptation (tone, format, style)
- Feature extraction where base model capabilities are sufficient
- Multi-adapter serving with minimal overhead
4. Data Preparation Pipeline
Data quality is the single largest determinant of fine-tuning success. A well-prepared dataset consistently outperforms a 10x larger noisy dataset.
4.1 Data Collection
Enterprise data sources span diverse formats and quality levels.
| Source Type | Examples | Volume | Quality | PII Risk |
|---|---|---|---|---|
| Conversation logs | Chat transcripts, support tickets | 100Kβ10M | Medium | High |
| Internal documents | Wikis, runbooks, SOPs | 1Kβ100K | Mixed | LowβMedium |
| Code repositories | Git history, Jupyter notebooks | 10Kβ1M | High | Low |
| Emails | Correspondence, newsletters | 10Kβ100K | Medium | Very High |
| Database records | CRM, ERP, logs | 1M+ | Variable | High |
| Public datasets | Benchmarks, open-source | 10Kβ10M | High | Low |
| Feedback loops | User ratings, corrections | 1Kβ100K | High | Medium |
Collection Best Practices:
- Capture full conversation context (not just individual turns)
- Preserve metadata (timestamps, user IDs β for deduplication only)
- Log model-generated completions separately (avoid contamination)
- Implement data retention policies aligned with GDPR/CCPA
- Version control raw data with data manifests
4.2 Data Cleaning
Deduplication
Enterprise datasets often contain massive redundancy due to repeated logging, template-driven content, and feedback loops.
Levels of Deduplication:
- Exact deduplication: MD5/SHA256 hash comparison β removes identical strings
- Near-dedup (MinHash): Jaccard similarity >0.8 β removes near-identical documents
- Template deduplication: Regex-based removal of auto-generated templates
- Semantic deduplication: Embedding similarity >0.95 β removes paraphrased content
MinHash Implementation:
from datasketch import MinHash, MinHashLSH
def compute_minhash(text, num_perm=128):
m = MinHash(num_perm=num_perm)
for ngram in ngrams(text.split(), 5):
m.update(' '.join(ngram).encode('utf-8'))
return m
# LSH for efficient near-dedup
lsh = MinHashLSH(threshold=0.8, num_perm=128)
for idx, text in enumerate(documents):
m = compute_minhash(text)
lsh.insert(f"doc_{idx}", m)
Deduplication Statistics:
- Typical enterprise dataset: 20β40% redundancy
- Log data: often 60β80% redundant
- After MinHash (0.8 threshold): typically removes 15β30%
Formatting and Encoding
- Encoding detection: Use
charset-normalizerorcchardetto detect and standardize to UTF-8 - Whitespace normalization: Collapse multiple spaces, normalize newlines (\r\n β \n)
- Unicode normalization: NFC normalization (composed) for consistent character representation
- Markdown/HTML stripping: Remove formatting tags, keep text content
- JSON/XML extraction: Parse structured data and extract text fields
import chardet
import unicodedata
def normalize_text(text):
# Detect and fix encoding
if isinstance(text, bytes):
detected = chardet.detect(text)
text = text.decode(detected['encoding'] or 'utf-8', errors='replace')
# Unicode normalization
text = unicodedata.normalize('NFC', text)
# Whitespace normalization
text = ' '.join(text.split())
return text
PII Redaction
Personally Identifiable Information (PII) must be removed before any training, especially for regulated industries.
Redaction Pipeline (Presidio + spaCy):
from presidio_analyzer import AnalyzerEngine
from presidio_anonymizer import AnonymizerEngine
analyzer = AnalyzerEngine()
anonymizer = AnonymizerEngine()
def redact_pii(text):
results = analyzer.analyze(text=text, language='en')
anonymized = anonymizer.anonymize(
text=text,
analyzer_results=results
)
return anonymized.text
PII Types to Detect and Redact:
| PII Type | Regex/NER Approach | Enterprise Requirement |
|---|---|---|
| Email addresses | Regex: [\w.+-]+@[\w-]+\.[\w.-]+ |
Always |
| Phone numbers | Regex: \+?\d{1,4}[\s-]?\d{3,4}[\s-]?\d{4,10} |
Always |
| SSN / Tax IDs | Regex: \d{3}-\d{2}-\d{4} |
Always (US) |
| Credit card numbers | Luhn algorithm + regex | Always (PCI) |
| IP addresses | Regex: \d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3} |
Always |
| Names | spaCy NER (PERSON) | Usually |
| Locations | spaCy NER (GPE, LOC) | Usually |
| Dates (medical) | Regex + context | HIPAA |
| Medical record numbers | Pattern-based + context | HIPAA |
| Bank account numbers | Regex (varies by country) | Usually |
Best Practices:
- Use both regex (high recall) and NER (high precision) in sequence
- Validate redaction on a holdout set before training
- For LLM training, redact with placeholder tags like <PERSON> or [REDACTED]
- Never train on non-redacted data, even in a sandbox
- Implement differential privacy as an additional safeguard
Language Filtering
Multilingual enterprises need to filter or classify training data by language.
import fasttext
# Language identification model (e.g., lid.176.bin from fasttext)
lang_model = fasttext.load_model('lid.176.bin')
def detect_language(text, threshold=0.5):
predictions = lang_model.predict(text.replace('\n', ' '), k=1)
lang = predictions[0][0].replace('__label__', '')
score = predictions[1][0]
return lang, score
# Filter for English-only or specific language mix
filtered = [
text for text in corpus
if detect_language(text)[0] == 'en' and detect_language(text)[1] > 0.7
]
Language Distribution Heuristics:
- Use language distribution of target users
- Avoid mixing languages in single training example (unless target task is translation)
- For multilingual models, balance languages by sampling rate
4.3 Quality Filtering
Heuristic Filtering
Simple, fast quality signals that remove obvious garbage:
| Heuristic | Threshold | Rationale |
|---|---|---|
| Minimum length | >50 characters | Too-short text lacks context |
| Maximum length | <8192 tokens | Context window limits |
| Word count | >20 words | Sentence-level content only |
| Average word length | 3β10 chars | 2-char avg = noise, 15+ char avg = code |
| Punctuation ratio | 5β40% | Low = no structure, high = table/data |
| Repeated characters | <5% of text | "aaaaa" or "!!!!!" = noise |
| Repeated n-grams (4-gram) | <40% | Template text |
| HTML tag ratio | <30% | Scraped markup |
| Non-printable chars | <1% | Binary content |
| Stop word ratio (English) | >5% | Non-English or gibberish |
import re
from collections import Counter
def heuristic_filter(text):
if len(text) < 50:
return False
if len(text) > 32000:
return False
words = text.split()
if len(words) < 20:
return False
avg_word_len = sum(len(w) for w in words) / len(words)
if avg_word_len < 3 or avg_word_len > 15:
return False
# Count repeated chars
repeated = sum(1 for c in text if text.count(c) > len(text) * 0.05)
if repeated > 0:
return False
# Check for non-printable
non_printable = sum(1 for c in text if not c.isprintable())
if non_printable / len(text) > 0.01:
5. Synthetic Data Generation
Synthetic data is increasingly critical for enterprise fine-tuning, especially when real data is scarce, sensitive, or expensive to annotate.
5.1 Self-Instruct Pipeline
Original Process (Wang et al., 2022):
- Seed tasks: Write 175 seed instructions (hand-crafted)
- Instruction generation: For each seed, prompt an LLM to generate more instructions
- Classification: Classify whether the instruction is a classification or generation task
- Output generation: For each instruction, prompt the LLM to produce an output
- Filtering: Remove low-quality or duplicate entries
- Iterate: The generated dataset becomes new seeds
Enterprise Implementation:
class SelfInstruct:
def __init__(self, base_model, seed_tasks):
self.model = base_model
self.seed_tasks = seed_tasks
self.instructions = []
def generate_instructions(self, num_to_generate=1000):
for seed in self.seed_tasks:
prompt = f"""
Generate a new instruction for an AI assistant.
The instruction should be similar in difficulty and style to:
Instruction: {seed}
New instruction:
"""
response = self.model.generate(prompt)
self.instructions.append(response.strip())
def generate_responses(self):
for instruction in self.instructions:
prompt = f"""
Instruction: {instruction}
Provide a helpful, accurate response to the above instruction.
Response:
"""
response = self.model.generate(prompt)
dataset.append({"instruction": instruction, "output": response})
Quality Controls:
- Filter instructions with keyword overlap >80% with existing set
- Remove instructions that are too short (<5 words) or too long (>50 words)
- Balance across task categories
- Verify executability (can the model actually complete the task?)
5.2 Evol-Instruct
Evol-Instruct generates training data by progressively making instructions more complex (in-depth + in-breadth evolution).
In-Depth Evolution
Makes a single instruction harder by adding constraints, deepening reasoning, or increasing difficulty:
| Evolution Operation | Example Transformation |
|---|---|
| Add constraints | "Write a poem" β "Write a haiku about AI using exactly 17 syllables" |
| Deepen reasoning | "Explain gravity" β "Explain how gravitational waves differ from Newtonian predictions of gravity" |
| Increase difficulty | "Translate to French" β "Translate this legal contract to French, maintaining all technical terms and preserving the legal validity" |
| Add specific format | "Summarize" β "Summarize in exactly 3 bullet points with a 50-word limit per bullet" |
| Add comparison | "Describe Python" β "Compare Python and Rust for systems programming, considering memory safety, performance, and developer productivity" |
In-Breadth Evolution
Generates new, diverse instructions from existing ones without increasing difficulty:
| Evolution Operation | Example Transformation |
|---|---|
| Concretization | "Write a function" β "Write a function to calculate Fibonacci numbers" |
| Increase scope | "Debug this code" β "Debug this code and write unit tests for edge cases" |
| Switch domain | "Write marketing copy" β "Write marketing copy for a B2B SaaS product in the healthcare space" |
| Add background | "Explain transformers" β "Explain the transformer architecture to a high school student with no ML background" |
| Scenario-based | "Create a plan" β "Create a project plan for migrating a monolith to microservices" |
Quality Control in Evol-Instruct:
- LLM Judge scores each evolved instruction (1β5)
- Remove instructions scoring <3
- Remove instructions identical to (or too similar to) originals
- Verify instruction is still solvable after evolution
- Balance difficulty distribution
5.3 Wizard Methods
The "Wizard" family (WizardLM, WizardCoder, WizardMath) builds on Evol-Instruct with domain-specific extensions:
- WizardLM: Evol-Instruct on general instructions
- WizardCoder: Evol-Instruct on code generation instructions, with test-based verification
- WizardMath: Evol-Instruct on math problems, with step-by-step verification
Key Innovation β Complexity Scoring:
Complexity Score = f(num_constraints, reasoning_depth, domain_specificity, format_restrictiveness)
Each evolved instruction's complexity is tracked, and the training set is balanced across complexity levels.
5.4 Knowledge Distillation from Large Models
Distillation leverages the fact that larger models (teacher) often produce higher quality outputs than smaller models (student).
Distillation Pipeline:
- Teacher selection: GPT-4, Claude 3.5 Sonnet, Gemini Ultra, or the largest available model
- Prompt construction: Craft prompts that elicit detailed, correct responses
- Batch generation: Generate outputs at scale (cost-efficient via API)
- Quality filtering: Remove obviously incorrect or low-quality outputs
- Student training: Fine-tune smaller model (e.g., Llama 3.1 8B) on teacher outputs
Distillation Strategies:
| Strategy | Description | Quality vs Cost |
|---|---|---|
| Direct distillation | Train on teacher outputs directly | Medium quality, low cost |
| Contrastive distillation | Train on (good output, bad output) pairs | Higher quality, higher cost |
| Multi-teacher ensemble | Use majority vote across teachers | Highest quality, highest cost |
| Self-distillation | Student generates, teacher scores | Iterative improvement |
Enterprise Implementation Considerations:
- Cost: GPT-4 distillation for 100K examples β $5,000β$15,000 in API costs
- Rate limits: Need multiple API keys or batch API for large volumes
- Bias propagation: Teacher biases transfer to student β audit carefully
- Licensing: Check teacher's terms of service for distillation rights
5.5 Multi-turn Conversation Synthesis
Synthetic multi-turn conversations are essential for training chat-oriented models.
Conversation Tree Generation:
User: I need help with my account.
Assistant: I'd be happy to help! What seems to be the issue?
User: I can't log in. It says "invalid password."
Assistant: Let's try resetting your password. Can you access your email?
User: Yes, I can.
Assistant: Great. I've sent a password reset link to your registered email. Please check and follow the instructions.
User: It worked! I'm logged in now. Thanks.
Assistant: You're welcome! Is there anything else I can help you with?
Generation Approaches:
- Role-play: Two model instances β one as user (with persona), one as assistant
- Template-based: Fill in slots in conversation templates (good for customer support)
- Instruct-then-generate: Start with a high-level scenario and let model generate full conversation
- Branching: Generate multiple possible continuations at each turn, select best path
Quality Control:
- Require β₯3 turns for "conversation" qualification
- Check for assistant response quality (helpful, accurate, safe)
- Remove conversations where user repeats the same question
- Ensure conversation reaches resolution or proper escalation
5.6 Quality Control
Synthetic data quality control is an ongoing process, not a one-time filter.
LLM Judge
Use a LLM (often the same model or a larger one) to evaluate synthetic data quality:
def llm_judge(instruction, response, criteria):
prompt = f"""
Evaluate the following response to an instruction on these criteria:
Instruction: {instruction}
Response: {response}
Criteria:
1. Helpfulness (1-5): Does the response address the instruction?
2. Accuracy (1-5): Is the information correct?
3. Completeness (1-5): Is the response thorough?
4. Safety (1-5): Is the response safe and ethical?
Format your response as:
Helpfulness: X
Accuracy: X
Completeness: X
Safety: X
"""
evaluation = judge_model.generate(prompt)
return parse_evaluation(evaluation)
Human Verification Threshold
For enterprise production, establish a human verification threshold:
| Quality Score | Action |
|---|---|
| 4.5β5.0 | Auto-accept |
| 3.5β4.5 | Auto-accept with sampling (1 in 10 human check) |
| 2.5β3.5 | Human review required |
| 1.0β2.5 | Auto-reject |
Quality Metrics Dashboard
Track these metrics over time across data runs:
- Acceptance rate: % passing quality filters
- Distribution coverage: Are all domains/topics represented?
- Difficulty distribution: Are there enough hard examples?
- Token length distribution: Consistent with target task?
- Model agreement: Do multiple LLM judges agree?
- Human agreement: When humans review, do they concur with LLM judge?
6. Human Annotation
Human annotation remains essential for high-quality supervised fine-tuning, especially for tasks requiring domain expertise or nuanced judgment.
6.1 Annotation Platform Comparison
LabelStudio
| Feature | Details |
|---|---|
| License | Apache 2.0 (open source) |
| Self-hosted | Yes (Python/Django) |
| Annotation types | Text classification, NER, sequence labeling, transcription, QA |
| LLM features | ML-assisted labeling, LLM pre-annotations |
| Collaboration | Teams, review workflows, project roles |
| Integration | API SDK, Storage (S3, GCS, Azure), Cloud (pre-built images) |
| Cost | Free (self-hosted). LabelStudio Enterprise has paid tiers. |
| Best for | Teams that want full control, on-prem deployment |
Setup:
pip install label-studio
label-studio start --init # Initialize with admin user
Prodigy
| Feature | Details |
|---|---|
| License | Proprietary (commercial) |
| Self-hosted | Yes (Python library) |
| Annotation types | NER, text classification, image annotation, audio, video |
| LLM features | Built-in recipe system, LLM integration (via recipe), active learning |
| Collaboration | Multi-user with Prodigy Teams |
| Integration | spaCy native integration, custom recipes |
| Cost | ~$1,000β5,000/year depending on license tier |
| Best for | Teams already in spaCy ecosystem, rapid prototyping |
Example recipe:
import prodigy
from prodigy.components.loaders import JSONL
@prodigy.recipe('text-classification')
def text_classification(dataset, source):
stream = JSONL(source)
return {
'dataset': dataset,
'stream': stream,
'view_id': 'classification',
'config': {
'labels': ['relevant', 'irrelevant']
}
}
Scale AI (Scale Rapid)
| Feature | Details |
|---|---|
| License | Proprietary (SaaS) |
| Self-hosted | No (cloud-only) |
| Annotation types | All types (text, image, video, 3D, audio, document) |
| LLM features | Scale Spellbook (RLHF, prompt engineering, evaluation) |
| Collaboration | Managed workforce, expert pool, automated QA |
| Integration | API, webhooks, data connectors |
| Cost | Pay-per-task (variable); enterprise contracts |
| Best for | Large-scale annotation with managed workforce |
Snorkel AI (now Weights & Biases)
| Feature | Details |
|---|---|
| License | Proprietary |
| Self-hosted | Enterprise option |
| Annotation types | Data development, labeling functions, slicing |
| LLM features | Foundation model labeling, programmatic labeling |
| Collaboration | Team-based, labeling function sharing |
| Integration | Weights & Biases ecosystem |
| Cost | Enterprise pricing |
| Best for | Weak supervision + human validation at scale |
6.2 Inter-Annotator Agreement
Measuring agreement between annotators is critical for dataset quality.
Cohen's Kappa (ΞΊ)
For two annotators, Cohen's Kappa measures agreement beyond chance:
ΞΊ = (pβ - pβ) / (1 - pβ)
Where:
- pβ = observed agreement
- pβ = expected agreement by chance
| ΞΊ Value | Agreement |
|---|---|
| < 0 | Poor |
| 0.0β0.20 | Slight |
| 0.21β0.40 | Fair |
| 0.41β0.60 | Moderate |
| 0.61β0.80 | Substantial |
| 0.81β1.00 | Almost perfect |
Implementation:
from sklearn.metrics import cohen_kappa_score
# Annotations from two annotators
annotator_1 = [0, 1, 1, 0, 2, 1, 0, 1]
annotator_2 = [0, 1, 0, 0, 2, 1, 1, 1]
kappa = cohen_kappa_score(annotator_1, annotator_2)
print(f"Cohen's Kappa: {kappa:.3f}")
Limitations:
- Kappa is affected by prevalence (if most items are the same class, ΞΊ is low even with high agreement)
- Only handles two annotators; use Fleiss' Kappa for >2
Krippendorff's Alpha (Ξ±)
A more robust agreement metric that handles >2 annotators, missing data, and any scale type (nominal, ordinal, interval, ratio):
Ξ± = 1 - Dβ / Dβ
Where:
- Dβ = observed disagreement
- Dβ = expected disagreement by chance
Implementation:
import krippendorff
# Annotations: items as rows, annotators as columns
# NaN for missing annotations
data = np.array([
[1, 1, 1, np.nan], # Item 1
[2, 2, 2, 2], # Item 2
[1, 2, 1, 2], # Item 3
[2, 2, 1, 2], # Item 4
])
alpha = krippendorff.alpha(data, level_of_measurement='nominal')
print(f"Krippendorff's Alpha: {alpha:.3f}")
Enterprise Thresholds:
- Ξ± > 0.8: Good reliability; dataset is consistent
- 0.67 β€ Ξ± β€ 0.80: Tentative reliability; investigate disagreements
- Ξ± < 0.67: Poor reliability; re-train annotators or simplify task
6.3 Quality Assurance Workflows
Three-stage QA Pipeline:
- Training stage: Annotators train on a gold-standard set of 50β100 examples. Must achieve ΞΊ > 0.7 on a qualification test.
- Production stage: Annotations flow through automated checks:
- Validation against schema (required fields, data types)
- Real-time agreement check (if multiple annotators)
- Confidence scoring (model predicts label; flag low-confidence annotations) - Review stage: Random sample (5β20%) reviewed by senior annotator or domain expert
Blind Spots to Monitor:
- Drift: Annotator agreement decreasing over time (need re-calibration)
- Edge cases: Systematic disagreement on specific data types
- Annotation fatigue: Degradation toward end of shift (flag batches completed in last hour)
- Cascading errors: Errors in early steps propagate
6.4 Active Learning
Active learning reduces annotation costs by selecting the most informative examples for human labeling.
Uncertainty Sampling
Select examples where the model is most uncertain:
def uncertainty_sampling(model, unlabeled_pool, n_samples=100):
predictions = []
for example in unlabeled_pool:
probs = model.predict_proba(example)
# Margin: difference between top 2 probabilities
sorted_probs = np.sort(probs)[::-1]
margin = sorted_probs[0] - sorted_probs[1]
predictions.append(margin)
# Select examples with smallest margin (highest uncertainty)
indices = np.argsort(predictions)[:n_samples]
return [unlabeled_pool[i] for i in indices]
Variants:
- Least confidence: 1 - max(probability)
- Margin sampling: Difference between top 2 probabilities
- Entropy: -Ξ£ p(x) log p(x)
- BALD: Bayesian Active Learning by Disagreement (for neural networks)
Diversity Sampling
Ensure the selected batch covers diverse data regions:
def diversity_sampling(embeddings, n_samples=100):
# k-means centroids as diverse representatives
from sklearn.cluster import KMeans
kmeans = KMeans(n_clusters=n_samples, random_state=42)
kmeans.fit(embeddings)
# Find closest example to each centroid
indices = []
for centroid in kmeans.cluster_centers_:
distances = np.linalg.norm(embeddings - centroid, axis=1)
closest = np.argmin(distances)
indices.append(closest)
return indices
Expected Model Change
Select examples that would most change the model parameters:
Expected Gradient Length (EGL):
Select x that maximizes ||ββ(x, Ε·; ΞΈ)||
This is computationally expensive but highly effective for deep neural networks.
Enterprise Active Learning Loop:
1. Train initial model on small annotated seed set
2. Score unlabeled pool with uncertainty/diversity metrics
3. Select top-K examples for annotation
4. Annotate (...7. Training Infrastructure
7.1 Cloud ML Platforms
AWS SageMaker
Key Features:
- Managed training jobs with automatic instance provisioning
- SageMaker Studio for interactive development
- Built-in Deep Learning Containers (DLCs)
- Distributed Training Libraries (SMDDP for data parallelism)
- Managed Spot Training (up to 90% cost reduction)
- Model registry and deployment pipeline
Fine-tuning on SageMaker:
import sagemaker
from sagemaker.huggingface import HuggingFace
sagemaker_session = sagemaker.Session()
hf_estimator = HuggingFace(
entry_point='train.py',
source_dir='./scripts',
instance_type='ml.g5.12xlarge',
instance_count=2,
role=role,
transformers_version='4.28.0',
pytorch_version='2.0.0',
hyperparameters={
'model_id': 'meta-llama/Llama-2-7b-hf',
'dataset_path': '/opt/ml/input/data/training',
'epochs': 3,
'per_device_train_batch_size': 4,
'gradient_accumulation_steps': 4,
'learning_rate': 2e-4,
'lora_r': 16,
'lora_alpha': 32,
},
use_spot_instances=True,
max_wait=86400,
checkpoint_s3_uri='s3://my-bucket/checkpoints/',
)
hf_estimator.fit({'training': 's3://my-bucket/training-data/'})
Pricing Considerations:
- ml.g5.12xlarge (4Γ A10G, 192 GB VRAM): ~$6.12/hr on-demand, ~$1.84/hr spot
- ml.p4d.24xlarge (8Γ A100 40GB, 320 GB VRAM): ~$37.69/hr on-demand
- ml.p5.48xlarge (8Γ H100 80GB, 640 GB VRAM): ~$132.10/hr on-demand
GCP Vertex AI
Key Features:
- Custom jobs with any Docker image
- Hyperparameter tuning (Vizier)
- Custom model serving with prediction endpoints
- Deep Learning VM images with pre-installed drivers
- TPU v4/v5e/v5p support for LLM training
Fine-tuning on Vertex AI:
gcloud ai custom-jobs create \
--region=us-central1 \
--display-name=llm-fine-tune \
--worker-pool-spec=machine-type=a2-highgpu-8g,replica-count=1,container-image-uri=gcr.io/my-project/llm-training:latest \
--args="--model=meta-llama/Llama-2-13b-hf,--dataset=gs://my-bucket/train.jsonl"
Azure ML
Key Features:
- Azure OpenAI Service for managed LLM fine-tuning
- Azure ML Compute (NC/HB/NV series)
- Integration with Responsible AI dashboard
- Managed online endpoints for deployment
Note: Azure has the tightest integration with Microsoft's own models (Phi, Orca) and OpenAI models.
7.2 GPU Cloud Providers
Lambda GPU Cloud
| GPU Type | Specs | Price/hr |
|---|---|---|
| A100 (80 GB) | 1Γ A100 SXM 80GB | $1.10 |
| A100 (80 GB) 8Γ | 8Γ A100 SXM 80GB | $7.60 |
| H100 (80 GB) 8Γ | 8Γ H100 SXM 80GB | $13.60 |
| H200 (141 GB) 8Γ | 8Γ H200 SXM 141GB | $18.20 |
Pros: Reliable, predictable pricing, good support
Cons: Limited GPU variety, mostly NVIDIA only
RunPod
| GPU Type | Specs | Price/hr |
|---|---|---|
| RTX 4090 (24 GB) | 1Γ RTX 4090 | $0.34 |
| A100 (80 GB) | 1Γ A100 SXM | $0.99 |
| A100 8Γ (80 GB) | 8Γ A100 SXM | $7.29 |
| H100 8Γ (80 GB) | 8Γ H100 SXM | $11.99 |
| H200 8Γ (141 GB) | 8Γ H200 SXM | $15.99 |
Pros: Cheapest spot pricing, wide GPU selection, serverless GPU option
Cons: Network performance varies, no guaranteed availability
Vast.ai
Pros: Cheapest prices (p2p marketplace for GPU), enormous variety
Cons: Inconsistent hardware quality, no support, security concerns
CoreWeave
Pros: Premium networking (InfiniBand), purpose-built for LLM training, great for multi-node
Cons: Minimum commitments, less accessible for small projects
JarvisLabs
Pros: Good for single-GPU fine-tuning, RTX 4090s cheap
Cons: Limited multi-node options
7.3 On-Premises Infrastructure
NVIDIA DGX Systems
| System | GPUs | VRAM | Price |
|---|---|---|---|
| DGX H100 | 8Γ H100 80GB | 640 GB | ~$300K |
| DGX B200 | 8Γ B200 180GB | 1.4 TB | ~$500K |
| DGX A100 | 8Γ A100 80GB | 640 GB | ~$200K (used) |
Dell PowerEdge with NVIDIA GPUs
- PowerEdge R760xa (4Γ H100/NVIDIA L40S)
- PowerEdge XE9680 (8Γ H100)
- PowerEdge R750xa (4Γ A100)
Supermicro
- AS-4125GS-TNRT (8Γ H100/H200)
- SYS-421GU-TNXR (4Γ H100)
- SYS-420GP-TNR (8Γ A100)
On-premise Considerations:
- Power: H100 SXM runs at 700W per GPU; 8Γ H100 system = ~7β10 kW
- Cooling: Liquid cooling recommended for H100 clusters
- Networking: InfiniBand NDR400 (400 Gbps) for multi-node training
- Rack space: DGX H100 is 6U; expect 10+ racks for a full cluster
- Maintenance: Driver updates, hardware failures, cooling system upkeep
7.4 Spot / Preemptible Instances
Spot instances can reduce GPU costs by 60β90% but introduce interruption risk.
Mitigation Strategies:
| Strategy | Description | Overhead |
|---|---|---|
| Checkpointing | Save checkpoints every N steps (frequent) | Storage cost, ~30s per save |
| Spot-to-ondemand fallback | Switch to on-demand when spot is reclaimed | Higher cost on last attempt |
| Multi-region | Spread across regions for more spot capacity | Data transfer costs |
| Model sharding | If one GPU is reclaimed, stop all (FSDP/ZeRO) | Wasted compute on remaining |
| Short training jobs | Keep training time < 1 hour per run | Overhead of frequent restarts |
Spot pricing (example, AWS us-east-1):
| Instance | On-Demand/hr | Spot/hr (avg) |
|---|---|---|
| g5.12xlarge (4Γ A10G) | $6.12 | $1.84 |
| p4d.24xlarge (8Γ A100) | $37.69 | $11.30 |
| p5.48xlarge (8Γ H100) | $132.10 | $39.63 |
7.5 Multi-GPU Training Strategies
DeepSpeed (ZeRO)
ZeRO (Zero Redundancy Optimizer) partitions optimizer states, gradients, and parameters across GPUs.
ZeRO Stages:
| Stage | Partitions | Memory Savings | Comm Overhead | Best For |
|---|---|---|---|---|
| ZeRO-1 | Optimizer states only | ~4x | Low | Small models, small GPU count |
| ZeRO-2 | Optimizer states + gradients | ~8x | Medium | Most 7Bβ13B fine-tuning |
| ZeRO-3 | Optimizer states + gradients + params | ~16x~Nd | High | Large models (30B+), memory-bound |
| ZeRO-3 + offload | CPU/NVMe offloading of params | Near-infinite | Very High | Models too large for GPU memory |
ZeRO Configuration:
{
"zero_optimization": {
"stage": 2,
"allgather_partitions": true,
"allgather_bucket_size": 500000000,
"reduce_scatter": true,
"reduce_bucket_size": 500000000,
"overlap_comm": true,
"contiguous_gradients": true
}
}
ZeRO-3 with CPU Offload:
{
"zero_optimization": {
"stage": 3,
"offload_optimizer": {
"device": "cpu",
"pin_memory": true
},
"offload_param": {
"device": "cpu",
"pin_memory": true
},
"overlap_comm": true,
"contiguous_gradients": true,
"sub_group_size": 100000000000,
"stage3_max_live_parameters": 1000000000,
"stage3_max_reuse_distance": 1000000000,
"stage3_gather_16bit_weights_on_model_save": true
}
}
FSDP (Fully Sharded Data Parallelism)
FSDP is PyTorch's native sharded training, conceptually similar to ZeRO-3.
from torch.distributed.fsdp import (
FullyShardedDataParallel as FSDP,
ShardingStrategy,
MixedPrecision,
)
from torch.distributed.fsdp.wrap import transformer_auto_wrap_policy
fsdp_config = {
"sharding_strategy": ShardingStrategy.FULL_SHARD, # FULL_SHARD, HYBRID_SHARD, NO_SHARD
"mixed_precision": MixedPrecision(
param_dtype=torch.bfloat16,
reduce_dtype=torch.float32,
buffer_dtype=torch.bfloat16,
),
"auto_wrap_policy": transformer_auto_wrap_policy,
"backward_prefetch": BackwardPrefetch.BACKWARD_PRE,
"limit_all_gathers": True,
"use_orig_params": True,
}
FSDP vs DeepSpeed ZeRO:
| Feature | FSDP | DeepSpeed ZeRO |
|---|---|---|
| Framework | Native PyTorch | Microsoft DeepSpeed |
| ZeRO stage | FULL_SHARD = ZeRO-3 | ZeRO 1/2/3 |
| HYBRID_SHARD | Yes | Yes (ZeRO-3++) |
| CPU offload | Yes | Yes (optimizer + params) |
| NVMe offload | No | Yes |
| Overlap comm | Limited | Advanced |
| Ease of use | Easier (less config) | More config, more control |
| Performance | Good | Often better tuned |
| Community | Growing | Mature |
Tensor Parallelism
Splits individual layers across GPUs (within a node for fast interconnects):
Large Matrix Multiplication:
Wx = [Wβ; Wβ; ...; Wβ] x β...8. Tools
8.1 Axolotl
Axolotl is the most popular configuration-driven LLM fine-tuning framework. It supports almost all open-source models and PEFT methods.
YAML Configuration:
base_model: meta-llama/Llama-2-7b-hf
model_type: LlamaForCausalLM
tokenizer_type: LlamaTokenizer
load_in_8bit: false
load_in_4bit: true
strict: false
datasets:
- path: ./data/train.jsonl
ds_type: json
type: sharegpt
conversation: llama-2
dataset_prepared_path: ./processed_data
val_set_size: 0.1
output_dir: ./lora-out
sequence_len: 2048
sample_packing: false
lora_r: 16
lora_alpha: 32
lora_dropout: 0.05
lora_target_modules:
- q_proj
- v_proj
- k_proj
- o_proj
- gate_proj
- up_proj
- down_proj
lora_modules_to_save:
- embed_tokens
- lm_head
train_on_inputs: false
group_by_length: false
bf16: auto
fp16: false
tf32: false
gradient_checkpointing: true
early_stopping_patience: 3
gradient_accumulation_steps: 4
per_device_train_batch_size: 4
per_device_eval_batch_size: 4
num_epochs: 3
optimizer: adamw_torch
lr_scheduler: cosine
learning_rate: 2e-4
warmup_ratio: 0.03
deepspeed: ./deepspeed_config.json # Optional
fsdp: false
Supported Models:
- Llama 1/2/3/3.1
- Mistral, Mixtral
- Qwen 1/2/2.5
- Gemma 1/2
- Falcon
- Phi 1/2/3
- DeepSeek, DeepSeek-V2
- Yi, Yi-1.5
- DBRX
- Command-R
- And many more
Dataset Formats Supported: Alpaca, ShareGPT, ChatML, messages, raw text, JSONL, Parquet
Training Entry:
# Single GPU
accelerate launch -m axolotl.cli.train config.yml
# Multi-GPU
deepspeed --num_gpus 8 train.py --config config.yml
8.2 Unsloth
Unsloth optimizes memory and speed for LoRA/QLoRA training through custom CUDA kernels and optimized gradient checkpointing.
Key Optimizations:
- Custom attention kernels: 2x faster than vanilla implementation
- Optimized gradient checkpointing: Selective checkpointing reduces memory by 50% with minimal speed loss
- 4-bit optimizations: Faster NF4 quantization and dequantization
- Memory tracking: Built-in memory usage reporting
Memory Reductions:
| Model | Standard QLoRA | Unsloth QLoRA | Savings |
|---|---|---|---|
| Llama 3 8B | ~16 GB | ~12 GB | 25% |
| Mistral 7B | ~14 GB | ~10 GB | 29% |
| Gemma 7B | ~14 GB | ~11 GB | 21% |
| Llama 3 70B | ~72 GB | ~56 GB | 22% |
Usage:
from unsloth import FastLanguageModel
import torch
model, tokenizer = FastLanguageModel.from_pretrained(
model_name="meta-llama/Llama-2-7b-hf",
max_seq_length=2048,
dtype=torch.bfloat16,
load_in_4bit=True,
)
model = FastLanguageModel.get_peft_model(
model,
r=16,
target_modules=["q_proj", "v_proj", "k_proj", "o_proj"],
lora_alpha=16,
lora_dropout=0,
bias="none",
use_gradient_checkpointing="unsloth", # Enable Unsloth's optimized checkpointing
random_state=42,
use_rslora=False,
loftq_config=None,
)
# Standard HuggingFace trainer interface
from transformers import TrainingArguments
from trl import SFTTrainer
trainer = SFTTrainer(
model=model,
tokenizer=tokenizer,
train_dataset=dataset,
args=TrainingArguments(
output_dir="./output",
per_device_train_batch_size=2,
gradient_accumulation_steps=4,
warmup_steps=5,
max_steps=100,
learning_rate=2e-4,
fp16=not torch.cuda.is_bf16_supported(),
bf16=torch.cuda.is_bf16_supported(),
logging_steps=1,
optim="adamw_8bit",
weight_decay=0.01,
lr_scheduler_type="linear",
seed=3407,
),
)
trainer.train()
8.3 TRL (Transformer Reinforcement Learning)
TRL provides trainers for various alignment methods.
SFTTrainer (Supervised Fine-Tuning)
from trl import SFTTrainer
trainer = SFTTrainer(
model=model,
tokenizer=tokenizer,
train_dataset=dataset,
dataset_text_field="text",
max_seq_length=2048,
args=TrainingArguments(
output_dir="./sft-output",
per_device_train_batch_size=4,
gradient_accumulation_steps=4,
learning_rate=2e-4,
num_train_epochs=3,
fp16=True,
logging_steps=10,
),
)
trainer.train()
Packing: TRL supports packing multiple short sequences into one training example to improve throughput:
SFTTrainer(..., packing=True)
DPOTrainer (Direct Preference Optimization)
DPO eliminates the need for a separate reward model by directly optimizing the policy on preference pairs:
from trl import DPOTrainer
dpo_trainer = DPOTrainer(
model=model,
ref_model=ref_model,
tokenizer=tokenizer,
train_dataset=preference_dataset,
args=TrainingArguments(
output_dir="./dpo-output",
per_device_train_batch_size=4,
gradient_accumulation_steps=4,
learning_rate=1e-6,
max_steps=1000,
fp16=True,
),
beta=0.1, # KL penalty coefficient
)
dpo_trainer.train()
Dataset Format (DPO):
{
"prompt": "Explain quantum computing",
"chosen": "Quantum computing leverages quantum mechanical phenomena..."...9. Hyperparameter Tuning
9.1 Learning Rate
Learning rate is the most impactful hyperparameter in fine-tuning.
Scheduling Strategies:
| Schedule | Description | Best For |
|---|---|---|
| Cosine | LR decreases following cosine curve from max to min | General purpose, smooth convergence |
| Linear | Linear decay to 0 | Short training runs, simple tasks |
| Constant | Fixed LR throughout training | Very short runs, minimal adaptation |
| Constant with warmup | Linear warmup then constant | Stable training with aggressive LR |
| Cosine with restarts | Cyclical cosine annealing | Escaping local minima, multi-task |
| Inverse square root | LR β 1/βt | Very long training runs |
Recommended Defaults:
| Setting | Value |
|---|---|
| Learning rate (LoRA, 7B) | 2e-4 |
| Learning rate (LoRA, 13B) | 1e-4 |
| Learning rate (LoRA, 70B) | 5e-5 |
| Learning rate (Full FT, 7B) | 1e-5 |
| Learning rate (Full FT, 13B) | 5e-6 |
| Learning rate (Full FT, 70B) | 2e-6 |
| Warmup ratio | 0.03 (3% of total steps) |
| Schedule | Cosine |
LR Range Guidelines:
LoRA: 1e-5 to 5e-4 (sweet spot: 1e-4 to 2e-4)
Full FT: 5e-6 to 5e-5 (sweet spot: 1e-5)
DPO: 1e-7 to 1e-6 (sweet spot: 5e-7)
9.2 Batch Size
Terminology:
- Per-device batch size: Samples per GPU per step
- Gradient accumulation steps: Accumulate gradients over N steps before applying update
- Effective batch size: Per-device Γ GPUs Γ gradient accumulation steps
Calculations:
Effective batch size = per_device_train_batch_size Γ num_gpus Γ gradient_accumulation_steps
Example:
per_device_train_batch_size: 4
gradient_accumulation_steps: 8
num_gpus: 8
# Effective: 4 Γ 8 Γ 8 = 256 samples per update
Batch Size Guidelines:
| Model Size | Min Batch | Recommended | Max Batch |
|---|---|---|---|
| 7B | 32 | 128 | 1024 |
| 13B | 16 | 64 | 512 |
| 34B | 8 | 32 | 256 |
| 70B | 4 | 16 | 128 |
| 180B | 2 | 8 | 64 |
Effects of Batch Size:
- Too small: Noisy gradients, unstable training, poor generalization
- Too large: Smooth gradients, but wall-clock time per epoch increases; may converge to sharp minima
Scaling Laws (Batch Size):
- Double batch size β halve learning rate for similar training dynamics
- Maximum useful batch size β 0.1 Γ dataset size (for most tasks)
9.3 Epoch Count vs Tokens Seen
The number of training epochs depends on dataset size and the amount of repetition desired.
| Dataset Size | Typical Epochs | Total Tokens Seen (7B model, 100M dataset) |
|---|---|---|
| 1K examples | 10β20 | 200Mβ400M tokens |
| 10K examples | 5β10 | 500Mβ1B tokens |
| 100K examples | 2β5 | 2Bβ5B tokens |
| 1M+ examples | 1β3 | 10Bβ30B tokens |
Rule of Thumb: Train until validation loss plateaus. For LoRA, this is typically 1β5 epochs. For full fine-tuning, 1β3 epochs is usually sufficient and more can cause overfitting.
Token Budget Monitoring:
total_tokens = 0
for epoch in range(num_epochs):
for batch in dataloader:
total_tokens += batch['input_ids'].numel()
# Monitor tokens seen vs model size
tokens_per_param_ratio = total_tokens / model_params
# Good: 20β200 tokens per parameter (LoRA)
# Good: 100β500 tokens per parameter (full FT)
9.4 LoRA Rank
See Section 3.1 for detailed rank recommendations.
Tuning Approach:
1. Start with r=8 or r=16
2. Monitor validation loss β if underfitting, double the rank
3. If overfitting (train loss << val loss), halve the rank or increase dropout
4. For very large datasets (100K+), consider r=32 or r=64
Rank Scaling:
Compute budget β O(r Γ (d + k)) where d, k are weight matrix dimensions
Doubling rank β ~2x parameter count, ~2x compute, ~1.1β1.3x quality
9.5 LoRA Alpha Scaling
Alpha controls the magnitude of the adapter update:
- Ξ± = r (default): Unit scaling
- Ξ± = 2r: Stronger adapter influence
- Ξ± = r/2: Weaker adapter influence
Tuning Alpha:
- If model is not adapting enough (similar to base): increase Ξ±/r ratio
- If training is unstable: decrease Ξ±/r ratio
- If catastrophic forgetting: decrease Ξ±/r ratio
- Common range: Ξ± = 8 to 128, r = 4 to 64
9.6 Target Modules Selection
See Section 3.1 for target module recommendations.
Module Selection Strategy:
1. Start with q_proj, v_proj
2. Evaluate: if quality is insufficient, add k_proj, o_proj
3. If still insufficient, add MLP: gate_proj, up_proj, down_proj
4. Monitor: each doubling of modules roughly doubles parameter count
Module Impact by Architecture:
| Architecture | Most Impactful Modules | Additional Modules |
|---|---|---|
| Llama/Mistral | q_proj, v_proj, gate_proj | o_proj, k_proj, up_proj, down_proj |
| GPT-NeoX | query, value, dense_h_to_4h | attention.output, dense_4h_to_h |
| Falcon | query, value, dense | attention_output, dense_4h_to_h |
| CodeGen | qkv_proj, out_proj | fc_in, fc_out |
| BLOOM | query_key_value, dense | dense_h_to_4h, dense_4h_to_h |
9.7 Weight Decay
Weight decay acts as L2 regularization on the adapter weights.
| Setting | Effect |
|---|---|
| 0.0 | No regularization (fast training, risky for small data) |
| 0.01 | Light regularization (default for most tasks) |
| 0.1 | Strong regularization (small datasets, overfitting) |
| 1.0 | Very strong (rarely used) |
Recommendation: 0.01 for LoRA fine-tuning with 1K+ examples. Increase to 0.1 for <500 examples.
9.8 AdamW Betas and Epsilon
Default AdamW Parameters:
optimizer = torch.optim.AdamW(
model.parameters(),
lr=2e-4,
betas=(0.9, 0.999), # Default: (0.9, 0.999)
eps=1e-8, # Default: 1e-8
weight_decay=0.01,
)
When to Tune Betas:
- Spiky loss: Increase Ξ²β to 0.9999 (smoother but slower adaptation)
- Slow convergence: Decrease Ξ²β to 0.85 (more weight on recent gradients)
- Small batch training: Consider Ξ²β=0.95, Ξ²β=0.999 (more stable)
When to Tune Epsilon:
- Training instability: Increase Ξ΅ to 1e-7 or 1e-6
- Mixed precision issues: Ξ΅ = 1e-4 or 1e-5 for fp16 training
- Convergence plateaus: Ξ΅ = 1e-8 is usually fine
9.9 LoRA Dropout
See Section 3.1 for dropout recommendations.
Tuning Dropout Together with Rank:
Low rank + low dropout: Under-parameterized, might underfit
Low rank + high dropout: Too constrained, won't learn
High rank + low dropout: Overfit risk
High rank + high dropout: Good for small data
10. Training Stability
10.1 Loss Spikes
Loss spikes during training can derail fine-tuning. Common causes and solutions:
| Cause | Symptom | Fix |
|---|---|---|
| Data quality issue | Isolated spike in one batch | Detect and remove bad data point |
| Learning rate too high | Gradual rise after initial drop | Reduce LR, increase warmup |
| Batch with outlier length | Spike on long sequences | Gradient clipping, sort by length |
| Numerical instability | Loss β NaN or Inf | Use bf16 instead of fp16, increase epsilon |
| Optimizer state corruption | Spikes at resume | Clear optimizer state on resume |
| Data ordering bias | Periodic spikes | Shuffle data before each epoch |
| LoRA rank too high | Oscillating loss | Reduce rank or increase dropout |
Gradient Clipping:
# In TrainingArguments
args = TrainingArguments(
max_grad_norm=1.0, # Default: clip gradients to max L2 norm of 1.0
)
Detection and Recovery:
def detect_loss_spike(loss_history, threshold=3.0, window=10):
if len(loss_history) < window * 2:
return False
recent = loss_history[-window:]
prior = loss_history[-window*2:-window]
recent_mean = np.mean(recent)
prior_mean = np.mean(prior)
prior_std = np.std(prior)
# Flag if recent mean > prior mean + threshold * prior std
if recent_mean > prior_mean + threshold * prior_std:
return True
return False
When Spikes Occur:
1. Pause training (if automated)
2. Examine the last batch β bad data point?
3. Reduce LR by 0.5x or 0.3x and restart from previous checkpoint
4. If persistent: Switch to bf16, increase gradient accumulation, reduce batch size
10.2 Loss Curves Interpretation
Ideal Loss Curve:
Loss
β \
β \
β \___
β \
β \___
β \__
βββββββββββββββββββ Steps
Common Patterns:
Pattern 1: Healthy training
- Loss drops smoothly, no spikes
- Train and val loss converge
- Val loss eventually plateaus
Pattern 2: Overfitting
Train: \___ (low)
Val: \___/ (higher, diverging)
Fix: Reduce epochs, increase dropout, add weight decay, reduce rank
Pattern 3: Underfitting
Train: \__________ (still high, still decreasing)
Val: \__________ (plateau)
Fix: Increase epochs, increase LR, increase rank, reduce dropout
Pattern 4: Learning rate too high
Train: \/\/\/\/\/ (oscillating)
Fix: Reduce LR, increase warmup
Pattern 5: Data quality issues
Train: \___/\___/\ (periodic spikes)
Fix: Remove outlier data, shuffle, gradient clipping
Pattern 6: Catastrophic forgetting
Train: \__ (low on new task)
Benchmark: /βΎβΎ (dropping on original task)
Fix: Reduce LR, use replay data, reduce epochs
10.3 Evaluation During Training
Regular evaluation prevents wasted training and enables early stopping.
Evaluation Types:
| Type | Frequency | Metric | Cost |
|---|---|---|---|
| Per-step loss | Every step | Cross-entropy loss | None |
| Perplexity | Every N steps (e.g., 100) | exp(loss) on eval set | ~1 minute |
| Downstream eval | Every N steps (e.g., 500) | Task-specific (F1, EM, etc.) | 5β30 minutes |
| Generative eval | Every N epochs | Human eval, LLM-as-judge | 10β60 minutes |
Implementation:
from transformers import TrainerCallback
class EvalCallback(TrainerCallback):
def __init__(self, eval_dataset, tokenizer, every_n_steps=100):
self.eval_dataset = eval_dataset
self.tokenizer = tokenizer
self.every_n_steps = every_n_steps
def on_step_end(self, args, state, control, **kwargs):
if state.global_step % self.every_n_steps == 0:
# Compute perplexity
model = kwargs['model']
eval_loader = DataLoader(self.eval_dataset, batch_size=4)
total_loss = 0
for batch in eval_loader:
outputs = model(**batch)
total_loss += outputs.loss.item()
avg_loss = total_loss / len(eval_loader)
perplexity = np.exp(avg_loss)
self.log({"eval/perplexity": perplexity, "step": state.global_step})
10.4 Checkpointing Strategies
Checkpoint Frequency:
| Strategy | Frequency | Storage Cost | Recovery |
|---|---|---|---|
| By step | Every N steps (e.g., 100) | N Γ model_size/hour | Fine-grained |
| By epoch | Every epoch | epoch_count Γ model_size | Coarse |
| Best N | When val loss improves | N Γ model_size | Best quality |
| Time-based | Every N hours | N Γ model_size/hour | Time-budget aligned |
Recommended Enterprise Strategy:
Save every 500 steps (by step)
Keep last 3 checkpoints (disk space management)
Save best checkpoint (by validation loss)
args = TrainingArguments(
save_strategy="steps",
save_steps=500,
save_total_limit=3, # Keep only 3 most recent
load_best_model_at_end=True,
metric_for_best_model="eval_loss",
greater_is_better=False,
)
Checkpoint Structure:
checkpoint-500/
βββ adapter_config.json # LoRA config
βββ adapter_model.safetensors # LoRA weights
βββ training_args.bin # Training arguments snapshot
βββ optimizer.pt # Optimizer state (for resume)
βββ scheduler.pt # LR scheduler state
βββ trainer_state.json # Step, epoch, best metric
βββ rng_state.pth # Random state for reproducibility
βββ scaling.pt # QLoRA scaling factors (if applicable)
10.5 Resuming from Checkpoint
# Resume training
trainer.train(resume_from_checkpoint=True)
# Or from specific checkpoint:
trainer.train(resume_from_checkpoint="./checkpoint-500")
# The trainer will:
# 1. Load model weights
# 2. Load optimizer and scheduler state
# 3. Skip already-processed data
# 4. Continue from save step
Issues When Resuming:
- Dataset shuffle: Will shuffle differently unless you set a seed for each run
- Data loading: If dataset changed, resume may skip wrong indices
- Optimizer state: Load optimizer.pt to avoid starting from fresh momentum
- LR scheduler: LR will jump if not properly restored
- Mixed precision: Loss scaler state must be restored for fp16
11. Evaluation
11.1 Holdout Sets Creation
Temporal Split (Recommended for Enterprise):
# Sort by timestamp, use oldest 80% for train, newest 10% for val, newest 10% for test
dataset_sorted = sorted(dataset, key=lambda x: x['timestamp'])
n = len(dataset_sorted)
train = dataset_sorted[:int(n*0.8)]
val = dataset_sorted[int(n*0.8):int(n*0.9)]
test = dataset_sorted[int(n*0.9):]
Stratified Split:
from sklearn.model_selection import StratifiedShuffleSplit
sss = StratifiedShuffleSplit(n_splits=1, test_size=0.2, random_state=42)
for train_idx, test_idx in sss.split(X, strata):
train = [dataset[i] for i in train_idx]
test = [dataset[i] for i in test_idx]
Cross-Validation (for small datasets):
from sklearn.model_selection import KFold
kf = KFold(n_splits=5, shuffle=True, random_state=42)
fold_metrics = []
for fold, (train_idx, val_idx) in enumerate(kf.split(dataset)):
train_data = [dataset[i] for i in train_idx]
val_data = [dataset[i] for i in val_idx]
# Train on fold
metrics = train_and_evaluate(train_data, val_data)
fold_metrics.append(metrics)
mean_metrics = {k: np.mean([m[k] for m in fold_metrics]) for k in fold_metrics[0]}
11.2 Domain-Specific Metrics
General Metrics:
- Perplexity
- Accuracy
- F1 Score
- BLEU (generation)
- ROUGE (summarization)
- METEOR
- BERTScore
Legal Domain:
- Citation accuracy (% of citations correctly formatted)
- Statute relevance (precision/recall of cited statutes)
- Legal reasoning coherence (expert rating, 1β5)
- Hallucination rate of legal facts
Medical Domain:
- ICD-10 code assignment accuracy
- Drug name accuracy (% correctly named)
- Clinical note completeness (expert checklist)
- Safety flag rate (incorrect medical advice)
- HIPAA compliance (PII presence in generated text)
Finance Domain:
- Numerical calculation accuracy (% correct)
- Financial ratio calculation error
- Sentiment alignment with ground truth (finance-specific sentiment)
- SEC filing format compliance
Code Domain:
- Pass@k (functional correctness)
- Syntax error rate
- Test coverage of generated code
- Code style compliance (PEP8, etc.)
- Security vulnerability rate (SAST scan)
Customer Support:
- CSAT score prediction (agreement with actual CSAT)
- First Contact Resolution accuracy
- Intent classification accuracy
- Escalation prediction precision/recall
- Brand voice compliance (expert rating)
11.3 Human Evaluation Protocol
Protocol Design:
- Sample selection: Random sample of 100β500 generations per model variant
- Blinding: Remove model identifiers, shuffle outputs
- Evaluators: 3+ domain experts per sample
- Criteria: Task-specific rubric (1β5 scale per criterion)
- Calibration: Gold-standard examples with known scores
- Quality control: Inter-annotator agreement monitoring
Sample Rubric (Legal Domain):
Criteria 1 (Poor) 3 (Good) 5 (Excellent)
Accuracy of legal facts >3 errors 0-1 errors Perfect
Citation format None cited Partial Complete & correct
Reasoning quality Illogical Sound Compelling
Helpfulness Unhelpful Somewhat Highly actionable
Safety (no hallucination) Hallucinates Minor issues No issues
Typical Enterprise Human Eval Cost:
100 samples Γ 3 evaluators Γ 5 minutes each Γ $50/hr = ~$125
1,000 samples Γ 3 evaluators Γ 5 min Γ $50/hr = ~$1,250
11.4 A/B Testing
Online A/B Testing for LLM Deployment:
- Traffic split: 50% control (baseline), 50% treatment (fine-tuned model)
- Metric collection: User engagement, task completion, latency, feedback ratings
- Statistical significance: Chi-squared test, t-test, or Bayesian A/B test
- Minimum sample size:
n = (Z_Ξ±/2 + Z_Ξ²)Β² Γ (pβ(1-pβ) + pβ(1-pβ)) / (pβ - pβ)Β²
Example: 5% improvement, 80% power, 95% confidence β ~3,000 samples per variant
Implementation:
from scipy import stats
def ab_test(control_outcomes, treatment_outcomes):
"""Two-proportion z-test"""
n_control = len(control_outcomes)
n_treatment = len(treatment_outcomes)
p_control = sum(control_outcomes) / n_control
p_treatment = sum(treatment_outcomes) / n_treatment
p_pool = (sum(control_outcomes) + sum(treatment_outcomes)) / (n_control + n_treatment)
se = np.sqrt(p_pool * (1 - p_pool) * (1/n_control + 1/n_treatment))
z = (p_treatment - p_control) / se
p_value = 2 * (1 - stats.norm.cdf(abs(z)))
return {
'control_rate': p_control,
'treatment_rate': p_treatment,
'lift': (p_treatment - p_control) / p_control,
'p_value': p_value,
'significant': p_value < 0.05,
}
11.5 Regression Testing
Maintain a regression test suite to catch quality regressions when updating models:
REGRESSION_TESTS = [
{
"name": "citation_format",
"input": "What is the holding in Miranda v. Arizona?",
"expected_pattern": r"\d+ U\.S\. \d+", # Must cite US Reports
"metric": "regex_match",
},
{
"name": "no_pii_leak",
"input": "Summarize this patient: John Doe, SSN 123-45-6789",
"expected_pattern": r"(?i)(?:\[REDACTED\]|<PERSON>|<SSN>)",
"metric": "regex_match",
},
{
"name": "code_compiles",
"input": "Write a Python function to sort a list",
"metric": "code_compiles",
},
]
Regression Suite Pipeline:
1. Run suite against baseline model β store ...
12. Deployment
12.1 Merging LoRA Adapters into Base Model
For production deployment, merging LoRA weights into the base model eliminates adapter loading overhead:
from peft import PeftModel
from transformers import AutoModelForCausalLM, AutoTokenizer
import torch
# Load base model
base_model = AutoModelForCausalLM.from_pretrained(
"meta-llama/Llama-2-7b-hf",
torch_dtype=torch.bfloat16,
device_map="auto",
)
# Load LoRA adapter
lora_model = PeftModel.from_pretrained(base_model, "./lora-checkpoint-500")
# Merge weights into base model
merged_model = lora_model.merge_and_unload()
# Save merged model
merged_model.save_pretrained("./merged-model")
tokenizer.save_pretrained("./merged-model")
Storage Comparison:
| Format | 7B Base | 7B + LoRA (r=16) | 7B Merged |
|---|---|---|---|
| FP16 | 14 GB | 14 GB + 16 MB | 14 GB |
| 4-bit | 4 GB | 4 GB + 16 MB | 4 GB |
When to Merge vs Keep Separate:
| Scenario | Merge | Keep Separate |
|---|---|---|
| Single adapter deployment | β | |
| Multiple adapter routing | β | |
| Maximum inference throughput | β | |
| Quick adapter switching | β | |
| GPU memory constrained | β (one forward pass) | |
| Hot-swapping | β |
12.2 Serving Multiple Adapters
vLLM LoRA Adapter Routing
vLLM supports serving multiple LoRA adapters simultaneously with dynamic routing:
from vllm import LLM, SamplingParams
# Initialize with base model + max LoRA config
llm = LLM(
model="meta-llama/Llama-2-7b-hf",
enable_lora=True,
max_loras=8, # Max concurrent adapters
max_lora_rank=64, # Max rank among all adapters
# LoRA modules used across all adapters
lora_extra_vocab_size=256,
)
# Load adapters
llm.load_lora(
"legal-v1",
lora_path="./adapters/legal-v1",
)
llm.load_lora(
"medical-v1",
lora_path="./adapters/medical-v1",
)
# Route requests to specific adapter
prompts = [
("What is the holding in Marbury v. Madison?", "legal-v1"),
("Diagnose based on these symptoms...", "medical-v1"),
]
sampling_params = SamplingParams(temperature=0.1, max_tokens=512)
# vLLM automatically routes each prompt to its specified adapter
outputs = llm.generate(
[p[0] for p in prompts],
sampling_params,
lora_request=[p[1] for p in prompts], # Per-prompt adapter routing
)
vLLM Adapter Hot-Swapping:
# Start vLLM server with LoRA support
python -m vllm.entrypoints.openai.api_server \
--model meta-llama/Llama-2-7b-hf \
--enable-lora \
--max-loras 8 \
--max-lora-rank 64
# Hot-swap: update an adapter without restarting
curl http://localhost:8000/v1/load_lora_adapter \
-H "Content-Type: application/json" \
-d '{"lora_name": "legal-v2", "lora_path": "./adapters/legal-v2"}'
# Then route traffic to new adapter
curl http://localhost:8000/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{
"model": "legal-v2",
"messages": [{"role": "user", "content": "Explain contract law"}]
}'
Tensor Parallelism with LoRA
When using tensor parallelism (model parallelism across GPUs), LoRA adapters must be sharded consistently with the base model:
from vllm import LLM
llm = LLM(
model="meta-llama/Llama-2-70b-hf",
tensor_parallel_size=4, # 4 GPUs
enable_lora=True,
max_loras=4,
)
Key Considerations for TP + LoRA:
- LoRA weights are sharded across GPUs along with base weights
- Each GPU holds 1/TP_size of each adapter
- Adapter loading/unloading must happen on all GPUs
12.3 Adapter Versioning
Versioning Schema:
adapters/
βββ legal-v1.0.0/ # Major.Minor.Patch
β βββ adapter_config.json
β βββ adapter_model.safetensors
βββ legal-v1.1.0/
β βββ adapter_config.json
β βββ adapter_model.safetensors
βββ legal-v2.0.0/
β βββ adapter_config.json
β βββ adapter_model.safetensors
βββ manifest.json
Manifest Example:
{
"adapters": [
{
"name": "legal-v1.0.0",
"base_model": "meta-llama/Llama-2-7b-hf",
"created": "2024-06-01T10:00:00Z",
"dataset": "legal-corpus-v1",
"metrics": {"f1": 0.92, "exact_match": 0.87},
"status": "deprecated",
"checksum": "sha256:abc123..."
},
{
"name": "legal-v1.1.0",
"base_model": "meta-llama/Llama-2-7b-hf",
"created": "2024-06-15T14:30:00Z",
"dataset": "legal-corpus-v2",
"metrics": {"f1": 0.94, "exact_match": 0.89},
"status": "active",
"checksum": "sha256:def456..."
}
]
}
12.4 Model Registry
Enterprise Model Registry (using MLflow example):
import mlflow
mlflow.set_tracking_uri("http://mlflow-server:5000")
with mlflow.start_run(run_name="llama-7b-legal-v2"):
# Log parameters
mlflow.log_params({
"base_model": "meta-llama/Llama-2-7b-hf",
"lora_r": 16,
"lora_alpha": 32,
"learning_rate": 2e-4,
"batch_size": 128,
"dataset": "legal-corpus-v2",
"num_epochs": 3,
})
# Log metrics
mlflow.log_metrics({
"eval_loss": 0.85,
"eval_accuracy": 0.94,
"eval_f1": 0.92,
})
# Log model
mlflow.transformers.log_model(
transformers_model={"model": merged_model, "tokenizer": tokenizer},
artifact_path="model",
registered_model_name="llama-7b-legal",
)
# Transition model stage
from mlflow.tracking import MlflowClient
client = MlflowClient()
client.transition_model_version_stage(
name="llama-7b-legal",
version=2,
stage="Staging", # Options: None, Staging, Production, Archived
)
12.5 Canary Deployments
Canary Deployment Strategy for LLM Adapters:
Phase 1: Internal (5% traffic, dev team only)
β (monitor forπ AI Infrastructure: Compute, Storage, and Networking β 103 lines
1. Introduction
AI infrastructure is the physical and virtual foundation for training and deploying ML models. It encompasses GPUs, storage systems, networking, and orchestration software. As models grow to trillion-plus parameters, infrastructure design becomes a critical competitive advantage.
2. GPU Architecture
2.1 Current Generation (2025-2026)
| GPU | Memory | BW | FP8 TFLOPS | Interconnect | Power |
|---|---|---|---|---|---|
| H100 SXM | 80GB HBM3 | 3.35 TB/s | 1,979 | NVLink 4 (900 GB/s) | 700W |
| H200 | 141GB HBM3e | 4.8 TB/s | 1,979 | NVLink 4 | 700W |
| B200 | 192GB HBM3e | 8 TB/s | 4,500 | NVLink 5 (1.8 TB/s) | 1,000W |
| MI300X (AMD) | 192GB HBM3 | 5.3 TB/s | 1,307 | Infinity Fabric | 750W |
2.2 Bottleneck: Memory Bandwidth
For inference, the bottleneck is memory bandwidth (loading model weights) not compute (matrix multiplication). This is why:
- Quantization helps (less data to load)
- Batch processing helps (amortize loading cost)
- Speculative decoding helps (fewer generations)
3. Training Infrastructure
3.1 Cluster Design
| Scale | GPUs | Model Size | Training Time | Example |
|---|---|---|---|---|
| Single | 1-8 | <7B | Hours | Fine-tuning |
| Small | 8-64 | 7-70B | Days | LLaMA 70B |
| Medium | 64-1024 | 70-400B | Weeks | GPT-4, Claude |
| Large | 1024-16384 | 400B-1T+ | Months | Frontier models |
| Frontier | 16K-100K+ | >1T | Months | Next-gen systems |
3.2 Parallelism Strategies
| Strategy | Split | Communication | Best For |
|---|---|---|---|
| Data Parallel (DP) | Data across GPUs | Gradient sync | Small models, large data |
| Tensor Parallel (TP) | Layers across GPUs | High (NVLink) | Large models, single node |
| Pipeline Parallel (PP) | Layer groups | Moderate | Cross-node scaling |
| Expert Parallel (EP) | MoE experts | All-to-all | Mixture of Experts |
| Sequence Parallel (SP) | Sequence dimension | High | Long sequences |
| FSDP (Fully Sharded DP) | Params + optimizer states | Moderate | Training efficiency |
4. Inference Infrastructure
4.1 Serving Comparison (see: [05-Enterprise/01-Enterprise-AI-Deployment.md] Β§4)
- vLLM: Best throughput (PagedAttention)
- TGI: Best Hugging Face integration
- Triton: Best multi-framework support
- TensorRT-LLM: Best NVIDIA-optimized performance
4.2 Inference Hardware
- Cloud GPUs: A10G (24GB), L4 (24GB), L40S (48GB), A100 (80GB), H100 (80GB)
- Edge/Custom: Apple M-series Neural Engine, Google TPU v5p, AWS Trainium2
- Inference ASICs: Groq LPU, Cerebras Wafer-Scale, d-Matrix
5. Storage for AI
| Tier | Type | Example | Speed | Cost/GB |
|---|---|---|---|---|
| Hot | NVMe SSD | 8x Samsung PM9A3 | 20 GB/s | ~$0.10 |
| Warm | Object storage | S3, GCS | 1-10 GB/s | ~$0.02 |
| Cold | Tape/Archive | Glacier, Azure Archive | Minutes | ~$0.001 |
| ML Cache | Distributed cache | Alluxio, JuiceFS | Varies | Varies |
6. Networking
| Interconnect | Speed | Topology | Latency |
|---|---|---|---|
| NVLink 4 | 900 GB/s (GPU-GPU) | Fully connected | <1Β΅s |
| NVLink 5 | 1.8 TB/s (GPU-GPU) | Fully connected | <1Β΅s |
| InfiniBand NDR 400 | 400 Gb/s (Node-Node) | Fat tree | 1-2Β΅s |
| InfiniBand XDR | 800 Gb/s (Node-Node) | Fat tree | 1-2Β΅s |
| RoCE v2 | 200-400 Gb/s | Various | 3-5Β΅s |
| Ethernet | 100-800 Gb/s | Various | 5-50Β΅s |
AI Library of Alexandria β Enterprise Β· 3 Docs Β· 4,493 Lines
Home Β· June 2026