infrastructureنُشر فـ 2 juin 20264 دقائق قراءة

vLLM V1 : réparer avant d'optimiser

Comment l'équipe Hugging Face a corrigé vLLM V1 pour qu'elle corresponde à V0. La leçon : fixez les fondations d'abord, ne patchez pas un système cassé.

vLLM V1: إصلاح الأخطاء قبل تحسين الخوارزمية

Le problème : V1 était incorrecte

Quand vLLM est passée de la version V0 à V1 (une réécriture complète du moteur), l'équipe a commencé à travailler sur le même service : entraîner des modèles IA en utilisant Reinforcement Learning (apprentissage par renforcement). L'idée est simple : exécuter le modèle, collecter les résultats (rollouts), et améliorer l'algorithme en fonction des récompenses.

Mais le problème était : V1 donnait des résultats différents de V0, même avec les mêmes données et les mêmes paramètres. Les graphiques étaient clairs :

  • Première tentative (ligne rouge) : catastrophe. Toutes les métriques étaient incorrectes.
  • Tentative finale (ligne verte) : correspondance parfaite avec V0.

La différence entre les deux ? 4 corrections techniques. Pas d'améliorations algorithmiques, pas de modifications intelligentes — juste réparer les fondations.

La leçon fondamentale : séparer les problèmes

L'équipe a suivi une stratégie claire :

  1. D'abord : vérifier que V1 retourne les données que l'entraîneur (trainer) attend
  2. Ensuite : exécuter le même travail sur V0 comme référence
  3. Enfin : modifier l'algorithme seulement après avoir confirmé les fondations

Cet ordre est crucial. Pourquoi ? Si vous appliquez une correction à un système cassé, vous mélangez deux problèmes :

  • « Le moteur retourne-t-il les bonnes données ? »
  • « L'algorithme est-il correct ? »

Quand vous les mélangez, les graphiques deviennent une énigme.

La première correction : le sens des nombres

vLLM V1 retournait logprobs (probabilités logarithmiques), qui sont des nombres représentant les probabilités des mots. Mais le problème :

  • V1 retournait logprobs du modèle brut (avant tout traitement)
  • L'entraîneur attendait logprobs du modèle traité (après temperature scaling, penalties, etc.)

La solution était une seule ligne dans les paramètres :

logprobs_mode: processed_logprobs

Cette correction a éliminé la différence fondamentale. Mais les graphiques ne correspondaient toujours pas exactement. Cela signifiait qu'il y avait autre chose de mal.

La deuxième correction : le comportement d'exécution

V1 utilisait de nouveaux paramètres qui n'existaient pas dans V0 :

  • Prefix caching : mise en cache des parties répétées du texte (comme des références de livres — si vous avez lu le même chapitre deux fois, vous n'avez pas besoin de le relire)
  • Async scheduling : planification asynchrone (comme demander plusieurs choses à la fois au lieu d'attendre qu'une se termine)

Dans online RL (entraînement sur des données en direct), ces éléments créaient des problèmes :

  • Le prefix cache pouvait utiliser des données anciennes même après la mise à jour du modèle
  • L'async scheduling laissait les requêtes s'exécuter à des vitesses différentes

La solution : désactiver ces fonctionnalités pour la comparaison :

enable_prefix_caching: false
async_scheduling: false

Ce n'était pas « mieux » — c'était « identique à V0 ».

La troisième correction : mise à jour des poids

Dans online RL, le modèle se met à jour régulièrement (les poids changent). Le problème :

  • V0 faisait quelque chose de simple : arrêter l'exécution, charger les nouveaux poids, continuer (sans vider le cache)
  • V1 faisait quelque chose de différent : vider le cache, ou attendre d'une manière non identique

La solution : correspondre exactement au comportement de V0 :

await engine.pause_generation(mode="keep", clear_cache=False)
await engine.receive_weight_update(...)
await engine.resume_generation()

Les détails comptent :

  • mode="keep" : ne pas supprimer les requêtes en cours d'exécution
  • clear_cache=False : ne pas vider le cache

La quatrième correction : précision des nombres

Cette dernière correction était la plus subtile. Le problème :

  • L'entraîneur utilisait fp32 (nombres en précision 32 bits) pour lm_head (la couche finale qui convertit les nombres en probabilités)
  • V1 utilisait fp16 ou bfloat16 (moins de précision, plus rapide mais moins exact)

La différence semble petite, mais dans RL elle devient grande. Pourquoi ? RL utilise les probabilités directement — une petite différence dans les nombres devient une différence visible dans le comportement.

La solution : utiliser fp32 pour lm_head :

fp32_lm_head: true

Pourquoi cet ordre est important ?

L'équipe avait une tentation :

  • L'option facile : « Et si on ajoute une correction dans l'algorithme ? Peut-être que ça marchera. »
  • L'option correcte : « Non, d'abord on s'assure que le moteur est correct. »

En choisissant l'option correcte, il s'est avéré que le problème était dans le moteur, pas dans l'algorithme. Après les 4 corrections, V1 correspondait à V0 sans aucun changement algorithmique.

S'ils avaient ajouté des corrections dès le départ :

  • Ils auraient masqué les vrais problèmes
  • Les graphiques seraient devenus des énigmes
  • L'entraînement aurait été lent et peu fiable

Quelle est la prochaine étape ?

Maintenant que vLLM V1 est correcte, l'équipe peut travailler sur les vraies améliorations :

  • Utiliser explicit behavior-policy logprobs (probabilités explicites du moment de l'exécution)
  • Recalculer old-policy logprobs au moment de l'entraînement
  • Séparer la correction du backend mismatch de la mise à jour de la politique
  • Suivre des métriques comme ESS (Effective Sample Size — taille effective de l'échantillon)

Mais ces choses se feront après avoir confirmé que les fondations sont correctes.

Qu'est-ce que cela signifie pour vous ?

Cette leçon ne concerne pas seulement vLLM. N'importe quel grand projet IA — qu'il s'agisse d'entraîner un modèle, de construire un chatbot, ou de développer un AI Agent — a besoin de la même idée :

Réparez les fondations d'abord, puis optimisez.

Les ingénieurs et développeurs marocains qui travaillent sur des projets machine learning — que ce soit dans de grandes entreprises ou des projets personnels — peuvent apprendre de cette stratégie. Au lieu d'ajouter des fonctionnalités et des corrections au hasard, divisez les problèmes en couches (sémantique, runtime, objectif) une par une. Cette approche économise du temps et vous assure de ce que vous faites. Et surtout en télétravail avec des équipes européennes, la clarté et l'organisation sont ce qui fait la différence entre un projet réussi et un projet qui perd du temps.

مقالات ذات صلة