

 **Aidez à améliorer cette page** 

Les traductions sont fournies par des outils de traduction automatique. En cas de conflit entre le contenu d'une traduction et celui de la version originale en anglais, la version anglaise prévaudra.

Pour contribuer à ce guide de l'utilisateur, cliquez sur le GitHub ** lien ** Modifier cette page qui se trouve dans le volet droit de chaque page.

Les traductions sont fournies par des outils de traduction automatique. En cas de conflit entre le contenu d'une traduction et celui de la version originale en anglais, la version anglaise prévaudra.

# Identifier les seuils des métriques de mise à l'échelle pour l'inférence de l'IA
<a name="ml-inference-autoscaling-thresholds"></a>

**Astuce**  
 [Inscrivez-vous ](https://events.eksworkshop.com/workshops/genai/) aux prochains AI/ML ateliers Amazon EKS.

Cette section explique comment tester la charge d'un serveur d'inférence vLLM sur Amazon EKS afin d'identifier le moment où il devient saturé. Utilisez les résultats des tests de charge pour configurer la profondeur de la file d'attente et les seuils de latence pour la mise à l'échelle automatique horizontale dans la [Inférence basée sur l'IA à l'échelle automatique avec HPA et KEDA](ml-inference-autoscaling-hpa-keda.md) section suivante.

La procédure pas à pas utilise les outils suivants :
+  [k6 ](https://k6.io/) (Grafana k6) est un outil de test de charge open source, exécuté en tant qu'image du `grafana/k6` conteneur, qui envoie des demandes d'inférence à la réplique à des taux de demandes contrôlés.
+  [Ministral-3-8B-Instruct-2512 modèle ](https://huggingface.co/mistralai/Ministral-3-8B-Instruct-2512) sur une `g6e.4xlarge` instance. Vous pouvez appliquer la même méthodologie à n'importe quel modèle open source et à n'importe quel type de GPU.

## Conditions préalables
<a name="_prerequisites"></a>

Cette section s'appuie sur deux sections précédentes de la documentation. Complétez les deux avant de commencer :
+  **[Cluster Setup ](ml-cluster-setup.md) ** fournit un cluster EKS avec un GPU NodePool, la pile de [ surveillance ](ml-cluster-setup-cli.md#cluster-setup-cli-monitoring) kube-prometheus-stack (Prometheus et Grafana) et un compartiment Amazon S3 pour les pondérations des modèles. [Le modèle pèse le godet S3](ml-cluster-setup-cli.md#cluster-setup-cli-model-bucket)
+  **[Load & Serve Models ](ml-inference-load-serve-model.md) ** déploie le serveur d'inférence vLLM et crée le. [`vllm-inference-app` ServiceMonitor ](ml-inference-load-serve-model.md#ml-inference-load-serve-model-monitoring)

Confirmez chaque élément ci-dessous avant de commencer, et complétez d'abord la section liée en cas d'échec de la vérification.

Si vous avez ouvert un nouveau terminal, définissez le nom du cluster et la région que vous avez utilisés précédemment :

```
export CLUSTER_NAME=ai-eks-docs
export AWS_REGION=us-east-2
```

Capturez le nom du compartiment de modèle Amazon S3 qui a été créé lors des [ étapes de configuration du ](ml-cluster-setup.md) cluster. Le générateur de charge transmet cette valeur à VLLm.

```
MODEL_BUCKET=$(aws s3api list-buckets \
  --query "Buckets[?starts_with(Name, '${CLUSTER_NAME}-models-')].Name | [0]" \
  --output text)
echo "Model bucket: ${MODEL_BUCKET}"
```

### Vérifiez que la pile de surveillance est en cours d'exécution
<a name="_confirm_the_monitoring_stack_is_running"></a>

La pile kube-prometheus de la configuration de [ surveillance ](ml-cluster-setup-cli.md#cluster-setup-cli-monitoring) s'exécute dans l'espace de noms. `monitoring`

```
kubectl get pods -n monitoring
```

Les capsules Prometheus et Grafana doivent être : `Running`

```
NAME                                              READY   STATUS    RESTARTS   AGE
kube-prometheus-stack-grafana-6f9c8b7d5c-2xk9p    3/3     Running   0          3h
kube-prometheus-stack-operator-7b8c9d6f4-q4m7n    1/1     Running   0          3h
prometheus-kube-prometheus-stack-prometheus-0     2/2     Running   0          3h
```

S'ils sont absents, terminez la [ configuration ](ml-cluster-setup-cli.md#cluster-setup-cli-monitoring) de la surveillance avant de continuer.

### Vérifiez que le modèle vLLM est en cours d'exécution
<a name="_confirm_the_vllm_model_is_running"></a>

Le `vllm-inference-app` déploiement et le `vllm-inference-svc` service de [ Load & Serve Models ](ml-inference-load-serve-model.md) s'exécutent dans l'`default`espace de noms.

```
kubectl get deployment vllm-inference-app
```

Le déploiement devrait être `1/1` prêt pour le rapport :

```
NAME                 READY   UP-TO-DATE   AVAILABLE   AGE
vllm-inference-app   1/1     1            1           3h
```

Si le déploiement est manquant ou n'est pas prêt, complétez les modèles [ Load & Serve ](ml-inference-load-serve-model.md) avant de continuer.

### Accédez à Grafana
<a name="_access_grafana"></a>

Vous regardez le test de charge dans le tableau de bord vLLM préchargé. Assurez-vous donc que l'équilibreur de charge Grafana que vous avez configuré dans la section [ Access Grafana est accessible. ](ml-cluster-setup-cli.md#cluster-setup-cli-grafana-loadbalancer) Imprimez son URL :

```
echo "http://$(kubectl get ingress kube-prometheus-stack-grafana -n monitoring -o jsonpath='{.status.loadBalancer.ingress[0].hostname}')"
```

Ouvrez l'URL dans votre navigateur et connectez-vous avec le nom d'utilisateur `admin` et le mot de passe à l'aide de la commande suivante :

```
kubectl --namespace monitoring get secrets kube-prometheus-stack-grafana -o jsonpath="{.data.admin-password}" | base64 -d ; echo
```

### Confirmez l'existence du vLLM ServiceMonitor
<a name="_confirm_the_vllm_servicemonitor_exists"></a>

Le `vllm-inference-app` ServiceMonitor, créé à l'[étape ](ml-inference-load-serve-model.md#ml-inference-load-serve-model-monitoring) Monitor vLLM, s'exécute dans l'`default`espace de noms et indique à Prometheus où extraire les métriques vLLM.

```
kubectl get servicemonitor vllm-inference-app
```

Sortie attendue :

```
NAME                 AGE
vllm-inference-app   3h
```

S'il est absent, terminez l'étape [ Monitor vLLM. ](ml-inference-load-serve-model.md#ml-inference-load-serve-model-monitoring)

## Étape 1 : préchauffez le GPU
<a name="_step_1_warm_up_the_gpu"></a>

Même si le modèle est chargé dans la mémoire du GPU et que la réplique indique Ready, les premières requêtes prennent plus de temps que les suivantes, car vLLM et le GPU terminent leur initialisation dès les premières inférences. vLLM capture les graphes CUDA, compile et atotalise les noyaux GPU, et alloue ses pools de mémoire cache KV, tandis que le GPU augmente sa vitesse d'horloge de repos à sa plage de boost.

Il s'agit de coûts ponctuels. Par conséquent, la mesure des seuils avant l'échauffement enregistre le démarrage des enregistrements plutôt que de procéder à un service en régime permanent. L'échauffement suivant envoie 100 demandes séquentielles pour terminer cette initialisation, de sorte que le test de charge qui suit mesure la capacité en régime permanent.

Commencez par créer le script ConfigMap contenant le script d'échauffement (`warmup.js`). Le script lit le modèle cible à partir de la variable d'`MODEL`environnement, que le Job définit à partir de la `MODEL_BUCKET` valeur que vous avez capturée précédemment :

**Example échauffement K6 ConfigMap**  

```
cat << 'EOF' | kubectl apply -f -
apiVersion: v1
kind: ConfigMap
metadata:
  name: vllm-warmup-script
  namespace: default
data:
  warmup.js: |
    import http from 'k6/http';

    export default function () {
      const payload = JSON.stringify({
        model: __ENV.MODEL,
        prompt: 'Write a brief technical explanation about cloud computing concepts.',
        max_tokens: 100,
        temperature: 0.7,
      });
      const params = { headers: { 'Content-Type': 'application/json' }, timeout: '240s' };
      http.post('http://vllm-inference-svc:8000/v1/completions', payload, params);
    }
EOF
```

Exécutez ensuite le job d'échauffement. Un seul client envoie 100 requêtes séquentielles :

**Example Warm-up Poste**  

```
cat << EOF | kubectl apply -f -
apiVersion: batch/v1
kind: Job
metadata:
  name: vllm-warmup
  namespace: default
spec:
  backoffLimit: 0
  template:
    spec:
      restartPolicy: Never
      containers:
        - name: k6
          image: grafana/k6:latest
          env:
            - name: MODEL
              value: "s3://${MODEL_BUCKET}/Ministral-3-8B-Instruct-2512/"
          args: ["run", "--vus", "1", "--iterations", "100", "/scripts/warmup.js"]
          volumeMounts:
            - name: script
              mountPath: /scripts
      volumes:
        - name: script
          configMap:
            name: vllm-warmup-script
EOF
```

Attendez la fin de l'échauffement (cela prend quelques minutes) :

```
kubectl wait --for=condition=complete job/vllm-warmup --timeout=600s
```

Ouvrez Grafana (ouvrez le nom d'hôte de l'équilibreur de charge ; voir [ Access Grafana ](ml-cluster-setup-cli.md#cluster-setup-cli-grafana-loadbalancer) pour plus de détails) et accédez à ** Tableaux de bord > Surveillance du GPU > Tests de performance - Analyse de charge vLLM. ** Le ** panneau ** VLLM Request Rate affiche la rafale de trafic de préchauffage. Le ** panneau ** vLLM Average Latency indique que la première demande est plus lente, ce qui correspond au coût unique de préchauffage du GPU.

 **Tableau de bord vLLM montrant l'effet d'échauffement du GPU sur la latence ** 

![Une courte rafale de trafic de préchauffage avec une latence à la première demande passant d'environ 2,6 secondes à une valeur de référence stable à mesure que le GPU s'échauffe.](https://docs.aws.amazon.com/fr_fr/eks/latest/userguide/images/ml-inference-autoscaling-thresholds-gpu-warm-up.png)


Supprimez ensuite la tâche de préchauffage :

```
kubectl delete job vllm-warmup
```

## Étape 2 : envoyer du chargement à des taux de demande croissants
<a name="_step_2_send_load_at_increasing_request_rates"></a>

Au cours de cette étape, vous exécutez un test de charge qui envoie des requêtes au réplica vLLM unique à des taux croissants : 10, 20, 30, 40, 50, 60 et 70 requêtes par seconde. Cela simule une demande croissante des utilisateurs. Le test utilise k6 dans un modèle à taux d'arrivée constant, où la variable d'`TARGET_RPS`environnement définit le taux de demande cible. Chaque taux est exécuté pendant 60 secondes pour permettre aux indicateurs de se stabiliser, suivi d'une pause de 30 secondes avant la prochaine augmentation de taux.

L'objectif de ce test est d'identifier le point de saturation, c'est-à-dire le taux de demandes auquel une seule réplique ne peut plus suivre les demandes entrantes. Vous surveillez la profondeur de la file d'attente (`vllm:num_requests_waiting`) et la latence de bout en bout (`vllm:e2e_request_latency_seconds`). L'augmentation de la profondeur et de la latence de la file d'attente indique que la réplique approche de la saturation.

Tout d'abord, créez le script ConfigMap qui contient le script de test de chargement k6 (). `script.js` Le script lit le modèle cible à partir de la variable d'`MODEL`environnement, que chaque pod définit à partir de la `MODEL_BUCKET` valeur que vous avez capturée précédemment :

**Example essai de charge k6 ConfigMap**  

```
cat << 'EOF' | kubectl apply -f -
apiVersion: v1
kind: ConfigMap
metadata:
  name: vllm-loadtest-script
  namespace: default
data:
  script.js: |
    import http from 'k6/http';

    // Dictionary of 10-letter words for variation (prevents prompt caching)
    const words = [
      'revolution', 'technology', 'javascript', 'kubernetes', 'basketball',
      'strawberry', 'watermelon', 'blackberry', 'throughout', 'background',
      'playground', 'understand', 'everything', 'protection', 'complexity'
    ];

    export const options = {
      noConnectionReuse: true,
      scenarios: {
        constant_request_rate: {
          executor: 'constant-arrival-rate',
          rate: __ENV.TARGET_RPS || 10,  // Overridden by TARGET_RPS env var
          timeUnit: '1s',
          duration: __ENV.DURATION || '60s',  // Overridden by DURATION env var
          preAllocatedVUs: 50,
          maxVUs: 500,
        },
      },
    };

    export default function () {
      const randomWord = words[Math.floor(Math.random() * words.length)];
      const payload = JSON.stringify({
        model: __ENV.MODEL,
        prompt: `Starting with the word "${randomWord}", write a brief technical explanation about cloud computing concepts. Cover containerization, orchestration, and scalability in about 80 words.`,
        max_tokens: 100,
        temperature: 0.7,
      });
      const params = { headers: { 'Content-Type': 'application/json' }, timeout: '240s' };
      http.post('http://vllm-inference-svc:8000/v1/completions', payload, params);
    }
EOF
```

Exécutez ensuite le test de charge à chaque taux de demande. La boucle suivante soumet un Job distinct pour chaque tarif :

**Example Boucle de test de charge**  

```
for RPS in 10 20 30 40 50 60 70; do
  echo "=== Testing at ${RPS} req/s ==="
  cat << EOF | kubectl apply -f -
apiVersion: batch/v1
kind: Job
metadata:
  name: vllm-loadtest-${RPS}
  namespace: default
  labels:
    app: vllm-loadtest
spec:
  backoffLimit: 0
  template:
    metadata:
      labels:
        app: vllm-loadtest
    spec:
      restartPolicy: Never
      containers:
        - name: k6
          image: grafana/k6:latest
          env:
            - name: K6_PROMETHEUS_RW_SERVER_URL
              value: "http://kube-prometheus-stack-prometheus.monitoring.svc.cluster.local:9090/api/v1/write"
            - name: MODEL
              value: "s3://${MODEL_BUCKET}/Ministral-3-8B-Instruct-2512/"
            - name: TARGET_RPS
              value: "${RPS}"
            - name: DURATION
              value: "60s"
          args: ["run", "-o", "experimental-prometheus-rw", "/scripts/script.js"]
          volumeMounts:
            - name: script
              mountPath: /scripts
      volumes:
        - name: script
          configMap:
            name: vllm-loadtest-script
EOF
  kubectl wait --for=condition=complete job/vllm-loadtest-${RPS} --timeout=180s
  sleep 30
done
```

Observez les statistiques dans Grafana pendant l'exécution du test (voir Étape 3). Chaque tarif crée sa propre tâche (`vllm-loadtest-10`à travers`vllm-loadtest-70`), ce qui vous permet d'inspecter n'importe quelle course avec`kubectl get jobs`. Lorsque vous avez terminé, supprimez toutes les tâches de test de charge :

```
kubectl delete jobs -l app=vllm-loadtest
```

## Étape 3 : Mesurer les métriques sous charge
<a name="_step_3_measure_metrics_under_load"></a>

Pendant l'exécution du chargement, observez les deux indicateurs qui pilotent la mise à l'échelle automatique :
+  **Profondeur de la file d'attente ** (`vllm:num_requests_waiting`) : nombre de demandes en attente de traitement.
+  **Latence de bout en bout p95 ** (`vllm:e2e_request_latency_seconds`) : temps de réponse du 95e centile.

### Afficher les statistiques dans Grafana
<a name="_view_the_metrics_in_grafana"></a>

Ouvrez Grafana (ouvrez le nom d'hôte de l'équilibreur de charge et connectez-vous en tant que tel `admin` ; voir [ Access Grafana ](ml-cluster-setup-cli.md#cluster-setup-cli-grafana-loadbalancer) pour plus de détails) et accédez à ** Tableaux de bord > Surveillance du GPU > Tests de performance - Analyse de charge vLLM. ** Ce tableau de bord est basé sur Amazon Managed Service pour Prometheus. Les panneaux peuvent donc être retardés par rapport à l'état réel jusqu'à une minute, car les métriques sont écrites à distance par lots.

 **Tests de performance - Tableau de bord d'analyse de charge vLLM pendant le test de charge ** 

![Six panneaux montrant l'utilisation du processeur et de la mémoire du pod k6 par tâche de test de charge, le taux de requêtes vLLM augmentant par étapes, la latence moyenne augmentant une fois que la réplique est saturée, les jetons générés par demande et la profondeur de la file d'attente passant de zéro à saturation.](https://docs.aws.amazon.com/fr_fr/eks/latest/userguide/images/ml-inference-autoscaling-thresholds-load-test.png)


Sur la base des résultats des tests de charge, la profondeur de la file d'attente est restée proche de zéro malgré 50 demandes simultanées. Il a ensuite fortement augmenté avec 60 demandes simultanées, avec un pic entre 150 et 350 demandes en file d'attente environ. Dans le même temps, la latence de bout en bout de p95 est passée d'une valeur de référence stable d'environ 2,5 secondes à 6 à 10 secondes. Ce comportement indique le début d'une surcharge prolongée et suggère que la mise à l'échelle automatique devrait commencer avant que la profondeur de la file d'attente n'atteigne des niveaux entraînant une latence d'environ 10 secondes. Un point de départ pratique consiste à déclencher la mise à l'échelle lorsque la profondeur de la file d'attente dépasse 25 demandes pendant 30 à 60 secondes. En tant que signal secondaire, vous pouvez également déclencher la mise à l'échelle lorsque la latence de bout en bout de p95 dépasse 5 secondes sur le même intervalle. Cela permet de prendre en compte les modèles de charge de travail dans lesquels la latence augmente avant que la profondeur de la file d'attente n'augmente.

## Étape 4 : Choisissez les indicateurs et les seuils sur lesquels vous souhaitez évoluer
<a name="_step_4_choose_the_metrics_and_thresholds_to_scale_on"></a>

À l'aide des mesures observées au point de saturation, vous déterminez les seuils utilisés pour configurer la mise à l'échelle automatique dans la section suivante.
+  **Profondeur de la file d'attente ** (`vllm:num_requests_waiting`) : nombre de demandes en attente de traitement une fois qu'une réplique est saturée. Définissez le seuil au-dessus du point où la file d'attente reste positive pour la première fois, afin que les pics transitoires ne déclenchent pas la mise à l'échelle. Pour le test de charge de cette section, la profondeur de la file d'attente est restée proche de zéro pendant 50 demandes simultanées et a fortement augmenté pour atteindre environ 150 à 350 demandes en file d'attente sur 60 demandes simultanées. Un point de départ pratique consiste à déclencher la mise à l'échelle lorsque la profondeur de la file d'attente dépasse ** 25 demandes ** pendant 30 à 60 secondes.
+  **Latence ** (`vllm:e2e_request_latency_seconds`) : temps de réponse de bout en bout lorsqu'une réplique approche de sa limite de capacité. Définissez le seuil inférieur à votre SLO de latence, afin que l'autoscaler évolue avant que la latence visible par l'utilisateur ne devienne inacceptable. Pour le test de charge décrit dans cette section, la latence de bout en bout de p95 est restée stable à environ 2,5 secondes et a augmenté à 6 à 10 secondes à saturation. En tant que signal secondaire, un point de départ pratique consiste à déclencher la mise à l'échelle lorsque la latence de bout en bout de p95 dépasse ** 5 secondes ** pendant 30 à 60 secondes. Cela permet de prendre en compte les modèles de charge de travail dans lesquels la latence augmente avant que la profondeur de la file d'attente n'augmente ou lorsque la latence augmente sans mise en file d'attente.
+  **Scale-down**— la vitesse à laquelle l'autoscaler supprime les répliques en cas de baisse de la demande. Réduisez la taille plus lentement que vous ne l'augmentez. Pour cet exemple, réduisez la taille uniquement lorsque la profondeur de la file d'attente est faible `0` et que la latence de bout en bout de p95 est inférieure à 3 secondes pendant 5 minutes. Cela empêche l'autoscaler de supprimer les répliques pendant de courtes pauses dans le trafic et permet d'éviter les oscillations.

Ces valeurs constituent un point de départ pour configurer la mise à l'échelle automatique dans la section suivante. Répétez ce processus pour votre propre modèle, votre type de GPU et vos modèles de demande afin de déterminer les seuils appropriés pour votre déploiement.