驾驭3200Gbps网络(3): libfabric
libfabric
一套通用的高性能网络接口,它的风格与前文提到的 RDMA ibverbs
接口十分相像,但是更易用一点。应用程序只需要调用 libfabric
的上层接口,而具体的协议由不同的 Provider 实现。libfabric
官方的 Provider 包括了 tcp
、udp
、shm
(共享内存)、verbs
(即 RDMA ibverbs
)以及对本文来说最重要的 efa
。
概念及术语
libfabric
定义了非常多的概念和术语,并且包含了很多缩写。这里我用稍不严谨地语言概括一下。
软件对象模型
- Fabric: 所有硬件资源和软件状态的的集合,类似于用于保存全局状态的一个结构。
- Domain: 类似于一张网卡,例如
eth0
。一个 Fabric 可以拥有多个 Domain。 - (EP) Endpoint: 数据收发的端点,一个 Domain 可以拥有多个端点,并且每个端点也有不同的类型。例如在同一张以太网卡上,可以监听多个
ip:port
,可以使用 TCP 和 UDP 这样不同类型的协议。 - (EQ) Event Queue:事件队列,用来报告控制平面操作的完成。在本文中没有用到。
- (CQ) Completion Queue: 完成队列,报告数据平面操作的完成(
RECV
/SEND
/WRITE
/READ
)。CQ 属于 Domain,可以绑定到 Endpoint 上。 - (AV) Address Vector:存储解析过的网络地址。需要先将通信的另一方加入 AV 才能对其发起通信。AV 属于 Domain,可以绑定到 Endpoint 上。
- (MR) Memory Region:用于收发数据的缓冲区。无论是哪种 RDMA 操作,都需要指定 MR。注册 MR 需要经过操作系统内核,因为操作系统内核需要设置 CPU 页表以及其他 PCIe 设备的页表。
通信方式
- 端点类型(Endpoint types)
FI_EP_MSG
(Reliable-connected):类似于 RDMA 中的 RC QP,基于连接的可靠传输。EFA 不支持这个类型。FI_EP_RDM
(Reliable-unconnected):基于数据报的可靠传输。- 包含重传
- 支持某些类型的消息送达顺序保证。例如 EFA 支持 send-after-send。
- 可以传输任意大小的数据。可以使用单边 RDMA(One-sided RDMA,
WRITE
/READ
)和双边 RDMA(Two-sided RDMA,RECV
/SEND
)。 - EFA 使用亚马逊自研的SRD(Scalable Reliable Datagram)协议,所以这是 EFA 的主力端点类型。
verbs
Provider 则不原生支持这一端点类型,因为 RDMA 本身并没有类似的 QP 类型。不过libfabric
上可以通过RxM
(RDM over MSG) Provider 来模拟这一端点类型。
FI_EP_DGRAM
(Unreliable datagram):类似于 RDMA 中的 UD QP,基于数据报的不可靠传输。在 EFA 上只能传输小于 MTU 的数据,并且只能使用双边 RDMA。在这里不多做介绍。
- 端点能力(Endpoint Capability)
FI_MSG
双边 RDMA:RECV
/SEND
,见fi_msg
(3)FI_TAGGED
:类似于FI_MSG
,但是每条消息带有一个标记,接收方可以根据这个标记选择缓冲区。本文不多做介绍。见fi_tagged
(3)FI_RMA
单边 RDMA:WRITE
/WRITE_IMM
/READ
,见fi_rma
(3)FI_ATOMIC
单边 RDMA 原子操作。本文不多做介绍。见fi_atomic
(3)
安装 libfabric
libfabric
依赖于 rdma-core
来进行 RDMA 操作,依赖于 GDRCopy 来实现 GPUDirect DMA。另外 libfabric
提供了一套性能测试工具 fabtests
。下面这一段脚本会在当前目录下面建立一个 build/
目录,下载这些库的代码到 build/
目录中,编译并安装这些库到 build/
的子目录当中。
mkdir -p build
cd build
BUILD_DIR=$(pwd)
# RDMA Core
sudo apt-get install rdma-core
# GDRCopy
wget -O gdrcopy-2.4.4.tar.gz https://github.com/NVIDIA/gdrcopy/archive/refs/tags/v2.4.4.tar.gz
cd gdrcopy-2.4.4/
make prefix="$BUILD_DIR/gdrcopy" \
CUDA=/usr/local/cuda \
-j$(nproc --all) all install
cd ..
export LD_LIBRARY_PATH="$BUILD_DIR/gdrcopy/lib:$LD_LIBRARY_PATH"
# libfabric
wget https://github.com/ofiwg/libfabric/releases/download/v2.0.0/libfabric-2.0.0.tar.bz2
tar xf libfabric-2.0.0.tar.bz2
cd libfabric-2.0.0
./configure --prefix="$BUILD_DIR/libfabric" \
--with-cuda=/usr/local/cuda \
--with-gdrcopy="$BUILD_DIR/gdrcopy"
make -j$(nproc --all)
make install
cd ..
export LD_LIBRARY_PATH="$BUILD_DIR/libfabric/lib:$LD_LIBRARY_PATH"
# fabtests
wget https://github.com/ofiwg/libfabric/releases/download/v2.0.0/fabtests-2.0.0.tar.bz2
tar xf fabtests-2.0.0.tar.bz2
cd fabtests-2.0.0
./configure --prefix="$BUILD_DIR/fabtests" \
--with-cuda=/usr/local/cuda \
--with-libfabric="$BUILD_DIR/libfabric"
make -j$(nproc --all)
make install
cd ..
运行示例程序
现在我们可以运行 libfabric
自带的示例程序,一来可以验证软件已经安装到位,二来可以对我们的硬件有个初步的了解。
获得基本的网卡信息
./build/libfabric/bin/fi_info --verbose
---
fi_info:
caps: [ FI_MSG, FI_RMA, FI_TAGGED, FI_ATOMIC, FI_READ, FI_WRITE, FI_RECV, FI_SEND, FI_REMOTE_READ, FI_REMOTE_WRITE, FI_MULTI_RECV, FI_LOCAL_COMM, FI_REMOTE_COMM, FI_SOURCE, FI_DIRECTED_RECV ]
mode: [ ]
addr_format: FI_ADDR_EFA
src_addrlen: 32
dest_addrlen: 0
src_addr: fi_addr_efa://[fe80::8e7:efff:feee:e81d]:0:0
dest_addr: (null)
handle: (nil)
fi_tx_attr:
caps: [ FI_MSG, FI_RMA, FI_TAGGED, FI_ATOMIC, FI_READ, FI_WRITE, FI_SEND ]
mode: [ ]
op_flags: [ FI_COMPLETION, FI_INJECT, FI_TRANSMIT_COMPLETE, FI_DELIVERY_COMPLETE ]
msg_order: [ FI_ORDER_SAS, FI_ORDER_ATOMIC_RAR, FI_ORDER_ATOMIC_RAW, FI_ORDER_ATOMIC_WAR, FI_ORDER_ATOMIC_WAW ]
inject_size: 4096
size: 4096
iov_limit: 4
rma_iov_limit: 1
tclass: 0x0
fi_rx_attr:
caps: [ FI_MSG, FI_RMA, FI_TAGGED, FI_ATOMIC, FI_RECV, FI_REMOTE_READ, FI_REMOTE_WRITE, FI_MULTI_RECV, FI_SOURCE, FI_DIRECTED_RECV ]
mode: [ ]
op_flags: [ FI_MULTI_RECV, FI_COMPLETION ]
msg_order: [ FI_ORDER_SAS, FI_ORDER_ATOMIC_RAR, FI_ORDER_ATOMIC_RAW, FI_ORDER_ATOMIC_WAR, FI_ORDER_ATOMIC_WAW ]
size: 8192
iov_limit: 4
fi_ep_attr:
type: FI_EP_RDM
protocol: FI_PROTO_EFA
protocol_version: 4
max_msg_size: 18446744073709551615
msg_prefix_size: 0
max_order_raw_size: 8776
max_order_war_size: 8776
max_order_waw_size: 8776
mem_tag_format: 0xaaaaaaaaaaaaaaaa
tx_ctx_cnt: 1
rx_ctx_cnt: 1
auth_key_size: 0
fi_domain_attr:
domain: 0x0
name: rdmap79s0-rdm
threading: FI_THREAD_SAFE
progress: FI_PROGRESS_AUTO
resource_mgmt: FI_RM_ENABLED
av_type: FI_AV_TABLE
mr_mode: [ FI_MR_VIRT_ADDR, FI_MR_ALLOCATED, FI_MR_PROV_KEY, FI_MR_HMEM ]
mr_key_size: 4
cq_data_size: 4
cq_cnt: 512
ep_cnt: 256
tx_ctx_cnt: 256
rx_ctx_cnt: 256
max_ep_tx_ctx: 1
max_ep_rx_ctx: 1
max_ep_stx_ctx: 0
max_ep_srx_ctx: 0
cntr_cnt: 0
mr_iov_limit: 1
caps: [ FI_LOCAL_COMM, FI_REMOTE_COMM ]
mode: [ ]
auth_key_size: 0
max_err_data: 0
mr_cnt: 262144
tclass: 0x0
fi_fabric_attr:
name: efa
prov_name: efa
prov_version: 200.0
api_version: 2.0
nic:
fi_device_attr:
name: rdmap79s0
device_id: 0xefa1
device_version: 6
vendor_id: 0x1d0f
driver: efa
firmware: 0.0.0.0
fi_bus_attr:
bus_type: FI_BUS_PCI
fi_pci_attr:
domain_id: 0
bus_id: 79
device_id: 0
function_id: 0
fi_link_attr:
address: EFA-fe80::8e7:efff:feee:e81d
mtu: 8760
speed: 100000000000
state: FI_LINK_UP
network_type: Ethernet
---
...
fi_info
会输出所有网卡的信息,我在上面贴出来了第一张 EFA 的全部信息以供参考。从中我们可以看到一些关键点,在后面的几篇文章中会用到,我在下面特地摘出来:
src_addrlen: 32 # EFA 地址长度为32字节
fi_tx_attr:
size: 4096 # 发送队列能容纳4096个操作
iov_limit: 4 # 发送操作最多能指定4个本地缓冲区
rma_iov_limit: 1 # 发送操作只能指定一个远端缓冲区
fi_rx_attr:
size: 8192 # 接收队列能容纳8192个操作
iov_limit: 4 # 接收操作最多能指定4个本地缓冲区
fi_ep_attr:
max_msg_size: 18446744073709551615 # 可以发送任意大的数据
fi_domain_attr:
name: rdmap79s0-rdm # domain 名称
mr_key_size: 4 # MR 远程密钥大小为4字节
cq_data_size: 4 # WRITE_IMM 携带的立即数为4字节
mr_iov_limit: 1 # 注册MR时一次只能指定一块缓冲区
nic:
fi_device_attr:
name: rdmap79s0 # 网卡名称
driver: efa # EFA 网卡
fi_link_attr:
mtu: 8760 # 单个数据包最大为8760字节
speed: 100000000000 # 网卡的速率为 100 Gbps
带宽测试
我们还可以运行 fabtests
当中的带宽测试。下面的这个命令可以在两台机器之间进行 GPUDirect RDMA WRITE 操作。可以看到,最高速度达到了 11843.86 MB/sec,也就是 94.751 Gbps,几乎打满了带宽。
ip-172-19-226-174$ ./build/fabtests/bin/fi_rma_bw -p efa -o write -E -D cuda -S all
ip-172-19-230-131$ ./build/fabtests/bin/fi_rma_bw -p efa -o write -E -D cuda -S all 172.19.226.174
bytes iters total time MB/sec usec/xfer Mxfers/sec
1 20k 19k 0.03s 0.75 1.33 0.75
2 20k 39k 0.03s 1.58 1.27 0.79
3 20k 58k 0.03s 2.36 1.27 0.79
4 20k 78k 0.03s 3.18 1.26 0.79
6 20k 117k 0.03s 4.72 1.27 0.79
8 20k 156k 0.03s 6.37 1.26 0.80
12 20k 234k 0.03s 9.48 1.27 0.79
16 20k 312k 0.03s 12.66 1.26 0.79
24 20k 468k 0.03s 19.18 1.25 0.80
32 20k 625k 0.03s 24.78 1.29 0.77
48 20k 937k 0.02s 38.75 1.24 0.81
64 20k 1.2m 0.02s 51.87 1.23 0.81
96 20k 1.8m 0.02s 77.48 1.24 0.81
128 20k 2.4m 0.02s 103.46 1.24 0.81
192 20k 3.6m 0.02s 155.55 1.23 0.81
256 20k 4.8m 0.02s 206.08 1.24 0.80
384 20k 7.3m 0.02s 307.40 1.25 0.80
512 20k 9.7m 0.02s 412.82 1.24 0.81
768 20k 14m 0.03s 614.11 1.25 0.80
1k 20k 19m 0.03s 814.12 1.26 0.80
1.5k 20k 29m 0.03s 1212.41 1.27 0.79
2k 20k 39m 0.03s 1590.49 1.29 0.78
3k 20k 58m 0.03s 2340.84 1.31 0.76
4k 20k 78m 0.03s 3068.05 1.34 0.75
6k 20k 117m 0.03s 4462.85 1.38 0.73
8k 20k 156m 0.03s 5440.12 1.51 0.66
12k 20k 234m 0.04s 6770.81 1.81 0.55
16k 20k 312m 0.04s 7921.86 2.07 0.48
24k 20k 468m 0.06s 8319.43 2.95 0.34
32k 20k 625m 0.07s 9470.25 3.46 0.29
48k 20k 937m 0.10s 10127.33 4.85 0.21
64k 2k 125m 0.01s 10465.67 6.26 0.16
96k 2k 187m 0.02s 10885.78 9.03 0.11
128k 2k 250m 0.03s 10215.66 12.83 0.08
192k 2k 375m 0.03s 11301.58 17.40 0.06
256k 2k 500m 0.05s 11501.58 22.79 0.04
384k 2k 750m 0.08s 10123.87 38.84 0.03
512k 2k 1000m 0.10s 10749.66 48.77 0.02
768k 2k 1.4g 0.13s 11843.86 66.40 0.02
1m 200 200m 0.02s 10968.94 95.60 0.01
1.5m 200 300m 0.03s 10837.99 145.12 0.01
2m 200 400m 0.04s 10204.87 205.51 0.00
3m 200 600m 0.06s 10522.41 298.95 0.00
4m 200 800m 0.08s 10826.94 387.39 0.00
6m 200 1.1g 0.11s 11488.20 547.65 0.00
8m 200 1.5g 0.16s 10504.60 798.57 0.00
现在我们已经验证了软硬件都已就绪,那么下一章我们就可以开始编写代码了。