一、传统 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 | 跨节点集合通信需在快层,模型参数可分层放置 |
一分级,十分容量;控好热冷,方能兼得。

发表评论 取消回复