1. 版本历史¶
文档版本 |
日期 |
新增功能 |
---|---|---|
V3.1.0 |
2024年05月21日 |
支持各种传输通道 |
V3.2.0 |
2024年06月20日 |
支持avg redop |
V3.5.1 |
2025年06月23日 |
增加enable ll128环境变量 |
2. 词汇表¶
名词 |
描述 |
---|---|
ECCL |
Enflame Collective Communications Library,燧原科技集合通信库 |
GCU |
General Compute Unit, 燧原科技通用计算单元 |
CPU |
Central Processing Unit,中央处理器 |
NUMA |
Non Uniform Memory Access,非统一内存访问 |
SMP |
Symmetric Multi-Processing,对称多处理器 |
QPI |
Quick Path Interconnect,快速通道互联 |
UPI |
Ultra Path Interconnect,超路径互联 |
SHM |
Shared Memory,共享内存 |
NIC |
Network Interface Controller,网络适配器 |
RDMA |
Remote Direct Memory Access,远程直接数据存取 |
IB |
InfiniBand,无限带宽技术 |
RoCE |
RDMA over Converged Ethernet,基于以太网的远程直接数据存取 |
3. ECCL 简介¶
燧原公司集合通信库(Enflame Collective Communications Library, 简称为 ECCL)在基于公司自研的芯片 GCU 上提供了卡间通信的功能。
其主要提供了以下多个通信原语:
ecclAllReduce:将多卡上的数据进行规约操作,并将得到的结果保存在每张卡上
ecclBroadcast:将一张卡上的数据广播给多张卡
ecclReduceScatter:将多卡上的数据进行规约,并将得到的结果均匀分片后分发到每张卡上
ecclAllGather:将多卡上的数据聚合一起,每张卡上的原始数据大小需一致,并将得到的结果保存在每张卡上
ECCL 同样支持点对点通信功能:
ecclSend:将卡上的数据发送给另一张指定的卡
ecclRecv:从指定的卡上接收数据到本卡
4. ECCL 用户使用手册¶
4.1. ECCL 使用前置准备¶
TopsPlatform,使用燧原公司提供《topsplatform 安装说明手册》进行安装
4.2. ECCL 安装¶
安装 ECCL 提供的 deb 包(rpm 包):
dpkg -i eccl_*_amd64.deb (rpm -ivh eccl-*.x86_64.rpm)
确认是否安装成功, 请执行:
dpkg -l | grep eccl (rpm -qa eccl)
检查 "eccl.h"
头文件是否存在于目录 /usr/include/ 下
ls /usr/include/ | grep eccl.h
检查 "libeccl.so"
是否存在于目录 /usr/lib/ 下
ls /usr/lib/ | grep libeccl.so
4.3. Docker使用注意事项¶
通常情况下,docker 容器会默认限制共享内存的申请上限。当在容器中使用 ECCL 的时候,请按需调整容器内共享内存的大小。
下面展示如何在 Docker 启动命令行中添加参数来实现增加共享内存大小,推荐设置为4G的容量:
--shm-size=4g --ulimit memlock=-1
4.4. ECCL 使用方式¶
以下内容描述了如何使用 ECCL 进行集合通信操作。包括了 ECCL 接口描述、使用概述以及使用示例介绍
ECCL 对外接口¶
描述了 ECCL 集合通信库的对外接口以及相关的参数信息。包括了通信域生成与管理类接口、通信算子类接口、组操作接口以及点对点通信接口。详情可见《ECCL API 参考》文档。
ECCL 使用¶
ECCL 支持多种启动方式:
多进程启动,每个进程对应单张 GCU 卡
下列代码将展示如何使用 MPI 启动多进程,每个进程对应单张 GCU 卡,创建一个通信域并在通信域上运行集合通信算子:
// 检索有关线程的 MPI 信息:
int myRank, nRanks;
MPI_Comm_rank(MPI_COMM_WORLD, &myRank);
MPI_Comm_size(MPI_COMM_WORLD, &nRanks);
// 选择任一个rank生成一个通信域的 UniqueId,并把 Id 广播给域内其余的卡:
ecclUniqueId id;
if (myRank == 0) ecclGetUniqueId(&id);
MPI_Bcast((void *)&id, sizeof(id), MPI_BYTE, 0, MPI_COMM_WORLD);
// 根据以上信息可以生成一个通信域:
ecclComm_t comm;
ecclCommInitRank(&comm, nRanks, id, myRank);
// 在生成的通信域上调用相应的通信算子进行集合通信(以 ecclAllReduce 为例):
ecclAllReduce(..., comm);
// 集合通信结束时,释放相应的资源并摧毁通信域:
ecclCommDestroy(comm);
单进程多线程启动,每个线程对应单张 GCU 卡
代码展示如下:
#include <cstdio>
#include <thread>
#include <cassert>
#include "eccl.h"
#define ECCLCHECK(call) do { \
ecclResult_t res = call; \
if (res != ecclSuccess) { \
return res; \
} \
} while (0);
struct ecclTestArgs {
int rank;
int nranks;
ecclUniqueId id;
};
#define N_TEST_RANKS 4
thread_local pthread_t ecclTestThreads[N_TEST_RANKS];
thread_local struct ecclTestArgs ecclThreadArgs[N_TEST_RANKS];
void* ecclTestThread(void* args_) {
struct ecclTestArgs* args = (struct ecclTestArgs*)args_;
int myRank = args->rank;
int nRanks = args->nranks;
ecclUniqueId id;
ecclComm_t comm;
memcpy(&id, &args->id, sizeof(id));
printf("ecclTestThread myRank %d, nRanks%d\n", myRank, nRanks);
assert(topsSetDevice(myRank) == topsSuccess);
assert(ecclCommInitRank(&comm, nRanks, id, myRank) == ecclSuccess);
using T = int;
const size_t TEST_SIZE = 4 * 1024 * 1024;
size_t count = TEST_SIZE / sizeof(T);
T *srcH, *dstH;
// Alloc host buffer
srcH = (T *)malloc(TEST_SIZE);
dstH = (T *)malloc(TEST_SIZE);
// Init host buffer
for (int i = 0; i < count; ++i) {
srcH[i] = i;
dstH[i] = -1;
}
// Alloc device buffer
void *sendBuff, *recvBuff;
assert(topsMalloc(&sendBuff, TEST_SIZE) == topsSuccess);
assert(topsMalloc(&recvBuff, TEST_SIZE) == topsSuccess);
assert(
topsMemcpy(sendBuff, srcH, TEST_SIZE, topsMemcpyHostToDevice) == topsSuccess);
assert(
topsMemcpy(recvBuff, dstH, TEST_SIZE, topsMemcpyHostToDevice) == topsSuccess);
topsStream_t topsStream;
assert(topsStreamCreate(&topsStream) == topsSuccess);
ecclDataType_t dataType = ecclInt32;
ecclRedOp_t redOp = ecclSum;
assert(ecclAllReduce(sendBuff, recvBuff, count, dataType, redOp,
comm, topsStream) == ecclSuccess);
assert(topsStreamSynchronize(topsStream) == topsSuccess);
assert(
topsMemcpy(dstH, recvBuff, TEST_SIZE, topsMemcpyDeviceToHost) == topsSuccess);
for (int j = 0; j < count; ++j) {
if (abs(dstH[j] - srcH[j] * N_TEST_RANKS) > 0.00001) {
printf("Check failed: at srcH[%d]=%d, dstH[%d]=%d\n",
j, srcH[j], j, dstH[j]);
assert(false);
}
}
// Release resources
assert(topsStreamSynchronize(topsStream) == topsSuccess);
assert(topsFree(sendBuff) == topsSuccess);
assert(topsFree(recvBuff) == topsSuccess);
free(srcH);
free(dstH);
assert(topsStreamDestroy(topsStream) == topsSuccess);
assert(ecclCommDestroy(comm) == ecclSuccess);
printf("Test single process multiple thread allreduce passed!\n");
return args;
}
int main(int argc, char **argv) {
ecclUniqueId id;
ECCLCHECK(ecclGetUniqueId(&id));
for(int i= 0 ; i < N_TEST_RANKS; i++){
ecclThreadArgs[i].rank = i;
ecclThreadArgs[i].nranks = N_TEST_RANKS;
memcpy(&ecclThreadArgs[i].id, &id, sizeof(id));
pthread_create(ecclTestThreads+i, NULL, ecclTestThread, &ecclThreadArgs[i]);
}
for(int i= 0 ; i < N_TEST_RANKS; i++){
int err = pthread_join(ecclTestThreads[i], NULL);
if (err != 0) {
printf("Error waiting for %d pthread_join : %s\n", i, strerror(errno));
return -1;
}
}
return 0;
}
单进程单线程启动,单个线程对应多张 GCU 卡
代码展示如下:
#include <cstdio>
#include <thread>
#include "eccl.h"
#define TOPSCHECK(cmd) \
{ \
topsError_t error = cmd; \
if (error != topsSuccess) { \
fprintf(stderr, "error: '%s'(%d) at %s:%d\n", \
topsGetErrorString(error), error, __FILE__, __LINE__); \
exit(EXIT_FAILURE); \
exit(EXIT_FAILURE); \
} \
}
#define ECCLCHECK(call) do { \
ecclResult_t res = call; \
if (res != ecclSuccess) { \
return res; \
} \
} while (0);
int main(int argc, char **argv) {
const int N_TEST_RANKS = 4;
// Init eccl library
ecclComm_t comm[N_TEST_RANKS];
ecclUniqueId id;
ECCLCHECK(ecclGetUniqueId(&id));
ECCLCHECK(ecclGroupStart());
for (int i = 0; i < N_TEST_RANKS; ++i) {
TOPSCHECK(topsSetDevice(i));
ECCLCHECK(ecclCommInitRank(&comm[i], N_TEST_RANKS, id, i));
}
ECCLCHECK(ecclGroupEnd());
using T = int;
const size_t TEST_SIZE = 4 * 1024 * 1024;
size_t count = TEST_SIZE / sizeof(T);
T *srcH, *dstH;
// Alloc host buffer
srcH = (T *)malloc(TEST_SIZE);
dstH = (T *)malloc(TEST_SIZE);
// Init host buffer
for (int i = 0; i < count; ++i) {
srcH[i] = i;
dstH[i] = -1;
}
// Alloc device buffer
void *sendBuffs[N_TEST_RANKS], *recvBuffs[N_TEST_RANKS];
for (int i = 0; i < N_TEST_RANKS; ++i) {
TOPSCHECK(topsSetDevice(i));
TOPSCHECK(topsMalloc(&sendBuffs[i], TEST_SIZE));
TOPSCHECK(topsMalloc(&recvBuffs[i], TEST_SIZE));
}
// Init device buffer, copy data from host to device
for (int i = 0; i < N_TEST_RANKS; ++i) {
TOPSCHECK(topsSetDevice(i));
TOPSCHECK(
topsMemcpy(sendBuffs[i], srcH, TEST_SIZE, topsMemcpyHostToDevice));
TOPSCHECK(
topsMemcpy(recvBuffs[i], dstH, TEST_SIZE, topsMemcpyHostToDevice));
}
topsStream_t topsStream[N_TEST_RANKS];
for(int i = 0; i < N_TEST_RANKS; ++i) {
TOPSCHECK(topsSetDevice(i));
TOPSCHECK(topsStreamCreate(&topsStream[i]));
}
ecclDataType_t dataType = ecclInt32;
ecclRedOp_t redOp = ecclSum;
// Do allreduce
ECCLCHECK(ecclGroupStart());
for (int i = 0; i < N_TEST_RANKS; ++i) {
ECCLCHECK(ecclAllReduce(sendBuffs[i], recvBuffs[i], count, dataType, redOp,
comm[i], topsStream[i]));
}
ECCLCHECK(ecclGroupEnd());
for (int i = 0; i < N_TEST_RANKS; ++i) {
TOPSCHECK(topsSetDevice(i));
TOPSCHECK(topsStreamSynchronize(topsStream[i]));
}
// Check result
for (int i = 0; i < N_TEST_RANKS; ++i) {
TOPSCHECK(topsSetDevice(i));
TOPSCHECK(
topsMemcpy(dstH, recvBuffs[i], TEST_SIZE, topsMemcpyDeviceToHost));
for (int j = 0; j < count; ++j) {
if (abs(dstH[j] - srcH[j] * N_TEST_RANKS) > 0.00001) {
printf("Check failed: at recvBuffs[%d] srcH[%d]=%d, dstH[%d]=%d\n", i,
j, srcH[j], j, dstH[j]);
TOPSCHECK(topsErrorUnknown);
}
}
}
// Release resources
for (int i = 0; i < N_TEST_RANKS; ++i) {
TOPSCHECK(topsFree(sendBuffs[i]));
TOPSCHECK(topsFree(recvBuffs[i]));
TOPSCHECK(topsStreamDestroy(topsStream[i]));
ECCLCHECK(ecclCommDestroy(comm[i]));
}
free(srcH);
free(dstH);
printf("Test single process single thread allreduce passed!\n");
return 0;
}
组操作¶
组操作(ecclGroupStart/ecclGroupEnd)可以被用来将多次调用合并为单次调用。当前 ECCL 仅支持在同一个通信域中聚合相同的通信操作以提高性能。
组操作使用示例¶
聚合通信操作意味着在单次任务下发中,将执行多次通信操作(当前仅支持聚合多个相同通信操作)。组操作对于减少下发开销效果明显,因为其在执行多个通信操作中仅会下发一次。
对通信操作进行聚合可以通过在 ecclGroupStart 和 ecclGroupEnd 区域内多次调用 ECCL 来完成,下列代码将会展示如何在单次下发中聚合多次 AllReduce 操作:
ecclGroupStart();
for (int i = 0; i < nlayers; ++i) {
ecclAllReduce(sendBuffs[i], recvBuffs[i], count, dataType, redOp,
comm, topsStream);
}
ecclGroupEnd();
点对点通信¶
点对点通信可以指定通信域内任意两张卡之间进行数据接发。该通信方式由两个举动组成:发送端调用 ecclSend(),相应的接收端调用 ecclRecv()。此时两端传入的数据类型和位数参数需要一致。
将针对不同节点的多次点对点通信调用跟组操作结合在一起,能够完成更加复杂的通信操作,下列代码将会展示如何在单次下发中聚合多次点对点通信:
ecclGroupStart();
for (int r=0; r<nlayers; r++) {
ecclSend(sendbuff[r], sendcount, sendtype, peer, comm, topsStream);
ecclRecv(recvbuff[r], recvcount, recvtype, peer, comm, topsStream);
}
ecclGroupEnd();
Attention
点对点通信中发送端和接收端需要严格对应,即发送端发送的次数要等于接收端接收的次数
运行 ECCL sample¶
ECCL 软件包安装完成后,对应的 sample 会被一并安装至 /usr/src/eccl_samples 目录下。
此次提供了下列多个 demo sample:
test_sendrecv
该 demo 提供了单次点对点通信的测试方法,编译命令如下:
cd test_sendrecv && make
运行单机4卡测试命令如下,由参数np指定参与的卡数:
mpirun -np 4 ./test_sendrecv
test_group_sendrecv
该 demo 提供了组操作下点对点通信的测试方法,编译命令如下:
cd test_group_sendrecv && make
运行单机4卡测试命令如下,由参数np指定参与的卡数:
mpirun -np 4 ./test_group_sendrecv
test_single_process_single_thread
该 demo 提供了单进程单线程模式下集合通信的测试方法,编译命令如下:
cd test_single_process_single_thread && make
运行测试命令如下(测试内部指定运行的卡数,此测试运行卡数为4):
./test_single_process_single_thread
test_single_process_multi_thread
该 demo 提供了单进程多线程模式下集合通信的测试方法,编译命令如下:
cd test_single_process_multi_thread && make
运行测试命令如下(测试内部指定运行的卡数,此测试运行卡数为4):
./test_single_process_multi_thread
test_commInitAll
该 demo 提供了单线程模式下使用 ecclCommInitAll 接口进行初始化并进行集合通信的测试方法,编译命令如下:
cd test_commInitAll && make
运行测试命令如下(测试内部指定运行的卡数,此测试运行卡数为4):
./test_commInitAll
ECCL 使用示例¶
以下代码描述了一个完整的工作示例,其中包含多个 MPI 进程,每个进程管理一个 GCU 设备。
示例代码可以使用 "gcc"
编译器进行编译。需要在编译时链接下列的库:
Openmpi,推荐版本4.0.5。
ECCL
topsruntime
#include <stdio.h>
#include "tops/tops_runtime.h"
#include "eccl.h"
#include "mpi.h"
#include <unistd.h>
#include <stdint.h>
#include <stdlib.h>
#define MPICHECK(cmd) do { \
int e = cmd; \
if( e != MPI_SUCCESS ) { \
printf("Failed: MPI error %s:%d '%d'\n", \
__FILE__,__LINE__, e); \
exit(EXIT_FAILURE); \
} \
} while(0)
#define TOPSCHECK(cmd) do { \
topsError_t e = cmd; \
if( e != topsSuccess ) { \
printf("Failed: Tops error %s:%d '%s'\n", \
__FILE__,__LINE__,topsGetErrorString(e)); \
exit(EXIT_FAILURE); \
} \
} while(0)
#define ECCLCHECK(cmd) do { \
ecclResult_t r = cmd; \
if (r!= ecclSuccess) { \
printf("Failed, ECCL error %s:%d '%d'\n", \
__FILE__,__LINE__,r); \
exit(EXIT_FAILURE); \
} \
} while(0)
static uint64_t getHostHash(const char* string) {
// Based on DJB2a, result = result * 33 ^ char
uint64_t result = 5381;
for (int c = 0; string[c] != '\0'; c++){
result = ((result << 5) + result) ^ string[c];
}
return result;
}
static void getHostName(char* hostname, int maxlen) {
gethostname(hostname, maxlen);
for (int i=0; i< maxlen; i++) {
if (hostname[i] == '.') {
hostname[i] = '\0';
return;
}
}
}
int main(int argc, char* argv[])
{
int size = 32*1024*1024;
int myRank, nRanks, localRank = 0;
// 初始化MPI
MPICHECK(MPI_Init(&argc, &argv));
MPICHECK(MPI_Comm_rank(MPI_COMM_WORLD, &myRank));
MPICHECK(MPI_Comm_size(MPI_COMM_WORLD, &nRanks));
// 基于hostname计算localRank, localRank用于指定使用哪个GCU
uint64_t hostHashs[nRanks];
char hostname[1024];
getHostName(hostname, 1024);
hostHashs[myRank] = getHostHash(hostname);
MPICHECK(MPI_Allgather(MPI_IN_PLACE, 0, MPI_DATATYPE_NULL, hostHashs, sizeof(uint64_t), MPI_BYTE, MPI_COMM_WORLD));
for (int p=0; p<nRanks; p++) {
if (p == myRank) break;
if (hostHashs[p] == hostHashs[myRank]) localRank++;
}
ecclUniqueId id;
ecclComm_t comm;
float *sendbuff, *recvbuff;
topsStream_t s;
// 获取rank 0的ECCL unique ID 然后广播到其他rank
if (myRank == 0) ECCLCHECK(ecclGetUniqueId(&id));
MPICHECK(MPI_Bcast((void *)&id, sizeof(id), MPI_BYTE, 0, MPI_COMM_WORLD));
// 依据localRank选择一个GCU, 分配资源
TOPSCHECK(topsSetDevice(localRank));
TOPSCHECK(topsMalloc(&sendbuff, size * sizeof(float)));
TOPSCHECK(topsMalloc(&recvbuff, size * sizeof(float)));
TOPSCHECK(topsStreamCreate(&s));
// 初始化ECCL
ECCLCHECK(ecclCommInitRank(&comm, nRanks, id, myRank));
// 使用ECCL进行集合通信
ECCLCHECK(ecclAllReduce((const void*)sendbuff, (void*)recvbuff, size, ecclFloat, ecclSum,
comm, s));
// stream 同步ECCL集合操作
TOPSCHECK(topsStreamSynchronize(s));
// 释放空间
TOPSCHECK(topsFree(sendbuff));
TOPSCHECK(topsFree(recvbuff));
// 销毁 ECCL
ecclCommDestroy(comm);
// 销毁MPI
MPICHECK(MPI_Finalize());
printf("[MPI Rank %d] Success \n", myRank);
return 0;
}
4.5. ECCL 环境变量¶
ECCL_ALGO¶
用于设置 ECCL 使用的算法,目前支持 Ring, Mesh 和 MeshDirect 三种算法。
该环境变量会影响分布式的性能,建议用户不额外设置此环境变量,ECCL会自适应选择合适的算法类型。
允许传入的值¶
Ring
Mesh
MeshDirect
ECCL_PROTO¶
用于设置 ECCL 使用的协议,目前支持 LL128,Simple。
该环境变量会影响分布式的性能及准确性,建议用户不额外设置此环境变量,ECCL会自适应选择合适的协议类型。
注意: 由于当前LL128使用会存在一些限制,且在某些CPU系统下可能有潜在的mismatch问题, 所以LL128 协议是默认disable的。如果用户经过严格的验证明确在某些CPU下不会出现问题。那么推荐用户设置 export ECCL_PROTO="LL128" 使能LL128协议。对于不支持的场景,在ECCL内部会基于当前的环境变量设置自动tune到一个合适的算法协议。
LL128只支持allreduce原语,只支持单机场景,不支持SHM/NET链路,不支持较大数据量的传输。对于不支持的场景,会添加如下log的打印:
WARN : no algorithm/protocol available for function ...
Redirect to ...
...表示这里省略了其他内容。
允许传入的值¶
LL128
Simple
ECCL_DEBUG¶
该变量用于设置 ECCL 打印的 log 级别,WARN、INFO 两种级别。WARN 级别在业务逻辑出现异常时打印,INFO 级别用于用户行为留痕、调用链跟踪。其中 INFO 需与定义子模块的环境变量 ECCL_DEBUG_SUBSYS 配合使用,WARN 会打印所有子模块。
允许传入的值¶
WARN:打开 WARN 级别 log
INFO:打开 INFO 级别 log
使用示例¶
打印 INFO 级别的初始化以及环境变量相关 log (即默认值):
export ECCL_DEBUG=INFO
export ECCL_DEBUG_SUBSYS=INIT,ENV
打印 WARN 级别的所有 log:
export ECCL_DEBUG=WARN
export ECCL_DEBUG_SUBSYS=ALL
ECCL_DEBUG_SUBSYS¶
用于设置需要打印的子模块,主要包括初始化、环境变量、调用等子模块,可以设置多个子模块,多个子模块间用 ","
隔开,默认值为 "INIT, ENV"
。关闭子模块,在子模块前加 "^"
。ECCL_DEBUG 设置为 INFO 时需与 ECCL_DEBUG_SUBSYS 配合使用。
允许传入的值¶
INIT:初始化阶段信息,默认值
ENV:环境变量信息,默认值
CALL:ECCL 对外接口调用信息
ALLOC:ECCL 内部资源分配信息
TRANSPORT:ECCL 内部 transport 信息
NET:网络相关信息,调用 socket、ibverbs 接口信息
TOPO:ECCL 内部拓扑相关信息
P2P:ECCL 点对点通信相关信息
ALL:ECCL 所有模块信息
使用示例¶
打印 INFO 级别的初始化以及环境变量相关 log:
export ECCL_DEBUG=INFO
export ECCL_DEBUG_SUBSYS=INIT,ENV
打印 WARN 级别的所有 log:
export ECCL_DEBUG=WARN
export ECCL_DEBUG_SUBSYS=ALL
关闭初始化模块打印:
export ECCL_DEBUG_SUBSYS=^INIT
ECCL_TOPO_DUMP_FILE¶
给定一个存储XML文件的路径,用于转存探测的物理拓扑链接。
允许传入的值¶
一个指向某文件的路径,这个文件可能会被创建,或者会被覆盖原有内容。
ECCL_GRAPH_DUMP_FILE¶
给定一个存储XML文件的路径,用于转存搜索graph的信息。
允许传入的值¶
一个指向某文件的路径,这个文件可能会被创建,或者会被覆盖原有内容。
ECCL_P2P_DISABLE¶
该变量用于设置关闭 peer to peer (P2P) transport。P2P 通信将使用 PCI/ESL 等方式直接访问来实现通信的功能。若关闭 P2P,ECCL 将转而使用 Shared Memory (SHM) 进行通信。
允许传入的值¶
默认值为0,设置为1则表示关闭。
ECCL_SHM_DISABLE¶
该变量用于设置关闭 Shared Memory (SHM) transport。SHM 是在设备之间无法进行 P2P 通信时使用的(此时 ECCL_P2P_DISABLE 被设置为 1),其将使用主机内存进行通信。若关闭 SHM,ECCL 将转而使用 IB/RoCE 进行通信。
允许传入的值¶
默认值为0,设置为1则表示关闭。
ECCL_IB_DISABLE¶
该变量用于设置关闭IB/RoCE transport,若关闭,ECCL将转而使用IP sockets。
允许传入的值¶
默认值为0,设置为1则表示关闭。
Attention
transport的优先级为 P2P > SHM > IB/RoCE > IP sockets。
ECCL_IB_GID_INDEX¶
该变量用于设置RoCE模式中使用的Global ID index(可通过show_gids命令查看),每个端口具有一个GUID(Globally Unique Identifier),GUID是全局唯一的,类似于以太网MAC地址。
允许传入的值¶
默认值为-1,可设置为相应的Global ID index。
ECCL_IB_HCA¶
该变量用于设置RDMA传输使用的网口。
允许传入的值¶
用于筛选RDMA网口的字符串,存在多个筛选条件时用 ","
隔开。默认值为空。
接受以下三种形式的字符串(以网卡mlx5_1为例):
网卡名(例:
"mlx5_1"
)。网卡名的部分前缀(例:
"mlx5"
)。网卡名加物理口序号,网卡与物理口之间用
":"
隔开(例:"mlx5_1:1"
表示网卡mlx5_1的第一个口)。
字符串中可增加 "^"
用于排除, "="
用于精确选择。在字符串为网卡名时, "="
可忽略。
使用示例¶
选择所有以mlx5开头的口,例如mlx5_0、mlx5_1等:
export ECCL_IB_HCA="mlx5"
选择网卡mlx5_1上的所有的口,例如mlx5_1:1、mlx5_1:2等:
export ECCL_IB_HCA="mlx5_1" 或 ECCL_IB_HCA="=mlx5_1"
使用网卡mlx5_0、mlx5_1的第一个口:
export ECCL_IB_HCA="=mlx5_0:1,mlx5_1:1"
不选择mlx5_1上的口:
export ECCL_IB_HCA="^mlx5_1" 或 ECCL_IB_HCA="^=mlx5_1"
不选择mlx5_1上的第一个口:
export ECCL_IB_HCA="^=mlx5_1:1"
ECCL_NET_GDR_LEVEL¶
该变量定义NIC和GCU之间的最大距离,用于精细控制NIC和GCU之间何时使用GCU Direct RDMA。如用户不指定,ECCL将根据架构以及环境选择最优的值。
允许传入的值¶
允许传入字符串形式的值:
LOC : 不使用GCU Direct RDMA。
PIX : GCU和NIC连接同一个PCI交换机,使用GCU Direct RDMA。
PXB : GCU和NIC通过多个PCI交换机相连(多跳),使用GCU Direct RDMA。
PHB : GCU和NIC位于同一个NUMA节点下,使用GCU Direct RDMA,此时流量将通过CPU。
SYS : GCU和NIC跨NUMA节点通过SMP互联(如QPI、UPI),也使用GCU Direct RDMA。
也允许传入数字形式的值,与字符串的对应关系如下:
0 : LOC
1 : PIX
2 : PXB
3 : PHB
4 : SYS
Attention
设置的值大于4时,同样按照 "SYS"
处理。
ECCL_NET_GDR_READ¶
该变量用于GCU和NIC在ECCL_NET_GDR_LEVEL指定的距离内时,发送数据开启GCU Direct RDMA。
Attention
已知在某些平台(如:PCI-E)上,发送数据直接从GCU内存读取比从CPU内存读取稍慢。
允许传入的值¶
0或者1。设置为1时使用GCU Direct RDMA将数据直接发送到NIC(即绕过CPU),设置为0时关闭使用GCU Direct RDMA。
ECCL_SOCKET_NTHREADS¶
该变量用于设置使用socket连接时CPU创建的线程个数,有可能会提高socket传输性能,以cpu利用率为代价。该环境变量会影响socket通信的性能。
允许传入的值¶
与ECCL_NSOCKS_PERTHREAD配合使用,取值范围是1至16,默认值为4。
使用示例¶
使用socket连接时CPU创建的线程个数2:
export ECCL_SOCKET_NTHREADS=2
ECCL_NSOCKS_PERTHREAD¶
该变量用于设置使用socket连接时单个线程创建的socket个数。在单个socket传输速率限制的情况下,设置>1可能会提高性能。该环境变量会影响socket通信的性能。
允许传入的值¶
与ECCL_SOCKET_NTHREADS配合使用,默认值为1。总的socket个数不超过64(即ECCL_NSOCKS_PERTHREAD*ECCL_SOCKET_NTHREADS<=64)。
使用示例¶
使用socket连接时CPU的单个线程创建的socket个数2:
export ECCL_NSOCKS_PERTHREAD=2
ECCL_SOCKET_IFNAME¶
该变量用于设置socket通信使用的ip interface。
允许传入的值¶
用于筛选ip interface的字符串前缀。"^"用于排除,"="用于精确选择。默认值为空。
使用示例¶
选择所有以eth开头的ip interface,例如eth0、eth1等:
export ECCL_SOCKET_IFNAME="eth"
只选择eth0:
export ECCL_SOCKET_IFNAME="=eth0"
不选择所有以eth开头的ip interface:
export ECCL_SOCKET_IFNAME="^eth"
不选择eth0:
export ECCL_SOCKET_IFNAME="^=eth0"
5. eccl-tests¶
用来测试检查ECCL操作的性能和正确性。
5.1. eccl-tests安装¶
eccl-tests安装包依赖eccl安装包,在安装eccl-tests之前需要先安装eccl包,见本文档 ECCL安装 部分。
eccl-tests安装
dpkg -i eccl-tests_*_amd64.deb (rpm -ivh eccl-tests-*.x86_64.rpm)
确认是否安装成功, 请执行:
dpkg -l | grep eccl-tests (rpm -qa eccl-tests)
检查eccl_all_reduce_perf、eccl_all_gather_perf、eccl_broadcast_perf、eccl_reduce_perf、eccl_reduce_scatter_perf、eccl_sendrecv_perf、eccl_gather_perf、eccl_scatter_perf、eccl_alltoall_perf 可执行文件是否存在于目录 /usr/local/bin 下。
5.2. 用法¶
eccl-tests运行在多张GCU卡上,当前支持通过mpirun命令启动程序并指定启动的进程的个数,每个进程对应着一个唯一rank。目前支持一个进程管理一块GCU,在一个进程上有一个线程。eccl-tests目前支持多种通信算子的性能测试,包括AllReduce、Broadcast、ReduceScatter、AllGather、Reduce、Gather、Scatter、SendRecv、AlltoAll,当前AllReduce、Broadcast、ReduceScatter、AllGather、Reduce、Gather、Scatter支持单机和多机,SendRecv、AlltoAll仅支持单机。
5.3. 命令参数¶
Number of GCUs¶
参数名称 |
使用方法 |
默认值 |
参数说明 |
---|---|---|---|
-t |
--nthreads <num threads> |
1(仅支持1) |
运行在每一个进程上的线程数 |
-g |
--ngcus <GCUs per thread> |
1(仅支持1) |
运行在每一个线程上的GCU个数 |
Attention
目前仅支持一个进程对应一个GCU启动
Sizes to scan¶
参数名称 |
使用方法 |
默认值 |
参数说明 |
---|---|---|---|
-b |
--minbytes <min size in bytes> |
32M |
启动时的最小尺寸 |
-e |
--maxbytes <max size in bytes> |
32M |
结束时的最大的尺寸 |
-i |
--stepbytes <increment size> |
1M |
在一定的尺度等量选择增量 |
-f |
--stepfactor <increment factor> |
不使用 |
乘法的使用 |
Attention
-i和-f只使用一个,默认使用-i
ECCL operations arguments¶
参数名称 |
使用方法 |
默认值 |
参数说明 |
---|---|---|---|
-o |
--op <sum/prod/min/max/all> |
sum |
指定要执行的规约操作类型。仅与AllReduce、Reduce或ReduceScatter等源操作相关 |
-d |
--datatype <eccltype> |
float32 |
指定数据类型 |
-r |
--root <root/all> |
0 |
指定 root GCU,应用于Broadcast或者Reduce相关操作 |
Attention
-d 可指定的类型有 <int8/uint8/int32/uint32/int64/uint64/float16/float32/float64/bfloat16>。AllReduce,ReduceScatter和Reduce当前不支持 64位的数据类型: int64,uint64和float64
Performance¶
参数名称 |
使用方法 |
默认值 |
参数说明 |
---|---|---|---|
-n |
--iters <iteration count> |
20 |
循环迭代次数 |
-w |
--warmup_iters <warmup iteration count> |
5 |
预热迭代次数(不是时间) |
-m |
--agg_iters <aggregation count> |
1 |
每次迭代中聚合在一起的操作数 |
-a |
--average <0/1/2/3> |
1 |
报告所有rank的平均性能(仅MPI=1)<0=Rank0,1=Avg,2=Min,3=Max> |
Test operation¶
参数名称 |
使用方法 |
默认值 |
参数说明 |
---|---|---|---|
-c |
--check <0/1> |
1 |
检查结果的正确性。在大量GCU上可能较慢,默认检查 |
-z |
--blocking <0/1> |
0 |
进行ECCL集合通信阻塞,即让CPU在每次集体阻塞后等待和同步 |
5.4. 单机测试示例¶
AllReduce¶
单机8卡:
mpirun -np 8 eccl_all_reduce_perf -b 1k -e 128M -f 2
Broadcast¶
单机8卡:
mpirun -np 8 eccl_broadcast_perf -b 1k -e 128M -f 2
ReduceScatter¶
单机8卡:
mpirun -np 8 eccl_reduce_scatter_perf -b 1k -e 128M -f 2
AllGather¶
单机8卡:
mpirun -np 8 eccl_all_gather_perf -b 1k -e 128M -f 2
Reduce¶
单机8卡:
mpirun -np 8 eccl_reduce_perf -b 1k -e 128M -f 2
SendRecv¶
单机8卡:
mpirun -np 8 eccl_sendrecv_perf -b 1k -e 128M -f 2
Gather¶
单机8卡:
mpirun -np 8 eccl_gather_perf -b 1k -e 128M -f 2
Scatter¶
单机8卡:
mpirun -np 8 eccl_scatter_perf -b 1k -e 128M -f 2
AlltoAll¶
单机8卡:
mpirun -np 8 eccl_alltoall_perf -b 1k -e 128M -f 2
5.5. 多机测试准备以及示例¶
eccl-tests同样支持多机测试,推荐在单机环境搭建测试成功后,再进行多机的相关测试。多机测试依赖测试环境的配置,以及设置一些环境变量,可按照如下步骤进行检查、测试(以下均以两机十六卡测试为例):
检查eccl、eccl-tests安装情况¶
多机测试需要待测的所有机器都已经安装TopsPlatform、eccl以及eccl-tests,安装以及检查可参考本文档ECCL安装以及eccl-tests安装部分,若要使能peer mem测试,则需要安装enflame_peer_mem,可以参考《topsplatform 安装说明手册》进行安装。
检查网络配置情况¶
eccl以及eccl-tests的测试依赖环境的网络配置,用户需进行一些相关检查以保证网络正常。目前燧原公司机器默认安装Mellanox网卡,测试前需确认网卡个数、驱动安装、以及网卡状态等情况,其他厂商网卡命令会有所不同;推荐不同机器使用相同个数的网卡,若不同,需要配合环境变量ECCL_IB_HCA使用,以下是一些基本检查步骤:
网卡的物理情况¶
以Mellanox网卡为例,检查当前设备Mellanox网卡情况,命令:
lspci -nn | grep Eth | grep Mellanox
#部分参考输出,以下结果表示设备具备两张Mellanox系列网卡
#型号是ConnectX-5;若无显示,则需要插上相关网卡
Ethernet controller : Mellanox Technologies MT28800 Family [ConnectX-5 Ex]
Ethernet controller : Mellanox Technologies MT28800 Family [ConnectX-5 Ex]
也可通过:
lspci -t -v
来查看网卡设备在PCI总线上的位置、个数、是否有多品牌网卡,以及包括在具体哪个numa节点,连接CPU、PCIe switch的情况,网卡的不同位置对实际性能会有较大影响。
网卡驱动¶
以Mellanox网卡为例,确认是否已经正确安装OFED驱动,命令:
ofed_info -s
#参考输出,以下结果表示版本为5.4-3.1.0.0;若无显示,则需要安装OFED驱动
MLNX_OFED_LINUX-5.4-3.1.0.0:
网卡接口¶
可通过以下命令查看所有网络接口,包含普通以太网口,以及rdma相关网口;
ifconfig
网卡状态¶
#查看网口与网卡映射情况
ibdev2netdev
#参考输出,以下结果表示mlx5_0和mlx5_2对应端口为ib0和ib2,均为up状态
mlx5_0 port 1 ==> ib0 (Up)
mlx5_2 port 1 ==> ib2 (Up)
#查看网卡工作情况,命令:
ibstatus
#参考输出,以下结果表示mlx5_0和mlx5_2均为ACTIVE状态,速率为100Gb/s
#工作在Ethernet模式(也可工作在InfiniBand模式,相关网卡需要保持一致)
Infiniband device 'mlx5_0' port 1 status:
default gid: fe80:0000:0000:0000:0e42:a1ff:fe60:0a88
base lid: 0x0
sm lid: 0x0
state: 4: ACTIVE
phys state: 5: LinkUp
rate: 100 Gb/sec (4X EDR)
link_layer: Ethernet
Infiniband device 'mlx5_2' port 1 status:
default gid: fe80:0000:0000:0000:0e42:a1ff:fe60:0408
base lid: 0x0
sm lid: 0x0
state: 4: ACTIVE
phys state: 5: LinkUp
rate: 100 Gb/sec (4X EDR)
link_layer: Ethernet
若网口以及网卡工作状态不符合预期,首先检查相关物理链路是否连接正常,以及检查是否为端口设置ip地址,ifconfig可查看,若无ip地址,则需要配置ip地址。
网卡实际速率测试¶
可通过ib_send_bw、ib_write_bw等命令测试网卡的实际收发速率:
#server端命令,mlx5_1为server端待测网卡
ib_write_bw --ib-dev=mlx5_1 -a --report_gbits
#client端命令,mlx5_2为client端待测网卡
#a.b.c.d为server端的待测网卡对应网口ip地址
ib_write_bw --ib-dev=mlx5_2 -a --report_gbits a.b.c.d -F
实际的收发速率若与网卡标称速率不符,或远低于标称速率则需要进行相关检查或者更换网卡;若测试失败,则需要检查物理连接以及网络配置。
mpirun相关配置¶
本手册使用mpirun启动进行测试,涉及到mpirun启动命令,以下是部分必须的设置,测试当中更多的mpirun启动问题可自行搜索mpirun相关文档。
免密登录配置¶
查看待测试的机器是否可以互相ssh免密登录,若无法免密登录,则:
#生成密钥
ssh-keygen
#拷贝至对应服务器,username为用户名,a.b.c.d为对应ip地址
ssh-copy-id username@a.b.c.d
hostfile配置¶
多机测试需要使用hostfile文件,用于指定slot的数量,在每台测试机的相同路径创建该文件,文件名为hostfile(文件名可自定义),并输入如下内容:
a.b.c.d slots=8
a.b.c.e slots=8
以上hostfile文件是以两机十六卡为例,a.b.c.d和a.b.c.e分别为两台机器的ip地址,slots=8指定每台机器使用八张GCU,也可根据实际情况设置GCU个数。
指定网络接口¶
mpirun通常不需要直接指定使用哪个物理网卡,会自动选择最优的通信路径。但推荐通过环境变量来设置,以下环境变量会限制TCP通信仅使用特定网络接口:
export OMPI_MCA_btl_tcp_if_include=${name}
这里的${name}是假设的网卡接口名,实际使用中需替换为系统中对应的接口名。
推荐配置的环境变量¶
多机测试默认需要配置一些网络相关环境变量,否则会导致测试失败,如下列举了推荐使用的环境变量,环境变量详细使用可参考环境变量章节。
ECCL_SOCKET_IFNAME¶
该变量用于设置socket通信使用的ip interface,使用mpirun进行eccl初始化时候会搜索可使用的ip interface,并且选择搜索到的第一个端口,不同机器搜索到的端口可能会不一致,从而导致初始化失败。可以ifconfig查看所有机器上都有的ip interface,并设置进行测试;例如所有机器上都存在端口eth0,则设置:
export ECCL_SOCKET_IFNAME="eth0"
ECCL_IB_HCA¶
该变量用于设置RDMA传输使用的网口,不同机器上的网卡数目、网卡设备名可能不一致,推荐设置从而精确选择网卡;例如所有机器上都存在网卡mlx5_0,则设置:
export ECCL_IB_HCA="mlx5_0"
ECCL_IB_GID_INDEX¶
该变量用于设置RoCE模式中使用的Global ID index,可通过show_gids命令查看,推荐使用RoCEv2对应的index(依赖物理环境的组网,需要先确认网络可通);例如RoCEv2对应的index为3,则设置:
export ECCL_IB_GID_INDEX=3
执行测试¶
在其中一台待测试机器执行命令,相关测试的具体命令如下,其中: "-np 16"
中的16表示测试一共使用16张卡,其中每台机器各八张卡; "--mca btl_tcp_if_include ${name}"
表示使用名为${name}的ip interface作为mpi tcp的通信端口,需保证${name}为两台机器的可用端口,可与ECCL_SOCKET_IFNAME环境变量设置一致,若不同机器的端口不一致,则需要按照mpirun指定网络接口章节设置; "-hostfile hostfile"
中第二个hostfile为设置所用hostfile文件的文件名,用户可自定义。
AllReduce¶
mpirun -np 16 -hostfile hostfile --allow-run-as-root -bind-to none -map-by slot --mca plm_rsh_args "-p 2223" --mca btl_tcp_if_include ${name} eccl_all_reduce_perf -b 1k -e 128M -f 2
Broadcast¶
mpirun -np 16 -hostfile hostfile --allow-run-as-root -bind-to none -map-by slot --mca plm_rsh_args "-p 2223" --mca btl_tcp_if_include ${name} eccl_broadcast_perf -b 1k -e 128M -f 2
ReduceScatter¶
mpirun -np 16 -hostfile hostfile --allow-run-as-root -bind-to none -map-by slot --mca plm_rsh_args "-p 2223" --mca btl_tcp_if_include ${name} eccl_reduce_scatter_perf -b 1k -e 128M -f 2
AllGather¶
mpirun -np 16 -hostfile hostfile --allow-run-as-root -bind-to none -map-by slot --mca plm_rsh_args "-p 2223" --mca btl_tcp_if_include ${name} eccl_all_gather_perf -b 1k -e 128M -f 2
Reduce¶
mpirun -np 16 -hostfile hostfile --allow-run-as-root -bind-to none -map-by slot --mca plm_rsh_args "-p 2223" --mca btl_tcp_if_include ${name} eccl_reduce_perf -b 1k -e 128M -f 2
Gather¶
mpirun -np 16 -hostfile hostfile --allow-run-as-root -bind-to none -map-by slot --mca plm_rsh_args "-p 2223" --mca btl_tcp_if_include ${name} eccl_gather_perf -b 1k -e 128M -f 2
Scatter¶
mpirun -np 16 -hostfile hostfile --allow-run-as-root -bind-to none -map-by slot --mca plm_rsh_args "-p 2223" --mca btl_tcp_if_include ${name} eccl_scatter_perf -b 1k -e 128M -f 2
5.6. 结果¶
参数名称 |
参数说明 |
---|---|
size |
数据的大小(S) |
count |
数据的计数 |
type |
数据的类型 |
redop |
操作的类型 |
time |
操作的执行时间(t) |
algbw |
算法带宽,根据数据大小以及操作执行时间计算得到,algbw=S/t |
busbw |
总线带宽,根据数据链路上实际传输的数据大小以及操作执行时间计算得到。实际是根据操作类型,将算法带宽乘以一个系数,详见下表 |
操作的类型 |
计算关系 |
---|---|
AllReduce |
busbw=algbw*2*(n-1)/n |
ReduceScatter |
busbw=algbw*(n-1)/n |
AllGather |
busbw=algbw*(n-1)/n |
Broadcast |
busbw=algbw |
Reduce |
busbw=algbw |
SendRecv |
busbw=algbw |
Gather |
busbw=algbw*(n-1)/n |
Scatter |
busbw=algbw*(n-1)/n |
AlltoAll |
busbw=algbw*(n-1)/n |
5.7. 注意事项¶
打开调试开关(ECCL_DEBUG,ECCL_DEBUG_SUBSYS)可能导致eccl-tests测得的性能低于实际性能,建议在进行eccl-tests性能测试时不要设置调试相关环境变量。