WSL2 虚拟磁盘空间暴涨根治与自动收缩调优

1. 故障现场与问题复现

1.1 运行环境与依赖版本

组件 / 工具版本 / 配置信息
宿主操作系统Windows 11 23H2 (OS Build 22621.x)
WSL 架构WSL 2 (Kernel 5.15.x / 6.x)
Linux 发行版Ubuntu 22.04 LTS (x86_64)
核心开发负载PyTorch / CUDA 12 / 大语言模型微调与推理 (Qwen / MiniMax)
磁盘拓扑虚拟硬盘迁移至数据盘 E:\WSL\ext4.vhdx

1.2 磁盘告急现场

在准备安装或更新本地游戏补丁时,系统提示磁盘空间不足。通过 PowerShell 查询宿主机卷容量:

1
Get-Volume | Select-Object DriveLetter, @{Name='TotalGB';Expression={[int]($_.Size/1GB)}}, @{Name='FreeGB';Expression={[int]($_.SizeRemaining/1GB)}}, @{Name='PercentFree';Expression={[int](($_.SizeRemaining/$_.Size)*100)}}

控制台返回如下告警数据:

1
2
3
4
5
DriveLetter TotalGB FreeGB PercentFree
----------- ------- ------ -----------
C 512 39 8
D 512 78 15
E 884 40 4

E 盘总计 884GB,可用空间仅剩 40GB (4%),已触发 Windows 底层红色高危预警。经对 E 盘各根目录进行空间穿透扫描,发现了空间消耗的绝对元凶:

1
2
3
4
5
6
Name                      SizeGB
---- ------
WSL 594.68
VM 93.63
Code 57.80
steam 47.46

单个 E:\WSL\ext4.vhdx 文件独占 594.68 GB,吃掉了整个磁盘近 70% 的存储容量。


2. 排查路径与根因深度推演

2.1 常见误区与算力账单核验

在直觉上,很多人第一反应是“一定是我的大模型和大文件占满了空间”。为了验证到底是有效数据还是纯粹虚胖,我们进入 WSL 内部进行深层审计:

1
2
3
4
5
# 1. 检查 Linux 根挂载点逻辑占用
df -h /

# 2. 深入 /home 目录进行大文件排查
du -h --max-depth=2 /home 2>/dev/null | sort -hr | head -n 10

审计输出揭示了惊人的事实:

1
2
3
4
5
475G    /home/<username>
154G /home/<username>/.cache
141G /home/<username>/models
116G /home/<username>/projects
26G /home/<username>/anaconda3

进一步穿透 .cache 目录:

1
2
3
4
93G     /home/<username>/.cache/uv
30G /home/<username>/.cache/pip
12G /home/<username>/.cache/modelscope
11G /home/<username>/.cache/huggingface
  • 误区 1:认为所有 595GB 都是模型。
    实际清点显示,所有大模型权重(MiniMax 68GB + Qwen 56GB 等)合计为 141 GB;
  • 误区 2:包管理器下载的临时包被长期遗忘。
    Python 新一代包管理器 uv 的 wheels 归档与 pip 历史缓存,无声无息积攒了 123 GB 的废弃缓存;
  • 误区 3:Linux 删了文件,Windows 文件就会变小。
    过去几个月里跑 AI 下载的测试模型与临时数据集,虽然在 Linux 里执行过 rm 删除,但在 Windows 上 ext4.vhdx 的体积毫无变化。

2.2 根本原因定位(Why & How)

核心根本原因简述:WSL2 采用微软 Hyper-V 动态扩展虚拟硬盘(Dynamic VHDX)技术。其分配机制是单向递增的:写入新块时向后追加分配,而在文件系统内部标记删除扇区时,底层的物理扇区映射并不会触发截断(Truncate),导致大量“空洞”长期霸占宿主机物理存储。

flowchart TD
    subgraph Linux["WSL2 Linux 内部 (ext4)"]
        L1["写新文件 / 下载大模型权重"] -->|"向文件系统追加 Block"| L2["高水位空间持续上涨"]
        L3["执行 rm 删除历史废弃文件"] -->|"仅在 Linux 内部标记 Block 空闲"| L4["未向底层虚拟磁盘触发 Truncate 截断"]
    end
    subgraph Host["Windows 宿主机底层 (VHDX 容器)"]
        H1["E:\\WSL\\ext4.vhdx 虚拟磁盘"] -->|"单向动态扩展 (只增不减)"| H2["594.68 GB 物理高水位霸占"]
        H2 -.->|"产生上百 GB 空洞扇区"| H3["宿主 E 盘仅剩 4% 标红告急"]
    end
    subgraph Solution["系统化瘦身闭环"]
        S1["1. Linux: fstrim -v / (广播空闲扇区)"] --> S2["2. Windows: diskpart compact (物理脱水收缩)"]
        S2 --> S3["3. .wslconfig: sparseVhd=true (开启全自动回缩)"]
    end
  1. 写穿透但不同步回缩:Linux 的 ext4 文件系统将废弃 block 归还给 super block 的空闲列表,但不会主动向底层虚拟控制器广播 TRIM/DISCARD 指令。
  2. 逻辑分母与物理分母混淆:WSL2 默认给虚拟磁盘配置了 1007 GiB (1TB) 的最大虚拟上限。执行全局 TRIM 时清点出的 510GB 空闲是指逻辑上限中的无数据空间,实际物理脱水空间等于“当前物理 VHDX 大小 - Linux 内部真实留存数据”。

3. 终极解决方案与实操复盘

实操遵循“内部清障 -> 内核标记 -> 物理脱水 -> 开启自动回收”四步闭环。

3.1 第一步:WSL 内部存量垃圾与缓存大扫除

在 Linux 终端内执行以下命令,彻底清空已失效的构建包与 wheels 归档:

1
2
3
4
5
# 清理 uv 与 pip 的海量临时轮子
rm -rf ~/.cache/uv ~/.cache/pip ~/.cache/huggingface/datasets

# 清理 apt 包缓存
sudo apt clean

清理后,Linux 内部实际占用直接缩减近 70GB。

3.2 第二步:调用内核级 fstrim 广播空闲扇区

利用 Linux 的 fstrim 机制,强制向底层虚拟磁盘驱动通知哪些逻辑扇区已被释放:

1
sudo fstrim -v /

控制台成功返回:

1
/: 510.9 GiB (548559196160 bytes) trimmed

3.3 第三步:解决排坑冲突,执行 diskpart 物理脱水

遇到的次生报错复盘

在尝试将 VHDX 文件脱水收缩时,先后踩了两个高频坑:

坑 1:PowerShell Here-String 格式语法错误
在通过 Here-String 多行语法向 diskpart 输入指令时,结束标记 "@ 必须严格顶格编写(不能有前置空格或缩进),否则 PowerShell 会判定输入未闭合并一直挂起在 >> 提示符。

坑 2:Sparse 稀疏属性导致 diskpart 报错拒收
直接使用 diskpart 压缩时抛出严重异常:

1
DiskPart 遇到错误: 由于虚拟磁盘系统限制,无法完成请求的操作。虚拟硬盘文件必须是未压缩和未加密的文件,并且不能是稀疏文件。

根因剖析:若之前对磁盘打过 sparse 标记,Windows 原生 VDS 引擎会拒绝直接 compact。必须先用 fsutil 去掉 sparse 标记,并停止锁定该文件的 WslService 服务。

正确的管理员收缩脚本

以管理员身份启动 PowerShell,执行以下标准脱水工作流:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 1. 彻底退出 WSL 并停止底层独占服务
wsl --shutdown
Stop-Service -Name WslService -Force -ErrorAction SilentlyContinue

# 2. 清除 NTFS sparse 限制标志
fsutil sparse setflag "E:\WSL\ext4.vhdx" 0

# 3. 驱动 diskpart 执行只读挂载与压缩
@"
select vdisk file="E:\WSL\ext4.vhdx"
attach vdisk readonly
compact vdisk
detach vdisk
exit
"@ | diskpart

# 4. 恢复 WSL 系统服务
Start-Service -Name WslService -ErrorAction SilentlyContinue

在 DiskPart 中看到进度输出:

1
2
3
  100 百分比已完成
DiskPart 已成功压缩虚拟磁盘文件。
DiskPart 已成功分离虚拟磁盘文件。

3.4 第四步:配置一劳永逸的“全自动收缩与内存归还”

为了避免未来继续手动运行 compact,我们需要在用户目录下配置 WSL2 现代高级特性。

编辑宿主机当前用户目录下的 .wslconfig(位于环境变量 %USERPROFILE%\.wslconfig),追加以下配置:

1
2
3
4
5
6
7
8
9
10
[wsl2]
networkingMode=mirrored
autoProxy=true

[experimental]
hostAutoFirewall=true
# 开启稀疏虚拟磁盘自动回收:在 Linux 中 rm 时 Windows 物理空间自动返还
sparseVhd=true
# 开启渐进式内存归还:AI 跑完闲置时自动将占用的大量 RAM 归还给 Windows
autoMemoryReclaim=gradual

重启 WSL 使其常态化生效:

1
wsl --shutdown

4. 避坑防范与成果对比

4.1 战报收益对比

通过本次系统化整治,在保证 141GB 深度学习大模型、全部业务源码、Python/Conda 环境零损坏、完全可用 的前提下,取得了显著的回血成果:

关键监控项优化前优化后收益变化
ext4.vhdx 文件大小594.68 GB499.50 GB净减 95.18 GB 空间
宿主 E 盘剩余空间40 GB (4%)135 GB (15%)可用空间暴涨超 330%
磁盘告警状态[高危预警] 严重标红[安全常态] 回归健康线彻底消除游戏更新/解压报错

4.2 防御性工程检查清单(Checklist)

  1. AI 开发者善用环境变量重定向缓存:在 WSL 中建议在 ~/.bashrc 中指定 pip 和 uv 的缓存生命周期或上限:
    1
    export PIP_NO_CACHE_DIR=off # 或定期设置定时清理脚本
  2. 避免在 Linux 虚拟磁盘内长期堆放废弃 checkpoint:大型 LoRA 或训练中间权重建议在验证完毕后及时归档转存至 NAS 或移动硬盘。
  3. 启用 sparseVhd=true:这是微软目前最优雅的解决机制,搭配定期 fstrim 可基本告别手动挂载 diskpart 压缩的繁琐流程。

5. 参考链接与延伸阅读