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 集群时,需要关注以下核心指标:

  1. 页迁移吞吐量(pages/min):反映 demotion/promotion 的活跃程度。异常飙升可能意味着访问模式突变。
  1. 跨层访问比例(cross-tier access ratio):本地 CPU 访问 CXL 节点内存的比例。理想值 < 5%。
  1. 提升失败率(promotion fail rate):当本地 DRAM 满时,热页无法提升,直接导致性能断崖。
  1. NUMA 命中本地率(local NUMA hit rate):越高越好,表示数据亲和性。
  1. CXL 链路利用率(CXL link utilization):接近 100% 表示带宽将成为瓶颈。

推荐的告警阈值:

alerts:
  - name: HighPromotionFailed
    condition: "rate(promotion_failed_total[5m]) &gt; 1000"
    severity: critical
    description: "热页提升大量失败,本地 DRAM 可能不足"
    
  - name: CrossTierAccessSpike
    condition: "cross_tier_access_ratio &gt; 0.15"
    severity: warning
    description: "超过 15% 的内存访问跨越 tier 边界"
    
  - name: CXLBandwidthSaturation
    condition: "cxl_link_utilization &gt; 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 集群的标准实践。

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论
立即
投稿

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部