← Files Domino Data LabARCHIVED FILE

SCALING.md

7.7 KB · Nov 14, 2026 · 01:02 UTC

↓ Download file

# Scaling Model Endpoints

This guide covers scaling strategies for Domino model endpoints, including GPU inference with NVIDIA Triton.

## Scaling Strategies

### Horizontal Scaling (Replicas)

Increase the number of model instances:

```
Traffic → Load Balancer → [Model Pod 1]
                       → [Model Pod 2]
                       → [Model Pod 3]
```

**When to use:**
- High request volume
- CPU-bound models
- Need high availability

### Vertical Scaling (Resources)

Increase CPU/memory per instance:

**When to use:**
- Large models
- Memory-intensive operations
- GPU requirements

## Configuring Replicas

### Via Domino UI

1. Go to Model API settings
2. Find **Scaling** section
3. Set **Minimum Replicas** and **Maximum Replicas**
4. Configure **Target CPU Utilization** for auto-scaling

### Replica Guidelines

| Traffic | Min Replicas | Max Replicas |
|---------|-------------|--------------|
| Low (< 10 RPS) | 1 | 2 |
| Medium (10-100 RPS) | 2 | 5 |
| High (> 100 RPS) | 3 | 10+ |

## Auto-Scaling

### CPU-Based Auto-Scaling

Domino automatically scales based on CPU utilization:

```yaml
autoscaling:
  minReplicas: 2
  maxReplicas: 10
  targetCPUUtilizationPercentage: 70
```

**How it works:**
1. Monitor average CPU across pods
2. If CPU > 70%, add pods
3. If CPU < 70%, remove pods (with cooldown)

### Memory-Based Considerations

- Memory doesn't trigger auto-scaling by default
- Set appropriate memory limits to prevent OOM
- Monitor memory for capacity planning

## Hardware Tiers

### Selecting Hardware

| Tier | Use Case |
|------|----------|
| small | Simple models, low traffic |
| medium | Standard ML models |
| large | Large models, high throughput |
| gpu-small | Small deep learning models |
| gpu-large | Large neural networks, LLMs |

### GPU Considerations

```python
# Check for GPU availability in your model
import torch

def load_model():
    device = 'cuda' if torch.cuda.is_available() else 'cpu'
    print(f"Using device: {device}")
    model = MyModel().to(device)
    return model, device
```

## GPU Inference with NVIDIA Triton

### What is Triton?

NVIDIA Triton Inference Server provides:
- High-performance inference
- Multiple model support
- Dynamic batching
- Model versioning
- Metrics and monitoring

### Triton Model Repository Structure

```
model_repository/
└── my_model/
    ├── config.pbtxt
    └── 1/
        └── model.onnx
```

### config.pbtxt Example

```protobuf
name: "my_model"
platform: "onnxruntime_onnx"
max_batch_size: 8
input [
  {
    name: "input"
    data_type: TYPE_FP32
    dims: [ 224, 224, 3 ]
  }
]
output [
  {
    name: "output"
    data_type: TYPE_FP32
    dims: [ 1000 ]
  }
]
instance_group [
  {
    kind: KIND_GPU
    count: 1
  }
]
dynamic_batching {
  preferred_batch_size: [ 4, 8 ]
  max_queue_delay_microseconds: 100
}
```

### Converting Models for Triton

**PyTorch to ONNX:**
```python
import torch

model = MyModel()
model.eval()

dummy_input = torch.randn(1, 3, 224, 224)
torch.onnx.export(
    model,
    dummy_input,
    "model.onnx",
    input_names=['input'],
    output_names=['output'],
    dynamic_axes={'input': {0: 'batch_size'}}
)
```

**TensorFlow to SavedModel:**
```python
import tensorflow as tf

model = tf.keras.models.load_model('my_model.h5')
model.save('model_repository/my_model/1/model.savedmodel')
```

### Dynamic Batching

Group multiple requests for efficient GPU utilization:

```protobuf
dynamic_batching {
  preferred_batch_size: [ 4, 8, 16 ]
  max_queue_delay_microseconds: 100
}
```

**Benefits:**
- Higher GPU utilization
- Better throughput
- Lower cost per prediction

## Optimizing Model Performance

### Model Optimization Techniques

| Technique | Speedup | Accuracy Impact |
|-----------|---------|-----------------|
| Quantization (INT8) | 2-4x | Minimal |
| Pruning | 2-3x | Varies |
| Knowledge Distillation | 2-10x | Some |
| ONNX Runtime | 1.5-2x | None |

### Quantization Example

```python
import torch

# Dynamic quantization
quantized_model = torch.quantization.quantize_dynamic(
    model,
    {torch.nn.Linear},
    dtype=torch.qint8
)
```

### ONNX Optimization

```python
import onnx
from onnxruntime.transformers import optimizer

# Optimize ONNX model
optimized_model = optimizer.optimize_model(
    "model.onnx",
    model_type='bert',
    num_heads=12,
    hidden_size=768
)
optimized_model.save_model_to_file("model_optimized.onnx")
```

## Caching Strategies

### In-Memory Caching

```python
from functools import lru_cache
import hashlib

@lru_cache(maxsize=1000)
def cached_predict(features_hash):
    """Cache predictions for repeated inputs."""
    features = decode_features(features_hash)
    return model.predict(features)

def predict(data):
    features = data['features']
    features_hash = hashlib.md5(str(features).encode()).hexdigest()
    return cached_predict(features_hash)
```

### External Caching (Redis)

```python
import redis
import json
import hashlib

redis_client = redis.Redis(host='redis-host', port=6379)
CACHE_TTL = 3600  # 1 hour

def predict(data):
    features = data['features']
    cache_key = f"pred:{hashlib.md5(str(features).encode()).hexdigest()}"

    # Check cache
    cached = redis_client.get(cache_key)
    if cached:
        return json.loads(cached)

    # Compute prediction
    result = model.predict(features)

    # Store in cache
    redis_client.setex(cache_key, CACHE_TTL, json.dumps(result))

    return result
```

## Load Testing

### Using Locust

```python
# locustfile.py
from locust import HttpUser, task, between

class ModelUser(HttpUser):
    wait_time = between(0.1, 0.5)

    @task
    def predict(self):
        self.client.post(
            "/models/abc123/latest/model",
            json={"data": {"features": [1.0, 2.0, 3.0]}},
            headers={
                "Authorization": "Bearer TOKEN",
                "Content-Type": "application/json"
            }
        )
```

Run test:
```bash
locust -f locustfile.py --host=https://your-domino.com
```

### Performance Targets

| Metric | Development | Production |
|--------|-------------|------------|
| Latency P50 | < 200ms | < 100ms |
| Latency P99 | < 1s | < 500ms |
| Throughput | 10 RPS | 100+ RPS |
| Error Rate | < 5% | < 0.1% |

## Cost Optimization

### Right-Sizing Resources

1. Start with small hardware tier
2. Monitor CPU/memory utilization
3. Scale up only if needed
4. Use auto-scaling for variable traffic

### Spot/Preemptible Instances

For non-critical workloads:
- Lower cost
- May be interrupted
- Good for batch processing

### Idle Scaling

```yaml
# Scale to zero when not in use
autoscaling:
  minReplicas: 0
  maxReplicas: 5
  scaleDownDelaySeconds: 300  # 5 minutes idle before scale down
```

## Best Practices

### 1. Warm-Up Requests

```python
def warmup():
    """Send warm-up requests on startup."""
    sample_input = {"features": [0.0] * 10}
    for _ in range(5):
        predict(sample_input)
    print("Model warmed up")
```

### 2. Connection Pooling

```python
import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry

session = requests.Session()
retry = Retry(total=3, backoff_factor=0.1)
adapter = HTTPAdapter(pool_connections=10, pool_maxsize=10, max_retries=retry)
session.mount('http://', adapter)
session.mount('https://', adapter)
```

### 3. Async Processing

```python
import asyncio
import aiohttp

async def predict_async(session, data):
    async with session.post(url, json=data, headers=headers) as response:
        return await response.json()

async def batch_predict(items):
    async with aiohttp.ClientSession() as session:
        tasks = [predict_async(session, item) for item in items]
        return await asyncio.gather(*tasks)
```

### 4. Monitor and Iterate

1. Establish baseline metrics
2. Test under load
3. Identify bottlenecks
4. Optimize and repeat

SHA-256: 1c861c344277c1e93b0c71956038441e0bc8483bcd46cb3d039990f88e2ef06c