DOCA device 구성 요소
DOCA에서는 2개의 Physical Device를 제공한다. 명명은 ECPF로 하고 있으며, NIC에 DOCA 이미지 설치 시 기본적으로 Device가 제공되고 있다.
NIC에서와 같이 2개의 Device가 제공되어 있다. mst start
명령어를 통해, device가 생성되며, NIC의 HW device와 연결되어 있다.
ECPF가 Host에 접근하기 위한 수단으로 2개의 ECPF에 대해 기본적으로 2개의 Representor가 존재한다. 그리고 Host가 ECPF에 접근하기 위한 수단으로 2개의 PF이 존재한다.
NIC과 Host는 서로의 HW device와 소통하기 위해 각기 다른 수단을 두고 있다. Host의 예로는 Physical Function, Virtual Function, Scalable Function이 있으며, 각각의 명명 규칙이 있다.
pf + pf num ( vf/sf일 경우) + vf/sf + vf/sf num
pf + pf num (vf/sf일 경우) + vf/sf + vf/sf num
SR-IOV는 PCIe device가 PCIe bus를 통해 자신을 여러 개로 노출 시키는 것이다. 이 기능을 통해, 분리된 자원을 사용하는 device의 가상의 인스턴스를 생성할 수 있다.
VF(Virtual fuctions)는 Virtual 인스턴스(VM)와 소통할 수 있는 함수로, PF에 연결되는 추가적인 장치로 볼 수 있다. VF는 PF와 자원을 공유하고, port 또한 PF와 같다. 이를 NIC에서는 representor라고 명명한다.
// Enable SR-IOV
host$ mlxconfig -y -d /dev/mst/mt41686_pciconf0 s SRIOV_EN=1
// Set number of VFs
host$ mlxconfig -y -d /dev/mst/mt41686_pciconf0 s NUM_OF_VFS=X
host$ echo X > /sys/class/net/<physical_function>/device/sriov_numvfs
SF(Scalable fuctions or sub-fuctions)은 SR-IOV에서의 VF와 매우 비슷하다. SF 또한 IO virtualization 기능을 제공하며, Device에 직접 접근할 수 있고, PF와 공유되는 자원이다.
BlueField에서는 VF의 기능을 활용하기 위해, SF를 사용한다. SF는 VF보다 많은 기능을 지원하며, 특히 DPU에서 여러 서비스가 동시에 실행될 수 있다. SF는 Parent PCIe의 가벼운 function이기 때문에, Parent PCIe의 자원에 접근할 수 있으며 자신의 자원과 함수 또한 가지고 있다. 이는, SF는 전용 TX, RX queue를 가지고 있다는 뜻이다.
SF는 PCIe의 SR-IOV 기능과 공존하지만, SR-IOV를 설정할 필요는 없다. SF는 PF와 VF처럼 E-Switch representation를 지원한다. 또한, PCIe 레벨의 자원을 다른 SF 또는 parent PCIe function과 공유한다.
// create SF by adding a port of "pcisf"
// Each SF must have a unique number <sfnum>
/opt/mellanox/iproute2/sbin/mlxdevm port add pci/<pci_address> flavour pcisf pfnum <corresponding pfnum> sfnum <sfnum>
// configure the hardware address, set trust mode to on, activate SF
/opt/mellanox/iproute2/sbin/mlxdevm port function set pci/<pci_address>/<sf_index> hw_addr <MAC address> trust on state active
// unbind SF from defualt config driver
// bind SF actual SF driver
echo mlx5_core.sf.<next_serial> > /sys/bus/auxiliary/drivers/mlx5_core.sf/bind
// the way to know <next_serial>
$ devlink dev show
// output before creating, configuring, deploying the SF
pci/0000:03:00.0
pci/0000:03:00.1
auxiliary/mlx5_core.sf.2
auxiliary/mlx5_core.sf.3
// output after creating, configuring, deploying the SF
pci/0000:03:00.0
pci/0000:03:00.1
auxiliary/mlx5_core.sf.2
auxiliary/mlx5_core.sf.3
auxiliary/mlx5_core.sf.4
// <next_serial> number is 4
// to see the <sfnum> of each sub-function
cat /sys/bus/auxiliary/devices/mlx5_core.sf.<next_serial>/sfnum
// output
4
/opt/mellanox/iproute2/sbin/mlxdevm port function set pci/<pci_address>/<sf_index> state inactive
/opt/mellanox/iproute2/sbin/mlxdevm port del pci/<pci_address>/<sf_index>
선택한 operator를 다른 처리 장치에서 수행하여 결과를 전역으로 저장하여 데이터를 수집하게 도와주는 어플리케이션이다. 결국, 결과는 모든 처리 장치에 다시 배포된다.
Allreduce는 총 3단계로 이루어져 있다.
일련의 allreduce을 각 participant들이 다른 연산을 수행하면, 복잡한 계산을 분산 작업할 수 있다. allreduce는 시뮬레이션이나 데이터 분석, 머신러닝과 같은 HPC 환경에서 병렬 어플리케이션으로 널리 사용된다.
Allreduce는 2가지 type을 지원한다.
어플리케이션은 다음 세 가지 항목을 측정하도록 설계되었다.
Allreduce에는 두 가지 타입의 프로세스가 있다.
cd /opt/mellanox/doca/applications/
meson build
ninja -C build
빌드 할 앱은 meson_options.txt
를 수정함으로써 지정 가능하다.
node 3 NIC에서 daemon 실행 시, localdomain으로 연결 요청 및 연결 요청 실패
connections_init()
in allreduce_core.cconnecting을 연결한 후 이후의 코드가 실행되지 않고 있다.
이를 통해, allreduce_ucx_connect()
함수에서 무한 루프가 돌지 않은지 의심할 수 있다.
allreduce_ucx_connect()
in allreduce_core.c다음은 allreduce_ucx_connect()
함수 내부이다.
함수 내부에는 소켓을 설정하고 연결하는 코드와 이를 확인하기 위해 Active Message를 전송하는 부분이 있다.
여기서 Active Message가 연결을 확인할 때까지 polling하는 방식으로 대기하고 있다.
기존의 실행은 핑을 통해 연결이 됐음을 확인할 수 있었다.
하지만, Active Message Warning이 뜨며, 연결이 완료되지 않았었는데, 여기 Active Message를 통한 연결 확인이 되지 않아 무한 루프를 돌고 있는 것으로 추정된다.
또한 Warning이 뜨는 이유도 이와 관련하여 request Active Message를 전송함과 동시에 Ack Active Message를 전송하는 과정에서 출력되는 것으로 추정된다.
am_send()
in allreduce_ucx.c다음은 am_send()
함수의 내부이며, Active Message를 전송하고, 결과값을 통해 request_process 함수를 수행한다.
ucp_am_send_nbx
함수는 DOCA documentation을 살펴보면 다음과 같은 기능을 한다.
*connection
: connect to send the AM. (end point)am_id
: AM identifier*header
: pointer to a user-defined header for an AMheader_length
: length of the header to send*buffer
: pointer to the AM payloadlength
: number of elements in the payload buffer.param
: additional parametersrequest_process()
in ~.c다음은 request_process()
함수의 내부이다. 각 if문은 예외 처리를 담당하며, 첫 번째 If문에서 연결 성공 시, 콜백 함수를 호출하며 함수를 종료하게 된다.
이 함수를 통해 알 수 있는 것은 request 요청이 성공 적이지 않아(ptr_status != NULL) 0으로 return되어 종료되고 있다.
-a
: 다른 daemon과 상호작용하기 위해 필수, offloaded 클라이언트에게는 연산을 수행할 daemon의 주소를 필수로 입력해야 함. daemon이나 non-offloaded 클라이언트에게 주소가 입력되면, 연산의 수행 결과를 공유한다.-m
: non-offloaded 모드를 기본으로 수행node 4 host와 node 3 NIC의 연결 요청 상황,
둘 다 연결은 되나 host에서 NIC으로 벡터를 보낸 후, offload 연산 수행이 안 됨
node 4 host와 node 3 NIC의 연결 요청 (offloaded 모드 지정)
기존 node 3 host와 연결 요청에서처럼, UCP warning
tcpdump에서는 계속해서 NIC이 연결 요청을 보내는 중
node 4 host에서 daemon으로 실행 시,
성공적으로 연결 후, 클라이언트가 없어 종료
context 2 상황에서 host와 nic 둘 다 client role로 수행하니 연결 후 수행 완료됨.
node 4 daemon, node 3 NIC client로 수행하니 context2와 동일한 상황
node 4 daemon, node 3 NIC client 수행 (offloaded mode),
수행 완료
정리
context | role | mode | role | mode | 결과 |
---|---|---|---|---|---|
1 | daemon | local domain error | |||
2 | client | non-offload | daemon | 배리어 O, 연산 수행 X | |
3 | client | offload | daemon | UCX WARN | |
4 | daemon | good | |||
5 | client | non-offload | client | non-offload | good |
6 | daemon | client | non-offload | 배리어 O, 연산 수행 x | |
7 | daemon | client | offload | good |
context 5의 경우 서로 non-offloaded 연산을 수행 후 결과를 공유하는 것으로 추정
context 2와 6의 경우, 연결 하나 non-offload 연산이라 수행 X 인 것으로 추정
4와 7의 경우 잘 됨
context 1, 3과 기존의 문제를 통해 NIC의 통신 문제 추정
다음은 node 4와 node 3의 NIC에서 daemon을 수행했을 때이다.
node 4의 경우에는 connecting 시도 없이 바로 successful이라는 로그가 뜬다.
하지만, node 3 NIC에서는 local domain으로 연결 시도 중 실패 오류가 난다.
이를 중점으로 코드를 보았다.
main()
in allreduce.c다음은 allreduce의 메인 함수이다.
allreduce_init()
함수에서 연결을 시도하며, 수행이 완료된 후에 "Successfully conected .." 로그가 등장하게 된다.
allreduce_init()
in allreduce_core.c다음은 allreduce_init()
함수이다.
간략하게 설명하면 dest_addresses_init()
함수에서 큐를 생성하여 목적지 엔트리 enqueue 함으로써 연결을 시도 할 목적지를 설정한다.
그리고 allreduce_ucx_init()
함수에서 프로그램 전역에서 사용할 context와 callback을 생성한다.
마지막으로 communication_init()
함수에서 연결을 설정한다. 이때, daemon이나 non-offloaded 클라이언트의 경우 리스너를 먼저 생성한 뒤 연결을 설정한다.
connections_init()
in allreduce_core.c다음 connections_init()
함수를 살펴보면, 큐에서 params를 통해 연결 목적지에 연결하는 모습이다.
여기서 알 수 있는 것은 node 4의 daemon은 큐가 비어있어 연결을 따로 수행하지 않고 daemon이 동작했다는 것이다. node 3의 NIC에서는 -a
를 이용한 peer 설정이 없어도 큐를 통해 연결 설정을 시도했다.
dest_addresses_init()
in allreduce_core.c
// env 설정
export UCX_NET_DEVICES=enp3s0f0s0,mlx5_2:1
// 메모리 limit 해제
Ulimit –l unlimited
// app 실행
/opt/mellanox/doca/applications/allreduce/bin/doca_allreduce -r daemon -t 34001 -i 1 -b 1 -c 1 -l 4
// app 실행
/opt/mellanox/doca/applications/allreduce/bin/doca_allreduce -r client -m offloaded -t 35001 -a 192.168.101.1:34001 -i 1 -b 1 -l 4