Le prochain token dépend désormais de l’emplacement du passé

Pendant le prefill Phase initiale d'une inférence LLM : les tokens du prompt sont traités en parallèle pour construire l'état du contexte. Cette réutilisation augmente l'intensité arithmétique et peut rendre la phase limitée par le calcul ; le régime exact dépend du modèle, du batch, du contexte et du backend. Approfondir dans le glossaire , un LLM transforme chaque token du prompt en vecteurs de clés et de valeurs. Le KV cache Mémoire des vecteurs clé et valeur déjà calculés pour chaque token traité par un LLM. Évite de recalculer l'attention sur tout l'historique, au prix d'une consommation mémoire qui croît avec le contexte. Approfondir dans le glossaire conserve ces vecteurs pour que le decode Phase de génération autorégressive d'un LLM : un token est produit à la fois en relisant les poids et l'état de contexte utile. À faible batch, ce trafic peut limiter le decode par la bande passante mémoire. Sa part dans le coût total dépend toutefois des longueurs d'entrée et de sortie, du batching et de la réutilisation du cache. Approfondir dans le glossaire n’ait pas à retraiter tout le texte avant de produire chaque token suivant. vLLM avait déjà rendu cette mémoire paginée, partageable entre requêtes au sein d’un moteur et déportable vers la RAM CPU.

Cette optimisation s’arrêtait à une frontière physique. Si le même préfixe arrivait sur un autre worker, son GPU recalculait les mêmes couches ou dépendait d’une architecture prefill/decode où un producteur et un consommateur avaient des rôles fixes. Un routeur pouvait envoyer une requête vers le worker déjà chaud, mais un worker froid ne savait pas interroger n’importe quel voisin comme une source de cache.

vLLM 0.27 ajoute ce mouvement. Chaque instance peut ouvrir un tier P2P, annoncer un canal de contrôle et répondre aux recherches de blocs dans son cache CPU. Pour une requête donnée, l’orchestrateur indique quel pair appeler. Le consommateur demande les empreintes dont il a besoin ; le producteur renvoie hit, miss ou hit en attente, puis NIXL déplace les pages trouvées.

Le changement paraît petit dans une note de version. Il transforme la question du scheduler. Le choix ne porte plus seulement sur le GPU qui exécutera la requête, mais sur le coût de rapatrier son passé depuis un autre nœud.

Trois niveaux, et la RAM CPU comme passerelle

Le connecteur d’offloading vLLM distingue une mémoire de calcul et des tiers de conservation. Le GPU exécute l’attention. La RAM CPU pinned constitue le tier primaire, seul niveau qui échange directement avec le GPU. Des tiers secondaires peuvent ensuite étendre la capacité vers un système de fichiers, un object store ou un pair distant.

GPU source blocs calculés HBM
CPU source cache primaire pinned DMA
NIXL transport entre pairs data plane
CPU cible promotion locale tier primaire
GPU cible préfixe réutilisé decode
Figure 1 : dans TieringOffloadingSpec, tout échange avec le GPU passe par le tier primaire CPU. ZMQ contrôle la session ; NIXL transporte les blocs entre pairs.

Ce n’est donc pas un accès direct universel GPU vers GPU. Le bloc calculé est d’abord copié vers la RAM du producteur par DMA. Le pair distant le lit via un backend NIXL, le place dans sa propre région CPU, puis le remonte vers son GPU. Le guide mentionne UCX par défaut et permet des backends comme Mooncake, GDS_MT ou libfabric.

Le canal de contrôle suit un chemin séparé. Une session ZMQ porte les recherches, réponses et états de transfert. Chaque rang de data parallelism écoute sur un port dérivé d’une base. Pour un déploiement inter-nœuds, l’adresse par défaut localhost doit être remplacée par une IP routable. vLLM n’auto-détecte pas l’adresse que les autres pairs doivent rappeler.

Cette séparation contrôle/données évite d’envoyer de gros tenseurs dans le protocole de coordination. Elle ajoute deux surfaces de panne : la session peut connaître un pair sans que le transport de données soit sain, ou NIXL peut être prêt tandis que le port ZMQ reste inaccessible.

Le pair ne choisit jamais tout seul où chercher

Le tier P2P reçoit une adresse ; il ne construit pas la carte globale du cache. Cette responsabilité appartient au routeur, à l’Endpoint Picker ou à un scheduler extérieur. L’orchestrateur doit connaître le pair qui détient le préfixe, son rang de data parallelism, son port de contrôle et l’identifiant unique de la transaction.

CléInstance qui la reçoitEffetDonnées requises
remote_decoderProducteur prefillConserve les blocs pour un decoderkv_request_id
remote_prefillerConsommateur decodeTire le cache d'un prefiller définiid, hôte, port
remote_kv_sourceConsommateur P2PCherche les blocs présents chez un pairid, hôte, port
Aucune cléPair localUtilise le tier CPU sans échange distantAucune adresse
decoder + kv_sourceProducteur et consommateurRécupère un préfixe puis sert la suiteCombinaison autorisée
Tableau 1 : rôles P2P transmis par requête. Le nom désigne le pair distant avec lequel l'instance échange.

La combinaison remote_decoder et remote_kv_source résume la nouveauté. Un worker de prefill peut récupérer chez un premier pair un préfixe qu’il ne possède pas, calculer seulement la suite, puis maintenir les nouveaux blocs pour un decoder différent. Les rôles forment une chaîne par requête au lieu de figer les processus en deux classes permanentes.

Les autres combinaisons contradictoires sont rejetées. Un consommateur ne peut pas tirer simultanément d’un remote_prefiller et d’un remote_kv_source, car deux sources se disputeraient la même séquence de blocs. L’orchestrateur doit produire une topologie cohérente avant que vLLM ne commence le transfert.

Lookup, promotion, calcul : le scheduler doit arbitrer trois états

Le consommateur envoie une liste d’empreintes. Le pair source répond HIT si le bloc est disponible, MISS s’il ne l’a pas, ou HIT_PENDING si une écriture annoncée n’est pas encore terminée. Le protocole borne l’attente de ce troisième état pour ne pas suspendre une requête indéfiniment derrière un producteur bloqué.

Sur un hit, les données passent dans la région CPU cible puis deviennent éligibles à une promotion GPU. Sur un miss, le moteur calcule le préfixe localement. Ce fallback est indispensable : une carte KV est toujours légèrement en retard sur les évictions, les fins de requêtes et les pannes.

La version 0.27 ajoute aussi un filtre par requête. kv_load_tiers peut limiter les tiers secondaires consultés par médium et localité. Une requête sensible à la latence peut accepter la RAM CPU locale et refuser le stockage distant ; une tâche asynchrone peut parcourir les tiers de stockage. Le tier primaire CPU reste toujours impliqué, puisqu’il constitue la cible de promotion avant le GPU.

Le contrôle devient donc une politique de coût. Pour chaque préfixe, le scheduler choisit entre HBM locale, RAM locale, pair distant, stockage et recomputation. Le premier hit dans la hiérarchie n’est pas nécessairement le moins cher si sa source est saturée ou éloignée.

Les événements auto-descriptifs donnent une carte au routeur

Un routeur KV-aware doit relier une empreinte de bloc au worker qui la possède. Un simple événement « bloc stocké » avec un identifiant local ne suffit pas : le routeur a besoin des tokens, du hash parent, de la taille du bloc et de la localité pour reconstruire la même clé que les moteurs.

vLLM 0.27 autorise les événements auto-descriptifs avec TieringOffloadingSpec. Les copies GPU vers CPU utilisent le chemin existant. Une promotion depuis un tier secondaire enregistre les métadonnées lorsqu’une recherche devient un hit, puis l’événement BlockStored CPU peut publier le payload complet. NVIDIA Dynamo peut alors indexer la copie promue et router une requête suivante vers ce worker.

La PR documente plusieurs limites. Un événement de promotion reste incomplet si aucune requête locale n’observe le hit avant sa traduction. Les groupes sliding-window et SSM conservent des payloads placeholder. Aucun tier secondaire fourni dans l’arbre n’émet encore ses propres événements de suppression, et le reset peut laisser passer des événements plus anciens déjà en file.

L’éviction devient une interface extensible

Le cache CPU utilisait déjà LRU et ARC. La version 0.27 ajoute CachePolicyFactory, qui permet à un paquet externe d’enregistrer une stratégie par nom sans modifier le manager interne. Le comportement de LRU et ARC ne change pas ; la construction devient extensible comme les specs d’offloading et les connecteurs.

Ce détail compte dans une hiérarchie. Une politique LRU locale ignore le coût de reconstruction d’un bloc, sa popularité globale, son emplacement chez d’autres pairs et le volume qu’il consomme sur le réseau. Une politique externe peut intégrer ces signaux ou réserver les longs prompts système tout en laissant expirer les branches propres à une conversation.

Le filtrage complète cette extensibilité. Une requête peut aussi plafonner le nombre de ses tokens offloadés. Cacher un prompt système partagé et ignorer les tours privés qui le suivent réduit les écritures inutiles. Le cache ne gagne pas par sa capacité brute ; il gagne en refusant de déplacer des blocs qui ne seront jamais relus.

Quand un hit distant bat-il un nouveau prefill ?

Un hit économise le calcul des couches sur L tokens de préfixe. Son coût contient au moins une recherche de contrôle, un transfert entre pairs et une copie CPU vers GPU. Si le bloc n’est pas déjà en RAM CPU du producteur, il faut aussi compter sa conservation antérieure. La condition utile peut s’écrire :

temps de lookup + octets transférés / bande passante effective + promotion GPU < temps de prefill évité.

La longueur seule ne décide pas. Un modèle dense de grande taille dépense davantage de calcul par token de prefill qu’un petit modèle ; un même transfert a donc plus de chances de gagner. Un modèle à KV volumineux déplace plus d’octets pour le même préfixe. Une interconnexion RDMA rapproche le coût du transfert de la mémoire distante ; un réseau Ethernet chargé peut rendre la recomputation plus rapide.

Le taux de hit intervient deux fois. Il amortit l’écriture initiale et il réduit le risque que les recherches ratées consomment le plan de contrôle sans éviter de calcul. Une charge de chat sans préfixes partagés peut dégrader légèrement avec l’offloading activé. Une charge d’agents qui relisent un dépôt, un système prompt et un historique commun peut réutiliser des dizaines de milliers de tokens.

Les mesures CPU publiées par vLLM montrent jusqu’à 2 à 22× de réduction du TTFT sur un H100 selon la longueur du prompt, et jusqu’à 9× de débit sur 10 000 requêtes de 512 tokens. Elles désactivent le cache GPU, excluent le préchauffage du temps mesuré et utilisent un seul nœud avec 500 Go de RAM. Elles prouvent que RAM contre recomputation peut gagner ; elles ne mesurent pas le nouveau saut P2P.

Le « 97 % » valide le transfert, pas la performance

La PR P2P publie un test réel entre deux pods. Le prefiller utilise TP1, le decoder TP2, le modèle est Llama 3.2 1B et le prompt compte 234 tokens. La continuation cohérente et un comptage déterministe passent. Le decoder mesure 97,0 % de hit externe, au-dessus du seuil de 90 %, ce qui confirme que les blocs viennent du prefiller.

Cette expérience répond à une question essentielle : le protocole récupère-t-il les bons tenseurs malgré des degrés de parallélisme différents ? Oui, dans ce cas. Elle ne répond pas aux questions d’exploitation. Un modèle 1B et 234 tokens produisent peu de KV, n’exercent pas un réseau saturé et ne montrent pas le coût du contrôle sous des milliers de requêtes.

Aucun chiffre publié avec vLLM 0.27 ne compare encore le TTFT, l’intervalle entre tokens, le goodput, les octets transférés ou la consommation CPU du P2P contre la recomputation. Le changelog décrit une capacité, pas un speedup. Présenter 97 % comme un gain de 97 % confondrait un ratio de blocs trouvés avec une réduction de temps.

La release elle-même reste une surface mobile

vLLM 0.27.0 réunit 561 commits et passe à PyTorch 2.13, torchvision 0.28 et Triton 3.7.1. Les notes classent ce changement comme cassant pour l’environnement. Elles ajoutent dans la même version Kimi K3, FlashAttention 4, la tolérance aux pannes simplifiée, les modèles hybrides en prefill/decode et les premiers chemins sm_107 pour Rubin.

La version 0.27.1 est sortie le 11 août, moins de quatorze heures après 0.27.0, avec un correctif DSpark. Ce patch ne cible pas le P2P, mais il confirme le rythme de la branche. Un exploitant doit épingler l’image, PyTorch, Triton, FlashInfer, NIXL et le commit de son routeur, puis refaire le test de correction avant le test de charge.

La feuille de route Q3 qualifie encore le cache distribué et multi-tier « production-ready » comme un objectif. Elle demande aussi des événements KV P2P pour Mooncake, des hints de session et un chemin ROCm/RDMA à parité. La fonctionnalité est fusionnée ; la promesse de maturité totale reste devant elle.

Le protocole de validation doit partir d’une trace réelle

Un test utile rejoue les sessions de production avec leur distribution de préfixes, leurs longueurs et leur intervalle entre tours. Il compare au moins quatre configurations : cache GPU seul, GPU plus CPU, hiérarchie avec stockage, puis P2P. Chaque configuration conserve le même modèle, les mêmes GPU, la même qualité de sortie et le même budget de concurrence.

Les métriques doivent séparer hit par tier, temps de lookup, octets lus et écrits, temps de promotion, TTFT p50/p99, débit conforme au SLO, recomputation et taux d’éviction avant réutilisation. Le routeur doit publier combien de décisions pointaient vers un pair qui n’avait plus le bloc. Les pannes sont testées en coupant le canal ZMQ, le transport NIXL puis le producteur après le routage.

La sécurité appartient aussi au protocole. Un hash de préfixe peut révéler qu’un contenu a été vu, et un bloc KV appartient à un modèle, une version de poids, un tenant et une configuration de cache précis. L’isolation des clés et les ACL réseau doivent empêcher qu’un pair d’un tenant serve l’état d’un autre. vLLM fournit les primitives de transfert ; la frontière multi-tenant reste une responsabilité d’architecture.

Le cache devient un système distribué à part entière

PagedAttention avait transformé la VRAM en pages gérées. L’offloading CPU avait ajouté un niveau plus grand et plus lent. Le P2P de vLLM 0.27 franchit l’étape suivante : une page peut être utile sur un moteur qui ne l’a jamais calculée, et un worker n’a plus un rôle permanent dans le trajet.

Ce changement rapproche le KV cache d’un produit de données distribué, avec index, localité, politique d’éviction, événements, cohérence faible et plan de contrôle. Le gain maximal ne viendra pas du nombre de tiers disponibles, mais de la qualité de la décision qui évite un calcul sans déplacer plus d’octets qu’il n’en sauve. Le runtime a livré le mécanisme. Le routeur et la mesure doivent maintenant prouver qu’ils savent choisir.

Sources et méthode