GLM5.3 CyberSec w4A16本地部署[6]:加载链路与磁盘规划

06 · 部署加载链路与磁盘规划

  • 📦 模型权重地址
  • 📦 部署仓库地址
    本篇讨论权重从 NFS 进入显存这条链路的实测账,以及由它推出的磁盘规划、内存护栏与 I/O 纪律。核心结论:加载耗时由 NFS 带宽×页缓存命中率 两个变量决定,带宽下限约 375 s,页缓存可以从 427 s 拉到 1802 s(4.2×)。

文章目录

  • [06 · 部署加载链路与磁盘规划](#06 · 部署加载链路与磁盘规划)
    • [0. 速览](#0. 速览)
    • [1. 部署加载链路](#1. 部署加载链路)
      • [1.1 启动四阶段](#1.1 启动四阶段)
      • [1.2 耗时的两个决定变量](#1.2 耗时的两个决定变量)
      • [1.3 CPU 侧反序列化不是瓶颈](#1.3 CPU 侧反序列化不是瓶颈)
    • [2. 页缓存:决定 7 分钟还是 30 分钟](#2. 页缓存:决定 7 分钟还是 30 分钟)
      • [2.1 热与冷是两种完全不同的负载状态](#2.1 热与冷是两种完全不同的负载状态)
      • [2.2 预读机制](#2.2 预读机制)
      • [2.3 为什么不做"串行预读再启动"](#2.3 为什么不做"串行预读再启动")
    • [3. 磁盘规划](#3. 磁盘规划)
      • [3.1 原则](#3.1 原则)
      • [3.2 磁盘速度测试](#3.2 磁盘速度测试)
      • [3.3 部署期磁盘布局](#3.3 部署期磁盘布局)
    • [4. 内存护栏](#4. 内存护栏)
      • [4.1 容器 `/dev/shm`](#4.1 容器 /dev/shm)
      • [4.2 cgroup 内存护栏:两个设置配套](#4.2 cgroup 内存护栏:两个设置配套)
      • [4.3 OOM 善后](#4.3 OOM 善后)
    • [5. I/O 纪律与判活](#5. I/O 纪律与判活)
      • [5.1 加载期间不要抢 I/O](#5.1 加载期间不要抢 I/O)
      • [5.2 判活三件套](#5.2 判活三件套)
      • [5.3 `/proc/<pid>/io` 的读法](#5.3 /proc/<pid>/io 的读法)
    • [6. 踩坑与证否](#6. 踩坑与证否)
    • [7. 复现命令](#7. 复现命令)

0. 速览

  • 权重 390.6 GiB / 25 分片,全部在 NFS 上。实测单流 0.72 GB/s,带宽下限 ≈ 375 s。
  • 页缓存命中率是实际耗时的决定性因素:热(99.8%)→ 427 s ;冷(≈17%)→ 1802 s。差异 4.2×。
  • 每次启动把检查点读两遍(第二遍是 MTP 草稿头,10~38 s),但启动耗时 100% 由第一遍决定。
  • 磁盘规划原则:权重只读挂载进容器;I/O 路径的判断先测再定,不要带"网络盘一定慢"之类的先验。
  • 内存护栏必须 MemoryMax 与 memory.oom.group=1 配套设置,缺一不可。
  • 判活必须看三件套(日志 + CPU 时间 + /proc/<pid>/io)同时停滞 ≥ 20 min,单看任何一项都会误判。

1. 部署加载链路

1.1 启动四阶段

一次冷启动(页缓存 99.8% 热)的耗时分解如下:

阶段 实测耗时 备注
docker run → 8 个 worker 就绪 90~150 s 含 Triton 后端选择,基本不可压缩
vLLM 预读检查点进页缓存 28 s(热)/ ~400 s(冷) 内建机制,无需额外干预
Model loading took 427 s (热)/ 1802 s(冷) 主要可优化项
torch.compile(AOT 缓存命中) 6 s(命中)/ 115 s(失效) 改配置会使缓存失效
KV pool + CUDA Graph + profile 105~254 s 图不可省;profile 有 ±2 GiB 抖动

其中 Model loading took 内部又包含一次重复读取:MTP 草稿头(model.layers.78.*)走同一条 NFS 路径再读一遍,但因第一遍已把 390 GB 拉进页缓存,只花 10~38 s,可忽略。

1.2 耗时的两个决定变量

把历史 47 次 Model loading took 排一遍,决定它的只有两件事:

  1. 页缓存里还剩多少权重。
  2. NFS 当时有多忙(共享存储抖动)。
场景 耗时 说明
页缓存全热 + --enable-expert-parallel 92 s 不可作为 EP 收益,页缓存状态与冷读不可比
页缓存温(~60%) 314~364 s 多数日常重启落在此区间
页缓存热(99.8%),无 EP 372~427 s 必须全部走 NFS 读 + CPU 侧反序列化的下限
共享存储抖动 / 冷 711~920 s 外部因素
页缓存仅 ~17% 1802 s 与同场景热 427 s 构成 4.2× 的直接对照

结论:375~430 s 是"必须全部从 NFS 读 + CPU 侧反序列化"的物理下限;92 s 是页缓存全热时的上限附近。两者之间的差值全部来自页缓存命中率。

1.3 CPU 侧反序列化不是瓶颈

  • 权重从 NFS 读入后,vLLM 加载器在 CPU 侧做 safetensors 反序列化、W4A16 权重的解包 / 反量化(dequantize)与 tensor 构造。实测这一步 25 个分片合计约 1~2 min,远低于 NFS 读取时间,不是加载瓶颈。

2. 页缓存:决定 7 分钟还是 30 分钟

2.1 热与冷是两种完全不同的负载状态

状态 CPU 利用率 磁盘读 有效速率
热(上次运行读过) 高(瓶颈转移到 CPU) 少量回源 快
冷(驱逐后) 极低(几乎空转等 I/O) 全部走 NFS 0.4~3.5 GB/min

冷缓存下 CPU 几乎不工作,全部时间在等 NFS 返回数据。因此:

  • 任何两次启动的性能对比,必须先统一页缓存状态,否则数据不可比。
  • 正式压测或性能基准从冷缓存开始,热缓存数据只用于验证"最好情况"。

2.2 预读机制

vLLM 自身已内建预读(weight_utils.py,日志行 Prefetching checkpoint files into page cache finished in X s):网络盘上走 safetensors_load_strategy="prefetch",8 线程 × 16 MiB 块,分片按 rank 轮分。

本套件在此基础上加了一步更早开始 :scripts/prefetch_weights.py 在 docker run 之前发非阻塞的 posix_fadvise(WILLNEED),利用 worker 启动前约 90~150 s 的空窗预填页缓存。

配套的 check 子命令用 mincore 精确统计页缓存命中率(390 GiB 约 24 s,不占 GPU),该数字能直接预测本次启动耗时 。缺省 WARM_PAGECACHE=1:命中率 ≥ 0.60 就跳过预读,省 NFS 带宽。

外部预读的增量收益未独立实测(两次真跑页缓存都已是 99.8%,预读正确跳过)。要测"冷 + 预读"那一格需先制造冷缓存(需要 root)。

2.3 为什么不做"串行预读再启动"

  • 预读和加载读的是同一份 419 GB。如果先显式预读完再启动,启动时页缓存已经热,确实快;但总时间 = 预读时间 + 启动时间 ,等于把同一次 NFS 传输拆成了两段串行,总时间不变还多了一次系统调用开销。正确做法是让预读与 worker 启动并行(空窗期预读),而不是串行。

3. 磁盘规划

3.1 原则

  1. 权重目录只读挂载进容器 (-v $MODEL_HOST_DIR:/models:ro),防止运行时意外写入。
  2. I/O 路径的判断先测再定。在这台机器上共享盘顺序读写明显快于某些本地盘。
  3. 元数据要一起测。共享文件系统上文件数往往比字节数更关键。创建 / stat / 删除是元数据操作,吞吐远低于大块读写。

3.2 磁盘速度测试

bash 复制代码
D=<目标目录>
# 顺序写(direct,避开页缓存)
dd if=/dev/zero of=$D/ddtest bs=8M count=1024 oflag=direct
# 顺序读(同一文件 + direct)
dd if=$D/ddtest of=/dev/null bs=8M iflag=direct
# 4K 随机读(评估小 I/O 延迟,影响同机其他人)
fio --name=rand4k --ioengine=libaio --direct=1 --bs=4k \
    --rw=randread --filename=$D/ddtest --numjobs=1 --runtime=15

3.3 部署期磁盘布局

用途 位置 原因
权重(只读) NFS / 共享存储 419 GB / 25 分片,多机复用同一份
工程 / 脚本 / 日志 本地盘 小文件,随代码走
/dev/shm(容器内) tmpfs NCCL 通信 + 权重暂存,按需分配
  • 部署磁盘规划的核心只有一条:确保权重目录的 NFS 链路带宽稳定,且加载期间不被其他大 I/O 抢占。

4. 内存护栏

4.1 容器 /dev/shm

  • Docker 默认 /dev/shm 只有 64 MB(或 1 GB),不足以承载 8 卡 NCCL 通信与权重暂存。启动时显式指定:
bash 复制代码
docker run --shm-size=8g ...

tmpfs 按需分配,--shm-size=8g 是上限而非预占,空时仅占 16 KB。

4.2 cgroup 内存护栏:两个设置配套

  • 防止容器内存涨满触发全局 OOM(波及同机其他用户),需要两条配置同时生效:
设置 作用 只设另一条的后果
MemoryMax=<N> 硬墙,进程内存不超过 N 默认 oom.group=0,OOM 只杀 1 个进程,其余 7 个 rank 卡在 NCCL 上(PG 超时 12 h)→ 白等
memory.oom.group=1 触墙时整个 cgroup 一起杀 没有墙,照样全局 OOM

systemd 属性写法注意:

  • MemoryOOMGroup=yes 在 systemd < 253 上不存在,会整条命令回滚 。正确做法:先 systemctl set-property <unit> MemoryMax=...,再 echo 1 > /sys/fs/cgroup/<unit>/memory.oom.group。
  • 任何 set-property 之后必须重设 memory.oom.group 并复核 io.max(set-property 是一次全量资源属性重下发,会覆盖手工热改值)。

取值依据 :留足系统 + 同机其他人的余量。memory.current 贴在 MemoryMax 附近是正常现象(内核拿可回收页缓存填满 cgroup),真正决定生死的是不可回收部分(anon + shmem + pagetables)。监视脚本应看距墙余量而非 shm 绝对值。

4.3 OOM 善后

进程被杀后,tmpfs 上的文件不会自动释放。必须确认无进程持有后手动清理,否则残留数据持续占用内存,影响后续启动。

bash 复制代码
find /dev/shm -maxdepth 1 -name 'torch_*' -print0 | xargs -0 -r rm -f

5. I/O 纪律与判活

5.1 加载期间不要抢 I/O

  • 不要在权重加载时执行大批量文件删除(会与 NFS 读抢带宽,两边都变慢)。
  • 不要在加载时做全目录扫描或大量元数据操作。
  • 与别人共用一块盘时,加载期的突发 I/O 会变成别人的延迟尖峰。阈值按 §3.2 在你自己的设备上实测后定。

5.2 判活三件套

NFS 慢看起来跟死了一样,D 态 ≠ 卡死。判据:

日志大小 + CPU 时间 + /proc/<pid>/io 三者同时停滞 ≥ 20 min,才判定挂起。

只看日志或只看 CPU 都会误判。误判的代价是重启------丢掉已跑的时间。最可靠的辅助信号是栈位置变化(py-spy dump)。

5.3 /proc/<pid>/io 的读法

  • read_bytes:可用于证明谁在实际读数据(部署期 8 rank 应各读自己那份)。
  • write_bytes:不计页缓存回写 。进程写进 page cache 就返回,回写由内核完成、不计在进程头上。它可以是 0 而数据确实在落盘,不能据此判定"没在写"。
  • df 用量增长:共享盘上不能直接归因给自己,别人的任务也在写。

6. 踩坑与证否

  1. "加载慢是 CPU 的问题"------部署期不成立。 实测 CPU 侧反序列化仅占 1~2 min,占总耗时的不到 5%。真因是 NFS 带宽 × 页缓存命中率。教训:先隔离测量再归因,不要凭直觉。

  2. "网络盘一定慢,排除共享盘"------实测推翻。 这台机器上共享盘顺序读 0.72 GB/s、写与本地盘相当。教训:I/O 路径判断一律先测(§3.2),且结论是逐机器的,不能照抄。

  3. "92 s 启动说明 EP 让加载快了 4 倍"------不可比。 那次 92 s 页缓存已全热,与 372 s 的冷读页缓存状态不同,是两件事。EP 本身还有额外启动代价(JIT +55 s,编译缓存 key 变化)。教训:两个变量同时变了就不能归因。

  4. 拿不同口径的 dd 读数当"盘够用"的依据。 块大小、direct/buffered、O_DIRECT 参数不同,读数不可并列。教训:口径不一致的读数不并列,宁可标"存疑"。

  5. 用 systemd-run --user 限速,参数被接受但静默忽略。 user.slice 未委派 io 控制器,io.max 根本没写进内核。教训:只信 --selftest 的实测数字,不信任"命令没报错"。

  6. 17.6 张量/秒是页缓存加持下的数字,不是常态。 冷缓存只有 0.4~3.5 GB/min。教训:不把某次热缓存下的速率当预期值。

7. 复现命令

bash 复制代码
# ══ 页缓存检查与预读 ══
./warm_cache.sh check                              # mincore 统计命中率
./deploy.sh prewarm dry-run                        # 列出 25 个分片与总量
./deploy.sh prewarm run --if-cold                  # 冷时预读(热则自动跳过)
WARM_PAGECACHE=0 ./deploy.sh start                 # 制造"冷 + 不预读"对照

# ══ 启动与日志 ══
./deploy.sh start
grep -h "Model loading took" logs/vllm-*.log | tail -20
grep -h "Prefetching checkpoint files into page cache finished" logs/vllm-*.log | tail -5
docker logs glm53-cybersec-w4a16 2>&1 | grep -m1 "init engine"

# ══ 存储与内存 ══
findmnt -no FSTYPE,SOURCE,TARGET "$(dirname "$(readlink -f models/GLM-5.3-CYBERSECURITY-W4A16)")"
df -h /dev/shm; free -g | head -2

# ══ cgroup 护栏复核 ══
CG=/sys/fs/cgroup/system.slice/glm53-cybersec.service
cat $CG/memory.max; echo
cat $CG/memory.oom.group; echo
# OOM 后的 tmpfs 残留检查
find /dev/shm -maxdepth 1 -name 'torch_*' | wc -l
相关推荐
张张123y4 小时前
2026 Agent 框架怎么选?拆 3 层底层逻辑(运行循环 / 记忆 / 工具协议)+ 一张决策表
linux·ubuntu·microsoft·langgraph·mcp·crewai·agent 框架
longerxin20206 小时前
Ubuntu 22.04 ARM64 云镜像开启 root 密钥登录(保留 ubuntu 密钥登录,禁止密码登录)
数据库·ubuntu·postgresql
skywalk81636 小时前
FreeBSD仿真环境里的Ubuntu linux系统,安装软件的时候不支持snap怎么办?
linux·运维·ubuntu·系统·freebsd
Cry丶16 小时前
ubuntu安装vsftpd
服务器·网络·ubuntu
程序员-King.19 小时前
【ROS1】Noetic 开发工作流程与常用命令
c++·ubuntu·ros
承渊政道1 天前
Linux网络学习【传输层协议UDP】
linux·网络·网络协议·学习·ubuntu·udp
pirateeee1 天前
在ubuntu中通过 docker 搭建 yocto 开发环境
ubuntu·docker·eureka
缘友一世1 天前
GLM5.3 CyberSec w4A16本地部署[5]:卸载换显存的边界
ubuntu·vllm·glm5.3 cybersec
bjxiaxueliang2 天前
一文自建STUN:在Ubuntu上分步搭建STUN服务
linux·运维·ubuntu