一、GPU 檢測
% 檢測 GPU 數量
gpuDeviceCount("available")
% 列出 GPU
gpuDeviceTable
在最後輸出信息中的最後一列可以看到該 GPU 沒有被選擇
設置 GPU,並查看 GPU 的屬性
該設備已被成功選擇
檢測是否具有支持的 GPU 可用於計算,輸出為1表示有
tf = canUseGPU()
其他查詢屬性的相關命令
% 創建默認 GPU 設備的對象,即查詢當前顯卡信息
D = gpuDevice;
% 查詢所有可用的 GPU
for ii = 1:gpuDeviceCount
D = gpuDevice(ii);
fprintf(1,'Device %i has ComputeCapability %s \n', ...
D.Index,D.ComputeCapability)
end
二、運算測試1、下面進行將運算傳遞 GPU 進行簡單的測試
% 使用gpuArray功能將其發送到 GPU
A = gpuArray([1 0 1; -1 -2 0; 0 1 -1]);
% 返回一個列向量,其中包含方陣 A 的特徵值
e = eig(A);
% 列印輸出結果
fprintf("%i \n",e);
輸出結果
8.019377e-01
-2.246980e+00
-5.549581e-01
2、創建一個1000 × 1000的 gpuArray 的 randn 值
G = randn(1000,'double','gpuArray');
G
可以正常輸出結果,沒有問題。經過查閱資料,大部分數據生成類函數可以添加 'gpuArray' 參數就可實現 GPU 計算,還有一部分運算類函數或值可以通過 gpuArray 包含其他函數實現,還有深度學習中也有其他不同的方法實現,另外 gather 函數可以將 GPU 數據回傳給 CPU。
主要步驟有以下兩步:
將數據從 CPU 中搬入 GPU,M = gpuArray(M)
在GPU中計算完成後,將數據搬出到 CPU 存儲,例如 M = gather(M)
例如下面求解一個線性方程組使用 gpuArray( ) 方式
tf = canUseGPU()
% 在 CPU 上創建數據。
N = 1000;
A = rand(N);
b = rand(N,1);
% 將矩陣 A 傳輸到 GPU(如果有可用的 GPU)
if tf
A = gpuArray(A);
end
% 求解線性方程組
x = A\b;
支持 GPU 的機器學習函數連結:
https://ww2.mathworks.cn/help/stats/referencelist.html?type=function&capability=gpuarrays
支持 GPU 的深度學習函數連結:
https://ww2.mathworks.cn/help/deeplearning/referencelist.html?type=function&capability=gpuarrays
但是有一個小問題是它和原生的 rand 生成的結果不一樣,查閱官網發現它們之間的算法調度不一樣導致的,共有三種隨機數生成器算法。
>> rand(1,4)
ans =
0.8147 0.9058 0.1270 0.9134
>> rand(1,4,'gpuArray')
ans =
0.4320 0.3710 0.1217 0.7504
若非要和原生 rand 函數使用的隨機數算法一致可以通過下面方式進行解決:分別使用 rng (控制隨機數生成器) 和 gpurng (控制 GPU 計算的隨機數生成) 在 CPU 和 GPU 上設置生成器算法和種子
% 原生 rand
sc = rng(1,'Threefry');
Rc = rand(1,4)
% GPU rand
sg = gpurng(1,'Threefry');
Rg = rand(1,4,'gpuArray')
輸出結果
Rc =
0.1404 0.8197 0.1073 0.4131
Rg =
0.1404 0.8197 0.1073 0.4131
官方提到也可以人工控制,暫時先了解這麼多。
3、官方提供的測試代碼
% 生成 GPU 上常用的分布隨機數字的數據集
dist = randn(1e5,1e4,"gpuArray");
% 確定是否為 GPU 陣列dist
TF = isgpuarray(dist)
提示我內存不足,我一開始怎麼也不相信……,事實證明我高估我的顯卡能力了,將值的範圍改小就能行的通了
Create a 1000-by-1000 gpuArray of randn values with underlying class double
Out of memory on device. To view more detail about available
memory on the GPU, use 'gpuDevice()'. If the problem persists,
reset the GPU by calling 'gpuDevice(1)'.
我這裡改成了 1000 × 1000
dist = randn(1e3,1e3,"gpuArray");
確定是否為 GPU 陣列 dist
>> TF = isgpuarray(dist)
TF =
logical
1
使用 GPU 陣列輸入參數執行功能。例如,計算每個列中的樣本偏斜度。由於是 GPU 陣列,因此該功能會在 GPU 上執行,並將結果作為 GPU 陣列返回。
skew = skewness(dist);
% 驗證輸出是否為 GPU 陣列
TF = isgpuarray(skew)
輸出結果
TF =
logical
1
三、評估 GPU 執行速度評估 GPU 上的功能執行時間,並將性能與 CPU 上的執行進行比較。如果一個函數具有 gpuArray 支持,隨著觀測次數的增加,GPU 上的計算通常會比 CPU 更具性能。
使用 gputimeit (並行計算工具箱)功能在幾秒鐘內測量功能運行時間。優於使用 GPU 的功能,因為它可確保操作完成並補償間接費用。
下面在 GPU 上計算一個很大的值,其中是 12000 乘 400 矩陣,即 400 乘 12000
A = rand(12000,400,'gpuArray');
B = rand(400,12000,'gpuArray');
f = @() sum(A.' .* B, 1);
t = gputimeit(f)
輸出結果。可以看到處理時間是非常快的。
t =
0.0013
CPU VS GPU
%% 用CPU進行計算
num = zeros(101,1);
A = randn(1e5,1,'single');
f = @() filter(num,1,A);
timeit(f)
%% 用GPU進行計算
A_gpu = randn(1e8,1,'single','gpuArray');
num_gpu = gpuArray(num);
f_gpu = @() filter(num_gpu,1,A_gpu);
gputimeit(f_gpu)
輸出結果
ans =
7.7344e-04
ans =
0.0170
現在有個問題是當矩陣維數比較小的時候,GPU 計算速度並沒有CPU 計算速度快,只有當矩陣維數比較大的時候,GPU 才有加速效果,測試代碼如下
clear all clc
M = rand(5, 5);
% 下面維度越大,GPU 的運算優勢越能凸顯出來
% M = rand(50, 50);
% M = rand(500, 500);
% M = rand(5000, 5000);
tt1 = 0;
for i = 1:1000
tic
N = M .* M;
t1 = toc;
tt1 = tt1 + t1;
end
tt1
M = gpuArray(single(M));
tt2 = 0;
for i = 1:1000
tic
N1 = M .* M;
t2 = toc;
tt2 = tt2 + t2;
end
tt2
輸出結果
tt1 =
8.6960e-04
tt2 =
0.0121
我下面接著循環 2000 × 2000 的矩陣,進行比對後發現,大約在 500 × 500 左右差距越來越來大,結果如下圖所示。
測試平臺(筆記本):
若你的訓練的數據集數據量維度大,一般選擇在 GPU 上跑就行,若維度較小,有一列或行低於 500 就沒有必要了。
四、CPU 代碼轉 GPU 代碼進行實測實測修改張濱碩士 code1 代碼後將數據傳遞到 GPU 上運算運算速度會慢許多,因為數據的維度太小,下面是時間損耗的差別。
GPU 上運算
CPU 上運算
GPU 時間開銷要比 CPU 大 7 倍左右,那這代碼不如不改了……,所以數據量不大,就不要用 GPU 了,學生機的 GPU 內存一般也比較小,當數據量太大時,也可能會出現一定的問題。
當我看到 code3 代碼時眼前一亮,這就是我一直要找的測試數據,這裡的圖片數據是一種稀疏矩陣的數據,目前暫時 MATLAB 的 gpuArray 不適用,這個問題在 15 年就有人反映過了,但是官方一直沒有更新,我在 Git 上找到一個 gpuSparse 工具,貌似可以實現,但是我調試了一會沒有成功。下面我將除了 sparse double 類型的其他高維度數據傳到 GPU 上運算,效果馬上就上來了,若是能解決 sparse double 問題,應該會更棒,下面是測試結果:
未修改前 CPU 上運算
修改後 GPU 上運算
當我測試另個一訓練層時,訓練速度更快了:
我是拿了其中的幾個小的訓練層做的測試,第一個測試內循環中 GPU 每個小循環平均不超過 0.02s,GPU 的運算時間相比 CPU 縮短了 3.5 倍, 這個 code3 我最開始跑大概用了 20 個小時,如果機器和代碼都穩定的話總訓練時間至少會降到 7 小時,終於可以節約時間了……,CPU 不會有被棧滿的情況發生了 ,不用擔心因為電腦太卡導致 Explore 奔潰了,下圖是我之前訓練過程中 CPU 的佔用情況,電腦除了訓練外,不能做別的事情。
現在的 CPU 佔用率降到了 26%(平均)
現在的 GPU 利用率 86%
CPU 被解放後,普通軟體桌面窗口所需的 GPU 資源有限,此時電腦也可做更多的事情。
不過代碼穩定性肯定還存在缺陷,因為這個程序的代碼過於複雜,代碼量和函數調用都挺多的,一旦某個地方出現了小問題有可能需要大改動,也有可能改了很久後也沒有解決,這些都很正常。
五、總結實現起來不複雜,有能力可以嘗試一下,萬一節約了一半的時間也是很賺的
數據量小,數據維度小沒必要放在 GPU 上,CPU 計算更快。
代碼量龐大和複雜的程序一般也不要寫成或改成在 GPU 上運行的程序,還是 CPU 版本的穩定,慢些就慢些吧……
遇到 sparse 類型的數據一般不要改動了,官方都沒有實現其 GPU 上計算,可能是有一定的缺陷的。
有些算法中用 gpuArray 函數運算速度可能會變快,也可能會變慢,需要自己調試,比如 code3 中我將激活函數 sigmod 的計算放到 GPU 上變慢了……
並不是所有的數據集合都是需要添加 gpuArray 參數,參與運算的一個重要數據集使用了 gpuArray 參數,那麼相關聯計算的數據集就不再需要額外添加了。
靈活使用 gather 函數回調會讓程序跑的更快,將低維度的大數據量回調到 CPU 上計算更快。