2026/4/18 8:36:06
网站建设
项目流程
网站 制作 工具,富阳网站制作,制作软件的手机软件,临沂制作网站软件文章目录DAY 37 GPU训练与 __call__ 方法1. 在 CPU 上搭建基线1.1 查看 CPU 指标2. GPU 训练2.1 如何快速看懂 GPU 型号3. 为什么 GPU 表现得更慢#xff1f;3.1 数据传输细节3.2 核心启动与批处理3.3 何时使用 GPU4. 减少额外开销的实践4.1 记录频率与耗时的关系5. 认识 __c…文章目录DAY 37 · GPU训练与 __call__ 方法1. 在 CPU 上搭建基线1.1 查看 CPU 指标2. GPU 训练2.1 如何快速看懂 GPU 型号3. 为什么 GPU 表现得更慢3.1 数据传输细节3.2 核心启动与批处理3.3 何时使用 GPU4. 减少额外开销的实践4.1 记录频率与耗时的关系5. 认识 __call__ 方法5.1 无参数示例5.2 带参数示例DAY 37 · GPU训练与call方法重新跑通鸢尾花分类任务并记录 CPU 运行时长学习如何快速了解硬件配置尤其是 CPU 与 GPU掌握在 PyTorch 中将数据和模型迁移到 GPU 的常见做法分析 GPU 看起来更慢的根源并给出优化策略理解 nn.Module 中call的工作机制1. 在 CPU 上搭建基线先用 CPU 完成一次完整训练流程包含数据预处理、模型定义、训练循环和耗时统计。importtorchimporttorch.nnasnnimporttorch.optimasoptimfromsklearn.datasetsimportload_irisfromsklearn.model_selectionimporttrain_test_splitimportnumpyasnp# 仍然用4特征3分类的鸢尾花数据集作为我们今天的数据集# 加载鸢尾花数据集irisload_iris()Xiris.data# 特征数据yiris.target# 标签数据# 划分训练集和测试集X_train,X_test,y_train,y_testtrain_test_split(X,y,test_size0.2,random_state42)# 归一化数据神经网络对于输入数据的尺寸敏感归一化是最常见的处理方式fromsklearn.preprocessingimportMinMaxScaler scalerMinMaxScaler()X_trainscaler.fit_transform(X_train)X_testscaler.transform(X_test)#确保训练集和测试集是相同的缩放# 将数据转换为 PyTorch 张量X_traintorch.FloatTensor(X_train)y_traintorch.LongTensor(y_train)X_testtorch.FloatTensor(X_test)y_testtorch.LongTensor(y_test)classMLP(nn.Module):def__init__(self):super(MLP,self).__init__()self.fc1nn.Linear(4,10)self.relunn.ReLU()self.fc2nn.Linear(10,3)defforward(self,x):outself.fc1(x)outself.relu(out)outself.fc2(out)returnout modelMLP()criterionnn.CrossEntropyLoss()optimizeroptim.SGD(model.parameters(),lr0.01)num_epochs20000losses[]importtime start_timetime.time()forepochinrange(num_epochs):outputsmodel.forward(X_train)# 显式调用forward函数losscriterion(outputs,y_train)optimizer.zero_grad()loss.backward()optimizer.step()losses.append(loss.item())if(epoch1)%1000:print(fEpoch [{epoch1}/{num_epochs}], Loss:{loss.item():.4f})time_alltime.time()-start_timeprint(fTraining time:{time_all:.2f}seconds)importmatplotlib.pyplotasplt plt.plot(range(num_epochs),losses)plt.xlabel(Epoch)plt.ylabel(Loss)plt.title(Training Loss over Epochs)plt.show()Epoch [100/20000], Loss: 1.0882 Epoch [200/20000], Loss: 1.0701 Epoch [300/20000], Loss: 1.0455 Epoch [400/20000], Loss: 1.0111 Epoch [500/20000], Loss: 0.9649 ... Epoch [20000/20000], Loss: 0.0624 Training time: 4.62 seconds1.1 查看 CPU 指标上述是在cpu的情况下训练即使安装了cuda但是没有使用cuda我们借这个机会简单介绍下cpu的性能差异。我使用的是wsl。因此我在终端输入lscpu来查看CPU信息。(base)ubuntu24DESKTOP-3Q94GS2:~/code/PythonPractice/day_37$ lscpu CPU(s):24Thread(s)per core:2Core(s)per socket:12Socket(s):1CPU 配置解读i7-12800HXIntel 第 12 代酷睿Alder Lake移动端高性能处理器核心架构混合大小核设计性能核P-Core8 核支持超线程16 线程能效核E-Core4 核不支持超线程物理核心数12 核逻辑线程数24在该配置下CPU 上的鸢尾花训练平均约 4.6 秒。接下来开始研究 GPU如何迁移模型、如何评估显卡、又该如何正确理解速度差异。2. GPU 训练在 PyTorch 中.to(device)可以把张量或模型移动到指定设备CPU/GPU。只有torch.Tensor对象和继承nn.Module的模型拥有该方法。实践中需要保证输入张量和模型在同一设备上否则会抛出运行时错误。2.1 如何快速看懂 GPU 型号以 RTX 3090 Ti、RTX 3080、RTX 3070 Ti、RTX 4070 等为例代际前两位数字代表代数40xx 为第 40 代、30xx 为第 30 代。新架构通常意味着更先进的制程和更高的能效比。级别后两位数字代表定位。xx90旗舰/次旗舰性能最强、显存最大。xx80高端性能强劲、显存较多。xx70中高端适合兼顾训练和日常使用。importtorch# 检查CUDA是否可用iftorch.cuda.is_available():print(CUDA可用)device_counttorch.cuda.device_count()print(f可用的CUDA设备数量:{device_count})current_devicetorch.cuda.current_device()print(f当前使用的CUDA设备索引:{current_device})device_nametorch.cuda.get_device_name(current_device)print(f当前CUDA设备的名称:{device_name})cuda_versiontorch.version.cudaprint(fCUDA版本:{cuda_version})print(cuDNN版本:,torch.backends.cudnn.version())else:print(CUDA不可用。)CUDA可用 可用的CUDA设备数量: 1 当前使用的CUDA设备索引: 0 当前CUDA设备的名称: NVIDIA GeForce RTX 4070 Laptop GPU CUDA版本: 12.4 cuDNN版本: 90100# 设置GPU设备devicetorch.device(cuda:0iftorch.cuda.is_available()elsecpu)print(f使用设备:{device})使用设备: cuda:0fromsklearn.datasetsimportload_irisfromsklearn.model_selectionimporttrain_test_splitfromsklearn.preprocessingimportMinMaxScaler# 加载鸢尾花数据集irisload_iris()Xiris.data yiris.target# 划分训练集和测试集X_train,X_test,y_train,y_testtrain_test_split(X,y,test_size0.2,random_state42)# 归一化数据scalerMinMaxScaler()X_trainscaler.fit_transform(X_train)X_testscaler.transform(X_test)# 将数据转换为PyTorch张量并移至GPUX_traintorch.FloatTensor(X_train).to(device)y_traintorch.LongTensor(y_train).to(device)X_testtorch.FloatTensor(X_test).to(device)y_testtorch.LongTensor(y_test).to(device)classMLP(nn.Module):def__init__(self):super(MLP,self).__init__()self.fc1nn.Linear(4,10)self.relunn.ReLU()self.fc2nn.Linear(10,3)defforward(self,x):outself.fc1(x)outself.relu(out)outself.fc2(out)returnout# 实例化模型并移至GPUmodelMLP().to(device)# 定义损失函数和优化器criterionnn.CrossEntropyLoss()optimizeroptim.SGD(model.parameters(),lr0.01)# 训练模型num_epochs20000losses[]start_timetime.time()forepochinrange(num_epochs):outputsmodel(X_train)losscriterion(outputs,y_train)optimizer.zero_grad()loss.backward()optimizer.step()losses.append(loss.item())if(epoch1)%1000:print(fEpoch [{epoch1}/{num_epochs}], Loss:{loss.item():.4f})time_alltime.time()-start_timeprint(fTraining time:{time_all:.2f}seconds)plt.plot(range(num_epochs),losses)plt.xlabel(Epoch)plt.ylabel(Loss)plt.title(Training Loss over Epochs)plt.show()Epoch [100/20000], Loss: 1.0390 Epoch [200/20000], Loss: 0.9741 Epoch [300/20000], Loss: 0.9003 Epoch [400/20000], Loss: 0.8244 Epoch [500/20000], Loss: 0.7507 ... Epoch [20000/20000], Loss: 0.0629 Training time: 16.85 seconds3. 为什么 GPU 表现得更慢对于如此小的数据集和简单模型GPU 往往比 CPU 慢主要受三类固定开销影响数据传输CPU 内存与 GPU 显存之间来回拷贝。核心kernel启动每个算子都要在 GPU 上启动一次核心程序。计算资源浪费批量小、计算量少GPU 的并行能力发挥不出来。3.1 数据传输细节在 GPU 计算之前输入张量、标签与模型参数都要从 RAM 复制到显存。loss.item()每次都会把标量从 GPU 拷回 CPU用于日志打印或可视化。在 20000 个 epoch 的循环中这些同步操作的总时间并不比实际计算少。3.2 核心启动与批处理每个前向或反向算子都会触发一次 kernel 启动哪怕只是一个线性层。当只有少量样本和极小的 batch 时GPU 无法并行足够多的计算来摊平这些固定成本。因此才会看到 “CPU 4.6 秒就跑完而 GPU 却耗时 17 秒” 的现象。3.3 何时使用 GPU深度学习项目往往动辄几十分钟或数小时此时 GPU 的高吞吐量能极大缩短训练时间。CPU 适合小任务省去了数据跨芯片的传输。GPU 需要把数据、模型搬到显存且频繁的 kernel 启动会放大额外成本。当模型规模、数据集或 batch size 足够大时GPU 才能发挥并行优势。4. 减少额外开销的实践针对上述瓶颈最直接的方向是减少不必要的 CPU⇄GPU 往返。下面演示两个思路彻底停止频繁记录不在循环中保存/打印loss.item()从根源上避免同步。降低记录频率例如改为每 200 个 epoch 才把损失值搬回来。既保留监控指标也控制传输次数。# 思路1完全不记录loss纯粹观察终端输出importtorchimporttorch.nnasnnimporttorch.optimasoptimfromsklearn.datasetsimportload_irisfromsklearn.model_selectionimporttrain_test_splitimportnumpyasnp irisload_iris()Xiris.data yiris.target X_train,X_test,y_train,y_testtrain_test_split(X,y,test_size0.2,random_state42)fromsklearn.preprocessingimportMinMaxScaler scalerMinMaxScaler()X_trainscaler.fit_transform(X_train)X_testscaler.transform(X_test)X_traintorch.FloatTensor(X_train)y_traintorch.LongTensor(y_train)X_testtorch.FloatTensor(X_test)y_testtorch.LongTensor(y_test)classMLP(nn.Module):def__init__(self):super(MLP,self).__init__()self.fc1nn.Linear(4,10)self.relunn.ReLU()self.fc2nn.Linear(10,3)defforward(self,x):outself.fc1(x)outself.relu(out)outself.fc2(out)returnout modelMLP()criterionnn.CrossEntropyLoss()optimizeroptim.SGD(model.parameters(),lr0.01)num_epochs20000start_timetime.time()forepochinrange(num_epochs):outputsmodel.forward(X_train)losscriterion(outputs,y_train)optimizer.zero_grad()loss.backward()optimizer.step()if(epoch1)%1000:print(fEpoch [{epoch1}/{num_epochs}], Loss:{loss.item():.4f})print(fTraining time:{time.time()-start_time:.2f}seconds)Epoch [100/20000], Loss: 1.0762 Epoch [200/20000], Loss: 1.0561 Epoch [300/20000], Loss: 1.0299 Epoch [400/20000], Loss: 0.9972 Epoch [500/20000], Loss: 0.9581 ... Epoch [20000/20000], Loss: 0.0616 Training time: 5.21 seconds实测下来GPU 训练时间迅速下降到与 CPU 接近说明大量时间确实耗在把标量搬回 CPU 上。# 思路2降低记录频率兼顾可视化与性能importtorchimporttorch.nnasnnimporttorch.optimasoptimfromsklearn.datasetsimportload_irisfromsklearn.model_selectionimporttrain_test_splitfromsklearn.preprocessingimportMinMaxScalerimporttimeimportmatplotlib.pyplotasplt devicetorch.device(cuda:0iftorch.cuda.is_available()elsecpu)print(f使用设备:{device})irisload_iris()Xiris.data yiris.target X_train,X_test,y_train,y_testtrain_test_split(X,y,test_size0.2,random_state42)scalerMinMaxScaler()X_trainscaler.fit_transform(X_train)X_testscaler.transform(X_test)X_traintorch.FloatTensor(X_train).to(device)y_traintorch.LongTensor(y_train).to(device)X_testtorch.FloatTensor(X_test).to(device)y_testtorch.LongTensor(y_test).to(device)classMLP(nn.Module):def__init__(self):super(MLP,self).__init__()self.fc1nn.Linear(4,10)self.relunn.ReLU()self.fc2nn.Linear(10,3)defforward(self,x):outself.fc1(x)outself.relu(out)outself.fc2(out)returnout modelMLP().to(device)criterionnn.CrossEntropyLoss()optimizeroptim.SGD(model.parameters(),lr0.01)num_epochs20000losses[]start_timetime.time()forepochinrange(num_epochs):outputsmodel(X_train)losscriterion(outputs,y_train)optimizer.zero_grad()loss.backward()optimizer.step()if(epoch1)%20000:losses.append(loss.item())print(fEpoch [{epoch1}/{num_epochs}], Loss:{loss.item():.4f})print(fTraining time:{time.time()-start_time:.2f}seconds)plt.plot(range(len(losses)),losses)plt.xlabel(Checkpoint Index)plt.ylabel(Loss)plt.title(Training Loss over Epochs (Sampled))plt.show()使用设备: cuda:0 Epoch [2000/20000], Loss: 0.3663 Epoch [4000/20000], Loss: 0.2058 Epoch [6000/20000], Loss: 0.1371 Epoch [8000/20000], Loss: 0.1054 Epoch [10000/20000], Loss: 0.0886 Epoch [12000/20000], Loss: 0.0787 Epoch [14000/20000], Loss: 0.0723 Epoch [16000/20000], Loss: 0.0679 Epoch [18000/20000], Loss: 0.0646 Epoch [20000/20000], Loss: 0.0622 Training time: 13.08 seconds4.1 记录频率与耗时的关系以总 epoch20000 为例我在本地的测试如下剩余时长总时长−4.6s纯计算时间记录间隔轮记录次数次剩余时长秒100200102001009.3510002011.552000108.5可以看到记录次数越少额外耗时会略有下降但并非严格线性真实项目中应结合监控需求取舍。5. 认识call方法nn.Linear、nn.Module的实例之所以可以被写成self.fc1(x)是因为它们实现了__call__。在 Python 里任何定义了__call__的对象都可以像函数一样被调用。classMLP(nn.Module):def__init__(self):super(MLP,self).__init__()self.fc1nn.Linear(4,10)self.relunn.ReLU()self.fc2nn.Linear(10,3)defforward(self,x):outself.fc1(x)outself.relu(out)outself.fc2(out)returnout在__init__中执行self.fc1 nn.Linear(4, 10)时self.fc1变成了一个nn.Linear的实例。调用self.fc1(x)实际上会触发nn.Module.__call__该方法再去调用子类的forward从而完成前向传播。5.1 无参数示例__call__可以在每次“函数式调用”时维护内部状态非常适合封装可调用对象。classCounter:def__init__(self):self.count0def__call__(self):self.count1returnself.count counterCounter()print(counter())# 输出: 1print(counter())# 输出: 2print(counter.count)# 输出: 21 2 2实例化只会发生一次counter Counter()随后每次调用counter()都会触发__call__并更新内部的count。5.2 带参数示例__call__也能像普通函数一样接收参数对象既能保存状态又能提供行为非常适合需要“带记忆”的可调用单元。classAdder:def__call__(self,a,b):print(唱跳篮球rap)returnab adderAdder()print(adder(3,5))# 输出: 8唱跳篮球rap 8浙大疏锦行