知識點回歸:
- CPU性能的查看:看架構代際、核心數、線程數
- GPU性能的查看:看顯存、看級別、看架構代際
- GPU訓練的方法:數據和模型移動到GPU device上
- 類的call方法:為什么定義前向傳播時可以直接寫作self.fc1(x)
判斷 CPU 的好壞需要綜合考慮硬件參數、性能表現、適用場景:
- 看架構代際,新一代架構通常優化指令集、緩存設計和能效比。如Intel 第 13 代 i5-13600K 比第 12 代 i5-12600K 多核性能提升約 15%
- 看制程工藝,制程越小,晶體管密度越高,能效比越好,如AMD Ryzen 7000 系列(5nm)比 Ryzen 5000 系列(7nm)能效比提升約 30%
- 看核心數:性能核負責高負載任務(如游戲、視頻剪輯),單核性能強。能效核負責多任務后臺處理(如下載、殺毒),功耗低。如游戲 / 辦公:4-8 核足夠,內容創作 / 編程:12 核以上更優
- ?看線程數目
- 看頻率,高頻適合單線程任務(如游戲、Office),低頻多核適合多線程任務(如 3D 渲染)
- 支持的指令集和擴展能力
如何衡量GPU的性能好壞?
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、GPU訓練
要讓模型在 GPU 上訓練,主要是將模型和數據遷移到 GPU 設備上。在 PyTorch 里,.to(device) 方法的作用是把張量或者模型轉移到指定的計算設備(像 CPU 或者 GPU)上:
- 對于張量(Tensor):調用 .to(device) 之后,會返回一個在新設備上的新張量
- 對于模型(nn.Module):調用 .to(device) 會直接對模型進行修改,讓其所有參數和緩沖區都移到新設備上
在進行計算時,所有輸入張量和模型必須處于同一個設備,要是它們不在同一設備上,就會引發運行時錯誤。并非所有 PyTorch 對象都有 .to(device) 方法,只有繼承自 torch.nn.Module 的模型以及 torch.Tensor 對象才有此方法
我這里用的kaggle的云服務器(算力平臺真的很推薦,環境啥的都配置好了,猛猛用就行),遷移到GPU時先看看CUDA,再設置一下設備
import torch# ----------- cell 1 ------------
# 檢查CUDA是否可用
if torch.cuda.is_available():print("CUDA可用!")# 獲取可用的CUDA設備數量device_count = torch.cuda.device_count()print(f"可用的CUDA設備數量: {device_count}")# 獲取當前使用的CUDA設備索引current_device = torch.cuda.current_device()print(f"當前使用的CUDA設備索引: {current_device}")# 獲取當前CUDA設備的名稱device_name = torch.cuda.get_device_name(current_device)print(f"當前CUDA設備的名稱: {device_name}")# 獲取CUDA版本cuda_version = torch.version.cudaprint(f"CUDA版本: {cuda_version}")# 查看cuDNN版本(如果可用)print("cuDNN版本:", torch.backends.cudnn.version())else:print("CUDA不可用。")# ------------ cell 2 ------------
# 設置GPU設備
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
print(f"使用設備: {device}")
代碼改動很小,就是張量和模型實例化的時候改一改
# 轉換為張量
x_train = torch.FloatTensor(x_train).to(device)
x_test = torch.FloatTensor(x_test).to(device)
y_train = torch.LongTensor(y_train).to(device)
y_test = torch.LongTensor(y_test).to(device)# 實例化網絡
model = MLP().to(device)
CPU和GPU都用kaggle的跑,CPU訓練用時0.3726秒,GPU訓練用時0.7579秒,照理說GPU會更快對吧。但對于非常小的數據集和簡單的模型,CPU通常會比GPU更快,本質是因為GPU在計算的時候,相較于cpu多了3個時間上的開銷:
- 數據傳輸開銷 (CPU 內存 <-> GPU 顯存),對于少量數據和非常快速的計算任務,這個傳輸時間可能比 GPU 通過并行計算節省下來的時間還要長
- 核心啟動開銷 (GPU 核心啟動時間),GPU執行的每個操作都涉及到在GPU上啟動一個“核心”(kernel),如果核心內的實際計算量非常小(本項目的小型網絡和鳶尾花數據),這個啟動開銷在總時間中的占比就會比較大
- 性能浪費:計算量和數據批次,這個數據量太少,GPU的很多計算單元都沒有被用到,即使用了全批次也沒有用到的全部計算單元
所以綜上所述,GPU適合大型數據集,大型模型,復雜繁瑣的并行計算操作
2、__call__方法
在 Python 中,__call__ 方法是一個特殊的魔術方法(雙下劃線方法),如果一個類定義了 __call__?方法,它的實例可以通過 實例名() 的方式調用,就像調用函數一樣,這種特性使得對象可以表現得像函數,同時保留對象的內部狀態
舉個例子,之前訓練時要選定損失函數,nn.CrossEntropyLoss() 是一個類,criterion 是它的實例,criterion(output, y_train) 實際上是?criterion.__call__(output, y_train),這個__call__方法內部會計算交叉熵損失,并返回結果
criterion = nn.CrossEntropyLoss() # 實例化損失函數
loss = criterion(output, y_train) # 像函數一樣調用
說白了,PyTorch 的損失函數、模型層(如?nn.Linear
)等模塊都通過__call__方法來實現相應的功能,它們內部可能保存了參數和狀態,每次調用時利用這些狀態進行計算,所以用的時候一定要記住實例化(加括號),忘了好多次總會出莫名其妙的錯誤
判斷到底是函數還是類的實例化,可以看官方文檔決定,但是看看命名也是好方法:
-
類名:通常首字母大寫(如?
CrossEntropyLoss
, torch.FloatTensor) -
函數名:全小寫(如
sum,add
)
@浙大疏錦行