简介:面向希望私有化运行大语言模型的开发者与企业运维人员,这份PDF围绕DeepSeek本地部署的硬件选型与环境配置展开,覆盖个人学习测试到企业生产级应用的分级需求。内容从7B以下模型的基础开发环境、32B以上模型的集群配置,到Linux与Windows/WSL2系统准备、CUDA与cuDNN验证、PyTorch及FlashAttention安装均有涉及,并补充4-bit量化、显存分级卸载、分布式训练参数和DCGM监控调优等实践要点。资源包共1个PDF文件,大小约274KB,结构紧凑,适合作为部署前的配置清单与排错参考。已有2219人学习,可帮助读者快速判断硬件门槛、梳理软件依赖并减少环境搭建中的试错成本。
1. 显存还剩一半却报 CUDA out of memory:DeepSeek 本地部署的第一道坎
DeepSeek 本地部署踩坑,最让人头疼的不是跑不动,而是它看起来马上就能跑起来:nvidia-smi里 24GB 显存明明空着一大半,权重加载到 60% 左右,终端突然出现一行torch.cuda.OutOfMemoryError: CUDA out of memory. Tried to allocate 2.00 GiB。硬件要求与环境配置这两件事,坑几乎都出在“账面数字对得上、实际就是跑不起来”的缝隙里——7B 和 32B 不是一个量级的活,FP16 和 4-bit 也不是改个开关就行。
下面按报错类型一层层说明:先是驱动、CUDA、PyTorch 三方版本对不上,再是显存估算和量化参数,然后是world_size对不上,最后是多卡 NCCL。每一段都给出可以直接复制使用的命令,以及命令行背后到底在验证什么。
2. 环境装完仍跑不起来:torch.cuda.is_available() 返回 False 的三方版本对不上
这类故障的迷惑性在于它完全不报错,只是静默降级。你import torch成功,from_pretrained也成功,就是慢得像在用 CPU 跑——因为它真的在用 CPU 跑。判断顺序永远是:先确认驱动活着,再确认 toolkit 版本,最后确认装进来的 wheel 带不带+cu121后缀。顺序反了,你会花两小时重装 CUDA,而问题其实在 pip 源上。
2.1 is_available() 返回 False:四种成因逐条验证
不要一上来就重装。四条命令按顺序打出去,基本能定位到哪一层断了。
# 1) 驱动是否活着,右上角 CUDA Version 是驱动能支持的最高运行时版本 nvidia-smi # 2) 内核模块是否真的加载了,输出为空说明只有驱动装了、模块没起来 lsmod | grep -E '^nvidia' | wc -l # 3) dkms 里驱动有没有编译失败(换过内核之后最常见的翻车点) dkms status # 4) 装进来的 torch 到底是 GPU 版还是 CPU 版 python -c "import torch; print(torch.__version__, torch.version.cuda, torch.cuda.device_count())"四条命令对应四种成因:nvidia-smi本身报Failed to initialize NVML,说明驱动层就没起来,后面全不用看;lsmod输出 0,说明linux-modules-extra-$(uname -r)没装或者内核刚升级过,sudo apt install -y linux-modules-extra-$(uname -r)之后重启即可;dkms status里出现install, ... , this kernel的报错,通常是 gcc 版本和新内核头文件对不上,装linux-headers-$(uname -r)再dkms autoinstall;最后一条输出里torch.version.cuda是None,那就是拿到了 CPU wheel,换--extra-index-url重装。
提示:容器里跑的时候,
nvidia-smi正常但 torch 找不到卡,八成是nvidia-container-toolkit没装,docker run --gpus all的--gpus被静默忽略了。
2.2 装错顺序的代价:驱动、toolkit、框架,少一步就白装
正确的顺序是驱动在前、toolkit 居中、框架最后,因为 toolkit 的安装程序会检测驱动版本,版本不匹配时它要么拒绝安装,要么给你装上一份自带的驱动把原来的顶掉。我一般的做法是安装时只选 toolkit 和 samples,把驱动这一项去掉。
# 1) 只装 toolkit 与 samples,不动已有驱动 wget https://developer.download.nvidia.com/compute/cuda/12.1.0/local_installers/cuda_12.1.0_530.30.02_linux.run sudo sh cuda_12.1.0_530.30.02_linux.run --override --toolkit --samples --silent # 2) 让新 toolkit 优先进 PATH,但保留系统自带的那份做兜底 echo 'export PATH=/usr/local/cuda-12.1/bin:$PATH' >> ~/.bashrc echo 'export LD_LIBRARY_PATH=/usr/local/cuda-12.1/lib64:$LD_LIBRARY_PATH' >> ~/.bashrc sudo ldconfig # 3) 装对应的框架版本,注意 +cu121 这个后缀不能丢 pip install torch==2.1.0+cu121 --extra-index-url https://download.pytorch.org/whl/cu121--override的作用是跳过编译器的版本检查,在新发行版上不跳过时安装程序会直接退出;--toolkit只装编译器和运行库,--samples顺带装上验证用例;--silent让安装过程不弹出交互式界面,适合写成脚本批量执行。之所以不选--driver,是因为 toolkit 自带的驱动往往比机器上现有的更旧,覆盖之后反而会把nvidia-smi一起搞崩。
装完不要直接用nvcc -V就认为成功了——nvcc用的是 PATH 里那份,torch 用的是它自己编译时链接的 runtime,两者可以完全不一致。真正的验收脚本是这样:
import torch print(torch.__version__) # 期望 2.1.0+cu121,出现 +cpu 就是拿错了 wheel print(torch.version.cuda) # 期望 12.1,None 说明 CPU 版 print(torch.cuda.is_available()) # 期望 True print(torch.backends.cudnn.version()) # 期望 >= 8902 print(torch.cuda.get_device_capability(0)) # A100 应为 (8, 0),3090 应为 (8, 6)最后一个get_device_capability经常被忽略,但它决定了后面 FlashAttention 编译时的TORCH_CUDA_ARCH_LIST该填什么。填错的表现不是报错,是编译成功、运行时抛no kernel image is available for execution on the device,非常迷惑人。
2.3 系统层还得改三处:swappiness、部署目录与 Windows 侧驱动
框架装对了只是第一步,系统层还有几处不改就会在后面慢慢影响你。
# 1) 降低换页倾向,避免权重被换到 swap 又从盘上读回来 echo "vm.swappiness=10" | sudo tee -a /etc/sysctl.conf sudo sysctl -p # 2) 建专用部署目录并给足权限,模型权重动辄几十 GB,别放 /home sudo mkdir -p /opt/deepseek && sudo chmod 777 /opt/deepseek # 3) 确认 PCIe 链路速率没被降级,x8 和 x16 的推理吞吐能差两成 nvidia-smi --query-gpu=pcie.link.gen.current,pcie.link.width.current --format=csvvm.swappiness从默认的 60 改到 10,是因为加载 32B 权重时内存会瞬间被占满,内核如果积极换页,模型的前几百个 token 会一直在等磁盘 IO,表现出来就是“第一个 token 要等半分钟,之后飞快”。默认值 60 在桌面场景下是合理的,在推理机上就是拖后腿的那一项。
Windows 侧要注意的是驱动归属:WSL2 里的 CUDA 走的是 Windows 主机上的驱动,不能在 Ubuntu 里再装一遍 Linux 驱动,否则会互相干扰。版本要求是 Windows 11 Pro 22H2 及以上、DirectX 12 Ultimate 运行时、驱动 535.54.03 以上,WSL2 内建议用 Ubuntu 20.04。还有一点:WSL2 的显存上限默认是主机显存的一半多一点,32GB 的卡在 WSL 里可能只认到 20GB 出头,如果模型刚好卡在这个边界上,会表现得像是显存被莫名占用。
2.4 FlashAttention 编译失败:MAX_JOBS 与 TORCH_CUDA_ARCH_LIST 怎么填
pip install flash-attn==2.3.3是最容易卡住的一步,因为它要现场编译,而编译过程对内存和架构参数都很敏感。
# MAX_JOBS 控制并发编译进程数,机器内存小就调低,否则会被 OOM Killer 干掉 # TORCH_CUDA_ARCH_LIST 指定目标架构,8.0 对应 A100,8.6 对应 3090 MAX_JOBS=4 TORCH_CUDA_ARCH_LIST="8.0" pip install flash-attn==2.3.3MAX_JOBS=4不是越多越好,每个编译进程都会吃掉几个 GB 内存,16GB 的机器上开到 8 基本必然被内核杀掉,报错还特别像“编译出错”,实际是Killed。TORCH_CUDA_ARCH_LIST只填实际使用的架构,不要图省事写一长串,每个架构都要单独编一份,时间翻好几倍。如果编译时间实在受不了,可以用官方提供的预编译 wheel,但必须先对齐 torch 版本和 CUDA 小版本,差一个小版本就会在 import 时抛undefined symbol。
3. 显存告急:CUDA out of memory 反复出现时的量化与卸载取舍
到了这一层,环境已经是对的,报错也是“真实”的——显存确实不够。但“不够”分三种:权重本身放不下、权重放得下但 KV Cache 撑爆、权重和缓存都够但碎片化导致分配失败。这三种的解法完全不同,混着试就是浪费时间。
3.1 先算再跑:按参数量估显存的三个公式
估算比试错快得多。按 7B、32B 这两个常见规格算一遍,心里就有数了。
| 模型规模 | FP16 权重 | INT8 权重 | NF4 量化权重 | 24GB 卡可否运行 |
|---|---|---|---|---|
| 7B | 约 8.4GB | 约 4.2GB | 约 2.1GB | 可以,留足 KV Cache |
| 13B | 约 15.6GB | 约 7.8GB | 约 3.9GB | 可以,输入长度受限 |
| 32B | 约 38.4GB | 约 19.2GB | 约 9.6GB | 只能走 4-bit 加卸载 |
| 70B | 约 84GB | 约 42GB | 约 21GB | 单卡不行,需多卡或 CPU 卸载 |
第一行公式直接来自每个模型规模的权重需求:FP16 下每 10 亿参数约 1.2GB,这个系数是“参数量 × 2 字节 + 少量对齐开销”的工程近似值,按它算不会差太多。第二块是 KV Cache,它和上下文长度、并发数线性相关,公式是2 × 层数 × 头数 × head_dim × 序列长度 × batch × 字节数,实际部署里它经常比权重还占地方——权重 9.6GB 的 32B 量化模型,跑 8K 上下文加 batch 8,KV Cache 很容易再吃十几 GB。第三块是激活值和临时缓冲,一般按权重的 10% 到 20% 预留。
还有一条常被忽略的:主机内存建议在显存的 2 倍以上,因为device_map="auto"会先把权重完整读进内存再往卡上搬,内存不够时表现同样是 OOM,但报错在 CPU 侧,看着对不上号。
3.2 4-bit 量化四个参数:省下来的显存是从哪抠出来的
from transformers import BitsAndBytesConfig bnb_config = BitsAndBytesConfig( load_in_4bit=True, # 权重按 4-bit 存储,显存直接降到 FP16 的四分之一 bnb_4bit_use_double_quant=True, # 对量化常数再量化一次,平均每个参数再省 0.4 bit bnb_4bit_quant_type="nf4", # 正态分布友好的分位量化,比 fp4 精度略好 bnb_4bit_compute_dtype=torch.bfloat16 # 计算时反量化到 bf16,注意别写 float16 ) model = AutoModelForCausalLM.from_pretrained( "deepseek-ai/deepseek-32b", quantization_config=bnb_config, device_map="auto" )四个参数里最容易被误改的就是compute_dtype。写float16时,反量化后的数值范围在某些层会溢出,症状不是崩溃,而是输出开始重复、胡言乱语,或者直接抛probability tensor contains either inf, nan or element < 0。A100 和 30 系之后对bfloat16支持良好,优先用它。
device_map="auto"也不是万能的:它会按显存余量把层切分到多卡甚至 CPU 上,切分粒度是层,层大小不均匀时会出现某张卡剩 300MB 放不下一层,然后整层被扔到 CPU,推理速度断崖式下跌。这种性能下降没有明显报错,表现为“能跑但很慢”,很多人会误以为是模型本身慢。
3.3 OOM 之后:gradient_checkpointing 与分级卸载
显存还是超了,就得上卸载。先用最轻的一档,不行再加。
# 激活检查点:用计算换显存,训练时效果明显,推理时收益有限 model.gradient_checkpointing_enable()# 分级卸载:参数留在显存,优化器状态和部分参数卸载到 CPU / NVMe accelerate launch --config_file accelerate_config.yaml \ --num_processes 4 \ --mixed_precision bf16 \ --gradient_accumulation_steps 8 \ --offload_param_device="nvme" \ --offload_optimizer_device="cpu" \ train.pygradient_checkpointing_enable()的原理是不保存中间激活值,反向传播时重算一遍,省显存但多花三成左右的计算时间。它在训练脚本里收益很大,在纯推理脚本里基本没用,因为推理没有反向传播——见过不少人推理时报 OOM 也加这一行,然后困惑为什么毫无变化。
--offload_param_device="nvme"意味着每次前向都要从 NVMe 读权重,延迟会从几十毫秒涨到几百毫秒,只有在“必须跑起来”比“跑得快”更重要的场景才用。--gradient_accumulation_steps是把大 batch 拆成小 batch 累积,它省的是激活显存,不省权重显存。参数配合不对时会出现一种奇怪的状况:显存占用看起来降下来了,但吞吐掉到原来的十分之一。
3.4 device_map 与 use_memory_efficient 的两个隐藏前提
# 内存映射加载:权重直接从磁盘映射到虚拟内存,减少峰值占用 model = AutoModelForCausalLM.from_pretrained( "deepseek-ai/deepseek-32b", use_memory_efficient=True, # 部分版本不认这个关键字,会抛 TypeError device_map="auto", low_cpu_mem_usage=True # 更通用的写法,按需加载分片,避免整份权重进内存 )这里有两个容易踩的前提。第一,不同 transformers 版本对use_memory_efficient的支持不一致,某些版本直接把未知关键字丢给底层而抛TypeError: from_pretrained() got an unexpected keyword argument,稳妥的做法是用low_cpu_mem_usage=True,它在多个版本里都可用,效果接近。第二,device_map="auto"需要accelerate已安装,没装时会退化成单卡加载,然后瞬间 OOM,报错信息里完全看不出是 accelerate 的问题。
如果显存始终在边界上晃,还有一个成本更低的办法:把加载精度从 FP16 换成 BF16,权重占用一样,但数值范围更大,某些层不至于溢出,从而可以少开一层卸载。
4. 多卡不认账:NCCL 报 unhandled system error 与 world_size 对不上
单卡跑通之后多卡往往是新的难关,而且报错信息极其不友好。NCCL 的错误基本都不告诉你“哪张卡、哪一步、为什么”,只会给一句unhandled system error或者Connection refused,然后整个进程组挂死在原地。定位这类问题的关键是分清:是找不到彼此,还是找到了但没法传数据。
4.1 NCCL 三类报错:分别指向网络、端口与架构
| 报错片段 | 实际含义 | 首查项 |
|---|---|---|
unhandled system error | 通信通道建立后传输失败 | IB 设备、网卡名、P2P 支持 |
Connection refused | 找不到 master 节点 | init_method的 IP 与端口、防火墙 |
no kernel image is available | 架构不匹配 | TORCH_CUDA_ARCH_LIST、GPU 是否同型号 |
unhandled cuda error | 某张卡本身有问题 | 逐卡跑单机测试隔离 |
Connection refused几乎都是地址问题:tcp://192.168.1.100:23456里的 IP 必须是所有节点都能访问到的那个网段,多网卡的机器上经常填成了只有本机可达的地址。端口被占用也会报这个,换一个高位端口最快。
unhandled system error复杂一些,先看网卡。机器上同时有业务网卡和高速网卡时,NCCL 可能选错:
# 指定 NCCL 走哪张网卡,先用 ip a 确认名字 export NCCL_SOCKET_IFNAME=eth0 # 没有 InfiniBand 环境时必须显式关掉,否则 NCCL 会去找 IB 设备然后超时 export NCCL_IB_DISABLE=1 # 打开调试日志,出问题时能看清它选了哪条链路 export NCCL_DEBUG=INFO export NCCL_DEBUG_SUBSYS=ALLNCCL_DEBUG=INFO是这类问题的关键,日志里会明确写出NET/IB : No device found或者Using network interface,一眼就能看出走的是不是你想要的那条路。生产环境如果用 InfiniBand HDR 200Gb,就不要再设NCCL_IB_DISABLE=1,那等于把高速链路关掉退回千兆,延迟指标直接崩。
4.2 deepseek.yaml 逐字段:backend、init_method、world_size、rank
# deepseek.yaml 分布式配置片段 distributed: backend: nccl # GPU 间通信用 nccl,cpu 用 gloo init_method: tcp://192.168.1.100:23456 # 所有节点统一指向 rank 0 的地址 world_size: 4 # 总进程数 = 节点数 × 每节点卡数,不是节点数 rank: 0 # 0 到 world_size-1,每个进程唯一四个字段里错得最多的是world_size。4 台机器每台 8 卡,world_size应该是 32 而不是 4。填小了会静默丢卡,训练照跑但只用了四分之一的算力;填大了会卡在init_process_group等到超时,报一句没有信息量的Timeout。
init_method必须所有节点一致,而且只能是 rank 0 那台机器的地址。rank必须互不相同,多机启动脚本里通常是rank = 节点序号 × 每节点卡数 + 卡序号,手写时最容易两台机器都写成 0,然后报端口冲突。
backend选nccl的前提是所有参与进程都能看到 GPU;如果有一个进程跑在只有 CPU 的节点上,整个组会直接失败,这时候要拆成两个组或者干脆用gloo。
4.3 torchrun 与 accelerate 的参数对应关系
# 节点 0 上启动,master_addr 填本机内网地址 torchrun \ --nnodes=2 \ --nproc_per_node=8 \ --node_rank=0 \ --master_addr=192.168.1.100 \ --master_port=23456 \ train.py--nnodes是节点数,--nproc_per_node是每节点进程数(一般等于卡数),这两个乘起来必须等于 yaml 里的world_size。--node_rank在每台机器上不同,0、1、2 依次递增;--master_addr和--master_port必须完全相同。
用accelerate launch时这些参数会被配置文件接管,--num_processes对应总进程数,多机场景下要配--num_machines和--machine_rank。两套工具混用是常见事故:yaml 里写 4,命令行传 8,进程组能建起来,但梯度同步的步调不一致,表现为 loss 曲线乱跳或者某些卡空转,日志里什么都看不出来。
4.4 网卡与 IB:什么时候该动环境变量
不是所有unhandled system error都要调网卡。判断依据很简单:单机多卡能跑通、多机才报错,那就是网络层;单机多卡就报错,先怀疑 P2P 和架构。
单机排查用这个顺序:先CUDA_VISIBLE_DEVICES=0和CUDA_VISIBLE_DEVICES=1分别跑一遍,确认每张卡单独都没问题;再把NCCL_P2P_DISABLE=1打开试一次,如果这样就正常了,说明卡间的 P2P 通道有问题,常见于跨 PCIe Switch 或者虚拟化环境;最后看nvidia-smi topo -m的输出,SYS表示跨 NUMA,PHB表示同桥,两者带宽差好几倍。
延迟指标上也有对应关系。生产环境给的参考是输入 512 tokens 时推理延迟低于 200ms、批量优化后吞吐超过 1000 requests/sec,这个量级如果没有 NVLink 和 IB 基本达不到。如果实测延迟是目标值的五到十倍,先别优化模型,先看nvidia-smi topo -m和 NCCL 走的是不是高速链路——很多时候问题就出在这里。
5. 避坑清单:部署环境里最容易翻车的五个细节
这几条都是排查过程中反复出现的,单独看不严重,混在一起就会让人误判方向。每条按现象、原因、解决写,照着对号入座。
5.1 现象:nvidia-smi 显示空闲,推理仍然 OOM
原因通常是三种叠加:一是别的进程还占着显存没释放,nvidia-smi默认显示的是总量不是可用量,要加--query-gpu=memory.used,memory.free才看得清;二是device_map="auto"已经在 CPU 上预占了大量内存,加载阶段峰值远高于稳定态;三是显存碎片,反复加载卸载之后虽然总量够,但没有一块连续的能放下一个完整的层。
解决上,先nvidia-smi --query-compute-apps=pid,used_memory --format=csv找出残留进程,确认无用后 kill;再在加载前加torch.cuda.empty_cache();碎片问题只能靠重启进程,同一个进程里反复换模型几乎必然碎片化。
5.2 现象:第一个 token 要等几十秒,之后恢复正常速度
原因多半在内存和磁盘之间来回搬运。vm.swappiness保持默认 60 时,权重刚加载完,内存压力大,内核把一部分页换出到 swap;第一个请求触发换入,这个等待时间就是那几十秒。另一种情况是模型放在机械盘或者网络存储上,首次读取走了冷缓存。
解决办法是把权重放 NVMe,把vm.swappiness调到 10,并且在服务启动后先发一个预热请求把所有权重页拉进内存。生产环境里预热这一步不要省,否则第一个真实用户就是那个倒霉的压测工具。
5.3 现象:Windows 下 WSL2 只认到一半显存,或者干脆走 CPU
原因是 WSL2 的显存分配策略和驱动归属。WSL2 不是独占 GPU,它从主机拿到的是一个上限,默认约为主机显存的一半多一点;同时 Linux 侧不能再装驱动,装了会和 Windows 侧的驱动冲突,nvidia-smi能跑但 torch 拿不到设备。
解决:卸载 WSL 内所有 NVIDIA 驱动包,只保留 Windows 侧 535.54.03 以上版本;确认 WSL2 是 Ubuntu 20.04 且已装 DirectX 12 Ultimate 运行时;显存不够就把模型量化,或者干脆换到原生 Linux 上跑,别在 WSL 里硬扛 32B。
5.4 现象:量化后输出重复、乱码、答非所问
原因基本锁定在计算精度上。bnb_4bit_compute_dtype写成float16,反量化后的激活值溢出,或者bnb_4bit_quant_type用了fp4而模型权重分布偏正态,量化误差被放大。也有一种情况是bnb_4bit_use_double_quant在某些层上叠加误差,长上下文时尤其明显。
解决:把compute_dtype改成bfloat16,quant_type保持nf4,double_quant先关掉做对照实验。改完还是乱,就把device_map从auto改成显式指定单卡,排除跨设备搬运导致的精度问题。
5.5 现象:DCGM 容器起来了,9400 端口拉不到指标
原因是容器没有真正拿到 GPU,或者 Prometheus 的抓取配置写错了目标。docker run --gpus all在没装nvidia-container-toolkit的机器上不会报错,只是静默地以 CPU 模式启动,DCGM 没有设备可采,端口开着但没有数据。
解决:先docker exec进容器跑nvidia-smi确认能看到卡;再看dcgm-exporter日志里有没有Failed to initialize;最后核对 Prometheus 的targets是不是localhost:9400或者 exporter 的实际地址。指标采不到的时候别急着调 Grafana,先确认数据源本身有值。
6. 快速定位:把排查顺序固化成一条从驱动到显存的链路
真出问题的时候最怕的是东一榔头西一棒子。我后来固定成一条自上而下的链路,跳步很容易误判。
| 顺序 | 检查层 | 一条命令 | 判据 |
|---|---|---|---|
| 1 | 驱动 | nvidia-smi | 能列出卡和驱动版本 |
| 2 | 内核模块 | lsmod | grep nvidia | 至少三行输出 |
| 3 | toolkit | nvcc -V | 与驱动支持的版本不冲突 |
| 4 | 框架 | torch.cuda.is_available() | True 且torch.version.cuda非 None |
| 5 | 架构 | torch.cuda.get_device_capability(0) | 与编译时的 ARCH_LIST 一致 |
| 6 | 显存 | nvidia-smi --query-gpu=memory.used,memory.free --format=csv | 空闲量大于权重加缓存估算 |
| 7 | 网络 | NCCL_DEBUG=INFO看选路 | 走的是预期的那张网卡 |
前四层任何一层断了,后面的排查都没有意义,因为此时所有现象都是前一层引起的假象。我见过在is_available()返回 False 的情况下去调 batch size,调了一下午。
出问题时先抓一份现场快照,比事后回想可靠得多:
#!/usr/bin/env bash # collect_env.sh —— 在出问题的机器上直接跑,把关键状态一次性落盘 set -u OUT="env_$(date +%Y%m%d_%H%M%S).log" { echo "== driver =="; nvidia-smi || true echo "== kernel =="; lsmod | grep -E '^nvidia' || true echo "== toolkit =="; nvcc -V 2>/dev/null || echo "no nvcc" echo "== memory =="; free -g echo "== swap =="; cat /proc/sys/vm/swappiness echo "== torch =="; python -c "import torch;print(torch.__version__, torch.version.cuda, torch.cuda.is_available())" 2>&1 echo "== topo =="; nvidia-smi topo -m || true } | tee "$OUT"set -u保证变量未定义时直接退出而不是带着空值继续跑,|| true让单条命令失败不影响整份快照,tee一边输出一边存盘。这份日志里最有价值的是nvidia-smi的头部和torch那一行,多数问题这两处就能看出来。
验证链路还有一个顺序原则:从 CPU 模式开始,到单 GPU,再到多卡,最后才是集群。CPU 模式能跑通说明代码和权重没问题,单 GPU 能跑通说明环境没问题,多卡能跑通说明通信没问题。跳过任何一级,出了问题你都分不清是模型、环境还是网络。
从那以后我每次换机器或者升级驱动,都强制走一遍这条七层链路,哪怕只是改了swappiness这种小参数。血泪经验是:所有“莫名其妙就好了”的情况,本质都是某次你没注意到的环境变更把问题藏起来了,下次它还会以另一副面孔回来。希望帮到你。
本文还有配套的精品资源,点击获取