一台 128 核的服务器,跑大模型推理的吞吐量却不如 32 核机器——这种情况在实际工程中并不罕见。根本原因往往不是核数不够,而是线程之间的"沟通成本"太高,以及内存访问路径不对。

本篇聚焦两个关键优化方向:线程亲和性(Thread Affinity)NUMA 感知(NUMA-Aware),解释它们的原理,并提供可直接使用的配置方案。


1 多核 CPU 的内存访问模型

1.1 UMA vs NUMA

现代高核数服务器处理器几乎都是 NUMA 架构:

NUMA 节点 0                    NUMA 节点 1
┌─────────────────────┐        ┌─────────────────────┐
│  CPU Core 0-31      │        │  CPU Core 32-63      │
│  L3 Cache (共享)    │        │  L3 Cache (共享)     │
│  内存控制器          │◄──────►│  内存控制器           │
│  本地 DRAM          │        │  本地 DRAM           │
└─────────────────────┘        └─────────────────────┘
  • 本地内存访问:Core 0 访问 NUMA 节点 0 的内存 → 低延迟
  • 远端内存访问:Core 0 访问 NUMA 节点 1 的内存 → 需跨 NUMA 互联,延迟高约 30-50%

1.2 大模型推理的内存访问特征

以 7B 模型(FP16,约 14GB)为例:

  • 模型权重在内存中占据连续大块空间
  • 每次推理都需要将权重从内存加载到 CPU 缓存
  • 若线程分散在多个 NUMA 节点,权重可能被分散在不同 NUMA 节点的内存上,产生大量远端内存访问

2 查看系统 NUMA 拓扑

在优化之前,先了解机器的 NUMA 布局:

# 查看 NUMA 节点数量和每个节点的 CPU 分布
numactl --hardware

# 示例输出:
# available: 2 nodes (0-1)
# node 0 cpus: 0 1 2 3 ... 31
# node 0 size: 128000 MB
# node 1 cpus: 32 33 34 ... 63
# node 1 size: 128000 MB
# node distances:
# node   0   1
#   0:  10  20
#   1:  20  10

# 查看详细 CPU 拓扑(核/线程/NUMA)
lscpu --extended

# 查看当前进程的 NUMA 绑定情况
numastat -p $(pgrep python)

3 NUMA 感知推理部署

3.1 numactl 绑定

最简单的方式:用 numactl 将推理进程绑定到单个 NUMA 节点:

# 将推理进程绑定到 NUMA 节点 0,内存也从节点 0 分配
numactl --cpunodebind=0 --membind=0 python inference_server.py

# 多实例部署:节点 0 和节点 1 各跑一个实例
numactl --cpunodebind=0 --membind=0 python inference_server.py --port 8080 &
numactl --cpunodebind=1 --membind=1 python inference_server.py --port 8081 &

3.2 Python 中的 NUMA 感知内存分配

import os
import ctypes

def set_numa_policy(node: int):
    """将当前进程的内存分配策略设置为绑定到指定 NUMA 节点"""
    # MPOL_BIND = 2,表示严格绑定到指定节点
    libnuma = ctypes.CDLL("libnuma.so.1")
    nodemask = ctypes.c_ulong(1 << node)    # 绑定到 node 节点
    libnuma.set_mempolicy(2, ctypes.byref(nodemask), 64)

# 在模型加载前调用
set_numa_policy(node=0)

import torch
model = torch.load("model.pt")    # 权重将分配在 NUMA 节点 0 的内存上

4 线程亲和性优化

4.1 什么是线程亲和性

线程亲和性(Thread Affinity / CPU Pinning)是指将线程固定到特定的 CPU 核心上运行,避免操作系统调度器在核心间迁移线程。

为什么重要:线程迁移会导致该线程的 L1/L2 缓存数据失效,迁移后需要重新预热缓存,这对推理性能影响可达 10-20%。

4.2 OpenMP 线程亲和性配置

llama.cpp、PyTorch 等框架使用 OpenMP 进行多线程并行,通过环境变量控制线程亲和性:

# OMP_PROC_BIND: 线程绑定策略
# close  - 线程尽量靠近主线程(适合数据局部性优化)
# spread - 线程均匀分散(适合独立并行任务)
# master - 所有线程绑定到主线程所在核心

export OMP_PROC_BIND=close
export OMP_PLACES=cores          # 以物理核为单位(不包含超线程)
export OMP_NUM_THREADS=32        # 线程数 = NUMA 节点 0 的物理核数

# 验证绑定效果
OMP_DISPLAY_ENV=TRUE python -c "import torch; print(torch.get_num_threads())"

4.3 taskset 手动绑定

# 将 Python 进程绑定到 CPU 0-31(NUMA 节点 0 的物理核)
taskset -c 0-31 python inference_server.py

# 查看进程的 CPU 亲和性掩码
taskset -p $(pgrep python)

# 组合 NUMA 绑定和 CPU 亲和性绑定
numactl --membind=0 taskset -c 0-31 python inference_server.py

4.4 Python 代码中设置亲和性

import os
import psutil

def pin_to_cores(core_list: list[int]):
    """将当前进程绑定到指定 CPU 核心"""
    p = psutil.Process(os.getpid())
    p.cpu_affinity(core_list)
    print(f"Process {os.getpid()} pinned to cores: {p.cpu_affinity()}")

# 推理进程绑定到 NUMA 节点 0 的核心(0-31)
pin_to_cores(list(range(32)))

5 超线程(HyperThreading)对推理的影响

超线程允许一个物理核运行两个逻辑线程,但二者共享计算单元和缓存。

在大模型推理中,超线程的影响存在争议:

场景 超线程收益
纯矩阵乘法(计算密集) 通常无益,甚至降低性能(竞争 FPU 单元)
IO 密集型预处理 有一定收益
混合批处理(多请求并行) 略有帮助

建议:对推理服务进行 A/B 测试,分别在 OMP_NUM_THREADS=物理核数OMP_NUM_THREADS=逻辑核数 下测试,取性能更好的配置。

# 查看物理核数 vs 逻辑核数
lscpu | grep -E "^CPU\(s\)|Thread\(s\) per core|Core\(s\) per socket"
# CPU(s): 128
# Thread(s) per core: 2    <- 超线程开启,逻辑核=物理核×2
# Core(s) per socket: 32

6 llama.cpp 的多核配置示例

llama.cpp 是目前 CPU 推理最高效的开源实现,其线程配置:

# -t: 线程数(建议等于物理核数,而非逻辑核数)
# -ngl: 将多少层卸载到 GPU(纯 CPU 推理设为 0)

# NUMA 节点 0,32 物理核,INT4 量化模型
numactl --cpunodebind=0 --membind=0 \
    ./llama-cli \
    -m ./models/llama-3-8b.Q4_K_M.gguf \
    -t 32 \
    -ngl 0 \
    -p "你好,请介绍一下自己"

7 总结

多核 CPU 推理优化的核心是减少跨 NUMA 访问保持缓存热数据

  1. 单 NUMA 节点部署:模型权重与计算线程在同一 NUMA 节点,消除远端内存延迟
  2. 线程亲和性:用 OMP_PROC_BIND=closetaskset 固定线程,保持缓存热度
  3. 线程数 = 物理核数:超线程对矩阵运算通常无益,不要盲目开满逻辑核数
  4. 多实例 > 单实例:高并发场景下,每个 NUMA 节点跑一个独立实例,比单实例多线程更高效
Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐