1. 先理解单精度/半精度,再谈选卡上云
1.1 你为什么会看到两个算力数字
很多朋友第一次打开GPU规格表或者云端算力平台的选卡页面时,都会遇到同一个困惑:同一张卡,为什么有的地方写“FP32算力多少T”,有的地方又写“FP16算力多少T”?这两个数字差了好几倍,到底该看哪个?这个问题的本质,就是单精度和半精度的区别。如果你不搞懂它,选卡基本靠猜,跑起来之后发现问题又得重来,浪费的时间和算力费用远比预想的多。
这篇文章我会从底层把单精度(FP32)和半精度(FP16)讲清楚,包括它们在计算机里是怎么存储的、为什么性能差那么多、深度学习为什么喜欢半精度,然后再带着你走一遍云端GPU算力平台的实操流程——从选卡、开实例、传代码、跑训练到看账单。为了不把文章变成某个具体商业产品的广告,我会用一个代称“智算云”来代表这类按小时付费、即租即用的GPU算力平台,你在自己常用的任何同类平台上都能对应上同样的操作逻辑。
1.2 这篇文章能帮你解决什么
什么人最适合读这篇文章?我觉得有三类。第一类是刚入门深度学习的学生和开发者,租了一张卡却不知道FP32、FP16、TFLOPS这些参数意味着什么;第二类是中小团队的技术负责人,需要在预算有限的情况下决定到底租A100还是租4070;第三类是已经踩过坑的人,比如训练时显存突然不够、用了半精度之后Loss变成NaN、GPU利用率一直上不去,想系统地把这些坑一次填平。
如果你属于其中任何一类,这篇文章都会比官方说明书更有用,因为我不仅讲“是什么”,还会讲“为什么”。比如FP16训练时为什么要做Loss Scaling,为什么云平台上一张卡每小时几块钱但实际成本可能翻倍。这些细节决定了你是在高效使用算力,还是在烧钱买教训。
2. 浮点数的底层逻辑:精度与范围是一场交易
2.1 二进制浮点数的位结构:FP32与FP16到底差在哪
计算机里的小数不是像我们在纸上写“3.14159”那样直接存的,而是用二进制的科学计数法来存。一个浮点数由三部分组成:符号位、指数位、尾数位。符号位决定正负,指数位决定数值范围,尾数位决定精度。就这么点东西,不同精度的格式只是给这三个部分分配了不同数量的比特。
FP32是单精度,32位里分成1位符号、8位指数、23位尾数。FP16是半精度,16位里分成1位符号、5位指数、10位尾数。指数位从8位缩到5位,意味着FP16能表示的最大值从约3.4乘以10的38次方,缩到了只有65536附近;尾数位从23位缩到10位,意味着小数位数变少,精度明显下降。所以FP16不是“省了一半内存但是效果一样”,它是在牺牲范围和精度的情况下换来了更小的存储和更快的计算。
我用一个生活类比来解释。FP32像一把标准软尺,最长能量到几十米,最小刻度能到毫米级;FP16像一把只有60厘米的小直尺,最小刻度只有5毫米。量桌面长度,软尺和小直尺都能量,精度差不多;但让你量一座桥的长度,小直尺直接超量程了。深度学习中很多数值恰好就落在FP16的有限范围内,所以能用,但绝对不能乱用。
下表是几种常见浮点格式的位结构对比:
| 格式 | 符号位 | 指数位 | 尾数位 | 大致数量级范围 | 相对精度 |
|---|---|---|---|---|---|
| FP64(双精度) | 1 | 11 | 52 | 10^-308 到 10^308 | 极高 |
| FP32(单精度) | 1 | 8 | 23 | 10^-38 到 10^38 | 高 |
| FP16(半精度) | 1 | 5 | 10 | 10^-5 到 10^5 | 低 |
| BF16(脑浮点) | 1 | 8 | 7 | 10^-38 到 10^38 | 极低 |
注意BF16这个格式很有意思,它的指数位和FP32一样多,所以取值范围和FP32几乎一致,但尾数位只有7位,精度比FP16还差。它专门用于深度学习场景,因为神经网络训练时最怕梯度下溢变成0,而不是怕精度不够。GPU硬件支持BF16之后,很多大模型训练都开始从FP16转向BF16,这是一个非常重要的趋势。
2.2 精度误差的直观感受:0.1加0.2不等于0.3
你可能觉得浮点数范围小一点没什么,反正日常计算也没那么极端。但浮点数还有一个隐性坑:它无法精确表示绝大多数十进制小数。用FP32算0.1加0.2,结果是一个接近0.30000000000000004的数;用FP16算,误差会更大。这个道理很多人在编程课里听过,但真正意识到严重性是在跑深度学习的时候。
我在某个云平台上做过一次小实验:用FP32和FP16分别累加100万个0.1,FP32的累计误差在几万分之一级别,FP16的累计误差已经大到会直接影响结果。为什么深度学习中没出大问题?因为神经网络不是在做连续求和,而是在做大量带随机性的矩阵乘法和梯度更新。单个数值精度的损失会被大规模参数的平均效应吸收,优化器本身对误差也有一定的容忍度。所以这成了一场交易:用精度换速度,多数时候划算。
3. 单精度与半精度:从原理到优劣
3.1 单精度:可靠的全能选手
FP32长期是GPU计算的主流格式。传统图形渲染、物理仿真、数值计算,甚至早期的CUDA编程教程,绝大多数计算都默认用单精度完成。CPU里的AVX指令集、GPU里的CUDA核心,对FP32的支持是最完善的,所有现代显卡都会完整支持FP32计算,而且运行时性能相对可预测。
我经常跟朋友说,FP32的最大优势不是“快”,而是“不需要动脑子”。你用FP32训练一个模型,基本不用担心数值范围溢出、梯度消失这种低精度引起的幺蛾子。它就像装修时用标准尺寸的螺栓,虽然不一定最省材料,但到处都能买到,拧上去也不会出问题。对于刚上手的同学,我建议第一版代码用FP32跑通,再考虑要不要切到混合精度,你会省掉很多玄学般的报错排查时间。
在算力指标上,FP32性能也被当作GPU定位的分水岭。同样一颗GPU核心,FP32算力通常是FP64算力的两倍左右甚至更高,而FP16算力又会是FP32的两倍往上。理解这个递进关系,你就知道为什么规格表里三个数字的差距会那么大。
3.2 半精度:为速度和显存而生的激进派
FP16的原理并不复杂,但它在现代AI芯片里能大显身手,核心原因是硬件厂商专门为低精度计算设计了专用加速电路。以AI训练和推理里最常见的矩阵乘法为例,传统CUDA核心做一次FP32乘加运算需要完整的32位数据通路;如果改用FP16,同样的晶体管面积和功耗预算下,可以塞入两倍以上的计算单元。这就是为什么不少GPU的FP16峰值算力可以做到FP32的两倍以上,如果是专用的Tensor Core,差距能有十倍以上。
举个具体的例子:某款面向数据中心的GPU,标称FP32算力约19.5 T FLOPS,而FP16的Tensor Core算力可以达到156 T FLOPS;另一款新一点的旗舰服务器卡,FP32算力约67 T FLOPS,FP16 Tensor Core算力能到接近990 T FLOPS。绝大多数人看到这个数字都会倒吸一口凉气——原来是同一个芯片,低精度模式下能爆发出这么大的能量。
但是FP16不是万能的。它的最大可表示数值是65504,如果你在训练里用FP16表示一些较大的中间值,比如大模型的LayerNorm输出,就有溢出的风险。另外,小于约6乘以10的负8次方的数会被当成0处理,这在梯度很小的时候是致命的,因为梯度一旦变成0,参数就彻底不更新了。这些坑不是理论问题,而是你动手训练时一定会撞上的实际问题。
3.3 混合精度训练:把两种精度的优势都用上
既然FP16又要溢出又要下溢,为什么还有那么多大模型用它训练?答案是混合精度训练。这套方案的核心逻辑很简单:模型权重的主副本保持在FP32,真正参与前向和反向传播的复制品用FP16,梯度更新在FP32上完成。这样既利用了FP16在矩阵运算上的高速度,又避免了误差在成千上万次迭代中累积爆炸。
另一个关键机制是Loss Scaling,也就是把Loss放大若干倍再做反向传播。因为反向传播里的梯度会越传越小,如果在FP16里直接计算,一部分梯度会下溢成0。先放大Loss,反向传播出来的梯度也相应放大,等更新权重之前再把它除回去。现代PyTorch里的GradScaler会自动动态调整缩放倍数,不需要你手动改代码,但从原理上理解这个机制,能帮你更快定位NaN问题。
我自己的经验是,混合精度训练在大部分CV和NLP任务里能带来1.5到3倍的整体提速,显存占用也能降一半左右。这已经非常可观了。代价是你需要额外处理少数数值敏感的运算,比如Softmax和LayerNorm,通常会保留在FP32下计算。PyTorch的autocast会自动做这些细粒度处理,所以你大部分时候只需要几行代码就能开启混合精度。
from torch.cuda.amp import autocast, GradScaler scaler = GradScaler() for data, target in dataloader: data, target = data.cuda(), target.cuda() optimizer.zero_grad() with autocast(): output = model(data) loss = loss_fn(output, target) scaler.scale(loss).backward() scaler.step(optimizer) scaler.update()这段代码是我在云平台实例里反复使用的最简模板。新手可能觉得多出来的两行代码无所谓,但等你在FP16训练中遇到Loss变成NaN那一刻,你就会感谢GradScaler的存在。
4. 算力数字怎么看:TFLOPS、显存与实测
4.1 FLOPs与FLOPS别再傻傻分不清
算力指标里最容易混淆的是FLOPs和FLOPS。FLOPs是Floating Point Operations的缩写,单位里的s表示“复数”,指的是总共做了多少次浮点运算。FLOPS是Floating Point Operations per Second,表示每秒运算次数。一个描述总量,一个描述速度,差一个字母,含义完全不同。你在GPU规格表里看到的“TFLOPS”是后者,而这个数字越高,意味着每秒能完成的浮点运算越多。
T代表Tera,也就是10的12次方。1 TFLOPS等于每秒一万亿次浮点运算。一个训练任务到底消耗多少FLOPs,是可以手工估算的,比如矩阵乘法C等于A乘以B,其中A的尺寸是M乘K,B的尺寸是K乘N,那么总计算量大约是2乘M乘K乘N次浮点运算。你用总FLOPs除以GPU的FLOPS,就能粗略估算出单卡跑完这个矩阵要多久。当然这只是理论值,实际还要乘以一个利用率系数。
我反复跟别人强调一个概念:峰值算力不等于有效算力。真实训练中计算单元不可能每时每刻满负荷工作,因为数据要从显存搬到计算单元,权重要被频繁读取,GPU之间还要同步通信。业界常说的MFU,也就是模型浮点运算利用率,能到50%就已经算优化得很好的大型训练任务了。很多个人项目跑下来MFU可能只有10%到20%,这时候你去追更高的算力卡,其实收益不大,不如先优化数据加载和模型结构。
4.2 一张表看懂主流GPU算力规格
这里我用几张常见GPU的公开规格做一个横向对照,方便你建立直觉。注意不同厂商的数据可能有细微差别,比如是否开启稀疏化计算会影响Tensor Core的标称值,我这里都采用业界常说的非稀疏数据:
| GPU型号 | 显存 | FP32算力 | FP16 Tensor Core算力 | 显存带宽 | 典型场景 |
|---|---|---|---|---|---|
| 某数据中心卡A | 80GB HBM2e | 约19.5 TFLOPS | 约156 TFLOPS | 约2TB/s | 大模型训练、科学计算 |
| 某旗舰服务器卡B | 80GB HBM3 | 约67 TFLOPS | 约990 TFLOPS | 约3.3TB/s | 超大模型训练、大规模推理 |
| 某消费级旗舰卡C | 24GB GDDR6X | 约82.6 TFLOPS | 约330 TFLOPS | 约1TB/s | 中小模型微调、本地推理、出图 |
看到这里你可能会奇怪,消费级旗舰卡的FP32算力反而比数据中心卡A还高,为什么大模型训练不用它?因为大模型训练不仅要看算力,还要看显存容量和显存带宽。24GB显存连一个7B模型的FP16权重都放不下,更别提优化器和梯度了。数据中心卡A的80GB显存才是它值钱的地方,算力反而是次要因素。所以选卡的第一原则永远是:先看你的模型放不放得下,再看速度多快,最后才是价格。
4.3 用nvidia-smi和小脚本实测算力
规格表上的数字是理论峰值,真实环境什么样我们要亲手验证。登录进一台GPU实例后,第一件事永远是运行下面这个命令:
nvidia-smi这个命令会告诉你驱动版本、CUDA版本、GPU型号、显存总量和当前占用、温度、功耗和利用率。如果你看到显存占用率一直是100%,利用率却只有个位数,说明你的程序可能把大量数据堆在显存里但计算没跟上,典型的数据加载或算子低效问题。
不过nvidia-smi只能看整体状态,要真正对比FP32和FP16的算力差异,还得自己写一段代码跑跑。下面这个脚本是我常用的小实验,在PyTorch里做同一尺寸的矩阵乘法,分别用FP32和FP16跑十次取平均:
import torch import time size = 4096 a = torch.randn(size, size, device='cuda') b = torch.randn(size, size, device='cuda') for dtype in [torch.float32, torch.float16]: x = a.to(dtype) y = b.to(dtype) if dtype == torch.float16: # 开启Tensor Core路径 torch.backends.cuda.matmul.allow_fp16_reduced_precision_reduction = True torch.cuda.synchronize() t0 = time.time() for _ in range(20): c = x @ y torch.cuda.synchronize() avg = (time.time() - t0) / 20 print(f"{dtype}: 单次耗时 {avg*1000:.2f} ms")我实测下来,同一张数据中心卡上,FP16矩阵乘法的耗时通常是FP32的40%到60%,如果任务里涉及Tensor Core的线性层,优势会更大。不要只看这个数字就觉得FP16永远快,当你的模型里有很多小矩阵运算或非矩阵算子时,FP16的优势会被明显稀释。根据任务实测,比任何规格表都靠谱。
5. 云端算力平台实操:选卡、开实例、跑训练
5.1 为什么把GPU放上云
自己买一张GPU卡放在办公室,听起来很硬核,但实际运维起来很麻烦。卡坏了要保修,机房温度要控制,电源要够大,多人使用还得做资源隔离。更现实的问题是,大部分项目只有一个冲刺阶段需要大算力,其余时间都在写代码和调参,让一张几万块的卡长期闲置在工位上,性价比很低。
云端算力平台的思路就是把你对算力的需求变成一个按小时计费的商品。你在网页上选卡、选镜像、点创建,几分钟后就能拿到一台带GPU的Linux服务器,用完关机释放就行。不用管硬件故障,不用管机房环境,甚至驱动都可以选择预装好的镜像。这种模式特别适合训练任务集中爆发、周期短、并发大的场景,比如比赛冲刺、毕设最后两周、公司上线前压测。
我之所以在文章里反复提到“智算云”这个代称,是因为这类平台的操作逻辑高度一致:注册、充值、创建实例、连接、跑任务、释放实例。你只要熟悉其中一个,换到另一个平台几乎零成本。差异最多体现在镜像市场的数据集覆盖面、是否提供文件存储挂载、网络带宽计费方式这些细节上。
5.2 选卡决策:先估显存,再看精度与预算
选卡是一门预算学。第一步不是看哪张卡算力高,而是估算你的模型需要多大显存。权重显存有公式可算:参数量乘以每个参数占用的字节数除以8除以1024的三次方。7B模型用FP16存储,权重就要大约14GB。但训练不止要存权重,还要存梯度、优化器状态(比如AdamW要存两个动量项)和激活值,加起来往往是权重的三到五倍。这意味着一块24GB的消费级卡想全参训练7B模型基本不可能,但做LoRA或QLoRA微调就绰绰有余。
我把常见的选卡场景整理成下面这张表,你直接对号入座:
| 任务类型 | 推荐卡型 | 备注 |
|---|---|---|
| 课程作业、小模型实验 | 入门级消费卡,显存8-16GB | 预算优先 |
| 中型CV模型训练 / SD微调 | 消费级旗舰卡,显存24GB | 注意散热和功耗 |
| 7B-13B模型LoRA微调 | 数据中心卡,显存40-80GB | 混合精度必开 |
| 7B以上全参训练 | 多卡数据中心卡集群 | 重点看通信带宽 |
| 大模型批量推理 | 具备高吞吐的推理卡 | 主要看显存带宽 |
在算力精度方面我也多说一句:如果你打算用FP16或BF16做混合精度训练,那选卡时不要只看FP32数字,要看Tensor Core的FP16指标,这决定了你混合精度训练的天花板。如果只是做推理,很多场景甚至可以用INT8量化,没必要为FP32算力付高价。
5.3 从创建实例到跑通训练:完整操作流
接下来我完整走一遍在智算云平台上的实操流程,你把这个流程搬到你自己的平台里,每一步都能对应上。
第一步,创建实例。进入控制台后选择GPU实例,挑选卡型、镜像、数据盘大小。我建议镜像直接选预装了最新CUDA和PyTorch的深度学习镜像,比如PyTorch 2.1加CUDA 12.1的版本组合。数据盘按需分配,如果只是跑实验,100GB到500GB之间够用。
第二步,拿到连接信息。创建完成后,平台会给你一个公网IP、SSH端口和初始密码,有的平台还支持密钥对登录。为了安全,我都是把公钥直接配到实例里,然后只用密钥登录。
ssh -p 22222 root@实例IP第三步,确认GPU环境。登录后第一件事是用nvidia-smi看显卡状态,然后用一段Python确认PyTorch能看到卡:
python -c "import torch; print(torch.__version__, torch.cuda.is_available(), torch.cuda.get_device_name(0))"如果输出里cuda.is_available为False,大概率是镜像里的PyTorch版本和驱动不匹配,直接换一个官方镜像更省事。不要自己在生产实例上装驱动,那是纯浪费时间。
第四步,上传代码和数据。最常见的上传方式scp和rsync:
scp -P 22222 ./train.py root@实例IP:/root/ rsync -avP -e "ssh -p 22222" ./dataset/ root@实例IP:/root/dataset/小文件用scp很方便,大目录一定要用rsync,它支持断点续传,传一半断了不用重新传。另外,如果你的数据集有几GB且要在多台机器复用,不如先传到对象存储,再从实例拉取,比直接传更快。
第五步,编写并运行训练脚本。下面是一个能在任何GPU实例上跑通的混合精度训练最小示例,我习惯先用这个模板验证环境,再替换成真实模型:
import torch import torch.nn as nn from torch.utils.data import DataLoader, TensorDataset from torch.cuda.amp import autocast, GradScaler torch.manual_seed(0) class MLP(nn.Module): def __init__(self): super().__init__() self.net = nn.Sequential( nn.Linear(256, 512), nn.ReLU(), nn.Linear(512, 256), nn.ReLU(), nn.Linear(256, 10) ) def forward(self, x): return self.net(x) model = MLP().cuda() optimizer = torch.optim.AdamW(model.parameters(), lr=1e-3) loss_fn = nn.CrossEntropyLoss() scaler = GradScaler() x = torch.randn(10000, 256) y = torch.randint(0, 10, (10000,)) dataloader = DataLoader(TensorDataset(x, y), batch_size=256, shuffle=True) for epoch in range(3): running_loss = 0.0 for data, target in dataloader: data, target = data.cuda(), target.cuda() optimizer.zero_grad() with autocast(): output = model(data) loss = loss_fn(output, target) scaler.scale(loss).backward() scaler.step(optimizer) scaler.update() running_loss += loss.item() print(f"epoch {epoch}: loss {running_loss / len(dataloader):.4f}")训练开始后,用下面命令实时监控GPU状态:
nvidia-smi -l 1每秒钟刷新一次,你可以看到显存占用、GPU利用率、温度和功耗曲线。如果GPU利用率长期低于80%,说明瓶颈不在GPU算力,大概率是数据加载或代码里等待同步的问题,要提高DataLoader的num_workers、开启pin_memory,或者放大batch size。
第六步,保存结果并释放实例。训练得到的模型文件要第一时间下载到本地或者传到对象存储,然后回到控制台释放实例。记住,实例释放后系统盘数据会丢掉,凡是不能丢的东西都不要放在系统盘下。
5.4 成本控制:别让账单吓到你
云端算力的计费看着简单,实际坑很多。大部分平台有两种收费方式:按量付费和包时包月。按量付费适合临时跑实验,小时单价高;包时适合连续多天跑任务,单价低但绑定期限。如果只是每天跑几小时,按量付费更划算。
我见过最典型的账单翻车案例是忘了关机。一个实例开了之后连续跑了一周,跑完一个模型就闲置在那,每天还在扣钱。很多平台有定时关机功能,你可以在创建实例时设置最长续费时长,比如“最多允许实例运行12小时”,到了时间自动关机,能很大程度避免这类损失。
还有一些平台提供所谓“抢占式实例”或“竞价实例”,价格可能只有按量付费的三分之一甚至更低,但它们随时可能被回收,适合做那种中断了可以重新跑的离线任务,不适合给线上服务用。训练任务只要做好断点续训,也就是每个epoch存一次checkpoint,用抢占式实例能省下非常可观的成本。
6. 常见问题与避坑指南
6.1 GPU利用率上不去
很多人跑训练时发现GPU利用率只有20%到40%,第一反应是卡不行,但绝大多数情况是CPU或磁盘拖了后腿。PyTorch的DataLoader默认num_workers为0,意味着数据读取在主进程里做,每读一个batch,GPU就要干等一次。你可以先打开一个终端执行htop,另一个终端执行nvidia-smi,同时观察。如果CPU跑满而GPU有空闲,立刻增大num_workers并开启pin_memory。
dataloader = DataLoader(dataset, batch_size=64, shuffle=True, num_workers=8, pin_memory=True)输入数据如果包括大量图片或文本,预处理是计算密集型的,建议把所有能提前做的处理都提前到离线阶段完成,在线只做最简单的张量转换。数据存在机械硬盘还是SSD也有影响,云平台一般提供不同性能的数据盘,预算允许就选高性能盘,IO瓶颈对训练的影响往往被低估。
6.2 半精度训练NaN/Inf排查
在智算云实例上混合精度训练遇到NaN,几乎成了群里的日经问题。我的排查顺序是这样的:第一步,把autocast去掉,用纯FP32跑一遍,如果还是NaN,说明问题不在精度,可能是数据本身有缺失值或者学习率太大;第二步,如果FP32正常而FP16才NaN,优先降低学习率,因为FP16本身数值范围小,过大的更新步长一步就冲爆了;第三步,确认GradScaler生效,设置阀值或改用动态缩放。
还有一个容易被忽略的点:某些自定义Loss或复杂结构里的数值操作,比如手动计算方差、对数、开方,数值敏感度极高,在FP16下会产生NaN。解决办法是把这些计算块用小范围的autocast禁用标记包起来,让它们保持在FP32。```python with autocast(enabled=False): loss = custom_operation(...)
### 6.3 显存不够的急救方案 显存报错CUDA out of memory几乎是每个训练过深度学习模型的人都见过的。急救方案按优先级排列:第一个是把模型和数据切成更小的batch,代价是训练变慢;第二个是开启混合精度,FP16的激活值比FP32省一半显存,这通常是最立竿见影的做法;第三个是打开梯度检查点,用重计算换显存,对大模型特别有效;第四个是梯度累积,每几步再更新一次参数,模拟增大batch size的效果。 ```python model.gradient_checkpointing_enable()对于微调场景,LoRA是显存危机的终极解法。它把冻结的原模型权重保持为半精度,只训练新增的少量低秩参数,7B模型用QLoRA微调时显存需求能压到十几GB。这也是为什么现在很多人在消费级显卡上做7B级模型微调的原因。
6.4 云平台操作中的六个坑
第一个坑是数据放错盘。平台实例通常有系统盘和数据盘,系统盘在你释放实例时会被清空,数据盘可以保留或备份。我一再强调,不要为了省事把所有东西都丢在系统盘,重要数据和代码请放到挂在/workspace或/mnt/data下的数据盘。
第二个坑是SSH端口没放行。创建完实例你发现SSH连不上,十有八九是安全组规则没放行端口,或者IP白名单设置不对。在控制台的安全组里放行TCP端口,然后再连一次。
第三个坑是密钥权限过宽。私钥文件权限必须是600,否则SSH客户端直接拒绝加载。我第一次用密钥登录时就吃过这个亏,报错信息还很隐晦。
第四个坑是CUDA和PyTorch版本不匹配。换镜像时发现torch.cuda.is_available为False,先检查镜像里的驱动版本和PyTorch构建版本,最简单的办法是选择平台维护好的官方镜像组合,不要自己从头装。
第五个坑是存储费用没算进去。有些平台按量付费只收GPU实例费用,数据盘、公网带宽、快照是单独计费的。你以为自己省了钱,月底账单一看还有一堆小项目在扣费。用平台前先看清计费明细。
第六个坑是批量任务没有断点续训。跑了几十个小时的训练任务,网络断了或者抢占式实例被回收,如果没有保存checkpoint,直接回到解放前。每个epoch保存一次模型和优化器状态,是云端训练的基本习惯。
6.5 排障速查表
我把上面这些经验整理成一个速查表,方便你遇到问题时快速定位:
| 问题现象 | 常见原因 | 处理方式 |
|---|---|---|
| GPU利用率为0但程序在跑 | 模型和数据不在CUDA设备上 | 检查model.cuda()与data.cuda() |
| GPU利用率低 | DataLoader读取太慢 | 增加num_workers、pin_memory,转SSD |
| 半精度训练NaN | 学习率过大或梯度下溢 | 纯FP32复现、降学习率、检查GradScaler |
| CUDA out of memory | 显存超卖或激活值过多 | 减小batch、混合精度、梯度检查点 |
| torch.cuda.is_available为False | CUDA/PyTorch版本不匹配 | 重装镜像或配对版本 |
| SSH连接超时 | 安全组未放行端口 | 控制台放行TCP端口、检查IP白名单 |
| 训练中断没进度 | 未保存checkpoint | 每个epoch保存断点 |
我个人在实际操作中的另一个体会是:不要指望一张规格表帮你解决所有选择问题。GPU算力是一个系统问题,精度、显存、带宽、数据加载、软件栈缺一不可。只有亲手在实例上跑通一遍真实任务,记录下耗时和成本,才能形成自己的决策依据。如果你能将文章里的混合精度模板和排障思路用在下一个项目里,大概率能省下一半以上的算力预算和时间。