Configuring Load Balancing Between GPU Instances
Imagine: you launch four GPU instances with vLLM, but 80% of requests go to the first server. The rest sit idle while users complain about timeouts. The reason — load balancing isn't configured. For LLMs this is critical: one long request of 4000 tokens can block a server for a minute, while the others remain idle. As a result, p99 latency skyrockets to 30 seconds, and GPU utilization drops to 25%. A typical cluster of 4 GPUs without balancing loses up to 50% throughput.
Proper load balancing reduces GPU infrastructure costs by up to 40% through uniform utilization. Average GPU-hour savings after implementation — 30% under the same load. P99 latency drops 1.7x compared to Round Robin. If you face similar issues, contact us — we will select the optimal configuration for your scenario.
Comparison of Balancing Algorithms for LLMs
| Algorithm |
Principle |
Suitability for LLM |
Drawbacks |
| Round Robin |
In turn |
Low |
Ignores load: long request overwhelms server |
| Least Connections |
Minimum active connections |
Medium |
Does not consider request length (tokens) |
| Least Pending Tokens |
Minimum tokens in queue |
High |
Requires metrics collection from each backend |
| Custom (GPU metrics) |
Based on VRAM/GPU load |
Medium |
Depends on monitoring, harder to implement |
Least Pending Tokens is the optimal choice for services with heterogeneous load. It uses Prometheus metrics from vLLM (vllm:num_requests_waiting) to select the least loaded instance. Our experience shows that Least Pending Tokens outperforms Round Robin by 1.7x in p99 latency.
Example: Nginx with Health Checks and Custom Balancer
Below is a basic Nginx configuration for an upstream of four vLLM servers, with active health checks and timeouts for streaming.
upstream vllm_cluster {
least_conn;
server 10.0.1.10:8000 max_fails=3 fail_timeout=30s weight=1;
server 10.0.1.11:8000 max_fails=3 fail_timeout=30s weight=1;
server 10.0.1.12:8000 max_fails=3 fail_timeout=30s weight=1;
server 10.0.1.13:8000 max_fails=3 fail_timeout=30s weight=1;
keepalive 100;
keepalive_requests 1000;
keepalive_timeout 60s;
}
server {
listen 443 ssl http2;
server_name llm-api.internal;
location /v1/ {
proxy_pass http://vllm_cluster;
proxy_http_version 1.1;
proxy_set_header Connection "";
# Timeout для длинных streaming ответов
proxy_read_timeout 600s;
proxy_send_timeout 600s;
proxy_connect_timeout 5s;
# Streaming: отключаем буферизацию
proxy_buffering off;
proxy_cache off;
chunked_transfer_encoding on;
# Circuit breaker
proxy_next_upstream error timeout http_500 http_502 http_503;
proxy_next_upstream_tries 2;
proxy_next_upstream_timeout 10s;
}
location /health {
proxy_pass http://vllm_cluster/health;
}
}
If a more intelligent backend selection is needed, we write a custom balancer in FastAPI that polls metrics in real time.
from fastapi import FastAPI, Request
import httpx
import asyncio
class LLMLeastPendingBalancer:
def __init__(self, backends: list[str]):
self.backends = {url: {"pending": 0, "healthy": True} for url in backends}
self.client = httpx.AsyncClient(timeout=300)
async def get_backend(self) -> str:
"""Выбираем backend с наименьшим числом pending токенов."""
healthy = {url: info for url, info in self.backends.items() if info["healthy"]}
if not healthy:
raise RuntimeError("No healthy backends")
metrics = await self._fetch_metrics(list(healthy.keys()))
best = min(metrics.items(), key=lambda x: x[1].get("vllm_num_requests_waiting", 0))
return best[0]
async def _fetch_metrics(self, backends: list[str]) -> dict:
tasks = [self._get_backend_queue(url) for url in backends]
results = await asyncio.gather(*tasks, return_exceptions=True)
return {url: result for url, result in zip(backends, results)
if not isinstance(result, Exception)}
async def _get_backend_queue(self, url: str) -> dict:
response = await self.client.get(f"{url}/metrics")
for line in response.text.split('\n'):
if line.startswith('vllm:num_requests_waiting'):
return {"vllm_num_requests_waiting": float(line.split()[-1])}
return {"vllm_num_requests_waiting": 0}
async def forward(self, request: Request) -> httpx.Response:
backend = await self.get_backend()
url = f"{backend}{request.url.path}"
self.backends[backend]["pending"] += 1
try:
return await self.client.request(
method=request.method,
url=url,
content=await request.body(),
headers=dict(request.headers)
)
finally:
self.backends[backend]["pending"] -= 1
app = FastAPI()
balancer = LLMLeastPendingBalancer(["http://gpu1:8000", "http://gpu2:8000", "http://gpu3:8000"])
@app.api_route("/v1/{path:path}", methods=["GET", "POST"])
async def proxy(path: str, request: Request):
return await balancer.forward(request)
Why Sticky Sessions Are Critical for LLMs?
If your LLM uses KV cache prefix reuse (e.g., a common system prompt in a chatbot), without sticky sessions each request may land on a different server — the cache becomes useless. The solution — consistent hashing by prefix and sticky sessions.
def get_backend_by_prefix(prompt: str, backends: list[str]) -> str:
prefix_hash = hashlib.md5(prompt[:256].encode()).hexdigest()
idx = int(prefix_hash, 16) % len(backends)
return backends[idx]
Applying sticky sessions increases cache hit ratio by 30-50%, reducing latency by 20%. Without them, a typical service with a shared system prompt loses up to 60% of cache efficiency.
Typical Mistakes in GPU Balancing
- Using Round Robin for LLMs — leads to uneven load.
- Lack of health checks — traffic goes to a failed server.
- Ignoring streaming timeouts — clients get 502 errors during long generations.
- Incorrect proxy_buffering configuration — increases latency.
- No GPU failover — all traffic is lost when one instance fails.
How to Set Up Health Checks for GPU Instances?
| Method |
Tool |
Complexity |
Features |
| Passive (nginx) |
max_fails, fail_timeout |
Low |
No additional setup required |
| Active (nginx plus) |
health_check |
High |
Accurately determines state, but paid |
| Custom |
HTTP /metrics |
Medium |
Works only with vLLM and compatible engines |
What's Included in Turnkey Load Balancing Configuration
- Analysis of load scenarios (number of requests, token length, latency requirements).
- Selection of algorithm and stack (Nginx, custom balancer, Envoy).
- Configuration of health checks, circuit breaker, timeouts.
- Implementation of sticky sessions (if KV cache is needed).
- Integration with monitoring (Prometheus + Grafana dashboards).
- Operational documentation and Incident Playbook.
Process of Work
- Analytics — collection of current infrastructure metrics, request profiling.
- Design — balancing architecture, algorithm selection, failover scheme.
- Implementation — deploying configs or writing custom module.
- Testing — load testing with p50/p99/p999 latency measurements.
- Deployment — phased rollout with canary release.
Timelines and Cost
Basic configuration on Nginx — from 1 day. Custom balancer with Least Pending Tokens support — from 3 to 5 days. Cost is calculated individually, based on infrastructure complexity and fault tolerance requirements. Guaranteed service stability after implementation — our engineers with 10+ years of experience in ML infrastructure deliver turnkey work. Typical ROI after implementation — 6 months.
Load Distribution Monitoring
After implementation, track: RPS distribution (should be uniform ±20%), queue depth on each backend, error rate, p99 latency. Set an alert: "one backend receives >80% traffic" — a sign of failure. With proper configuration, p99 latency drops to 5 seconds, and GPU utilization increases to 95%. Cache hit ratio reaches 70% with sticky sessions. We also train your team to work with dashboards.
Contact us for a preliminary audit — we will assess your current configuration and propose the optimal solution. Order a consultation — we will help you choose a balancing strategy for your GPU cluster.
MLOps: Infrastructure for Training, Deploying, and Monitoring ML Models
The model is trained, metrics — F1 0.94 on validation. Three months later in production, quality drops by 12%. No one knows when — there is no monitoring. It's impossible to retrain quickly — the training script is in a Jupyter notebook of a data scientist who has already left. Data for retraining is collected manually from three disparate systems. About half of the projects come to us with this pain. We build a turnkey MLOps platform: from experiment tracking to automatic deployment and data drift monitoring. We will assess your infrastructure in 1–2 weeks, and in 4–6 weeks you will get a basic MLOps core running in production. Our team has 10+ years of experience in ML infrastructure, over 50 implementations.
How does MLOps infrastructure benefit your ML projects?
Experiment Tracking and Reproducibility
Without tracking, an ML project turns into chaos: it's unclear which checkpoint is better, which hyperparameters were used, which dataset. Reproducing a result a month later is a quest.
Why is experiment tracking the foundation of reproducibility?
MLflow is an open source standard for tracking. It logs parameters, metrics, artifacts (models, graphs), and code. MLflow Model Registry is a centralized model storage with versioning and lifecycle stages (Staging → Production → Archived). Deployment via MLflow Serving or integration with external systems.
Typical initialization in code:
import mlflow
mlflow.set_experiment("fraud-detection-v2")
with mlflow.start_run():
mlflow.log_params({"learning_rate": 3e-4, "batch_size": 64, "epochs": 10})
mlflow.log_metric("val_f1", val_f1, step=epoch)
mlflow.pytorch.log_model(model, "model")
This is the minimum. In production, we add logging of system metrics (GPU utilization, memory), dataset (hash, version), code (git commit hash). Weights & Biases — richer UI, collaboration features, sweep for hyperparameter optimization. MLflow — for on-premise deployment without external dependencies.
DVC (Data Version Control) — versioning of data and models on top of git. Data is stored in S3/GCS/Azure Blob, only metadata (hashes) in git. dvc repro reproduces the entire pipeline from raw data to metrics.
To ensure reproducibility of training, fix random seeds (torch.manual_seed, numpy.random.seed, random.seed) and record them in experiment metadata. Without this, debugging irregular results is painful. Log the dataset version (DVC hash) and git commit — then any experiment can be reproduced down to the byte.
Pipeline Orchestration: Kubeflow, Airflow, Prefect
A pipeline orchestrator becomes necessary when: A 100-line training script in cron is fine for simple tasks. But as soon as you have a multi-step pipeline (data loading → preprocessing → feature engineering → training → validation → deployment if quality above threshold), you need an orchestrator with retry logic, visualization, and alerts.
Kubeflow — Kubernetes-native orchestrator for ML (see Kubeflow). Each step is a Docker container. Supports parallel steps, conditional branches, artifacts between steps. Integrates with Katib (AutoML), KServe (serving), Feast (feature store).
Apache Airflow — more general DAG orchestrator. Wide ecosystem of operators (S3, Spark, DBT, Kubernetes). Easier to deploy if Airflow already exists in the company.
Prefect / Metaflow — less boilerplate. Prefect 2.x with @flow and @task decorators — quick start for small teams.
Typical training pipeline architecture on Kubeflow:
- Data ingestion component — fetches data from S3/DB, validates schema via Great Expectations
- Preprocessing component — transformations, normalization, train/val/test split
- Training component — training on GPU, logging to MLflow
- Evaluation component — metric calculation, comparison with baseline in Model Registry
- Conditional deployment — deploy only if new model is better than current by >2% F1
Each component is a separate Docker image. Pipeline is versioned in git. Scheduled run (retraining once a week on new data) or manual.
Model Registry and Lifecycle Management
Model Registry is not just a checkpoint store. It is a centralized system that knows:
- Which model is currently in production (and with what metrics)
- History of all versions with training parameters
- Metadata: dataset, git commit, validation results
- Lifecycle stage: None → Staging → Production → Archived
MLflow Model Registry — standard. For enterprise — Vertex AI Model Registry (GCP), SageMaker Model Registry (AWS), Azure ML Model Registry.
Model promotion through stages: automatically move model to Staging after successful eval, then manual or automatic (during A/B test) promotion to Production. Rollback — switch to previous Production version in seconds.
Serving: From FastAPI to Triton Inference Server
Simple case. FastAPI + PyTorch/ONNX on one server — 80% of production ML deployments are exactly that. Sufficient for most tasks with load up to 100 req/s.
from fastapi import FastAPI
import onnxruntime as ort
app = FastAPI()
session = ort.InferenceSession("model.onnx", providers=["CUDAExecutionProvider"])
@app.post("/predict")
async def predict(request: PredictRequest):
inputs = preprocess(request.text)
outputs = session.run(None, {"input_ids": inputs})
return {"label": postprocess(outputs)}
Triton Inference Server — production standard for high loads (500+ req/s). Dynamic batching, concurrent model execution, model ensemble. Supports TensorRT, ONNX, PyTorch TorchScript, TensorFlow SavedModel.
KServe — Kubernetes-native ML serving with autoscaling, canary deployments, A/B testing out of the box. Scale-to-zero for inactive models — savings on infrastructure up to 40% annually for a project with 10 models.
Monitoring: Data Drift, Model Drift, Infrastructure Metrics
Monitoring — what is usually done last and regretted first. Three levels.
Infrastructure monitoring. Latency (P50/P95/P99), throughput (req/s), error rate (4xx, 5xx), GPU/CPU utilization. Prometheus + Grafana — standard. Alert when P99 latency > threshold or error rate > 1%.
Data drift monitoring. Distribution of input data changes over time. Detect via PSI (Population Stability Index) for numerical features: PSI > 0.2 — strong drift. Chi-squared test for categorical, Kolmogorov-Smirnov test for continuous. Evidently AI — open source library with ready-made drift tests.
Model drift monitoring. If ground truth is delayed (e.g., we know conversion after a week) — monitor real metrics. If not — surrogate metrics: distribution of prediction scores, proportion of confident predictions.
Alerting. Three levels: INFO (minor drift, log it), WARNING (significant, notify team), CRITICAL (quality dropped below threshold — automatic switch to fallback model).
Why is data drift monitoring important?
Without it, you learn about model degradation only from user complaints or ringing SLA. A drift alert allows you to retrain the model in advance, before errors start causing losses. In one of our projects, PSI monitoring detected drift 2 days after a data source change — this saved the campaign.
| Common Mistake |
Consequences |
Solution |
| Lack of data versioning |
Irreproducible experiments |
Implement DVC or similar |
| Manual model deployment |
Human errors, slow rollback |
Automate CI/CD pipeline |
| Monitoring only by business metrics |
Late drift detection |
Add data drift monitoring (PSI, KS) |
Feature Store
Feature Store solves the training-serving skew problem. If preprocessing during training and inference is implemented in two different places — divergence is inevitable.
A Feature Store is needed when:
- Several models use the same features
- Features are computed from streaming data (real-time)
- Large team with different people on feature engineering and model training
Feast — open source Feature Store. Offline store (S3 + Parquet) for training, online store (Redis, DynamoDB) for low-latency inference. Feature definitions as code, materialization job syncs offline → online.
Tecton (commercial), Vertex AI Feature Store (GCP), SageMaker Feature Store (AWS) — managed options with less ops overhead.
CI/CD for ML
ML CI/CD is regular CI/CD plus specific ML steps.
ML-specific checks in CI:
- Reproducibility check: run training with a fixed seed, result must match
- Data validation: Great Expectations or Pandera on schema/distribution checks
- Model performance check: automatic eval on holdout, block merge if degradation > threshold
- Latency regression test: inference must meet SLA
GitOps for deployment. Merge to main → CI triggers training → eval → if passes → automatic deployment to Staging → smoke tests → manual promotion to Production or automatic upon successful canary.
Tools: GitHub Actions / GitLab CI for CI, ArgoCD for GitOps deployment on Kubernetes.
What's Included in MLOps Platform Development
We provide a full cycle of work, documentation, and team training.
| Stage |
Duration |
Result |
| Audit of current infrastructure and data pipeline |
1–2 weeks |
Roadmap with risks and priorities |
| Core deployment: MLflow, orchestrator, serving |
4–6 weeks |
Working training and deployment pipeline |
| Feature Store and CI/CD for ML |
2–3 months |
Feature Store, automatic retrain and deployment |
| Drift monitoring and alerting |
3–4 weeks |
Dashboards, alerts, incident playbook |
| Team training and documentation |
1–2 weeks |
Runbook, policies, training for data scientists |
Total time from audit to full MLOps platform: 3–5 months. Also possible phased launch: basic level (tracking + serving) in 4–6 weeks.
Cost is calculated individually based on data volume, number of models, and infrastructure requirements. Order an MLOps infrastructure audit — get a roadmap in 1–2 weeks. Contact us for a project assessment — we will send a preliminary estimate within 2 business days.
Note: warranty on architectural solutions — 12 months. We provide integration certificates with major cloud providers (AWS, GCP, Azure). During our work, we have not lost a single client after the first implementation — the experience of 50+ successful MLOps projects speaks for itself. Get a consultation on building an MLOps platform today.