Aller au contenu

APEX v2 — Architecture d'un Hyperviseur de Tiering Mémoire CXL

Version : 2.0
Date : Février 2026
Statut : Architecture greenfield
Stack : C++23 / C (eBPF, libbpf CO-RE) / Kernel Linux ≥ 6.11 (≥ 6.14 pour le self-tuning DAMOS)


1. Résumé exécutif

APEX v2 est un daemon Linux en espace utilisateur qui orchestre le placement dynamique de la mémoire entre la DRAM locale et la mémoire CXL pour des workloads de classe entreprise. Son objectif est de réduire le coût total de possession (TCO) de la mémoire serveur en exploitant massivement le CXL, sans dégrader les latences P99 des applications critiques.

Ce document synthétise les leçons d'un premier prototype (APEX v1, ~40k LOC C++/eBPF), les résultats d'une analyse croisée par des experts indépendants, et une revue de la littérature scientifique récente — SOSP '24, OSDI '24 et '25, ASPLOS '22 et '23, MICRO '23, HotOS '25, et les sessions LSFMM+BPF 2024-2025. Il propose une architecture complète pour un produit de qualité industrielle.

Le principe fondateur est le suivant : l'intelligence métier et la gestion du risque vivent en espace utilisateur ; la télémétrie granulaire et l'exécution physique des migrations vivent dans le noyau Linux et le matériel. APEX ne déplace jamais un seul octet de mémoire lui-même. Il fixe les politiques, calcule les budgets, et délègue l'exécution au kernel.


2. Le problème

Les serveurs modernes équipés de CXL présentent une architecture mémoire hétérogène. La DRAM locale offre une latence d'accès non-chargée d'environ 80 nanosecondes et une bande passante de 200 GB/s par socket. La mémoire CXL, connectée via le bus PCIe Gen5, offre des capacités de l'ordre du téraoctet à un coût réduit, mais avec une latence non-chargée d'environ 170-250ns selon le contrôleur. Les travaux de Sun et al. ("Demystifying CXL Memory with Genuine CXL-Ready Systems and Devices", MICRO '23) sur du matériel CXL réel (Intel Sapphire Rapids et Emerald Rapids) ont mesuré des latences entre 170ns (contrôleurs ASIC) et 400ns+ (émulations multi-socket), avec une réduction de l'efficacité du prefetcher L2 sous CXL qui amplifie l'impact au-delà de la simple différence de latence brute.

Le défi est triple. Premièrement, il faut identifier dynamiquement quelles régions mémoire d'une application sont "chaudes" et lesquelles sont "froides", pour placer les premières en DRAM et les secondes en CXL. Deuxièmement, il faut exécuter ces migrations sans dégrader les performances, ce qui implique de gérer la contention sur le bus PCIe partagé, de préserver l'intégrité des Transparent Huge Pages (2 Mo), et de réagir aux changements de phase des workloads. Troisièmement — et c'est la raison d'être d'un daemon userspace plutôt que d'une solution purement in-kernel — il faut que ces décisions respectent des priorités métier : dans un cluster Kubernetes, un Pod Redis de production est infiniment plus important qu'un batch Hadoop de nuit, et le kernel Linux ne sait pas faire cette distinction.


3. Leçons du prototype v1

APEX v1 a fait l'objet d'une analyse architecturale complète suivie d'un fact-check croisé par cinq analystes indépendants contre le code source réel. Sept défauts structurels ont été validés. Ils forment les contraintes de conception de v2.

3.1 Traçage aveugle par page faults (sévérité : moyenne)

Le hook eBPF sur handle_mm_fault ne voyait que les premières allocations, les swap-in et les COW. Une fois le PTE installé, le MMU matériel sert les accès directement, sans intervention du kernel. Pour une base de données pré-allouant 10 Go, zéro événement BPF après le mapping initial. Le scoring d'APEX v1 combinait six signaux supplémentaires (MGLRU, PSI, TLB flushes, etc.) qui atténuaient partiellement ce problème, mais l'angle mort restait réel pour les pages pré-allouées avant le démarrage du daemon. Résolution v2 : DAMON échantillonne le bit Accessed des PTE, mis à 1 par le hardware à chaque accès. Il voit 100% de la mémoire, y compris la mémoire pré-allouée.

3.2 Déconnexion scoring-exécution en démotion (sévérité : haute)

Le DemotionSelector calculait un score de froideur par page en O(N + K log K), puis appelait memory.reclaim, une interface qui accepte uniquement un nombre de bytes — pas des adresses. Le kernel utilisait sa propre LRU aveugle pour choisir les victimes, rendant le scoring granulaire entièrement inutile. L'asymétrie avec la promotion (qui utilisait move_pages(2) avec adresses exactes) était flagrante. Résolution v2 : DAMOS exécute les migrations sur les régions identifiées par DAMON lui-même. Le scoring et l'exécution sont couplés : DAMON détecte les régions froides, DAMOS les migre. Le daemon écrit des règles, pas des adresses.

3.3 Blocage single-thread (sévérité : haute)

L'appel move_pages(2) synchrone dans le thread principal bloquait la boucle de 100ms. Pendant l'exécution du syscall (qui prend le mmap_lock et fait de l'I/O inter-nœuds), le ring buffer eBPF saturait, le circuit breaker ne pouvait pas réagir, et le kill-switch était inaccessible. La BoundedSPSCQueue existait dans le code mais n'était jamais instanciée. Résolution v2 : le thread de contrôle n'exécute aucun syscall bloquant lié à la migration. Les promotions urgentes passent par un pool de std::jthread workers dédiés.

3.4 Absence de gestion des THP (sévérité : haute)

Le BPF utilisait exclusivement PAGE_SHIFT = 12 (pages 4 Ko). Migrer une page de 4 Ko au sein d'une THP de 2 Mo force le kernel à fragmenter la Huge Page en 512 morceaux (THP split), détruisant le hit-rate TLB et effondrant les latences P99 des bases de données. Résolution v2 : DAMOS opère nativement au niveau folio (unité atomique de taille variable : 4 Ko, 64 Ko, 2 Mo). Les THP ne sont jamais fragmentées par les migrations.

3.5 Absence de distinction File-backed / Anonymous (sévérité : moyenne-haute)

APEX v1 ne distinguait pas la mémoire anonyme (tas, pile, shmem) de la mémoire file-backed (page cache). Démotier du page cache vers CXL est un anti-pattern : le kernel peut simplement le supprimer (drop) puisqu'il est sauvegardé sur disque. Le CXL ne se justifie financièrement que pour la mémoire anonyme, dont la seule alternative est le swap. Résolution v2 : les règles DAMOS utilisent le filtre natif qui exclut la mémoire file-backed des actions de migration. Seule la mémoire anonyme et le shmem/tmpfs sont éligibles au tiering.

3.6 Problème ABA des adresses virtuelles (sévérité : moyenne)

Sans hook sur munmap/mremap/mmap, un VPN libéré puis réalloué héritait du score d'accès de l'ancien mapping. Le TTL de 300s du garbage collector ne couvrait pas les réutilisations rapides (JVM, bases de données mmap). Résolution v2 : un hook eBPF sur tracepoint:syscalls:sys_enter_munmap invalide les données de monitoring de la plage libérée. DAMON, qui opère au niveau régions physiques (pas VPN), est moins vulnérable à ce problème par construction.

3.7 Conflit avec AutoNUMA (sévérité : moyenne)

Si numa_balancing=1 était actif, le kernel et APEX se battaient pour déplacer les mêmes pages dans des directions opposées. APEX documentait le problème et émettait un warning mais ne refusait pas de démarrer. Résolution v2 : APEX désactive AutoNUMA au démarrage (sysctl kernel.numa_balancing=0) et assume le monopole total sur les promotions et les démotions. La raison est double : premièrement, AutoNUMA procède par hinting page faults (il retire les droits d'accès aux pages pour forcer un page fault au prochain accès), ce qui génère des invalidations TLB (shootdowns) extrêmement coûteuses qui sabotent la latence applicative — exactement ce qu'APEX cherche à protéger. Deuxièmement, les données du LSFMM 2025 montrent que NUMA balancing v2 dégrade la performance de 7.34%, alors que DAMON self-tuning l'améliore de 4.42%. L'alliance DAMOS (macro) + PEBS (micro) est largement supérieure à AutoNUMA pour la promotion. Exception multi-socket : sur les serveurs à plusieurs sockets DRAM (ex. 2× Xeon avec DRAM locale par socket), la désactivation totale d'AutoNUMA détruirait la localité CPU ↔ DRAM inter-sockets. Sur ces topologies, le Topology Parser (§5.3.1) détecte la présence de plusieurs sockets et le Policy Compiler adopte une stratégie chirurgicale : il désactive uniquement la démotion automatique du kernel via echo 0 > /sys/kernel/mm/numa/demotion_enabled (disponible depuis Linux 5.15), ce qui empêche le kernel de démotier vers la mémoire lente lors du reclaim, tout en conservant l'équilibrage AutoNUMA pour la localité DRAM inter-sockets. Sur les serveurs single-socket (cas le plus courant pour CXL aujourd'hui), la désactivation totale reste le comportement par défaut.


4. Fondations scientifiques

Chaque principe documenté ici est sourcé dans la littérature récente et engendre une décision architecturale concrète tracée dans la section 5.

4.1 La latence chargée prime sur la fréquence d'accès

La majorité des systèmes de tiering existants (TPP, HeMem, MEMTIS) placent les pages fréquemment accédées en DRAM et les autres en CXL. Ce modèle est nécessaire mais insuffisant.

Colloid (Vuppalapati & Agarwal, SOSP '24, Cornell) a démontré que la latence chargée (loaded access latency) de chaque tier est le signal déterminant. La latence non-chargée du CXL est d'environ 250ns, mais sous contention, elle peut exploser au-delà de 500ns. Inversement, concentrer trop de pages chaudes en DRAM augmente aussi sa latence effective. Colloid utilise la loi de Little appliquée aux compteurs CHA (Caching and Home Agent) du processeur Intel pour mesurer la latence effective de chaque tier en temps réel : L_tier = Occupancy_tier / Requests_tier. Il équilibre ensuite les pages chaudes entre les tiers pour minimiser la latence globale, pas simplement pour tout concentrer en DRAM. Intégré sur HeMem, TPP et MEMTIS, Colloid améliore systématiquement la performance.

Décision architecturale : APEX intègre la mesure de latence chargée par tier via les compteurs CHA/Uncore PMU comme signal de premier ordre. Une promotion n'est bénéfique que si la latence du tier cible est suffisamment inférieure à celle du tier source sous la charge actuelle.

4.2 Le tiering excessif peut être pire que l'inaction

Soar/Alto (Liu et al., OSDI '25, "Tiered Memory Management Beyond Hotness") a démontré que de nombreux systèmes de tiering state-of-the-art sous-performent par rapport à un simple first-touch (zéro migration) quand les migrations sont excessives. Ce phénomène est exacerbé sur CXL, où le gap de latence avec la DRAM se réduit avec les contrôleurs ASIC modernes, rendant le coût de migration proportionnellement plus élevé par rapport au bénéfice. Alto propose d'utiliser la latence observée moyennée (Amortized Observed Latency, AOL) pour réguler dynamiquement le taux de migration. Sur un serveur Intel Sapphire Rapids avec CXL ASIC, Alto+TPP surpasse TPP seul de 2% à 178% selon les workloads.

De plus, les pages "froides" au sens de la fréquence d'accès peuvent être performance-critical si elles sont accédées à des moments de haute contention — un cas que les heuristiques basées uniquement sur la fréquence ne capturent pas.

Décision architecturale : APEX inclut un mécanisme d'auto-régulation inspiré d'Alto. Si la latence observée reste dans des bornes acceptables, les migrations sont réduites. Au démarrage, un paramètre min_latency_improvement_ratio (défaut : 1.2) sert de garde-fou conservateur. En régime établi, ce seuil statique est supplanté par le Latency-Aware Regulator (§5.3.4) qui combine la bisection adaptative de Colloid (équilibrage dynamique de L_CXL / L_DRAM) et un monitoring continu de l'AOL : une promotion n'est autorisée que si les latences chargées mesurées la justifient et que l'AOL globale confirme un bénéfice net des migrations récentes.

4.3 La migration doit être asynchrone et non-bloquante

Nomad (Xiang et al., OSDI '24, UT Arlington / Intel Labs) a introduit la migration transactionnelle : au lieu du schéma classique unmap-copy-remap qui bloque l'application pendant la copie, Nomad copie la page sans la démapper d'abord, vérifie si elle a été modifiée pendant la copie via le dirty bit du PTE, et ne remappe que si la copie est propre. Ce mécanisme rend la migration complètement asynchrone et la retire du chemin critique d'exécution. Les résultats montrent jusqu'à 6× d'amélioration sur TPP sous pression mémoire.

Nomad introduit également le tiering non-exclusif (page shadowing) : une page promue en DRAM garde une copie fantôme en CXL. La démotion d'une page clean se réduit à un simple remapping du PTE sans copie physique, ce qui est une optimisation majeure pour les workloads en lecture.

Décision architecturale : le coût effectif d'une migration — et donc l'agressivité optimale des politiques d'APEX — dépend du mécanisme kernel sous-jacent. APEX détecte au démarrage si le kernel supporte la migration transactionnelle (Nomad, disponible à partir de Linux 6.x) et le page shadowing. En présence de ces mécanismes, le Latency-Aware Regulator (§5.3.4) abaisse le seuil de rentabilité des migrations, permettant des promotions plus fréquentes et un tiering plus réactif. Sans ces mécanismes, APEX adopte une posture conservatrice pour compenser le coût de migration plus élevé du schéma classique. Au niveau du daemon lui-même, aucune migration ne bloque le thread de contrôle : DAMOS exécute les démotions via des kthreads asynchrones (kdamond), et les promotions urgentes sont déléguées à un pool de worker threads séparé.

4.4 Le Folio est l'unité atomique

Meta TPP (ASPLOS '23) et les développements récents du sous-système MM de Linux (principalement le travail de Matthew Wilcox, commençant à merger dans Linux 5.16) ont formalisé la transition des "pages" vers les "folios" — des blocs mémoire physiquement contigus, de taille variable (4 Ko, 64 Ko, 2 Mo), traités comme des unités atomiques indivisibles. Cette abstraction résout l'ambiguïté historique de struct page / compound pages, où le code kernel devait constamment distinguer head pages et tail pages.

Les applications entreprise (PostgreSQL, Redis, JVM) utilisent massivement des Transparent Huge Pages (THP) de 2 Mo. Une seule entrée TLB couvrant 2 Mo remplace 512 entrées de 4 Ko, ce qui est critique quand le STLB d'un Xeon moderne ne dispose que de ~1536 à 2048 entrées. Un système de tiering qui migre à granularité 4 Ko est contraint de splitter les huge pages pour ne promouvoir que les sous-pages chaudes — opération que MEMTIS (SOSP '23) reconnaît comme coûteuse, impliquant migration de sous-pages et TLB shootdowns, et pouvant dégrader significativement les latences tail. DAMOS opère nativement au niveau folio : il migre le bloc entier ou ne migre rien, garantissant l'intégrité des THP.

Décision architecturale : APEX ne manipule jamais des adresses de pages 4 Ko individuelles. Toute métrique de hotness est agrégée au niveau folio, et toute interaction avec le kernel passe par des mécanismes folio-aware. Le trade-off est explicitement accepté : on perd en précision de placement (un folio de 2 Mo est migré même si seule une fraction de ses sous-pages est chaude), mais on préserve la performance TLB et on réduit la surcharge de migration (1 migration de 2 Mo vs. potentiellement 512 migrations de 4 Ko avec autant de modifications PTE et de TLB flushes).

4.5 DAMON est le standard industriel

DAMON (Data Access MONitor, mainline Linux depuis v5.15) est utilisé en production par AWS (Aurora Serverless v2) et SK hynix (HMSDK pour le tiering CXL). Les patches DAMOS_MIGRATE_HOT et DAMOS_MIGRATE_COLD de SK hynix sont mergés depuis Linux 6.11 et montrent une réduction du ralentissement de 11% à 3-5% sur Redis (YCSB, distribution zipfienne). Le self-tuning DAMON (patchset mars 2025, SeongJae Park, LSFMM+BPF 2025) permet à DAMON d'auto-ajuster ses seuils de hotness/coldness en visant un taux d'utilisation cible du fast tier. Les résultats montrent 4.42% d'amélioration en promotion, là où NUMA balancing v2 dégrade de 7.34% à cause de sa nature synchrone.

DAMON fonctionne par échantillonnage du bit Accessed des PTE, regroupe les pages de même température en "régions" (complexité O(R) au lieu de O(N)), et son overhead CPU est mathématiquement borné à < 0.5%.

Limitations documentées : DAMON ne monitore que la mémoire en espace utilisateur (confirmé par le mainteneur au LSFMM 2025). Sa résolution temporelle est de l'ordre de l'intervalle d'agrégation (100ms à quelques secondes), insuffisant pour les changements de phase rapides. Il ne fournit pas d'information de latence d'accès.

Décision architecturale : DAMON/DAMOS est le moteur de monitoring et d'exécution macro. Il est complété par un capteur rapide (PEBS/IBS) pour les promotions urgentes et par des compteurs de latence (CHA) pour la dimension Colloid.

4.6 La QoS-aware tiering est le prochain champ de bataille

Mercury (fin 2024) propose un tiering QoS-aware in-kernel avec gestion de SLO par application, surpassant Colloid de 20.3% et TPP de 53.4%. C'est un signal stratégique : le kernel va progressivement absorber l'intelligence de tiering. La valeur d'un daemon userspace réside dans l'intégration avec l'écosystème applicatif (Kubernetes, Prometheus, SLA business) et la capacité à prendre des décisions cross-cgroup que le kernel ne fera jamais nativement.

Décision architecturale : APEX se positionne comme un orchestrateur de politiques au-dessus du kernel, pas comme un remplacement. Sa complexité se concentre sur la traduction SLA-business → politiques kernel, pas sur le micro-management des pages.


5. Architecture

5.1 Vue d'ensemble : trois plans isolés

┌─────────────────────────────────────────────────────────────────────────┐
│                     MANAGEMENT PLANE                                     │
│   Kubernetes Operator (Go) · CRDs MemoryTieringPolicy · Prometheus      │
│   Traduit les intentions humaines en contrats de QoS                     │
└──────────────────────────────┬──────────────────────────────────────────┘
                               │ gRPC / Unix Domain Socket
┌──────────────────────────────▼──────────────────────────────────────────┐
│                     CONTROL PLANE (C++23)                                │
│                                                                          │
│  ┌───────────────┐  ┌────────────────┐  ┌───────────────────────────┐   │
│  │ QoS Policy    │  │ Risk Engine    │  │ Latency-Aware             │   │
│  │ Engine        │  │ (FSM + Breaker)│  │ Regulator (§4.1, §4.2)   │   │
│  └──────┬────────┘  └───────┬────────┘  └──────────┬────────────────┘   │
│         └──────────┬────────┘                       │                   │
│         ┌──────────▼────────────────────────────────▼───────────────┐   │
│         │                POLICY COMPILER                            │   │
│         │  Sortie : règles DAMOS (sysfs) + coordination NUMA        │   │
│         └──────────────────────┬───────────────────────────────────┘   │
│                                │  Écritures sysfs non-bloquantes       │
│  ┌─────────────────────────────┼──────────────────────────────────┐    │
│  │  Pool Workers Promotion     │  Topology Parser (HMAT/CDAT)     │    │
│  │  (std::jthread × 2-4)      │  (init. au démarrage)            │    │
│  └─────────────────────────────┼──────────────────────────────────┘    │
└────────────────────────────────┼──────────────────────────────────────┘
═════════════════════════════════╪═══════════════════════════════════════
┌────────────────────────────────▼──────────────────────────────────────┐
│          DATA & TELEMETRY PLANE (Kernel + Hardware)                    │
│                                                                        │
│  Observation :                                                         │
│  ┌────────────┐  ┌──────────────────┐  ┌──────────────────────────┐   │
│  │ DAMON      │  │ PEBS/IBS (eBPF)  │  │ CHA/Uncore PMU           │   │
│  │ Radar Macro│  │ Sniper Micro     │  │ Latence Chargée          │   │
│  │ Bit Access.│  │ L3 cache misses  │  │ Loi de Little            │   │
│  └─────┬──────┘  └────────┬─────────┘  └────────────┬─────────────┘   │
│        │                  │                          │                 │
│  Exécution :              │                          │                 │
│  ┌─────▼──────────────────▼──────────────────────────▼─────────────┐   │
│  │ DAMOS (kdamond kthreads)  ·  move_pages (worker threads)       │   │
│  │ Démotion async DRAM→CXL  ·  Promotion async CXL→DRAM          │   │
│  │ Folio-aware, THP préservé ·  Adresses ciblées par PEBS         │   │
│  └────────────────────────────────────────────────────────────────┘   │
│                                                                        │
│  Nœud 0 : DRAM (~80ns)  ◄══ PCIe Gen5 ══►  Nœud 1 : CXL (~170-250ns)│
└────────────────────────────────────────────────────────────────────────┘

5.2 Data & Telemetry Plane (Kernel + Hardware)

Ce plan est décrit en premier car ses capacités et ses limitations contraignent tout le reste.

5.2.1 Radar Macro — DAMON/DAMOS

APEX configure DAMON via l'interface sysfs (/sys/kernel/mm/damon/admin/). DAMON opère en mode physical address space pour le tiering système. Il échantillonne périodiquement le bit Accessed des PTE (mis à 1 par le hardware à chaque accès mémoire), le remet à 0, et au cycle suivant identifie les pages dont le bit est repassé à 1 comme "chaudes". Il regroupe les pages de même température en "régions", réduisant la complexité spatiale de O(N) pages à O(R) régions (typiquement quelques milliers).

DAMOS exécute automatiquement les migrations quand les conditions spécifiées par APEX sont remplies. Les actions pertinentes pour le tiering CXL sont DAMOS_MIGRATE_COLD (démotion DRAMCXL, mergée Linux 6.11, SK hynix) et DAMOS_MIGRATE_HOT (promotion CXLDRAM). Le kernel lance des kthreads dédiés (kdamond) qui font le transfert en arrière-plan, via DMA, en respectant les folios. Le daemon userspace n'est jamais bloqué.

Les paramètres DAMOS que le Policy Compiler d'APEX ajuste dynamiquement pour chaque cgroup sont les suivants : les seuils d'accès (min/max access rate et min/max age) qui définissent ce qui est "chaud" ou "froid" ; l'action (DAMOS_MIGRATE_COLD ou DAMOS_MIGRATE_HOT) ; le nœud cible (target_nid) ; les quotas en bytes/s et ms/s qui bornent le volume de migration ; et les filtres (exclusion des pages file-backed, exclusion des pages young pour la protection anti-thrashing). Un filtre est absolument critique pour le fonctionnement en mode paddr : DAMOS_FILTER_TYPE_MEMCG (disponible depuis Linux 6.3, implémenté dans mm/damon/paddr.c via folio_memcg_check()). En mode physical address space, DAMON voit la RAM comme un bloc brut, agnostique des processus et des cgroups. Sans filtre MEMCG, une règle de démotion DAMOS affecterait toute la mémoire du serveur, indépendamment des Pods et de leurs contrats QoS. Le Policy Compiler doit récupérer l'identifiant du memory cgroup Kubernetes ciblé (le chemin du cgroup depuis le point de montage cgroupfs, par exemple /kubepods.slice/kubepods-burstable.slice/kubepods-burstable-pod<UID>.slice/) et créer un filtre MEMCG dans sysfs pour chaque règle DAMOS, afin que le scan physique n'impacte que les Pods sous contrat.

Le self-tuning DAMON (Linux 6.14+, goal metric free_mem_rate) permet à DAMOS d'auto-ajuster ses seuils pour maintenir un headroom de mémoire libre dans le fast tier. APEX exploite cette fonctionnalité pour le headroom proactif (cf. §5.3.3) plutôt que de fixer des seuils absolus.

Limitation architecturale du mode paddr : en espace d'adressage physique, l'allocateur Buddy du noyau entrelace les pages de différents processus et cgroups. Les régions DAMON couvrent donc un mélange de pages provenant de Pods distincts. Le scoring de température (chaud/froid) calculé par DAMON reflète l'agrégation de ces pages hétérogènes, et non le profil d'accès d'un seul Pod. Le filtre DAMOS_FILTER_TYPE_MEMCG ne s'applique qu'au moment de l'exécution des actions DAMOS (migration), pas pendant la phase de scoring. En pratique, cette limitation est acceptable pour la démotion (les pages froides d'un Pod protégé sont exclues de l'action par le filtre MEMCG, même si le scoring est bruité), mais elle réduit la sensibilité de la détection fine par Pod. Pour les cas d'utilisation nécessitant une QoS stricte par Pod (ex. isolation de performance entre tenants), une future évolution pourra utiliser le mode vaddr avec un contexte DAMON dédié par processus monitoré, au prix d'un overhead de gestion accru (un kdamond par cible).

DAMOS résout simultanément quatre des sept défauts de v1 : il voit toute la mémoire (pas seulement les page faults, §3.1), il couple scoring et exécution (§3.2), il opère au niveau folio (§3.4), et il distingue nativement anonyme/file-backed via ses filtres (§3.5).

5.2.2 Sniper Micro — Hardware PMU via eBPF

DAMON est inadapté pour la promotion urgente : sa résolution temporelle (100ms-secondes) est trop lente pour détecter un changement de phase rapide (une page CXL soudainement accédée en boucle). Pour ce cas, APEX déploie un programme eBPF (écrit en C, compilé avec Clang/CO-RE via libbpf) attaché à perf_event_open sur l'événement MEM_LOAD_RETIRED.L3_MISS (Intel PEBS) ou l'équivalent AMD IBS.

Le processeur logge les adresses des accès qui manquent le cache L3 (donc qui touchent la RAM directement) dans un buffer hardware. Le programme eBPF effectue l'agrégation directement dans le kernel via une BPF_MAP_TYPE_LRU_PERCPU_HASH. Le choix d'une map LRU per-CPU combine deux propriétés indispensables. Premièrement, la sécurité NMI : l'événement PEBS déclenche une NMI (Non-Maskable Interrupt), et les maps globales (HASH, LRU_HASH) protègent leurs buckets par des raw_spinlock. En contexte NMI, si le verrou d'un bucket est déjà tenu par un autre CPU (ou par le même CPU dans un contexte interrompu), raw_spin_trylock échoue silencieusement et la mise à jour est abandonnée (-EBUSY). Sur un serveur 64+ cœurs sous charge, la contention entraînerait une perte massive d'échantillons. Avec une map per-CPU, chaque cœur incrémente son compteur dans une zone mémoire strictement locale, sans aucun verrou. Deuxièmement, la résilience aux débordements : sans le mécanisme LRU, une BPF_MAP_TYPE_PERCPU_HASH qui atteint sa capacité maximale rejette silencieusement les nouvelles insertions avec -E2BIG, rendant le sniper PEBS aveugle aux nouveaux accès mémoire entre deux cycles de drain. Avec le préfixe LRU, le noyau évince automatiquement les entrées les moins récemment utilisées, garantissant que les adresses les plus actives restent toujours visibles. La map est dimensionnée généreusement (≥ 64K entrées) pour minimiser le taux d'éviction et conserver un historique d'accès suffisamment profond entre deux cycles de collecte. Le daemon C++ draine et remet à zéro l'intégralité de la map à 10 Hz via l'API bpf_map_lookup_and_delete_batch() (Linux ≥ 5.6), qui effectue une collecte atomique sans fenêtre de perte de télémétrie entre un lookup et un delete séparés ; le daemon itère ensuite sur les valeurs de tous les CPU pour calculer la somme par clé.

Un point d'implémentation critique : la clé de cette map doit être une structure combinée {u32 tgid, u64 vaddr_aligned} — et non un Page Frame Number (PFN). La raison est que move_pages(2), le seul syscall capable de forcer une migration NUMA de pages déjà résidentes, exige un tableau d'adresses virtuelles valides dans l'espace d'adressage du processus ciblé. Il n'accepte aucune adresse physique, et le kernel Linux ne fournit aucune API performante pour faire le reverse-mapping PFN → {PID, vaddr} depuis l'espace utilisateur (ce problème fait l'objet d'une proposition de syscall move_phys_pages par Gregory Price, non mergée à ce jour). Attention au padding C : le compilateur insère 4 octets de bourrage implicites entre le u32 et le u64 pour respecter l'alignement mémoire. La fonction de hachage eBPF lit la totalité brute de la clé (16 octets), incluant ce padding. Si le padding n'est pas initialisé, des déchets de pile produiront des hachages différents pour la même adresse logique, remplissant la map de doublons. Le code eBPF doit impérativement initialiser la structure entière à zéro avant utilisation (struct map_key key = {}; ou __builtin_memset(&key, 0, sizeof(key));).

Un piège sémantique majeur concerne l'identification du processus. Le helper bpf_get_current_pid_tgid() retourne un entier de 64 bits où les 32 bits de poids fort sont le TGID (Thread Group ID, qui correspond au PID de l'espace utilisateur) et les 32 bits de poids faible sont le PID kernel (qui correspond au Thread ID de l'espace utilisateur). Le programme eBPF doit impérativement extraire le TGID par décalage binaire : u32 tgid = bpf_get_current_pid_tgid() >> 32;. Un cast implicite en u32 (les 32 bits faibles) capturerait le Thread ID, fragmentant les accès d'un processus multi-threadé (PostgreSQL, Redis) sur de multiples clés BPF map et détruisant le regroupement par processus indispensable au batching de move_pages.

L'adresse virtuelle est capturée via ctx->addr (le champ Linear Address de la structure bpf_perf_event_data, activé par le flag PERF_SAMPLE_ADDR lors de la configuration de perf_event_open). Un point critique : lors de la configuration de l'événement perf_event_open, il faut impérativement activer les flags attr.exclude_kernel = 1 et attr.exclude_hv = 1 pour filtrer les échantillons provenant du code noyau et de l'hyperviseur. Sans ces flags, le compteur matériel capture les accès mémoire de toutes les couches de privilège, y compris les adresses de l'espace noyau (typiquement vaddr > 0xffff800000000000 sur x86_64). Ces adresses ne sont pas migrables par move_pages(2) (qui retournerait -EFAULT) et pollueraient la BPF map avec des entrées inutiles. Le programme eBPF n'effectue aucun filtrage NUMA : tenter de déterminer le nœud NUMA d'une adresse virtuelle depuis l'eBPF impliquerait un parcours manuel des tables de pages (pgd→p4d→pud→pmd→pte) via des bpf_probe_read_kernel imbriqués. Ce parcours est extrêmement fragile (dépendant de Huge Pages, 5-level paging), lent, non portable, et hautement susceptible d'être rejeté par le vérificateur eBPF en contexte NMI. L'eBPF se contente d'appliquer un masque d'alignement à la page de base matérielle (vaddr & ~(PAGE_SIZE - 1), soit un alignement aveugle à 4 Ko). Le programme eBPF n'a aucun moyen matériel de déterminer si une adresse appartient à une page standard 4 Ko ou à une Transparent Huge Page (THP) de 2 Mo. Pour une THP chaude, l'eBPF remontera donc plusieurs adresses 4 Ko distinctes au daemon (jusqu'à 512 pour une THP de 2 Mo intensément accédée). Le kernel, lors de l'appel ultérieur à move_pages(2), résoudra ces multiples adresses vers le même struct folio sous-jacent et le migrera atomiquement une seule fois — la correctité est donc garantie. Cependant, le coût de performance de ces adresses redondantes n'est pas négligeable : chaque adresse implique un page table walk et une acquisition de folio_lock sous le mmap_read_lock, gaspillant du temps de contention. Le daemon C++ doit donc dédoublonner les adresses en userspace avant d'appeler move_pages(2). La méthode est simple : un hash set sur les adresses masquées à 2 Mo (vaddr & ~((1 << 21) - 1)) élimine les doublons THP, réduisant potentiellement le nombre d'adresses d'un facteur 512× pour les workloads THP-intensifs (bases de données, JVM). Le coût de cette opération est négligeable (quelques microsecondes pour un hash set de quelques milliers d'entrées) comparé au gain sur le syscall.

C'est le daemon C++ qui détermine le placement NUMA des pages, via le mode interrogation natif de move_pages(2) : en appelant move_pages(pid, count, pages, NULL, status, 0) avec le paramètre nodes à NULL, le kernel ne déplace aucune page mais remplit instantanément le tableau status avec l'ID du nœud NUMA actuel de chaque page. Le worker filtre ensuite les adresses situées sur le nœud CXL, et ré-invoque move_pages avec le nœud DRAM cible uniquement pour ces adresses. Cette approche en deux passes (query puis migrate) est propre, portable, et sans risque de rejet par le vérificateur eBPF. Le daemon draine la BPF map périodiquement (à 10 Hz, synchronisé avec la boucle de contrôle) via bpf_map_lookup_and_delete_batch() pour récupérer le "top-K des folios chauds", groupés par TGID.

La promotion est ensuite déléguée à un pool de worker threads (std::jthread) qui appellent exclusivement move_pages(2) avec les adresses ciblées et le target_nid du nœud DRAM. Un point critique souvent source de confusion : MADV_WILLNEED (via process_madvise ou madvise) ne peut pas être utilisé pour cette promotion. MADV_WILLNEED ordonne un readahead depuis le backing store (fichier ou swap), mais une page CXL est déjà résidente en RAM physique sur un nœud NUMA CPU-less — le kernel retourne un succès silencieux sans déclencher aucune migration inter-nœuds. Seul move_pages(2) (ou mbind(2) pour les nouvelles allocations) permet de forcer explicitement une migration NUMA d'une page déjà résidente. Les worker threads sont nécessaires car move_pages est synchrone et prend le mmap_lock ; l'essentiel est qu'ils ne bloquent jamais le thread de contrôle principal.

Un mécanisme de protection anti-thrashing est impératif entre DAMON et le sniper PEBS. Quand le sniper déclenche une promotion, le folio promu risque d'être re-démoté par DAMOS au cycle suivant s'il ne le voit pas encore comme "chaud". La mitigation repose sur le filtre DAMOS young (Linux 6.10+) qui exclut les pages récemment accédées des actions de démotion, combiné à un seuil d'âge minimum dans la règle DAMOS de démotion supérieur à la durée de protection (3-5 cycles d'agrégation DAMON, soit 300ms-5s).

Portabilité : PEBS est spécifique Intel, IBS est spécifique AMD. L'architecture prévoit une couche d'abstraction HardwareSampler avec trois backends (Intel, AMD, désactivé). Le backend est sélectionné automatiquement au démarrage par lecture du CPUID.

5.2.3 Capteur de Latence Chargée — Compteurs CHA / Uncore PMU

C'est le composant inspiré de Colloid (§4.1). APEX lit les compteurs matériels du Caching and Home Agent du processeur pour mesurer la latence effective de chaque tier en temps réel. Sur Intel Xeon (Sapphire Rapids+), les compteurs pertinents sont UNC_CHA_TOR_OCCUPANCY et UNC_CHA_TOR_INSERTS. La latence chargée est L_tier = Occupancy / Requests (loi de Little). APEX les lit via perf_event_open à 1-10 Hz.

Portabilité : c'est le point le plus fragile de l'architecture. La disponibilité des compteurs Uncore dépend de la microarchitecture et de la configuration BIOS (les PCI monitoring devices sont souvent désactivés par défaut et nécessitent parfois un firmware update). AMD n'a pas d'équivalent direct aussi mature. De plus, dans les environnements virtualisés (AWS Nitro, GCP, Azure, KVM), les hyperviseurs ne virtualisent et n'exposent presque jamais les compteurs Uncore aux machines virtuelles, pour des raisons de sécurité (prévention des attaques par canaux auxiliaires). CXL est aujourd'hui déployé quasi-exclusivement en bare-metal, mais cette limitation doit être architecturalement assumée. Le CXL Performance Monitoring Unit (CPMU, spec CXL 3.0, driver Linux cxl_pmu_mem) offre une source future standardisée directement sur le device CXL, mais n'est pas encore largement déployé.

L'architecture prévoit un mode de fonctionnement sans compteurs matériels, traité comme un composant de premier ordre (pas un simple fallback dégradé) : APEX estime la "pression de latence" à partir du volume de migration qu'il émet lui-même (il connaît ses quotas DAMOS), des métriques PSI mémoire du cgroup, et du taux de refaults observé. Parmi ces trois signaux, le taux de refaults est le plus directement actionnable pour les décisions de tiering : un refault élevé signifie que les seuils de démotion sont trop agressifs et que des pages utiles sont migrées vers CXL à tort. Les métriques PSI sont un signal de pression global utile en complément, mais elles ne distinguent pas la cause de la pression (saturation DRAM, saturation bus PCIe/CXL, ou activité de swap) et doivent être pondérées en conséquence — le Latency-Aware Regulator (§5.3.4) donne un poids prépondérant aux refaults pour moduler les quotas de démotion. Ce mode est potentiellement le mode nominal dans la majorité des déploiements (VM cloud, serveurs avec BIOS restrictif, matériel AMD). Il doit donc être testé et validé avec le même niveau d'exigence que le mode avec compteurs matériels.

5.2.4 Sondes eBPF de Cycle de Vie

Trois programmes eBPF légers complètent les capteurs précédents. Un hook sur tracepoint:syscalls:sys_enter_munmap détecte les libérations mémoire et invalide les données de monitoring associées (résout le problème ABA, §3.6). Un hook sur tracepoint:tlb:tlb_flush surveille les tempêtes d'IPI comme signal d'alarme précoce. Un listener sur les events de pression mémoire cgroup (memory.events) détecte les situations de stress par cgroup.

5.3 Control Plane — Le daemon C++23

Le daemon est un processus unique en espace utilisateur avec les capabilities minimales nécessaires (CAP_SYS_ADMIN pour sysfs DAMON, CAP_SYS_PTRACE pour les contrôles PTRACE_MODE_READ_REALCREDS exigés par move_pages depuis Linux 4.13, CAP_PERFMON pour les compteurs hardware, et CAP_SYS_NICE requis par move_pages(2) pour déplacer les pages de processus arbitraires et pour utiliser le flag MPOL_MF_MOVE_ALL qui autorise la migration de pages partagées entre plusieurs processus — indispensable pour les shared buffers PostgreSQL, les segments tmpfs Redis, et toute mémoire partagée d'applications d'entreprise).

5.3.1 Topology Parser

Au démarrage, le daemon parse les tables ACPI HMAT (Heterogeneous Memory Attribute Table) et les registres CXL CDAT (Coherent Device Attribute Table) pour découvrir automatiquement la topologie mémoire : quels nœuds NUMA sont DRAM, lesquels sont CXL, quelles sont les latences et bandes passantes nominales, et quels sont les bus PCIe impliqués. Cette information est immutable pour la durée de vie du daemon. Le daemon refuse de démarrer si la topologie ne contient pas au moins deux tiers distincts.

5.3.2 QoS Policy Engine

Ce module reçoit les contrats de QoS du Management Plane et les traduit en politiques par cgroup v2. Chaque cgroup est associé à un profil de tiering : la priorité (Platinum, Gold, Silver, Bronze) détermine l'ordre de protection lors de la démotion ; le headroom minimum de DRAM (pourcentage garanti) ; l'activation du sniper PEBS ; le quota maximum de migration ; et le seuil de froideur minimum pour la démotion.

La logique centrale est proactive : quand la DRAM est sous pression (détectée via PSI ou le ratio d'utilisation), les cgroups de basse priorité voient leurs quotas de démotion augmenter progressivement, libérant de la DRAM pour les cgroups haute priorité. Le self-tuning DAMOS (free_mem_rate, Linux 6.14+) est le mécanisme kernel utilisé pour maintenir ce headroom de manière autonome. C'est un avantage décisif par rapport à un mécanisme réactif d'OOM-Spill : le headroom proactif maintient une réserve permanente au lieu de réagir en urgence quand il est potentiellement trop tard (la fenêtre entre le signal OOM et le kill effectif peut être de quelques millisecondes, insuffisant pour migrer des Go).

5.3.3 Risk Engine

Le Risk Engine est un automate à états finis (FSM) avec trois états : NORMAL, DEGRADED, et EMERGENCY. Les transitions sont pilotées par une combinaison pondérée de signaux : la pression PSI memory (instantanée et dérivée), le taux de TLB flushes, la latence chargée des tiers (§5.2.3), et le ratio de refaults (pages récemment démotées immédiatement ré-accédées — signal que les seuils de froideur sont trop agressifs).

En état NORMAL, les quotas DAMOS sont à leur valeur nominale. En état DEGRADED, ils sont réduits proportionnellement. En état EMERGENCY, tous les quotas passent à zéro : les migrations s'arrêtent net en < 1ms (DAMOS vérifie ses quotas à chaque cycle). L'hystérésis — seuils d'entrée différents des seuils de sortie — évite l'oscillation entre états.

5.3.4 Latency-Aware Regulator

C'est le module qui intègre les enseignements de Colloid (§4.1) et Alto (§4.2). Il consomme les mesures de latence chargée (§5.2.3) et module les décisions du Policy Compiler en conséquence.

Pour les promotions : une promotion CXLDRAM n'est autorisée que si L_CXL / L_DRAM > min_latency_improvement_ratio (défaut : 1.2). Si la DRAM est déjà sous forte charge (latence élevée), les promotions sont suspendues même si PEBS détecte des pages chaudes sur CXL, car ajouter de la charge sur un tier saturé empirerait la situation globale.

Pour les démotions : si la latence CXL est proche de la saturation, les quotas de démotion sont réduits pour ne pas surcharger le tier lent.

Ce mécanisme remplace le contrôleur PID initialement proposé pour la bande passante PCIe, qui était mal adapté au trafic en rafales (bursty) des migrations. Un PID fonctionne bien pour des signaux continus (température, vitesse), mais les migrations arrivent par batch et provoquent un integral windup destructeur. Le Latency-Aware Regulator ajuste directement les quotas DAMOS natifs (en bytes/s et ms/s), qui sont par construction un mécanisme de type token bucket géré par le kernel — précisément le bon modèle pour le trafic en rafales.

5.3.5 Policy Compiler

Le Policy Compiler prend en entrée les décisions du QoS Policy Engine, du Risk Engine, et du Latency-Aware Regulator, et génère des écritures concrètes. Pour la démotion : des règles DAMOS dans /sys/kernel/mm/damon/admin/kdamonds/<id>/contexts/<id>/schemes/. Pour la promotion DAMOS : des règles symétriques avec DAMOS_MIGRATE_HOT. Pour la promotion urgente (PEBS) : des adresses de folios poussées dans la file lock-free vers les worker threads qui exécutent move_pages(2). Au démarrage, le daemon coordonne sa coexistence avec les mécanismes NUMA du kernel selon la topologie détectée (cf. §3.7) : désactivation totale d'AutoNUMA en single-socket, ou désactivation de demotion_enabled seul en multi-socket. Dans les deux cas, APEX assume le monopole sur les décisions de tiering (promotions DAMOS + PEBS, démotions DAMOS).

Aucune de ces opérations ne bloque le thread de contrôle. Les écritures sysfs sont quasi-instantanées (pseudo-fichiers en RAM kernel, pas d'I/O disque). Point d'implémentation critique : dans l'interface sysfs de DAMON, modifier les fichiers de quotas ou de seuils d'un kdamond en cours d'exécution ne change pas son comportement immédiatement. Le noyau met ces modifications en cache dans les structures sysfs. Pour que les nouvelles valeurs soient effectivement appliquées par le kdamond, le Policy Compiler doit impérativement terminer chaque cycle d'ajustement par l'écriture de la commande commit dans le fichier state du kdamond cible (/sys/kernel/mm/damon/admin/kdamonds/<id>/state). Cette commande ordonne au noyau de relire l'ensemble des paramètres sysfs et de les appliquer au kdamond en cours d'exécution, sans l'interrompre ni le redémarrer. Pour les mises à jour qui ne concernent que les quota goals d'auto-tuning (objectifs free_mem_rate, some_mem_psi_us, etc.), une commande plus ciblée commit_schemes_quota_goals est disponible (Linux 6.7+) et permet de ne relire que les goals sans toucher aux autres paramètres. Le Policy Compiler utilise commit après chaque cycle complet de mise à jour (10 Hz), et commit_schemes_quota_goals pour les ajustements intermédiaires du Latency-Aware Regulator qui ne modifient que les objectifs de tuning. Le Policy Compiler s'exécute à 10 Hz.

5.3.6 Modèle de threads

Le thread de contrôle (main thread) exécute la boucle principale à 10 Hz. Il lit les métriques agrégées, alimente le Risk Engine et le Latency-Aware Regulator, exécute le Policy Compiler, et écrit les règles DAMOS. Aucun appel système bloquant lié à la migration.

Le thread de télémétrie PEBS lit périodiquement la BPF map agrégée in-kernel, extrait le top-K des folios chauds sur CXL groupés par PID, et expose ces données au thread de contrôle via un mécanisme de triple buffering déterministe. Un point d'implémentation critique : std::atomic<std::shared_ptr> (C++20) n'est pas lock-free dans les implémentations standard (libstdc++ de GCC utilise un spinlock interne, libc++ de LLVM et MSVC également — is_lock_free() retourne false). De plus, toute stratégie de libération mémoire basée sur un délai temporel (par exemple "différer d'un cycle de 100ms") est un anti-pattern fatal dans un OS préemptif : le scheduler peut suspendre un thread lecteur pendant bien plus de 100ms (migration de cœur, softirq, cgroup throttling), et au réveil il déréférencerait un pointeur libéré — un Use-After-Free garanti. Le mécanisme retenu utilise soit un triple buffer circulaire pré-alloué (trois slots fixes, index atomiques — zéro allocation runtime, zéro libération, aucun risque de UAF), soit des Hazard Pointers via folly::hazptr (Meta) qui garantissent une réclamation déterministe sans hypothèse temporelle. Le triple buffer est préféré pour sa simplicité : le producteur écrit dans le slot suivant et publie l'index atomiquement, le consommateur lit le dernier index publié et accède au slot correspondant. Le troisième slot absorbe les cas de concurrence sans contention.

Le pool de workers de promotion (2-4 std::jthread) dépile les requêtes de promotion urgente depuis une file SPMC (Single-Producer / Multiple-Consumer) lock-free et exécute move_pages(2) avec le target_nid DRAM. Le choix d'une file SPMC (et non SPSC) est impératif : une file SPSC (Single-Producer / Single-Consumer) garantit par contrat qu'un unique thread consomme les éléments, et n'utilise aucune instruction atomique coûteuse (CAS) sur son pointeur de lecture. Attacher 2 à 4 workers sur une file SPSC provoquerait une data race massive sur le pointeur tail, entraînant des dépilages en double et un segfault inévitable du daemon. Avec une file SPMC, le pointeur de lecture est protégé par un CAS atomique qui coordonne les consommateurs concurrents. Un point de performance critique : les workers doivent impérativement effectuer du batching. L'appel move_pages(2) prend le verrou kernel mmap_lock en lecture sur l'espace d'adressage du processus cible à chaque invocation. Appeler le syscall individuellement pour chaque folio saturerait ce verrou et générerait une contention massive avec les threads de l'application cible (Redis, PostgreSQL) qui tentent de faire des allocations mémoire. Le syscall a été conçu pour accepter un tableau d'adresses et un paramètre count précisément pour amortir ce coût. Les workers doivent donc dépiler un lot d'adresses, les grouper par PID (typiquement par paquets de 256 à 512 adresses), et n'effectuer qu'un seul appel système par lot et par PID. En complément du batching, un token bucket par PID limite le débit global de promotions par processus cible (par défaut : budget configurable de N migrations/seconde par PID). Cette régulation est nécessaire car le batching seul ne borne pas la fréquence d'acquisition du mmap_read_lock : un flux continu de lots de 512 adresses pourrait maintenir le verrou quasi-continuellement. Le token bucket garantit des fenêtres de respiration pendant lesquelles l'application cible peut effectuer ses propres opérations mémoire (mmap, munmap, brk) sans contention. La latence de ces syscalls groupés et régulés n'impacte jamais le thread de contrôle.

Le thread de santé expose les endpoints HTTP /health, /ready, /metrics (format Prometheus) et gère le heartbeat systemd (watchdog).

Discipline de concurrence : aucun std::mutex sur le chemin critique. Les structures partagées entre threads utilisent exclusivement des primitives lock-free (std::atomic, std::atomic_ref, file SPMC pour le pool de workers, file MPSC pour les flux événementiels). Les échanges de données agrégées entre threads utilisent le double buffering avec swap atomique de pointeurs.

5.4 Management Plane — Kubernetes Operator

L'opérateur Kubernetes (en Go, convention de l'écosystème K8s) définit un CRD nommé MemoryTieringPolicy. Les utilisateurs créent des objets de ce type dans leur namespace, spécifiant le sélecteur de Pods, le tier de priorité, le headroom DRAM minimum, l'activation de la promotion rapide, et le quota de démotion maximum.

Un point d'intégration critique souvent ignoré : dans un cluster K8s configuré pour la performance, le Topology Manager de Kubelet épingle la mémoire des Pods (surtout en QoS Guaranteed) aux nœuds NUMA locaux via le fichier cgroup cpuset.mems. La mémoire CXL, qui apparaît comme un nœud NUMA "CPU-less", est systématiquement exclue de ce cgroup par défaut. Si le nœud CXL n'est pas autorisé dans cpuset.mems, toute tentative de migration — que ce soit par DAMOS in-kernel ou par les workers userspace via move_pages — sera rejetée silencieusement par le kernel pour violation de cgroup (-ENOMEM ou -EPERM). APEX échouera sans aucun message d'erreur explicite.

Un opérateur Kubernetes industriel ne doit jamais altérer directement les fichiers cgroupfs dans le dos de Kubelet. La raison est que Kubelet possède une boucle de réconciliation interne (via le CPU Manager et le Topology Manager) qui réécrit périodiquement les valeurs des cpusets. Toute modification unitaire de cpuset.mems effectuée directement dans cgroupfs sera écrasée par Kubelet lors du prochain cycle de synchronisation ou lors de tout événement lié au Pod (redémarrage de conteneur, mise à jour de ressources). De plus, avec Cgroup v2 (architecture top-down), le kernel interdira l'ajout d'un nœud NUMA dans un cgroup enfant si le cgroup parent hiérarchique (kubepods.slice) n'a pas été modifié en amont.

Un prérequis d'infrastructure est donc indispensable avant que le plugin NRI puisse fonctionner : les tranches cgroup parentes gérées par Kubelet (kubepods.slice, kubepods-burstable.slice, kubepods-besteffort.slice) doivent autoriser l'identifiant du nœud CXL dans leur cpuset.mems. Sans cette "ouverture hiérarchique" de haut en bas, le noyau Linux rejettera toute tentative d'assignation du nœud CXL à un conteneur feuille avec l'erreur -EINVAL, puisque le code de update_nodemasks_hier() dans kernel/cgroup/cpuset.c calcule les mems effectifs de chaque enfant par intersection stricte avec les mems du parent (nodes_and(child_mems, parent_effective_mems)). Concrètement, l'opérateur APEX déploie un conteneur d'initialisation privilégié (DaemonSet avec hostPID: true et CAP_SYS_ADMIN) qui, au démarrage de chaque nœud K8s équipé de CXL, écrit l'identifiant du nœud CXL dans les fichiers cpuset.mems des tranches de niveau supérieur. Cette opération est effectuée une seule fois au boot (l'identifiant du nœud CXL est stable pour la durée de vie du serveur) et un watchdog vérifie périodiquement que la configuration persiste après les mises à jour de Kubelet. Une fois ce "tuyau" hiérarchique ouvert, le plugin NRI peut assigner le nœud CXL aux conteneurs feuilles sans blocage du noyau.

La solution architecturalement correcte est d'utiliser le standard NRI (Node Resource Interface), l'interface d'extension officielle de containerd (≥ 1.7.0) et CRI-O (≥ 1.26.0). L'opérateur APEX déploie un plugin NRI (sous forme de DaemonSet K8s) qui s'enregistre auprès du runtime de conteneurs et intercepte les requêtes de création et de mise à jour de conteneurs via le protocole ttRPC/protobuf. Le plugin doit impérativement s'abonner aux événements CreateContainer ET UpdateContainer : si Kubelet décide de mettre à jour les ressources du conteneur (réconciliation du CPU Manager, synchronisation du Topology Manager, ou In-place Pod Resource Resizing de K8s 1.27+), il émet une requête UpdateContainerResources au runtime. Si le plugin ne s'abonne qu'à la création, cette mise à jour écrasera la spec OCI et retirera silencieusement l'autorisation d'accès au nœud CXL. Le plugin injecte de manière idempotente l'identifiant du nœud CXL dans la spécification OCI du conteneur lors des deux types d'événements, avant que le runtime ne crée ou ne mette à jour le cgroup. La modification est dès lors "officielle" pour Kubelet, intégrée dans le cycle de vie normal du conteneur, et ne sera jamais écrasée par la boucle de réconciliation. Cette approche est idempotente et réversible : quand la politique de tiering est retirée, le plugin NRI cesse simplement d'injecter l'autorisation pour les nouveaux conteneurs et leurs mises à jour.

L'opérateur surveille les CRDs et les Pods matchant les sélecteurs. Quand un Pod est schedulé sur un nœud équipé de CXL, l'opérateur envoie le contrat de QoS au daemon APEX local via gRPC. L'observabilité est assurée par l'export de métriques Prometheus depuis le daemon (bytes migrés par cgroup, latence chargée par tier, état du circuit breaker, quotas DAMOS effectifs) et par des dashboards Grafana distribués avec l'opérateur.


6. Points de vigilance

Cette section documente les risques identifiés lors de l'analyse critique et les stratégies de mitigation retenues.

6.1 Portabilité des compteurs matériels

La mesure de latence chargée (CHA) et le sniper PEBS sont spécifiques Intel. AMD utilise IBS et des compteurs Data Fabric différents. De plus, la disponibilité des compteurs Uncore dépend de la configuration BIOS, qui est souvent restrictive par défaut, et les hyperviseurs cloud (AWS Nitro, GCP, Azure, KVM) ne les virtualisent pas. L'architecture prévoit trois niveaux dans la couche HardwareTelemetry : Intel (PEBS + CHA + IIO PMU), AMD (IBS + Data Fabric), et Standard (DAMON seul + estimation par PSI et refaults). Le mode Standard n'est pas un fallback dégradé mais un composant de premier ordre, car il sera le mode nominal dans la majorité des déploiements (VM cloud, BIOS restrictif, matériel AMD). Il est testé et validé avec le même niveau d'exigence que les modes avec compteurs matériels. Le backend est sélectionné automatiquement au démarrage via CPUID et détection des compteurs disponibles.

6.2 Évolution du kernel Linux

Le kernel absorbe progressivement l'intelligence de tiering : self-tuning DAMON (2025), CPMU CXL 3.0, QoS-aware tiering (Mercury). Le risque stratégique est que dans 2-3 ans, le kernel intègre nativement la plupart des fonctionnalités de base d'APEX. La valeur pérenne réside dans ce que le kernel ne fera jamais : l'intégration Kubernetes, la traduction des SLA business en politiques de tiering, les décisions cross-cgroup (sacrifier Bronze pour sauver Platinum), et l'observabilité fine. C'est sur cet axe que l'effort de développement doit se concentrer.

6.3 Le CXL CPMU comme future source de télémétrie

La spécification CXL 3.0 définit un Performance Monitoring Unit directement sur le device CXL, avec un driver Linux existant (cxl_pmu_mem). Au LSFMM 2024, il a été discuté que les contrôleurs CXL futurs pourraient fournir des informations de hotness directement au kernel, sans passer par DAMON ni PEBS. L'architecture doit prévoir un backend CPMU dans la couche HardwareTelemetry pour intégrer cette source quand elle sera largement déployée.

6.4 Cycle de vie et upgrades du daemon

Le mécanisme de hot-upgrade via memfd_create + execve (sérialiser l'état en RAM, remplacer le binaire, désérialiser) est séduisant en théorie mais fragile en pratique : la compatibilité entre versions du schéma de sérialisation est un cauchemar de maintenance pour un composant interne. L'architecture retenue utilise un drain gracieux : le daemon signale à DAMOS de maintenir ses règles (elles sont persistantes dans sysfs), arrête les workers, sauvegarde les métriques long-terme, et s'arrête proprement. DAMON continue de fonctionner indépendamment dans le kernel pendant le redémarrage (quelques secondes), garantissant la continuité du tiering. Le nouveau binaire redémarre, relit la topologie, et reprend le contrôle. Ce mécanisme est robuste, testable, et ne nécessite pas de compatibilité binaire.

6.5 Résumé : traçabilité défauts v1 → décisions v2

Défaut v1 Sévérité Résolution v2 Section
Traçage par page faults Moyenne DAMON (bit Accessed, 100% couverture) §5.2.1
Déconnexion scoring/exécution Haute DAMOS couple monitoring et action §5.2.1
Blocage single-thread Haute Thread contrôle non-bloquant + workers §5.3.6
Pas de gestion THP Haute DAMOS opère au niveau folio §5.2.1
Pas de distinction file/anon Moy-Haute Filtres DAMOS natifs §5.2.1
Problème ABA (tgid,vpn) Moyenne Hook eBPF munmap + DAMON physique §5.2.4
Conflit AutoNUMA Moyenne Désactivation au démarrage, monopole DAMOS+PEBS §3.7, §5.3.5
Fréquence ≠ latence chargée — (nouveau) Latency-Aware Regulator (Colloid) §5.3.4
Tiering excessif > inaction — (nouveau) Auto-régulation (Alto) §5.3.4
PID inadapté trafic bursty — (nouveau) Quotas DAMOS natifs (token bucket) §5.3.4

7. Stack technologique

7.1 C++23 avec discipline de safety

Le daemon est écrit en C++23. Pour maximiser la safety dans un contexte de daemon système multi-threadé manipulant l'état mémoire d'un serveur de production, les règles suivantes sont imposées.

Toute allocation mémoire utilise des smart pointers (std::unique_ptr, std::shared_ptr). Les raw pointers ne sont autorisés que dans les interfaces avec le C (libbpf, syscalls). La gestion d'erreur utilise std::expected<T, E> (C++23) au lieu d'exceptions pour les chemins critiques. Les structures de données partagées entre threads utilisent exclusivement des primitives lock-free. Aucun std::mutex sur le chemin critique de la boucle de contrôle (10 Hz). Les sanitizers (ASan, TSan, UBSan) sont activés en CI. Le fuzzing (libFuzzer) cible le parsing HMAT/CDAT et la désérialisation des métriques.

La norme C++23 apporte des fonctionnalités directement utiles au projet : std::expected pour la gestion d'erreur sans exceptions, std::jthread avec stop tokens pour les worker threads interruptibles proprement, et les améliorations de std::format pour le logging structuré haute-performance. Note importante : std::atomic<std::shared_ptr> (C++20) n'est pas lock-free dans les implémentations actuelles (libstdc++, libc++, MSVC utilisent toutes un spinlock interne), et toute libération mémoire basée sur un délai temporel est un anti-pattern dans un OS préemptif. Le projet utilise un triple buffer pré-alloué avec index atomiques pour l'échange de données inter-threads (cf. §5.3.6).

7.2 eBPF — libbpf + CO-RE (C)

Les programmes eBPF sont écrits en C et compilés avec Clang/LLVM en utilisant CO-RE (Compile Once, Run Everywhere). libbpf (>= 1.3) gère le chargement et l'attachement. CO-RE permet de compiler le binaire une seule fois et de le distribuer sans Clang sur les serveurs de production, grâce au BTF embarqué dans les kernels modernes (6.x+).

Trois programmes eBPF sont produits : le sampler PEBS (attaché à perf_event_open sur MEM_LOAD_RETIRED.L3_MISS, agrège les compteurs dans une BPF_MAP_TYPE_LRU_PERCPU_HASH in-kernel — le choix d'une map LRU per-CPU combine la sécurité NMI, puisque chaque cœur écrit dans sa propre zone mémoire sans verrou partagé, et l'éviction automatique des entrées les moins récemment utilisées lorsque la map atteint sa capacité, évitant ainsi le rejet silencieux d'insertions -E2BIG qui rendrait le sniper aveugle ; le daemon C++ draine et remet à zéro l'intégralité de la map à 10 Hz via l'API bpf_map_lookup_and_delete_batch() introduite dans Linux 5.6, qui permet une collecte atomique sans perte de télémétrie entre un lookup et un delete séparés), le traqueur de lifecycle (tracepoint:syscalls:sys_enter_munmap, communique via BPF ring buffer — flux faible), et le moniteur TLB (tracepoint:tlb:tlb_flush, communique via BPF ring buffer).

7.3 Build et distribution

CMake (>= 3.25) avec presets Debug (sanitizers), Release (LTO, -O2), et CI (sanitizers + coverage). Dépendances externes : libbpf (>= 1.3), bpftool (squelettes CO-RE), spdlog (logging structuré), nlohmann/json (configuration), prometheus-cpp (métriques), gRPC/protobuf (communication K8s). Tests : GoogleTest + Google Benchmark.

Le binaire est un exécutable statique (hors libc) distribué en .deb/.rpm ou image OCI. Géré par systemd avec watchdog, restart automatique, et sandboxing complet (seccomp whitelist, capabilities minimales, ProtectSystem=strict, PrivateTmp=yes).


8. Références

Les travaux suivants ont directement informé les choix architecturaux de ce document.

Colloid — Vuppalapati, M. & Agarwal, R. "Tiered Memory Management: Access Latency is the Key!" SOSP '24, Cornell. Placement basé sur la latence chargée via compteurs CHA et loi de Little. §4.1, §5.3.4.

Soar/Alto — Liu, J. et al. "Tiered Memory Management Beyond Hotness." OSDI '25. Auto-régulation par latence observée moyennée (AOL). Démontre que le tiering excessif sous-performe le first-touch. §4.2, §5.3.4.

Nomad — Xiang, L. et al. "Non-Exclusive Memory Tiering via Transactional Page Migration." OSDI '24, UT Arlington / Intel Labs. Migration transactionnelle asynchrone et page shadowing. Jusqu'à 6x sur TPP. §4.3.

MEMTIS — Lee, T. et al. "Efficient Memory Tiering with Dynamic Page Classification and Page Size Determination." SOSP '23. Classification dynamique avec PEBS. §4.4.

HeMem — Raybuck, A. et al. "Scalable Tiered Memory Management for Big Data Applications and Real NVM." SOSP '21. Architecture fondatrice du tiering userspace avec PEBS.

TPP — Maruf, A. et al. "Transparent Page Placement for CXL-Enabled Tiered-Memory." ASPLOS '23, Meta. Standard industriel du tiering CXL in-kernel. Mergé Linux 5.18+.

Pond — Li, H. et al. "CXL-Based Memory Pooling Systems for Cloud Platforms." ASPLOS '23, Meta. Caractérisation du mur de bande passante PCIe. §4.2.

Sun et al. — "Demystifying CXL Memory with Genuine CXL-Ready Systems and Devices." MICRO '23. Mesures de latence/bande passante CXL sur matériel réel. §2.

Memstrata — Zhong, Y. et al. "Managing Memory Tiers with CXL in Virtualized Environments." OSDI '24, Microsoft/Columbia. Tiering CXL en VM avec hardware tiering et page coloring.

DAMON — Park, S. et al. Middleware '19 Industry, HPDC '22. En production chez AWS Aurora Serverless et SK hynix HMSDK. §4.5, §5.2.1.

DAMON Self-Tuning — Park, S. Patchset mars 2025, LSFMM+BPF 2025. Auto-ajustement des seuils visant un taux d'utilisation cible. +4.42% vs -7.34% NUMA balancing. §4.5, §5.3.2.

SK hynix CXL Tiering — Kim, H. et al. Patchset v6, mergé Linux 6.11. DAMOS_MIGRATE_HOT et DAMOS_MIGRATE_COLD. Réduit le ralentissement de 11% à 3-5% sur Redis. §4.5, §5.2.1.

Mercury — "QoS-Aware Tiered Memory System." Décembre 2024. Tiering QoS-aware in-kernel. Surpasse Colloid de 20.3% et TPP de 53.4%. §4.6.

ARMS — "Adaptive and Robust Memory Tiering System." 2025. Tiering adaptatif sans seuils fixes.


9. Roadmap

Phase 1 — Fondations (Mois 1-2). Squelette du daemon C++23, Topology Parser HMAT/CDAT, connectivité DAMON/DAMOS basique. Règle DAMOS de démotion statique sur un cgroup cible. Modèle de threads en place. Métriques Prometheus. Packaging systemd avec sandboxing. Livrable : daemon minimal fonctionnel qui démoute la mémoire froide vers CXL pour un workload Redis.

Phase 2 — Intelligence (Mois 3-5). QoS Policy Engine avec priorités par cgroup. Risk Engine (FSM + circuit breaker + hystérésis). Policy Compiler dynamique ajustant les quotas DAMOS en fonction des priorités et du risque. Headroom proactif via self-tuning DAMOS. Coordination avec AutoNUMA. Tests d'intégration simulant des scénarios de pression mémoire.

Phase 3 — Sniper et latence (Mois 6-8). Sniper PEBS/IBS via eBPF pour la promotion rapide. Pool de workers de promotion asynchrone. Latency-Aware Regulator avec compteurs CHA. Auto-régulation inspirée d'Alto. Protection anti-thrashing DAMON/PEBS. Backends Intel, AMD, et Generic. Hooks eBPF lifecycle (munmap, TLB flush).

Phase 4 — Kubernetes et production (Mois 9-12). Opérateur Kubernetes, CRDs MemoryTieringPolicy, gRPC daemon-opérateur. Dashboards Grafana. Tests de charge à l'échelle (multi-socket, YCSB/Redis/PostgreSQL, scénarios multi-tenant K8s). Drain gracieux pour les upgrades. Documentation opérationnelle.


 


APEX — Partie II : Extensions vers la Parité Industrielle

Statut : Spécification prospective
Prérequis : Partie I (tiering single-node DRAMCXL, intégration Kubernetes)
Périmètre : Pooling multi-device, inférence AI / KV Cache, Shared Memory Objects, maturité opérationnelle


10. Contexte et nécessité de la Partie II

La Partie I spécifie un daemon de tiering single-node entre une DRAM locale et un expander CXL attaché directement. Ce périmètre résout le problème fondamental — placer intelligemment les pages chaudes et froides sur deux tiers mémoire — mais il ne couvre qu'une fraction de la surface fonctionnelle requise pour un produit de classe industrielle capable de concurrencer les solutions commerciales existantes (MemVerge Memory Machine X, SK hynix HMSDK). Trois mutations du marché imposent d'étendre l'architecture.

Premièrement, les serveurs CXL de production embarquent désormais plusieurs devices CXL simultanément. Un Lenovo ThinkSystem V4 équipé de quatre modules Samsung CMM-D (96 Go chacun) expose quatre nœuds NUMA CPU-less avec des caractéristiques distinctes de latence et de bande passante. Le tiering n'est plus une décision binaire (DRAM ou CXL) ; il devient un problème d'optimisation multi-niveaux sur un graphe mémoire hétérogène.

Deuxièmement, le marché CXL a pivoté vers l'inférence AI à grande échelle. Le KV cache des Large Language Models est le nouveau consommateur dominant de mémoire serveur. Pour un modèle de 70 milliards de paramètres servant 100 requêtes concurrentes avec des contextes de 8K tokens, le KV cache peut atteindre 50-200 Go — dépassant largement la VRAM d'un GPU H100 (80 Go). La mémoire CXL offre un rapport capacité/coût 3-5× supérieur à la DRAM avec une latence 200-500× inférieure au SSD. Un orchestrateur CXL qui ignore l'AI ignore la majorité de la demande future.

Troisièmement, la spécification CXL 3.0/3.2 (décembre 2024) introduit les Multi-Headed Devices (MHD), les Dynamic Capacity Devices (DCD) et la back-invalidation matérielle — les briques qui permettent le memory pooling et le partage de mémoire entre serveurs via un switch fabric CXL. Des pools de 100 TiB sont démontrés en production depuis 2025 (XConn Apollo + MemVerge, OCP Global Summit et SC25). Le kernel Linux travaille activement sur le support DCD (Jonathan Cameron, LKML, patches ciblant Linux 6.19+). APEX doit être prêt à orchestrer cette topologie dès que le kernel la supportera.

Cette Partie II spécifie les extensions architecturales nécessaires. Chaque section décrit le problème technique, l'architecture retenue, et son intégration avec les composants de la Partie I. Aucune échelle de temps n'est prescrite : les sections sont ordonnées par dépendance technique, pas par calendrier.


11. Tiering multi-device et placement multi-tier

11.1 Graphe de topologie mémoire

Le Topology Parser de la Partie I (§5.3.1) découvre deux tiers : DRAM locale et CXL. L'extension multi-device le remplace par un graphe de topologie mémoire pondéré, modélisé comme un graphe dirigé G = (T, E) où chaque nœud T_i représente un tier mémoire (caractérisé par sa latence, sa bande passante et sa capacité) et chaque arête E_ij représente un chemin de migration entre tiers avec son coût.

Les sources de données pour la construction du graphe sont les suivantes. L'ACPI HMAT (Heterogeneous Memory Attribute Table) fournit les latences et bandes passantes nominales pour chaque paire {initiateur CPU, cible mémoire}. Les registres CDAT (Coherent Device Attribute Table) de chaque device CXL, accessibles via les mailboxes CXL (/dev/cxl/), fournissent les caractéristiques mesurées du device — qui peuvent différer significativement des valeurs HMAT, souvent conservatrices. La topologie PCIe, parsée via sysfs (/sys/bus/pci/), révèle les contraintes de partage de bande passante : deux expanders CXL derrière le même root port PCIe Gen5 x16 en mode bifurcation x8+x8 se partagent la bande passante, et cette contrainte doit être modélisée comme une capacité d'arête partagée dans le graphe. Enfin, le CXL CPMU (Performance Monitoring Unit, driver cxl_pmu_mem), quand disponible, fournit des métriques de latence et de débit en temps réel directement depuis le device, permettant une mise à jour dynamique des poids du graphe.

Le graphe est construit une fois au démarrage du daemon (la topologie physique est stable) mais les poids des arêtes peuvent être mis à jour périodiquement si le CPMU est disponible. La structure est consultée par le Policy Compiler et le Latency-Aware Regulator pour toutes les décisions de placement.

Exemple concret : un serveur avec 512 Go de DRAM, deux expanders CXL rapides (Samsung CMM-D, 96 Go, ~170ns, derrière des root ports dédiés) et un expander CXL haute capacité (Micron CZ120, 512 Go, ~250ns, derrière un root port partagé) produit un graphe à quatre tiers. Le Policy Compiler exploite cette topologie pour ne pas traiter les trois nœuds CXL de manière identique.

11.2 Politique de placement à seuils gradués

Avec N tiers de caractéristiques distinctes, le Policy Compiler génère des règles DAMOS avec des target_nid différenciés. Le modèle est celui de seuils gradués : chaque tier est défini par un intervalle de fréquence d'accès (access rate min/max dans la terminologie DAMON) et un âge minimum.

Tier 0  DRAM locale       ~80ns    Pages les plus chaudes (top percentile)
Tier 1  CXL rapide        ~170ns   Pages tièdes (accès régulier, non critique)
Tier 2  CXL haute capacité ~250ns  Pages froides (accédées rarement)
Tier 3  CXL fabric        ~400ns+  Pages archivées (cf. §13)

L'avantage par rapport au tiering binaire de la Partie I est double. Premièrement, la granularité de placement exploite la capacité totale du serveur de manière graduée : les pages ne sont plus simplement "en DRAM" ou "en CXL" mais positionnées sur le tier optimal pour leur profil d'accès. Deuxièmement, le coût de re-promotion est réduit : une page qui refroidit légèrement passe de Tier 0 à Tier 1 (migration courte, même bus PCIe) plutôt que d'être reléguée directement au tier le plus lent, ce qui minimise la distance de re-promotion si elle redevient chaude.

Pour les promotions PEBS, le worker choisit le tier de destination en fonction de l'intensité mesurée. Une page CXL avec un compteur PEBS dans le top 1% est promue directement en Tier 0 (DRAM). Une page avec un compteur modéré est promue vers le Tier 1 (CXL rapide), si un tier intermédiaire existe dans le graphe de topologie. Cette décision est prise par page, pas par cgroup, et respecte les budgets du QoS Policy Engine.

11.3 Placement orienté bande passante

Certains workloads ne sont pas latency-bound mais bandwidth-bound : les scans séquentiels sur de grands datasets (analytics, entraînement ML, compression in-memory) saturent la bande passante d'un seul canal mémoire bien avant d'atteindre la limite de latence. Concentrer toutes les pages en DRAM est alors sous-optimal car la bande passante DRAM d'un socket (~200 Go/s) constitue le plafond. Distribuer intentionnellement les pages entre DRAM et CXL exploite les deux bus simultanément et obtient une bande passante agrégée supérieure (~200 + 64 = 264 Go/s dans un cas typique).

L'implémentation repose sur le weighted interleave du kernel Linux, disponible nativement via mempolicy depuis Linux 6.9 (contribution majeure du projet HMSDK de SK hynix). Le weighted interleave distribue les nouvelles allocations entre les nœuds NUMA selon des poids configurables, au lieu de l'interleave uniforme classique. Le Policy Compiler d'APEX configure les poids via sysfs (/sys/kernel/mm/mempolicy/weighted_interleave/) en fonction du ratio de bande passante mesuré entre les tiers. Si le Tier 0 offre 200 Go/s et le Tier 1 offre 64 Go/s, les poids sont calibrés à ~3:1 pour maximiser le débit agrégé tout en respectant les proportions de capacité.

La détection du profil bandwidth-bound vs latency-bound est effectuée par le Latency-Aware Regulator (§5.3.4) via les compteurs CHA Uncore : un ratio Occupancy/Requests faible avec un débit de requêtes élevé signale un workload bandwidth-bound. En mode Standard (sans compteurs CHA), un indicateur proxy est le ratio entre le volume de données accédées par cycle d'agrégation DAMON et la taille du working set : un ratio supérieur à ~80% suggère un scan séquentiel.

Le mode de placement est configurable par Pod via le CRD MemoryTieringPolicy existant, étendu avec un champ placementPolicy :

  • latency-optimized (défaut) — privilégie la DRAM pour les pages chaudes, démotion séquentielle vers les tiers plus lents.
  • bandwidth-optimized — interleave pondéré pour les workloads séquentiels, exploite la bande passante agrégée multi-tier.

12. Intégration AI : tiering du KV Cache pour l'inférence LLM

12.1 Anatomie du problème

L'inférence autoregressive d'un LLM se décompose en deux phases aux profils antagonistes. La phase de prefill calcule les vecteurs Key et Value pour tous les tokens du prompt — c'est une opération compute-bound, parallélisable, qui exploite pleinement les FLOPS du GPU. La phase de decode génère les tokens de sortie un par un, en accédant à chaque étape l'intégralité du KV cache accumulé — c'est une opération memory-bandwidth-bound qui sous-utilise le GPU mais exige un accès rapide à un volume de données croissant linéairement avec le nombre de tokens.

Le KV cache par requête croît comme 2 × n_layers × n_heads × head_dim × seq_len × dtype_size. Pour un Llama-3 70B en FP16 avec un contexte de 8K tokens, cela représente environ 1.2 Go par requête. Servir 100 requêtes concurrentes exige ~120 Go de KV cache — soit plus que la VRAM d'un GPU H100 (80 Go). Le KV cache doit déborder vers un tier de mémoire plus large. Les alternatives sont le SSD (~100μs de latence, inutilisable pour le decode en temps réel), le RDMA vers un serveur distant (~1-5μs, complexe et coûteux en réseau), et la DRAM hôte (~200ns côté CPU, ~2-5μs via DMA GPU→CPU). La mémoire CXL offre un quatrième chemin : même bus PCIe, même latence que la DRAM hôte du point de vue du GPU, mais capacité 3-5× supérieure pour un coût 2-3× inférieur. C'est le sweet spot pour le KV cache overflow.

12.2 Backend vLLM KV Connector pour CXL

vLLM (le framework d'inférence LLM dominant, > 70K stars GitHub) expose une API de connecteur pluggable pour le KV cache offloading depuis la version 0.11.0. L'architecture repose sur un OffloadingConnector qui implémente deux opérations asynchrones : store(request_id, kv_data) → job_id pour offloader les blocs KV depuis la VRAM GPU et load(request_id) → kv_data pour les recharger. Un backend CPU natif est fourni. APEX implémente un backend CXL qui redirige le KV cache vers la mémoire CXL au lieu de la DRAM hôte.

L'implémentation alloue les buffers KV sur le nœud NUMA CXL cible via mbind(MPOL_BIND, cxl_node_mask). Les transferts GPU → CXL sont transparents pour vLLM : le GPU effectue un DMA vers l'espace d'adressage physique de l'hôte, et le contrôleur mémoire route les accès vers le device CXL via le protocole CXL.mem. Aucune modification du code vLLM n'est nécessaire au-delà de la sélection du backend dans la configuration (kv_offloading_backend: "apex-cxl").

L'avantage différenciant par rapport au backend CPU natif est structurel : le backend CPU vole de la DRAM à l'hôte, réduisant la mémoire disponible pour le système d'exploitation, les applications co-localisées et le page cache. Le backend CXL utilise de la mémoire additionnelle qui n'existait pas dans le budget DRAM. Ce n'est pas un compromis — c'est de la capacité supplémentaire.

12.3 Tiering intelligent du KV Cache

Le backend basique (§12.2) est un allocateur CXL statique. Le tiering intelligent ajoute une couche d'optimisation qui exploite le profil d'accès non-uniforme du KV cache.

Gradient d'accès positionnel. Les tokens récemment générés (les dernières positions dans la séquence) sont significativement plus accédés que les tokens du début du prompt lors de la phase de decode. L'attention masquée concentre les poids d'attention sur les tokens récents dans la plupart des architectures (GPT, Llama, Mistral). Le sniper PEBS (§5.2.2) peut profiler ce gradient et le daemon promeut les blocs KV chauds (tokens récents) en DRAM tout en conservant les blocs froids (début du prompt) en CXL. Le résultat est un pipeline de tiering à trois niveaux — VRAM GPU → DRAM hôte → CXL — où chaque tier absorbe le débordement du précédent.

Prefetch prédictif. Quand le scheduler vLLM préempte une requête en cours (pour libérer de la VRAM GPU à une requête prioritaire), ses blocs KV sont offloadés vers CXL. Quand cette requête est re-schedulée, ses blocs doivent être rechargés. Sans prédiction, vLLM déclenche le chargement au moment de la re-schedule, ajoutant un délai de ~1-10ms au Time-To-First-Token (TTFT). APEX peut anticiper cette re-schedule en observant la file d'attente du scheduler vLLM (exposée via les métriques Prometheus) et en pré-chargeant les blocs KV de CXL vers DRAM avant que vLLM ne les demande. Le coût d'une prédiction fausse est une copie inutile CXLDRAM, rapidement évincée par le tiering normal. Le bénéfice d'une prédiction correcte est une suppression quasi-totale du délai de reprise.

Coordination cross-workload. Si un Pod Redis et un Pod vLLM cohabitent sur le même nœud Kubernetes, APEX arbitre le budget DRAM entre les deux via le QoS Policy Engine de la Partie I. Le CRD MemoryTieringPolicy permet de déclarer les priorités relatives : un Pod d'inférence critique peut avoir un budget DRAM garanti supérieur, forçant le daemon à démotionner plus agressivement les pages des Pods de moindre priorité. Ce mécanisme est transparent — chaque Pod ne voit que "sa" mémoire, indépendamment des décisions d'arbitrage.

12.4 Intégration avec les frameworks de serving distribué

NVIDIA Dynamo est le framework de serving LLM distribué de NVIDIA qui gère la disaggregation des phases prefill et decode sur des GPUs distincts. NIXL (NVIDIA Inference eXchange Library) gère les transferts de KV cache entre GPUs. Dans l'architecture classique, ces transferts passent par RDMA (InfiniBand ou RoCE). APEX offre un chemin alternatif : le KV cache transite par la mémoire CXL comme staging area.

L'avantage est une latence d'accès ~200ns (CXL.mem) vs ~1-5μs (RDMA, incluant le setup de connexion et la sérialisation du protocole). Cet avantage est significatif pour les architectures de disaggregation prefill/decode intra-nœud, où un GPU de decode accède aux KV produits par un GPU de prefill co-localisé. Quand les deux GPUs sont dans le même serveur, le KV cache en mémoire CXL est accessible par les deux GPUs sans aucune copie réseau — un simple store atomique dans un registre partagé CXL (~200ns) remplace un message RDMA complet.

L'implémentation repose sur un transport NIXL custom qui écrit les blocs KV dans un segment de mémoire partagée CXL et notifie le consommateur via un flag atomique. La condition préalable est que les deux GPUs soient dans le même domaine de cohérence CXL (même serveur ou même switch fabric CXL). Pour le cas inter-serveur, cette optimisation nécessite le support CXL 3.0 avec back-invalidation (cf. §13.2).

12.5 CRD dédié : KVCachePool

L'orchestration du KV cache CXL est exposée aux utilisateurs Kubernetes via un CRD dédié KVCachePool. Ce CRD spécifie la capacité CXL allouée au KV caching sur le nœud, la politique d'éviction (LRU, LFU, ou priorité par modèle), le nombre maximum de requêtes concurrentes dont le KV cache peut résider simultanément en CXL, et les seuils d'alerte pour le remplissage.

L'opérateur Kubernetes coordonne le KVCachePool avec les MemoryTieringPolicy existants (§5.4) pour garantir que le budget CXL total du nœud n'est pas sursouscrit. Si la somme des allocations déclarées (tiering applicatif + KV cache) dépasse la capacité CXL physique, l'opérateur rejette la ressource avec un événement Kubernetes explicite et un message d'erreur documentant le conflit.


13. Shared Memory Objects

13.1 Partage intra-nœud sans copie

Avant le partage inter-serveur (qui nécessite du hardware CXL 3.0), il existe un use case intra-nœud immédiat et réalisable avec le matériel actuel (CXL 2.0, single-headed expanders) : permettre à des conteneurs co-localisés de partager des régions de mémoire CXL sans copie. Le scénario type est un pipeline ML où un Pod de preprocessing écrit des tenseurs dans un buffer partagé CXL et un Pod d'inférence GPU les lit directement — zéro copie, zéro I/O réseau, zéro sérialisation.

L'implémentation repose sur memfd_create (Linux 3.17+) pour créer un descripteur de fichier anonyme en mémoire, suivi de mbind(MPOL_BIND, cxl_node_mask) pour forcer l'allocation physique sur le nœud CXL cible. Le file descriptor est transmis entre conteneurs via un socket Unix (mécanisme standard SCM_RIGHTS). Chaque conteneur mappe le file descriptor dans son espace d'adressage via mmap(MAP_SHARED). Le résultat est un segment de mémoire physiquement situé sur le device CXL, accessible en lecture/écriture par les deux conteneurs, avec la sémantique de cohérence mémoire standard du processeur — car CXL.mem est cache-cohérent dans un même serveur.

Le daemon APEX gère un namespace d'objets partagés. Chaque objet a un nom unique, une taille, un tier de placement (choisi dans le graphe de topologie, §11.1), et une liste de contrôle d'accès basée sur les identités cgroup des conteneurs autorisés. L'API est exposée via un socket Unix local (/run/apex/shmem.sock) avec un protocole gRPC :

  • Create(name, size, tier) → fd — alloue un objet sur le tier CXL spécifié.
  • Open(name) → fd — ouvre un objet existant (avec vérification du cgroup appelant).
  • Delete(name) — libère un objet (uniquement si aucun consommateur actif ne le référence).
  • List() → objects[] — énumère les objets avec leur utilisation courante.

Le CRD correspondant est SharedMemoryObject, spécifiant le nom, la taille, le tier CXL cible et un sélecteur de Pods autorisés. L'opérateur réconcilie ces objets avec le daemon : quand un SharedMemoryObject est créé dans Kubernetes, le daemon alloue la mémoire CXL ; quand il est supprimé, le daemon libère la mémoire après vérification qu'aucun consommateur actif ne le référence (compteur de références atomique dans le namespace).

13.2 Partage inter-nœuds via CXL fabric

Le partage de mémoire entre serveurs distincts est le territoire de la spécification CXL 3.0 et constitue la frontière la plus avancée de cette architecture.

Mécanisme matériel. Un Multi-Headed Device (MHD) est un device mémoire CXL doté de plusieurs ports (heads), chacun connecté à un serveur différent via un switch CXL fabric. Chaque serveur voit le device comme un nœud NUMA local, mais le device peut exposer des régions mémoire partagées entre les heads. Un Dynamic Capacity Device (DCD) ajoute la gestion dynamique des extents : un Fabric Manager (agent logiciel privilégié, un par rack) alloue, libère et réassigne des blocs de mémoire entre serveurs à la volée, via le protocole FM-API défini dans la spec CXL 3.0.

État du kernel Linux. Le driver CXL (drivers/cxl/) supporte la découverte et l'initialisation des devices Type-3, les régions mémoire DAX et le CPMU. Le support DCD est en développement actif (patches Jonathan Cameron sur LKML, infrastructure QEMU DCD mergée). Le support multi-headed formalisé et le Fabric Manager kernel-side ne sont pas encore dans le mainline. La stratégie APEX est progressive :

  • Immédiat : le daemon inclut un composant Fabric Manager qui communique avec les devices CXL via l'interface mailbox (/dev/cxl/) pour configurer les logical devices et gérer les extents. C'est un daemon séparé (un seul par rack, déployé sur un nœud de gestion) qui coordonne les daemons APEX sur chaque serveur du rack.
  • Post-stabilisation kernel : quand le support DCD sera mergé dans le mainline, APEX s'appuiera sur les interfaces sysfs standardisées, remplaçant la communication mailbox directe par une abstraction kernel stable.

Cohérence mémoire inter-serveurs. Deux stratégies sont prévues. La cohérence matérielle (back-invalidation CXL 3.0, gérée par le switch fabric) est la solution cible — le hardware invalide les caches des autres hosts quand un host modifie une page partagée. La cohérence logicielle, pour le matériel qui ne supporte pas la back-invalidation, repose sur un protocole directory-based minimaliste implémenté par le Fabric Manager : un répertoire central maintient l'état de chaque page partagée (exclusive, shared, invalid), et les daemons APEX sur chaque host notifient le Fabric Manager avant toute modification, qui propage les invalidations via un canal TCP out-of-band. Ce protocole logiciel est limité aux use cases à faible taux de modifications partagées (KV cache en lecture seule, datasets read-only) ; les workloads avec des écritures concurrentes fréquentes doivent utiliser la back-invalidation matérielle.

Exposition Kubernetes. Les objets partagés inter-nœuds sont exposés comme des PersistentVolumeClaim backed by CXL shared memory. Un provisioner CSI (Container Storage Interface) APEX communique avec le Fabric Manager pour allouer les extents et configurer les mappings sur les nœuds cibles. Un Pod monte un volume CXL partagé comme un filesystem en mémoire (via FUSE ou DAX direct), avec une sémantique POSIX standard. Cette intégration CSI est la proposition de valeur que les solutions bare-metal ne peuvent pas offrir : le partage de mémoire CXL inter-serveurs se déclare dans le manifest Kubernetes de l'application, exactement comme un volume NFS ou un PVC Ceph.


14. Maturité opérationnelle

14.1 Observabilité multi-niveaux

Le daemon de la Partie I expose des métriques Prometheus via l'endpoint /metrics. Pour un déploiement industriel, l'observabilité doit couvrir trois niveaux de profondeur.

Le premier niveau est le dashboard de topologie : une visualisation en temps réel du graphe mémoire du nœud (ou du cluster) montrant chaque tier (DRAM, CXL rapide, CXL haute capacité, CXL fabric), les capacités utilisées et libres, et les flux de migration entre tiers représentés en volume (bytes/s promus, bytes/s démotés). L'état du Risk Engine par Pod (NORMAL, CAUTION, THROTTLE, EMERGENCY) est affiché avec un code couleur. Ce dashboard est implémenté comme un plugin Grafana consommant les métriques Prometheus existantes, garantissant la compatibilité native avec les stacks d'observabilité enterprise.

Le deuxième niveau est la vue par Pod. Pour chaque Pod sous contrat de tiering, elle affiche la distribution de ses pages par tier, la fréquence de migration (promotions et démotions par seconde), le ratio de refaults (indicateur de démotions trop agressives), et la latence mémoire estimée P50/P95/P99 par tier. Cette vue permet à un SRE d'identifier immédiatement un Pod en difficulté — un ratio de refaults élevé signale que des pages démotées sont accédées avant d'avoir eu le temps de refroidir.

Le troisième niveau est l'alerting automatisé. Des règles d'alerte préconfigurées (distribuées avec le Helm chart) signalent les situations critiques : Risk Engine en état EMERGENCY pendant plus de 60 secondes, ratio de refaults dépassant un seuil configurable, saturation de bande passante PCIe d'un root port partagé (détectable via les compteurs IIO PMU), ou défaillance d'un device CXL (événement RAS CXL, propagé par le driver kernel).

14.2 Interface en ligne de commande — apexctl

Un outil CLI nommé apexctl fournit l'accès opérationnel au daemon. Il communique via le socket gRPC local (/run/apex/control.sock), avec authentification par credentials Unix (UID/GID du socket). En environnement Kubernetes, il est accessible via kubectl exec dans le Pod du DaemonSet APEX.

apexctl status affiche l'état global : topologie détectée (nombre de tiers, latences, capacités), nombre de cgroups sous contrat, état agrégé des Risk Engines, volume de migration cumulé depuis le démarrage.

apexctl topology affiche le graphe mémoire du nœud sous forme tabulaire, avec les latences et bandes passantes mesurées par tier, les contraintes de partage PCIe, et l'occupation courante.

apexctl pod <nom> --detail affiche le profil mémoire d'un Pod spécifique : distribution des pages par tier, politiques actives (latency-optimized, bandwidth-optimized), historique récent des migrations, et recommandation automatique si le profil observé ne correspond pas à la politique configurée.

apexctl drain <pod> est la commande de sécurité opérationnelle critique. Elle ordonne au daemon de promouvoir toutes les pages du Pod cible vers la DRAM, de supprimer les règles DAMOS de démotion pour ce Pod, et de le marquer comme exclu du tiering jusqu'à annulation explicite. C'est l'interrupteur d'urgence que tout opérateur de production exige. L'opération est non-bloquante : la commande retourne immédiatement et le drain s'exécute en arrière-plan via les workers de promotion, avec progression consultable via apexctl drain --status <pod>.

apexctl policy set <pod> <politique> modifie la politique de placement d'un Pod à chaud, sans redémarrage du daemon ni du Pod.

apexctl benchmark <pod> lance un profiling de 60 secondes sur un Pod et produit une recommandation de politique basée sur le profil d'accès observé (latency-optimized, bandwidth-optimized, ou un ratio custom entre tiers).

14.3 Packaging et distribution

Le daemon est distribué sous trois formes.

Packages natifs : .deb (Ubuntu 22.04 LTS, 24.04 LTS) et .rpm (RHEL 9, Rocky Linux 9, Fedora 40+). Le packaging inclut le fichier systemd unit, la configuration par défaut (/etc/apex/apex.conf), les pages man, et les règles udev pour les devices CXL.

Image OCI : pour le déploiement Kubernetes sous forme de DaemonSet. L'image contient le daemon APEX, apexctl, le programme eBPF pré-compilé (CO-RE, portable entre kernels), et le plugin NRI. Le DaemonSet est configuré avec les capabilities Linux minimales (§7.3), hostPID: true, et les montages cgroupfs/sysfs de l'hôte.

Helm chart : déploie l'ensemble de la stack — DaemonSet du daemon, opérateur Kubernetes (Go), CRDs (MemoryTieringPolicy, KVCachePool, SharedMemoryObject), dashboards Grafana, et règles d'alerting Prometheus. Le chart expose des values configurables pour toutes les options du daemon (intervalle de la boucle de contrôle, taille du pool de workers, seuils du Risk Engine, backends de télémétrie, tiers CXL cibles).

14.4 Matrice de compatibilité et validation continue

La validation couvre une matrice à trois axes. L'axe kernel comprend les versions 6.11 (minimum requis), 6.14 (self-tuning DAMOS), et le dernier kernel stable. L'axe CPU comprend Intel Sapphire Rapids (SPR), Intel Granite Rapids (GNR), et AMD EPYC Genoa/Turin. L'axe CXL comprend les modules Samsung CMM-D, SK hynix CMB, et Micron CZ120, ainsi que le mode QEMU émulé pour les tests sans hardware.

Le pipeline CI exécute pour chaque commit : tests unitaires (C++ et Go), tests d'intégration sur QEMU CXL (démarrage du daemon, règles DAMOS, promotion PEBS, coordination AutoNUMA), et un test de charge de 30 minutes avec memtier_benchmark vérifiant l'absence de régression de latence P99.

Un test de stress soutenu 72 heures valide l'absence de fuite mémoire (valgrind / AddressSanitizer), l'absence de crash (fuzzing des entrées sysfs et gRPC), la stabilité des métriques Prometheus, et la convergence du Latency-Aware Regulator sous des workloads variables (alternance de phases latency-bound et bandwidth-bound).


15. Positionnement et asymétrie structurelle

15.1 L'avantage kernel

L'avantage structurel d'un projet construit sur les primitives kernel (DAMON/DAMOS, move_pages, mempolicy, CXL driver) est que chaque amélioration du kernel renforce le produit sans effort de développement. Les patches SK hynix (DAMOS migration, Linux 6.11), le self-tuning DAMON (Linux 6.14), le support DCD (en cours), et les futures améliorations du driver CXL sont absorbés par APEX nativement. Cette dynamique est inversée pour les produits propriétaires dont la valeur ajoutée est directement menacée par les fonctionnalités natives du kernel.

La valeur pérenne d'APEX réside exclusivement dans les couches que le kernel ne fournira jamais : l'intégration Kubernetes (NRI, CRDs, opérateur), la traduction des SLA business en politiques de tiering, les décisions cross-cgroup (sacrifier Bronze pour protéger Platinum), l'orchestration du KV cache AI, le shared memory Kubernetes-native, et l'observabilité fine. L'effort de développement doit se concentrer sur ces couches, pas sur le micro-management des pages — le kernel fait déjà ça mieux que quiconque.

15.2 Différenciation Kubernetes-native

Aucun produit commercial de tiering CXL ne fournit d'intégration Kubernetes de premier ordre. MemVerge Memory Machine X est une solution bare-metal avec une GUI web. SK hynix HMSDK est un SDK kernel-level sans couche d'orchestration. Les solutions in-kernel (TPP, DAMON natif) n'ont aucune notion de Pod, de namespace, ou de SLA applicatif. APEX est conçu comme un composant natif de l'écosystème cloud-native : le daemon est un DaemonSet, les politiques sont des CRDs, l'observabilité passe par Prometheus/Grafana, le déploiement se fait via Helm, et le contrôle d'accès au CXL passe par le plugin NRI et la hiérarchie cgroup v2.

Ce positionnement résout des problèmes techniques réels que les solutions bare-metal ignorent structurellement : la réconciliation cpuset.mems par Kubelet (§5.4), l'isolation multi-tenant par cgroup, la coordination entre Pods co-localisés, le drain gracieux lors des rolling updates, et la déclaration de politiques de tiering comme partie intégrante du manifest Kubernetes de l'application.

15.3 Contribution upstream

La crédibilité dans l'écosystème CXL/Linux se construit par la contribution au kernel mainline. Les axes identifiés sont les suivants : amélioration du scoring DAMON per-memcg en mode paddr (cf. limitation documentée §5.2.1), propositions de nouveaux quota goals pour le self-tuning DAMOS adaptés au tiering CXL, participation aux discussions DCD/Fabric Manager sur LKML, et contribution de tests au projet QEMU CXL. Les communautés DAMON (maintenue par SeongJae Park) et CXL kernel (Dan Williams, Jonathan Cameron, Gregory Price) sont les interlocuteurs clés. Chaque contribution upstream génère de la visibilité, renforce la crédibilité technique, et garantit que le kernel évolue dans une direction compatible avec l'architecture APEX.


16. Traçabilité des extensions

Extension Problème résolu Briques Partie I réutilisées Nouvelles briques Section
Multi-tier Serveurs à N devices CXL hétérogènes Topology Parser, Policy Compiler, DAMOS Graphe de topologie, seuils gradués §11
Bandwidth placement Workloads séquentiels plafonnés par la bande passante DRAM Latency-Aware Regulator, CRD Weighted interleave, détection bandwidth-bound §11.3
Backend vLLM KV KV cache > VRAM GPU, débordement vers CXL Topology Parser, QoS Policy Engine OffloadingBackend CXL, mbind NUMA-aware §12.2
KV Cache intelligent Gradient d'accès positionnel dans le KV cache Sniper PEBS, Risk Engine Prefetch prédictif, coordination cross-workload §12.3
Transport NIXL/CXL Transferts KV inter-GPU via RDMA trop lents Transport NIXL custom, staging CXL §12.4
KVCachePool CRD Orchestration K8s du budget KV cache CXL Opérateur K8s, MemoryTieringPolicy CRD KVCachePool, validation de sursouscription §12.5
Shared Memory intra Partage de tenseurs entre conteneurs co-localisés Plugin NRI, cgroup ACL Namespace objets, API gRPC, memfd+mbind §13.1
Shared Memory inter Partage mémoire entre serveurs via CXL fabric Fabric Manager, DCD, cohérence SW/HW, CSI driver §13.2
Observabilité Visibilité opérationnelle insuffisante Prometheus /metrics Dashboard Grafana, alerting, heatmap Pod §14.1
CLI apexctl Contrôle opérationnel (drain, policy, debug) gRPC daemon Commandes drain/benchmark/topology §14.2
Packaging enterprise Déploiement industriel reproductible systemd unit, image OCI Helm chart, matrice de compatibilité, CI §14.3

17. Références complémentaires (Partie II)

vLLM KV Cache Offloading — Projet vLLM (v0.11.0+). OffloadingConnector API, backend CPU natif, intégration LMCache/KServe. §12.2, §12.3.

NVIDIA Dynamo + NIXL — Framework de serving LLM distribué avec disaggregation prefill/decode. Transferts KV inter-GPU. §12.4.

CXL 3.0/3.2 Specification — Compute Express Link Consortium (décembre 2024). Dynamic Capacity Devices, Multi-Headed Devices, Back-Invalidation, FM-API, CXL Trusted Security Protocol. §13.2.

XConn Apollo — Premier switch hybride CXL/PCIe commercial. Démonstrations 100 TiB pooling avec MemVerge GISMO (OCP 2025, SC25). §13.2.

Astera Labs Leo — Plateforme de connectivité mémoire CXL. Support expansion, pooling et partage sur CXL 1.1/2.0. Déploiements cloud-scale en production. §13.2.

Weighted Interleave — Patches HMSDK SK hynix (Rakie Kim, Honggyu Kim), mergés Linux 6.9. Interleave pondéré entre nœuds NUMA hétérogènes via mempolicy. §11.3.

Pond — Li, H. et al. "CXL-Based Memory Pooling Systems for Cloud Platforms." ASPLOS '23, Meta. Caractérisation du memory pooling CXL et modélisation du mur de bande passante. §11.3, §13.2.

Memory Sharing with CXL — Berger et al. HCDS 2024. Approches hardware (back-invalidation CXL 3.0) et software (driver custom, OpenSHMEM) pour le partage mémoire multi-host. §13.2.

PNM-KV — Processing-Near-Memory pour KV store sur devices CXL. Jusqu'à 21.9× amélioration de throughput via computation côté device. §12.1.