文|Seraph
01 | 模型並行與數據並行
- 模型並行:分佈式系統中的不同 GPU 負責網絡模型的不同部分。例如,神經網絡模型的不同網絡層被分配到不同的 GPU,或者同一層內部的不同參數被分配到不同 GPU;
數據並行:不同的 GPU 有同一個模型的多個副本,每個 GPU 分配到不同的數據,然後將所有 GPU 的計算結果按照某種方式合併。 - 模型並行各部分存在一定的依賴性,規模伸縮性差,也就是不能隨機增加GPU的數量
- 數據並行則各部分獨立,規模伸縮性好,實際訓練中更常用,提速效果也更好
01 | 消息傳遞並行程序設計
- 消息傳遞並行程序設計值用戶必須通過顯示地發送和接受消息來實現處理機間的數據交換。
這種編程方式是大規模並行處理機(MPP)和機羣(Cluster)採用的主要變成方式。 - 消息傳遞程序設計要求用戶很好地
分解問題
,組織進程間的數據交換。 - 消息傳遞程序設計是當前並行計算領域的一個非常重要的
並行程序設計方式
。
02 | MPI
- MPI(Message Passing Interface)是消息傳遞函數庫的標準規範,不是特指某一個具體實現。
- MPI具有高可移植性。
03 | 安裝OpenMPI
見Horovod使用文中OpenMPI安裝步驟。
注:Horovod的數據傳遞是基於MPI。
04 | Ring Allreduce算法
- Ring Allreduce算法是高性能計算(HPC)領域內一個衆所周知的算法。
- Horovod的梯度同步和權值同步就採用了ring-allreduce算法。
具體見Bringing HPC Techniques to Deep Learning
05 | 使用OpenMPI
一、函數介紹
函數原型 | 含義 |
---|---|
int MPI_Init(int *argc, char **argv) | MPI_Init是MPI程序的第一個調用,完成MPI程序的初始化操作,參數接受main函數的參數,所以我們的main函數一定要帶參數,以接收當我們運行程序時附加的參數。 |
int MPI_Finalize(void) | MPI_Finalize是MPI程序的最後一個調用,標誌並行代碼的結束,結束除主進程的其他進程 |
int MPI_Comm_size(MPI_Comm comm, int *size) | 獲取進程個數 |
int MPI_Comm_rank(MPI_Comm comm, int *rank) | 獲取當前進程的rank值,假如進程總數爲p,則rank值爲0~p-1 |
int MPI_Send(void *buf, int count, MPI_Datatype datatye, int dest, int tag, MPI_Coom comm) | Blocking Send阻塞發送函數,其中參數含義如下:IN buf 發送緩衝區的起始地址,IN count 發送信息的元素個數,IN datatype 發送信息的數據類型,IN dest 目標進程的rank值,必須指定唯一的接收者,IN tag 消息標籤,IN comm 通信域。 |
int MPI_Recv(void *buf, int count, MPI_Datatype, int source, int tag, MPI_Comm comm, MPI_Status *status) | Blocking Receive阻塞接收函數,其中參數含義如下:OUT buf 接收緩衝區的起始地址,IN count 接收緩衝區的大小,IN datatype 接收信息的數據類型,IN dest 目標進程的rank值,IN tag 消息標籤,用於標識不同的消息, IN comm 通信域, OUT status 對象status包含實際接收到的消息的相關信息 |
int MPI_Get_cout(MPI_Status status, MPI_Datatype datatype, int *count) | 返回實際接收的數據長度,其中參數含義如下:IN status 接受操作的返回值,IN datatype 接收緩衝區中的元素的數據類型,OUT count 接收消息中的元素個數 |
double MPI_Wtime( void ) | 返回調用的處理器已經過的時間 |
int MPI_Bcast(void *buf, int count, MPI_Datatype datatype, int root, MPI_Comm comm) | rank號爲root 進程廣播一條消息到通信域組內的所有進程 |
int MPI_Get_processor_name(char *name, int *resultlen) | 獲取本進程的機器名,其參數含義如下:OUT name 機器名,OUT resultlen 結果長度。 |
int MPI_Reduce(const void *sendbuf, void *recvbuf, int count, MPI_Datatype datatype, MPI_Op op, int root, MPI_Comm comm); | 將通信域內各進程的同一個變量參與規約計算,並向指定的進程輸出計算結果。其中各參數含義如下:IN sendbuf 發送緩衝區的起始地址,OUT recvbuf 接收緩衝區的起始地址, IN datatye 發送元素的類型,IN op 規約操作, IN root 主進程rank |
三、MPI基礎數據類型
MPI數據類型 | C 語言數據類型 |
---|---|
MPI_CHAR | signed char |
MPI_SHORT | signed short int |
MPI_INT | signed int |
MPI_LONG | signed long |
MPI_UNSIGNED_CHAR | unsigned char |
MPI_UNSIGNED_SHORT | unsigned short int |
MPI_UNSIGNED | unsigned int |
MPI_UNSIGNED_LONG | unsigned long int |
MPI_FLOAT | float |
MPI_DOUBLE | double |
MPI_LONG_DOUBLE | long double |
MPI_BYTE | 8 binary digits |
MPI_PACKED | data packed or unpacked with MPI_Pack()/MPI_Unpack |
二、運行指令
指令 | 含義 |
---|---|
mpicc | c編譯代碼,示例:mpicc -o hello hello.c |
mpirun | 運行MPI並行程序,eg.mpirun -np 4 hello ,其中-np 表示進程數 |
三、常量
常量名 | 含義 |
---|---|
MPI_COMM_WORLD | 默認的缺省通信域。在MPI_Init執行後,MPI程序的所有進程形成一個組,這個組的通信域即爲MPI_COMM_WORLD。指定通信域是MPI通信操作函數必不可少的參數,用於限定參加通信的進程的範圍。 |
MPI_ANY_SOURCE | 用於MPI_Recv函數的source參數賦值,表示接收任意處理器的數據 |
MPI_ANY_TAG | 匹配任意tag值的消息 |
MPI_SUCCESS | 函數返回成功值 |
三、C語言示例代碼
- Hello World
#include <stdio.h>
#include <mpi.h>
int main (int argc, char **argv)
{
int myid, numprocs;
MPI_Init(&argc, &argv);
MPI_Comm_rank(MPI_COMM_WORLD, &myid);
MPI_Comm_size(MPI_COMM_WORLD, &numprocs);
MPI_Finalize();
return 0;
}
- MPI並行通信
#include <stdio.h>
#include <mpi.h>
int main (int argc, char **argv)
{
int myid, numprocs,source;
MPI_Status status;
char message[100];
MPI_Init(&argc, &argv);
MPI_Comm_rank(MPI_COMM_WORLD, &myid);
MPI_Comm_size(MPI_COMM_WORLD, &numprocs);
if(myid!=0)
{
sprintf(message,"Greetings from process %d!", myid);
MPI_Send(message, strlen(message)+1, MPI_CHAR, 0, 99, MPI_COMM_WORLD);
}
else
{
for(source = 1; source<numprocs; source++)
{
MPI_Recv(message, 100, MPI_CHAR, source, 99, MPI_COMM_WORLD, &status);
printf("%s\n", message);
}
}
MPI_Finalize();
return 0;
}
- 求PI
題目:
令函數
則有
#include <stdio.h>
#include <mpi.h>
#include <math.h>
double f(double);
double f(double a)
{
return (4.0/(1.0+a*a));
}
int main(int argc, char **argv)
{
int done = 0, n, myid, numprocs, i;
double PI25DT = 3.141592653589793238462643;
double mypi, pi, h, sum, x;
double startwtime = 0.0, endwtime;
int namelen;
char processor_name[MPI_MAX_PROCESSOR_NAME];
MPI_Init(&argc, &argv);
MPI_Comm_size(MPI_COMM_WORLD, &numprocs);
MPI_Comm_rank(MPI_COMM_WORLD, &myid);
MPI_Get_processor_name(processor_name, &namelen);
fprintf(stderr, "Process %d on %s\n", myid, processor_name);
n=0;
while(!done)
{
if(myid==0)
{
if(n==0)
{
n=100;
}
else
{
n=0;
}
startwtime = MPI_Wtime();
}
MPI_Bcast(&n, 1, MPI_INT, 0, MPI_COMM_WORLD);
if(n==0)
{
done = 1;
}
else
{
h = 1.0/(double)n;
sum = 0.0;
for(i = myid + 1; i<=n; i+=numprocs)
{
x = h*((double)i - 0.5);
sum += f(x);
}
mypi = h*sum;
MPI_Reduce(&mypi, &pi, 1, MPI_DOUBLE, MPI_SUM, 0, MPI_COMM_WORLD);
if(myid == 0)
{
printf("pi is approximately %.16f, Error is %.16f\n", pi, fabs(pi-PI25DT));
endwtime = MPI_Wtime();
printf("wall clock time = %f\n", endwtime-startwtime);
}
}
}
MPI_Finalize();
return 0;
}
06 | MPI與Hadoop的對比
- MPI是計算與存儲分離,Hadoop是計算向存儲遷移。
- 在MPI中數據存儲的節點和數據處理的節點往往是不同的,一般在每次計算開始時MPI需要從數據存儲節點讀取需要處理的數據分配給各個計算節點,然後進行數據處理,即MPI的數據存儲和數據處理是分離的。對於計算密集型的應用MPI能表現出良好的性能,但對於處理TB級數據的數據密集型應用,大量的數據在節點間進行交換,網絡通信時間將成爲影響系統性能的重要因素,性能會大大降低。
- 在Hadoop中有HDFS文件系統的支持,數據是分佈式存儲在各個節點的,計算時各節點讀取存儲在自己節點的數據進行處理,從而避免了大量數據在網絡上的傳輸,實現“計算向存儲的遷移”。
- MPI無法應對節點的失效。如果MPI在運行過程出現節點失效及網絡通信中斷,則只有返回並退出,MPI沒有提供一套機制處理節點失效後的備份處理方案問題,所以如果中途出現問題,所有的計算將重新開始,這是非常耗時的。Hadoop爲應對服務器的失效,在數據備份上下了很大的功夫,數據塊會形成多個副本存儲在不同的地方,一般會有3個副本,採用簡單化的跨機架數據塊存儲,最大限度避免了數據丟失,數據的安全性得到了保證。