# Autoscaling Intelligent des LLM avec KEDA et vLLM sur OpenShift : Scaler sur le KV-Cache et la Latence ITL

> Guide d'ingénierie complet pour dimensionner dynamiquement vos serveurs LLM en production : pourquoi l'HPA standard échoue et comment configurer KEDA avec les métriques vLLM.

- **Date de publication** : 2026-09-14T08:00:00.000Z

---

Dans les déploiements de modèles de langage (LLMs) en production, l'**autoscaling d'infrastructure** représente un paradoxe économique et opérationnel majeur :
D'un côté, le sur-provisionnement de nœuds GPU (NVIDIA H100/H200, B200 ou L40S) génère des coûts d'infrastructure exorbitants pendant les heures creuses. De l'autre, un sous-dimensionnement entraîne une explosion de la file d'attente de requêtes, une saturation de la mémoire et des dégradations inacceptables du **Time-To-First-Token (TTFT)** et de l'**Inter-Token Latency (ITL)** pour les utilisateurs et agents IA.

Le réflexe classique des équipes d'infrastructure consiste à déployer un **Horizontal Pod Autoscaler (HPA)** Kubernetes basé sur l'utilisation CPU, mémoire RAM ou l'allocation GPU standard. Or, sur les moteurs d'inférence modernes comme **vLLM** ou **TGI**, cette approche est inefficace : la mémoire VRAM est préallouée à 90 % dès l'initialisation du moteur par **PagedAttention**, rendant les métriques d'allocation mémoire plates et aveugles.

Découvrez comment mettre en œuvre un **autoscaling événementiel intelligent avec KEDA (Kubernetes Event-driven Autoscaling)** et **Red Hat OpenShift AI (RHOAI)**, en pilotant le cycle de vie des réplicas d'inférence directement à partir de la pression du **KV-Cache** et de la file d'attente vLLM.

---

### 1. Pourquoi l'Autoscaling Traditionnel Échoue sur les LLM

Pour comprendre l'échec des mécanismes d'autoscaling traditionnels sur les charges de travail GenAI, il faut analyser le comportement interne du moteur vLLM :

```
┌─────────────────────────────────────────────────────────────────────────────┐
│                    Pourquoi l'HPA Standard est Aveugle                      │
├─────────────────────────────────────────────────────────────────────────────┤
│ 1. Mémoire VRAM : vLLM préalloue le KV-Cache au boot (ex. gpu_memory_utilization=0.90). │
│    => Métrique VRAM perçue par Kubernetes : 90 % en permanence (aucun signal de charge). │
│                                                                             │
│ 2. GPU Utilization (%) : Fluctue violemment entre les phases de Prefill et Decode.     │
│    => Crée du "flapping" (oscillations rapides et destructrices de pods).    │
│                                                                             │
│ 3. Trafic Réseau HTTP (RPS) : Toutes les requêtes ne se valent pas !        │
│    => Un prompt de 4000 tokens sature le KV-Cache 20x plus qu'un prompt court. │
└─────────────────────────────────────────────────────────────────────────────┘
```

Pour déclencher un scaling prédictif et fluide, Kubernetes doit surveiller l'état interne de la boucle de batching continu (*Continuous Batching*) via les métriques exposées par l'endpoint `/metrics` de vLLM.

---

### 2. Les Métriques Clés de vLLM pour l'Autoscaling

vLLM expose nativement au format Prometheus des indicateurs de performance haute fidélité. Trois métriques constituent les piliers du scaling automatisé :

1. **`vllm:num_requests_waiting` :** Le nombre de requêtes en attente dans la file d'ordonnancement interne de vLLM, faute de blocs KV-Cache disponibles. Tout chiffre supérieur à 0 indique une saturation immédiate.
2. **`vllm:gpu_cache_usage_factor` :** Le ratio de blocs mémoire du KV-Cache actuellement réservés pour les séquences en cours de décodage (valeur de `0.0` à `1.0`).
3. **`vllm:time_to_first_token_seconds_bucket` / `vllm:time_per_output_token_seconds` :** La latence effective subie par les clients, idéale pour déclencher des alertes de dégradation SLA.

```
                  ┌─────────────────────────────────────────────────────────┐
                  │   Trafic Ingress (Requêtes LLM / Prompts Entrants)     │
                  └────────────────────────────┬────────────────────────────┘
                                               │
                                               ▼
                  ┌─────────────────────────────────────────────────────────┐
                  │     OpenShift AI / KServe InferenceService (vLLM)       │
                  │   ┌─────────────────────────────────────────────────┐   │
                  │   │ Metrics Exporter: /metrics                      │   │
                  │   │ • vllm:gpu_cache_usage_factor                   │   │
                  │   │ • vllm:num_requests_waiting                     │   │
                  │   └────────────────────────┬────────────────────────┘   │
                  └────────────────────────────┼────────────────────────────┘
                                               │ Scrape (10s)
                                               ▼
                  ┌─────────────────────────────────────────────────────────┐
                  │             OpenShift User-Workload Monitoring          │
                  │                   (Prometheus / Thanos)                 │
                  └────────────────────────────┬────────────────────────────┘
                                               │ Query PromQL
                                               ▼
                  ┌─────────────────────────────────────────────────────────┐
                  │                 KEDA Autoscaling Operator               │
                  │  (ScaledObject: évalue seuil cache > 0.85 ou wait > 2)  │
                  └────────────────────────────┬────────────────────────────┘
                                               │ Scale Out / Scale In
                                               ▼
                  ┌─────────────────────────────────────────────────────────┐
                  │      Orchestrateur GPU OpenShift (DRA / Node AutoScaler)│
                  │      Allocation de nouveaux Pods vLLM sur nœuds GPU     │
                  └─────────────────────────────────────────────────────────┘
```

---

### 3. Architecture Déclarative : Configuration KEDA & ScaledObject

Sur OpenShift, KEDA s'installe en un clic via l'OperatorHub. Il s'appuie sur le composant **OpenShift User-Workload Monitoring** pour interroger les métriques Prometheus sans surcharger les pods d'inférence.

#### Étape 1 : Activer le ServiceMonitor pour vLLM

Ce manifeste indique à Prometheus de collecter les métriques du runtime vLLM toutes les 10 secondes :

```yaml
apiVersion: monitoring.coreos.com/v1
kind: ServiceMonitor
metadata:
  name: vllm-metrics-monitor
  namespace: ai-inference-prod
  labels:
    app.kubernetes.io/name: vllm-granite-3b
spec:
  selector:
    matchLabels:
      serving.kserve.io/inferenceservice: granite-3b-instruct
  endpoints:
    - port: metrics
      path: /metrics
      interval: 10s
      scrapeTimeout: 5s
```

#### Étape 2 : Définir l'Authentification KEDA avec Thanos Querier

Pour sécuriser l'accès de KEDA à l'API de métriques interne d'OpenShift :

```yaml
apiVersion: keda.sh/v1alpha1
kind: TriggerAuthentication
metadata:
  name: keda-trigger-auth-prometheus
  namespace: ai-inference-prod
spec:
  secretTargetRef:
    - parameter: bearerToken
      name: thanos-querier-token
      key: token
    - parameter: ca
      name: thanos-querier-ca
      key: ca.crt
```

#### Étape 3 : Déployer le ScaledObject KEDA Multi-Critères

Le `ScaledObject` suivant ajuste le nombre de réplicas vLLM entre 1 et 8 pods selon deux règles combinées :
* Si **`vllm:num_requests_waiting`** dépasse **2** requêtes en attente.
* Si le **`vllm:gpu_cache_usage_factor`** moyen dépasse **0.80** (80 % du KV-Cache alloué).

```yaml
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
  name: vllm-granite-scaledobject
  namespace: ai-inference-prod
spec:
  scaleTargetRef:
    apiVersion: serving.kserve.io/v1beta1
    kind: InferenceService
    name: granite-3b-instruct
  minReplicaCount: 1
  maxReplicaCount: 8
  cooldownPeriod: 300   # Évite le scale-in trop rapide (cooldown de 5 min)
  pollingInterval: 15  # Fréquence d'évaluation
  advanced:
    horizontalPodAutoscalerConfig:
      behavior:
        scaleUp:
          stabilizationWindowSeconds: 0
          policies:
            - type: Percent
              value: 100
              periodSeconds: 15
        scaleDown:
          stabilizationWindowSeconds: 300
          policies:
            - type: Pods
              value: 1
              periodSeconds: 60
  triggers:
    - type: prometheus
      metadata:
        serverAddress: https://thanos-querier.openshift-monitoring.svc.cluster.local:9091
        metricName: vllm_waiting_requests_trigger
        threshold: '2'
        query: sum(vllm:num_requests_waiting{namespace="ai-inference-prod", model_name="granite-3b-instruct"})
        authModes: bearer
      authenticationRef:
        name: keda-trigger-auth-prometheus

    - type: prometheus
      metadata:
        serverAddress: https://thanos-querier.openshift-monitoring.svc.cluster.local:9091
        metricName: vllm_kv_cache_pressure_trigger
        threshold: '0.80'
        query: avg(vllm:gpu_cache_usage_factor{namespace="ai-inference-prod", model_name="granite-3b-instruct"})
        authModes: bearer
      authenticationRef:
        name: keda-trigger-auth-prometheus
```

---

### 4. Gérer le Défi du "Cold Start" GPU sur OpenShift

Si KEDA permet de déclencher l'instanciation de nouveaux pods en quelques millisecondes, le temps de démarrage effectif d'un pod vLLM (*Cold Start*) peut atteindre 45 à 120 secondes en raison :
1. De l'allocation dynamique du nœud GPU via le MachineSet OpenShift / Cluster Autoscaler.
2. Du téléchargement des poids du modèle (10 à 70 Go) depuis le stockage objet S3/Ceph.
3. De l'initialisation des kernels CUDA et du graphe d'exécution PyTorch.

#### Bonnes Pratiques d'Ingénierie pour Réduire le Cold Start :

* **Shared Model Cache via ReadWriteMany (CephFS / Lustre / ODF) :** Montez un cache local en lecture partagée (`PVC ReadWriteMany`) pour que les nouveaux pods accèdent instantanément aux poids sans téléchargement réseau.
* **Warm Pool & Pre-warmed Nodes :** Conservez au moins un réplica actif (`minReplicaCount: 1`) et des nœuds GPU prêts à l'emploi avec les images de conteneurs pré-téléchargées (*cached images*).
* **Politique de Scale-In Conservatrice :** Utilisez un `cooldownPeriod` d'au moins 300 secondes (5 minutes) pour empêcher de détruire des pods GPU qui ont mis du temps à chauffer alors qu'une nouvelle rafale de trafic peut survenir.

---

### 5. Comparatif des Stratégies d'Autoscaling pour les LLM

| Critère d'Autoscaling | HPA Standard (CPU / RAM) | HPA Custom (GPU Memory Util) | KEDA + Métriques vLLM (KV-Cache & Waiting) |
| :--- | :--- | :--- | :--- |
| **Précision du Signal** | Nulle (non corrélée à l'inférence) | Trompeuse (VRAM préallouée à 90%) | **Maximale (pression réelle de calcul & file d'attente)** |
| **Réactivité face aux Bursts** | Très lente | Inefficace | **Instantanée (déclenchement dès `requests_waiting > 2`)** |
| **Gestion des Prompts Longs** | Aveugle | Aveugle | **Native (mesure directe de l'occupation du KV-Cache)** |
| **Optimisation des Coûts GPU** | Faible (risque de crash) | Faible (sur-provisionnement permanent) | **Optimale (scale-in contrôlé et scale-out chirurgical)** |
| **Intégration OpenShift** | Basique | Nécessite NVIDIA GPU Operator | **Complète via KEDA Operator + OpenShift Monitoring** |

---

### 6. Guide d'Implémentation pour les Équipes Plateforme & LLMOps

Pour industrialiser l'autoscaling de vos clusters d'inférence en production :

1. **Exposez systématiquement les métriques vLLM :** Vérifiez que le flag `--disable-log-stats` n'est pas activé et que le port de métriques est accessible par votre ServiceMonitor.
2. **Combinez toujours deux déclencheurs :** Utilisez conjointement la pression de cache (`gpu_cache_usage_factor`) pour absorber les prompts volumineux et la file d'attente (`num_requests_waiting`) pour les rafales d'utilisateurs simultanés.
3. **Paramétrez des alertes sur le temps de réponse (TTFT) :** Si la latence médiane au premier token dépasse 800 ms, ajustez les seuils de déclenchement du ScaledObject pour être plus agressif sur le scale-out.
4. **Automatisez le déploiement via GitOps (ArgoCD) :** Déclarez vos `ScaledObject` et `ServiceMonitor` dans vos dépôts d'infrastructure pour un versioning rigoureux.

---

### Maîtrisez l'Ingénierie des Systèmes d'Inférence IA avec Aperta Scientia

La mise en production de modèles de fondation à grande échelle exige une double expertise : la maîtrise de l'orchestration Cloud-Native Kubernetes et l'optimisation fine des moteurs d'inférence GenAI.

Aperta Scientia prépare vos ingénieurs aux architectures d'IA les plus exigeantes :

* **[AS300 — AI Platform Engineer](/curriculums/as300)** : Le cursus d'élite de 3 mois pour maîtriser le déploiement d'**OpenShift AI (RHOAI)**, l'optimisation des moteurs **vLLM**, l'autoscaling avec **KEDA**, le serving déclaratif avec **KServe**, les bases vectorielles **Milvus**, et la gouvernance avec **TrustyAI**.
* **[AS200 — New DevOps Platform Engineer](/curriculums/as200)** : Le cursus fondamental pour concevoir des clusters **Red Hat OpenShift** résilients, automatiser en **GitOps (ArgoCD)**, configurer l'observabilité **Prometheus / Thanos**, et orchestrer le multi-cluster avec **RHACM**.

---

*Source : https://aperta.io/fr/radar/2026-09-14-autoscaling-llm-keda-vllm-kv-cache-openshift/ — Aperta Scientia Tech Radar*
