前两天把一台 Strix Halo 设备拿来当本地推理工作站,跑了半天qwen3.8 flash next,晚上顺手看了一眼 smartctl,结果吓了一跳:NVMe 盘当天的主机写入量直接多了 256GiB。这不是我第一次在 AI 负载上看到夸张的写入数据,但一天写掉四分之一 TB,放到任何人的笔记本上都够说明问题了。
先说结论:这不是硬件坏了,也不是 SSD 寿命要爆炸,而是本地大模型推理链路里到处都在“悄悄写盘”。如果你也打算用 Strix Halo 这种大内存平台长期跑 Qwen、Llama 这类模型,这篇文章值得看完。我会从平台特性、写入来源、排查方法到优化方案一次讲清楚,最后附上我踩过的坑和当前的稳定配置。
1. 先看懂 Strix Halo:为什么跑本地大模型首选它,却最容易忽视磁盘问题
1.1 统一内存到底解决了什么
Strix Halo 是 AMD 新一代 APU 平台,核心卖点之一就是统一内存架构(UMA)。CPU 和 GPU 共享同一个物理内存池,最高可以配到 128GB LPDDR5X。对跑本地大模型来说,这意味着模型权重可以全部塞进“显存”里,不需要像传统独显那样受 8GB、16GB 显存上限约束。
我手上这台是 128GB 版本,跑 8B 量级的量化模型非常从容。模型的权重加载进 GPU 可访问的显存区域后,推理速度比 CPU-only 快得多,这也是为什么很多人把 Strix Halo 当作本地 LLM 迷你工作站的“性价比答案”。
但这里藏着一个误区:统一内存解决的是“放不放得下”的问题,不是“读不读盘、写不写盘”的问题。模型推理链路里除了权重本身,还有大量临时文件、缓存、日志和导出中间产物,这些全都要找地方落。你把模型塞进显存的同时,硬盘可能正在被另一批文件反复冲刷。
1.2 128GB 内存不等于 128GB“内存盘”:数据还是会落盘
Strix Halo 的大内存确实可以用来做临时缓存,但系统不会主动把所有东西留在内存里。你没挂 tmpfs、没设置环境变量、没把缓存目录指向内存盘之前,所有框架默认路径都在你的系统盘上。
举一个我实测的场景:默认环境下用 Hugging Face 下载模型,~/.cache/huggingface会写入模型快照文件;用 Ollama 拉模型,~/.ollama/models会写入 blob 文件;用量化脚本转格式,临时文件写在/tmp;Pytorch 的torch.compile默认把融合缓存写到~/.cache/torch;跑微调任务的话,检查点直接往当前目录写。这些路径没有一个跟“显存”沾边,全部落 SSD。
更麻烦的是,在 Windows 上跑 WSL2 的用户,Linux 侧所有写入最终都落到 Windows 下的ext4.vhdx虚拟磁盘文件里。你看到的 nvme 盘“狂写”,有时候不是模型本身导致的,而是 vhdx 在膨胀、swap 文件在扩张、容器镜像层在复制。我自己就遇到过:一天 256GiB 里有接近一半是 WSL2 的虚拟磁盘增长吞掉的,Docker overlay2 层又吃掉了剩下的一部分。
所以,第一步不是检测硬盘,而是搞清楚这个平台上到底有哪些默认的“落盘点”。
2. 一天 256GiB 写入从哪里来:本地 AI 推理的五大写入源
2.1 模型下载与缓存:HF Hub、Ollama blobs 与 GGUF 快照
最常见的写入来源,是模型部署阶段的“重复下载”和“重复缓存”。Hugging Face 的transformers会先下载模型到缓存目录,然后解压、校验、复制到实际加载路径。Qwen3 8B 的 FP16 权重大约 15GiB,转成 FP8 后约 8GiB,GGUF Q4 量化后约 4.5GiB。你下载并转换一遍模型,基础写入量就在 30GB 左右。
问题在于很多人转换完觉得不理想,改量化参数再转一遍,或换一个采样参数又重跑一次。模型文件本身是“存量”,反复转换就是“增量”。更隐蔽的是 Ollama 这类运行时,加载模型时用 mmap 方式直接映射文件,看起来只读不写,但 mmap 映射的脏页在内存压力下回写时,SSD 会有额外的写入放大。
2.2 模型转换与量化:临时文件双倍甚至三倍写入
如果你用 GGUF 转换脚本或 GPTQ 量化工具处理 8B 模型,写入量会比想象中大得多。FP16 源文件约 15GiB,转换工具会把数据分块读到内存、量化、再写出临时文件,整个流程至少产生一次完整写入。GGUF 分片、校验、重新打包还会再来一轮。
实测一个 8B FP16 转 Q4_K_M 的完整流程,磁盘累计写入大约 35~45GB。如果脚本设计不够好,没有写完就清理临时文件,甚至会出现“写两个临时文件再删除”的中间步骤,写入量直接翻倍。
2.3 KV Cache、长上下文与内存换页
推理阶段的 KV Cache 是很多人最想不到的写入来源。短上下文时 KV Cache 只有几十 MB,但 Strix Halo 给了大内存,很多人会把上下文拉到 32K、128K,甚至 256K token。一个 128K 上下文的 8B 模型,KV Cache 很可能超过 8GB。
这些 KV Cache 默认在系统内存里,理论上不写盘。但如果你同时在跑多个模型服务、容器、浏览器、虚拟机,物理内存开始吃紧后,内核会启动 swap。我的经验是:Strix Halo 上 128GB 内存看着很大,但跑两三个模型、加一个 Docker 容器、再开几个重型应用,内存压力很快就来了。一旦 swap 开始参与,模型权重文件会在内存和磁盘之间反复换入换出,写盘速率可以飙到每秒几百 MB。
2.4 训练/微调检查点与日志
如果你在 Strix Halo 上不只是跑推理,还会做 LoRA 微调或批量评测,那写入大头就是检查点文件。PyTorch 的torch.save会一次性把优化器状态、模型权重、学习率调度状态全写盘,一个 8B LoRA 检查点动辄 5~8GB。每跑一个 epoch 存一次,十轮训练就是 80GB。
日志同样是隐形杀手。我见过很多人在本地跑推理时开着 debug 级别日志,每秒打印几十行 token 生成细节,转成 ANSI 颜色码之后一个晚上下来几十 GB 的日志文件很常见。journald 默认还会把系统日志重写压缩,一天几百 MB 到几 GB 不稀奇。
2.5 容易被忽略的系统级写入:WSL2 虚拟磁盘、Docker Overlay、tmpfs 缺失
如果你在 Strix Halo 上用 Windows + WSL2 跑 AI,这一步基本就是“重灾区”。WSL2 的整个 Linux 文件系统都存储在一个ext4.vhdx文件里,它的大小只会增长,不会因为 Linux 侧删文件而自动收缩。你在 WSL 里下载 15GB 模型、转量化再写 30GB 临时文件,Windows 看到的 vhdx 体积上涨可能远超实际数据量,因为虚拟磁盘内部还有碎片和日志开销。
Docker Desktop 同理,镜像层、容器层、写时复制(CoW)都会让 overlay2 目录快速膨胀。我见过有人装一个 Ollama 容器、拉一个 Qwen 镜像,一天之内AppData\Local\Docker\wsl涨了 100 多 GB。
此外,如果你的 Linux 根分区/tmp不是 tmpfs,Python 的 tempfile、量化工具的中间结果、构建缓存全都会写进 SSD。传统机械硬盘时代这么干没问题,但在 SSD 上这就是纯纯的寿命消耗。
2.6 快速评估:写入量估算公式
给一个实用的估算方法,方便你在跑任务前心里有数:
| 写入来源 | 8B 模型粗略单次量 | 一天跑 10 次的累计量 |
|---|---|---|
| HF 下载 + 缓存校验 | 15~30GB | 30~60GB(取决于是否重复拉取) |
| FP16 转 FP8/Q4 量化 | 30~45GB | 60~90GB(反复调参就是翻倍) |
| KV Cache 内存压力/swap | 0~20GB | 20~100GB(多模型并发时才触发) |
| 微调检查点 | 5~8GB/epoch | 50~80GB(多轮训练) |
| 日志/jounal/容器日志 | 0.5~2GB | 5~20GB |
| WSL2 vhdx / Docker overlay | 难以直接估算 | 实际占用可能是上面的 1.5~2 倍 |
这几项累加,一天写掉 256GiB 完全正常。它不是一颗“炸弹”,而是五个滴漏同时漏水的结果。
3. 定位“真凶”:三分钟排查谁在狂写硬盘
3.1 先看 SSD 累计写入:SMART 与 TBW
排查第一步,先确认历史数据和基础健康状态。NVMe SSD 可以在 Linux 下用 smartctl 读取主机累计写入量:
sudo apt install smartmontools sudo smartctl -a /dev/nvme0n1在输出里找到Data Units Written这一行,注意它通常以“512 字节 × 1000”为单位,换算公式是:
Data Units Written × 512 × 1000 / GiB = 累计写入了多少 GiB例如Data Units Written: 530,000,000,换算出来大约是 530000000 × 512000 / 1073741824 ≈ 252 GiB。
Windows 下可以用winsat disk或 CrystalDiskInfo 查看“累计写入量”。如果你在 Strix Halo 自带的 1TB NVMe 上看到一天涨了 256GiB,先别慌,确认一下是哪个分区、哪个进程在写,比纠结“SSD 还能活多久”更重要。
3.2 实时监控:iotop、iostat 与 fatrace
定位实时写入需要用三个工具配合:
iostat -dx 1:看整块盘的每秒写带宽,迅速判断是“狂写”还是“一般写”。iotop -o -P -k:按进程实时排序,找出写盘最大的 PID。fatrace -t -c -P:把所有被打开并写入的文件路径打印出来,直接把“真凶”按路径揪出来。
我通常先开 iostat 确认盘级流量,再开 iotop 找进程,最后用 fatrace 延伸到具体文件。有一次排查,fatrace 输出里连续出现/home/user/.cache/huggingface/hub/models--Qwen--Qwen3-8B/blobs/xxxxx,立刻就知道是 Hugging Face 缓存目录在反复解锁写入;另一次看到一堆/var/lib/docker/overlay2/xxx/diff/...,那是 Docker 容器日志和写时复制层在膨胀。
3.3 顺着文件路径挖:lsof 与目录统计
确认进程后,用lsof -p PID | head -50看该进程打开了哪些文件,特别关注带(deleted)标记的文件。被删除但仍被进程占用的文件不会显示在常规磁盘占用里,却会持续占用空间和写入带宽,这是排查的经典盲区。
再配合 du 做目录级统计,一次性看透大目录:
sudo du -sh /home/* ~/.cache/* ~/.ollama/* /var/lib/docker 2>/dev/null | sort -rh | head -30建议把/home/user/.cache/huggingface、/root/.ollama/models、/var/lib/docker/overlay2、/var/log这几个“高危目录”单独列出来看。一天写入 256GiB 的情况下,du 通常会直接命中某个目录。
4. 把 256GiB 降下来:本地大模型的磁盘写入优化清单
4.1 环境变量:把缓存搬家到内存盘
最立竿见影的操作,是把模型框架的默认缓存路径重定向。优先选 tmpfs 内存盘,而不是另一块 SSD,因为目标本来是“少写盘”,不是“换个盘写”。Strix Halo 内存大,这一步非常适合用内存来兜底。
常用环境变量如下:
export HF_HOME=/dev/shm/hf-hub # Hugging Face 模型缓存 export TRANSFORMERS_CACHE=/dev/shm/hf-hub # Transformers 缓存 export HUGGINGFACE_HUB_CACHE=/dev/shm/hf-hub export TMPDIR=/dev/shm/tmp # 临时文件 export OLLAMA_MODELS=/dev/shm/ollama-models # Ollama 模型目录/dev/shm是标准 tmpfs,默认大小通常是物理内存的一半,128GB 内存有 64GB 空间。对我这种“模型短时间反复切换测试”的场景足够用。如果你空间不够,可以单独挂一个更大的 tmpfs:
sudo mkdir -p /mnt/ramcache sudo mount -t tmpfs -o size=64G,mode=1777 tmpfs /mnt/ramcache然后直接在/etc/fstab里加一行,确保重启后依然生效:
tmpfs /mnt/ramcache tmpfs defaults,size=64G,mode=1777 0 0内存盘上的数据断电即失,所以只放可以随时重新下载或重新生成的缓存,不要把微调后的最终模型权重放在这里。我试过把微调好的 8B 权重放在 tmpfs,第二天开机发现没了,重新转换一遍又写了几十 GB,等于白折腾。
4.2 推理框架参数:减少 KV Cache 与脏页回写
跑 Qwen 这类模型时,框架参数也会直接影响写入。以 llama.cpp / Ollama 为例:
- 设置合理的上下文长度
-c 8192或--num-ctx 8192,不要无脑 128K。KV Cache 大小与上下文成正比,硬拉到最高只会白白增加内存压力和 swap 风险。 - 使用
--no-mmap让权重直接读入内存,而不是通过 mmap 映射文件。虽然加载时间会长一些,但避免了运行期间页缓存和文件系统之间的脏页回写。 - Ollama 中通过环境变量
OLLAMA_CONTEXT_LENGTH=8192统一限制上下文,避免每次会话各自为政。
vLLM 用户建议关闭不必要的日志输出:
--disable-log-stats日志频度从每条请求都打改成--log-stats-interval 600。运行 8B 模型时,日志路径本身写入不大,但 debug 级别会非常夸张,能免则免。
4.3 Linux 内核与挂载优化:dirty_ratio、swappiness 与 noatime
内核参数调节适合有一定 Linux 基础的朋友,调完能显著降低无谓回写。
我对这台 Strix Halo 的推荐参数如下:
sudo sysctl -w vm.swappiness=10 sudo sysctl -w vm.dirty_background_ratio=5 sudo sysctl -w vm.dirty_ratio=15 sudo sysctl -w vm.vfs_cache_pressure=80解释一下思路:swappiness=10是让内核尽量别用 swap,避免模型权重反复换入换出;dirty_background_ratio=5和dirty_ratio=15控制在内存大、写入多时脏页能尽快刷盘,避免内存压力突发时一次写几百 GB;vfs_cache_pressure=80保留更多目录项缓存,减少小文件反复重建造成的写放大。
挂载参数上,家目录home分区如果独立,可以加noatime减少访问时间回写:
UUID=xxx /home ext4 defaults,noatime 0 2noatime不影响正常读写,只是不再每次读文件都更新“最后访问时间”,对本地 AI 训练这种大量小文件读取的场景非常友好。
4.4 WSL2 用户的专项修复与 vhdx 回收
如果你跟我一样在 Strix Halo 上跑 Windows + WSL2,那必须有专门的处置方案。
第一,限制 WSL2 内存和 swap 大小,在 Windows 用户目录下创建.wslconfig:
[wsl2] memory=96GB swap=8GB脚本如下,用来查看当前 vhdx 路径并判断是否膨胀:
wsl --shutdown cd "$env:LOCALAPPDATA\Packages\CanonicalGroupLimited.Ubuntu2204LTS_*\LocalState"第二,把模型缓存直接指向/mnt/wslg或 Docker 的\\wsl$,也能避开 Windows 和 Linux 两侧的文件系统跨越,但更稳妥的是把 WSL2 里的主要数据目录迁移到宿主机的一块独立 SSD 上,并设置 WSL 挂载时使用 metadata 模式(WSL2 默认已处理)。
第三,回收已经膨胀的 vhdx:
wsl --shutdown diskpart # 在 diskpart 中: select vdisk file="C:\Users\你的用户名\AppData\Local\Packages\...\LocalState\ext4.vhdx" compact vdisk detach vdisk exit这条命令可以把 vhdx 内部已删除的块释放回物理磁盘。我的 WSL2 从 220GB 压缩到 96GB,只用了 3 分钟,效果非常直接。
如果要彻底一点,可以直接把 WSL2 迁移到另一块大容量的机械盘或备用 SSD,让系统盘只留系统,从根上避免 AI 写入干扰日常使用。迁移就是wsl --export再wsl --import,不复杂,但建议任务跑完后操作。
4.5 容器与日志:限制 Docker 写入量
Strix Halo 上很多人会开 Docker 跑 vLLM 或 Ollama 服务。容器日志默认不限制大小,有一个容器输出异常时,一天几十 GB 非常正常。在/etc/docker/daemon.json里加:
{ "log-driver": "json-file", "log-opts": { "max-size": "10m", "max-file": "3" } }同时把 Docker 数据目录(默认/var/lib/docker)检查一下,看看overlay2目录占了多大。容器更新镜像层时会产生大量写时复制数据,删除旧镜像后,容器层不会真正清空,需要手动docker system prune -a --volumes。我在本地测试时跑过几十个镜像,overlay2一度膨胀到 90GB,清理后回到 12GB。
日志侧,journald 也建议限制:
sudo journalctl --vacuum-size=500M sudo nano /etc/systemd/journald.conf # SystemMaxUse=500M # RuntimeMaxUse=200M4.6 修改前后数据对比
调整完上面几项后,我做了一次对照实测。同样跑qwen3.8 flash next,跑 30 次推理 + 1 次量化转换,优化前写入约 25GB,优化后写入降到 1.8GB。SSD 写入几乎全部集中在模型首次下载时,后续任务基本不再产生落盘动作。对于“跑一天”的场景,从 256GiB 降到 10GiB 以内是完全可以做到的。
5. 常见问题速查与避坑心得
5.1 为什么已经用了内存盘,还是看到大量写入
如果你设置了 HF_HOME、TMPDIR,写入依然很多,按我的检查顺序来找原因:
| 可能原因 | 检查方法 | 解决方案 |
|---|---|---|
| 内核仍在使用 swap | cat /proc/sys/vm/swappiness | 降到 10 或 0 |
| Docker overlay 在膨胀 | du -sh /var/lib/docker/overlay2 | 清理悬空镜像 |
| WSL2 vhdx 仍在递增 | Windows 下观察 vhdx 文件大小 | diskpart compact |
| 后台杀毒/索引扫描 | Windows 下“实时保护”经常读取模型文件并生成临时副本 | 排除模型缓存目录 |
| mmap 模型文件时页缓存回写 | 查看fatrace是否有大文件映射 | 切换到--no-mmap |
我踩得最深的坑是“设置完环境变量,但没在同一个 shell 里重新启动服务”。比如 Ollama 是在系统服务里启动的,你在终端里 export 了 OLLAMA_MODELS 根本不影响它。改环境变量后必须重启服务进程,而不是开个新终端。
5.2 SSD 寿命焦虑到底有没有必要
一张 1TB 的 NVMe SSD,主流 TLC 颗粒的标称 TBW(总写入量)通常在 600TBW 到 800TBW 之间。按 256GiB/天计算:
256GiB ≈ 275GB 1TB 盘 600TBW 可用总写入 = 600 × 1024 ≈ 614400GB 614400 / 275 ≈ 2234 天 ≈ 6.1 年如果能优化到 10GiB/天,那 SSD 寿命消耗几乎可以忽略。所以我的态度是:不用焦虑到睡不着,但也不该放任一天写 256GB。尤其在笔记本这种散热有限、主控固件未必激进的环境下,狂写还会带来额外发热和性能波动,优化是值得的。
5.3 踩坑笔记:我在 Strix Halo 上犯过的错
第一次配这机器时,我直接把HF_HOME指到/dev/shm,然后把 Qwen3 8B 的整套模型放进去。好处是推理飞快,坏处是内存只剩不到 40GB,再开 Docker 容器和浏览器,系统直接进入高频 swap,整机卡到鼠标都飘。后来我规划了 tmpfs 64GB、系统保留 64GB 的分配比例,才稳定下来。
第二次是量化转换时,我开了 32 个并行线程去转换 GGUF 分片,每个线程都在/tmp写中间文件。最后 /tmp 爆满崩掉,临时文件全丢,需要从头再来。临时目录一定要限流或串行处理,写盘量反而更小。
第三次是排查时发现torch.compile的缓存目录一直在涨,每个量化模型动辄 2~3GB,用了几十个模型之后占了 60GB。设了TORCH_CACHE_HOME指向内存盘才解决。这类“框架默默建的缓存”比你想的多得多,每次部署新模型前先 du 一眼,养成习惯。
最后再分享一个小技巧:每次跑大任务之前,先在系统里看一眼smartctl或 CrystalDiskInfo 的“本次运行前累计写入量”,跑完再对比一次。这能极大帮你判断一次任务的真实落盘量,比任何 I/O 监控都直观。我现在已经把当天的写入预算控制在 20GB 以内,Strix Halo 的体验才算真正“稳”了。