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

凌晨 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 定时、固件巡检