Time Series Deployment Guide

Production deployment strategies for generative time series models

Comprehensive guide to deploying generative time series models in production with scalability, monitoring, and reliability considerations.

1. Deployment Architecture Overview

Deploying generative time series models in production requires careful consideration of scalability, reliability, and monitoring to ensure optimal performance and availability.

Key Deployment Considerations:

  • Scalability: Handle varying loads and concurrent requests
  • Reliability: Ensure high availability and fault tolerance
  • Performance: Optimize inference speed and resource usage
  • Monitoring: Track model performance and system health
  • Security: Protect models and data in production

2. Model Optimization

Model Compression

# Model Quantization import torch.quantization as quantization def optimize_model_for_deployment(model, calibration_data): """Optimize model for production deployment""" # 1. Quantization model.eval() model.qconfig = quantization.get_default_qconfig('fbgemm') model_prepared = quantization.prepare(model) # Calibrate with sample data with torch.no_grad(): for batch in calibration_data: _ = model_prepared(batch) quantized_model = quantization.convert(model_prepared) # 2. Pruning import torch.nn.utils.prune as prune # Prune 20% of connections for module in quantized_model.modules(): if isinstance(module, torch.nn.Linear): prune.l1_unstructured(module, name='weight', amount=0.2) prune.remove(module, 'weight') return quantized_model

TensorRT Optimization

NVIDIA TensorRT Integration:

import torch_tensorrt def optimize_with_tensorrt(model, input_shape, precision='fp16'): """Optimize model with TensorRT for NVIDIA GPUs""" # Compile model for TensorRT trt_model = torch_tensorrt.compile( model, inputs=[torch.randn(input_shape)], enabled_precisions={torch.float, torch.half}, workspace_size=1 << 30, # 1GB workspace min_block_size=7, truncate_long_and_double=True ) return trt_model # Usage example optimized_model = optimize_with_tensorrt( model=timegpt_model, input_shape=(1, 512, 1), precision='fp16' )

3. Containerization

Docker Configuration

# Dockerfile for Time Series Model Deployment FROM nvidia/cuda:11.8-devel-ubuntu20.04 # Install Python and dependencies RUN apt-get update && apt-get install -y \ python3.8 python3-pip \ && rm -rf /var/lib/apt/lists/* # Set working directory WORKDIR /app # Copy requirements and install dependencies COPY requirements.txt . RUN pip3 install --no-cache-dir -r requirements.txt # Copy model and application code COPY models/ ./models/ COPY app.py . # Expose port EXPOSE 8000 # Health check HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \ CMD curl -f http://localhost:8000/health || exit 1 # Run application CMD ["python3", "app.py"]

Docker Compose

Multi-Service Deployment:

# docker-compose.yml version: '3.8' services: timeseries-api: build: . ports: - "8000:8000" environment: - MODEL_PATH=/app/models/timegpt_best.pth - REDIS_URL=redis://redis:6379 depends_on: - redis - postgres deploy: replicas: 3 resources: limits: memory: 4G cpus: '2.0' reservations: memory: 2G cpus: '1.0' redis: image: redis:7-alpine ports: - "6379:6379" volumes: - redis_data:/data postgres: image: postgres:15 environment: POSTGRES_DB: timeseries POSTGRES_USER: admin POSTGRES_PASSWORD: password volumes: - postgres_data:/var/lib/postgresql/data nginx: image: nginx:alpine ports: - "80:80" volumes: - ./nginx.conf:/etc/nginx/nginx.conf depends_on: - timeseries-api volumes: redis_data: postgres_data:

4. API Design

FastAPI Implementation

from fastapi import FastAPI, HTTPException, BackgroundTasks from pydantic import BaseModel import asyncio import redis import json from typing import List, Optional app = FastAPI(title="TimeGPT API", version="1.0.0") # Redis for caching redis_client = redis.Redis(host='redis', port=6379, decode_responses=True) class ForecastRequest(BaseModel): data: List[float] horizon: int = 24 include_uncertainty: bool = False confidence_level: float = 0.95 class ForecastResponse(BaseModel): forecast: List[float] uncertainty: Optional[List[float]] = None confidence_level: float request_id: str @app.post("/forecast", response_model=ForecastResponse) async def forecast(request: ForecastRequest, background_tasks: BackgroundTasks): """Generate time series forecast""" # Check cache first cache_key = f"forecast:{hash(tuple(request.data))}:{request.horizon}" cached_result = redis_client.get(cache_key) if cached_result: result = json.loads(cached_result) result['from_cache'] = True return ForecastResponse(**result) try: # Generate forecast forecast_result = await generate_forecast(request) # Cache result cache_data = { 'forecast': forecast_result['forecast'], 'uncertainty': forecast_result.get('uncertainty'), 'confidence_level': forecast_result['confidence_level'], 'request_id': forecast_result['request_id'] } redis_client.setex(cache_key, 3600, json.dumps(cache_data)) # Cache for 1 hour # Log request background_tasks.add_task(log_request, request, forecast_result) return ForecastResponse(**forecast_result) except Exception as e: raise HTTPException(status_code=500, detail=str(e)) @app.get("/health") async def health_check(): """Health check endpoint""" return { "status": "healthy", "timestamp": datetime.utcnow().isoformat(), "version": "1.0.0" }

Rate Limiting

Request Rate Limiting:

from slowapi import Limiter, _rate_limit_exceeded_handler from slowapi.util import get_remote_address from slowapi.errors import RateLimitExceeded limiter = Limiter(key_func=get_remote_address) app.state.limiter = limiter app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler) @app.post("/forecast") @limiter.limit("100/minute") async def forecast(request: Request, forecast_request: ForecastRequest): """Rate-limited forecast endpoint""" # Implementation here pass

5. Monitoring and Observability

Metrics Collection

from prometheus_client import Counter, Histogram, Gauge, start_http_server import time # Metrics REQUEST_COUNT = Counter('requests_total', 'Total requests', ['method', 'endpoint']) REQUEST_DURATION = Histogram('request_duration_seconds', 'Request duration') ACTIVE_CONNECTIONS = Gauge('active_connections', 'Active connections') MODEL_ACCURACY = Gauge('model_accuracy', 'Model prediction accuracy') class MetricsMiddleware: def __init__(self, app): self.app = app async def __call__(self, scope, receive, send): if scope['type'] == 'http': start_time = time.time() # Track request REQUEST_COUNT.labels( method=scope['method'], endpoint=scope['path'] ).inc() # Track active connections ACTIVE_CONNECTIONS.inc() try: await self.app(scope, receive, send) finally: # Record duration REQUEST_DURATION.observe(time.time() - start_time) ACTIVE_CONNECTIONS.dec() else: await self.app(scope, receive, send)

Logging Configuration

Structured Logging:

import logging import json from datetime import datetime class JSONFormatter(logging.Formatter): def format(self, record): log_entry = { 'timestamp': datetime.utcnow().isoformat(), 'level': record.levelname, 'message': record.getMessage(), 'module': record.module, 'function': record.funcName, 'line': record.lineno } if hasattr(record, 'request_id'): log_entry['request_id'] = record.request_id if hasattr(record, 'user_id'): log_entry['user_id'] = record.user_id return json.dumps(log_entry) # Configure logging logging.basicConfig( level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' ) logger = logging.getLogger(__name__) logger.addHandler(logging.StreamHandler())

6. Scalability Strategies

Horizontal Scaling

Scaling Considerations:

  • Load Balancing: Distribute requests across multiple instances
  • Stateless Design: Ensure services can be scaled independently
  • Database Scaling: Use read replicas and connection pooling
  • Caching Strategy: Implement distributed caching

Kubernetes Deployment

# kubernetes-deployment.yaml apiVersion: apps/v1 kind: Deployment metadata: name: timeseries-api spec: replicas: 3 selector: matchLabels: app: timeseries-api template: metadata: labels: app: timeseries-api spec: containers: - name: timeseries-api image: timeseries-api:latest ports: - containerPort: 8000 env: - name: MODEL_PATH value: "/app/models/timegpt_best.pth" resources: requests: memory: "2Gi" cpu: "1000m" nvidia.com/gpu: 1 limits: memory: "4Gi" cpu: "2000m" nvidia.com/gpu: 1 livenessProbe: httpGet: path: /health port: 8000 initialDelaySeconds: 30 periodSeconds: 10 readinessProbe: httpGet: path: /health port: 8000 initialDelaySeconds: 5 periodSeconds: 5 --- apiVersion: v1 kind: Service metadata: name: timeseries-service spec: selector: app: timeseries-api ports: - port: 80 targetPort: 8000 type: LoadBalancer

7. Security Considerations

Authentication and Authorization

from fastapi import Depends, HTTPException, status from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials import jwt import os security = HTTPBearer() def verify_token(credentials: HTTPAuthorizationCredentials = Depends(security)): """Verify JWT token""" try: payload = jwt.decode( credentials.credentials, os.getenv('JWT_SECRET'), algorithms=['HS256'] ) return payload except jwt.ExpiredSignatureError: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Token expired" ) except jwt.JWTError: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid token" ) @app.post("/forecast") async def forecast( request: ForecastRequest, user: dict = Depends(verify_token) ): """Authenticated forecast endpoint""" # Check user permissions if not user.get('can_forecast'): raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="Insufficient permissions" ) # Generate forecast return await generate_forecast(request)

Input Validation

Data Validation:

from pydantic import validator import numpy as np class ForecastRequest(BaseModel): data: List[float] horizon: int = 24 include_uncertainty: bool = False confidence_level: float = 0.95 @validator('data') def validate_data(cls, v): if len(v) < 10: raise ValueError('Input data must have at least 10 points') if len(v) > 10000: raise ValueError('Input data too large (max 10000 points)') # Check for NaN or infinite values if any(not np.isfinite(x) for x in v): raise ValueError('Input data contains invalid values') return v @validator('horizon') def validate_horizon(cls, v): if v < 1 or v > 365: raise ValueError('Horizon must be between 1 and 365') return v @validator('confidence_level') def validate_confidence(cls, v): if v < 0.5 or v > 0.99: raise ValueError('Confidence level must be between 0.5 and 0.99') return v

8. Testing and Quality Assurance

Load Testing

# locustfile.py - Load testing with Locust from locust import HttpUser, task, between import random class TimeSeriesUser(HttpUser): wait_time = between(1, 3) @task(3) def forecast_request(self): """Generate forecast requests""" data = [random.uniform(50, 150) for _ in range(100)] payload = { "data": data, "horizon": random.randint(1, 48), "include_uncertainty": random.choice([True, False]), "confidence_level": 0.95 } with self.client.post("/forecast", json=payload) as response: if response.status_code != 200: print(f"Request failed: {response.status_code}") @task(1) def health_check(self): """Health check requests""" self.client.get("/health")

Integration Testing

Test Suite:

import pytest import asyncio from httpx import AsyncClient @pytest.mark.asyncio async def test_forecast_endpoint(): """Test forecast endpoint functionality""" async with AsyncClient(app=app, base_url="http://test") as ac: # Test valid request response = await ac.post("/forecast", json={ "data": [100, 105, 110, 108, 112, 115, 118, 120], "horizon": 5, "include_uncertainty": True }) assert response.status_code == 200 data = response.json() assert "forecast" in data assert len(data["forecast"]) == 5 assert "uncertainty" in data @pytest.mark.asyncio async def test_rate_limiting(): """Test rate limiting functionality""" async with AsyncClient(app=app, base_url="http://test") as ac: # Make multiple requests quickly tasks = [] for _ in range(150): # Exceed rate limit task = ac.post("/forecast", json={ "data": [100, 105, 110], "horizon": 3 }) tasks.append(task) responses = await asyncio.gather(*tasks, return_exceptions=True) # Some requests should be rate limited rate_limited = sum(1 for r in responses if isinstance(r, Exception) or r.status_code == 429) assert rate_limited > 0