LLM Deployment Strategies: Стратегии развёртывания LLM

llmdeploymentproductionservinginfrastructure
← Back to Blog

Введение

Развёртывание LLM в production — сложная задача, требующая учёта latency, throughput, cost и reliability. Рассмотрим основные стратегии.


Deployment Options Overview

Deployment Strategy    | Latency    | Cost      | Scalability | Control
-----------------------|------------|-----------|-------------|--------
Cloud API (GPT-4)      | Low        | High      | High        | Low
Cloud GPU (RunPod)     | Medium     | Medium    | Medium      | Medium
Self-hosted           | Low        | High      | High        | High
Edge/On-device         | Very Low   | Low       | Low         | High
Hybrid                 | Variable   | Variable  | Variable    | Variable

Cloud API Deployment

Простая интеграция

from openai import OpenAI

client = OpenAI(api_key="your-api-key")

def generate(text: str) -> str:
    response = client.chat.completions.create(
        model="gpt-4o",
        messages=[{"role": "user", "content": text}],
        temperature=0.7
    )
    return response.choices[0].message.content

Multi-provider для reliability

import litellm

def reliable_generate(text: str) -> str:
    # Primary: GPT-4o
    try:
        response = litellm.completion(
            model="gpt-4o",
            messages=[{"role": "user", "content": text}]
        )
        return response.choices[0].message.content
    except Exception:
        pass
    
    # Fallback: Claude
    try:
        response = litellm.completion(
            model="claude-sonnet-4",
            messages=[{"role": "user", "content": text}]
        )
        return response.choices[0].message.content
    except Exception:
        pass
    
    # Last resort: Gemini
    response = litellm.completion(
        model="gemini-2.0-flash",
        messages=[{"role": "user", "content": text}]
    )
    return response.choices[0].message.content

Cloud GPU Deployment

RunPod / Lambda Labs

# Dockerfile для vLLM
FROM vllm/vllm-serving:latest

RUN pip install transformers

COPY config.json /model/config.json
COPY model.safetensors /model/model.safetensors

ENV VLLM_MODEL=/model
ENV VLLM_PORT=8000
# Запуск на RunPod
runpod template deploy llama-3-70b \
  --gpu A100-80GB \
  --min-gpus 1 \
  --max-gpus 4 \
  --docker-image vllm/vllm-serving:latest

AWS SageMaker

import sagemaker
from sagemaker.hugging_model import HuggingFaceModel

# Регистрация модели
huggingface_model = HuggingFaceModel(
    role="SageMakerRole",
    model_url="https://huggingface.co/meta-llama/Llama-3-70b-instruct",
    env={
        'HF_MODEL_ID': "/opt/ml/model",
        'MAX_BATCH_SIZE': '8',
        'MAX_INPUT_LENGTH': '2048'
    }
)

# Deployment
predictor = huggingface_model.deploy(
    initial_instance_count=2,
    instance_type="ml.g5.12xlarge",
    container_startup_health_check_timeout=300
)

Azure AI

from azure.ai.ml import MLClient
from azure.ai.ml.entities import Endpoint, Deployment

ml_client = MLClient.from_config()

# Create endpoint
endpoint = Endpoint(
    name="llama-3-endpoint",
    description="Llama 3 inference endpoint"
)
ml_client.endpoints.create(endpoint)

# Create deployment
deployment = Deployment(
    name="llama-3-70b-v1",
    model="azureml://models/llama-3-70b-instruct",
    instance_type="Standard_NC48ads_A100_v4",
    instance_count=2,
    app_settings={
        "AZURE_ML_MODEL_REVISION": "v1"
    }
)
ml_client.deployments.create(deployment)

Self-hosted Deployment

vLLM — high-throughput serving

from vllm import LLM, SamplingParams

# Load model
llm = LLM(
    model="meta-llama/Llama-3-70b-instruct",
    tensor_parallel_size=4,  # GPU count
    max_model_len=4096,
    gpu_memory_utilization=0.9
)

# Sampling params
sampling_params = SamplingParams(
    temperature=0.7,
    top_p=0.9,
    max_tokens=1024,
    stop=["\n\n"]
)

# Batch inference
prompts = [
    "Explain quantum computing",
    "Write a Python function",
    "Translate to French"
]

outputs = llm.generate(prompts, sampling_params)

for output in outputs:
    generated_text = output.outputs[0].text
    print(generated_text)

FastAPI + vLLM

from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from vllm import LLM, SamplingParams

app = FastAPI(title="LLM Serving API")

# Initialize model
llm = LLM(
    model="meta-llama/Llama-3-70b-instruct",
    tensor_parallel_size=4
)

class ChatRequest(BaseModel):
    messages: list
    temperature: float = 0.7
    max_tokens: int = 1024
    top_p: float = 0.9

@app.post("/v1/chat/completions")
async def chat(request: ChatRequest):
    try:
        sampling_params = SamplingParams(
            temperature=request.temperature,
            top_p=request.top_p,
            max_tokens=request.max_tokens
        )
        
        outputs = llm.generate(
            [format_messages_for_prompt(request.messages)],
            sampling_params
        )
        
        return {
            "choices": [{
                "message": {
                    "role": "assistant",
                    "content": outputs[0].outputs[0].text
                }
            }]
        }
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))

@app.get("/health")
async def health():
    return {"status": "healthy"}

TGI (Text Generation Inference)

# Docker Compose для TGI
version: '3.8'

services:
  tgi:
    image: ghcr.io/huggingface/text-generation-inference:2.0
    ports:
      - "8080:80"
    volumes:
      - ./models:/models
    environment:
      - HUGGING_FACE_HUB_TOKEN=${HF_TOKEN}
      - MODEL_ID=meta-llama/Llama-3-70b-instruct
      - NUM_SHARD=4
      - MAX_INPUT_TOKENS=4096
      - MAX_TOTAL_TOKENS=8192
    deploy:
      resources:
        reservations:
          devices:
            - driver: nvidia
              count: 4
              capabilities: [gpu]

Edge / On-device Deployment

ONNX Runtime

import onnxruntime as ort
import numpy as np

# Load ONNX model
session = ort.InferenceSession("llama3-onnx/model.onnx")

# Prepare input
input_ids = np.array([[1, 2, 3, 4, 5]])
attention_mask = np.array([[1, 1, 1, 1, 1]])

# Inference
outputs = session.run(
    output_names=["logits", "past_key_values"],
    input_feed={
        "input_ids": input_ids,
        "attention_mask": attention_mask
    }
)

print(outputs[0])

llama.cpp — CPU inference

from llama_cpp import Llama

# Load model
llm = Llama(
    model_path="./models/llama-3-7b-q4_k_m.gguf",
    n_gpu_layers=-1,  # Use GPU
    n_ctx=4096,
    n_threads=8
)

# Generate
output = llm(
    "Explain deep learning:",
    max_tokens=512,
    temperature=0.7,
    stop=["\n\n"]
)

print(output["choices"][0]["text"])

Core ML (Apple Silicon)

import coremltools as ct
from transformers import AutoModelForCausalLM

# Convert to Core ML
model = AutoModelForCausalLM.from_pretrained("microsoft/Phi-3-mini-4k-instruct")
traced_model = ct.models.detection_problems.DetectionOutput(
    score_threshold=0.5,
    iou_threshold=0.5
)

core_ml_model = ct.convert(
    model,
    inputs=[ct.TensorType(shape=(1, 128))],
    convert_to="mlprogram",
    compute_precision=ct.precision.FLOAT16
)

core_ml_model.save("phi3.mlmodel")

Kubernetes Deployment

Helm chart для LLM serving

# charts/llm-serving/values.yaml
replicaCount: 3

image:
  repository: vllm/vllm-serving
  tag: latest

model:
  name: "meta-llama/Llama-3-70b-instruct"
  revision: "main"

resources:
  limits:
    nvidia.com/gpu: 4
  requests:
    memory: "16Gi"
    cpu: "8"

autoscaling:
  enabled: true
  minReplicas: 2
  maxReplicas: 10
  targetGPUUtilization: 75

service:
  type: ClusterIP
  port: 8000

persistence:
  enabled: true
  storageClass: "nvidia-gpu"
  size: 500Gi

K8s deployment manifest

apiVersion: apps/v1
kind: Deployment
metadata:
  name: llm-serving
spec:
  replicas: 3
  selector:
    matchLabels:
      app: llm-serving
  template:
    metadata:
      labels:
        app: llm-serving
    spec:
      containers:
        - name: vllm
          image: vllm/vllm-serving:latest
          ports:
            - containerPort: 8000
          env:
            - name: VLLM_MODEL
              value: "meta-llama/Llama-3-70b-instruct"
            - name: VLLMtensor_parallel_size
              value: "4"
          resources:
            limits:
              nvidia.com/gpu: 4
          volumeMounts:
            - name: model-cache
              mountPath: /model
      volumes:
        - name: model-cache
          persistentVolumeClaim:
            claimName: model-pvc

Auto-scaling strategies

HPA с GPU metrics

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: llm-hpa
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: llm-serving
  minReplicas: 2
  maxReplicas: 10
  metrics:
    - type: Pods
      pods:
        metric:
          name: gpu_utilization
        target:
          type: AverageValue
          averageValue: "75"
    - type: Pods
      pods:
        metric:
          name: requests_per_second
        target:
          type: AverageValue
          averageValue: "100"

KEDA для event-driven scaling

apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
  name: llm-scaledobject
spec:
  scaleTargetRef:
    name: llm-serving
  minReplicaCount: 1
  maxReplicaCount: 20
  triggers:
    - type: kafka
      metadata:
        bootstrapServers: kafka:9092
        topic: llm-requests
        lagThreshold: "100"

Monitoring and Observability

Prometheus metrics

from prometheus_client import Counter, Histogram, generate_latest

# Metrics
request_counter = Counter(
    'llm_requests_total',
    'Total LLM requests',
    ['model', 'status']
)

request_latency = Histogram(
    'llm_request_latency_seconds',
    'LLM request latency'
)

generation_time = Histogram(
    'llm_generation_time_seconds',
    'Time to generate tokens'
)

@request_latency.time()
def generate_with_metrics(prompt):
    request_counter.labels(model="llama-3", status="success").inc()
    return generate(prompt)

Grafana dashboard JSON

{
  "dashboard": {
    "title": "LLM Serving Monitor",
    "panels": [
      {
        "title": "Requests per Second",
        "type": "graph",
        "targets": [
          {
            "expr": "rate(llm_requests_total[5m])"
          }
        ]
      },
      {
        "title": "P50/P95/P99 Latency",
        "type": "graph",
        "targets": [
          {
            "expr": "histogram_quantile(0.5, rate(llm_request_latency_seconds_bucket[5m]))"
          },
          {
            "expr": "histogram_quantile(0.95, rate(llm_request_latency_seconds_bucket[5m]))"
          },
          {
            "expr": "histogram_quantile(0.99, rate(llm_request_latency_seconds_bucket[5m]))"
          }
        ]
      },
      {
        "title": "GPU Utilization",
        "type": "gauge",
        "targets": [
          {
            "expr": "gpu_utilization"
          }
        ]
      }
    ]
  }
}

Cost comparison

Provider           | $/hour    | $/M tokens  | Min cost
-------------------|-----------|-------------|----------
OpenAI GPT-4o      | N/A      | $5.00       | $0
OpenAI GPT-4o-mini | N/A      | $0.15       | $0
RunPod A100        | $1.50     | Self-hosted | $1.50/h
Lambda A100        | $1.20     | Self-hosted | $1.20/h
AWS p4d            | $32.00    | Self-hosted | $32/h
Self-hosted A100   | $0.10*    | Self-hosted | $500+ GPU

*Amortized GPU cost


Best practices

1. Model caching

import os
from pathlib import Path

MODEL_CACHE = os.environ.get("MODEL_CACHE", "/model-cache")

def ensure_model_cached(model_id: str):
    cache_dir = Path(MODEL_CACHE) / model_id
    if not cache_dir.exists():
        from huggingface_hub import snapshot_download
        snapshot_download(
            model_id,
            local_dir=str(cache_dir),
            local_dir_use_symlinks=False
        )
    return str(cache_dir)

2. Health checks

@app.get("/health")
async def health_check():
    try:
        # Check model is loaded
        llm.llm_engine.model_executor.is_model_ready
        
        # Check GPU memory
        free_mem = torch.cuda.mem_get_info()[0]
        if free_mem < 1e9:  # Less than 1GB free
            return {"status": "degraded", "reason": "low memory"}
        
        return {"status": "healthy"}
    except Exception as e:
        return {"status": "unhealthy", "error": str(e)}

3. Graceful shutdown

import signal
import sys

def graceful_shutdown(signum, frame):
    print("Shutting down gracefully...")
    llm.close()
    sys.exit(0)

signal.signal(signal.SIGTERM, graceful_shutdown)
signal.signal(signal.SIGINT, graceful_shutdown)

Итоги

Выбор стратегии deployment зависит от требований к latency, budget и control. Для production используйте self-hosted с auto-scaling, для prototyping — cloud API. Всегда implement monitoring и health checks.