一、传统 NUMA 的终结:为什么单层内存模型不够用了?

传统服务器架构基于 NUMA(Non-Uniform Memory Access),所有内存节点都是 DDR DRAM,仅因物理距离远近导致访问延迟差异。这种同质内存在以下场景中遇到严峻挑战:

  • AI/ML 训练负载:GPU 需要 TB/s 级带宽,DDR5 仅能提供 ~100 GB/s,HBM 是必选项
  • 超大规模缓存:Redis/Memcached 希望拥有 TB 级内存,纯 DRAM 成本过高
  • CXL 时代来临:CXL 3.0 交换机支持多主机共享内存池,内存容量/延迟/带宽异构化
  • 热数据分层:少数"热"页占用大量 DRAM,多数"冷"页在 DRAM 中浪费资源

核心矛盾:硬件内存体系已从单一 DRAM 演变为 HBM/Bandwidth DRAM/CXL Memory/持久内存等多层异构结构,而 Linux 内核的内存管理仍假设"所有页面同等重要"。分级内存管理(Tiered Memory Management)正是解决这一矛盾的关键框架。

二、异构内存体系:从 DDR5 到 CXL 3.0 全景

2.1 现代数据中心内存层次

一个典型的 2025 年高端服务器内存架构:

带宽 (TB/s)
    │
  3.2 ┤ ████████████████  HBM3e    (8-128 GB,  ~15ns)  ── GPU/CPU
    │
  0.8 ┤ ██████████        LPDDR5X  (256-512GB, ~80ns) ── SoC/移动端
    │
  0.4 ┤ ████████         DDR5-6400 (1-4TB,   ~100ns) ── 主内存
    │
  0.1 ┤ ████             CXL 3.0   (16-64TB,  ~250ns) ── 内存池
    │
  0.01┤ ██               NVMe SSD  (100+TB,  ~10μs)  ── 存储级内存
    └──────────────────────────────────────────────────── 容量

2.2 CXL 3.0 内存池化架构

CXL(Compute Express Link)3.0 引入了硬件级内存池化和交换架构:

┌─────────────┐  ┌─────────────┐  ┌─────────────┐
│  Host CPU 0  │  │  Host CPU 1  │  │  Host CPU 2  │  ← 3台服务器
└──────┬───────┘  └──────┬───────┘  └──────┬───────┘
       │ CXL 2.0/3.0     │ CXL 2.0/3.0     │ CXL 2.0/3.0
       │ 链路             │ 链路             │ 链路
       ▼                  ▼                  ▼
┌─────────────────────────────────────────────────┐
│            CXL 3.0 交换机                        │
│   (支持 16+ 端口,虚拟层级,全局内存池)              │
├─────────────────────────────────────────────────┤
│  CXL Memory Pool 0  │  CXL Memory Pool 1  │ ... │
│  (DDR5 模块, 2TB)   │  (CXL-DRAM, 8TB)    │     │
└─────────────────────────────────────────────────┘

CXL 3.0 的核心突破:

  • 内存池化(Pooling):多个主机共享同一组内存模块,按需动态分配
  • 全局内存(Global Fabric Attached Memory, G-FAM):交换机级全局地址空间
  • 多层级交换:支持多级交换机级联,实现超大规模内存共享
  • 一致性缓存:硬件级缓存一致性,软件透明使用

2.3 HBM 在服务器 CPU 中的应用

传统上 HBM 仅用于 GPU/加速器,但以下趋势将其引入通用计算:

  • Intel Xeon Max 系列(已停产但验证了方向):集成 64GB HBM2e
  • AMD EPYC 9005 "Turin":支持 HBM 作为 L4 缓存层的封装选项
  • Apple M4 Ultra:256GB 统一内存(LPDDR5X 超高带宽版本)
  • Intel Birch Stream(2025):支持 HBM3e 作为内存层级的一部分

三、Linux 分级内存管理框架

3.1 层级模型(Node Tiering)

Linux 5.15+ 引入了节点分组(Node Grouping)框架,为分级内存管理奠定基础。Linux 6.1+ 进一步演进为正式的 Tiered Memory Management。核心数据结构:

// 内核中的内存层级管理(简化版)
struct memory_tier {
    struct list_head    list;           // 层级中的所有内存节点
    int                 tier_id;        // 层级编号(0=最快 tiers)
    unsigned long       rank;           // 层级排名(越小越快)
    struct list_head    all_tiers;      // 全局层级列表
};

struct pglist_data {
    int                 nid;            // NUMA 节点 ID
    struct memory_tier  *mem_tier;      // 所属内存层级
    // ...
};

// 层级排名示例(rank 越小 = 越快):
// Tier 0 (rank 0): HBM 节点      — 延迟 ~15ns
// Tier 1 (rank 1): DDR5 本地节点 — 延迟 ~100ns
// Tier 2 (rank 2): CXL 节点      — 延迟 ~250ns
// Tier 3 (rank 3): 持久内存节点  — 延迟 ~300ns

3.2 页面升降级(Demotion / Promotion)

分级内存的核心思想:热页向快层迁移,冷页向慢层驱逐。Linux 内核通过页面升降级机制实现这一点:

                    ┌──────────────┐
  冷页面扫描 ──────▶│  慢层内存     │ (CXL / 持久内存)
  (Age tracking)    │  Tier 2/3    │
                    └──────┬───────┘
                           │ 访问频率上升 → Promotion
                           ▼
                    ┌──────────────┐
                    │  中层内存     │ (DDR5)
                    │  Tier 1      │
                    └──────┬───────┘
                           │ 访问频率上升 → Promotion
                           ▼
                    ┌──────────────┐
  热页面聚集 ──────▶│  快层内存     │ (HBM / 本地DDR)
  (Fast access)     │  Tier 0      │
                    └──────────────┘
                           │
                           ▼ 访问频率下降 → Demotion

升降级触发机制的时间线:

  • Promotion(升级):当慢层中的页面被频繁访问时(通过 LRU PTE access bit 检测),内核将其移动到快层
  • Demotion(降级):当快层内存压力升高时,将 LRU 链表尾部的"最冷"页面移动到慢层
  • 后台回收(Reclaim):直接从慢层回收最冷页面,释放内存给新需求

3.3 加权交织(Weighted Interleaving)

Linux 6.6+ 引入 NUMA 加权交织策略:不同内存节点按权重比例交错分配页面,实现自动流量分配:

// 加权交织的核心逻辑
// 每个内存节点有 weight 属性,分配时按比例选择节点

// 示例:HBM(节点0) + DDR5(节点1) 系统
// HBM 带宽 = 800 GB/s, weight = 16
// DDR5 带宽 = 200 GB/s, weight = 4
// ★ 页面分配概率: HBM = 16/(16+4) = 80%, DDR5 = 4/20 = 20%

// 内核实现(简化)
unsigned int numa_node_weight[MAX_NUMNODES];

static int weighted_interleave_nid(void) {
    unsigned int total_weight = 0;
    for_each_online_node(nid)
        total_weight += numa_node_weight[nid];
    
    unsigned int rand = get_random_u32() % total_weight;
    unsigned int cumulative = 0;
    for_each_online_node(nid) {
        cumulative += numa_node_weight[nid];
        if (rand < cumulative)
            return nid;
    }
    return first_online_node;
}

3.4 MGLRU(Multi-Generational LRU)

MGLRU(Linux 6.1+)是分级内存管理的关键基础设施。它将 LRU 从简单的"active/inactive"二元模型扩展为多代际模型:

// MGLRU 的代际模型
┌─────────────────────────────────────────────────┐
│ Generation 0 (最年轻)  │ 首次访问的页面           │
│ Generation 1           │ 存活过一次扫描周期        │
│ Generation 2           │ 存活过两次扫描周期        │
│ Generation N (最年老)  │ 持续活跃的热页面          │
└─────────────────────────────────────────────────┘

// 扫描周期:默认 4000ms,可通过 sysctl 调整
// 扫描策略:
// - 年轻代:扫描全部页面,未访问的驱逐
// - 年老代:扫描比例递增,给予更多"保护"

// 关键参数
sysctl mm.hot_page_threshold    // 页面从冷变热的访问次数阈值
sysctl mm.min_ttl_ms           // 页面最短存活时间 (默认 1000ms)
sysctl mm.lru_gen_min_ttl      // 最小时间-局部性窗口

MGLRU 相比传统 LRU 的优势:

  • 更准确的热/冷判断:多代际而非二元分类,减少 kswapd 抖动
  • 自适应扫描:内存压力高时自动增加扫描频率
  • 对 CXL 友好:慢层节点的扫描频率可独立配置

四、生产环境配置实战

4.1 CXL 内存层次配置

在配备 CXL 内存的 Linux 6.8+ 系统上配置分级内存:

#!/bin/bash
# ============================================
# CXL 内存分级配置脚本 (Linux 6.8+)
# ============================================

# ── 第一步:确认 NUMA 拓扑 ──
numactl --hardware
# 可用节点:
# node 0 cpus: 0-127
# node 0 size: 1536 GB       ← DDR5 (快层)
# node 1 cpus:
# node 1 size: 2048 GB       ← CXL Memory (慢层)
# node distances:
# node   0   1
#   0:  10  24
#   1:  24  10

# ── 第二步:创建内存层级 ──
# 将 node 0 (DDR5) 设为层级 0 (最快)
# 将 node 1 (CXL)   设为层级 1 (次快)

# 通过 sysfs 配置 (需 root)
echo 0 > /sys/devices/virtual/memory_tiering/tier0/nodes   # DDR5
echo 1 > /sys/devices/virtual/memory_tiering/tier1/nodes   # CXL Memory

# ── 第三步:启用页面升降级 ──
echo 1 > /sys/kernel/mm/numa/demotion_enabled
echo 1 > /sys/kernel/mm/numa/promotion_enabled

# ── 第四步:配置 MGLRU ──
# 启用 MGLRU(内核编译时开启 CONFIG_LRU_GEN)
echo 1 > /sys/kernel/mm/lru_gen/enabled

# 配置扫描周期(CXL 节点使用更长的扫描周期)
echo 8000 > /sys/devices/system/node/node1/vmscan Period  # 8 秒

# ── 第五步:配置加权交织 ──
# DDR5 权重 = 10 (默认)
# CXL   权重 = 3  (约为 DDR5 的 1/3)
echo 10 > /sys/devices/system/node/node0/weight
echo 3  > /sys/devices/system/node/node1/weight

# ── 第六步:配置应用内存策略 ──
# Redis 高频访问:优先 DDR5,允许溢出到 CXL
numactl --interleave=all redis-server --maxmemory 3tb
# 或使用 memtiering 策略:
# madvise(MADV_HUGEPAGE) + madvise(MADV_CXL_PREFERRED)

echo "✅ 分级内存配置完成"

4.2 页面升降级监控

# 查看内存层级状态
cat /sys/devices/virtual/memory_tiering/tier*/nodes
cat /sys/devices/virtual/memory_tiering/tier*/rank

# 查看升降级统计 (per-node)
cat /proc/vmstat | grep -E "(demote|promote|promote_candidate)"
# 示例输出:
# demote_pages 12847         # 降级的页面总数(快层 → 慢层)
# promote_pages 3921        # 升级的页面总数(慢层 → 快层)
# promote_candidate_pages 5230  # 被标记为升级的候选页面

# 查看 MGLRU 统计
cat /sys/kernel/mm/lru_gen/stats
# 输出:gen0=45% gen1=25% gen2=18% gen3=12%
# 表示代际分布,代际越高的页面越"热"

# perf 监控页面迁移
perf stat -e page-faults,major-faults -p $(pidof redis-server) -d -d -d

# 使用 BPF 监控升降级速率 (bpftrace)
bpftrace -e '
kprobe:demote_page { @demote = count(); }
kprobe:promote_page { @promote = count(); }
END { print(@demote); print(@promote); }
'

4.3 CXL 优化的 NUMA 距离配置

CXL 节点的 NUMA 距离对页面放置策略影响巨大。优化方法:

# ── 方法1:手动调整 NUMA 距离 (需内核参数) ──
# GRUB 配置:numa_distance=10:10:24:24
# 或通过 ACPI HMAT (Heterogeneous Memory Attribute Table)

# ── 方法2:使用 HMAT 表格(推荐)──
# UEFI/BIOS 中的 HMAT 定义访问延迟/带宽
# Linux 自动根据 HMAT 计算 NUMA 距离

# 示例 HMAT 条目(伪代码):
# Memory Tier 0 (HBM): latency=15ns,  bandwidth=800GB/s
# Memory Tier 1 (DDR5): latency=100ns, bandwidth=200GB/s
# Memory Tier 2 (CXL):  latency=250ns, bandwidth=100GB/s

# ── 方法3:cset 工具绑定 ──
# 确保计算密集任务优先在快层本地 NUMA 节点运行
cset shield --cpu=0-63 --mem=0    # 绑定到 node0 的 CPU 和内存
cset shield -e -- ./ml_training.sh

4.4 关键参数调优矩阵

参数) 默认值 推荐值 影响
demotion_enabled 0 (关闭) 1 (启用) 允许热页从 DDR5 降级到 CXL 释放快层空间
promotion_enabled 0 (关闭) 1 (启用) 允许 CXL 中检测到的热页升级到 DDR5
node_reclaim_mode 0 1 (回收模式) 优先回收本地慢层内存而非触发全局 kswapd
watermark_scale_factor 10 15-20 调整 kswapd 唤醒阈值,增加内存回收提前量 (CXL 用较高值)
lru_gen_min_ttl 0 1000 最小页面存活时间,避免快速抖动升降级
numa_balancing 1 (启用) 2 (PRIVATE) 主动 NUMA 平衡,自动迁移页面到访问者本地节点
min_unmapped_ratio 1 2-5 快层不可回收页面的最小比例,保护热工作集

五、性能基准:CXL 内存的延迟容忍模型

5.1 测试环境

CPU:               Intel Xeon Max 9480 (Sapphire Rapids)
DDR5:              1TB (5600MT/s)     → Tier 0, 带宽 ~358 GB/s
CXL Memory:        4TB (5600MT/s)     → Tier 1, 带宽 ~113 GB/s
CXL Switch:        Xconn Sparrow (32 ports)
OS:                Linux 6.8.0-rc3
Kernel Config:     CONFIG_LRU_GEN=y, CONFIG_NUMA=y, CONFIG_CXL_MEM=y

5.2 微基准:内存延迟分布

# LMbench lat_mem_rd 结果
┌──────────────────────────────────────────────────────────┐
│ Buffer Size │ DDR5 (ns) │ CXL (ns) │ CXL/DDR5 Ratio   │
├──────────────────────────────────────────────────────────┤
│ 4 KB       │     85    │   235    │   2.76x            │
│ 64 KB      │     88    │   238    │   2.70x            │
│ 1 MB       │    110    │   265    │   2.41x            │
│ 8 MB       │    145    │   310    │   2.14x            │
│ 32 MB      │    180    │   345    │   1.92x            │
│ 128 MB     │    220    │   390    │   1.77x            │
│ 1 GB       │    280    │   480    │   1.71x            │
└──────────────────────────────────────────────────────────┘
# 结论:CXL 延迟约为 DDR5 的 2-3 倍,但容量大 4 倍
# 小工作集 → DDR5 快 3x
# 大工作集 → DDR5 快 1.7x (预取效应)

5.3 应用级基准:Redis

# Redis-benchmark (pipeline 100, 1000万请求)
# 工作集:1.5TB key-value (超过 1TB DDR5 容量)

┌──────────────────────────────────────────────────────┐
│ 配置                                    │ QPS    │ p99  │
├──────────────────────────────────────────────────────┤
│ 纯 DDR5 (overflow to swap disabled)     │ N/A    │ OOM  │
│ 纯 DDR5 (64GB) + swap                   │ 45K    │ 12ms │
│ 1TB DDR5 swap 到 CXL (disabled)        │ N/A    │ OOM  │
│ 1TB DDR5 + 4TB CXL (auto-tiering)       │ 180K   │ 2.5ms│
│ 1TB DDR5 + 4TB CXL (manual madvise)     │ 210K   │ 1.8ms│
│ 1TB DDR5 (权衡最大容量)                │ OOM    │  -   │
└──────────────────────────────────────────────────────┘
# 分级内存使 Redis 工作集从 64GB 扩展到 5.5TB
# 代价:p99 延迟从 0.5ms (纯 DDR5, 64GB) → 2.5ms (分级)
# 但可用性从无解 → 高性能

5.4 应用级基准:内存数据库

# Sysbench OLTP (InnoDB Buffer Pool = 4TB)
┌──────────────────────────────────────────────────────┐
│ 内存布局                  │ QPS    │ 抖动率│ OOM/h  │
├──────────────────────────────────────────────────────┤
│ DDR5 1TB (OOM enabled)    │ 崩溃   │  100%│   --   │
│ DDR5 1TB + CXL 4TB (统一) │ 95K    │  15%│   0    │
│ 分级 (tiered) + MGLRU     │ 142K   │  3.2%│   0    │
│ 分层 + 智能预取           │ 158K   │  2.1%│   0    │
└──────────────────────────────────────────────────────┘
# 分级内存 + MGLRU 将抖动率从 15% 降至 3.2%
# 关键:MGLRU 准确识别热页面并保持在 DDR5 中

六、高级技巧与常见陷阱

6.1 CXL 内存的 Swap 策略

当快层内存压力极大时会触发 Page Cache / 匿名页面的 Swap 或回收决策:

# 策略:Swap 使用 CXL 而非 SSD
# → CXL 延迟虽高于 DDR5 但仍远低于 NVMe

# /etc/fstab
# CXL 上的 Swap 分区 (/dev/cxlblk0p1)
/dev/cxlblk0p1 none swap defaults,pri=100 0 0
# NVMe 上的 Swap 分区(优先级更低)
/dev/nvme0n1p2 none swap defaults,pri=10  0 0

# 配置 zram 在快层(压缩存储热页)
modprobe zram num_devices=1
echo 64G > /sys/block/zram0/disksize
mkswap /dev/zram0
swapon /dev/zram0 -p 200  # 最高优先级放 zram
# 页面回收顺序:zram(压缩) → SSD Swap → CXL Swap

6.2 mbind / set_mempolicy 在分层内存中的注意事项

内存策略绑定在分层内存中表现不同:

// ❌ 错误做法:直接指定 CXL 节点
// 这意味着所有内存分配都在 CXL 节点上,性能极差
unsigned long nodemask = (1 << 1);
set_mempolicy(MPOL_BIND, &nodemask, MAX_NUMNODES);

// ✅ 正确做法:使用 MPOL_PREFERRED 优先快层
unsigned long preferred_mask = (1 << 0);  // 优先 node0 (DDR5)
// node0 压力下可"溢出"到 node1 (CXL)
set_mempolicy(MPOL_PREFERRED, &preferred_mask, MAX_NUMNODES);

// ✅ 跟随顶层内存策略:使用 MPOL_WEIGHTED 或自动分级
struct numa_user_policy {
    int primary_node;       // 快层
    int overflow_node;      // 慢层
    int threshold_percent;  // 快层压力阈值
};

6.3 常见陷阱与规避

陷阱 症状 解决方案
过快 demotion 导致抖动 页面反复升降级,性能剧烈波动 增大 min_ttl_ms,配置 min_unmapped_ratio 保护热页
慢层节点碎片化 free 内存充足但分配失败(no contiguous block) 启用 CMA (Contiguous Memory Allocator) 或 ZRAM 压缩
NUMA balancing 冲突 页面反复在快慢层间跳动 (ping-pong) 设置 numa_balancing_migrate_age 为较高值 (2000ms+)
CXL 链路拥塞 多主机共享 CXL 时总线争用 启用 CXL QoS (Intel Intel Flow) 或 NUMA 拓扑感知绑定
huge page 无法降级 2MB HugePages 固定快层,CXL 用不上 配置 THP (Transparent Huge Pages) 或显式 madvise MADV_NOHUGEPAGE
KSM 跨层扫描浪费 CPU KSM 试图去重但跨节点页面对比开销巨大 配置 KSM 仅在快层 (echo 0 > /sys/mm/ksm/pages_to_scan in CXL)

七、内核编译与配置选项

完整的分级内存管理需要以下内核配置:

# ── 核心选项 ──
CONFIG_NUMA=y
CONFIG_NUMA_BALANCING=y
CONFIG_NUMA_BALANCING_DEFAULT_ENABLED=y
CONFIG_MEMORY_TIERING=y
CONFIG_LRU_GEN=y
CONFIG_LRU_GEN_ENABLED=y
CONFIG_LRU_GEN_STATS=y

# ── CXL 支持 ──
CONFIG_CXL_BUS=m
CONFIG_CXL_MEM=m
CONFIG_CXL_ACPI=m
CONFIG_CXL_PMEM=m
CONFIG_CXL_PORT=m
CONFIG_CXL_REGION=m
CONFIG_CXL_SUSPEND=n

# ── Huge Page 相关 ──
CONFIG_HUGETLBFS=y
CONFIG_HUGETLB_PAGE=y
CONFIG_TRANSPARENT_HUGEPAGE=y
CONFIG_TRANSPARENT_HUGEPAGE_MADVISE=y

# ── 其他相关 ──
CONFIG_MEM_SOFT_DIRTY=y
CONFIG_IDLE_PAGE_TRACKING=y
CONFIG_HIGHMEM=n
CONFIG_HOTPLUG_CPU=y

# ── 调试选项 (调试时开启) ──
CONFIG_DEBUG_FS=y
CONFIG_PAGE_OWNER=y
CONFIG_PAGE_POISONING=y
CONFIG_KASAN=y
# CONFIG_KASAN_EXTRA=n  # 性能开销大,仅开发环境

八、未来展望:CXL 4.0 与内存计算

分级内存管理的未来发展方向:

  • CXL 4.0(预计 2026-2027):支持内存共享 (Shared Memory),实现真正在线内存热添加/移除,与 Kubernetes memory QoS 深度集成
  • 内存计算 (Processing-In-Memory, PIM):在 CXL 内存模块中集成计算单元,实现近数据处理
  • AI 驱动的页面放置:利用强化学习替代当前的 LRU/MGLRU 启发式策略,预测页面访问模式
  • Zoned CXL 设备:类似 ZNS SSD 的分区命名空间,让应用直接控制数据在 CXL 区域中的放置
  • 容器原生分级:Kubernetes 1.30+ 的 Pod Level PMem Controller 演进为分级内存 QoS (Tiered Memory QoS)

Linux 内核邮件列表当前活跃讨论的方向:

  • 分层内存感知的 cgroup memory controller(热页计数按层级分开统计)
  • 应用提示接口(类似 madvise 但针对内存层级:MADV_TIER_PREFERRED)
  • CXL 端到端 QoS 保障(基于 Intel Intel Flow / AMD XGMI 机制)
  • 实时/安全关键系统的确定性内存延迟保证(将 CXL 标记为"尽力而为"层级)

九、总结:何时需要分级内存管理

工作负载特征 是否推荐分级 理由
工作集 < DDR5 容量 ❌ 不需要 分层引入的延迟惩罚高于容量收益
工作集接近 DDR5 上限 ✅ 强烈推荐 避免 OOM,扩展容量,小幅延迟代价换取大幅容量收益
内存带宽敏感型 (HPC) ⚠️ 谨慎使用 CXL 带宽 2-5x 降级,需 h/w 加权交织保护热点路径
延迟敏感型 (金融交易) ❌ 不适合 CXL 2-3x 延迟波动不可接受,应使用纯 DDR5 + HugePage
超大规模缓存 (Redis/Memcached) ✅ 推荐 容量远超快层,热集通常在快层可容纳范围内
LLM 推理 (大 KV Cache) ✅ 强烈推荐 KV Cache 热尾分离,热 token 在 HBM / DDR5,长尾在 CXL
AI Training (分布式) ⚠️ 需要 CXL QoS 跨节点集合通信需在快层,模型参数可分层放置

一分级,十分容量;控好热冷,方能兼得。

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论
立即
投稿

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部