MPI——基础知识

基础知识

编译与执行

编译和运行程序的细节主要取决于系统,很多系统都成为mpicc的命令来编译程序

mpicc -g -Wall -o  mpi_hello mpi_hello.c

mpicc是C语言编译器的包装脚本(wrapper script)。包装脚本的主要目的是运行某个程序。指定使用c99

mpiexec -n <number of processes> ./mpi_hello

很多系统还支持用mpiexec命令来启动程序

例如:使用1个进程运行程序

mpiexec -n 1 ./mpi_hello

使用4个进程运行程序

mpiexec -n 4 ./mpi_hello

对于程序

#include <stdio.h>
#include <string.h>
#include <mpi.h>

const int MAX_STRING = 100;

int main(){
    char greeting[MAX_STRING];
    int comm_sz;
    int my_rank;
    MPI_Init(NULL, NULL);
    MPI_Comm_size(MPI_COMM_WORLD, &comm_sz);
    MPI_Comm_rank(MPI_COMM_WORLD, &my_rank);

    if(my_rank != 0) {
        sprintf(greeting, "Greeting from process %d of %d!",my_rank,comm_sz);
        MPI_Send(greeting, strlen(greeting)+1, MPI_CHAR, 0, 0, MPI_COMM_WORLD);
    }
    else {
        printf("Greeting from process %d of %d!\n",my_rank, comm_sz);
        for(int q = 1; q< comm_sz;q++){
            MPI_Recv(greeting, MAX_STRING, MPI_CHAR, q,0,MPI_COMM_WORLD, MPI_STATUS_IGNORE);
            printf("%s\n",greeting);
        }
    }
    MPI_Finalize();
    return 0;
}

由于里面有C语言的新特性,可以指定

mpicc -g -Wall -std=c11  -o hello hello.c

运行4个进程,得到的输出

➜  mpiexec -n 4 ./hello
Greeting from process 0 of 4!
Greeting from process 1 of 4!
Greeting from process 2 of 4!
Greeting from process 3 of 4!

MPI程序

所有的MPI定义的标识符都是由字符串MPI_开始,下划线后的第一个字符大写,表示函数名和MPI定义的类型。MPI定义的宏和常量的所有字母都是大写的。

MPI_Init和MPI_Finalize

调用MPI_Init是为了告知系统进行所有必要的初始化设置。

int MPI_Init(
	int * argc_p /*in/out*/,
    char ** argv_p /*in/out*/
);

参数argc_pargv_p是指向参数argcargv的指针。当不需要的时候设置为NULL。

调用MPI_Finalize是为了告知MPI系统MPI已经使用完毕。为MPI分配的任何资源都可以释放了。

MPI程序的基础构架

#include <mpi.h>

int main(int argc, char * argv[]) {
    MPI_Init(&argc, &argv);
    ...
    MPI_Finalize();
    return 0;
}

通信子

通信子(communicator)指的是一组可以互相发送信息的进程集合。MPI_Init的其中一个目的,是在用户启动程序时,定义有用户启动的所有进程组成的通信子。称为MPI_COMM__WORLD

int MPI_Comm_size(
	MPI_Comm comm ,
    int * comm_sz_p
);
int MPI_Comm_rank(
	MPI_Comm comm,
    int * my_rank_p
);

第一个参数是一个通信子,它所属的类型是MPI的通信子定义的特殊类型:MPI_Comm

MPI_Comm_size 函数在它的第二个参数返回通信子的进程数。MPI_Comm_rank函数在它的第二个参数返回正在调用进程的通信子中的进程号。

MPI_Send

1 , 2 , ⋯   . c o m m _ s z − 1 1,2,\cdots.comm\_sz-1 1,2,.comm_sz1号进程执行的发送其实很复杂的。

int MPI_Send(
	void * 	msg_buf_p,
    int		msg_size,
    MPI_Datatype 	msg_type,
    int 	dest,
    int 	tag,
    MPI_Comm	commmunicator
);

第一个参数msg_buf_p是一个指向消息内容的内存块的指针。

第二个参数和第三个参数:msg_size和msg_type 指定了要发送的数据量

MPI数据类型C语言数据类型
MPI_CHARsigned char
MPI_SHORTsigned short int
MPI_INTsigned int
MPI_LONGsigned long int
MPI_LONG_LONGsigned long long int
MPI_UNSIGNED_CHARunsigned char
MPI_UNSIGNED_SHORTunsigned short int
MPI_UNSIGNEDunsigned int
MPI_UNSIGNED_LONGunsigned long int
MPI_FLOATfloat
MPI_DOUBLEdouble
MPI_LONG_DOUBLElong double
MPI_BYTE
MPI_PACKED

第四个参数:dest指定了要接收消息的进程的进程号

第五个参数:tag是个非负int型,用于区分看上去完全一样的消息。

最后一个参数:是一个通信子,用于指定通信范围。通信子指的是一组互相发送消息的进程的集合。一个通信子的进程所发送的消息不能被另一个通信子的进程所接收。

MPI_Recv

int MPI_Recv(
    void * msg_buf_p,
    int 	buf_size,
    MPI_Datatype	buf_type,
    int 	source,
    int 	tag, 
    MPI_Comm	communicator,
    MPI_Status*	status_p
);

参数source用来指定了接收消息应该从哪个进程发送来的,参数tag要与发送消息的参数tag相匹配。参数communicator必须与发送进程所用的通信子匹配。

消息匹配

假定 q q q号进程调用MPI_Send()函数

MPI_Send(send_buf_p, send_buf_sz, send_type, dest, send_tag, send_comm)

假定 r r r号进程调用了MPI_Recv()函数

MPI_Recv(recv_buf_p, recv_buf_sz, recv_type, src, recv_tag, recv_comm, &status)

则q号进程调用MPI_Send函数所发送的消息可以被r号进程调用MPI_Recv函数接收,如果

  • recv_comm = send_comm
  • recv_tag = send_tag
  • dest=r & src =q

在多数的情况下,满足下面的规则就可以了

  • 如果recv_type = send_type 同时recv_buf_sz ≥ \ge send_buf_sz 那么由q号进程发送的消息就可以被r号进程成功的接收。

一个进程可以接收多个进程发送的消息,接收进程并不知道其他进程执行发送消息的顺序。MPI提供了一个特殊的常量MPI_ANY_SOURCE,就可以传递给MPI_Recv。

for(i=1; i<comm_sz;i++) {
    MPI_Recv(result, result_sz, result_type, MPI_ANY_SOURCE, result_tag, comm, MPI_STATUS_IGNORE);
    Process_result(result);
}

类似的,一个进程有可能接收多条来自另一个进程的有着不同的标签的消息,并且接收进程不知道消息发送的顺序。

使用通配符(wildcard)参数时,需要注意的几点:

  • 只要接收者可以调用通配符参数,发送者必须指定一个进程号与另一个非负整数标签。此外,MPI使用的是所谓的推(push)通信机制,而不是拉(pull)通信机制。
  • 通信子参数没有通配符,发送者和接收者必须指定通信子。

status_p

MPI类型MPI_Status是一个有至少三个成员的结构,MPI_SOURCE,MPI_TAG和MPI_ERROR。假定程序有如下的定义:

MPI_Status status;

&status作为最后一个参数传递给MPI_Recv函数并调用它后,可以通过检查以下两个成员来确定发送者和标签。

status.MPI_SOURCE
status.MPI_TAG

接收量不是存储在应用程序可以直接访问到的域内,但是用户可以调用MPI_Get_count函数找到这个值。

int MPI_Get_count(
	MPI_Status * 	status_p,
    MPI_Datatype 	type,
    int *			count_p
);

MPI_Send和MPI_Recv

发送进程可以缓冲消息,也可以阻塞。

如果是缓冲消息,则MPI系统将会把消息放置在自己内部存储器里,并返回MPI_Send的调用。

如果是系统发生阻塞,那么它将一直等待,知道可以开始发送消息,并不立即返回MPI_Send的调用。

MPI_Send 的精确行为可以由MPI实现所决定的,但是,典型的实现方法有一个默认的消息截止大小,如果一条消息的大小小于截止大小,,就会被缓冲,如果大于截止大小,就会被阻塞。

MPI_Recv函数总是阻塞是,直到接收到一条匹配消息,当MPI_Recv函数调用返回时,就知道一条消息已经存储在接收缓冲区中,接收消息函数同样可以替代,系统检查是否有一条匹配的消息并返回。

MPI要求消息是不可超越的(nonvertaking),如果q号进程发送两条消息给r号进程,那么q号发送的第一条消息必须在第二条消息之前可用,但是如果消息来自不同的进程的消息的到达顺序是没有限制的。

MPI(Message Passing Interface)是一种并行计算的标准,可以实现跨节点的数据通信和计算任务分配。在多卡训练中,我们可以使用MPI来实现多个GPU之间的数据通信和计算协同。 在使用MPI进行多卡训练时,需要使用mpiexec命令来启动训练程序。mpiexec命令可以指定使用的GPU数量和每个GPU使用的进程数,例如: ``` mpiexec -n 4 -bind-to none python train.py ``` 其中,-n参数指定了使用的进程数量,这里使用4个进程,即使用4个GPU进行训练;-bind-to none参数表示不绑定进程和CPU核心,可以让MPI自动进行进程和GPU的分配;train.py为训练程序的入口文件。 在训练程序中,可以使用MPI相关的库进行数据通信和计算协同。例如,使用mpi4py库可以实现Python程序与MPI的交互,可以使用MPI的发送和接收函数进行数据传输,例如: ``` from mpi4py import MPI comm = MPI.COMM_WORLD rank = comm.Get_rank() size = comm.Get_size() if rank == 0: data = [i for i in range(10)] comm.send(data, dest=1) elif rank == 1: data = comm.recv(source=0) print(data) ``` 在这个例子中,使用MPI的COMM_WORLD对象获取进程的rank和size信息,rank为进程的编号,size为进程的总数。使用comm.send函数将数据发送给编号为1的进程,使用comm.recv函数从编号为0的进程接收数据,并打印接收到的数据。 这只是一个简单的例子,实际上在多卡训练中需要更复杂的数据通信和计算协同。但是使用MPI可以很好地实现多卡训练,并发挥多GPU的计算能力。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值