{"notes":[{"id":"retrieval-augmented-generation/cag","category":"retrieval-augmented-generation","legacyPath":"","legacySlug":"","legacyPaths":[],"title":"CAG","subtitle":"Apprendre par cœur pour gagner des points","author":"Z. Yuan","paperDate":"2024-12-20","publishedOn":"2026-07-19","paperTitle":"Don't Do RAG: When Cache-Augmented Generation is All You Need for Knowledge Tasks","paperUrl":"https://arxiv.org/abs/2412.15605","bibliography":{"title":"Don't Do RAG: When Cache-Augmented Generation is All You Need for Knowledge Tasks","authors":["Brian J Chan","Chao-Ting Chen","Jui-Hung Cheng","Hen-Hsen Huang"],"url":"https://arxiv.org/abs/2412.15605","pdfUrl":"https://arxiv.org/pdf/2412.15605v2","codeUrl":"https://github.com/hhhuang/CAG","preprint":{"id":"2412.15605","submittedOn":"2024-12-20","url":"https://arxiv.org/abs/2412.15605"},"publication":{"venue":"WWW Companion","year":2025,"kind":"conference","url":"https://doi.org/10.1145/3701716.3715490"},"citation":"@misc{retrievalaugmentedgenerationcag2024,\n  title = {{Don't Do RAG: When Cache-Augmented Generation is All You Need for Knowledge Tasks}},\n  author = {Brian J Chan and Chao-Ting Chen and Jui-Hung Cheng and Hen-Hsen Huang},\n  year = {2024},\n  eprint = {2412.15605},\n  archivePrefix = {arXiv},\n  url = {https://arxiv.org/abs/2412.15605}\n}\n","citationPath":"/content/papers/citations/retrieval-augmented-generation/cag.52cd8e1fb14c.bib"},"thumbnail":{"src":"/content/papers/assets/1ccb714a2b9e40fe07cb712c508763c4447c40b66c78a8c364a030957c65a789.png","width":694,"height":575},"locale":"fr","path":"/content/papers/fr/retrieval-augmented-generation/cag.758fb971cc7f.md","headings":[{"id":"définir-le-problème","text":"Définir le problème","depth":2},{"id":"résoudre-le-problème","text":"Résoudre le problème","depth":2},{"id":"que-reste-t-il-après-la-lecture-","text":"Que reste-t-il après la lecture ?","depth":3},{"id":"trois-étapes","text":"Trois étapes","depth":3},{"id":"calculons-sa-taille-réelle","text":"Calculons sa taille réelle","depth":3},{"id":"pourquoi-tout-charger-ensemble-","text":"Pourquoi tout charger ensemble ?","depth":3},{"id":"discussion","text":"Discussion","depth":2},{"id":"comment-les-expériences-sont-elles-menées-","text":"Comment les expériences sont-elles menées ?","depth":3},{"id":"qualité--en-tête-dans-la-plupart-des-cas","text":"Qualité : en tête dans la plupart des cas","depth":3},{"id":"vitesse--éviter-la-relecture","text":"Vitesse : éviter la relecture","depth":3},{"id":"les-questions-laissées-ouvertes","text":"Les questions laissées ouvertes","depth":3},{"id":"conclusion","text":"Conclusion","depth":2}],"anchorAliases":{},"guide":null,"relatedReading":[]}],"text":"Le titre a du caractère : « **Ne faites pas de RAG** ».\n\nLe RAG, ou génération augmentée par la recherche, est devenu ces dernières années la recette standard pour relier un modèle de langage à des données externes. L'utilisateur pose une question, le système récupère quelques passages pertinents dans une base de connaissances, les place avant la question et demande au modèle de répondre à partir de ces éléments. Documents internes, FAQ produit, historiques du service client : beaucoup de chatbots les intègrent ainsi.\n\nLe procédé fonctionne, mais chaque étape peut dérailler.\n\nSi le moteur récupère le mauvais passage, le modèle doit répondre avec les mauvaises données. S'il trouve le bon endroit mais n'en récupère que la moitié, la réponse sera elle aussi incomplète. Entre découpage des documents, indexation, réglage du top-k et choix du modèle d'embedding, la maintenance demande du travail.\n\nEn 2024, les modèles open source acceptaient déjà plus de cent mille tokens de contexte. Les auteurs posent alors une question très directe :\n\n- **Si toute la base de connaissances tient déjà dans le contexte, pourquoi commencer par une recherche ?**\n\n\n> [!TIP]\n> \n> **Qui sont les auteurs ?**\n> \n> Les trois premiers auteurs à contribution égale, Brian J Chan, Chao-Ting Chen et Jui-Hung Cheng, appartiennent au département d'informatique de la National Chengchi University. L'auteur correspondant, Hen-Hsen Huang, travaille à l'Institut des sciences de l'information de l'Academia Sinica. L'article a été publié à WWW Companion '25. Cette note suit arXiv v2 ; le code est disponible sur [**GitHub**](https://github.com/hhhuang/CAG).\n\n\n## Définir le problème\n\nInsérer toute une base de connaissances dans le prompt n'a rien de nouveau en soi. Le problème est son coût.\n\nSupposons une base de 85 000 tokens. À chaque question, on transmet ces 85 000 tokens avec la question. Le modèle doit lire depuis le premier token jusqu'à la fin des documents avant de pouvoir commencer sa réponse.\n\nÀ la question suivante, les données n'ont pas changé d'une lettre. Pourtant, il faut tout relire.\n\nCette lecture du prompt s'appelle le **prefill**. Plus le prompt est long, plus le prefill dure, et plus l'utilisateur attend le premier token de réponse : c'est le TTFT, *time to first token*.\n\nLe véritable problème posé par les auteurs est donc :\n\n- **Si les données restent les mêmes, peut-on les lire une seule fois ?**\n\nPour y répondre, il faut d'abord comprendre ce que le modèle conserve après avoir « lu ».\n\n## Résoudre le problème\n\n### Que reste-t-il après la lecture ?\n\nLa plupart des modèles de langage actuels sont des Transformers decoder-only. À chaque couche d'auto-attention, chaque token est projeté en trois vecteurs : Query, Key et Value.\n\nLorsqu'on produit le token suivant, sa nouvelle Query est comparée aux **Keys de tous les tokens précédents**, puis sert à pondérer leurs **Values**.\n\nLe point décisif est que les Keys et Values des tokens précédents restent fixes une fois calculées.\n\nOn les conserve donc pour les réutiliser à l'étape suivante : c'est le **cache KV**. Lors d'une génération ordinaire, il évite déjà de refaire ces calculs à chaque token.\n\n\n> [!TIP]\n> \n> **Pourquoi les Keys et Values précédentes restent-elles fixes ?**\n> \n> Le décodeur utilise un masque causal : chaque token ne voit que lui-même et ses prédécesseurs. Ajouter du texte après lui ne modifie pas rétroactivement son calcul.\n> \n> En revanche, changer un élément du texte précédent oblige à recalculer les Keys et Values de tous les tokens qui suivent. Nous retrouverons cette propriété dans les limites de la méthode.\n\n\nCAG, *Cache-Augmented Generation*, étend cette réutilisation à toutes les questions :\n\n- **Faire lire une fois toute la base au modèle, enregistrer le cache KV obtenu, puis ne lui faire lire que chaque nouvelle question.**\n\nLa figure compare les deux pipelines. Les éléments roses interviennent pendant chaque inférence :\n\n[![Comparaison des pipelines RAG et CAG : le RAG passe par un modèle de recherche à l'inférence ; CAG précharge hors ligne un cache de contexte et ne traite ensuite que la question](/content/papers/assets/1ccb714a2b9e40fe07cb712c508763c4447c40b66c78a8c364a030957c65a789.png)](/content/papers/assets/1ccb714a2b9e40fe07cb712c508763c4447c40b66c78a8c364a030957c65a789.png)\n\n*Figure 1 de l'article, extraite de la [**page 2 du PDF v2**](https://arxiv.org/pdf/2412.15605v2#page=2). RAG en haut, CAG en bas ; les composants roses sont actifs pendant l'inférence en ligne.*\n\nEn haut, chaque question RAG passe par l'IR Model, le modèle de recherche, pour interroger la base. Le contexte récupéré est transmis au LLM avec la question $q$.\n\nEn bas, CAG transforme la base en Context Cache grâce au préchargement hors ligne, « Offline Preloading ». À l'inférence, seule la question $q$ est rose. Le moteur de recherche a entièrement disparu.\n\n### Trois étapes\n\nLes auteurs décrivent trois étapes.\n\n**Première étape : précharger les connaissances.** On prépare l'ensemble documentaire $\\mathcal{D}=\\{d_1,d_2,\\dots\\}$, puis on le fait lire une fois au modèle pour obtenir le cache KV :\n\n$$\nC_{KV}=\\text{KV-Encode}(\\mathcal{D})\n$$\n\nCe cache peut rester en mémoire ou être enregistré sur disque. Le coût de lecture des documents n'est payé qu'une fois, quel que soit le nombre de questions suivantes.\n\n**Deuxième étape : l'inférence.** On charge $C_{KV}$ et on lui ajoute la question $q$ :\n\n$$\nr=\\mathcal{M}(\\mathcal{D}\\oplus q)=\\mathcal{M}(q\\mid C_{KV})\n$$\n\nÀ gauche, on concatène documents et question avant de les soumettre au modèle. À droite, le modèle dispose déjà du cache et ne lit que la question. Le résultat du calcul est identique ; la partie documentaire n'a simplement plus besoin d'être relue.\n\n**Troisième étape : réinitialiser le cache.** Pendant la première réponse, les tokens de la question et de la réponse sont ajoutés au cache KV. Avant la deuxième question, il faut retirer cette partie supplémentaire, sinon elle verra la conversation précédente.\n\nLe cache se prolonge uniquement à la fin. Il suffit donc de mémoriser sa longueur après lecture de la base et de tronquer les positions ajoutées, sans recharger le cache depuis le disque.\n\n### Calculons sa taille réelle\n\n« Conserver le cache KV » reste assez abstrait. Prenons le Llama 3.1 8B de l'article pour voir concrètement sa forme.\n\nD'après la Table 3 de l'[**article Llama 3**](https://arxiv.org/abs/2407.21783), le modèle 8B possède 32 couches, une dimension cachée de 4096 et 32 têtes d'attention. Les Keys et Values n'ont que 8 têtes, grâce à l'attention à requêtes groupées ; chaque tête possède $4096/32=128$ dimensions.\n\nÀ chaque couche, le tenseur Key à conserver pour un token a donc la forme :\n\n$$\n[\\text{batch},\\ \\text{kv\\_heads},\\ \\text{seq\\_len},\\ \\text{head\\_dim}]=[1,\\ 8,\\ 1,\\ 128]\n$$\n\nIl en va de même pour Value. Sur toutes les couches, un token demande :\n\n$$\n2\\ (\\text{K et V})\\times 32\\ (\\text{couches})\\times 8\\ (\\text{têtes})\\times 128\\ (\\text{dimensions})=65{,}536\n$$\n\nÀ 16 bits, soit 2 octets par valeur, cela fait 128 KiB par token.\n\nLe plus grand groupe HotPotQA de l'article contient environ 85k tokens :\n\n$$\n85{,}000\\times 128\\ \\text{KiB}\\approx 11.1\\ \\text{GB}\n$$\n\nLes poids du modèle occupent environ 16 GB en 16 bits. Le cache atteint déjà près de 70 % de cette taille.\n\n\n> [!NOTE]\n> \n> Il s'agit d'une estimation à partir de la configuration du modèle, avec un cache supposé en 16 bits. L'article ne rapporte ni la taille réelle du cache ni la mémoire consommée. Le code officiel charge le modèle en `torch.float16` par défaut et propose aussi une quantification sur 4 bits.\n\n\nAvec cette forme en tête, la réinitialisation du code officiel devient facile à lire :\n\n```python\ndef clean_up(kv: DynamicCache, origin_len: int):\n    \"\"\"\n    Truncate the KV Cache to the original length.\n    \"\"\"\n    for i in range(len(kv.key_cache)):\n        kv.key_cache[i] = kv.key_cache[i][:, :, :origin_len, :]\n        kv.value_cache[i] = kv.value_cache[i][:, :, :origin_len, :]\n```\n\n*Extrait de `kvcache.py` dans [**hhhuang/CAG**](https://github.com/hhhuang/CAG/blob/5c0d8ed6e28f34c72bb7208068f55cb3147d0c8c/kvcache.py#L120-L126), sous licence MIT.*\n\n`kv.key_cache[i]` contient les Keys de la couche $i$, de forme `[1, 8, seq_len, 128]`. La tranche `[:, :, :origin_len, :]` ne conserve, sur la troisième dimension — la longueur de séquence —, que la partie correspondant à la base.\n\n`origin_len` est enregistré à la fin du préchargement. Avant chaque question, le cache revient à cette longueur. Toute la « réinitialisation » consiste donc en deux découpages par couche, sur 32 couches.\n\n### Pourquoi tout charger ensemble ?\n\nÀ ce stade, on peut se demander : puisque le cache est si pratique, pourquoi ne pas précalculer **séparément** celui de chaque document, puis assembler ceux des documents récupérés ?\n\nLa difficulté vient de la propriété décrite plus haut. Les Keys et Values d'un token dépendent de tout le texte qui le précède, ainsi que de sa position dans la séquence. Calculé seul, le document B ignore qu'un document A le précédera ; sa numérotation de position repart aussi de zéro. Concaténer directement les deux caches produit donc un résultat différent d'une lecture continue de A puis B.\n\nDans l'introduction, les auteurs évoquent TurboRAG : son précalcul de caches par passage exige un réagencement supplémentaire des identifiants de position. CAG choisit une solution plus radicale : **si la base est fixe, tout lire ensemble**. Supprimer la recherche supprime aussi le problème d'assemblage.\n\nLe prix à payer : modifier un seul élément de la base oblige à recalculer tout le cache à partir de cette position.\n\n\n> [!TIP]\n> \n> **N'est-ce pas simplement du prompt caching ?**\n> \n> La [**question a aussi été posée sur GitHub**](https://github.com/hhhuang/CAG/issues/18) : mettre tous les documents dans le system prompt ne donnerait-il pas le même résultat ?\n> \n> Le contributeur brian030128 répond que la sortie est effectivement la même. La différence vient du coût élevé de conversion du prompt en cache KV. CAG le précalcule pour réduire le TTFT. D'autres demandent ensuite ce qui distingue cela du context caching proposé par les fournisseurs d'API.\n> \n> Les mécanismes sont très proches. Le [**prompt caching de Claude**](https://platform.claude.com/docs/en/build-with-claude/prompt-caching) et le [**context caching de Gemini**](https://ai.google.dev/gemini-api/docs/caching) permettent tous deux de réutiliser de longs prompts répétés. L'apport de cet article consiste surtout à comparer le préchargement complet avec cache au RAG sur les mêmes questions-réponses.\n\n\n## Discussion\n\n### Comment les expériences sont-elles menées ?\n\nLes auteurs utilisent deux jeux de questions-réponses :\n\n- **SQuAD 1.0** : la réponse est un extrait d'un paragraphe, avec une tâche surtout centrée sur la localisation dans un passage.\n- **HotPotQA** : il faut combiner plusieurs documents, ce qui demande un raisonnement à plusieurs étapes, ou multi-hop.\n\nPour étudier l'effet de la taille de la base, chacun est divisé en trois groupes : petit, moyen et grand. Pour HotPotQA small, par exemple, les auteurs sélectionnent 16 documents, les concatènent en un long texte de référence, puis ne gardent que les questions liées à ces documents.\n\n| Jeu de données | Groupe | Documents | Tokens | Questions |\n| --- | --- | ---: | ---: | ---: |\n| HotPotQA | Small | 16 | 21k | 1 392 |\n| HotPotQA | Medium | 32 | 43k | 1 056 |\n| HotPotQA | Large | 64 | 85k | 1 344 |\n| SQuAD | Small | 3 | 21k | 500 |\n| SQuAD | Medium | 4 | 32k | 500 |\n| SQuAD | Large | 7 | 50k | 500 |\n\n*D'après la Table 1 de l'article.*\n\nTous les systèmes répondent avec Llama 3.1 8B, exécuté sur huit Tesla V100 de 32 GB. Les références sont deux pipelines RAG construits avec LlamaIndex :\n\n- **Sparse RAG** : BM25, fondé sur les correspondances de mots-clés et les fréquences lexicales.\n- **Dense RAG** : un index d'embeddings OpenAI, classé par similarité sémantique.\n\nLes deux sont testés avec les top-1, 3, 5 et 10 passages.\n\nLe score est **BERTScore**, qui compare la similarité sémantique entre la réponse produite et la référence.\n\n\n> [!TIP]\n> \n> **Qu'est-ce que BERTScore ?**\n> \n> Il encode les deux textes avec un modèle de type BERT, récupère les vecteurs de chaque token, puis les apparie pour calculer leur similarité. Cela évite de traiter « Paris » et « Paris, capitale de la France » comme deux réponses entièrement différentes, contrairement à une comparaison stricte des chaînes.\n> \n> Il mesure toutefois la ressemblance plutôt que la justesse. Deux réponses différant par un seul nombre peuvent garder un BERTScore élevé. Les écarts à la troisième décimale dans le tableau suivant demandent donc une lecture prudente.\n\n\n### Qualité : en tête dans la plupart des cas\n\n[![Table 2 de l'article : BERTScore de Sparse RAG et Dense RAG avec top-1, 3, 5 et 10, ainsi que de CAG, sur les groupes petits, moyens et grands de HotPotQA et SQuAD](/content/papers/assets/13f5f729ad0290d70a0afcceaf6ec535895cb1017a05906e8dcfa0a9e91cae31.png)](/content/papers/assets/13f5f729ad0290d70a0afcceaf6ec535895cb1017a05906e8dcfa0a9e91cae31.png)\n\n*Table 2 de l'article, extraite de la [**page 4 du PDF v2**](https://arxiv.org/pdf/2412.15605v2#page=4). HotPotQA à gauche, SQuAD à droite ; le gras indique le meilleur score de chaque groupe.*\n\nCommençons par la dernière ligne de chaque groupe, « CAG (Ours) ».\n\nDans les groupes Small et Medium, CAG obtient le meilleur score sur les deux jeux. Sur HotPotQA small, par exemple, il atteint 0,7951, contre 0,7676 pour le meilleur RAG, Sparse top-5.\n\nPassons au groupe Large. Pour SQuAD, CAG reste premier avec 0,7734. Mais pour HotPotQA, le gras se trouve sur Sparse RAG top-5 à **0,7535**. CAG n'atteint que 0,7407, sous Sparse top-3 à 0,7463 et presque à égalité avec Dense top-3 à 0,7409.\n\nLes auteurs indiquent aussi dans le texte que l'écart entre CAG et RAG se réduit quand les données grossissent, et citent des travaux montrant une dégradation des modèles à long contexte sur les entrées très longues.\n\nUn détail supplémentaire mérite attention. Dans l'introduction, ils citent RULER et donnent à Llama 3.1 8B une **longueur de contexte effective de 32K**. Les quelque 85k tokens de HotPotQA large dépassent largement cette limite. Cela pourrait contribuer au recul de CAG dans ce groupe. L'article ne décompose toutefois pas cet effet ; il s'agit de notre interprétation du tableau.\n\nAutre observation : dans cinq des six groupes, le meilleur réglage RAG utilise BM25. Seul SQuAD medium donne une courte avance de 0,0009 à Dense top-10. Les auteurs estiment que ces deux jeux sont peut-être trop faciles : les mots-clés suffisent à trouver l'essentiel des informations. Les références RAG ne rencontrent donc pas ici de difficulté exigeant spécifiquement une recherche sémantique.\n\n### Vitesse : éviter la relecture\n\nPassons au temps d'exécution.\n\n[![Table 3 de l'article : temps de recherche et de génération des configurations RAG, de CAG et de l'In-Context Learning sur les trois tailles HotPotQA](/content/papers/assets/fbdd7c78239b53372fb8d168a05e1cb9ffda9cfdc3c6301abb36dd2913389490.png)](/content/papers/assets/fbdd7c78239b53372fb8d168a05e1cb9ffda9cfdc3c6301abb36dd2913389490.png)\n\n*Table 3 de l'article, extraite de la [**page 4 du PDF v2**](https://arxiv.org/pdf/2412.15605v2#page=4). Valeurs en secondes ; Retrieval désigne la recherche et Generation la génération.*\n\nUne référence supplémentaire apparaît : **In-Context Learning**. Elle transmet directement tout le texte de référence avec la question et calcule le cache KV à chaque fois. C'est la relecture intégrale à chaque question décrite au début.\n\nLa comparaison de cette ligne avec CAG isole l'économie apportée par le préchargement :\n\n| Groupe HotPotQA | Relecture à chaque question | CAG | Facteur |\n| --- | ---: | ---: | ---: |\n| Small (21k) | 9,32 s | 0,85 s | Environ 11 fois |\n| Medium (43k) | 26,37 s | 1,41 s | Environ 19 fois |\n| Large (85k) | 92,08 s | 2,26 s | Environ 41 fois |\n\nPlus la base est grande, plus la relecture coûte cher et plus le préchargement devient avantageux.\n\nLa comparaison avec Sparse RAG donne cependant un autre résultat. Dans le groupe Large, BM25 top-3 consomme seulement 0,0008 seconde de recherche et 0,67 seconde de génération, moins d'une seconde au total, contre 2,26 secondes pour CAG. BM25 ne transmet que trois courts passages au modèle : le contexte à traiter est naturellement plus court.\n\nLe temps de CAG augmente aussi avec la base : 0,85, puis 1,41, puis 2,26 secondes. Le cache évite la relecture, mais à chaque token généré, l'attention doit encore parcourir l'ensemble des 85k tokens du cache.\n\nUn chiffre manque au tableau : **le temps de construction du cache**. La Table 3 mesure uniquement l'inférence par question après préchargement. Le code officiel enregistre la durée de ce préchargement, mais l'article ne la publie pas. Pour une base mise à jour quotidiennement, cette mesure compte beaucoup.\n\n### Les questions laissées ouvertes\n\nLa section Limitations est explicite : tous les documents pertinents doivent tenir dans le contexte du modèle. La méthode convient à des scénarios de taille maîtrisée, comme la base interne d'une petite entreprise, une FAQ ou un centre de service client. Elle devient peu pratique lorsque les données grossissent beaucoup.\n\nLe raisonnement précédent permet aussi d'identifier quelques difficultés d'ingénierie :\n\n1. **Des documents fréquemment mis à jour.** Tout le cache après la position modifiée doit être recalculé. L'article ne mesure pas ce coût.\n2. **Des droits d'accès différents selon les utilisateurs.** Un cache contient toute la base. Des permissions différentes peuvent donc imposer un cache distinct par combinaison de documents. C'est une déduction de cette note, non discutée dans l'article.\n3. **Un cache lié au modèle.** Le cache KV est un état intermédiaire calculé par un modèle et une version précis. Changer de modèle exige de le reconstruire.\n\n## Conclusion\n\nCAG tient en une phrase : **si la base est stable et tient dans le contexte, la faire lire une fois au modèle, conserver le cache KV, puis ne lire que les questions suivantes.**\n\nDans le cadre expérimental de l'article :\n\n- **Le gain face à la relecture intégrale est important.** Sur HotPotQA large, on passe de 92,08 à 2,26 secondes, soit environ un facteur 41.\n- **La qualité est généralement comparable ou supérieure à celle du RAG.** CAG arrive premier sur Small et Medium ; sur Large, il reste premier pour SQuAD, mais perd contre BM25 top-5 sur HotPotQA.\n- **Un RAG BM25 léger peut être plus rapide que CAG.** Avec seulement trois courts passages, son temps total reste inférieur à une seconde.\n\nCes résultats portent sur un unique modèle 8B, une base d'au plus environ 85k tokens et une mesure de similarité sémantique, BERTScore.\n\nLe titre dit « Ne faites pas de RAG ». Le corps de l'article donne une réponse plus nuancée : pour une petite base stable et consultée à répétition, on peut envisager de tout mémoriser dans le cache et de supprimer la recherche.\n\nQuand la base devient trop grande pour tenir en mémoire de contexte, il faudra tout de même retourner chercher.\n","imageSizes":{"/content/papers/assets/1ccb714a2b9e40fe07cb712c508763c4447c40b66c78a8c364a030957c65a789.png":{"width":694,"height":575},"/content/papers/assets/13f5f729ad0290d70a0afcceaf6ec535895cb1017a05906e8dcfa0a9e91cae31.png":{"width":694,"height":1028},"/content/papers/assets/fbdd7c78239b53372fb8d168a05e1cb9ffda9cfdc3c6301abb36dd2913389490.png":{"width":689,"height":717}}}
