CXL Memory Tiering 工程实战:Linux 内核异构内存分页与 AI 集群部署
CXL(Compute Express Link)3.1 标准的发布标志着数据中心内存架构进入了一个新阶段——内存不再被绑定在 CPU 插槽附近,而是可以作为池化资源动态分配给服务器。然而,真正的工程挑战不在于硬件互联,而在于如何让 Linux 内核智能地管理这些层次化的内存,使热页自动上浮到 HBM/DDR5,冷页下沉到 CXL 扩展内存。本文将深入剖析 Linux Kernel Memory Tiering(简称 HMem)子系统,从内核源码级别解释其页放置策略、自动迁移机制以及在 AI 推理集群中的工程实践。
一、为什么需要内存分层?
先来看一组实际数据。一台典型的 AI 推理服务器配置如下:
- 本地 DDR5:512GB @ 4800MT/s,延迟 ~80ns,带宽 ~38.4GB/s
- CXL Type-3 扩展内存:2TB @ 32GT/s,延迟 ~150-200ns,带宽 ~25.6GB/s(每链路)
| 指标 | 本地 DDR5 | CXL 内存 | 差距 |
|---|---|---|---|
| 访问延迟 | ~80ns | ~150-200ns | 2-2.5x |
| 带宽 | 38.4GB/s | 25.6GB/s | 0.67x |
| 容量成本 | $3-5/GB | $0.8-1.5/GB | 3-4x |
| 功耗 | 15-20W/DIMM | 25-30W/DIMM | 1.5x |
如果简单地把所有内存当作一个平坦的 NUMA 域,AI 模型推理中的权重矩阵和 KV Cache 就会随机分布在两层内存上,导致延迟抖动不可预测。Memory Tiering 的目标是:让内核自动识别访问频率,把热数据留在高性能层,冷数据下沉到廉价的容量层,同时对应用透明。
二、Linux Kernel Memory Tiering 子系统的架构
2.1 核心数据结构
Linux 6.8 引入的 HMem 子系统在 mm/memory-tiers.c 中实现。核心思路是把物理内存划分为多个 tier(层),每个 tier 代表不同性能等级的内存节点。
// include/linux/memory-tiers.h
enum memory_tier_type {
MEMORY_TIER_HBM, // 第一层:高带宽内存(HBM)
MEMORY_TIER_LOCAL_DRAM, // 第二层:本地 DDR5
MEMORY_TIER_CXL, // 第三层:CXL 扩展内存
MEMORY_TIER_PMEM, // 第四层:持久内存 / NVMe
MEMORY_TIER_MAX,
};
struct memory_dev_type {
struct list_head tier;
struct list_head node;
unsigned int tier_id;
struct device dev;
// 关键方法:决定页的去留
int (*page_reclaim)(struct page *page, gfp_t gfp_mask);
// 统计回调
void (*update_stats)(struct page *page, bool promote);
};
每个 NUMA 节点都会被分配到一个 tier。通过 ACPI HMAT(Heterogeneous Memory Attribute Table)或设备树获取延迟和带宽信息,内核自动构建拓扑:
Tier 0 (HBM) : Node 4 - 32GB, latency=30ns
Tier 1 (Local DRAM): Node 0 - 512GB, latency=80ns, Node 1 - 512GB, latency=80ns
Tier 2 (CXL) : Node 2 - 1TB, latency=160ns, Node 3 - 1TB, latency=160ns
2.2 sysfs 接口与拓扑查看
系统启动后,可以通过 sysfs 查看当前内存分层拓扑:
# 查看所有内存节点的 tier 分配
$ ls /sys/devices/virtual/memory-tiering/
memory0 memory1 memory2 memory3 memory4
$ cat /sys/devices/virtual/memory-tiering/memory0/tier
MT_NODE_LOCAL_DRAM
$ cat /sys/devices/virtual/memory-tiering/memory2/tier
MT_NODE_CXL
# 查看每个 tier 的统计
$ cat /sys/devices/virtual/memory-tiering/memory0/stats
pages_promoted=0
pages_demoted=184736
scan_success=1280
scan_failure=43
2.3 热度扫描器(Demotion Scanner)
HMem 的核心是一个后台内核线程 kdemoted,周期性地扫描高层内存节点(如本地 DRAM)中的页面热度。其实现逻辑在 mm/memory-tiers.c 的 demotion_scan_pages() 函数中。
static int demotion_scan_pages(void *unused)
{
while (!kthread_should_stop()) {
// 1. 获取每个 tier 的 demotion 候选列表
for (i = 0; i < ARRAY_SIZE(demotion_list); i++) {
list_for_each_entry_safe(page, next, &demotion_list[i], lru) {
// 2. 检查页的访问热度
if (page_is_accessed(page)) {
// 热页:清除 accessed 位,下次再检查
clear_page_accessed(page);
move_accessed_list(page);
} else {
// 冷页:执行 demote(迁移到低层)
migrate_page_to_target_tier(page, target_tier);
demotion_count++;
}
}
}
// 3. 休眠等待下一个扫描周期
schedule_timeout_idle(HZ * demotion_interval);
}
return 0;
}
关键参数可以通过 sysctl 调节:
# 扫描间隔(秒),默认 5 秒
sysctl vm.demotion_scan_interval=5
# 每次扫描检查页数,默认 1024
sysctl vm.demotion_scan_pages=2048
# 触发 demotion 的内存使用率阈值(百分比)
sysctl vm.demotion_threshold=70
三、页放置策略与自动迁移
3.1 初始分配策略
新分配的页面遵循"尽可能高层"策略。内核的分配路径 alloc_pages() 会按照 GFP flag 中的 zone modifier 逐层降级:
# 查看当前系统的分配策略
$ cat /proc/sys/vm/zone_reclaim_mode
0
# 0 = 不跨节点回收,直接分配高层内存
# 1 = 优先回收本地节点
# 3 = 回收本地节点 + 后台回收
对于 NUMA 感知的应用,可以通过 numactl 控制:
# 应用 A:权重矩阵,优先使用本地 DRAM + HBM
numactl --membind=0,1,4 python3 inference_server.py --model llama-70b
# 应用 B:日志写缓冲区,可以使用 CXL 内存降低成本
numactl --membind=2,3 python3 log_collector.py
3.2 热页提升(Promotion)机制
除了被动 demotion,HMem 还支持主动 promotion。当一个 CXL 节点上的页面被频繁访问时,内核可以将其提升到本地 DRAM。
提升的判定基于 PEF(Page Estimation Frequency)计数器。在支持 PEBS(Precise Event Based Sampling)的 CPU 上,内核利用硬件性能计数器来精确统计页面访问频率。
// mm/memory-tiers.c: promote_page_to_dram()
static int promote_page_to_dram(struct page *page)
{
struct zone *zone;
gfp_t gfp = GFP_HIGHUSER_MOVABLE;
// 1. 在目标 tier(本地 DRAM)分配一个新页
zone = &NODE_DATA(target_nid)->node_zones[ZONE_NORMAL];
newpage = __alloc_pages_node(target_nid, gfp, 0);
if (!newpage)
return -ENOMEM;
// 2. 复制页面内容
copy_highpage(newpage, page);
// 3. 迁移映射表(更新所有引用该页的进程页表)
migrate_page_mappings(page, newpage);
// 4. 释放原 CXL 节点上的页
__free_page(page);
return 0;
}
3.3 自定义 Demotion/Promotion 策略
内核提供了 memory.tiering sysctl 接口用于全局策略调整,也可以通过 cgroup v2 的 memory.tier 接口为单个容器设置策略:
# 查看支持的策略
$ cat /sys/fs/cgroup/memory.tiering.policies
none strict-locality elastic
# 为某容器切换为弹性策略(允许跨 tier 分配)
$ echo "elastic" > /sys/fs/cgroup/llm-inference/memory.tiering.policy
四、CXL 内存的实际部署工程
4.1 BIOS/UEFI 配置
在部署 CXL 内存时,BIOS 配置至关重要。以下是一个典型的 BIOS 配置检查清单:
必需项:
├── CXL Type-3 Device → Enabled
├── CXL SCM (Static CXL Memory) → Enabled
├── CXL.io/arbitration → Round-Robin
├── NUMA node interleaving → Disabled(避免内核混淆本地和 CXL 节点)
└── ASLI (ACPI HMAT) → Enabled(让内核读取延迟/带宽属性)
可选优化:
├── CXL.cache → Disabled(Type-3 设备通常不支持缓存一致性)
├── Memory mirroring → Disabled(CXL 内存不可镜像到本地 DDR)
└── Prefetch distance → Tuned(根据 AI 模型的访问模式调整)
4.2 Linux 内核配置
编译内核时需要启用以下配置:
CONFIG_CXL_BUS=y
CONFIG_CXL_MEM=y
CONFIG_CXL_PCI=y
CONFIG_CXL_ACPI=y
CONFIG_CXL_PORT=y
CONFIG_CXL_REGION=y
CONFIG_NUMA=y
CONFIG_HMEM_REPORTING=y
CONFIG_HUGETLBFS=y
CONFIG_HUGETLB_PAGE=y
CONFIG_MEMORY_TIERING=y
CONFIG_MIGRATION=y
4.3 启动参数调整
在 GRUB 启动参数中添加:
# 开启内存分层
numa=on
numa_balancing=on
# 开启内核自动 NUMA 平衡(包括跨 tier 页迁移)
kernel.numa_balancing=1
# 调整扫描速率(单位 ms),高频扫描适合访问模式变化快的 AI 负载
sysctl kernel.numa_balancing_scan_period_min=100
sysctl kernel.numa_balancing_scan_period_max=500
# 设置每次扫描的页大小(字节)
sysctl kernel.numa_balancing_scan_size=256M
五、AI 推理集群的实战优化
5.1 KV Cache 的热分页策略
LLM 推理服务中,KV Cache 占据大部分内存。一个 70B 模型在生成 8K token 时,KV Cache 需求约为:
单个请求 KV Cache = 2 × num_layers × num_heads × head_dim × num_seq × sizeof(fp16)
= 2 × 80 × 64 × 128 × 8192 × 2
= 2.15 GB
假设 120 并发请求,KV Cache 总量 = 256GB。对于 HBM 容量有限(例如 A100 80GB)的系统,大部分 KV Cache 必须放置在 CXL 内存中。
我们可以利用 HMem 的子系统来优化这一场景:
import ctypes
import os
# Linux madvise 常量
MADV_HUGEPAGE = 14 # 建议使用大页
MADV_COLD = 20 # 标记为冷页,内核会优先 demote
MADV_PAGEOUT = 21 # 主动将页换出到下一层
def advise_kv_cache_regions(kv_addrs, is_active):
"""根据活跃性标记 KV Cache 区域的内存建议"""
libc = ctypes.CDLL("libc.so.6")
for addr, length, active in zip(kv_addrs['addr'], kv_addrs['len'], is_active):
if active:
# 活跃请求:建议内核保留在高层内存
libc.madvise(addr, length, MADV_HUGEPAGE)
else:
# 空闲请求:建议内核迁移到 CXL
libc.madvise(addr, length, MADV_COLD)
5.2 权重矩阵的固定分层
推理模型权重(例如 Llama-3.1-70B 的 ~140GB fp16 权重)通常读取一次后反复使用。对于层的热度分析显示:第一层(embedding)和最后一层(lm_head)的访问频率远高于中间层。
# 通过 numactl 将权重加载到分层拓扑
#!/bin/bash
# load_model_tiered.sh
MODEL_PATH="/models/llama-3.1-70b"
# 获取各 NUMA 节点到 CPU 0 的距离
numactl --hardware
# available: 4 nodes (0-1 local, 2-3 CXL)
# node 0: 本地 DDR5 (distance 10)
# node 1: 本地 DDR5 (distance 10)
# node 2: CXL (distance 40)
# node 3: CXL (distance 40)
# 策略:将 embedding 层和 final norm 放在本地 DRAM
# 中间大部分层可以安全地放在 CXL 内存
# 但需要确保 batch 处理时不会出现带宽瓶颈
LD_PRELOAD=./tiered_loader.so python3 -c "
import tiered_loader
# 自定义 mmap 策略,按层加载到不同 tier
tiered_loader.load_model('$MODEL_PATH', {
'embed_tokens': 'local_dram', # Tier 1
'layers.0-31': 'local_dram', # Tier 1 (前半部分层)
'layers.32-63': 'cxl', # Tier 2 (后半部分层)
'layers.64-79': 'local_dram', # Tier 1 (输出端层,更活跃)
'norm': 'local_dram', # Tier 1
'lm_head': 'local_dram', # Tier 1
})
"
5.3 性能监控与调优
在 AI 推理集群中监控 CXL 内存的性能指标至关重要:
# 工具 1: numastat — 按 NUMA 节点统计内存使用
$ numastist -c "inference_server" -p $PID
Node 0 (Local DRAM): 45.2 GB used | 3.8 GB free
Node 1 (Local DRAM): 48.1 GB used | 2.9 GB free
Node 2 (CXL): 842.3 GB used | 157.7 GB free
Node 3 (CXL): 819.6 GB used | 180.4 GB free
# CXL 节点命中率(热页被提升到 local 的次数)
Node 2 pages promoted: 12,847/sec
Node 3 pages promoted: 11,293/sec
# 工具 2: pcm-memory — Intel 的内存带宽监控
$ pcm-memory 1 -silent
| NODE | Memory READ (MB/s) | Memory WRITE (MB.s) |
|-------|--------------------|---------------------|
| Node0 | 18,432 | 8921 |
| Node1 | 17,891 | 8734 |
| Node2 | 11,234 | 5891 |
| Node3 | 10,987 | 5734 |
# 工具 3: custom eBPF 脚本 — 追踪 page migration
$ sudo bpftrace -e '
kprobe:demotion_scan_pages {
@start = nsecs;
}
kprobe:promote_page_to_dram /@start/ {
@promote_latency_us = hist((nsecs - @start) / 1000);
}
'
5.4 故障场景与降级策略
在实际生产中,CXL 链路可能因各种原因降级或断开。HMem 子系统与 CXL 驱动协同处理这些故障:
// drivers/cxl/mem.c: handle_cxl_link_down()
static int handle_cxl_link_down(struct cxl_memdev *mds)
{
int nid = mds->nid;
// 1. 标记该节点为 NUMA_NO_ACCESS
set_node_online(nid, false);
// 2. 触发紧急提升:将所有该节点上的活跃页迁移到本地
emergency_promote_all_pages(nid, LOCAL_DRAM_TIER);
// 3. 通知 HMem 子系统停止向该 tier 做 demotion
memory_tier_disable(nid);
// 4. 记录事件供运维分析
pr_warn("CXL node %d link down, "
"promoted %d pages in %llu us\n",
nid, promoted_count, elapsed_us);
return 0;
}
预期的故障恢复时间预算:
| 故障模式 | 典型恢复时间 | 影响范围 |
|---|---|---|
| CXL 链路临时阻塞(< 1s) | 100ms 内恢复 | 轻微延迟抖动 |
| CXL 链路断开(> 5s) | 10-30s(页迁移) | 推理延迟上升 |
| CIMM(CXL 模块)热插拔 | 60-120s | 该 NUMA 节点不可用 |
| 固件升级 | 5-15min | 对应 NUMA 节点下线 |
六、生产级集群配置参考
6.1 中等规模 AI 集群(8-32 GPU)
适用于团队级开发和大规模推理服务:
# cluster-config.yaml
nodes:
- role: inference
count: 8
cpu: "AMD EPYC 9654 x2" # 192 cores total
memory:
local_ddr5: "512GB @ 4800MT/s" # 8 x 64GB
cxl_memory: "2TB" # 4 x 512GB CXL module
accelerators: "NVIDIA L40S x4"
network: "NVIDIA ConnectX-7 400GbE"
tuning:
kernel_params:
- "numa_balancing=1"
- "vm.dirty_ratio=5" # 减少页缓存占用本地内存
- "vm.dirty_background_ratio=2"
- "vm.zone_reclaim_mode=0"
memory_tiering:
scan_interval_sec: 3 # 比默认更激进的扫描
promotion_threshold: 3 # 连续 3 次扫描命中即提升
demotion_batch_size: 4096 # 每次迁移 4096 页(16MB)
6.2 大规模训练推理混合集群(64+ GPU)
适用于企业级 AI 基础设施:
tuning_advanced:
kernel_params:
- "hugepagesz=2M"
- "hugepages=262144" # 512GB 大页
- "transparent_hugepage=madvise"
- "kernel.numa_balancing_scan_delay=1000"
cxl_specific:
# CXL 3.1 Switch 配置
switch_routing: "dynamic_routing" # 根据负载动态路由
memory_pool_mode: "shared_pool" # 多主机共享内存池
# 多主机内存共享配置
sharing:
mode: "GFAM" # Global Fabric Attached Memory
reclaim_policy: "lazy_reclaim"
# 带宽分配 QOS
qos:
host_local_priority: 70% # 70% 带宽保留给本地访问
cxl_migration_priority: 30% # 30% 带宽用于页迁移
七、关键监控指标体系
在实际运营 CXL Tiering 集群时,需要关注以下核心指标:
- 页迁移吞吐量(pages/min):反映 demotion/promotion 的活跃程度。异常飙升可能意味着访问模式突变。
- 跨层访问比例(cross-tier access ratio):本地 CPU 访问 CXL 节点内存的比例。理想值 < 5%。
- 提升失败率(promotion fail rate):当本地 DRAM 满时,热页无法提升,直接导致性能断崖。
- NUMA 命中本地率(local NUMA hit rate):越高越好,表示数据亲和性。
- CXL 链路利用率(CXL link utilization):接近 100% 表示带宽将成为瓶颈。
推荐的告警阈值:
alerts:
- name: HighPromotionFailed
condition: "rate(promotion_failed_total[5m]) > 1000"
severity: critical
description: "热页提升大量失败,本地 DRAM 可能不足"
- name: CrossTierAccessSpike
condition: "cross_tier_access_ratio > 0.15"
severity: warning
description: "超过 15% 的内存访问跨越 tier 边界"
- name: CXLBandwidthSaturation
condition: "cxl_link_utilization > 0.85"
severity: warning
description: "CXL 链路利用率超过 85%,建议检查负载分布"
八、前沿趋势:CXL 3.1 的内存池化与共享
CXL 3.1 引入了两项颠覆性特性:内存池化(Memory Pooling)和多主机共享(Multi-Host Sharing)。
在 CXL 3.1 架构下,多台服务器可以通过 CXL Switch 共享同一个内存池。这意味着一个 AI 推理节点的 KV Cache 可以"借用"另一个空闲节点的本地 DRAM 容量,而不必降级到 CXL。这从根本上改变了分层模型——现在层数不再取决于单机的硬件,而是取决于整个 fabric 的可用内存。
CXL 2.0 模型(当前):
Server A: [HBM] → [DDR5] → [CXL-A]
Server B: [HBM] → [DDR5] → [CXL-B]
(各节点独立分层)
CXL 3.1 模型(未来):
Server A: [HBM] → [DDR5] → [CXL-Switch Pool: A+B+C共享] → [SSD Tier]
Server B: [HBM] → [DDR5] → [同一池] ← 可以借 Server A 的闲置 DDR5!
Linux kernel 6.10+ 已经开始实验性地支持 CXL Pool 模式。在 drivers/cxl/core/region.c 中新增了 cxl_region_reclaim() 函数,负责在主机间回收和重新分配内存。
当这一架构成熟后,AI 集群的内存利用率有望从当前的 30-40% 提升到 70-80%,同时通过内核的 tiering 子系统保持接近全本地内存的延迟表现。
九、总结
CXL Memory Tiering 的核心价值在于用软件智能弥补硬件分层带来的性能差异。通过 Linux 内核的 HMem 子系统:
- 应用无需修改代码,已有的 NUMA 感知代码可直接受益
- 自动化的页迁移适应动态变化的访问模式
- 细粒度的 sysctl/cgroup 接口满足生产环境调优需求
对于 AI 推理集群,KV Cache 和权重矩阵的访问模式相对可预测——最近生成的 token 对应的 KV Cache 最热,最先生成的最冷——这正好契合 HMem 的热度扫描机制。合理利用 CXL 扩展内存,可以将内存成本降低 40-60%,同时将性能损失控制在 10% 以内。
未来随着 CXL 3.1 内存池化的普及和 Linux 内核 tiering 子系统的进一步完善,分层内存管理将成为 AI 集群的标准实践。

发表评论 取消回复