news 2026/2/10 4:36:44

Day 40 GPU训练及类的call方法

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Day 40 GPU训练及类的call方法

@浙大疏锦行

判断 CPU 的好坏需要综合考虑硬件参数、性能表现、适用场景。

1. 看架构代际,新一代架构通常优化指令集、缓存设计和能效比。如Intel 第 13 代 i5-13600K 比第 12 代 i5-12600K 多核性能提升约 15%

2. 看制程工艺,制程越小,晶体管密度越高,能效比越好,如AMD Ryzen 7000 系列(5nm)比 Ryzen 5000 系列(7nm)能效比提升约 30%。

3. 看核心数:性能核负责高负载任务(如游戏、视频剪辑),单核性能强。能效核负责多任务后台处理(如下载、杀毒),功耗低。如游戏 / 办公:4-8 核足够,内容创作 / 编程:12 核以上更优。

4. 看线程数目

5. 看频率,高频适合单线程任务(如游戏、Office),低频多核适合多线程任务(如 3D 渲染)

6. 支持的指令集和扩展能力。

GPU训练

要让模型在 GPU 上训练,主要是将模型和数据迁移到 GPU 设备上。

在 PyTorch 里,.to(device) 方法的作用是把张量或者模型转移到指定的计算设备(像 CPU 或者 GPU)上。

对于张量(Tensor):调用 .to(device) 之后,会返回一个在新设备上的新张量。

对于模型(nn.Module):调用 .to(device) 会直接对模型进行修改,让其所有参数和缓冲区都移到新设备上。

在进行计算时,所有输入张量和模型必须处于同一个设备。要是它们不在同一设备上,就会引发运行时错误。并非所有 PyTorch 对象都有 .to(device) 方法,只有继承自 torch.nn.Module 的模型以及 torch.Tensor 对象才有此方法。

如何衡量GPU的性能好坏呢?

以RTX 3090 Ti, RTX 3080, RTX 3070 Ti, RTX 3070, RTX 4070等为例

1.通过“代”

前两位数字代表“代”: 40xx (第40代), 30xx (第30代), 20xx (第20代)。“代”通常指的是其底层的架构 (Architecture)。每一代新架构的发布,通常会带来工艺制程的进步和其他改进。也就是新一代架构的目标是在能效比和绝对性能上超越前一代同型号的产品。

2.通过级别

后面的数字代表“级别”,

xx90: 通常是该代的消费级旗舰或次旗舰,性能最强,显存最大 (如 RTX 4090, RTX 3090)。

xx80: 高端型号,性能强劲,显存较多 (如 RTX 4080, RTX 3080)。

xx70: 中高端,甜点级,性能和价格平衡较好 (如 RTX 4070, RTX 3070)。

xx60: 主流中端,性价比较高,适合入门或预算有限 (如 RTX 4060, RTX 3060)。

xx50: 入门级,深度学习能力有限。

3.通过后缀

Ti 通常是同型号的增强版,性能介于原型号和更高一级型号之间 (如 RTX 4070 Ti 强于 RTX 4070,小于4080)。

4.通过显存容量 VRAM (最重要!!)他是GPU 自身的独立高速内存,用于存储模型参数、激活值、输入数据批次等。单位通常是 GB(例如 8GB, 12GB, 24GB, 48GB)。如果显存不足,可能无法加载模型,或者被迫使用很小的批量大小,从而影响训练速度和效果

1. 训练阶段:小批量梯度是对真实梯度的一个有噪声的估计。批量越小,梯度的方差越大(噪声越大)。显存小只能够使用小批量梯度。

2. 推理阶段:有些模型本身就非常庞大(例如大型语言模型、高分辨率图像的复杂 CNN 网络)。即使你将批量大小减到 1,模型参数本身占用的显存可能就已经超出了你的 GPU 显存上限。

有时候CPU比GPU跑得快,为什么?

1.数据传输开销 (CPU 内存 <-> GPU 显存)

- 在 GPU 进行任何计算之前,数据(输入张量 X_train、y_train,模型参数)需要从计算机的主内存 (RAM) 复制到 GPU 专用的显存 (VRAM) 中。

- 当结果传回 CPU 时(例如,使用 loss.item() 获取损失值用于打印或记录,或者获取最终预测结果),数据也需要从 GPU 显存复制回 CPU 内存。

- 对于少量数据和非常快速的计算任务,这个传输时间可能比 GPU 通过并行计算节省下来的时间还要长。

在上述代码中,循环里的 loss.item() 操作会在每个 epoch 都进行一次从 GPU 到 CPU 的数据同步和传输,以便获取标量损失值。对于20000个epoch来说,这会累积不少的传输开销。

2.核心启动开销 (GPU 核心启动时间)

- GPU 执行的每个操作(例如,一个线性层的前向传播、一个激活函数)都涉及到在 GPU 上启动一个“核心”(kernel)——一个在 GPU 众多计算单元上运行的小程序。

- 启动每个核心都有一个小的、固定的开销。

- 如果核心内的实际计算量非常小(本项目的小型网络和鸢尾花数据),这个启动开销在总时间中的占比就会比较大。相比之下,CPU 执行这些小操作的“调度”开销通常更低。

3.性能浪费:计算量和数据批次

- 这个数据量太少,gpu的很多计算单元都没有被用到,即使用了全批次也没有用到的全部计算单元。

综上,数据传输和各种固定开销的总和,超过了 GPU 在这点计算量上通过并行处理所能节省的时间,导致了 GPU 比 CPU 慢的现象。

- CPU (12th Gen Intel Core i9-12900KF): 对于这种小任务,CPU 的单核性能强劲,且没有显著的数据传输到“另一块芯片”的开销。它可以非常迅速地完成计算。

- GPU (NVIDIA GeForce RTX 3080 Ti):需要花费时间将数据和模型从 CPU 内存移动到 GPU 显存。

- 每次在 GPU 上执行运算(如 model(X_train)、loss.backward()) 都有核心启动的固定开销。

- loss.item() 在每个 epoch 都需要将结果从 GPU 传回 CPU,这在总共 20000 个 epoch 中会累积。

- GPU 强大的并行计算能力在这种小任务上完全没有用武之地。

这些特性导致GPU在处理鸢尾花分类这种“玩具级别”的问题时,它的优势无法体现,反而会因为上述开销显得“笨重”。

那么什么时候 GPU 会发挥巨大优势?

- 大型数据集: 例如,图像数据集成千上万张图片,每张图片维度很高。

- 大型模型: 例如,深度卷积网络 (CNNs like ResNet, VGG) 或 Transformer 模型,它们有数百万甚至数十亿的参数,计算量巨大。

- 合适的批处理大小: 能够充分利用 GPU 并行性的 batch size,不至于还有剩余的计算量没有被 GPU 处理。

- 复杂的、可并行的运算: 大量的矩阵乘法、卷积等。

针对上面反应的3个问题,能够优化的只有数据传输时间,针对性解决即可,很容易想到2个思路:

1. 直接不打印训练过程的loss了,但是这样会没办法记录最后的可视化图片,只能肉眼观察loss数值变化。

2. 每隔200个epoch保存一下loss,不需要20000个epoch每次都打印

__call__方法

在 Python 中,__call__ 方法是一个特殊的魔术方法(双下划线方法),它允许类的实例像函数一样被调用。这种特性使得对象可以表现得像函数,同时保留对象的内部状态。

# 我们来看下昨天代码中你的定义函数的部分 class MLP(nn.Module): # 定义一个多层感知机(MLP)模型,继承父类nn.Module def __init__(self): # 初始化函数 super(MLP, self).__init__() # 调用父类的初始化函数 # 前三行是八股文,后面的是自定义的 self.fc1 = nn.Linear(4, 10) # 输入层到隐藏层 self.relu = nn.ReLU() self.fc2 = nn.Linear(10, 3) # 隐藏层到输出层 # 输出层不需要激活函数,因为后面会用到交叉熵函数cross_entropy,交叉熵函数内部有softmax函数,会把输出转化为概率 def forward(self, x): out = self.fc1(x) out = self.relu(out) out = self.fc2(out) return out

可以注意到,self.fc1 = nn.Linear(4, 10) 此时,是实例化了一个nn.Linear(4, 10)对象,并把这个对象赋值给了MLP的初始化函数中的self.fc1变量。

那为什么下面的前向传播中却可以out = self.fc1(x) 呢?,self.fc1是一个实例化的对象,为什么具备了函数一样的用法,这是因为nn.Linear继承了nn.Module类,nn.Module类中定义了__call__方法。(可以ctrl不断进入来查看)

在 Python 中,任何定义了 __call__ 方法的类,其实例都可以像函数一样被调用。

当调用 self.fc1(x) 时,实际上执行的是:

- self.fc1.__call__(x)(Python 的隐式调用)

- 而 nn.Module 的 __call__ 方法会调用子类的 forward 方法(即 self.fc1.forward(x))。这个方法就是个前向计算方法。

relu是torch.relu()这个函数为了保持写法一致,又封装成了nn.ReLU()这个类。来保证接口的一致性

PyTorch 官方强烈建议使用 self.fc1(x),因为它会触发完整的前向传播流程(包括钩子函数)这是 PyTorch 的核心设计模式,几乎所有组件(如 nn.Conv2d、nn.ReLU、甚至整个模型)都可以这样调用。

# 不带参数的call方法 class Counter: def __init__(self): self.count = 0 def __call__(self): self.count += 1 return self.count # 使用示例 counter = Counter() print(counter()) # 输出: 1 print(counter()) # 输出: 2 print(counter.count) # 输出: 2

类名后跟(),表示创建类的实例(对象),仅在第一次创建对象时发生。

call方法无参数的情况下,在实例化之后,每次调用实例时触发 __call__ 方法

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/2/3 13:00:12

力扣139

/* 还是和决策树一样&#xff0c;从s的第0个位置开始遍历&#xff0c; 然后只要word是s的子串&#xff0c;那么则置为true&#xff0c;而且要注意边界条件 */ class Solution { public:bool wordBreak(string s, vector<string>& wordDict) {vector<bool> dp(s…

作者头像 李华
网站建设 2026/2/7 8:46:27

终极指南:3分钟掌握Vue3低代码平台,让开发效率飙升500%

终极指南&#xff1a;3分钟掌握Vue3低代码平台&#xff0c;让开发效率飙升500% 【免费下载链接】vite-vue3-lowcode vue3.x vite2.x vant element-plus H5移动端低代码平台 lowcode 可视化拖拽 可视化编辑器 visual editor 类似易企秀的H5制作、建站工具、可视化搭建工具 …

作者头像 李华
网站建设 2026/2/8 8:35:15

Windows平台Miniconda安装教程:告别Anaconda臃肿问题

Windows平台Miniconda安装与高效环境管理实战指南 在人工智能项目日益复杂的今天&#xff0c;你是否曾遇到过这样的场景&#xff1a;刚跑通一个PyTorch模型&#xff0c;却因为另一个项目需要TensorFlow而陷入依赖冲突&#xff1f;或者接手同事代码时&#xff0c;发现“在我机器…

作者头像 李华
网站建设 2026/2/2 2:24:05

大麦抢票神器DamaiHelper:告别手速焦虑的智能解决方案

还在为抢不到心仪的演唱会门票而烦恼吗&#xff1f;&#x1f3b5; 面对秒光的热门场次&#xff0c;手动操作往往力不从心。DamaiHelper作为一款专为大麦网设计的自动化抢票工具&#xff0c;将彻底改变你的购票体验&#xff0c;让你轻松拥有心仪演出的入场券&#xff01; 【免费…

作者头像 李华
网站建设 2026/2/8 12:06:13

掌握.NET调试:dnSpy异常分析与堆栈跟踪终极指南

掌握.NET调试&#xff1a;dnSpy异常分析与堆栈跟踪终极指南 【免费下载链接】dnSpy 项目地址: https://gitcode.com/gh_mirrors/dns/dnSpy 在.NET开发中&#xff0c;你是否经常遇到"对象引用未设置到实例"这类让人头疼的异常&#xff1f;当程序在运行时抛出异…

作者头像 李华
网站建设 2026/2/7 10:44:36

Linux基础命令

Linux基础命令 用户添加 sudo useradd wzx杀死所有wzx用户进程 sudo pkill -9 -u wzx用户更改名字 usermod -l wzxs wzxpwd&#xff1a;查找当前所在文件路径 which&#xff1a;查找某个命令在那个路径下 例如&#xff1a;which pwd ls命令&#xff1a; ls -a:展示所有包括隐藏…

作者头像 李华