资讯动态

第九章 深度学习 -- 工程实践

发布时间:2026/10/26 1:58:59 来源:尧图企业网站定制
第九章 工程实践一、训练工程1.1 GPU 与混合精度GPU 是深度学习的标配。GPU 的大量并行 ALU(几千到上万)非常适合矩阵乘法、卷积、注意力等「密集并行」运算,相比 CPU 通常有 10-100 倍的速度提升。PyTorch GPU 基础:importtorch# 检查 GPU 是否可用print(torch.cuda.is_available())# True/Falseprint(torch.cuda.device_count())# GPU 数量# 指定设备devicetorch.device(cudaiftorch.cuda.is_available()elsecpu)# 把模型迁移到 GPUmodelMyModel().to(device)# 把数据迁移到 GPU(每个 batch 都要做)forx,yindataloader:x,yx.to(device),y.to(device)# ...多 GPU 环境:单机多卡:torch.cuda.device_count()显示数量,可以用device cuda:0、device cuda:1指定。多机多卡:需要分布式训练(见 1.2)。GPU 显存管理。OOM(Out Of Memory)是深度学习最常见的错误。常用策略:减小 batch size:最直接,但可能影响模型收敛。梯度累积(Gradient Accumulation):多次forward backward不清梯度,模拟大 batch。混合精度训练(见下):bf16/fp16 比 fp32 省一半显存。梯度检查点(Gradient Checkpointing):牺牲 20-30% 时间换大量显存。及时释放:del loss等不需要的引用,让 Python 回收。torch.cuda.empty_cache()不建议放进训练循环(会打碎 PyTorch 缓存分配器的复用,反而降低吞吐),仅在长序列末尾或调试时调用。Pin Memory:DataLoader(pin_memoryTrue)让 CPU→GPU 传输更快。避免中间变量累积:用with torch.no_grad():包裹不需要梯度的代码。监控 GPU:nvidia-smi# 实时查看 GPU 使用率与显存nvidia-smi-l1# 每秒刷新一次watch-n1nvidia-smi# 类似或在 Python 中:print(torch.cuda.memory_allocated()/1024**3,GB)# 已分配print(torch.cuda.max_memory_allocated()/1024**3,GB)# 峰值混合精度训练(Mixed Precision)。使用 16 位浮点数(fp16 或 bf16)做前向/反向,同时保留 32 位权重做优化器更新。为什么有效:显存减半:fp16 张量是 fp32 的一半大小。计算加速:Tensor Core 在 fp16/bf16 下吞吐翻倍(NVIDIA A100/H100 强相关)。通信减半:多卡通信时梯度传输量减半。为什么不直接用 fp16:数值范围窄,容易下溢(梯度极小变 0)或上溢(NaN)。需要「损失缩放」(Loss Scaling)防止下溢。bf16 vs fp16:fp16(IEEE 半精度):1 符号位 5 指数位 10 尾数位。范围窄(~6e-5 到 65504),精度高(11 位有效位)。bf16(Brain Float 16):1 符号位 8 指数位 7 尾数位。范围与 fp32 相同,精度低(8 位有效位)。现代 GPU(A100、H100)支持 bf16,默认推荐使用 bf16(无需 Loss Scaling,训练更稳定)。V100(Volta, 2017)及更早 GPU 只能 fp16 训练(其中 V100 支持 fp16 Tensor Core 加速,但不支持 bf16),需要 Loss Scaling。PyTorch 混合精度 API(torch.amp):# bf16 模式(推荐):无需 GradScalerforx,yindataloader:x,yx.to(device),y.to(device)optimizer.zero_grad()withtorch.amp.autocast(cuda,dtypetorch.bfloat16):outmodel(x)losscriterion(out,y)loss.backward()optimizer.step()# fp16 模式:需要 GradScaler 损失缩放scalertorch.amp.GradScaler(cuda)forx,yindataloader:x,yx.to(device),y.to(device)optimizer.zero_grad()withtorch.amp.autocast(cuda,dtypetorch.float16):outmodel(x)losscriterion(out,y)scaler.scale(loss).backward()scaler.unscale_(optimizer)# 关键:先反缩放,才能正确做梯度裁剪torch.nn.utils.clip_grad_norm_(model.parameters(),max_norm1.0)scaler.step(optimizer)# 若梯度含 inf/NaN,scaler 会自动跳过本步scaler.update()fp16 路径的裁剪顺序。scaler.scale()把梯度放大了 loss_scale 倍,此时直接clip_grad_norm_裁剪的是放大后的梯度——裁剪阈值形同虚设。必须先scaler.unscale_(optimizer)反缩放,再裁剪;scaler.step在 unscale 之后会检测 inf/NaN 并自动跳过该次更新。bf16 路径数值范围与 fp32 相同,不需要GradScaler,直接 backward 即可(这也是推荐 bf16 的原因之一)。PyTorch 1.10 引入torch.amp.autocast(2.4 起torch.cuda.amp.autocast标记 deprecated)。torch.set_float32_matmul_precision(high)还能进一步加速(用 TF32)。常见错误:fp16 未用 Loss Scaling:小 loss 下溢为 0,梯度消失,训练停滞。混合精度 BatchNorm:BN 在 fp16 下统计量不稳定,需要保留 fp32。混合精度 RNN/LSTM cuDNN:某些 cuDNN 版本对 LSTM 的 fp16 支持有限,需要测试。TF32(Tensor Float 32)。NVIDIA Ampere 架构(A100 等)引入,把 fp32 的部分计算用 19 位精度做(指数位 8、尾数位 10),速度接近 fp16,精度损失小。torch.set_float32_matmul_precision(high)启用,几乎无副作用,推荐开启。注意:PyTorch 1.12 起,torch.set_float32_matmul_precision默认从 TF32-on 改为highest(完整 fp32 精度),如需 TF32 加速需显式启用。1.2 分布式训练概览为什么需要分布式。单卡显存有限(主流 A100 80GB),大模型(Llama-70B 参数约占 140GB)无法装下;同时训练时间也希望尽可能短。并行策略:数据并行(Data Parallel, DP):模型复制到多卡,每卡处理不同 batch 切片,梯度同步。简单,适合模型能装下单卡的场景。模型并行(Model Parallel, MP):模型切分到多卡,适合单卡装不下的模型。张量并行(Tensor Parallel, TP):把单个 tensor 切到多卡(按行/按列)。流水线并行(Pipeline Parallel, PP):把模型按层切到多卡,微批次流水线。3D 并行:TP PP DP 同时使用,是大模型训练的标配。ZeRO(Zero Redundancy Optimizer):把优化器状态、梯度、参数分片到多卡(FSDP 是其 PyTorch 实现)。单进程多卡 DataParallel(DP,nn.DataParallel)。最简单,但有 GIL 与主卡瓶颈问题,生产中不推荐:modelnn.DataParallel(model,device_ids[0,1,2,3])DistributedDataParallel(DDP,生产推荐):importtorch.distributedasdistfromtorch.nn.parallelimportDistributedDataParallelasDDP# 1. 初始化进程组dist.init_process_group(backendnccl,init_methodenv://)# 2. 每进程一个模型,放在不同 GPUlocal_rankint(os.environ[LOCAL_RANK])devicetorch.device(fcuda:{local_rank})modelMyModel().to(device)modelDDP(model,device_ids[local_rank])# 3. 用 DistributedSampler 让每卡看到不同数据samplertorch.utils.data.distributed.DistributedSampler(dataset)loaderDataLoader(dataset,batch_size32,samplersampler)forepochinrange(num_epochs):sampler.set_epoch(epoch)# 重要:保证每 epoch shuffle 不同forx,yinloader:# ... 前向、反向# DDP 自动同步梯度启动:torchrun --nproc_per_node4 train.py或python -m torch.distributed.launch(后者已弃用)。Ring AllReduce(NCCL 后端)。DDP 用环形拓扑做梯度同步:每卡只与相邻卡通信,O(N) 步后所有卡的梯度都同步。NCCL 是 NVIDIA 的集合通信库,在 GPU 间高效实现 AllReduce、AllGather 等原语。FSDP(Fully Sharded Data Parallel, PyTorch 1.11)。ZeRO 的 PyTorch 实现,把模型参数、梯度、优化器状态都分片到多卡,显存占用接近「单卡/卡数」,适合大模型。fromtorch.distributed.fsdpimportFullyShardedDataParallelasFSDP modelFSDP(model,sharding_strategyShardingStrategy.FULL_SHARD)FSDP2(PyTorch 2.4):基于 DTensor 重写的第二代 FSDP,与张量并行组合更干净,新项目建议直接用 FSDP2:from torch.distributed._composable.fsdp import fully_shard。# FSDP2 写法(PyTorch 2.4 推荐)fromtorch.distributed._composable.fsdpimportfully_shard modelfully_shard(model)主流分布式训练框架:PyTorch DDP / FSDP:官方,基础。DeepSpeed(Microsoft):ZeRO 系列的工程化,API 友好。Megatron-LM(NVIDIA):张量并行 流水线并行,Transformer 训练专用。Accelerate(Hugging Face):简化分布式训练的封装。Lightning(PyTorch Lightning):高层 API,自动处理 DDP。实战建议:从单卡开始:先在单卡跑通,再扩展到多卡。数据并行优先:能装下单卡就用 DDP,最简单。大模型用 FSDP/DeepSpeed:超大规模(10B)用 FSDP 或 DeepSpeed ZeRO-3。混合精度必开:bf16 几乎免费换速度。Gradient Checkpointing:显存紧张时启用。监控吞吐:用tokens_per_second、images_per_second等业务指标,而不是只看 loss。学习率调整:batch size 翻倍,学习率按 sqrt 缩放(经验法则)。二、调试与诊断2.1 过拟合诊断过拟合的本质。模型容量相对数据量过大,学到了训练集特有的「噪声模式」而非「数据规律」。表现:训练集指标持续提升,但验证集指标停滞或下降。诊断方法:绘制学习曲线:训练 loss 与验证 loss 随 epoch 的变化。经典模式:两者都下降 → 正常训练。训练 loss 持续下降,验证 loss 停滞或上升 →过拟合。两者都停滞且差距小 →欠拟合(模型容量不足或学习率太低)。比较指标差距:训练准确率与验证准确率的差距。差距大 → 过拟合;都低 → 欠拟合。检查数据分布:训练集与验证集分布不一致(数据泄露、采样偏差)也会导致「假过拟合」。缓解方法:更多数据:根本上解决问题,但成本高。数据增强(Data Augmentation):图像任务用 Random Crop、Flip、Color Jitter、Cutout、MixUp;文本用回译、随机删除;音频用加噪、时移。正则化:权重衰减(weight decay)Dropout(Dropout2d 对 CNN,Dropout 对 MLP)Label SmoothingEarly Stopping降低模型容量:减少层数、宽度,或用更小架构。集成:多个模型投票,但部署成本高。归一化:BatchNorm、LayerNorm 隐式正则化。预训练 微调:迁移学习天然抗过拟合。欠拟合的诊断与缓解:训练 loss 下不去 → 欠拟合。缓解:增大模型、学习率微调、减少正则化、改进特征工程、检查数据标注。过拟合 vs 欠拟合的判断。一个简单的决策表:训练 loss验证 loss差距诊断行动低高大过拟合增加正则化/数据高高小欠拟合增大模型/调学习率低低小正常继续训练或微调高高大数据/代码 bug检查数据、loss 实现Early Stopping(早停)。在验证集指标不再提升时停止训练,是简单有效的正则化手段。PyTorch 没有内置,但容易实现:best_val_lossfloat(inf)patience5counter0forepochinrange(num_epochs):train_losstrain_one_epoch()val_lossvalidate()ifval_lossbest_val_loss:best_val_lossval_loss torch.save(model.state_dict(),best_model.pth)counter0else:counter1ifcounterpatience:print(fEarly stopping at epoch{epoch})break2.2 训练问题排查训练问题的诊断框架。训练出问题时,按「数据 → 模型 → 损失 → 优化 → 训练配置」的顺序排查。1. Loss 不下降 / 一直是 NaN可能原因与排查:学习率过大:从 1e-4 起步,逐步增大。数据预处理错误:检查 mean/std、标签范围、token 化。损失函数选择错误:分类用 MSE、回归用 CrossEntropy 等。标签错误:随机检查样本标签。数值溢出:用 bf16 替代 fp16;梯度裁剪。学习率调度错误:warmup 太长、衰减太快。优化器问题:Adam 用 SGD 的学习率,或反之。模型 bug:forward 写错、参数未注册到 optimizer。调试技巧:用极小 batch(如 4 张图)看能否过拟合。如果连 4 张图都学不到,问题在模型/数据/损失实现。2. Loss 下降但验证集效果差训练集表现好,验证集差 → 过拟合,见 2.1。训练集与验证集都差 → 欠拟合或数据问题。3. Loss 震荡学习率过大 → 降低学习率。batch size 太小 → 增大 batch 或用梯度累积。数据有噪声/异常值 → 清洗数据。4. Loss 出现尖峰(NaN 或 spike)学习率过大。梯度爆炸 → 梯度裁剪。数据有极端值 → 清洗。混合精度 Loss Scaling 问题。数值不稳定(Softmax 输入过大等)→ 数值稳定的实现。5. GPU 显存 OOM减小 batch size。启用混合精度。启用 Gradient Checkpointing。用 FSDP/DeepSpeed。模型并行。检查是否有不必要的 tensor 引用。6. 训练速度过慢检查 DataLoader:num_workers、pin_memory。用torch.compile()加速(PyTorch 2.0)。GPU 利用率低 → 数据加载是瓶颈,优化 DataLoader。模型太小,GPU 等待数据 → 增大 batch 或模型。启用 cuDNN benchmark:torch.backends.cudnn.benchmark True(对固定输入尺寸有效)。7. 训练中断/不稳定检查随机种子:设置torch.manual_seed(42)等保证可复现。启用 checkpoint:torch.save({model: ..., optimizer: ...}, ckpt.pth)。用 wandb / TensorBoard 远程监控。常用调试工具:工具用途tensorboard/wandb训练曲线可视化、对比实验torchinfo(前 torchsummary)打印模型结构、参数量、FLOPstorch.autograd.gradcheck验证自定义算子的梯度正确性torch.profiler性能 profiling,定位瓶颈torchviz/torch.fx计算图可视化nvidia-smiGPU 实时状态py-spy/cProfilePython 代码 profilingTensorBoard 基础用法:fromtorch.utils.tensorboardimportSummaryWriter writerSummaryWriter(runs/experiment_1)forepochinrange(num_epochs):writer.add_scalar(train/loss,train_loss,epoch)writer.add_scalar(val/loss,val_loss,epoch)writer.add_scalar(train/acc,train_acc,epoch)writer.add_scalar(val/acc,val_acc,epoch)writer.add_scalar(lr,optimizer.param_groups[0][lr],epoch)# 直方图forname,paraminmodel.named_parameters():writer.add_histogram(name,param,epoch)ifparam.gradisnotNone:writer.add_histogram(f{name}.grad,param.grad,epoch)writer.close()Wandb(Weights Biases):第三方工具,功能比 TensorBoard 更强(实验管理、超参搜索、团队协作),有免费个人层。设置随机种子(可复现性):importrandom,numpyasnp,torchdefset_seed(seed42):random.seed(seed)np.random.seed(seed)torch.manual_seed(seed)torch.cuda.manual_seed_all(seed)# 让 cuDNN 确定性(可能影响性能)torch.backends.cudnn.deterministicTruetorch.backends.cudnn.benchmarkFalse完全可复现几乎不可能(GPU 非确定性、cuDNN 算法选择等),但固定种子能让实验对比有意义。训练 loop 标准模板(整合上面所有最佳实践):deftrain_one_epoch(model,loader,criterion,optimizer,device,scalerNone):model.train()total_loss,correct,total0,0,0forx,yinloader:x,yx.to(device,non_blockingTrue),y.to(device,non_blockingTrue)optimizer.zero_grad()withtorch.amp.autocast(cuda,dtypetorch.bfloat16):outmodel(x)losscriterion(out,y)ifscalerisnotNone:# fp16 路径scaler.scale(loss).backward()scaler.unscale_(optimizer)# 必须先反缩放,再裁剪torch.nn.utils.clip_grad_norm_(model.parameters(),max_norm1.0)scaler.step(optimizer)scaler.update()else:# bf16 / fp32 路径loss.backward()torch.nn.utils.clip_grad_norm_(model.parameters(),max_norm1.0)optimizer.step()total_lossloss.item()*x.size(0)correct(out.argmax(1)y).sum().item()totalx.size(0)returntotal_loss/total,correct/totaltorch.no_grad()defvalidate(model,loader,criterion,device):model.eval()total_loss,correct,total0,0,0forx,yinloader:x,yx.to(device,non_blockingTrue),y.to(device,non_blockingTrue)withtorch.amp.autocast(cuda,dtypetorch.bfloat16):outmodel(x)losscriterion(out,y)total_lossloss.item()*x.size(0)correct(out.argmax(1)y).sum().item()totalx.size(0)returntotal_loss/total,correct/total整体训练主循环:modelMyModel().to(device)optimizertorch.optim.AdamW(model.parameters(),lr1e-3,weight_decay0.01)schedulertorch.optim.lr_scheduler.CosineAnnealingLR(optimizer,T_maxnum_epochs)criterionnn.CrossEntropyLoss()scalerNone# bf16 模式;若改用 fp16,设为 torch.amp.GradScaler(cuda) 并传入 train_one_epochbest_val_acc0forepochinrange(num_epochs):train_loss,train_acctrain_one_epoch(model,train_loader,criterion,optimizer,device,scalerscaler)val_loss,val_accvalidate(model,val_loader,criterion,device)scheduler.step()print(fEpoch{epoch1}/{num_epochs}| fTrain Loss:{train_loss:.4f}Acc:{train_acc:.4f}| fVal Loss:{val_loss:.4f}Acc:{val_acc:.4f})ifval_accbest_val_acc:best_val_accval_acc torch.save(model.state_dict(),best_model.pth)

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑