3D高斯泼溅这套东西,第一次在 Windows 上折腾的人几乎都会卡在同一个地方:代码跑起来了,但编译那一步过不去。问题往往不在算法本身,而在训练链路对 Linux 工具链的依赖——CUDA 扩展、gcc、ninja、Eigen、GLM,这些东西在原生 Windows 上要么没有,要么版本对不上。WSL2 恰好补上了这块短板:它把真实的 Linux 内核和显卡直通搬到了 Windows 里,让你不用双系统就能跑完整的高斯泼溅训练流程。
下面这份内容是我自己在两台不同配置的机器上反复重装、反复踩坑之后整理出来的完整路径。它讲的不是“点几下就装完”的傻瓜教程,而是每一步为什么这么做、做到哪一步该验证、验证不过怎么往回找。适合三类人看:手上只有一台 Windows 机器但又想跑三维重建训练的;之前装过一次但卡在扩展编译环节的;以及环境能跑但速度慢得离谱想优化的。即使你完全没碰过 Linux 命令行,按顺序走也能落地。
1. 为什么这套环境必须落在 WSL2 上而不是 Windows 原生
1.1 高斯泼溅训练链路对 Linux 工具链的真实依赖
很多人以为高斯泼溅就是个 PyTorch 项目,装个 Python 环境就能跑,其实不是。核心的渲染部分是一个手写的 CUDA 扩展,它需要在训练开始之前用 nvcc 现场编译出一个 Python 可导入的动态库。这个编译过程对工具链的要求相当苛刻:需要 gcc/g++ 参与主机侧代码编译,需要 ninja 作为构建后端,需要 CUDA 头文件路径正确,还需要 C++ 标准库版本匹配。Windows 原生的 MSVC 编译器在这套组合里是走不通的,因为该扩展的构建脚本默认按 Linux 的路径约定和链接方式写的。
除了编译,还有一串间接依赖同样偏向 Linux 生态:稀疏重建常用的那个多视图几何工具,官方推荐的分发方式基本是 apt 或源码编译;数据集预处理脚本大量用了 shell 管道;一些辅助库的 Python 包在 PyPI 上只有 Linux 的 wheel。你可以把这些一个一个在 Windows 上手工移植过去,但那个工作量远超“装个双系统”。
WSL2 的价值就在这里——它给的是一个真正的 Linux 用户态,apt 能用、gcc 能装、nvcc 能跑,同时显卡还是物理机那张卡,不需要额外的虚拟机显卡透传配置。
需要说清楚的一点:WSL2 不是模拟层,它底下是微软自己的轻量虚拟化,跑的是一个真实内核。所以凡是依赖内核行为的操作(比如大页内存、某些 sysctl 参数)它会有差异,但常规的 CUDA 计算和编译完全没问题。
1.2 WSL2 的显卡直通到底直通到什么程度
这一点必须讲明白,因为它直接决定了你在 Linux 里该装什么、不该装什么。WSL2 的显卡访问路径是:Windows 侧的驱动负责真正和硬件对话,然后通过一个特殊的设备节点/dev/dxg把计算能力暴露给 Linux 用户态,Linux 侧的 CUDA 用户态库再通过这个节点调用。
这个设计带来两个直接后果。第一,你在 WSL2 里绝对不要安装完整的 Linux 显卡驱动,装了会把这条通道搞坏,典型现象是nvidia-smi直接报找不到设备。第二,你能拿到的 CUDA 版本上限,取决于 Windows 侧驱动自带的版本——驱动太老,Linux 里装再新的 Toolkit 也用不了新特性。
我用一个类比来解释:Windows 驱动是“总闸”,Linux 里的 CUDA Toolkit 是“分闸”。总闸的容量决定了分闸最多能开到多大,分闸装得再大也没用。
验证这一步是否正常,只需要在 WSL 里敲一条:
nvidia-smi正常情况下你会看到和 Windows 里一样的显卡型号、驱动版本、显存容量,只是右上角的 CUDA Version 显示的是驱动支持的上限。如果这条命令报command not found,说明/usr/lib/wsl/lib没在 PATH 里,这通常是安装顺序错了或者发行版被替换过。
1.3 什么时候其实不该用 WSL2
这话我得说在前面,免得有人白折腾一天。有三种情况,WSL2 不是最优解。
第一种是显存特别小的机器。WSL2 本身要吃掉一部分内存做虚拟化开销,如果你的显卡只有 8GB 显存,高斯泼溅训练稍微大一点的场景就会 OOM,而 WSL2 的显存回收策略比原生 Linux 保守,更容易在临界点上崩掉。
第二种是需要长时间跑大规模场景的。WSL2 的 IO 性能在跨文件系统访问时衰减很明显,数据集放在/mnt/c下面读取速度可能只有原生 Linux 的三分之一,训练时数据加载线程会成为瓶颈。
第三种是你已经有 Linux 机器或者能接受双系统。原生 Linux 的编译速度、IO 性能、驱动稳定性都更好,WSL2 的定位是“在只有 Windows 的前提下尽量可用”,不是“比 Linux 更好”。
如果你的情况不属于这三类,那继续往下走。
2. Windows 侧的准备工作:开关、发行版与资源配置
2.1 组件开启的正确顺序和容易漏掉的一步
WSL2 的安装现在简化成了一条命令,但简化不代表没有坑。以管理员身份打开 PowerShell,先执行:
wsl --install -d Ubuntu-22.04这条命令会顺带开启“虚拟机平台”和“适用于 Linux 的 Windows 子系统”两个可选功能。关键点在于:这两个功能开启后需要重启一次才真正生效,很多人没重启就直接跑wsl命令,结果报一堆莫名其妙的错误,然后开始怀疑人生。
重启之后确认版本:
wsl --list --verbose输出里的 VERSION 列必须是 2。如果是 1,用wsl --set-version Ubuntu-22.04 2转换,转换过程要几十秒到几分钟不等,取决于发行版大小。
选 Ubuntu 22.04 而不是 24.04,理由是生态成熟度。22.04 的 glibc 版本、gcc 默认版本、apt 源里的 CUDA 仓库都在一个被大量验证过的组合上,遇到问题搜到的答案也基本对得上。24.04 不是不能跑,但你会成为第一批踩新坑的人。
还有一个容易漏的地方:如果你机器上装了虚拟机软件或者开了内核隔离(内存完整性),可能会和 WSL2 的虚拟化层冲突。表现是wsl启动时卡住或者报HCS_E_HYPERV_NOT_INSTALLED。这种情况需要去“Windows 安全中心 → 设备安全性 → 内核隔离”里把内存完整性关掉,重启后再试。
2.2 发行版落地位置与磁盘空间的提前规划
WSL2 的发行版默认落地在C:\Users\你的用户名\AppData\Local\Packages\...下面,是一个叫ext4.vhdx的虚拟磁盘文件。这个文件有一个特性:它只会变大,不会自动变小。你下载了 50GB 数据集、训练完删掉,磁盘占用还是 50GB。
高斯泼溅的完整流程对空间的需求比想象中大:一个中等规模场景的原始图片几 GB,COLMAP 的稀疏重建产物和去畸变后的图片又是几 GB,训练输出的点云模型几百 MB 到几 GB,加上中间缓存和 conda 环境,一个场景跑下来 100GB 是常态。
所以我建议在C:\以外的地方规划空间。有两种做法。第一种是直接把发行版装到别的盘:
wsl --export Ubuntu-22.04 D:\wsl\ubuntu-backup.tar wsl --unregister Ubuntu-22.04 wsl --import Ubuntu-22.04 D:\wsl\ubuntu D:\wsl\ubuntu-backup.tar --version 2第二种是只把数据目录挂到别的盘,环境留在默认位置。我倾向第二种,因为环境本身不大(十几 GB),频繁迁移反而容易出问题。
导入之后有个副作用要提醒:默认登录用户会变成 root。修复方式是编辑/etc/wsl.conf:
[user] default=你的用户名2.3 .wslconfig 里该改的几个参数
这个文件在C:\Users\你的用户名\.wslconfig,控制整个 WSL2 虚拟机实例的资源分配。默认配置下 WSL2 最多会用掉你一半的物理内存,这个默认值在跑训练时会带来一个隐蔽的麻烦——训练进程把内存吃满之后,Windows 侧的响应会变慢,然后 WSL2 可能主动杀掉进程。
我一般这么配(16GB 物理内存的例子,按比例调整):
[wsl2] memory=12GB processors=8 swap=32GB swapFile=D:\\wsl\\swap.vhdx localhostForwarding=true [experimental] autoMemoryReclaim=gradual sparseVhd=true几个参数的作用值得展开说。memory是给虚拟机设的上限,留一部分给 Windows 本体,我通常留 4GB 到 6GB 给宿主。swap设得比物理内存大一倍,是因为高斯泼溅的稠密化阶段会有一个瞬时内存尖峰,物理内存顶不住的时候靠交换区兜一下比直接被 OOM 杀掉好。swapFile放到非系统盘,避免频繁读写拖慢 C 盘。
autoMemoryReclaim=gradual是让 WSL2 在空闲时把内存还给 Windows,不然你会发现关掉训练之后 WSL 还是占着十几个 G。sparseVhd=true能让虚拟磁盘在删除文件后有机会收缩。
改完必须执行wsl --shutdown完全关闭再启动才生效,光重启发行版没用。
3. Linux 侧的 CUDA 栈:只装该装的那一半
3.1 为什么 Linux 里装完整驱动会把环境搞坏
前面提过一次,这里展开讲。WSL2 的架构里,Linux 用户态通过/usr/lib/wsl/lib/libcuda.so这个由 Windows 侧注入的库来访问显卡。如果你在 Linux 里apt install cuda(注意是不带后缀的那个包),它会顺带装一个cuda-drivers,把 Linux 版的驱动库放到/usr/lib/x86_64-linux-gnu/下面。
于是系统里就有了两套libcuda.so。动态链接器加载哪一套取决于LD_LIBRARY_PATH和ldconfig缓存的顺序,很容易出现“nvidia-smi能跑但 PyTorch 说找不到设备”这种自相矛盾的现象。
正确做法是只装 Toolkit 组件,明确排除驱动:
wget https://developer.download.nvidia.com/compute/cuda/repos/wsl-ubuntu/x86_64/cuda-keyring_1.1-1_all.deb sudo dpkg -i cuda-keyring_1.1-1_all.deb sudo apt-get update sudo apt-get install -y cuda-toolkit-12-4注意包名是cuda-toolkit-12-4,不是cuda。前者只含编译器、库、头文件,后者是全家桶。装完之后nvcc就有了,但nvidia-smi依然来自 WSL 注入的那套,两者互不干扰。
如果你已经手滑装错了,卸载方式是:
sudo apt-get purge "^cuda.*" "^nvidia.*" sudo apt-get autoremove然后重装cuda-toolkit。清完之后一定要检查/usr/lib/x86_64-linux-gnu/下面还有没有残留的libcuda.so*,有就手工删掉再sudo ldconfig。
3.2 Toolkit 版本怎么选,别只看最新
选版本有一个硬约束和一个软约束。
硬约束是:Toolkit 的主版本不能高于 Windows 驱动支持的上限。比如驱动支持到 12.2,你装 12.4 的 Toolkit,编译能过,但运行时会报CUDA driver version is insufficient for CUDA runtime version。查驱动的支持上限就看nvidia-smi右上角那个数字。
软约束是:PyTorch 官方预编译包提供的 CUDA 版本。截至我写这些内容的时候,常用的是 cu118、cu121、cu124 三个档。装 Toolkit 的版本最好和 PyTorch 的 CUDA 版本一致,否则编译扩展时会出现头文件和运行库版本错位。
我的选择顺序是:先看显卡驱动支持到哪一档,然后在 PyTorch 支持的版本里选最高的那个。假设驱动支持 12.4,那就装cuda-toolkit-12-4+ PyTorch 的 cu124 版本,这是最省事的组合。
环境变量写在~/.bashrc里:
export PATH=/usr/local/cuda-12.4/bin:$PATH export LD_LIBRARY_PATH=/usr/local/cuda-12.4/lib64:$LD_LIBRARY_PATHLD_LIBRARY_PATH这一行很多人会漏。漏了之后nvcc能编译,但编译出来的扩展在 import 时找不到libcudart.so,报ImportError: libcudart.so.12: cannot open shared object file。
3.3 三层验证:驱动、编译、运行各测一次
装完立刻验证,别等到编译报错才回头查。我一般按三层来测。
第一层,设备可见:
nvidia-smi python -c "import torch; print(torch.cuda.is_available(), torch.cuda.device_count())"第二层,编译链路:
nvcc --version which nvcc要确认nvcc --version里的 release 和nvidia-smi显示的支持版本匹配。
第三层,实际算一次:
python -c " import torch a = torch.randn(4096, 4096, device='cuda') b = torch.randn(4096, 4096, device='cuda') print((a @ b).sum().item()) print(torch.cuda.get_device_capability(0)) "最后一行输出的架构编号(比如(8, 6)或(8, 9))很关键,后面编译扩展时如果报unsupported gpu architecture,就是 nvcc 不认识这个编号。CUDA 11.8 最高支持到sm_89,CUDA 12.x 才支持sm_90。显卡是 40 系及以上的必须用 12.x 的 Toolkit。
4. Python 侧的环境隔离与版本咬合
4.1 用 conda 还是 venv,我的实际取舍
两边都能用,但我现在固定用 conda,原因很实际:高斯泼溅的依赖树里混了 Python 包和非 Python 的原生库,conda 能把这两类一起管。比如 OpenGL 相关的头文件、GLM、Eigen,conda 都能装,而在 venv 里你就得手工 apt 装或者源码编译。
wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh bash Miniconda3-latest-Linux-x86_64.sh -b -p $HOME/miniconda3 ~/miniconda3/bin/conda init bash source ~/.bashrc建环境的时候把 Python 版本钉在 3.10:
conda create -n gs python=3.10 -y conda activate gs为什么是 3.10?3.9 偏老,一些新包不再提供 wheel;3.11 及以上在编译 CUDA 扩展时偶发Py_LIMITED_API相关的链接问题。3.10 是目前这套组合里最稳的。
4.2 PyTorch 与 CUDA 的对应关系,装完必须验
PyTorch 的安装命令要带--index-url,不要用默认源。默认源在 Linux 上装出来的是 CUDA 版本的通配包,有时候会拉到和 Toolkit 不匹配的版本。
pip install torch==2.1.2 torchvision==0.16.2 --index-url https://download.pytorch.org/whl/cu121装完立刻验,验的粒度要比torch.cuda.is_available()更细:
python -c " import torch print('torch:', torch.__version__) print('built with cuda:', torch.version.cuda) print('cudnn:', torch.backends.cudnn.version()) print('capability:', torch.cuda.get_device_capability(0)) "built with cuda应该和你的 Toolkit 主版本一致(12.1 对 12.1)。如果不一致,编译扩展的时候会出现undefined symbol一类的错误——因为扩展是拿 Toolkit 的头文件编译的,却链接到了另一个版本的运行库。
4.3 不要一上来就 pip install -r requirements.txt
这是我最想让新手记住的一条。上游仓库的 requirements 文件里钉死了几个包的版本,直接装有时候会把已经装好的 torch 顶掉重装,重装出来的是不匹配 CUDA 的版本,然后你前面半小时的验证全白做了。
正确顺序是:
- 先按上面固定好 torch 和 torchvision
- 再单独装其余依赖,避开 torch 相关的行
- 最后装两个 CUDA 扩展
常见的其余依赖大致是这些:
pip install plyfile tqdm opencv-python joblib pip install einops装完之后再pip list | grep torch确认一下版本没被动过。如果被改了,说明某个包的依赖声明过宽,把它单独拎出来指定版本重装。
另一个经验:装包顺序上,把会触发编译的包放最后。因为编译过程会占用大量 CPU 和内存,前面装纯 Python 包时如果失败了,重来一次成本低得多。
5. 两个 CUDA 扩展的编译:全流程最硬的一段
5.1 编译前必须确认的三件事
这段是整篇的核心,也是最容易翻车的地方。编译之前,把这三件事逐条确认,能省掉后面至少两小时的排错。
第一,gcc 版本。CUDA 12.4 官方支持的 gcc 上限是 13,12.0 到 12.3 支持到 12,CUDA 11.8 只支持到 11。Ubuntu 22.04 默认装的是 gcc-11,如果你的 Toolkit 是 12.x,默认版本是能用的。Ubuntu 24.04 默认 gcc-13,配 CUDA 11.8 就会报错。
查版本:
gcc --version g++ --version第二,ninja 是否存在。构建脚本优先用 ninja,没有就退回 setuptools 的老式构建,速度慢十倍且报错信息难读。
sudo apt install ninja-build -y第三,子模块是否拉全。扩展代码在子模块里,克隆时忘了--recursive,目录是空的,编译时直接报找不到源文件。
git submodule update --init --recursive ls submodules/应该能看到两个目录,每个目录下有setup.py和源码文件。
5.2 gcc 版本过高时的降级与切换
如果你确实撞上了 gcc 过高的问题,报错通常长这样:
error: 'numeric_limits' is not a member of 'std'或者更直白的:
#error -- unsupported GNU version! gcc versions later than 12 are not supported第二条是 CUDA 头文件里主动检查的,看到它就说明必须降版本。做法是装一个旧版本然后切换:
sudo apt install gcc-11 g++-11 -y sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-11 20 sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-11 20 sudo update-alternatives --config gcc注意update-alternatives的优先级数字(上面写的 20)要比系统原有的高,否则切换不生效。
还有一种更精细的做法:只在编译扩展的那条命令上临时指定编译器,不影响系统全局:
CC=/usr/bin/gcc-11 CXX=/usr/bin/g++-11 pip install submodules/diff-gaussian-rasterization我更喜欢这个方式,因为它不动系统的默认配置,其他项目不受影响。
5.3 GLM 头文件缺失:最常见但也最莫名其妙的一个报错
编译光栅化扩展时,如果报:
fatal error: glm/glm.hpp: No such file or directory原因是项目用了 GLM 做矩阵运算,但很多发行版的默认源里没有这个包,或者版本太老。
三种解法,我按推荐顺序排:
第一种,用 conda 装(最省事,因为头文件路径会被自动加到编译搜索路径里):
conda install -c conda-forge glm -y第二种,apt 装:
sudo apt install libglm-dev -y第三种,从源码放到固定位置:
git clone https://github.com/g-truc/glm.git /opt/glm export CPLUS_INCLUDE_PATH=/opt/glm:$CPLUS_INCLUDE_PATH如果装了还是找不到,问题往往出在编译隔离上——pip 默认在隔离环境里构建,看不到你在当前 shell 设的环境变量。加--no-build-isolation可以解决:
pip install --no-build-isolation submodules/diff-gaussian-rasterization用了这个参数之后,构建过程会用你当前环境的 torch 和 Python,所以必须先确保 torch 装好且 CUDA 版本对。
5.4 编译成功的判定标准和验证方法
编译过程可能要十几分钟,期间你会看到大量 nvcc 的输出。判断成功的标志不是“没有红色报错”,而是最后出现了Successfully installed,并且pip list里能看到这两个包。
进一步验证是能 import:
python -c " from diff_gaussian_rasterization import GaussianRasterizationSettings from simple_knn._C import distCUDA2 print('extensions ok') "如果 import 时报undefined symbol: _ZN3c10...这类东西,基本可以断定是 torch 版本和编译时用的版本不一致,或者是 ABI 问题(torch 2.x 默认用 C++17 ABI,如果你的扩展是按旧 ABI 编的就会错位)。解决办法是加编译标志重装:
TORCH_CUDA_ARCH_LIST="8.6" pip install --no-build-isolation --force-reinstall submodules/diff-gaussian-rasterizationTORCH_CUDA_ARCH_LIST这个变量值得单独说一句。它告诉编译器只针对指定架构生成代码,能显著缩短编译时间。不设的话,构建脚本会为所有已知架构都编一遍,三十分钟可能都编不完。你的架构编号从前面get_device_capability的输出里拿,30 系写8.6,40 系写8.9,A 系列写9.0。
6. 稀疏重建工具与数据集目录的规范化
6.1 apt 版本的稀疏重建工具够不够用
Ubuntu 22.04 的官方源里有colmap,版本 3.7 左右。这个版本做常规的 SfM 重建完全够用,特征提取、匹配、稀疏重建、图像去畸变这几个环节都支持命令行调用。
sudo apt install colmap -y colmap -h什么时候需要自己编新版本?两种情况:一是你的数据集用了鱼眼或者特殊相机模型,老版本不支持;二是你想用 GPU 加速的稠密重建,老版本对较新架构的支持不完整。这两种情况之外,apt 版本足够。
自己编的话,依赖会比较多(boost、ceres、eigen、freeimage、metis、qt5),编译时间半小时起步,而且 WSL2 里还要处理无 GUI 环境下的 Qt 依赖问题。除非有明确需求,我建议先用 apt 版本跑通,别在第一步就给自己加难度。
6.2 从原始素材到可训练格式的完整命令链
整个流程分四步,每步一条命令。假设图片放在~/data/scene/images/,工作目录是~/data/scene/。
第一步,特征提取:
colmap feature_extractor \ --database_path ~/data/scene/database.db \ --image_path ~/data/scene/images \ --ImageReader.camera_model OPENCV \ --ImageReader.single_camera 1 \ --SiftExtraction.use_gpu 1--ImageReader.single_camera 1这个参数很关键。如果整组照片是同一台设备拍的,必须是 1,这样所有图片共享一套内参,重建结果的尺度一致性会好很多。如果是多台设备混拍,改成 0,让每张图独立估计内参。
第二步,特征匹配:
colmap exhaustive_matcher \ --database_path ~/data/scene/database.db \ --SiftMatching.use_gpu 1图片数量少(几十张到一百多张)用 exhaustive,暴力两两匹配,最准但慢。超过两百张要考虑用sequential_matcher或者vocab_tree_matcher,前者适合视频抽帧,后者适合大规模无序图片集。
第三步,稀疏重建:
mkdir ~/data/scene/sparse colmap mapper \ --database_path ~/data/scene/database.db \ --image_path ~/data/scene/images \ --output_path ~/data/scene/sparse这一步的输出是sparse/0/目录下的三个文件:cameras.bin、images.bin、points3D.bin。如果sparse/下出现了0以外的编号,说明重建被分成了多个不连通的子模型,通常是拍摄时覆盖不连续导致的。
第四步,图像去畸变:
colmap image_undistorter \ --image_path ~/data/scene/images \ --input_path ~/data/scene/sparse/0 \ --output_path ~/data/scene \ --output_type COLMAP这一步会在~/data/scene/下生成images/(去畸变后的图)和sparse/0/(更新后的点云)。训练脚本读的是去畸变之后的这套数据,不是原始的,这是新手最常搞混的一点。
6.3 目录结构错一位就白跑
训练脚本对目录结构有硬性要求,错一层就报找不到文件。标准结构是这样的:
~/data/scene/ ├── images/ # 去畸变后的图片 ├── sparse/ │ └── 0/ │ ├── cameras.bin │ ├── images.bin │ └── points3D.bin如果输入的路径是-s ~/data/scene,脚本会自动去images/和sparse/0/下面找。所以如果你直接用image_undistorter的输出目录作为-s,结构正好对得上。
还有一点:images/下面不要留非图片文件,包括.DS_Store、缩略图缓存、隐藏文件。有些读取逻辑会遍历目录下所有文件并尝试解码,碰到非图片文件直接抛异常。
7. 训练的启动、参数取舍与过程观测
7.1 第一轮训练该用什么命令
最简启动方式:
python train.py -s ~/data/scene -m ~/output/scene-s是源数据目录,-m是输出目录,里面会存点云文件、训练日志和检查点。默认迭代三万次。
第一次跑,建议先加个限制把流程走通,别一上来就等一个小时:
python train.py -s ~/data/scene -m ~/output/scene_test --iterations 7000 --disable_viewer--disable_viewer这个参数必须有。WSL2 里默认没有图形界面,训练脚本启动时会尝试打开一个实时查看窗口,打不开就报错退出。这个参数一加,训练进程纯粹跑计算,不碰显示。
七千次迭代一般十几分钟,能输出一个粗糙但能看的模型,用来验证整条链路通不通足够了。确认没问题再跑全量。
7.2 显存不够时该动哪几个参数
高斯泼溅的显存占用主要来自两块:高斯点的参数存储,和稠密化阶段的临时缓存。显存不够的典型表现是跑到几千次迭代突然 OOM 退出。
按效果强弱排,这几个参数值得调:
| 参数 | 作用 | 显存影响 | 画质影响 |
|---|---|---|---|
--densify_gpucache_size | 限制稠密化阶段的显存缓存大小 | 大 | 小 |
--data_device cpu | 把部分数据放内存而非显存 | 大 | 无 |
--resolution | 降采样输入图片 | 中 | 中 |
--densify_until_iter | 提前停止稠密化 | 中 | 中 |
--optimizer_type sparse_adam | 用稀疏优化器 | 小 | 小 |
--data_device cpu是我最常用的一个。它把优化器状态放到内存里,显存只保留高斯参数本身。代价是每步优化的数据搬运会多一点开销,速度略降,但能让你在 8GB 显卡上跑 12GB 才能跑的场景。
--densify_gpucache_size这个参数的名字在不同版本里可能不一样,有的叫--densify_gpu_cache,用python train.py -h查一下当前版本的准确拼写。它的单位是高斯点数量,默认值通常在几十万到上百万之间,显存紧张的时候往下调一半试试。
--resolution是个粗暴但有效的办法。设成 2 表示宽高各除以 2,像素总数变成四分之一,显存和训练时间都大幅下降,但细节会明显丢失。只适合快速验证,不适合出成品。
7.3 训练过程中的观测手段
WSL2 里开一个终端专门跑监控:
watch -n 2 nvidia-smi看两个数:显存占用和 GPU 利用率。显存占用如果在稠密化阶段持续上涨直到顶格,说明参数设大了;利用率如果长期低于 50%,说明瓶颈在数据加载,把数据集从/mnt/c挪到~/下面试试。
训练日志里关注这几个量:迭代到多少时高斯点数量到峰值、loss 曲线的下降是否平滑、每千次迭代的耗时是否稳定。高斯点数量突然暴涨然后 loss 跳变,通常意味着稠密化阈值设得太激进。
想看训练中途的效果,用检查点续训加一个低频保存:
python train.py -s ~/data/scene -m ~/output/scene \ --start_checkpoint ~/output/scene/chkpnt7000.pth \ --iterations 30000续训的时候注意,学习率的调度会重置,实际效果和一次性跑完 30000 次会有细微差别,做正式实验时还是建议一次跑完。
8. 报错现场还原:五类高频问题的完整排查链路
8.1 编译报 unsupported gpu architecture
现象:nvcc 输出里出现nvcc fatal : Unsupported gpu architecture 'compute_89',编译中断。
排查链路是这样的。先确认显卡架构:
python -c "import torch; print(torch.cuda.get_device_capability(0))"假设输出(8, 9),对应sm_89。再查 nvcc 支持的上限:
nvcc --version如果 Toolkit 是 11.8,它最高支持sm_89中的部分特性,实际编 8.9 会失败。这就确认了根因是 Toolkit 太老。
解法有两条。稳妥的是升级 Toolkit 到 12.x,同时把 PyTorch 换成对应的 cu12x 版本。省事的是把TORCH_CUDA_ARCH_LIST设成一个老卡也能跑的架构,比如8.0,让代码在 8.9 的卡上以兼容模式运行。后者的代价是性能损失大概百分之十到二十,因为没法用到新架构的特定指令。
我一般选前者,因为一次装对后面省心。
8.2 训练跑到一半进程被杀
现象:训练日志正常输出,突然终止,没有 Python 异常栈,只有一行Killed。
这是 Linux 的 OOM killer 干的,不是显卡的问题。查证:
dmesg | tail -30如果看到Out of memory: Killed process xxx (python),就实锤了。
根因通常是 WSL2 的内存上限设得太小,或者交换区不够。回忆一下.wslconfig里的memory和swap,如果memory设得比物理内存的一半还低,稠密化阶段的内存尖峰就会触发。改大swap,并且确保swapFile指向的盘有足够空间。
还有一个隐蔽的原因:autoMemoryReclaim如果设成dropcache模式,会在后台主动释放缓存,某些情况下会误伤正在运行的进程。改成gradual或者直接关掉。
8.3 数据放在 /mnt/c 下导致的性能塌陷
现象:训练能跑,但 GPU 利用率长期在 30% 上下,每千次迭代耗时是别人的三四倍。
这是 WSL2 跨文件系统 IO 的典型问题。/mnt/c下面的访问要经过一层文件系统协议转换,小文件随机读的性能损失非常明显。而训练过程中的数据加载恰恰是小文件随机读为主。
验证方法很直接,把数据集复制到 Linux 侧的 home 目录再跑一遍:
cp -r /mnt/c/Users/xxx/data ~/data time python train.py -s ~/data/scene -m ~/output/scene --iterations 1000对比两次的耗时就知道了。如果差别超过一倍,就别再纠结,以后所有数据集都放~/下面。
顺带说一个清理办法。Linux 侧的磁盘用久了会涨得很难看,删掉文件也不释放。可以在 PowerShell 里手工压缩:
wsl --shutdown Optimize-VHD -Path D:\wsl\ubuntu\ext4.vhdx -Mode FullOptimize-VHD需要 Hyper-V 模块,家庭版 Windows 可能没有。替代方案是导出再导入,虽然慢但通用。
8.4 版本错配引发的 undefined symbol
现象:import扩展时报错,信息里有一长串undefined symbol: _ZN3c104cuda...之类的符号名。
这类错误的本质是“编译时链接的库”和“运行时加载的库”不是同一个版本。排查步骤:
第一步,确认当前环境的 torch 版本:
pip show torch | grep Version第二步,确认扩展是什么时候编的、用的哪个 torch:
pip show diff-gaussian-rasterization如果扩展的安装时间早于 torch 的重装时间,基本可以锁定根因。
第三步,强制重编:
pip uninstall diff-gaussian-rasterization simple-knn -y pip install --no-build-isolation --force-reinstall submodules/diff-gaussian-rasterization pip install --no-build-isolation --force-reinstall submodules/simple-knn重编的时候用--no-build-isolation是关键,它保证编译过程用当前环境的 torch 头文件,而不是 pip 临时拉一个隔离环境。
8.5 找不到 libcudart.so 的三种可能
报错长这样:
ImportError: libcudart.so.12: cannot open shared object file: No such file or directory三种可能,按概率排:
第一种,LD_LIBRARY_PATH没设或者设错。检查:
echo $LD_LIBRARY_PATH ls /usr/local/cuda-12.4/lib64/libcudart.so*第二种,只装了cuda-toolkit的子包,没装 runtime。检查:
dpkg -l | grep cuda-runtime没有就补装cuda-runtime-12-4。
第三种,装了多个版本的 Toolkit,ldconfig缓存指向了旧版本。检查:
ldconfig -p | grep libcudart如果列出来的版本不是你想要的,在/etc/ld.so.conf.d/下加一个文件指定路径,然后sudo ldconfig。
9. 从能跑到跑得顺:几处实际收益明显的调整
9.1 编译时间从半小时压到五分钟
前面提过TORCH_CUDA_ARCH_LIST,这里说清楚原理。不设这个变量时,构建脚本会为每个已知架构生成一份 PTX 和一份 SASS 代码,十几份编译产物串行生成。而你的机器只有一张卡,只需要一份。
所以在编译前固定下来:
export TORCH_CUDA_ARCH_LIST="8.6"30 系桌面卡是8.6,30 系移动版有的也是8.6,40 系是8.9,不确定就用get_device_capability查。这一条能把首次编译从三十多分钟压到五分钟以内。
同样的原理也适用于MAX_JOBS。ninja 默认并发数是 CPU 核数,WSL2 里如果processors设得小,编译会慢。可以显式指定:
MAX_JOBS=8 pip install --no-build-isolation submodules/diff-gaussian-rasterization但别设太大,编译过程每个 job 都要吃几百 MB 内存,设成物理核数容易触发 OOM。
9.2 把 IoU 和 PSNR 的评价环节也放到 WSL2 里
训练完之后一般要跑评估。评估脚本依赖的渲染流程和训练是同一套扩展,所以环境天然兼容,直接跑就行:
python render.py -m ~/output/scene --skip_train python metrics.py -m ~/output/scene--skip_train是跳过训练集的渲染,只渲测试集,省一半时间。如果你的数据集是随手拍的、没有划分训练测试集,这一步会报错,需要在数据目录下补一个sparse/0/test.txt之类的划分文件,或者干脆跳过评估。
有个细节:render.py默认会尝试启动查看器,同样要加--skip_train之外再确认有没有--quiet之类的参数抑制窗口弹出。报错退出的时候先看日志里最后一行是不是和显示相关的,是的话就是这个问题。
9.3 备份环境比备份代码更值钱
这话听起来反直觉,但实际就是这样。代码可以从上游仓库重新拉,环境重装一次要两三个小时,而且每次重装都可能踩到不同的坑。
我的做法是把 conda 环境整个导出:
conda env export --no-builds > ~/gs_env_backup.yaml--no-builds是去掉具体的构建号,让恢复时能适配不同的平台。恢复的时候:
conda env create -f ~/gs_env_backup.yaml但注意,CUDA 扩展是通过 pip 从源码装的,不在 conda 的依赖清单里。所以恢复之后还要重新编一遍那两个扩展。真正稳妥的备份是连编译好的 wheel 一起存:
pip wheel --no-build-isolation --no-deps -w ~/wheels submodules/diff-gaussian-rasterization pip wheel --no-build-isolation --no-deps -w ~/wheels submodules/simple-knn下次换机器,装好 torch 之后直接pip install ~/wheels/*.whl就行。前提是目标机器的 Python 小版本、torch 版本、CUDA 版本三者完全一致,有一个不同就得重编。
我在实际使用中吃过一次教训:换机器之后图省事直接装了备份的 wheel,torch 从 2.1.2 变成了 2.1.0,结果 import 时一堆符号找不到。从那以后我在备份目录里放一个version.txt,把python --version、pip show torch、nvcc --version三条输出都记下来,恢复前先比对一遍。
9.4 几个日常维护的小习惯
隔一段时间清理一次 conda 缓存,能回收好几 GB:
conda clean -a -y pip cache purge训练输出目录定期归档,别都堆在 home 下面。output/里每个场景动辄几个 G,十几个场景之后 home 分区就满了,而 WSL2 的根分区满了之后表现很奇怪,不是报磁盘满,而是各种操作随机失败。
还有一个:wsl --shutdown不要随手敲。它会杀掉所有正在跑的进程,包括训练到一半的。养成习惯,关之前先nvidia-smi看一眼有没有活着的计算进程。
真要说一个最省心的建议,那就是把整个流程写成一个 shell 脚本,从建环境到编译到训练全串起来,换机器的时候改两个路径变量就能跑。我第一次搭这套环境前前后后花了两天,写成脚本之后第三次重装只用了四十分钟,这个投入产出比是实打实的。