上一篇 下一篇 分享链接 返回 返回顶部

如何在香港服务器的 Ubuntu 环境中结合 AMD EPYC 7H12 与 NVMe,实现高性能 AI 推理任务?

发布人:Minchunlin 发布时间:2025-08-28 10:37 阅读量:1104


凌晨 2:40,我站在香港葵涌机房过道里,看着指示灯一排排亮着。值班工程师把最后一根 U.2 线插稳,我敲下电源按钮,耳边只剩高转速风扇的呼啸。这台专门为推理优化的机器,要在天亮前接入线上流量。——这是我把 EPYC 7H12 + NVMe 在 Ubuntu 上跑通、并榨干性能的全过程。

1. 目标与思路

目标: 在香港机房,用单路 AMD EPYC 7H12(64C/128T)+ 多块企业级 NVMe,部署 Triton/ONNX/TensorRT 推理服务,在高吞吐同时把 P99 延迟压到业务可接受范围。
思路: 先从硬件链路与 BIOS/NUMA 打底,随后进行 NVMe 阵列与文件系统优化,再做 内核与网络调参,最后推理框架/进程亲和/IO 模型一条龙打通。中途遇到的坑,逐一记录。

2. 硬件与环境清单(真实部署参数)

2.1 机型与关键部件

模块 型号/规格 说明
CPU AMD EPYC 7H12,64C/128T,基频 ~2.6GHz,Boost ~3.3GHz,TDP 280W 单路(1P)配置,PCIe 4.0 x128
内存 8×32GB DDR4-3200 ECC RDIMM(共 256GB) 全 8 通道填满,保证带宽
系统盘 1× SATA SSD 480GB 仅装系统与日志
数据盘 4× NVMe U.2 企业盘(示例:Samsung PM1733 3.84TB PCIe 4.0) 模型仓库与特征库
网卡 2× 25GbE SFP28(直连 ToR),bond0 LACP 香港出口,低抖动,保证回源
机箱背板 支持 x16→4×x4 bifurcation 直连 CPU PCIe,避开瓶颈
电源/散热 1+1 冗余 PSU,前后风道,风扇高转速 7H12 发热大,留足裕量

说明:7H12 是为 HPC/吞吐而生的“猛将”,强并发 + 高内存带宽特性非常适合服务多实例推理。

2.2 软件栈

层级 版本/说明
OS Ubuntu Server 22.04 LTS(5.15+ 内核)
驱动 nvme-cli、irqbalance、numactl
运行时 Docker 24.x / containerd
推理 NVIDIA Triton Inference Server(GPU 场景)或纯 CPU 的 ONNX Runtime
监控 node_exporter、dcgm-exporter(如有 GPU)、Prometheus + Grafana
压测 wrk、hey、locust、fio(磁盘)

注:本文同时兼顾CPU 推理与GPU 推理(若你插了 A100/L40S 等),但重点在IO 与并发,不强依赖 GPU 也能落地。

3. 机房开荒:BIOS 与 NUMA 的第一刀

3.1 BIOS 建议项(落地经验)

选项 建议 理由
SMT(超线程) 开启 推理服务线程多,吞吐更高;个别低延迟极端场景可 A/B
NPS(NUMA Per Socket) NPS1 单路 7H12 选 NPS1 简化 NUMA,减少跨节点访问延迟
Global C-state 关闭 避免深度节能引入的抖动
PBO/功耗墙 适度放开 长时间高并发下减少降频
PCIe 固定 Gen4 保证 NVMe 带宽
Memory Interleaving Auto/Channel interleave 更稳定的带宽利用

3.2 NUMA/中断亲和

# 查看 NUMA/CPU 拓扑
lscpu -e
numactl --hardware

# 绑定 NVMe 中断到本地一组 CPU(示例绑到 0-15 号核)
grep -E "nvme0|nvme1|nvme2|nvme3" /proc/interrupts
echo ffff > /proc/irq/XX/smp_affinity  # 依据上面的 IRQ 号设置

实战里我用 irqbalance 做基础分配,再对关键 NVMe 队列手动绑核。绑核后,P99 IO 延迟下降 8%~15%。

4. Ubuntu 落地:内核与系统参数

4.1 基础 sysctl

cat >/etc/sysctl.d/99-infer-tuning.conf <<'EOF'
vm.swappiness=1
vm.dirty_background_ratio=5
vm.dirty_ratio=20
vm.max_map_count=262144
fs.file-max=2097152

net.core.somaxconn=1024
net.core.netdev_max_backlog=250000
net.ipv4.tcp_congestion_control=bbr
net.ipv4.tcp_tw_reuse=1
net.ipv4.tcp_fin_timeout=15
EOF

sysctl --system

4.2 NVMe I/O 策略

NVMe 默认调度器应为 none(多队列直通),确认并固定:

lsblk -d -o NAME,ROTA,SCHED | grep nvme
# 如非 none,可用 udev 固定
cat >/etc/udev/rules.d/60-nvme-sched.rules <<'EOF'
ACTION=="add|change", KERNEL=="nvme*n1", ATTR{queue/scheduler}="none", ATTR{queue/read_ahead_kb}="2048"
EOF
udevadm control --reload && udevadm trigger

把 read_ahead_kb 提到 2048(2MB)对顺序读取的模型权重加载很友好。

5. NVMe 阵列与文件系统

5.1 RAID 方案选择

RAID0(mdadm):最大吞吐,零冗余;我用于缓存/可再生成的模型仓库,配对象存储同步。

RAID10(mdadm):性能 + 容错折中;适合特征向量库、热点索引。

ZFS:快照/校验好用,但 CPU/内存开销更高;吞吐型场景谨慎权衡。

我线上采用:模型仓库 RAID0(4×NVMe) + 索引库 RAID10(2×NVMe)。本文以 4 盘 RAID0 演示。

5.2 建阵 & 格式化(XFS)

apt-get update && apt-get install -y mdadm xfsprogs nvme-cli

# 识别盘
nvme list

# 创建 RAID0
mdadm --create /dev/md0 --level=0 --raid-devices=4 /dev/nvme0n1 /dev/nvme1n1 /dev/nvme2n1 /dev/nvme3n1
mdadm --detail --scan >> /etc/mdadm/mdadm.conf
update-initramfs -u

# XFS 格式化 & 挂载
mkfs.xfs -f /dev/md0
mkdir -p /data/models
echo '/dev/md0 /data/models xfs noatime,nodiratime,attr2,inode64,logbufs=8,logbsize=256k 0 0' >> /etc/fstab
mount -a

经验:XFS 在高并发读取(多目录/小到中等文件)非常稳,结合 noatime/inode64 的延迟表现优于我在同机型上测试的 ext4。

5.3 fio 验证(示例数据,设备不同结果会不同)

# 顺序读(模拟模型加载)
fio --name=seqread --filename=/data/models/fio.test --rw=read --bs=1M --iodepth=32 --numjobs=8 --size=64G --direct=1

# 随机读(模拟 embedding/检索)
fio --name=randread --filename=/data/models/fio.test --rw=randread --bs=4k --iodepth=128 --numjobs=16 --size=64G --direct=1

对比表(优化前后)

指标 优化前(单盘,ext4) 优化后(RAID0×4,XFS)
顺序读吞吐(1M bs) ~2.9 GB/s ~11.6 GB/s
随机读 IOPS(4k) ~520k ~1.9M
P99 读延迟(4k) ~1.2 ms ~0.48 ms

现场变化:模型首次加载时间从 ~18s 降到 ~6s,容器冷启动整体缩短约 66%。

6. 容器化推理:Triton / ONNX Runtime

6.1 模型仓库结构

/data/models/
  ├── resnet50/
  │   └── 1/model.onnx
  ├── llm-int8/
  │   └── 1/model.plan         # TensorRT 引擎
  └── bert/
      └── 1/model.onnx

6.2 Docker Compose(Triton 示例)

version: "3.8"
services:
  triton:
    image: nvcr.io/nvidia/tritonserver:24.05-py3
    command: >
      tritonserver --model-repository=/models
                   --exit-on-error=false
                   --http-port=8000
                   --grpc-port=8001
                   --metrics-port=8002
                   --pinned-memory-pool-byte-size=268435456
                   --response-cache-byte-size=134217728
    runtime: nvidia
    environment:
      - NVIDIA_VISIBLE_DEVICES=all
      - CUDA_MPS_ACTIVE_THREAD_PERCENTAGE=60
    ulimits:
      nofile: 1048576
    volumes:
      - /data/models:/models:ro
    network_mode: "host"

两个关键点:

  • 模型目录只读(ro)保证一致性;
  • 合理设置 pinned memory & response cache,减少反复拼装输出的开销。

6.3 ONNX Runtime(CPU 推理)示例

import onnxruntime as ort
import numpy as np

so = ort.SessionOptions()
so.intra_op_num_threads = 16
so.inter_op_num_threads = 2
so.graph_optimization_level = ort.GraphOptimizationLevel.ORT_ENABLE_ALL

sess = ort.InferenceSession(
    "/data/models/resnet50/1/model.onnx",
    providers=["CPUExecutionProvider"],
    sess_options=so
)

x = np.random.rand(1,3,224,224).astype(np.float32)
for _ in range(1000):
    sess.run(None, {"input": x})

线程策略:EPYC 7H12 上,单实例 intra=16, inter=2 常见是个不错起点;再横向开多进程(每进程绑一组核),总体吞吐更高。

7. 进程/线程亲和与 IO 配合

7.1 多实例 + 绑核(示例用 taskset/numactl)

# 启两个推理实例,各占 0-31、32-63 两组核
taskset -c 0-31 docker run ... tritonserver ...
taskset -c 32-63 docker run ... tritonserver ...

配合把 nvme0/1 的中断绑到 0-31,nvme2/3 绑到 32-63,减少跨核抖动。

7.2 io_uring(可选)

若业务使用自研高并发文件读取,可启用 liburing 直通。Nginx/Triton 自身不暴露这层也没关系,模型预热+足够大页缓存已能覆盖大部分场景。

8. 网络与内核态队列

BBR:跨境回源/回调延迟稳定(已在 sysctl 配置)。

RPS/XPS:对 25GbE 场景可按队列绑核,避免软中断拥挤。

SO_REUSEPORT:多进程监听,配合 reuseport 做负载均衡(Nginx/Envoy/自研网关)。

9. 冷启动与预热策略

模型文件分层:常用模型放在 RAID0,冷门模型走对象存储 S3 网关,首次载入后落地到本地缓存。

系统级预读:

vmtouch -vt /models/llm-int8/1/model.plan
# 或用 simple-cat 读一遍,让页缓存“热起来”

分批 Warmup:容器启动后,先做 30~60 秒低 QPS 的“引导流量”,让编译 / JIT / page fault 都在前期完成。

10. 观测与基准

10.1 关键监控项

  • 磁盘:node_disk_read_time_seconds_total、avgqu-sz、iostat -x 1 的 await/svctm
  • CPU:irq、softirq、steal(虚拟化场景)
  • 内存:page cache 命中、oom_kill
  • 网络:tcp_retrans_segs、队列丢包
  • 应用:P50/P95/P99、实例级 QPS、错误分布

10.2 压测结果(真实区间,示例)

场景 优化前 优化后
CPU ResNet50(batch=1)吞吐 ~2.8k RPS ~4.5k RPS
同场景 P99 ~42 ms ~27 ms
GPU TensorRT(batch=8)吞吐 ~8.2k RPS ~10.1k RPS
模型首次加载 ~18 s ~6 s

主要收益来自:NVMe 聚合吞吐 + 绑核 + 预热。GPU 场景里,IO 不再是明显短板。

11. 线上坑与机房解法

NVMe 温度过高:凌晨高峰读写上来后,U.2 盘温度一度逼近 70℃。

解:提风扇 PWM,前面板加风罩,盘位改“交错”插法,温度回落到 55℃ 左右。

中断飘移导致抖动:irqbalance 把关键 IRQ 分散到“大核 + 小核”混合。

解:对 nvme*/mlx5* 的 IRQ 显式 smp_affinity,并屏蔽几个用于系统守护的核。

RAID 重建影响延迟:一次异常重启后 md0 进入检查/重建,延迟大涨。

解:高峰期禁止自动重建;安排维护窗;线上用 RAID10 的索引库不受影响。

页缓存“顶满”:大模型 warmup 后,其他服务被挤压。

解:给推理进程所在 cgroup 设置 memory.high 与 memory.max;给日志/备份进程限速与 IO class。

Docker overlayfs 与 XFS:曾遇到 overlay 白名单属性问题导致镜像层报错。

解:XFS ftype=1(Ubuntu 默认 OK),并确保内核 ≥ 5.10。

12. 灾备与日常运维

对象存储双活:模型产线打包上传 COS/S3,同时下发机房;RAID0 只是副本。

定期 fstrim:企业 NVMe 支持良好,周任务:

systemctl enable fstrim.timer && systemctl start fstrim.timer

固件巡检:nvme fw-log,跨批次设备统一版本。

容量告警:模型多版本并存很快吃满盘,Prometheus + Webhook 到当班手机。

SLA 回放:异常窗口保留 iostat -x 1、sar、dmesg 抓取,便于复盘。

13. 一键初始化脚本(精简版,可根据你环境改)

#!/usr/bin/env bash
set -euo pipefail

apt-get update
apt-get install -y mdadm xfsprogs nvme-cli numactl irqbalance docker.io containerd fio jq

cat >/etc/sysctl.d/99-infer-tuning.conf <<'EOF'
vm.swappiness=1
vm.dirty_background_ratio=5
vm.dirty_ratio=20
vm.max_map_count=262144
fs.file-max=2097152
net.core.somaxconn=1024
net.core.netdev_max_backlog=250000
net.ipv4.tcp_congestion_control=bbr
net.ipv4.tcp_tw_reuse=1
net.ipv4.tcp_fin_timeout=15
EOF
sysctl --system

cat >/etc/udev/rules.d/60-nvme-sched.rules <<'EOF'
ACTION=="add|change", KERNEL=="nvme*n1", ATTR{queue/scheduler}="none", ATTR{queue/read_ahead_kb}="2048"
EOF
udevadm control --reload && udevadm trigger

# RAID0 示例(按需替换设备名)
mdadm --create /dev/md0 --level=0 --raid-devices=4 /dev/nvme0n1 /dev/nvme1n1 /dev/nvme2n1 /dev/nvme3n1
mdadm --detail --scan >> /etc/mdadm/mdadm.conf
update-initramfs -u
mkfs.xfs -f /dev/md0
mkdir -p /data/models
echo '/dev/md0 /data/models xfs noatime,nodiratime,attr2,inode64,logbufs=8,logbsize=256k 0 0' >> /etc/fstab
mount -a

systemctl enable --now irqbalance
systemctl enable --now fstrim.timer

echo "DONE. 请继续部署 Triton/ONNX 与你的模型仓库。"

14. FAQ:你可能也会问

单路 vs 双路? 推理更看重单核延迟一致性和 IO,单路 7H12 + 直连 NVMe 更易控。如果要塞更多 GPU,才考虑双路与更多 PCIe 通道。

XFS vs ext4 vs ZFS? 纯吞吐+并发读,XFS 实测更稳;要强校验/快照选 ZFS;ext4 更通用但在大目录并发读略逊。

RAID0 会不会怕? 线上必须有上游对象存储或镜像分发,RAID0 只做落地缓存;核心数据请放 RAID10/分布式存储。

15. 收尾:凌晨 5:10 的机房走廊

流量切过去后,Grafana 的 P99 曲线像被手按住一样稳,文件系统的读吞吐在 10GB/s 上下“呼吸”。我把工单状态改成 Resolved,从机房走出来,天边开始泛白。
这套 7H12 + NVMe 的组合,不是最“花哨”的堆料,却是我在香港线下环境反复打磨后,能被日常运维、能顶住凌晨高峰的一套方法论:

  • 先从硬件与 BIOS 把底座夯实;
  • 再用阵列与文件系统把吞吐提起来;
  • 配合内核/绑核/预热让延迟稳下来;
  • 最后用监控与流程把不可预期关在笼子里。

如果你也站在冷风呼呼的机房里,我希望这篇实操能让你少走几步弯路,多睡一会回笼觉。

附:快速自查清单(带勾就能上线)

  •  BIOS:NPS1/禁 C-State/PCIe Gen4
  •  Ubuntu 22.04 + 5.15+ 内核
  •  NVMe 调度器 none + read_ahead_kb=2048
  •  RAID0(模型)/RAID10(索引)+ XFS
  •  irqbalance + 关键 IRQ 手动亲和
  •  sysctl:BBR/dirty_ratio/文件句柄数
  •  容器:模型目录只读、pinned memory 配置
  •  预热:vmtouch/引导流量
  •  观测:P99、IO await、缓存命中
  •  灾备:对象存储副本、fstrim 定时、固件巡检
目录结构
全文