Ce qui se passe
Un agent renvoie, à chaque tour, tout l'historique : consignes, échanges précédents, résultats d'outils, réponses. Le serveur retraite donc des milliers de jetons déjà vus. Avec la séparation prefill/decode de vLLM, un nœud de prefill traite le prompt et construit son cache KV, puis un nœud de decode récupère ces blocs et génère la réponse.
Le flux ne va que dans un sens. Conséquence, au tour suivant : le prefill doit recalculer les jetons de la réponse précédente, qu'il n'a jamais vus, et tout l'historique si son cache a été évincé ou si le routeur l'envoie vers un autre nœud. Le temps avant le premier jeton (TTFT) grimpe avec la conversation.
L'équipe vLLM, avec des contributeurs d'Amazon, ajoute un transfert bidirectionnel : le decode renvoie au prefill les blocs qu'il a déjà.
Le mécanisme
En clair :
- Après la génération, le nœud de decode conserve les blocs du prompt et de
la réponse pendant
decoder_kv_blocks_ttl(480 secondes par défaut). - Il publie leur emplacement dans le dernier morceau de la réponse en flux.
- Un routeur sensible au cache le mémorise par
conversation_id, et le joint à la requête suivante. Il ne transporte que des métadonnées : les tenseurs vont de machine à machine, via NIXL (NVIDIA Inference Xfer Library). - Le prefill, après sa recherche de cache local, relit les blocs distants par une lecture NIXL du decode vers le prefill, puis ne calcule que le reste.
Deux réglages commandent le comportement :
decoder_kv_blocks_ttl, 480 s : durée fixe et non renouvelée. À régler sur l'écart attendu entre deux tours, entre réutilisation du cache et mémoire d'accélérateur occupée. Un tour qui arrive plus tard retombe sur un prefill normal.kv_recompute_threshold, 64 jetons : sous ce seuil, le prefill recalcule, car le transfert coûterait plus que le calcul.
Les mesures
Paire de nœuds p5en.48xlarge (8 GPU H200 chacun) reliés par AWS EFA, avec
--no-enable-prefix-caching sur le prefill pour simuler un cache évincé.
| Modèle | TTFT sans | TTFT avec | Écart |
|---|---|---|---|
| Qwen3-32B, 2k → 24k jetons | 158 → 778 ms | 122 → 460 ms | jusqu'à 1,6× |
| GLM-5-FP8, 2k → 20k jetons | 500 → 2 334 ms | 358 → 702 ms | jusqu'à 3,3× |
Le transfert ajoute de 30 à 130 ms (Qwen3-32B) ou de 29 à 110 ms (GLM-5-FP8), mais le calcul retiré pèse plus. Le modèle le plus gros gagne davantage, parce que son recalcul par jeton est plus cher.
Pour l'essayer
Prérequis du billet : vLLM 0.21.0 ou plus, le connecteur NixlConnector,
bidirectional_kv_xfer activé sur le prefill et sur le decode, et un
routeur qui renvoie les métadonnées. Un exemple de proxy est dans le dépôt :
examples/disaggregated/disaggregated_serving/disagg_proxy_multiturn.py.
{
"kv_connector": "NixlConnector",
"kv_connector_extra_config": {
"kv_recompute_threshold": 64,
"decoder_kv_blocks_ttl": 480,
"bidirectional_kv_xfer": true
}
}Le guide de reproduction fixe vllm==0.29.0 et nixl==1.4.1, et applique la
PR n° 40186, qui attache kv_transfer_params aux réponses en flux : sans elle,
chaque tour tombe en échec de cache. Autres causes d'un cache MISS permanent :
l'identifiant de conversation non envoyé par le client, ou bidirectional_kv_xfer
resté à false sur le decode. Si fi_info -p efa est vide, NIXL peut
retomber en silence sur TCP, ce qui rend les mesures inutilisables.
Limites
Une seule paire (1 prefill, 1 decode), un réseau EFA, deux modèles, et un cache de préfixe volontairement coupé sur le prefill. Les résultats, dit le billet, dépendent de la charge et de la configuration. Le routeur reste à fournir ou à adapter. Idée d'évolution citée : étendre le bail renouvelé par battement de cœur au côté decode, pour libérer les blocs dès qu'une conversation s'arrête.
Source : Improve TTFT for Multi-Turn Agentic Workloads with Bi-Directional KV Transfer in vLLM, blog vLLM, 12 octobre 2026.