GenAI Production Deployment Guide
Deploying Generative AI applications to production at scale.
Table of Contents
- Introduction
- GenAI Architecture Patterns
- Scaling Strategies
- Hyperscaler Deployment
- Production Best Practices
- Monitoring and Observability
- Cost Optimization
- Security and Compliance
- Resources
Introduction
What is GenAI Production Deployment?
Production deployment of Generative AI involves:
- Model Serving: Deploying LLMs and GenAI models for inference
- RAG Systems: Retrieval Augmented Generation architectures
- Multi-Agent Systems: Coordinating multiple AI agents
- Scalability: Handling high traffic and concurrent requests
- Reliability: Ensuring uptime and fault tolerance
- Cost Management: Optimizing infrastructure costs
Key Challenges
- High Latency: LLMs can be slow
- High Costs: GPU infrastructure is expensive
- Scalability: Need to handle variable load
- Model Management: Versioning and updates
- Context Management: Handling long contexts
- Rate Limiting: Preventing abuse
GenAI Architecture Patterns
1. RAG Architecture
Retrieval Augmented Generation combines retrieval and generation:
User Query → Embedding → Vector Search → Context Retrieval → LLM → Response
Production RAG Architecture
from langchain_community.vectorstores import Pinecone
from langchain_openai import OpenAIEmbeddings, ChatOpenAI
from langchain.chains import RetrievalQA
class ProductionRAGSystem:
def __init__(self):
# Vector store
self.embeddings = OpenAIEmbeddings()
self.vectorstore = Pinecone.from_existing_index(
index_name="production-index",
embedding=self.embeddings
)
# Chat LLM (temperature=0 lowers randomness; not a correctness guarantee)
self.llm = ChatOpenAI(
model="gpt-4o",
temperature=0,
max_tokens=1000,
)
# RAG chain
self.qa_chain = RetrievalQA.from_chain_type(
llm=self.llm,
chain_type="stuff",
retriever=self.vectorstore.as_retriever(
search_kwargs={"k": 5}
),
return_source_documents=True
)
def query(self, question: str):
"""Query with caching and error handling"""
try:
result = self.qa_chain({"query": question})
return {
"answer": result["result"],
"sources": result["source_documents"]
}
except Exception as e:
return {"error": str(e)}
2. Multi-Agent Architecture
Coordinated multi-agent systems:
from crewai import Agent, Task, Crew
from langgraph.graph import StateGraph
class MultiAgentSystem:
def __init__(self):
self.agents = self._create_agents()
self.workflow = self._create_workflow()
def _create_agents(self):
return {
"researcher": Agent(
role="Researcher",
goal="Research topics",
backstory="Expert researcher"
),
"writer": Agent(
role="Writer",
goal="Write content",
backstory="Skilled writer"
),
"reviewer": Agent(
role="Reviewer",
goal="Review content",
backstory="Expert reviewer"
)
}
def _create_workflow(self):
workflow = StateGraph(State)
workflow.add_node("research", self.research_node)
workflow.add_node("write", self.write_node)
workflow.add_node("review", self.review_node)
workflow.set_entry_point("research")
workflow.add_edge("research", "write")
workflow.add_edge("write", "review")
return workflow.compile()
3. Streaming Architecture
Stream responses for better UX:
from fastapi import FastAPI
from fastapi.responses import StreamingResponse
import asyncio
app = FastAPI()
@app.post("/chat/stream")
async def chat_stream(query: str):
"""Stream LLM responses"""
async def generate():
llm = OpenAI(streaming=True)
async for chunk in llm.astream(query):
yield f"data: {chunk}\n\n"
return StreamingResponse(generate(), media_type="text/event-stream")
4. Caching Architecture
Cache responses to reduce costs:
from functools import lru_cache
import hashlib
import json
class GenAICache:
def __init__(self, redis_client):
self.redis = redis_client
self.ttl = 3600 # 1 hour
def _cache_key(self, query: str, model: str) -> str:
"""Generate cache key"""
c>f"{query}:{model}"
return hashlib.md5(content.encode()).hexdigest()
def get(self, query: str, model: str):
"""Get from cache"""
key = self._cache_key(query, model)
cached = self.redis.get(key)
if cached:
return json.loads(cached)
return None
def set(self, query: str, model: str, response: dict):
"""Set in cache"""
key = self._cache_key(query, model)
self.redis.setex(
key,
self.ttl,
json.dumps(response)
)
# Usage
cache = GenAICache(redis_client)
def query_with_cache(query: str, model: str):
# Check cache
cached = cache.get(query, model)
if cached:
return cached
# Query LLM
resp>
# Cache response
cache.set(query, model, response)
return response
Scaling Strategies
1. Horizontal Scaling
Scale out with multiple instances:
# Load balancer configuration
# nginx.conf
upstream genai_backend {
least_conn; # Use least connections
server app1:8000;
server app2:8000;
server app3:8000;
}
server {
listen 80;
location / {
proxy_pass http://genai_backend;
proxy_set_header Host $host;
}
}
2. Model Parallelism
Split models across GPUs:
import torch
from torch.nn.parallel import DataParallel
class ParallelLLM:
def __init__(self, model, device_ids=[0, 1, 2, 3]):
self.model = DataParallel(model, device_ids=device_ids)
def generate(self, input_ids):
return self.model.generate(input_ids)
3. Batch Processing
Process multiple requests together:
from queue import Queue
import threading
class BatchProcessor:
def __init__(self, batch_size=32, timeout=0.1):
self.batch_size = batch_size
self.timeout = timeout
self.queue = Queue()
self.results = {}
def add_request(self, request_id: str, input_data: dict):
"""Add request to batch"""
self.queue.put((request_id, input_data))
def process_batch(self):
"""Process batch of requests"""
batch = []
while len(batch) < self.batch_size:
try:
item = self.queue.get(timeout=self.timeout)
batch.append(item)
except:
break
if batch:
# Process batch
inputs = [item[1] for item in batch]
outputs = self.model.generate_batch(inputs)
# Store results
for (request_id, _), output in zip(batch, outputs):
self.results[request_id] = output
def get_result(self, request_id: str):
"""Get result for request"""
return self.results.pop(request_id, None)
4. Async Processing
Process requests asynchronously:
from fastapi import FastAPI, BackgroundTasks
import asyncio
app = FastAPI()
@app.post("/generate/async")
async def generate_async(prompt: str, background_tasks: BackgroundTasks):
"""Async generation"""
task_id = str(uuid.uuid4())
# Start async task
background_tasks.add_task(
process_generation,
task_id,
prompt
)
return {"task_id": task_id, "status": "processing"}
async def process_generation(task_id: str, prompt: str):
"""Process generation in background"""
result = await llm.agenerate(prompt)
# Store result
store_result(task_id, result)
Hyperscaler Deployment
AWS Deployment
SageMaker Endpoints
import sagemaker
from sagemaker.huggingface import HuggingFaceModel
# Deploy model
huggingface_model = HuggingFaceModel(
model_data="s3://bucket/model.tar.gz",
role=sagemaker.get_execution_role(),
transformers_version="4.26",
pytorch_version="1.13",
py_version="py39"
)
predictor = huggingface_model.deploy(
initial_instance_count=1,
instance_type="ml.g4dn.xlarge"
)
# Invoke endpoint
resp>
"inputs": "What is AI?"
})
Lambda for GenAI
# lambda_function.py
import json
import boto3
bedrock = boto3.client('bedrock-runtime')
def lambda_handler(event, context):
prompt = event['prompt']
resp>
modelId='anthropic.claude-v2',
body=json.dumps({
"prompt": prompt,
"max_tokens_to_sample": 1000
})
)
result = json.loads(response['body'].read())
return {
'statusCode': 200,
'body': json.dumps(result)
}
Google Cloud Deployment
Vertex AI
from google.cloud import aiplatform
# Deploy model
endpoint = aiplatform.Endpoint.create(
display_name="genai-endpoint"
)
model = aiplatform.Model.upload(
display_name="genai-model",
artifact_uri="gs://bucket/model"
)
model.deploy(
endpoint=endpoint,
machine_type="n1-standard-4",
min_replica_count=1,
max_replica_count=10
)
# Predict
resp>instances=[{"prompt": "What is AI?"}])
Cloud Run
# Dockerfile
FROM python:3.9
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .
CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "8080"]
# Deploy
gcloud run deploy genai-service \
--source . \
--platform managed \
--region us-central1 \
--memory 4Gi \
--cpu 2
Azure Deployment
Azure ML
from azure.ai.ml import MLClient
from azure.ai.ml.entities import Model, ManagedOnlineEndpoint
# Deploy model
endpoint = ManagedOnlineEndpoint(
name="genai-endpoint",
auth_mode="key"
)
ml_client.online_endpoints.begin_create_or_update(endpoint)
# Deploy to endpoint
deployment = ManagedOnlineDeployment(
name="genai-deployment",
endpoint_name="genai-endpoint",
model=model,
instance_type="Standard_NC6s_v3",
instance_count=1
)
ml_client.online_deployments.begin_create_or_update(deployment)
Production Best Practices
1. Error Handling
from fastapi import FastAPI, HTTPException
import logging
app = FastAPI()
logger = logging.getLogger(__name__)
@app.post("/generate")
async def generate(prompt: str):
try:
result = await llm.generate(prompt)
return {"result": result}
except Exception as e:
logger.error(f"Generation error: {e}")
raise HTTPException(
status_code=500,
detail="Generation failed"
)
2. Rate Limiting
from slowapi import Limiter, _rate_limit_exceeded_handler
from slowapi.util import get_remote_address
limiter = Limiter(key_func=get_remote_address)
app.state.limiter = limiter
app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler)
@app.post("/generate")
@limiter.limit("10/minute")
async def generate(request: Request, prompt: str):
return await llm.generate(prompt)
3. Input Validation
from pydantic import BaseModel, validator
class GenerationRequest(BaseModel):
prompt: str
max_tokens: int = 1000
temperature: float = 0.7
@validator('prompt')
def validate_prompt(cls, v):
if len(v) > 10000:
raise ValueError('Prompt too long')
if not v.strip():
raise ValueError('Prompt cannot be empty')
return v
@validator('temperature')
def validate_temperature(cls, v):
if not 0 <= v <= 2:
raise ValueError('Temperature must be between 0 and 2')
return v
4. Health Checks
@app.get("/health")
async def health_check():
"""Health check endpoint"""
try:
# Check LLM availability
test_resp>await llm.generate("test", max_tokens=1)
return {
"status": "healthy",
"llm": "available"
}
except Exception as e:
return {
"status": "unhealthy",
"error": str(e)
}, 503
Monitoring and Observability
1. Logging
import logging
from datetime import datetime
logger = logging.getLogger(__name__)
def log_generation(prompt: str, response: str, latency: float):
"""Log generation request"""
logger.info({
"event": "generation",
"prompt_length": len(prompt),
"response_length": len(response),
"latency_ms": latency * 1000,
"timestamp": datetime.utcnow().isoformat()
})
2. Metrics
from prometheus_client import Counter, Histogram, Gauge
# Metrics
generati>
'genai_requests_total',
'Total generation requests'
)
generati>
'genai_latency_seconds',
'Generation latency'
)
active_c>
'genai_connections_active',
'Active connections'
)
@app.post("/generate")
async def generate(prompt: str):
generation_requests.inc()
active_connections.inc()
with generation_latency.time():
result = await llm.generate(prompt)
active_connections.dec()
return result
3. Tracing
from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
tracer = trace.get_tracer(__name__)
@app.post("/generate")
async def generate(prompt: str):
with tracer.start_as_current_span("generate") as span:
span.set_attribute("prompt_length", len(prompt))
result = await llm.generate(prompt)
span.set_attribute("response_length", len(result))
return result
Cost Optimization
1. Model Selection
- Use smaller models when possible
- Use quantization (8-bit, 4-bit)
- Use model distillation
2. Caching
- Cache frequent queries
- Cache embeddings
- Use CDN for static content
3. Batching
- Batch multiple requests
- Use async processing
- Optimize batch sizes
4. Auto-Scaling
# Auto-scaling configuration
# Scale based on queue depth
if queue_depth > threshold:
scale_up()
elif queue_depth < low_threshold:
scale_down()
Security and Compliance
1. API Keys
from fastapi import Security, HTTPException
from fastapi.security import APIKeyHeader
api_key_header = APIKeyHeader(name="X-API-Key")
async def verify_api_key(api_key: str = Security(api_key_header)):
if api_key != valid_api_key:
raise HTTPException(status_code=403, detail="Invalid API key")
return api_key
2. Input Sanitization
import re
def sanitize_input(text: str) -> str:
"""Sanitize user input"""
# Remove potentially harmful patterns
text = re.sub(r'<script.*?>.*?</script>', '', text, flags=re.DOTALL)
# Limit length
text = text[:10000]
return text
3. Data Privacy
- Don't log sensitive data
- Encrypt data in transit and at rest
- Implement data retention policies
- Comply with GDPR, CCPA
Resources
Official Documentation
Tools
- Model Serving: TensorFlow Serving, TorchServe, Triton
- Orchestration: Kubernetes, Docker Swarm
- Monitoring: Prometheus, Grafana, Datadog
- Caching: Redis, Memcached
Best Practices
- Start Small: Deploy simple version first
- Monitor Early: Add monitoring from day one
- Test Thoroughly: Load test before production
- Document Everything: Keep deployment docs updated
- Plan for Scale: Design for growth from start
Try next: Add latency, cost-per-query, and failure logging to one GenAI endpoint before you add features.