☰
大模型推理疯狂写入NVMe?Strix Halo的swap与内存优化指南
2026/9/30 9:46:10 网站建设 项目流程

先交代一下背景:那台Strix Halo被我装好后,第一件事就是拉最新的Qwen3 8B Flash Next量化版下来跑。头几个小时一切正常,速度比我那台老Xeon工作站快了一个数量级。结果第二天我顺手敲了一条smartctl -x /dev/nvme0n1,看到Host Write Count一夜之间多了256GiB,当场就有点绷不住了。一天256GiB,这对任何一块消费级NVMe都算得上重型磨损,更别提Strix Halo薄本里通常还是1TB甚至512GB的盘。这篇文章不聊性能跑分,就聊这个坑是怎么踩出来的,以及怎么在保留大模型推理能力的前提下,把磁盘写入拉回正常水平。

1. 事发经过:一台猛兽为何成了“硬盘杀手”

1.1 Strix Halo的诱惑与我的部署方式

先说说Strix Halo这台机器。AMD Strix Halo(也就是Ryzen AI Max系列)的定位很明确:把尽可能多的LPDDR5X内存塞给CPU和核显共用,最高可以配到128GB的统一内存,核显规模也直逼独显,理论上就是本地大模型推理的小钢炮。

我当时选它是看中了两个点:一是统一内存架构让CPU和GPU共享同一块物理内存,加载模型后不需要在显存和内存之间搬来搬去;二是内存容量够大,跑个8B参数量的模型绰绰有余。

部署方式也简单粗暴:Ubuntu 24.04,下载Qwen3 8B Flash Next的GGUF量化版(Q4_K_M),直接用llama.cpp的server模式跑起来,默认参数直接拉。刚开始用的时候,多轮对话、长文档总结、开thinking模式,全部正常,生成速度也稳,我还感慨说这机器买对了。

真正的问题是在第二天看磁盘统计时才暴露的。smartctl显示的可不是十来GB的正常损耗,而是整整256GiB一天。我当时第一反应是硬盘坏了,第二反应是有什么程序在疯狂写日志,查了一圈才发现,写盘的元凶居然就是我自己跑模型的姿势。

1.2 256GiB写入到底有多离谱

很多人可能对256GiB没有直观概念。举个例子:一块标称480GB的TLC盘,通常TBW(总写入字节数)在300到600TB之间,理论上256GiB连零头都算不上。但这是“每天256GiB”的持久化呢?一个月就是7.5TB,一年就是90TB左右。

如果是一块512GB的TLC笔记本盘,TBW一般在300TB量级,这样算下来也能撑三年左右,乍看还能接受。可问题是,QLC盘就完全不同了,同样512GB,QLC的TBW常常只有100TB到200TB,一天256GiB的话,一年半就能把健康度打到0。更要命的是,写入量并不是均匀分布的,大模型推理时往往是突发写入,瞬间IO放大和Wear Leveling的损耗还要额外打折。

所以这个数字绝对不算“无所谓”。尤其是如果用Strix Halo做主力开发机,长期挂模型服务,一年下来硬盘健康度会肉眼可见地掉。

2. 先别急着删模型,把写入量拿到实锤

2.1 让smartctl和iostat开口说话

发现问题后,我的第一反应不是盲目关程序,而是先量化写入到底发生在哪一层。这里推荐一套组合拳,全部基于Linux自带的工具,不需要额外安装商业软件。

先用smartctl看盘的宏观状态:

sudo smartctl -a /dev/nvme0n1

重点关注两个字段:Data Units Written和Percentage Used。前者会直接告诉你累计写入量有多少,后者则是厂商根据TBW计算出的寿命消耗百分比。注意,很多NVMe盘的Percentage Used在出厂后就会显示一个初始值,不一定是0,只要不是快速飙升就没大问题。

接着用iostat看实时吞吐:

iostat -x 1

重点看wkB/s(每秒写入KB数)和aqu-sz(IO请求队列长度)。如果wkB/s经常冲到几百MB,说明有东西在持续写盘。

我的实测结果是:模型服务在跑长对话时,wkB/s经常跳到200MB以上,峰值一度到500MB。这已经完全不是日志或缓存的级别了,而是系统在干某种“搬运工”的活。

2.2 顺着 /proc 和 fatrace 找到真凶

知道写入量大之后,下一步就是定位是哪个进程在写、写进了哪个文件。这里我用的方法比较老但很有效——fatrace会实时报告进程访问文件的事件:

sudo fatrace -o /tmp/fatrace.log

挂上之后跑一轮对话,再去看日志,发现大量写操作集中在两个地方:一个是swap分区,另一个是llama.cpp在临时目录创建的映射文件。

同时配合pidstat:

pidstat -d 1

看到进程的KB_VDIRTY和KB_WRITE,基本就能锁定身份。最后再用/proc确认内存压力:

cat /proc/meminfo

结果非常典型:SwapTotal和SwapFree之间差距巨大,SwapCached居高不下,说明系统确实处于“内存不够,拿SSD换”的状态。

到这里真相已经浮出水面:跑一个8B模型,物理内存和显存明明看起来很充足,真正缺的是把整个工作集一次性装下的余量,而操作系统在内存告急时果断拉起了swap垫背。

3. 为什么跑一个8B小模型会把盘写穿

3.1 模型本身的内存胃口比想象中大

先算一笔内存账。Qwen3 8B Flash Next,8B参数量,如果是FP16精度加载,光权重就要16GB内存;如果是GGUF Q4_K_M量化版,权重大概5.2GB,貌似很小。

但别高兴太早,模型推理的内存需求不只是权重。一个大头是KV Cache,也就是模型在生成每个token时需要缓存的Key和Value向量。8B模型虽然不大,但框架如果按超长上下文来预留,KV Cache可以轻松吃掉好几GB甚至十几GB。尤其是Qwen3的thinking模式,每次生成都会先输出一长串“思考过程”,这些token全部要计入上下文,KV Cache会随着对话轮数不断膨胀。

再加上我们打开的是llama.cpp server,默认可能还会预留--parallel(并发数)和多序列的KV空间。如果开成4并发,且每个序列都跑32K上下文,你在任务管理器里看到的就不是5GB,而是20GB以上的“已提交”内存。这还没算操作系统本身的页面缓存、桌面环境、浏览器、IDE这些背景占用。

Strix Halo的32GB内存版本,在极限情况下很容易被掏空。64GB版本会好很多,但128GB版如果开了超大--mlock或--mmap配置,依然有swap风险。

3.2 Linux如何在内存告急时拉swap垫背

Linux内核在物理内存不足时,会通过回收页面的方式腾出空间。回收分两类:一类是干净的文件页,直接丢弃,下次读盘;另一类是脏页和匿名页,比如当前进程在内存中修改过的数据、模型权重里的某些动态缓冲区,需要先写回磁盘或者换出到swap。

如果swap分区在NVMe上,那么“换出”动作就是一次实打实的写入。更麻烦的是,大模型推理的访问模式是反复读取和修改同一个工作集,操作系统把一部分页面换出到swap,很快又发现推理要访问这些页面,又把它换回内存,这就会形成俗称的“抖动”(thrashing)。每次换入换出都有成本,而且往往还伴随页面的合并写入放大——写入量因此成倍增长。

我的情况就是从32GB内存里,模型、浏览器还有IDE一起挤占了28GB,剩下不到4GB,系统开始频繁swap。一次长对话下来,swap分区写入能轻松破100GB,一天256GiB就是这么堆出来的。

3.3 还有一个隐形凶手:mmap与page cache回写

除了swap,还有另一个容易被忽略的写盘来源:模型权重文件的mmap加载。

llama.cpp默认支持把GGUF模型文件以mmap方式映射到内存,好处是加载快、不会一启动就吞掉大量物理内存。坏处是,当物理内存紧张时,内核会把映射页回收。如果当前页被标记为脏(比如某些框架在运行时要修改临时缓冲区),回收时就必须写回。

写回的目标依然是磁盘上的原文件或临时文件。这意味着,即使你根本没开swap,只要物理内存压力大,mmap映射的模型文件也可能产生大量回写。搭配fatrace看到的“临时目录映射文件”,基本就是对号入座了。

所以模型的“内存占用”和“磁盘写入”并不是割裂的,它们共享同一套操作系统内存回收机制。理解了这一点,后面的优化方向才真正清晰。

4. 治本方案:让模型不再把硬盘当内存

4.1 方案A:禁掉swap,让统一内存直面压力

第一个建议不是“加内存”,而是先把swap关掉,或者至少降低swappiness。

sudo sysctl vm.swappiness=10

更彻底一点:

sudo swapoff -a

关掉swap后,内存不够时系统不会再默默把数据写到SSD,而是直接触发OOM。对跑大模型来说,OOM反而比swap好排查:要么是模型加上下文确实超了内存,要么是后台进程太多,关掉几个就能解决。

代价是,如果推理过程中某个瞬间内存占用超过物理内存,进程会被直接杀死,而不是像之前那样用“疯狂写盘”的方式硬撑。这也是为什么我会先做内存预算再跑模型:预先把浏览器、IDE关到只剩必要的进程,给模型留出足够的余量。

我做这个调整后,立刻发现磁盘写入量降到了每天10GB以内。效果显著。

有同学可能会问:既然要内存够用,那我直接把swap开小一点行不行?我的经验是,开什么但设swappiness=0并不等于禁用swap,内核在某些内存压力下仍会使用swap,只是优先级降低。如果真的怕模型进程被OOM杀掉,可以只保留一个很小的swap分区(比如4GB),这样动态缓冲溢出时有个缓冲,但因为swap太小,不会演变为无休止的写入放大。

4.2 方案B:用 /dev/shm 把模型加载进内存盘

既然问题出在系统把“磁盘”当成了换页仓库,那我们把页面真正放到内存里不就行了?Linux的/dev/shm就是一个tmpfs,所有内容都只驻留在内存中,不会写回SSD。

具体操作很简单,把模型文件复制到/dev/shm,然后从那里加载:

sudo mkdir -p /dev/shm/models cp qwen8b-flash.q4_k_m.gguf /dev/shm/models/ llama-server -m /dev/shm/models/qwen8b-flash.q4_k_m.gguf --ctx-size 32768

这样模型文件在推理过程中就算发生回写,也只是写到tmpfs,内存压力上来后内容直接被丢弃,SSD完全不会参与。

但是要注意,/dev/shm默认大小通常是物理内存的一半,对于Strix Halo 32GB版本就是16GB,放一个5GB模型文件加若干临时文件够用;如果要跑8B模型同时留出大块KV Cache空间,可以手动调大:

sudo mount -o remount,size=48G /dev/shm

这里有个坑:把模型整个装进/dev/shm会显著增加内存占用,原本10GB的模型和缓存,现在多了一份tmpfs拷贝,实际占用会明显上升。所以这个方案适合内存确实宽裕的场合(比如96GB或128GB内存版本),32GB版本建议小心。

4.3 方案C:从模型参数上给系统减负

如果不想动系统层面,最简单有效的办法是让模型自己“少吃”内存。

首要参数是上下文长度。默认32K上下文对8B模型来说已经不小了,如果不需要处理超长文档,直接砍到8K或16K,KV Cache会缩减一半以上。以Qwen3 8B为例,32K上下文的KV Cache大约在8GB以上,砍到8K能省下6GB左右,这个差距非常可观。

其次是启用量化KV Cache。llama.cpp里可以用:

llama-server -m model.gguf --cache-type q8_0 --cache-type-k q8_0 --cache-type-v q8_0

将KV Cache的精度从FP16降到8-bit甚至4-bit,内存占用能压缩一大截。代价是推理精度略微下降,但在8B模型规模下,日常对话和文档摘要基本感知不到区别。

最后是关闭或限制thinking模式。Qwen3 8B Flash Next支持内置思考,思考模式的每个token都要进入KV Cache并停留多轮,token数量一多,内存和磁盘压力都上来了。如果场景偏重快速应答,可以明确关闭thinking,或者把--reasoning-token-limit设小一些。

这一套组合拳打下来,8B模型的实际工作集可以从25GB以上压到10GB左右。之后即便开着swap,交换量也大幅度下降。

4.4 方案D:换推理后端,把权重真正留到GPU显存

还有一个优化方向是换后端,利用Strix Halo的核显。说得直白一点,llama.cpp的CPU推理虽然稳,但权重文件走的是系统内存和交换机制;而Strix Halo的Radeon 8060S核显有大量统一内存可访问,配合ROCm或Vulkan后端,可以把权重和KV Cache直接放在GPU可管理的内存区域,由显存驱动分配,而不是通用内存换页。

实际操作中,llama.cpp的Vulkan后端在Strix Halo上表现很好:

llama-server -m model.gguf -ngl 999 --device Vulkan -fa on

-ngl 999表示尽可能把层放到GPU,-fa on开启Flash Attention,能显著降低KV Cache带宽压力。权重一旦放到了显存管理区,系统在内存紧张时就不会去回收这部分页面,swap写入自然大幅减少。

不过也要说明一点,Strix Halo的核显共享内存,最终物理内存还是同一块。这里的核心变化在于“谁在管理内存”:GPU驱动管理时,内存生命周期由推理框架控制,不会轻易被操作系统换出;而CPU模式时,内存在内核眼中跟普通进程没有任何区别,压力一大就会被回收。

5. 排查与避坑:换完硬件还能怎么查

5.1 常见问题速查表

我把这次排查下来遇到的高频问题整理成了表,方便大家直接对照。

现象可能原因处理方式
swap分区持续增长物理内存不足,页面换出关swap或减大型后台应用
smartctl写量一天暴增模型推理时的swap/page cache回写启用/dev/shm加载模型
模型加载后系统卡顿内存抖动,反复换入换出降低--ctx-size、限制并发
KV Cache占满内存上下文过长或并行序列过多开KV量化、调小上下文
日志目录不断膨胀推理框架开启了debug关闭debug日志或限制日志大小
模型服务被随机杀掉swap关闭后内存不足触发OOM优化模型参数或加内存

这些条目背后基本都有一个共性:不是硬件不行,而是没有把推理框架的内存策略和操作系统对齐。

5.2 突击检查SSD寿命的姿势

关闭swap并优化参数后,最重要的不是继续跑分,而是确认这一天的256GiB到底给盘带来了多大影响。这一步我用的是SMART数据。

sudo smartctl -x /dev/nvme0n1

看看Media and Data Integrity Errors,如果为0,说明没有发生介质错误;再看看Available Spare,如果还是100%,说明备用块没有被大量消耗。就看Percentage Used:如果一天的256GiB让这个值涨了1%以上,说明这块盘的TBW余量非常紧张,后续要做好每天监控。

为了持续观察,我写了一个简单的小脚本来统计每天增量:

#!/bin/bash before=$(smartctl -a /dev/nvme0n1 | grep "Data Units Written" | awk '{print $3}') sleep 86400 after=$(smartctl -a /dev/nvme0n1 | grep "Data Units Written" | awk '{print $3}') echo "24h写入: $((after - before)) 个单位"

用这种方式连续测了三天,确认写入量稳定在个位数GB/天之后,才算真正放心。

5.3 已写入256GiB后的心态与行动

最后碎碎念几句。已经写掉的256GiB,不可能找回来,纠结也没什么用。关键是认清一个事实:消费级NVMe的寿命是有限的,但也没脆弱到一次高强度写入就报废的程度。一块TLC盘在300TBW写入寿命下,256GiB连千分之一都不到;哪怕是最保守的单位写放大,也就消耗了几个百分点的理论寿命,不必因此产生“硬盘焦虑”。

真正需要反思的是使用习惯。既然Strix Halo天生适合跑大模型,那就应该从一开始就设计好内存规划:预留多大空间给模型、开多长的上下文、并发开几路、系统swap是否启用、模型文件放哪里。这些配置不是一劳永逸的,它会随着你跑的模型型号和场景而变化,但判断依据始终是那两条:看物理内存占用率,看smartctl写入速率。

我从这次事故里最大的收获是:跑模型不只是看算力和内存容量,更要看操作系统怎么对待溢出数据。你说模型推理框架只知道疯狂调内核分配内存,但内核在内存不够时并不会帮你挑更优的方案,它只会老老实实把脏页写回磁盘。把swap和内存盘这两件事理解透之后,Strix Halo才算真正跑出了应有的水平——而不是一边生成文字一边悄悄磨损硬盘。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询