InfiniBand / RDMA
InfiniBand 서브넷 아키텍처, RDMA 핵심 개념(QP, CQ, MR), 커널 Verbs API, 사용자 공간(User Space) 라이브러리(libibverbs, librdmacm), IPoIB, 상위 프로토콜(SRP, iSER, NVMe-oF RDMA), GPUDirect RDMA, 성능 최적화 및 트러블슈팅 종합 가이드. 커널 내부 데이터 경로, 핵심 자료구조/API, 운영 환경 튜닝 포인트와 장애 디버깅(Debugging) 절차까지 실무 관점으로 다룹니다.
핵심 요약
- HCA — RDMA 엔진, DMA, QP/CQ를 가진 InfiniBand NIC입니다.
- QP — 송신/수신 작업이 실제로 적재되는 하드웨어 큐 쌍입니다.
- CQ — 완료 이벤트를 회수하는 큐이며 지연(Latency)과 CPU 사용률을 좌우합니다.
- MR / rkey — 원격 접근 가능한 메모리와 그 접근 권한입니다.
- SM / SA — 패브릭에 LID와 경로를 배포하고 파티션 정책을 관리합니다.
단계별 이해
- 패브릭 준비
포트가Active이고 SM이 살아 있으며 LID/GID/P_Key가 정상 배포됐는지 확인합니다. - 리소스 생성
프로세스(Process)는 PD, MR, CQ, QP를 만들고 버퍼를 등록합니다. - 경로 확정
QPN/LID/GID/PSN을 교환하거나librdmacm으로 경로를 해석해 QP를 RTS까지 올립니다. - 데이터 전송
WR을 post하고 CQ를 poll하여 Send, RDMA Write/Read, Atomic을 실행합니다. - 스케일링
SRQ/XRC/DC, NUMA 배치, AR/QoS, GPU 메모리 연동으로 대규모 성능을 끌어올립니다.
InfiniBand 개요
InfiniBand(IB)는 고성능 컴퓨팅(HPC), 데이터센터, 스토리지 인터커넥트를 위해 설계된 고대역폭·저지연 네트워크 기술입니다. RDMA(Remote Direct Memory Access)를 네이티브로 지원하여 커널 바이패스, 제로카피, 프로토콜 오프로드를 통해 전통적인 TCP/IP 소켓(Socket) 대비 극적인 성능 향상을 제공합니다.
이 문서는 네이티브 InfiniBand 패브릭을 중심으로 설명합니다. 같은 Verbs API를 쓰더라도
link_layer가 Ethernet이면 RoCE 계열이며, 이 경우 손실 모델, 혼잡 제어(Congestion Control), 운영 도구, 장애 지점이 달라집니다.
따라서 실무에서는 먼저 장비가 정말 InfiniBand 포트인지, 아니면 Ethernet 포트에서 RDMA를 수행하는지부터 구분해야 합니다.
InfiniBand vs Ethernet 비교
| 특성 | InfiniBand | Ethernet (RoCE v2) | iWARP |
|---|---|---|---|
| RDMA 지원 | 네이티브 | RoCE v1/v2 (컨버전스) | TCP 위에 RDMA |
| 전송 계층 | IB Transport (신뢰성 내장) | UDP/IP (RoCE v2) | TCP/IP |
| 최대 대역폭 | 공개 규격상 XDR 800Gb/s급 이상으로 확장 | 800GbE급까지 확장 | 상용 생태계는 제한적 |
| 지연시간 | ~0.5 μs (HCA-to-HCA) | ~1–2 μs | ~5–10 μs |
| 손실 처리 | 신용 기반 흐름 제어 (Lossless) | PFC/ECN 필요 (Lossless 구성) | TCP 재전송(Retransmission) |
| 서브넷 관리 | Subnet Manager (opensm) | 표준 이더넷 스위칭 | 표준 이더넷 스위칭 |
| 주요 사용처 | HPC, AI/ML 클러스터, 스토리지 | 데이터센터, 클라우드 | 엔터프라이즈 스토리지 |
RDMA 전송 기술 비교
| 기술 | 하위 계층 | 라우팅(Routing) | Lossless 요구 | 특징 |
|---|---|---|---|---|
| IB Native RDMA | InfiniBand L2 | IB 서브넷 라우팅 | 내장 (Credit-based) | 최저 지연, HPC 표준 |
| RoCE v1 | Ethernet L2 | L2 전용 | PFC 필수 | 같은 VLAN 내에서만 동작 |
| RoCE v2 | UDP/IP | IP 라우팅 가능 | PFC + ECN 권장 | L3 라우팅, 데이터센터 표준 |
| iWARP | TCP/IP | IP 라우팅 가능 | 불필요 (TCP 재전송) | Lossy 네트워크 호환, 높은 지연 |
InfiniBand 서브넷 아키텍처
서브넷 구성 요소
InfiniBand 서브넷은 다음 구성 요소로 이루어집니다:
- HCA (Host Channel Adapter): 호스트에 장착되는 InfiniBand NIC. 각 HCA는 1~2개의 물리 포트를 가지며, 각 포트가 서브넷에 연결됩니다. RDMA 엔진, DMA 엔진, QP/CQ 하드웨어 큐를 내장합니다.
- Switch: IB 패킷(Packet)을 LID(Local ID) 기반으로 포워딩하는 L2 스위치. 서브넷 내부 통신을 담당하며, 포워딩 테이블은 Subnet Manager가 설정합니다.
- Router: 서로 다른 서브넷 간 GID 기반 L3 라우팅을 수행합니다.
- Subnet Manager (SM): 서브넷의 중앙 관리자. 토폴로지(Topology) 탐색, LID 할당, 경로 계산, 포워딩 테이블 배포, QoS 설정 등을 수행합니다. 보통 전용 노드 또는 스위치 내장 SM을 사용하며, standby SM으로 HA를 구성합니다.
물리 계층 및 속도
| 세대 | lane 신호 속도 | 대표 포트 속도 | 주요 링크 폭 | 비고 |
|---|---|---|---|---|
| SDR | 2.5 Gbaud | 4X 기준 8 Gb/s | 1X / 4X / 12X | 초기 세대, 8b/10b |
| DDR | 5 Gbaud | 4X 기준 16 Gb/s | 1X / 4X / 12X | 8b/10b |
| QDR | 10 Gbaud | 4X 기준 32 Gb/s | 1X / 4X / 12X | 오랫동안 HPC 표준 |
| FDR | 14.0625 Gbaud | 4X 기준 54.54 Gb/s | 4X / 10X | 64b/66b 도입 |
| EDR | 25 Gbaud | 4X 기준 100 Gb/s | 1X / 2X / 4X | 현대 세대의 출발점 |
| HDR | 50 Gbaud | HDR100(2X), HDR200(4X) | 2X / 4X | PAM4 |
| NDR | 100 Gbaud | NDR200(2X), NDR400(4X) | 2X / 4X | PAM4 |
| XDR | 200 Gbaud | XDR800(4X), 스위치 간 1.6 Tb/s급 링크 지원 | 4X / 8X | 최신 공개 세대 |
주소 체계
- LID (Local Identifier): 16비트, 서브넷 내 고유 주소. Subnet Manager가 할당합니다. 유니캐스트 LID 범위: 0x0001~0xBFFF, 멀티캐스트: 0xC000~0xFFFE.
- GID (Global Identifier): 128비트, IPv6 형식의 글로벌 주소. 서브넷 간 라우팅에 사용됩니다.
Subnet Prefix (64bit) + GUID (64bit)로 구성됩니다. - GUID (Globally Unique Identifier): 64비트, 제조사가 부여하는 고유 식별자. Node GUID, Port GUID, System Image GUID가 있습니다.
- P_Key (Partition Key): 16비트, IB 서브넷 내 논리적 파티션 구분. 이더넷의 VLAN과 유사한 개념으로, 같은 P_Key를 가진 노드끼리만 통신할 수 있습니다.
링크 계층, 경로 선택, QoS
InfiniBand는 단순히 "빠른 NIC"가 아니라, 패브릭 수준에서 경로와 서비스 클래스를 제어하는 링크 계층을 내장합니다. 애플리케이션은 QP를 만들고 WR을 post하지만, 실제 패킷은 SA가 돌려준 Path Record와 SM이 배포한 SL/VL 정책에 따라 흐릅니다.
- Path MTU: 경로별 MTU는 보통 256, 512, 1024, 2048, 4096 바이트 중 하나이며, RC/UC QP의
path_mtu설정은 상대 포트/경로의 지원 범위를 넘을 수 없습니다. - SL (Service Level): 서비스 등급입니다. 애플리케이션이나 ULP가 직접 또는 간접으로 선택하며, SM이 QoS 정책과 함께 배포합니다.
- VL (Virtual Lane): 한 물리 링크를 여러 논리 레인으로 분리해 혼잡을 완화합니다. 실무에서는 데이터 트래픽과 관리 트래픽을 서로 다른 레인 정책으로 취급합니다.
- Credit-based Flow Control: 수신 버퍼 크레딧이 없으면 송신이 멈추므로, 네이티브 IB는 별도의 PFC 없이도 무손실 특성을 유지합니다.
- LMC / 다중 경로: 하나의 포트에 여러 경로 비트를 부여해 동일 엔드포인트 사이에도 여러 포워딩 경로를 만들 수 있습니다.
ibv_post_send() 호출부만 보지 말고, 해당 연결의 Path MTU, SL, P_Key, LID 경로, AR 설정까지 함께 봐야 합니다.
InfiniBand의 병목(Bottleneck)은 소켓 버퍼보다도 경로 정책과 패브릭 큐 배치에서 자주 발생합니다.
관리 평면: SM, SA, MAD, CM
InfiniBand의 관리 평면은 데이터 평면과 분리되어 있습니다. 패브릭이 처음 올라올 때는 SM(Subnet Manager)이 토폴로지를 스캔하고, 각 포트에 LID를 부여하고, 스위치의 포워딩 테이블을 채웁니다. 이후 애플리케이션과 ULP는 SA(Subnet Administration)에 Path Record, 멀티캐스트 그룹, 파티션 정보를 질의합니다.
- MAD (Management Datagram): 관리 패킷 포맷입니다. SM/SA/성능 카운터 질의 등 제어 트래픽이 여기에 실립니다.
- QP0 / QP1: QP0는 Subnet Management 패킷용, QP1은 일반 관리 서비스(GSI)용으로 쓰입니다. 데이터 QP와 별도입니다.
/dev/infiniband/umadX: 사용자 공간에서 MAD를 직접 송수신할 때 쓰는 문자 디바이스입니다.opensm, 진단 도구가 활용합니다./dev/infiniband/issmX: 로컬 포트의 IsSM capability bit를 제어해 해당 포트를 SM 후보로 다룰 때 사용합니다.- RDMA CM: 애플리케이션이 QPN/LID/GID/PSN 교환을 직접 하지 않도록 연결 설정을 자동화하는 계층입니다. 사용자 공간에서는
librdmacm이/dev/infiniband/rdma_cm을 통해 이 기능을 사용합니다.
핵심 자료구조 및 개념
Queue Pair (QP)
QP는 RDMA 통신의 기본 단위로, Send Queue (SQ)와 Receive Queue (RQ) 한 쌍으로 구성됩니다. 각 QP는 고유한 QP Number (QPN)를 가지며, 원격 QP와 1:1(RC), 1:N(UD) 등의 연결 관계를 맺습니다.
- SQ (Send Queue): 송신 측 Work Request(WR)를 큐잉하는 하드웨어 큐. RDMA Write, RDMA Read, Send, Atomic 등의 WR을 제출합니다.
- RQ (Receive Queue): 수신 측 Receive WR을 큐잉합니다. Send/Recv 모델에서 수신 버퍼를 미리 게시(post)해야 합니다.
- SRQ (Shared Receive Queue): 여러 QP가 공유하는 수신 큐. 수신 버퍼를 효율적으로 관리할 수 있습니다.
Completion Queue (CQ)
CQ는 WR의 완료 이벤트(Work Completion, WC)를 수신하는 큐입니다. 하나의 CQ를 여러 QP의 SQ/RQ에 공유할 수 있습니다. 완료 확인 방식으로 polling(busy-wait)과 completion channel(이벤트 기반) 두 가지를 지원합니다.
Memory Region (MR)
RDMA 동작에 사용할 메모리 영역을 HCA에 등록하는 객체입니다. 등록 시 물리 페이지(Page)가 핀(pin)되고, HCA가 DMA로 직접 접근할 수 있는 가상→물리 주소 변환(Address Translation) 테이블이 생성됩니다.
- lkey (Local Key): 로컬 QP가 이 MR에 접근할 때 사용하는 키.
- rkey (Remote Key): 원격 QP가 RDMA Write/Read로 이 MR에 접근할 때 사용하는 키. rkey를 원격 측에 전달해야 RDMA 동작이 가능합니다.
고급 메모리 등록: ODP, MW, dma-buf
실무에서는 단순한 ibv_reg_mr()만으로는 부족한 경우가 많습니다. 대용량 메모리, GPU 메모리, 고빈도 권한 부여/회수 패턴을 처리하려면
메모리 등록 전략 자체를 따로 설계해야 합니다.
- ODP (On-Demand Paging):
IBV_ACCESS_ON_DEMAND플래그로 생성하는 MR입니다. 필요 시점에 페이지를 가져오므로 등록 시간이 짧고,ibv_advise_mr()로 prefetch 힌트를 줄 수 있습니다. - Memory Window (MW): MR 자체를 다시 등록하지 않고도 원격 접근 권한을 bind / invalidate로 동적으로 부여·회수합니다. type 2 MW는 WR 기반 invalidate가 가능해 동적 capability 모델에 적합합니다.
- dma-buf 기반 MR: 최신 사용자 공간은 dma-buf FD를 통해 외부 메모리 객체를 MR로 등록할 수 있습니다. GPU, DPU, 미디어 가속기 메모리를 같은 Verbs 모델에 연결할 때 유용합니다.
- UMR / 간접 mkey: mlx5 계열에서는 여러 산재 버퍼를 커널 개입 없이 간접 키로 재구성하는 UMR 경로를 제공해, SG가 많은 워크로드의 등록 오버헤드(Overhead)를 줄일 수 있습니다.
rkey는 사실상 "원격 DMA 접근 토큰"입니다. 데이터 버퍼 주소와 rkey를 애플리케이션 프로토콜에 평문으로 흘려보내면
그 자체가 capability 누출이 되므로, 제어 채널과 데이터 채널을 분리하고 무효화(Invalidation) 시점을 설계해야 합니다.
Protection Domain (PD) / Address Handle (AH)
- PD (Protection Domain): QP, MR, CQ 등의 리소스를 논리적으로 격리(Isolation)하는 보호 도메인. 같은 PD 내의 리소스만 상호 접근 가능합니다.
- AH (Address Handle): UD QP에서 목적지 주소(LID/GID)를 캡슐화(Encapsulation)하는 객체. RC/UC QP는 연결 시 주소를 설정하므로 AH가 불필요합니다.
Work Request (WR) / Work Completion (WC)
- WR (Work Request): QP의 SQ 또는 RQ에 제출하는 작업 요청. Send, Recv, RDMA Write, RDMA Read, Atomic CAS/FAA 등의 유형이 있습니다. 각 WR은 하나 이상의 SGE (Scatter/Gather Element)로 데이터 버퍼를 지정합니다.
- WC (Work Completion): CQ에서 poll하여 얻는 완료 결과. 성공/실패 상태, 전송 바이트 수, 완료된 WR의 ID(
wr_id) 등을 포함합니다.
QP 상태 머신
QP는 엄격한 상태 전이 순서를 따라야 합니다:
RESET → INIT → RTR → RTS 순서로 전이해야 합니다.
INIT 단계에서 P_Key, 포트 번호, 접근 플래그를 설정하고,
RTR(Ready to Receive)에서 원격 QPN, LID/GID, PSN 등 수신 파라미터를 설정하며,
RTS(Ready to Send)에서 타임아웃, 재시도 횟수 등 송신 파라미터를 설정합니다.
순서를 건너뛰면 ibv_modify_qp()가 -EINVAL을 반환합니다.
ibv_reg_mr()는 물리 페이지를 핀(pin)하므로 대용량 메모리 등록 시 상당한 시간이 소요됩니다.
Mellanox/NVIDIA ConnectX HCA는 ODP (On-Demand Paging)을 지원하여 실제 접근 시점에 페이지를 핀하는 지연 등록을 제공합니다.
ibv_reg_mr() 시 IBV_ACCESS_ON_DEMAND 플래그를 지정하면 ODP가 활성화됩니다.
ODP는 등록 시간을 단축하지만, 첫 접근 시 페이지 폴트(Page Fault) 오버헤드가 발생할 수 있습니다.
RDMA 동작 (Operations)
Send / Receive
양측 관여(two-sided) 동작입니다. 송신 측이 IBV_WR_SEND WR을 SQ에 post하면,
수신 측은 미리 RQ에 게시한 Receive WR의 버퍼로 데이터를 수신합니다.
수신 측 CPU가 반드시 관여해야 하므로 RDMA Write/Read 대비 오버헤드가 높지만,
메시지 도착을 수신 측이 즉시 인지할 수 있는 장점이 있습니다.
RDMA Write / RDMA Write with Immediate
단측 관여(one-sided) 동작입니다. 송신 측이 원격 MR의 주소(rkey + remote addr)를 지정하여 데이터를 직접 씁니다. 수신 측 CPU는 전혀 관여하지 않습니다 (커널 바이패스 + 제로카피). RDMA Write with Immediate는 32비트 즉시값(imm_data)을 함께 전달하며, 수신 측 RQ에서 완료 이벤트가 생성되어 데이터 도착을 알릴 수 있습니다.
RDMA Read
단측 관여 동작입니다. 로컬 측이 원격 MR에서 데이터를 읽어옵니다.
원격 MR의 rkey와 주소를 알아야 하며, 원격 측 CPU 개입 없이 HCA가 직접 처리합니다.
원격 MR에 IBV_ACCESS_REMOTE_READ 접근 권한이 설정되어 있어야 합니다.
Atomic 연산 (CAS, FAA)
- Compare and Swap (CAS): 원격 메모리의 64비트 값을 비교하여 일치하면 새 값으로 교체합니다. 분산 락 구현에 활용됩니다.
- Fetch and Add (FAA): 원격 메모리의 64비트 값에 지정한 값을 더하고 이전 값을 반환합니다. 분산 카운터에 활용됩니다.
Atomic 연산은 RC QP에서만 지원되며, 원격 MR에 IBV_ACCESS_REMOTE_ATOMIC 권한이 필요합니다.
연결 모드 — RC, UC, UD, XRC
| QP 타입 | 연결 방식 | 신뢰성 | 지원 동작 | MTU | 사용 사례 |
|---|---|---|---|---|---|
| RC | 연결 지향 (1:1) | 신뢰 (ACK/재전송) | Send, RDMA Write/Read, Atomic | 최대 4KB | 스토리지, 범용 RDMA |
| UC | 연결 지향 (1:1) | 비신뢰 (무 ACK) | Send, RDMA Write | 최대 4KB | 벌크 전송 (손실 허용) |
| UD | 비연결 (1:N) | 비신뢰 | Send 전용 | 최대 MTU (4KB) | 멀티캐스트, 관리 트래픽 |
| XRC | 연결 지향 (N:1 SRQ) | 신뢰 | Send, RDMA Write/Read, Atomic | 최대 4KB | 다수 프로세스 간 효율적 통신 |
스케일링 모델: SRQ, XRC, DC, Tag Matching
작은 클러스터에서는 RC QP를 peer 수만큼 만드는 단순 모델도 충분하지만, 대규모 MPI/AI/스토리지 환경에서는 QP와 RQ 상태가 급격히 늘어납니다. 그래서 RDMA 스택은 다음과 같은 스케일링 기법을 함께 사용합니다.
- SRQ: 여러 QP가 하나의 Receive Queue를 공유합니다. 대량 fan-in 서버에서 수신 버퍼 총량을 크게 줄일 수 있습니다.
- XRC: 수신 측 컨텍스트를 공유해 RC의 신뢰성은 유지하면서도 연결 수 증가에 따른 메모리 사용량을 억제합니다.
- DC (DCT/DCI): mlx5 계열의 동적 연결 전송입니다. 수많은 피어와 희소 통신을 해야 할 때 모든 피어마다 풀 RC 상태를 만들지 않고도 낮은 지연을 노릴 수 있습니다.
- Tag Matching Offload: MPI의 eager/rendezvous 패턴에서 예상 수신 큐와 unexpected message 리스트를 하드웨어/소프트웨어가 협력 관리하여 메시지 매칭 비용을 줄입니다.
RDMA 동작 비교
| 동작 | 방향 | 관여 측 | 수신 WR 필요 | 원격 rkey 필요 | CQ 완료 위치 |
|---|---|---|---|---|---|
| Send | 송신→수신 | 양측 (two-sided) | 예 | 아니오 | 송신 + 수신 CQ |
| RDMA Write | 로컬→원격 | 송신측만 (one-sided) | 아니오 | 예 | 송신 CQ만 |
| RDMA Write+Imm | 로컬→원격 | 양측 | 예 (imm 수신용) | 예 | 송신 + 수신 CQ |
| RDMA Read | 원격→로컬 | 로컬만 (one-sided) | 아니오 | 예 | 로컬 CQ만 |
| Atomic CAS | 원격 in-place | 로컬만 | 아니오 | 예 | 로컬 CQ만 |
| Atomic FAA | 원격 in-place | 로컬만 | 아니오 | 예 | 로컬 CQ만 |
WQE (Work Queue Element) 아키텍처
Work Queue Element(WQE)는 RDMA HCA가 실제로 처리하는 하드웨어 수준의 작업 단위입니다.
사용자 공간에서 ibv_post_send()로 제출하는 Work Request(WR)는 드라이버 내부에서 WQE로 변환되어
Send Queue(SQ)의 링 버퍼에 기록됩니다. HCA는 이 WQE를 DMA로 읽어 네트워크 패킷을 생성하거나,
수신 측에서는 Receive WQE의 버퍼 주소로 데이터를 직접 기록합니다.
Mellanox/NVIDIA ConnectX 시리즈(mlx5 드라이버)에서 WQE는 64바이트(BB, Basic Block) 단위로 정렬되며, 여러 세그먼트(Segment)로 구성됩니다. 각 세그먼트는 16바이트(DS, Doubleword Segment) 단위로 측정됩니다.
WQE 구조 레이아웃
WQE 세그먼트 상세
Control Segment (mlx5_wqe_ctrl_seg)
모든 Send WQE의 첫 번째 세그먼트입니다. HCA가 WQE를 파싱(Parsing)할 때 가장 먼저 읽는 부분으로, 동작 유형, WQE 크기, 완료 모드 등 핵심 메타데이터를 포함합니다.
/* include/linux/mlx5/qp.h — Control Segment */
struct mlx5_wqe_ctrl_seg {
__be32 opmod_idx_opcode; /* [31:24] opcode modifier
[23:8] WQE index (producer counter)
[7:0] opcode */
__be32 qpn_ds; /* [31:8] QP number
[7:0] WQE 크기 (DS 단위, 16B) */
u8 signature; /* WQE 무결성 검증 시그니처 */
u8 rsvd[2];
u8 fm_ce_se; /* [7:6] fence mode
[3:2] completion mode (CE)
[1:0] solicited event (SE) */
__be32 imm; /* immediate data / invalidation key / UMR mkey */
};
| 필드 | 크기 | 설명 |
|---|---|---|
opcode |
8비트 | 동작 유형: MLX5_OPCODE_SEND(0x0A), MLX5_OPCODE_RDMA_WRITE(0x08), MLX5_OPCODE_RDMA_READ(0x10) 등 |
WQE index |
16비트 | Producer Counter 값. 하드웨어가 CQE에 이 인덱스를 기록하여 완료된 WQE를 식별합니다 |
qpn_ds |
32비트 | 상위 24비트: QP Number, 하위 8비트: WQE 전체 크기(DS 단위). 최소 1 DS(Control만), 최대 255 DS |
fm_ce_se |
8비트 | Fence Mode: None/Initiator Small/Strong Ordering. CE: CQE 생성 여부. SE: 수신 측 이벤트 생성 |
imm |
32비트 | Send with Immediate의 즉시값, Send with Invalidate의 rkey, UMR의 mkey 등 동작별 용도 |
MLX5_FENCE_MODE_NONE: 순서 보장 없음 (최고 성능).
MLX5_FENCE_MODE_INITIATOR_SMALL: 이전 RDMA Read/Atomic의 응답이 도착한 후에만 이 WQE를 실행합니다 (WAR 의존성 보호).
MLX5_FENCE_MODE_STRONG_ORDERING: 이전 모든 WQE의 완료를 보장한 후 실행합니다.
Data Segment (mlx5_wqe_data_seg)
SGE(Scatter/Gather Element)에 해당하는 세그먼트입니다. 각 Data Segment는 하나의 메모리 버퍼를 가리킵니다.
/* include/linux/mlx5/qp.h — Data Segment */
struct mlx5_wqe_data_seg {
__be32 byte_count; /* 전송할 바이트 수 */
__be32 lkey; /* Memory Region의 Local Key */
__be64 addr; /* 버퍼의 가상 주소 (VA) */
};
/* 특수 Data Segment: Inline Data */
struct mlx5_wqe_inline_seg {
__be32 byte_count; /* [31] inline 플래그 = 1, [30:0] 바이트 수 */
/* inline_data[] 가 바로 뒤에 위치 */
};
HCA는 Data Segment의 lkey와 addr를 사용하여 등록된 MR의 주소 변환 테이블을 조회하고,
물리 주소로 DMA를 수행합니다. Inline Data의 경우 byte_count의 최상위 비트가 1로 설정되며,
lkey/addr 대신 WQE 자체에 패킷 데이터가 직접 포함됩니다.
RDMA Segment (mlx5_wqe_raddr_seg)
RDMA Write, RDMA Read, Atomic 동작에서 원격 메모리 주소를 지정하는 세그먼트입니다.
/* include/linux/mlx5/qp.h — RDMA/Remote Address Segment */
struct mlx5_wqe_raddr_seg {
__be64 raddr; /* 원격 가상 주소 */
__be32 rkey; /* 원격 MR의 Remote Key */
__be32 reserved;
};
Ethernet Segment (mlx5_wqe_eth_seg)
Ethernet(mlx5e) 전송 경로에서 사용되는 세그먼트입니다. TCP Segmentation Offload(TSO), 소프트웨어 파서(SWP) 플래그, 인라인 헤더 등을 포함합니다.
/* drivers/net/ethernet/mellanox/mlx5/core/en.h — Ethernet Segment */
struct mlx5_wqe_eth_seg {
u8 swp_outer_l4_offset;
u8 swp_outer_l3_offset;
u8 swp_inner_l4_offset;
u8 swp_inner_l3_offset;
u8 cs_flags; /* 체크섬 오프로드 플래그 */
u8 swp_flags; /* 소프트웨어 파서 플래그 */
__be16 mss; /* TSO의 Max Segment Size */
__be32 flow_table_metadata;
u8 inline_hdr_sz; /* 인라인 헤더 크기 */
u8 inline_hdr_start[17]; /* L2 헤더 인라인 데이터 */
};
Atomic Segment (mlx5_wqe_atomic_seg)
/* include/linux/mlx5/qp.h — Atomic Segment */
struct mlx5_wqe_atomic_seg {
__be64 swap_add; /* CAS: swap 값 / FAA: add 값 */
__be64 compare; /* CAS: 비교 값 / FAA: 미사용 */
};
WQE Opcode 종류
| Opcode | 값 | 세그먼트 구성 | 설명 |
|---|---|---|---|
MLX5_OPCODE_SEND |
0x0A | Ctrl + Data[] | 기본 Send 동작 |
MLX5_OPCODE_SEND_IMM |
0x0B | Ctrl(imm) + Data[] | Send with Immediate (32비트 즉시값 전달) |
MLX5_OPCODE_RDMA_WRITE |
0x08 | Ctrl + RDMA + Data[] | 원격 MR에 직접 쓰기 |
MLX5_OPCODE_RDMA_WRITE_IMM |
0x09 | Ctrl(imm) + RDMA + Data[] | RDMA Write + 수신 측 CQE 생성 |
MLX5_OPCODE_RDMA_READ |
0x10 | Ctrl + RDMA + Data[] | 원격 MR에서 읽기 |
MLX5_OPCODE_ATOMIC_CS |
0x11 | Ctrl + RDMA + Atomic + Data | Compare and Swap |
MLX5_OPCODE_ATOMIC_FA |
0x12 | Ctrl + RDMA + Atomic + Data | Fetch and Add |
MLX5_OPCODE_SEND_INVAL |
0x0F | Ctrl(inv_key) + Data[] | Send with Invalidation (원격 MR 무효화) |
MLX5_OPCODE_TSO |
0x0E | Ctrl + ETH(mss) + Data[] | TCP Segmentation Offload (Ethernet 전송) |
MLX5_OPCODE_UMR |
0x25 | Ctrl + UMR + KLM[] | User Memory Region 재등록 (간접 mkey) |
MLX5_OPCODE_NOP |
0x00 | Ctrl만 | 링 버퍼 끝 패딩 (빈 WQE) |
링 버퍼 관리 (Producer/Consumer 모델)
SQ와 RQ는 고정 크기의 순환 링 버퍼(Cyclic Ring Buffer)로 구현됩니다. 드라이버(소프트웨어)가 Producer, HCA(하드웨어)가 Consumer 역할을 합니다.
/* drivers/net/ethernet/mellanox/mlx5/core/wq.h — 순환 워크 큐 */
struct mlx5_wq_cyc {
struct mlx5_frag_buf_ctrl fbc;
__be32 *db; /* doorbell 레지스터 매핑 */
u16 sz_m1; /* 엔트리 수 - 1 (비트마스크) */
u16 log_stride; /* 엔트리 크기 (log2 바이트) */
};
/* WQE 슬롯 주소 계산 — O(1) 인덱싱 */
static inline void *mlx5_wq_cyc_get_wqe(struct mlx5_wq_cyc *wq, u16 ix)
{
return mlx5_frag_buf_get_wqe(&wq->fbc, ix);
}
/* 가용 슬롯 확인 — 큐가 가득 찼는지 검사 */
static inline int mlx5_wq_cyc_cc_bigger(struct mlx5_wq_cyc *wq,
u16 pc, u16 cc)
{
return !!((u16)(cc - pc) & wq->sz_m1);
}
MLX5_OPCODE_NOP WQE로 패딩하여 분할을 방지합니다.
이는 HCA가 연속 메모리에서 WQE를 읽는 것을 보장합니다.
WQE 포스팅 메커니즘: Doorbell과 BlueFlame
드라이버가 WQE를 SQ 버퍼에 기록한 후, HCA에 새 WQE가 있음을 알려야 합니다. 이를 Doorbell이라 하며, mlx5에서는 두 가지 방식을 지원합니다.
/* drivers/net/ethernet/mellanox/mlx5/core/en_tx.c — Doorbell 처리 (간략화) */
static void mlx5e_notify_hw(struct mlx5_wq_cyc *wq,
u16 pc,
void __iomem *uar_map,
struct mlx5_wqe_ctrl_seg *ctrl)
{
/* 1단계: Doorbell Record에 Producer Index 기록 */
/* HCA가 DB를 DMA로 읽어 새 WQE 존재를 인지 */
wmb(); /* WQE 데이터 → DB 순서 보장 */
*wq->db = cpu_to_be32(pc);
/* 2단계: UAR에 MMIO 쓰기로 HCA를 즉시 깨움 */
wmb(); /* DB → MMIO 순서 보장 */
mlx5_write64((__be32 *)ctrl, /* Control Segment의 첫 8바이트 */
uar_map); /* BlueFlame/Non-cached UAR */
}
/* BlueFlame 경로: WQE 전체를 UAR로 전송 */
static void mlx5e_bf_copy(void __iomem *dst,
struct mlx5_wqe_ctrl_seg *src,
u16 size)
{
/* Write-Combining MMIO로 WQE 데이터를 HCA에 직접 전달 */
/* DMA Read가 필요 없어 PCIe 왕복 1회 절약 */
__iowrite64_copy(dst, src, size / 8);
}
mmap()으로 직접 접근하여 시스템 콜 없이 doorbell을 울릴 수 있습니다.
mlx5에서는 각 QP에 전용 UAR를 할당하여 여러 QP가 동시에 doorbell을 울려도 간섭이 없습니다.
BlueFlame은 UAR 영역을 Write-Combining(WC) 모드로 매핑하여, MMIO 쓰기에 WQE 데이터를 함께 실어 보냅니다.
Ethernet 전송 경로: mlx5e_sq_xmit_wqe()
리눅스 커널의 mlx5e 이더넷 드라이버에서 패킷 전송은 다음 경로를 따릅니다:
/* drivers/net/ethernet/mellanox/mlx5/core/en_tx.c — 전송 WQE 구성 (간략화) */
/* 1단계: ndo_start_xmit 콜백에서 WQE 구성 시작 */
netdev_tx_t mlx5e_xmit(struct sk_buff *skb, struct net_device *dev)
{
struct mlx5e_txqsq *sq = priv->txq2sq[skb_get_queue_mapping(skb)];
/* SQ에 빈 슬롯이 있는지 확인 */
if (unlikely(!mlx5e_wqc_has_room_for(&sq->wq, sq->cc, sq->pc,
sq->stop_room)))
return NETDEV_TX_BUSY;
/* WQE 슬롯 예약 (PI 위치) */
pi = mlx5_wq_cyc_ctr2ix(&sq->wq, sq->pc);
wqe = MLX5E_TX_FETCH_WQE(sq, pi);
/* 2단계: Control Segment 초기화 */
cseg = &wqe->ctrl;
cseg->opmod_idx_opcode = cpu_to_be32((sq->pc << 8) |
MLX5_OPCODE_SEND);
cseg->qpn_ds = cpu_to_be32((sq->sqn << 8) | ds_cnt);
/* 3단계: Ethernet Segment 설정 (TSO, 체크섬 등) */
eseg = &wqe->eth;
eseg->cs_flags = MLX5_ETH_WQE_L3_CSUM | MLX5_ETH_WQE_L4_CSUM;
if (skb_is_gso(skb))
eseg->mss = cpu_to_be16(skb_shinfo(skb)->gso_size);
/* 4단계: Data Segment — SKB fragment를 SGE로 변환 */
dseg = (struct mlx5_wqe_data_seg *)eseg + 1;
for (i = 0; i < num_frags; i++) {
dseg->byte_count = cpu_to_be32(frag_len);
dseg->lkey = sq->mkey_be;
dseg->addr = cpu_to_be64(dma_addr);
dseg++;
}
/* 5단계: Producer Counter 증가 + Doorbell */
sq->pc += num_wqebbs; /* WQE Basic Block 수만큼 증가 */
mlx5e_notify_hw(&sq->wq, sq->pc, sq->uar_map, cseg);
return NETDEV_TX_OK;
}
WQE와 CQE의 관계
HCA가 WQE를 처리 완료하면 CQE(Completion Queue Element)를 생성하여 Completion Queue에 기록합니다. 드라이버는 CQE를 폴링하여 전송 완료를 확인하고 리소스를 회수합니다.
/* include/linux/mlx5/cqe.h — CQE 구조 (64바이트 모드, 간략화) */
struct mlx5_cqe64 {
u8 outer_l3_tunneled;
u8 rsvd0;
__be16 wqe_id; /* 완료된 WQE의 인덱스 */
u8 lro_tcppsh_abort_dupack;
u8 l4_l3_hdr_type;
__be16 vlan_info;
__be32 srqn_uidx;
__be32 imm_inval_pkey; /* immediate data / invalidated rkey */
u8 rsvd40[4];
__be32 byte_cnt; /* 수신 바이트 수 */
__be32 timestamp_h;
__be32 timestamp_l;
__be32 sop_qpn; /* [31:24] opcode, [23:0] QP number */
__be16 wqe_counter; /* SQ: 완료된 마지막 WQE의 PI 값 */
u8 signature;
u8 op_own; /* [7:4] CQE opcode, [0] ownership bit */
};
/* 완료 처리 — Consumer Counter 갱신 */
static void mlx5e_handle_tx_cqe(struct mlx5e_txqsq *sq,
struct mlx5_cqe64 *cqe)
{
u16 wqe_counter = be16_to_cpu(cqe->wqe_counter);
u16 npackets;
/* wqe_counter까지의 모든 WQE가 완료됨 */
npackets = wqe_counter - sq->cc;
for (i = 0; i < npackets; i++) {
/* DMA unmap, SKB 해제 */
mlx5e_free_tx_wqe(sq);
sq->cc++; /* Consumer Counter 증가 → SQ 슬롯 회수 */
}
}
/* CQE의 ownership bit로 새 CQE 여부 판별 */
static inline int mlx5_get_cqe_ownership(struct mlx5_cqe64 *cqe)
{
return cqe->op_own & MLX5_CQE_OWNER_MASK; /* bit 0 */
}
cqe_ts_to_ns)만 기록하는 미니 CQE(8바이트) 배열로 압축되며,
드라이버가 압축을 풀어 개별 완료를 처리합니다.
이 기능은 소형 패킷이 대량으로 들어오는 워크로드에서 CQ 메모리 대역폭을 50% 이상 절감합니다.
WQE 성능 최적화 기법
1. Inline Data
패킷 데이터(또는 일부)를 WQE 안에 직접 삽입하는 기법입니다. HCA가 별도의 DMA Read 없이 WQE 자체에서 데이터를 읽을 수 있어 소형 패킷의 지연시간을 줄입니다.
txq_inline_min: L2 헤더(최소 ETH_HLEN)를 항상 인라인. HCA가 패킷 파싱에 필요합니다.txq_inline_max: 이 크기 이하의 전체 패킷을 인라인. DMA 매핑 비용을 제거합니다.- 트레이드오프: 인라인은 WQE 크기를 늘리므로 SQ 용량이 감소합니다.
2. Multi-Packet WQE (MPWQE)
ConnectX-5 이상에서 지원하는 확장 기능으로, 하나의 WQE로 여러 패킷을 전송합니다. Ethernet Segment(eseg)를 공유하여 WQE당 제어 오버헤드를 줄이고, 소형 패킷 전송 시 Doorbell 횟수와 PCIe 대역폭을 대폭 절감합니다.
/* MPWQE 모드: 하나의 WQE에 여러 패킷의 데이터를 연속 배치 */
/* */
/* +-------------------+ */
/* | Control Segment | opcode = ENHANCED_MPSW (0x29) */
/* +-------------------+ */
/* | Ethernet Segment | 공유 (TSO, checksum) */
/* +-------------------+ */
/* | Pkt 0 Data (inline or ptr) */
/* +-------------------+ */
/* | Pkt 1 Data (inline or ptr) */
/* +-------------------+ */
/* | ... | */
/* +-------------------+ */
/* */
/* MPWQE는 동일한 체크섬/오프로드 설정을 가진 패킷에 적합 */
3. RDMA Receive WQE Striding (Strided RQ)
수신 측에서 하나의 큰 버퍼를 여러 stride(보폭)로 나누어, 하나의 Receive WQE가 여러 수신 패킷을 처리합니다. WQE 재게시(re-post) 빈도를 크게 줄여 수신 경로의 CPU 오버헤드를 낮춥니다.
4. WQE 기반 하드웨어 스티어링 (HWS)
ConnectX-6 Dx 이상에서는 플로우 룰을 WQE로 HCA에 삽입하는 Hardware Steering을 지원합니다. 초당 수백만 개의 플로우 룰을 삽입/삭제할 수 있으며, 커널의 TC flower/OVS offload가 이 경로를 사용합니다.
5. 성능 비교 요약
| 최적화 기법 | 대상 | 메커니즘 | 효과 |
|---|---|---|---|
| BlueFlame | 소형 WQE (1 BB) | WQE를 WC MMIO로 직접 전달 | PCIe RTT 1회 절약, 지연시간 ~50% 감소 |
| Inline Data | 소형 패킷 | 패킷 데이터를 WQE에 삽입 | DMA Read 제거, DMA 매핑 비용 제거 |
| MPWQE | 다수 소형 패킷 전송 | 1 WQE에 여러 패킷 묶음 | Doorbell 횟수 감소, PCIe 대역폭 절감 |
| Strided RQ | 수신 경로 | 1 WQE로 여러 패킷 수신 | WQE 재게시 빈도 감소, CPU 부하 감소 |
| CQE Compression | 대량 완료 이벤트 | 유사 CQE를 미니 CQE로 압축 | CQ 메모리 대역폭 50%+ 절감 |
| Signaled Completion | Send WQE | N개 WQE 중 1개만 CQE 생성 | CQE 생성 빈도 감소, HCA 처리량 증가 |
fm_ce_se 필드에서 CE(Completion Enable) 비트로 제어합니다.
Unsignaled WQE는 CQE를 생성하지 않아 HCA와 CQ의 부하를 줄이지만,
SQ 슬롯을 회수하려면 이후의 Signaled WQE가 완료되어야 합니다.
일반적으로 16~64개 WQE마다 1개를 Signaled로 설정하는 것이 최적입니다.
Receive WQE 구조
Receive WQE는 Send WQE와 달리 Control Segment가 없습니다. Data Segment(SGE)만으로 구성되며, HCA가 수신한 패킷을 기록할 버퍼 위치를 지정합니다.
/* Receive WQE — Data Segment의 배열 */
struct mlx5e_rx_wqe {
struct mlx5_wqe_data_seg data[1]; /* 가변 길이 SGE 배열 */
};
/* 수신 WQE 게시 — RQ에 빈 버퍼를 미리 등록 */
static void mlx5e_post_rx_wqes(struct mlx5e_rq *rq)
{
while (mlx5_wq_cyc_missing(wq) >= rq->wqe_bulk) {
u16 ix = mlx5_wq_cyc_get_head(wq);
struct mlx5e_rx_wqe *wqe = mlx5_wq_cyc_get_wqe(wq, ix);
/* 페이지를 할당하고 DMA 매핑 */
dma_addr = dma_map_page(rq->pdev, page, 0, PAGE_SIZE,
DMA_FROM_DEVICE);
/* Data Segment에 버퍼 정보 기록 */
wqe->data[0].byte_count = cpu_to_be32(rq->buff.wqe_sz);
wqe->data[0].lkey = rq->mkey_be;
wqe->data[0].addr = cpu_to_be64(dma_addr);
mlx5_wq_cyc_push(wq); /* Head 인덱스 증가 */
}
/* Doorbell: HCA에 새 Receive WQE가 있음을 알림 */
wmb();
*wq->db = cpu_to_be32(mlx5_wq_cyc_get_head(wq));
}
delay_drop 기능으로 WQE 고갈 시 일정 시간 동안 패킷을 보류할 수 있지만,
근본적으로는 RQ 크기를 충분히 설정하고 NAPI 폴링이 적시에 WQE를 재게시해야 합니다.
ethtool -S의 rx_wqe_err 카운터로 WQE 부족 이벤트를 모니터링합니다.
WQE 전체 라이프사이클 정리
| 단계 | 주체 | 동작 | 관련 자료구조 |
|---|---|---|---|
| 1. WQE 구성 | 드라이버 (CPU) | Control/Data/RDMA 세그먼트를 SQ 버퍼에 기록 | mlx5_wqe_ctrl_seg, mlx5_wqe_data_seg |
| 2. PI 갱신 | 드라이버 (CPU) | Producer Counter 증가, Doorbell Record에 PI 기록 | sq->pc, wq->db |
| 3. Doorbell Ring | 드라이버 (CPU) | UAR에 MMIO 쓰기 (일반) 또는 BlueFlame (WC MMIO) | uar_map |
| 4. WQE Fetch | HCA (하드웨어) | PCIe DMA Read로 WQE를 가져옴 (BlueFlame 시 생략) | PCIe TLP |
| 5. 데이터 DMA | HCA (하드웨어) | Data Segment의 lkey/addr로 패킷 데이터를 DMA Read | mlx5_wqe_data_seg |
| 6. 패킷 전송 | HCA (하드웨어) | 네트워크 패킷 조립 및 와이어로 전송 | HW 패킷 엔진 |
| 7. CQE 생성 | HCA (하드웨어) | 완료 이벤트를 CQ에 DMA Write (Signaled WQE만) | mlx5_cqe64 |
| 8. 완료 폴링 | 드라이버 (CPU) | NAPI poll 또는 ibv_poll_cq()로 CQE를 읽고 CC 갱신 | sq->cc |
| 9. 리소스 회수 | 드라이버 (CPU) | DMA unmap, SKB 해제, SQ 슬롯 재사용 가능 | sk_buff, dma_unmap |
QP 유형별 WQE 차이
WQE의 세그먼트 구성과 의미는 QP 전송 유형(Transport Type)에 따라 달라집니다.
동일한 MLX5_OPCODE_SEND라도 QP 유형에 따라 포함되는 세그먼트가 다릅니다.
| QP 유형 | Send WQE 구성 | Receive WQE | 특징 |
|---|---|---|---|
| RC (Reliable Connected) | Ctrl + [RDMA/Atomic] + Data[] | Data[] (또는 SRQ) | 재전송 보장, RDMA Read/Write/Atomic 지원. 연결 당 QP 1:1 매핑 |
| UC (Unreliable Connected) | Ctrl + [RDMA] + Data[] | Data[] | RDMA Write 가능하나 Read/Atomic 불가. 재전송 없음, ACK 불필요 |
| UD (Unreliable Datagram) | Ctrl + UD Segment + Data[] | Data[] (GRH 40B 포함) | 1:N 통신. UD Segment에 목적지 QPN/Q_Key/AH 포함. MTU 이내만 전송 가능 |
| XRC (Extended RC) | Ctrl + XRC Segment + [RDMA] + Data[] | SRQ 공유 | 여러 Send QP가 하나의 SRQ를 공유. 대규모 클러스터에서 메모리 절약 |
| Raw Ethernet | Ctrl + ETH + Data[] | Data[] (Strided 가능) | Ethernet NIC 모드. TSO/체크섬 오프로드. mlx5e 드라이버가 사용 |
| DC (Dynamically Connected) | Ctrl + AV Segment + [RDMA] + Data[] | DCT의 SRQ | Mellanox 확장. DCI가 동적으로 DCT에 연결. UD의 확장성 + RC의 기능 |
/* include/linux/mlx5/qp.h — UD Address Vector Segment */
struct mlx5_wqe_datagram_seg {
__be32 av[8]; /* Address Vector: 목적지 GID/LID/SL/port */
__be32 av_flags; /* GRH 사용 여부, 경로 MTU 등 */
__be32 dqp_dct; /* [31:8] 목적지 QP Number, [7:0] 예약 */
__be32 qkey; /* Q_Key — UD 패킷 인증용 */
u8 rsvd[4];
};
buf + 40입니다.
ibv_wc의 wc_flags에 IBV_WC_GRH가 설정되어 GRH 유무를 확인할 수 있습니다.
Shared Receive Queue (SRQ) WQE
SRQ는 여러 QP가 하나의 Receive Queue를 공유하는 메커니즘입니다. 각 QP가 개별 RQ를 유지할 필요가 없어, N개 QP 연결 시 Receive WQE 버퍼 메모리가 O(N)에서 O(1)로 줄어듭니다. 대규모 MPI 클러스터에서는 수만 개의 QP가 존재하므로, SRQ 없이는 수신 버퍼만으로도 수 GB의 메모리가 필요합니다.
/* include/linux/mlx5/qp.h — SRQ WQE 구조 */
struct mlx5_wqe_srq_next_seg {
u8 rsvd0[2];
__be16 next_wqe_index; /* 링크드 리스트: 다음 빈 WQE 인덱스 */
u8 signature;
u8 rsvd1[11];
};
/* SRQ WQE = Next Segment (16B) + Data Segments[]
*
* SQ/RQ의 순환 링 버퍼와 달리, SRQ WQE는 linked list로 관리됩니다.
* next_wqe_index가 다음 사용 가능한 WQE를 가리키며,
* HCA는 수신 패킷 도착 시 head에서 WQE를 꺼내 사용합니다.
*/
/* SRQ WQE 게시: 빈 버퍼를 SRQ에 추가 */
static void mlx5_ib_post_srq_wqe(struct mlx5_ib_srq *srq,
int wqe_index)
{
struct mlx5_wqe_srq_next_seg *next;
next = mlx5_frag_buf_get_wqe(&srq->fbc, wqe_index);
/* tail의 next를 새 WQE로 연결 */
srq->tail_next->next_wqe_index = cpu_to_be16(wqe_index);
srq->tail_next = next;
/* Doorbell: HCA에 새 SRQ WQE가 있음을 알림 */
wmb();
*srq->db.db = cpu_to_be32(srq->counter);
}
| 비교 항목 | 개별 RQ | SRQ |
|---|---|---|
| WQE 관리 구조 | 순환 링 버퍼 (Producer/Consumer 인덱스) | 링크드 리스트 (next_wqe_index) |
| 버퍼 메모리 (QP N개) | N × RQ_depth × WQE 크기 | 1 × SRQ_depth × WQE 크기 |
| WQE 회수 방식 | CQE의 wqe_counter로 CC 갱신 | CQE의 wqe_id → 해당 WQE를 free list에 반환 |
| Starvation 영향 | 해당 QP만 드롭 | SRQ 고갈 시 모든 연결된 QP가 드롭 |
| 적합한 워크로드 | 소수 QP, 고빈도 수신 | 수천~수만 QP, 비균일 수신 패턴 |
SRQ_LIMIT_REACHED 비동기 이벤트를 생성합니다.
드라이버는 이 이벤트를 받아 WQE를 재게시하여 고갈을 방지합니다.
ibv_modify_srq()의 srq_limit 파라미터로 임계값을 설정합니다.
UMR WQE (User Memory Registration)
UMR(User Memory Registration)은 데이터 경로에서 WQE를 통해 MR(Memory Region)을 등록/수정하는 메커니즘입니다.
기존의 ibv_reg_mr()는 커널 명령 인터페이스를 거치므로 지연시간이 크지만,
UMR WQE는 일반 Send WQE처럼 SQ에 제출되어 마이크로초 단위로 MR 메타데이터를 변경합니다.
/* include/linux/mlx5/qp.h — UMR Control Segment */
struct mlx5_wqe_umr_ctrl_seg {
u8 flags; /* inline KLM / indirect mkey 플래그 */
u8 rsvd0[3];
__be16 xlt_octowords; /* 변환 테이블 엔트리 수 (8B 단위) */
u8 rsvd1[2];
__be64 mkey_mask; /* 수정할 mkey 속성 비트마스크 */
u8 rsvd2[32];
};
/* UMR WQE = Ctrl(opcode=0x25) + UMR Ctrl Seg + mkey Context + KLM[]
*
* KLM(Key/Length/Memory) 엔트리: 물리 페이지 매핑 정보
* - bcount: 이 엔트리가 커버하는 바이트 수
* - key: 자식 mkey (간접 MR 구성 시)
* - va: 물리/가상 주소
*/
struct mlx5_klm {
__be32 bcount; /* 바이트 수 */
__be32 key; /* 자식 mkey 또는 0 */
__be64 va; /* 가상 주소 */
};
| UMR 활용 사례 | 설명 |
|---|---|
| Fast Registration | NVMe-oF, iSER 등에서 I/O마다 MR을 동적 등록/해제. 제어 경로 대신 데이터 경로로 처리하여 지연시간 수십 usec → 수 usec |
| Indirect MR | 여러 MR을 KLM 리스트로 묶어 하나의 가상 연속 MR로 구성. 비연속 물리 메모리를 RDMA로 단일 접근 가능 |
| MR Invalidation | MLX5_OPCODE_SEND_INVAL과 연계하여 원격 측에서 MR을 무효화. 보안 및 MR 재사용에 활용 |
| Signature MR | DIF(Data Integrity Field)/DIX 보호를 위한 시그니처 속성을 MR에 부착. NVMe-oF에서 end-to-end 데이터 무결성 보장 |
확장 Atomic WQE
표준 IB Atomic 동작은 64비트 Compare-and-Swap과 Fetch-and-Add만 제공하지만,
ConnectX 시리즈는 확장 Atomic 연산을 지원합니다.
이 연산들은 Control Segment의 opmod 필드로 구분됩니다.
| 연산 | Opcode/Opmod | Atomic Segment | 설명 |
|---|---|---|---|
| CAS 64 | 0x11 / 0x00 | swap(8B) + compare(8B) | 표준 64비트 Compare-and-Swap |
| FAA 64 | 0x12 / 0x00 | add(8B) + 0(8B) | 표준 64비트 Fetch-and-Add |
| Masked CAS | 0x11 / 0x01 | swap(8B) + compare(8B) + swap_mask(8B) + compare_mask(8B) | 비트 마스크 적용. 특정 비트만 선택적으로 CAS 수행 |
| Masked FAA | 0x12 / 0x01 | add(8B) + boundary(8B) + field_mask(8B) | 비트 필드 단위 덧셈. 하나의 64비트 워드 내 여러 카운터를 독립적으로 증가 |
| Extended CAS (128b+) | 0x11 / log2(size) | swap(NB) + compare(NB) | 128비트, 256비트 등 확장 크기 CAS. DCAS(Double CAS) 구현 가능 |
WQE 메모리 순서 보장과 배리어
WQE는 CPU와 HCA 사이의 공유 메모리 인터페이스이므로, 메모리 순서(Memory Ordering)가 정확히 보장되어야 합니다. x86은 TSO(Total Store Ordering)로 비교적 강한 순서를 제공하지만, ARM64/RISC-V 등 약한 순서 모델에서는 명시적 배리어가 필수입니다.
| 배리어 위치 | 리눅스 커널 API | x86 | ARM64 | 목적 |
|---|---|---|---|---|
| WQE 데이터 → DB Record | wmb() |
sfence (컴파일러 배리어만으로 충분) | DMB ST | HCA가 DB를 먼저 읽고 불완전한 WQE를 가져가는 것을 방지 |
| DB Record → UAR MMIO | wmb() |
sfence | DMB ST | DB가 메모리에 기록된 후에 doorbell이 울리도록 보장 |
| CQE 읽기 → ownership bit | dma_rmb() |
lfence (보통 불필요) | DMB LD | ownership bit가 유효하면 CQE 전체가 유효함을 보장 |
| BlueFlame WC 쓰기 | __iowrite64_copy() |
MOVNTI (WC 영역) | Non-temporal ST | Write-Combining으로 64B를 하나의 PCIe TLP로 병합 |
/* WQE 포스팅 시 배리어 순서 — 핵심 불변식(invariant) */
/* 1. WQE 세그먼트 기록 (Ctrl, Data, RDMA 등)
* — 일반 store 명령으로 시스템 메모리에 기록
*/
cseg->opmod_idx_opcode = cpu_to_be32(...);
dseg->addr = cpu_to_be64(dma_addr);
wmb(); /* ① WQE 데이터가 메모리에 확정됨을 보장 */
/* 2. Doorbell Record에 Producer Index 기록
* — HCA는 이 값을 폴링하거나 doorbell 시 참조
*/
*wq->db = cpu_to_be32(sq->pc);
wmb(); /* ② DB가 메모리에 기록된 후 doorbell MMIO 수행 */
/* 3. UAR MMIO 쓰기 — HCA가 즉시 처리 시작
* MMIO는 uncacheable/WC 영역이므로 자체적으로 순서 보장
*/
mlx5_write64(ctrl, uar_map);
/* 배리어를 빠뜨리면 발생할 수 있는 증상:
* - ① 누락: HCA가 불완전한 WQE를 읽음 → CQE_SYNDROME_LOCAL_LENGTH_ERR
* - ② 누락: HCA가 이전 PI를 읽음 → WQE를 인식하지 못하고 무시
* 두 경우 모두 x86에서는 드물지만, ARM64에서는 즉시 재현됩니다.
*/
WQE 에러 처리와 디버깅
HCA가 WQE를 처리하다 에러가 발생하면, Error CQE를 생성하고
해당 QP를 Error 상태(SQE/SQD/ERR)로 전이시킵니다.
Error CQE의 syndrome 필드가 에러 원인을 나타냅니다.
| Syndrome 값 | 에러 유형 | 원인 | 일반적인 해결책 |
|---|---|---|---|
| 0x01 | LOCAL_LENGTH_ERR | Data Segment의 byte_count 합계가 메시지 크기와 불일치. 또는 WQE DS 수가 잘못됨 | SGE 리스트 검증, WQE 크기(qpn_ds) 재확인 |
| 0x02 | LOCAL_QP_OP_ERR | QP 상태가 RTS가 아닌데 Send 시도. 또는 지원하지 않는 opcode 사용 | QP 상태 머신 확인, ibv_modify_qp() 순서 검증 |
| 0x04 | LOCAL_PROT_ERR | lkey가 유효하지 않거나 MR 권한(read/write) 부족. DMA 주소가 MR 범위 밖 | ibv_reg_mr()의 access 플래그 확인, MR 범위와 주소 일치 검증 |
| 0x05 | WR_FLUSH_ERR | QP가 Error 상태로 전이된 이후 SQ에 남아 있던 WQE. 정상적인 cleanup 과정 | 이전 에러의 원인을 확인. 이 에러 자체는 부수 효과 |
| 0x06 | MW_BIND_ERR | Memory Window Bind 실패. MW의 base MR이 유효하지 않거나 범위 초과 | MW와 base MR의 관계 검증 |
| 0x10 | BAD_RESP_ERR | 원격 측 응답이 프로토콜 위반. 패킷 손상이나 펌웨어 버그 | 양측 HCA 펌웨어 버전 확인, 케이블/스위치 점검 |
| 0x11 | LOCAL_ACCESS_ERR | 로컬 MR에 write 접근 불가 (RDMA Read 응답 기록 실패) | MR 등록 시 IBV_ACCESS_LOCAL_WRITE 포함 확인 |
| 0x12 | REMOTE_INV_REQ_ERR | 원격 측에서 잘못된 요청 수신 (잘못된 rkey, 범위 초과 등) | 원격 MR의 rkey, 주소 범위, access 권한 확인 |
| 0x13 | REMOTE_ACCESS_ERR | 원격 MR에 접근 권한 없음 (Remote Write/Read에 IBV_ACCESS_REMOTE_* 미설정) |
원격 측 MR의 access 플래그 확인 |
| 0x15 | TRANSPORT_RETRY_EXCEEDED | RC QP에서 재전송 횟수 초과 (원격 측 미응답) | 네트워크 연결 확인, QP의 retry_cnt/timeout 튜닝 |
| 0x16 | RNR_RETRY_EXCEEDED | 원격 RQ에 Receive WQE가 없어 RNR NAK 반복 후 재시도 초과 | 원격 측 RQ 깊이 증가, rnr_retry 값 증가 (7 = infinite) |
/* 에러 CQE 처리 — mlx5e Ethernet 경로 */
static void mlx5e_handle_tx_cqe_error(struct mlx5e_txqsq *sq,
struct mlx5_cqe64 *cqe)
{
u8 syndrome = cqe->op_own >> 1; /* CQE opcode 추출 */
if (syndrome == MLX5_CQE_SYNDROME_LOCAL_LENGTH_ERR)
sq->stats->cqe_err++;
/* 에러 QP는 더 이상 WQE를 처리하지 않음 → SQ의 모든 미완료 WQE를 flush */
while (sq->cc != sq->pc) {
mlx5e_free_tx_wqe(sq); /* DMA unmap + SKB 해제 */
sq->cc++;
}
/* QP 복구: Error → Reset → Init → RTR → RTS */
mlx5e_reporter_tx_err_cqe(sq);
}
/* 디버깅: dmesg에서 WQE 에러 syndrome 확인 */
/* mlx5_core: CQE error on CQN 0x... syndrome 0x04 (LOCAL_PROT_ERR)
*
* 추가 디버깅 도구:
* ethtool -S | grep err — 에러 카운터 확인
* rdma res show qp — QP 상태 확인 (ERR/SQE)
* ibv_devinfo -v — HCA 능력(capability) 확인
* mlx5_core 모듈의 debug_mask — 상세 로그 활성화
*/
WR_FLUSH_ERR syndrome의 Error CQE가 생성됩니다.
RC QP에서는 하나의 WQE 에러가 전체 QP를 중단시키므로,
복구하려면 QP를 Reset → Init → RTR → RTS 순서로 재설정해야 합니다.
UC QP에서는 해당 WQE만 실패하고 다음 WQE는 정상 처리됩니다.
WQE 무결성 검증 (Signature)
Control Segment의 signature 필드는 WQE 메모리 무결성을 검증하는 데 사용됩니다.
HCA가 WQE를 DMA로 가져올 때, 시그니처가 불일치하면 에러를 발생시킵니다.
이는 메모리 오류나 드라이버 버그로 인한 손상된 WQE 실행을 방지하는 안전장치입니다.
/* drivers/infiniband/hw/mlx5/wr.c — WQE 시그니처 계산 */
static u8 calc_sig(void *wqe, int size)
{
u8 *p = wqe;
u8 res = 0;
int i;
/* WQE 전체를 XOR하여 1바이트 시그니처 생성 */
for (i = 0; i < size; i++)
res ^= p[i];
return ~res; /* 비트 반전하여 반환 */
}
/* 시그니처 설정: WQE 기록 완료 후, doorbell 전에 호출 */
static void set_sig_seg(struct mlx5_rwqe_sig *sig_seg,
int size, u8 signature)
{
sig_seg->signature = calc_sig(sig_seg, size);
}
/* 시그니처 기능은 QP 생성 시 활성화:
* struct mlx5_ib_create_qp → flags |= MLX5_QP_FLAG_SIGNATURE
*
* 활성화 시:
* - WQE 크기가 16B 증가 (시그니처 세그먼트 추가)
* - HCA가 WQE fetch 시 시그니처를 검증
* - 불일치 시 LOCAL_QP_OP_ERR 에러 CQE 생성
*
* 주의: 시그니처는 성능 오버헤드가 있으므로 디버깅/검증 목적으로만 사용
*/
WQE 캐시와 메모리 최적화
고성능 RDMA 환경에서는 WQE 메모리 접근 패턴이 성능에 직접적인 영향을 미칩니다. WQE 버퍼의 물리 메모리 배치, 캐시 라인 정렬, NUMA 지역성이 모두 중요합니다.
| 최적화 영역 | mlx5 구현 | 성능 영향 |
|---|---|---|
| 캐시 라인 정렬 | WQE BB(64B)가 정확히 캐시 라인 1개에 대응. Control Segment가 BB 시작에 위치 | WQE fetch 시 한 번의 캐시 라인 읽기로 Control Segment 전체를 처리 |
| 순차 접근 패턴 | 링 버퍼의 Producer Index가 순차 증가 → 선형 메모리 접근 | CPU L1/L2 캐시 프리페치(prefetch) 효과. 캐시 미스율 최소화 |
| NUMA 지역성 | SQ 버퍼를 HCA가 연결된 NUMA 노드에 할당 (node_aware_alloc) |
원격 NUMA 접근 지연(~100ns) 방지. 특히 DMA 경로에서 중요 |
| DMA 매핑 방식 | 큰 SQ는 mlx5_frag_buf으로 비연속 물리 페이지를 관리. IOMMU가 연속 DMA 주소로 매핑 |
대형 SQ(수천 WQE)에서 연속 물리 메모리 확보 실패 방지 |
| Hot/Cold 분리 | WQE 메타데이터(mlx5e_tx_wqe_info)는 별도 배열로 관리. WQE 버퍼와 분리 |
HCA DMA 영역을 오염시키지 않고 CPU 측 메타데이터 접근 |
/* drivers/net/ethernet/mellanox/mlx5/core/en.h — TX WQE 메타데이터 (CPU 전용) */
struct mlx5e_tx_wqe_info {
struct sk_buff *skb; /* 전송 완료 시 해제할 SKB */
u32 num_bytes; /* 전송 바이트 수 (통계용) */
u8 num_wqebbs; /* 이 패킷이 차지하는 BB 수 */
u8 num_dma; /* DMA 매핑 수 (unmap 시 필요) */
};
/* SQ 초기화 시 WQE 버퍼와 메타데이터 배열을 분리 할당:
*
* wqe_info[] — CPU만 접근하는 소프트웨어 메타데이터
* - 캐시 라인: CPU 코어의 L1에 상주
* - 크기: sizeof(mlx5e_tx_wqe_info) × SQ_depth
*
* wq.buf — HCA와 CPU 모두 접근하는 WQE 데이터
* - DMA 매핑된 coherent 메모리
* - HCA가 DMA Read로 가져감
*
* 이 분리는 false sharing을 방지하고,
* HCA DMA가 CPU 캐시를 불필요하게 무효화하지 않도록 합니다.
*/
WQE 크기 제한과 튜닝 파라미터
WQE 크기는 Control Segment의 qpn_ds 하위 8비트로 지정되므로
최대 255 DS = 4,080바이트입니다.
실제 운용에서는 SQ 깊이(WQE 슬롯 수), WQE 크기, SGE 수의 균형이 중요합니다.
| 파라미터 | 설정 방법 | 기본값 (mlx5) | 권장 범위 |
|---|---|---|---|
| SQ 깊이 | QP 생성 시 max_send_wr / ethtool -G tx |
1024 (Ethernet), 128 (IB verbs) | 128~8192. 지연시간 중시면 작게, 처리량 중시면 크게 |
| RQ 깊이 | QP 생성 시 max_recv_wr / ethtool -G rx |
1024 | 512~8192. Starvation 방지 위해 충분히 크게 |
| SGE 수 | QP 생성 시 max_send_sge, max_recv_sge |
1 (Ethernet), 30+ (IB verbs) | Scatter/Gather 수에 따라. 많을수록 WQE 크기 증가 |
| Inline 크기 | ethtool --set-priv-flags / 모듈 파라미터 |
18 (ETH_HLEN + VLAN) | 18~128. 소형 패킷이 많으면 증가 고려 |
| Log WQE stride | HCA 능력에 따라 자동 설정 | 6 (64B = 1 BB) | 6~7. Strided RQ에서는 더 큰 stride 가능 |
# WQE 관련 ethtool 튜닝 예시
# SQ/RQ 깊이 확인 및 변경
ethtool -g eth0 # 현재 링 버퍼 크기 확인
ethtool -G eth0 tx 2048 rx 4096 # SQ 2048, RQ 4096으로 변경
# WQE 에러 카운터 모니터링
ethtool -S eth0 | grep -E 'wqe|cqe_err|oversize'
# 주요 카운터:
# rx_wqe_err — RQ WQE 부족으로 드롭된 패킷
# tx_cqe_err — 전송 WQE 에러 (syndrome 확인 필요)
# tx_queue_stopped — SQ 가득 참으로 인한 큐 정지 횟수
# tx_queue_wake — 완료 후 큐 재개 횟수
# RDMA verbs QP의 WQE 상태 확인
rdma res show qp # QP 상태 (RTS/ERR/SQD 등)
rdma stat show link mlx5_0/1 # RDMA 포트별 트래픽 통계
WQE 보안 관점
WQE는 하드웨어에 직접 명령을 전달하므로, 보안 경계에서 중요한 역할을 합니다. RDMA의 커널 우회(kernel bypass) 특성상 사용자 공간에서 WQE를 직접 구성하므로, 하드웨어 수준의 보호 메커니즘이 필수적입니다.
| 보안 메커니즘 | 보호 대상 | 구현 위치 |
|---|---|---|
| lkey/rkey 검증 | 임의 메모리 접근 방지 | HCA가 WQE의 Data Segment를 처리할 때 MR 테이블에서 lkey를 조회하여 (VA, 길이, 권한) 검증 |
| QP isolation | 다른 프로세스의 QP 오용 방지 | Control Segment의 QPN이 WQE를 제출한 UAR의 소유자와 일치하는지 HCA가 검증 |
| UAR 격리 | 다른 QP의 doorbell 위조 방지 | 각 프로세스에 별도 UAR 페이지 할당. mmap으로 자신의 UAR만 접근 가능 |
| rkey 예측 불가 | 원격 MR 무단 접근 방지 | rkey 상위 비트는 MR 등록마다 랜덤 생성. 추측으로 유효한 rkey를 얻을 확률이 극히 낮음 |
| MR 범위 검사 | MR 등록 범위 밖 접근 방지 | HCA가 VA + byte_count가 MR의 [start, start+length) 내에 있는지 확인 |
| SQ/RQ 범위 | WQE 버퍼 외부 기록 방지 | Producer/Consumer Index는 sz_m1 마스크로 자동 범위 제한. 오버플로우 불가 |
Send with Invalidate로 사용 후 즉시 무효화),
(2) MR 크기를 필요한 범위로 제한하며,
(3) 신뢰할 수 없는 네트워크에서는 IPoIB over IPsec 또는 TLS 기반 연결 설정을 사용해야 합니다.
벤더별 WQE 구현 비교
WQE의 기본 개념은 InfiniBand 표준(IBTA Specification)에 정의되어 있지만,
하드웨어 수준의 WQE 포맷은 각 벤더가 독자적으로 설계합니다.
리눅스 커널의 drivers/infiniband/hw/ 디렉토리에서 각 드라이버의 구현을 확인할 수 있습니다.
| 항목 | NVIDIA mlx5 (ConnectX) | Intel/Cornelis irdma (E810/X722) | Broadcom bnxt_re |
|---|---|---|---|
| WQE 정렬 단위 | 64B (BB, Basic Block) | 32B (Quanta) | 16B (SGE 단위) |
| 최대 WQE 크기 | 255 DS = 4,080B | 256 Quanta = 8,192B | 벤더 의존적 |
| Doorbell 방식 | UAR MMIO + BlueFlame (WC) | Doorbell Register MMIO | Doorbell BAR MMIO |
| WQE 캐싱 | BlueFlame: HCA 내부 캐시에 WQE 직접 저장 | DMA fetch만 지원 | DMA fetch만 지원 |
| Multi-Packet WQE | ConnectX-5+: Enhanced MPWQE (opcode 0x29) | 미지원 | 미지원 |
| Strided RQ | ConnectX-4+: Multi-Packet RQ (WQE당 N stride) | 미지원 (대신 Ring Buffer) | 미지원 |
| 확장 Atomic | Masked CAS/FAA, 128b+ CAS | 표준 64b CAS/FAA만 | 표준 64b CAS/FAA만 |
| 리눅스 커널 드라이버 | drivers/infiniband/hw/mlx5/ |
drivers/infiniband/hw/irdma/ |
drivers/infiniband/hw/bnxt_re/ |
ibv_post_send() / ibv_post_recv() API는 벤더 독립적인
ibv_send_wr / ibv_recv_wr 구조체를 사용합니다.
각 벤더의 사용자 공간 드라이버(libmlx5, libirdma 등)가 이를
하드웨어별 WQE 포맷으로 변환합니다.
커널 드라이버의 경우 ib_core 모듈이 추상화 계층을 제공하며,
각 HW 드라이버가 .post_send, .post_recv 콜백을 구현합니다.
WQE 활용 패턴: 실무 RDMA 프로그래밍
WQE의 세그먼트 조합은 RDMA 프로그래밍 패턴에 따라 달라집니다. 주요 통신 패턴별 WQE 사용 방식을 정리합니다.
| 통신 패턴 | 송신 측 WQE | 수신 측 | 사용 사례 |
|---|---|---|---|
| 양방향 Send/Recv | SEND: Ctrl + Data[] | Recv WQE 소모, CQE 생성 | RPC 메시지 교환 (gRPC over RDMA), 제어 경로 |
| 단방향 RDMA Write | RDMA_WRITE: Ctrl + RDMA(raddr, rkey) + Data[] | CPU 개입 없음 (zero-copy) | 대용량 데이터 전송. 원격 로그 수집, 분산 파일시스템 |
| RDMA Write + Immediate | RDMA_WRITE_IMM: Ctrl(imm) + RDMA + Data[] | Recv WQE 소모, CQE에 imm값 포함 | 데이터 전송 + 알림 결합. NVMe-oF RDMA 커맨드 완료 통지 |
| RDMA Read | RDMA_READ: Ctrl + RDMA(raddr, rkey) + Data[] | CPU 개입 없음 | 원격 메모리 폴링. 분산 해시 테이블 조회 |
| Atomic CAS | ATOMIC_CS: Ctrl + RDMA + Atomic(swap, compare) + Data(결과 버퍼) | 원자적 비교-교환 수행 | 분산 잠금(distributed lock), FIFO 큐 head/tail 관리 |
| Atomic FAA | ATOMIC_FA: Ctrl + RDMA + Atomic(add_val) + Data(이전값 버퍼) | 원자적 덧셈 수행 | 분산 카운터, 시퀀스 넘버 할당, 원격 참조 카운팅 |
| Send with Invalidate | SEND_INVAL: Ctrl(inv_rkey) + Data[] | Recv WQE 소모 + 지정된 rkey 무효화 | Fast Registration 후 즉시 rkey 회수. iSER/NVMe-oF 보안 |
/* 실무 패턴 예시: RDMA Write + Immediate를 이용한 RPC 응답 */
/*
* 서버가 클라이언트의 응답 버퍼에 직접 RDMA Write하고,
* Immediate 값으로 응답 길이를 전달합니다.
* 클라이언트는 Receive WQE에서 CQE를 받아 응답 도착을 인지합니다.
*
* WQE 구성:
* Ctrl Segment: opcode = RDMA_WRITE_IMM, imm = response_length
* RDMA Segment: raddr = client_buf_addr, rkey = client_rkey
* Data Segment: lkey = server_mr_lkey, addr = server_response_buf
*
* 장점: 데이터 복사 + 완료 알림이 하나의 WQE로 원자적으로 수행
* 주의: 수신 측에 Receive WQE가 게시되어 있어야 함 (Immediate가 있으므로)
*/
/* NVMe-oF RDMA의 WQE 활용 */
/*
* NVMe 커맨드 (64B): Send WQE로 전송
* → 작은 크기이므로 Inline Data + BlueFlame 최적화 적용
*
* NVMe 데이터 (4KB~수MB): RDMA Read/Write로 전송
* → UMR WQE로 SGL을 동적 MR로 매핑 후 RDMA 수행
* → 완료 시 Send with Invalidate로 MR 즉시 해제
*
* 이 패턴은 단일 I/O에 3~4개의 WQE가 사용됩니다:
* 1. UMR WQE (MR 등록)
* 2. SEND WQE (NVMe 커맨드)
* 3. [타겟 측] RDMA Read/Write WQE (데이터 전송)
* 4. SEND_INVAL WQE (응답 + MR 무효화)
*/
WQE 관련 참고 자료
표준 및 공식 사양
- IBTA InfiniBand Architecture Specification — InfiniBand 표준 문서. WQE/CQE 포맷, QP 상태 머신, 에러 Syndrome 코드 등 정의 (Vol. 1, Ch. 10~11)
- NVIDIA Adapters Programmer's Reference Manual (PRM) — ConnectX HCA의 WQE/CQE 하드웨어 포맷 상세. mlx5_wqe_ctrl_seg, BlueFlame, MPWQE, UMR 등 벤더별 구현 사양
- RoCE Initiative — RoCE v1/v2 표준 사양 및 배포 가이드. Annex A17 (IB 스펙) 기반
리눅스 커널 소스 코드
- include/linux/mlx5/qp.h —
mlx5_wqe_ctrl_seg,mlx5_wqe_data_seg,mlx5_wqe_raddr_seg,mlx5_wqe_eth_seg,mlx5_wqe_atomic_seg구조체 정의 - drivers/net/ethernet/mellanox/mlx5/core/en_tx.c — Ethernet 전송 WQE 구성,
mlx5e_xmit(),mlx5e_notify_hw(), BlueFlame 경로 - drivers/net/ethernet/mellanox/mlx5/core/en_rx.c — Receive WQE 게시, Strided RQ, NAPI 폴링 경로
- drivers/net/ethernet/mellanox/mlx5/core/wq.h —
mlx5_wq_cyc순환 워크 큐,mlx5_wq_cyc_get_wqe()인덱싱 - drivers/infiniband/hw/mlx5/wr.c — IB Verbs WQE 구성, UMR WQE, Atomic WQE, 시그니처 계산
- drivers/infiniband/hw/mlx5/qp.c — QP 생성/수정, SQ/RQ 크기 계산, WQE 크기 결정 로직
- include/linux/mlx5/cqe.h —
mlx5_cqe64구조체, CQE syndrome, wqe_counter, ownership bit - drivers/infiniband/hw/mlx5/srq.c — SRQ WQE 관리, linked list, SRQ limit warning
- drivers/infiniband/core/verbs.c — IB core Verbs API,
ib_post_send(),ib_post_recv()추상화 계층 - drivers/nvme/host/rdma.c — NVMe-oF RDMA 호스트: UMR WQE 활용, Fast Registration, Send with Invalidate 패턴
- drivers/infiniband/hw/irdma/ — Intel/Cornelis irdma 드라이버: 벤더별 WQE 포맷 비교 참조
사용자 공간 라이브러리 (rdma-core)
- github.com/linux-rdma/rdma-core — libibverbs, librdmacm, libmlx5 소스. 사용자 공간에서의 WQE 구성 코드
- providers/mlx5/qp.c — libmlx5의
mlx5_post_send(): WR → WQE 변환, Inline Data, BlueFlame 경로 - providers/mlx5/mlx5dv.h — mlx5 Direct Verbs (DevX) API: WQE 직접 접근, CQE 압축 설정
- ibv_post_send(3) man page — Work Request(WR) API 문서: ibv_send_wr 구조체, opcode, SGE 리스트
- ibv_post_recv(3) man page — Receive WR API 문서: ibv_recv_wr, SGE 배열
기술 문서 및 블로그
- Linux Kernel Documentation: InfiniBand — 커널 공식 InfiniBand 문서 (RDMA Verbs, sysfs ABI, 모듈 설명)
- NVIDIA MLNX_OFED Documentation — MLNX_OFED 드라이버 가이드: mlx5 설정, 성능 튜닝, ethtool 카운터 해석
- Understanding mlx5 Linux Counters — ethtool -S 카운터 상세 설명: rx_wqe_err, tx_cqe_err 등 WQE 관련 카운터
- RDMA Core Programming Manual — rdma-core API 프로그래밍 가이드: QP, CQ, MR, SRQ, WR 활용법
- RDMAmojo — RDMA 프로그래밍 튜토리얼 블로그: ibv_post_send, ibv_poll_cq 코드 예제, 에러 디버깅 기법
- How to Receive a Million Packets (Cloudflare) — 고성능 패킷 수신: Ring Buffer, NAPI 폴링, 수신 WQE 관리 관점
학술 논문 및 기술 보고서
- Kalia, A. et al. — "Design Guidelines for High Performance RDMA Systems" (ATC 2016). WQE Doorbell batching, Unsignaled Completion, Inline Data 최적화 기법의 실증 분석
- Kalia, A. et al. — "Using RDMA Efficiently for Key-Value Services" (SIGCOMM 2014). RDMA Atomic WQE를 활용한 분산 해시 테이블의 lock-free 설계
- Tsai, S. et al. — "LITE Kernel RDMA Support for Datacenter Applications" (SOSP 2017). 커널 내 RDMA 추상화와 WQE 보안 격리 문제 분석
- Mitchell, C. et al. — "Using One-Sided RDMA Reads to Build a Fast, CPU-Efficient Key-Value Store" (ATC 2013). RDMA Read WQE 기반 원격 메모리 접근 패턴
- Dragojevic, A. et al. — "FaRM: Fast Remote Memory" (NSDI 2014). RDMA Write + Atomic WQE를 활용한 분산 트랜잭션 프로토콜
- Lu, Y. et al. — "Multi-Path Transport for RDMA in Datacenters" (NSDI 2018). 다중 QP/SQ를 활용한 WQE 분산과 경로 다양성
도구 및 디버깅
- github.com/linux-rdma/perftest — ib_write_bw, ib_send_lat 등: WQE 포스팅 성능 벤치마크 도구
- rdma(8) man page — iproute2 rdma 도구:
rdma res show qp로 QP 상태, WQE 카운터 확인 - Mellanox VMA (Voltaire Messaging Accelerator) — 소켓 API 투명 가속: 내부적으로 WQE를 직접 구성하여 커널 우회
ethtool -S <dev> | grep wqe— WQE 관련 하드웨어 카운터 모니터링rdma res show qp— QP 상태 (RTS/ERR/SQD), SQ/RQ 깊이, CQ 연결 정보 확인dmesg | grep mlx5_core— CQE syndrome, WQE 에러 로그 확인
리눅스 커널 InfiniBand 서브시스템
drivers/infiniband/ 소스 구조
ib_core 모듈
ib_core는 InfiniBand 서브시스템의 핵심 프레임워크입니다. HCA 드라이버가 ib_register_device()로 디바이스를 등록하면,
ib_core가 sysfs 항목 생성, 이벤트 알림, Verbs API 디스패치(Dispatch) 등을 처리합니다.
모든 RDMA 디바이스(IB, RoCE, iWARP)가 동일한 Verbs 추상화를 통해 접근됩니다.
ib_uverbs / ib_umad
- ib_uverbs:
/dev/infiniband/uverbsN캐릭터 디바이스를 통해 사용자 공간에서 Verbs API를 사용할 수 있게 합니다. libibverbs가 이 인터페이스를 사용합니다. - ib_umad:
/dev/infiniband/umadN을 통해 사용자 공간에서 MAD(Management Datagram)를 송수신할 수 있게 합니다. opensm이 이 인터페이스를 사용합니다.
대표 모듈과 Kconfig
배포판 커널에서는 대부분 RDMA 기능이 모듈 형태로 제공됩니다. 현재 리눅스 메인라인 Kconfig와 rdma-core README를 기준으로, 실무에서 자주 만나는 항목만 추려 보면 다음과 같습니다.
| 역할 | 대표 모듈 | Kconfig / 소스 기준 | 비고 |
|---|---|---|---|
| RDMA 미들레이어 | ib_core |
menuconfig INFINIBAND |
Verbs 공통 프레임워크와 장치 등록 기반 |
| 사용자 공간 Verbs | ib_uverbs |
INFINIBAND_USER_ACCESS, INFINIBAND_USER_MEM 계열 |
/dev/infiniband/uverbsN 제공 |
| 주소 해석 계층 | ib_addr 계열 |
INFINIBAND_ADDR_TRANS |
GID, netdev 연동, 경로 해석 보조 |
| IPoIB | ib_ipoib |
INFINIBAND_IPOIB |
ib0 같은 netdev 제공 |
| Mellanox / NVIDIA HCA | mlx5_ib |
MLX5_INFINIBAND |
ConnectX 계열의 핵심 RDMA provider |
| Intel Omni-Path 계열 | hfi1 |
INFINIBAND_HFI1 |
대규모 HPC 설치에서 자주 보임 |
| 소프트웨어 RoCE | rdma_rxe |
RDMA_RXE |
Ethernet 기반 개발/랩 환경에 유용 |
| 소프트웨어 iWARP | siw |
RDMA_SIW |
TCP/IP 기반 RDMA 기능 검증용 |
주요 커널 구조체(Struct)
/* include/rdma/ib_verbs.h - 핵심 구조체 (간략화) */
struct ib_device {
const char *name; /* 디바이스 이름 (mlx5_0 등) */
struct ib_device_ops ops; /* Verbs 콜백 함수 테이블 */
struct device dev; /* sysfs 디바이스 */
u32 phys_port_cnt; /* 물리 포트 수 */
struct ib_device_attr attrs; /* 디바이스 능력 (속성) */
struct list_head event_handler_list;
enum rdma_transport_type (*get_transport)(struct ib_device *, u32);
};
struct ib_qp {
struct ib_device *device;
struct ib_pd *pd; /* Protection Domain */
struct ib_cq *send_cq; /* 송신 Completion Queue */
struct ib_cq *recv_cq; /* 수신 Completion Queue */
struct ib_srq *srq; /* Shared Receive Queue (선택) */
u32 qp_num; /* QP Number */
enum ib_qp_type qp_type; /* RC, UC, UD, XRC 등 */
void (*event_handler)(struct ib_event *, void *);
};
struct ib_cq {
struct ib_device *device;
int cqe; /* CQ 엔트리 수 */
struct ib_uobject *uobject;
ib_comp_handler comp_handler; /* 완료 콜백 */
void *cq_context;
int comp_vector; /* IRQ 벡터 */
enum ib_poll_context poll_ctx; /* 폴링 컨텍스트 */
};
struct ib_mr {
struct ib_device *device;
struct ib_pd *pd;
u32 lkey; /* Local Key */
u32 rkey; /* Remote Key */
u64 iova; /* I/O Virtual Address */
u64 length; /* 등록된 메모리 길이 */
enum ib_mr_type type; /* MEM_REG, DMA, USER 등 */
};
커널 Verbs API
| 카테고리 | API 함수 | 설명 |
|---|---|---|
| 디바이스 | ib_register_device() | HCA 드라이버가 디바이스 등록 |
ib_query_device() | 디바이스 속성(포트 수, 최대 QP 수 등) 조회 | |
| PD | ib_alloc_pd() | Protection Domain 생성 |
ib_dealloc_pd() | PD 해제 | |
| QP | ib_create_qp() | QP 생성 (타입, CQ, SRQ 지정) |
ib_modify_qp() | QP 상태 전이 (RESET→INIT→RTR→RTS) | |
ib_destroy_qp() | QP 파괴 | |
| CQ | ib_alloc_cq() / ib_create_cq() | CQ 생성 |
ib_poll_cq() | CQ에서 완료 이벤트 폴링(Polling) | |
| MR | ib_reg_user_mr() | 사용자 공간 메모리 등록 |
ib_dereg_mr() | MR 해제 | |
| 데이터 전송 | ib_post_send() | SQ에 Work Request 게시 |
ib_post_recv() | RQ에 Receive WR 게시 |
커널 내부에서 QP를 생성하고 데이터를 전송하는 기본 흐름:
/* 커널 Verbs API 사용 예제 (간략화) */
struct ib_pd *pd;
struct ib_cq *cq;
struct ib_qp *qp;
/* 1. Protection Domain 생성 */
pd = ib_alloc_pd(ib_dev, 0);
/* 2. Completion Queue 생성 */
cq = ib_alloc_cq(ib_dev, NULL, 256, 0, IB_POLL_SOFTIRQ);
/* 3. Queue Pair 생성 */
struct ib_qp_init_attr qp_attr = {
.event_handler = my_qp_event,
.send_cq = cq,
.recv_cq = cq,
.cap = {
.max_send_wr = 128,
.max_recv_wr = 128,
.max_send_sge = 1,
.max_recv_sge = 1,
},
.qp_type = IB_QPT_RC,
};
qp = ib_create_qp(pd, &qp_attr);
/* 4. QP 상태 전이: RESET → INIT → RTR → RTS */
struct ib_qp_attr attr;
memset(&attr, 0, sizeof(attr));
attr.qp_state = IB_QPS_INIT;
attr.pkey_index = 0;
attr.port_num = 1;
attr.qp_access_flags = IB_ACCESS_REMOTE_WRITE | IB_ACCESS_REMOTE_READ;
ib_modify_qp(qp, &attr, IB_QP_STATE | IB_QP_PKEY_INDEX |
IB_QP_PORT | IB_QP_ACCESS_FLAGS);
/* RTR, RTS 전이도 유사하게 진행 ... */
/* 5. Work Request 게시 (RDMA Write) */
struct ib_sge sge = {
.addr = dma_addr,
.length = len,
.lkey = mr->lkey,
};
struct ib_send_wr wr = {
.opcode = IB_WR_RDMA_WRITE,
.send_flags = IB_SEND_SIGNALED,
.sg_list = &sge,
.num_sge = 1,
.wr.rdma = {
.remote_addr = remote_addr,
.rkey = remote_rkey,
},
};
const struct ib_send_wr *bad_wr;
ib_post_send(qp, &wr, &bad_wr);
/* 6. 완료 폴링 */
struct ib_wc wc;
while (ib_poll_cq(cq, 1, &wc) == 0)
/* busy wait */;
if (wc.status != IB_WC_SUCCESS)
pr_err("WC error: %d\\n", wc.status);
사용자 공간 라이브러리
libibverbs (ibv_* API)
libibverbs는 RDMA Verbs API의 사용자 공간 구현체입니다.
/dev/infiniband/uverbsN을 통해 커널의 ib_uverbs와 통신하며,
데이터 경로(post_send, poll_cq 등)는 메모리 매핑(Mapping)된 Doorbell/CQ를 통해 커널 바이패스로 동작합니다.
주요 API 함수:
ibv_get_device_list(): RDMA 디바이스 목록 조회ibv_open_device(): 디바이스 컨텍스트 열기ibv_alloc_pd(): Protection Domain 생성ibv_reg_mr(): Memory Region 등록ibv_create_cq()/ibv_create_cq_ex(): CQ 생성ibv_create_qp(): QP 생성ibv_modify_qp(): QP 상태 전이ibv_post_send()/ibv_post_recv(): WR 게시 (커널 바이패스)ibv_poll_cq(): CQ 폴링 (커널 바이패스)
librdmacm (rdma_* API)
librdmacm은 RDMA Connection Manager 라이브러리로, TCP 소켓과 유사한 연결 설정/해제 인터페이스를 제공합니다.
QP 상태 전이를 자동으로 처리하므로 개발이 훨씬 간편합니다.
rdma_create_id(): RDMA CM ID 생성 (소켓에 해당)rdma_resolve_addr(): IP → GID 주소 해석rdma_resolve_route(): 라우팅 경로 해석rdma_connect()/rdma_accept(): 연결 설정rdma_disconnect(): 연결 해제
rdma-core 패키지
rdma-core는 libibverbs, librdmacm, 벤더 플러그인(libmlx5, libirdma 등), 관리 도구를 통합한 오픈소스 패키지입니다.
대부분의 Linux 배포판에서 rdma-core 패키지로 설치할 수 있습니다.
# rdma-core 설치 (Ubuntu/Debian)
sudo apt install rdma-core libibverbs-dev librdmacm-dev
# rdma-core 설치 (RHEL/CentOS)
sudo dnf install rdma-core libibverbs-devel librdmacm-devel
/dev/infiniband/uverbsX— libibverbs가 PD/CQ/QP/MR 등 Verbs 객체를 생성하는 기본 경로/dev/infiniband/rdma_cm— librdmacm이 주소 해석, 경로 해석, 연결 이벤트를 처리하는 경로/dev/infiniband/umadX— opensm, saquery, 진단 도구가 MAD를 직접 보내는 경로/dev/infiniband/issmX— 특정 포트를 SM 후보로 다룰 때 IsSM capability bit를 제어하는 경로
Provider 확장 API: mlx5dv, DevX, CQE 최적화
표준 Verbs는 공통 기능을 제공하지만, 최고 성능 튜닝은 provider 확장에 들어 있는 경우가 많습니다.
특히 mlx5 계열은 mlx5dv_*와 DevX를 통해 표준 계층이 아직 추상화하지 못한 하드웨어 기능을 직접 노출합니다.
| 확장 경로 | 대표 기능 | 언제 쓰는가 | 주의점 |
|---|---|---|---|
mlx5dv_create_qp() |
DCT / DCI, 고급 QP 속성 | 대규모 희소 통신, MPI/AI collective 최적화 | mlx5 전용, 타 provider 이식성 낮음 |
mlx5dv_create_cq() |
CQE compression, mini-CQE 포맷 | 대량 완료 이벤트에서 PCIe/메모리 대역폭 절감 | 디코딩 경로가 복잡해지고 관측성도 낮아질 수 있음 |
| DevX | 펌웨어(Firmware) 객체 직접 생성/수정 | 표준 Verbs보다 더 낮은 수준의 HW 기능 제어 | 펌웨어 의존성이 높고 코드 유지보수 비용이 큼 |
상위 통신 스택: UCX, Open MPI, libfabric
실제 HPC/AI 애플리케이션은 보통 ibv_post_send()를 직접 호출하지 않습니다. 대부분은
MPI, PGAS, RPC, collective 라이브러리가 UCX 또는 libfabric(OFI) 같은 중간 계층을 통해
Verbs와 provider 확장을 선택합니다.
| 계층 | 역할 | InfiniBand 문맥에서 중요한 점 |
|---|---|---|
| UCX | 전송 선택, 프로토콜 분할, multi-rail, GPU 메모리 연동 | UCX_TLS로 rc, dc, cuda, rocm 조합을 제어하며, UCX_LOG_LEVEL=info로 실제 transport 선택을 볼 수 있습니다. |
| Open MPI | MPI point-to-point, collective, RMA | Open MPI v6.1.x 문서는 InfiniBand/RoCE를 UCX PML 경로로 지원하고, 과거 openib BTL은 더 이상 포함하지 않는다고 명시합니다. |
| libfabric / OFI | provider 기반 통신 추상화 | verbs provider는 libibverbs, librdmacm 위에 올라가며 FI_EP_RDM은 보통 verbs;ofi_rxm 형태로 layering 됩니다. |
# UCX가 실제로 어떤 transport를 선택했는지 확인
ucx_info -d
UCX_LOG_LEVEL=info mpirun -x UCX_TLS=rc -x UCX_NET_DEVICES=mlx5_0:1 ./app
# Adaptive Routing이 켜진 SL을 UCX가 고르도록 시도
mpirun -x UCX_IB_AR_ENABLE=yes -x UCX_IB_SL=1 ./app
# Open MPI에서 UCX 경로를 명시
mpirun --mca pml ucx -x UCX_NET_DEVICES=mlx5_0:1 ./app
개발용 소프트웨어 RDMA 랩
네이티브 InfiniBand 장비가 없어도 RDMA 프로그래밍 모델 자체는 연습할 수 있습니다.
rdma-core README는 rdma_rxe와 siw를 사용해 기존 NIC 위에 소프트웨어 RDMA 장치를 추가하는 방식을 안내합니다.
# Soft-RoCE (RXE) 예시
modprobe rdma_rxe
rdma link add rxe0 type rxe netdev eth0
# 소프트웨어 iWARP (SIW) 예시
modprobe siw
rdma link add siw0 type siw netdev eth0
# 장치 확인
rdma link
ibv_devices
rxe와 siw는 Verbs 실험과 기능 검증에는 좋지만, 네이티브 InfiniBand 패브릭의 지연·혼잡·SM/SA·credit-based flow control 특성을 재현하지는 못합니다.
이들은 API/상태머신 학습용으로 이해하는 편이 정확합니다.
코드 예제 — RDMA RC Send/Recv
/* libibverbs RC Send/Recv 전체 흐름 (간략화) */
#include <infiniband/verbs.h>
int main(void)
{
struct ibv_device **dev_list;
struct ibv_context *ctx;
struct ibv_pd *pd;
struct ibv_mr *mr;
struct ibv_cq *cq;
struct ibv_qp *qp;
char buf[4096];
/* 1. 디바이스 열기 */
dev_list = ibv_get_device_list(NULL);
ctx = ibv_open_device(dev_list[0]);
/* 2. PD, MR, CQ 생성 */
pd = ibv_alloc_pd(ctx);
mr = ibv_reg_mr(pd, buf, sizeof(buf),
IBV_ACCESS_LOCAL_WRITE |
IBV_ACCESS_REMOTE_WRITE |
IBV_ACCESS_REMOTE_READ);
cq = ibv_create_cq(ctx, 16, NULL, NULL, 0);
/* 3. QP 생성 */
struct ibv_qp_init_attr qp_init = {
.send_cq = cq,
.recv_cq = cq,
.cap = { .max_send_wr = 16, .max_recv_wr = 16,
.max_send_sge = 1, .max_recv_sge = 1 },
.qp_type = IBV_QPT_RC,
};
qp = ibv_create_qp(pd, &qp_init);
/* 4. QP 상태 전이: RESET → INIT → RTR → RTS */
struct ibv_qp_attr attr;
memset(&attr, 0, sizeof(attr));
/* INIT */
attr.qp_state = IBV_QPS_INIT;
attr.pkey_index = 0;
attr.port_num = 1;
attr.qp_access_flags = IBV_ACCESS_REMOTE_WRITE | IBV_ACCESS_REMOTE_READ;
ibv_modify_qp(qp, &attr,
IBV_QP_STATE | IBV_QP_PKEY_INDEX | IBV_QP_PORT | IBV_QP_ACCESS_FLAGS);
/* RTR — 원격 QPN, LID, GID, PSN 필요 (out-of-band 교환) */
attr.qp_state = IBV_QPS_RTR;
attr.path_mtu = IBV_MTU_4096;
attr.dest_qp_num = remote_qpn; /* 상대방 QPN */
attr.rq_psn = 0;
attr.max_dest_rd_atomic = 1;
attr.min_rnr_timer = 12;
attr.ah_attr.dlid = remote_lid;
attr.ah_attr.port_num = 1;
ibv_modify_qp(qp, &attr,
IBV_QP_STATE | IBV_QP_AV | IBV_QP_PATH_MTU |
IBV_QP_DEST_QPN | IBV_QP_RQ_PSN |
IBV_QP_MAX_DEST_RD_ATOMIC | IBV_QP_MIN_RNR_TIMER);
/* RTS */
attr.qp_state = IBV_QPS_RTS;
attr.sq_psn = 0;
attr.timeout = 14;
attr.retry_cnt = 7;
attr.rnr_retry = 7;
attr.max_rd_atomic = 1;
ibv_modify_qp(qp, &attr,
IBV_QP_STATE | IBV_QP_SQ_PSN | IBV_QP_TIMEOUT |
IBV_QP_RETRY_CNT | IBV_QP_RNR_RETRY | IBV_QP_MAX_QP_RD_ATOMIC);
/* 5. Receive WR 게시 (수신 측) */
struct ibv_sge recv_sge = { .addr = (uintptr_t)buf,
.length = sizeof(buf), .lkey = mr->lkey };
struct ibv_recv_wr recv_wr = { .sg_list = &recv_sge, .num_sge = 1 };
struct ibv_recv_wr *bad_recv;
ibv_post_recv(qp, &recv_wr, &bad_recv);
/* 6. Send WR 게시 (송신 측) */
memcpy(buf, "Hello RDMA", 10);
struct ibv_sge send_sge = { .addr = (uintptr_t)buf,
.length = 10, .lkey = mr->lkey };
struct ibv_send_wr send_wr = {
.opcode = IBV_WR_SEND,
.send_flags = IBV_SEND_SIGNALED,
.sg_list = &send_sge,
.num_sge = 1,
};
struct ibv_send_wr *bad_send;
ibv_post_send(qp, &send_wr, &bad_send);
/* 7. 완료 폴링 */
struct ibv_wc wc;
while (ibv_poll_cq(cq, 1, &wc) == 0)
; /* busy-wait */
/* 8. 정리 */
ibv_destroy_qp(qp);
ibv_destroy_cq(cq);
ibv_dereg_mr(mr);
ibv_dealloc_pd(pd);
ibv_close_device(ctx);
ibv_free_device_list(dev_list);
return 0;
}
코드 예제 — RDMA Write with librdmacm
/* librdmacm RDMA Write 예제 (클라이언트 측, 간략화) */
#include <rdma/rdma_cma.h>
#include <infiniband/verbs.h>
int main(void)
{
struct rdma_cm_id *id;
struct rdma_event_channel *ec;
struct rdma_conn_param conn_param = {};
struct ibv_pd *pd;
struct ibv_mr *mr;
struct ibv_cq *cq;
char buf[4096];
/* 1. RDMA CM ID 생성 및 주소 해석 */
ec = rdma_create_event_channel();
rdma_create_id(ec, &id, NULL, RDMA_PS_TCP);
struct sockaddr_in addr = {
.sin_family = AF_INET,
.sin_port = htons(20000),
};
inet_pton(AF_INET, "192.168.1.100", &addr.sin_addr);
rdma_resolve_addr(id, NULL, (struct sockaddr *)&addr, 2000);
/* ... rdma_resolve_route() ... */
/* 2. 리소스 생성 (rdmacm이 QP 상태 전이 자동 처리) */
pd = ibv_alloc_pd(id->verbs);
cq = ibv_create_cq(id->verbs, 16, NULL, NULL, 0);
mr = ibv_reg_mr(pd, buf, sizeof(buf),
IBV_ACCESS_LOCAL_WRITE | IBV_ACCESS_REMOTE_WRITE);
struct ibv_qp_init_attr qp_attr = {
.send_cq = cq, .recv_cq = cq,
.cap = { .max_send_wr = 16, .max_recv_wr = 16,
.max_send_sge = 1, .max_recv_sge = 1 },
.qp_type = IBV_QPT_RC,
};
rdma_create_qp(id, pd, &qp_attr);
/* 3. 연결 (QP 자동으로 RTS 전이) */
rdma_connect(id, &conn_param);
/* 4. RDMA Write (원격 rkey/addr은 out-of-band로 수신) */
memcpy(buf, "RDMA Write Data", 15);
struct ibv_sge sge = {
.addr = (uintptr_t)buf, .length = 15, .lkey = mr->lkey
};
struct ibv_send_wr wr = {
.opcode = IBV_WR_RDMA_WRITE,
.send_flags = IBV_SEND_SIGNALED,
.sg_list = &sge,
.num_sge = 1,
.wr.rdma = { .remote_addr = remote_addr, .rkey = remote_rkey },
};
struct ibv_send_wr *bad;
ibv_post_send(id->qp, &wr, &bad);
/* 5. 완료 대기 및 정리 */
struct ibv_wc wc;
while (ibv_poll_cq(cq, 1, &wc) == 0);
rdma_disconnect(id);
rdma_destroy_qp(id);
ibv_dereg_mr(mr);
ibv_destroy_cq(cq);
ibv_dealloc_pd(pd);
rdma_destroy_id(id);
rdma_destroy_event_channel(ec);
return 0;
}
sysfs / 디바이스 노드 / ABI
성능 분석이나 장애 대응을 할 때는 Verbs 호출만 보지 말고, 커널이 노출하는 관측 경로를 함께 붙잡아야 합니다.
Linux RDMA 스택은 사용자 API뿐 아니라 /sys/class/infiniband*, /dev/infiniband/*를 통해
상태, 포트 속성, 카운터, ABI 매핑, 관리 디바이스 정보를 꽤 풍부하게 노출합니다.
| 경로 | 의미 | 언제 보는가 |
|---|---|---|
/sys/class/infiniband/<dev>/node_type/sys/class/infiniband/<dev>/node_guid/sys/class/infiniband/<dev>/fw_ver |
장치 종류, GUID, 펌웨어 버전 | 벤더 이슈 분리, 펌웨어 호환성 점검 |
/sys/class/infiniband/<dev>/ports/<port>/{state,phys_state,link_layer,lid,rate,sm_lid,sm_sl} |
포트 활성 상태, 링크 계층, LID, 속도, 연결된 SM 정보 | 포트가 왜 INIT/ACTIVE에 머무는지, InfiniBand와 RoCE를 구분할 때 |
/sys/class/infiniband/<dev>/ports/<port>/counters/* |
링크/패킷/에러 카운터 | 재시도, 링크 에러, 혼잡 징후 확인 |
/sys/class/infiniband/<dev>/ports/<port>/gid_attrs/types/*/sys/class/infiniband/<dev>/ports/<port>/gid_attrs/ndevs/* |
GID 타입과 연동 netdev 매핑 | RoCE GID 해석, IPoIB / Ethernet 연동 확인 |
/sys/class/infiniband_mad/umadN/{ibdev,port} |
umad 문자 디바이스가 어느 HCA/포트와 연결되는지 표시 | opensm, saquery, ibdiagnet가 어떤 포트를 쓰는지 추적 |
/sys/class/infiniband_verbs/uverbsN/{ibdev,abi_version} |
uverbs 장치와 커널 ABI 버전 매핑 | 사용자 공간/커널 ABI mismatch 의심 시 |
# 기본 장치/포트 상태 관찰
cat /sys/class/infiniband/mlx5_0/node_guid
cat /sys/class/infiniband/mlx5_0/fw_ver
cat /sys/class/infiniband/mlx5_0/ports/1/link_layer
cat /sys/class/infiniband/mlx5_0/ports/1/state
cat /sys/class/infiniband/mlx5_0/ports/1/rate
# uverbs / umad가 어느 장치에 매핑되는지 확인
cat /sys/class/infiniband_verbs/uverbs0/ibdev
cat /sys/class/infiniband_mad/umad0/ibdev
cat /sys/class/infiniband_mad/umad0/port
IPoIB (IP over InfiniBand)
IPoIB는 InfiniBand 패브릭 위에서 기존 IP/TCP/UDP 프로토콜을 투명하게 사용할 수 있게 합니다.
커널의 drivers/infiniband/ulp/ipoib/에 구현되어 있으며, ib0, ib1 등의 네트워크 인터페이스로 나타납니다.
Connected Mode vs Datagram Mode
- Datagram Mode (기본): UD QP를 사용합니다. MTU는 IB MTU (보통 2044 바이트, IB 헤더 포함)로 제한됩니다. 브로드캐스트/멀티캐스트를 지원합니다.
- Connected Mode: RC QP를 사용하여 더 큰 MTU(최대 65520 바이트)를 지원합니다. 대용량 IP 트래픽에 적합하지만, 각 peer마다 RC QP가 필요하므로 리소스 소비가 큽니다.
MTU 설정 및 커널 구성
# IPoIB Connected Mode 활성화
echo connected > /sys/class/net/ib0/mode
# Connected Mode MTU 설정 (최대 65520)
ip link set ib0 mtu 65520
# IPoIB 인터페이스에 IP 주소 할당
ip addr add 10.0.0.1/24 dev ib0
ip link set ib0 up
# 커널 구성 옵션
CONFIG_INFINIBAND_IPOIB=m
CONFIG_INFINIBAND_IPOIB_CM=y # Connected Mode 지원
CONFIG_INFINIBAND_IPOIB_DEBUG=y # 디버그 메시지 (선택)
cat /sys/class/net/ib0/mode로 직접 확인하는 편이 가장 확실합니다.
파티션, child interface, Enhanced IPoIB
IPoIB 인터페이스는 기본적으로 포트의 P_Key index 0을 사용합니다. 같은 물리 포트라도 다른 파티션에 참여해야 한다면 P_Key별 child interface를 만들어 별도의 IP 인터페이스처럼 운용할 수 있습니다.
# P_Key 0x8001 파티션용 child interface 생성
echo 0x8001 > /sys/class/net/ib0/create_child
# 생성된 인터페이스 확인 및 활성화
ip link show ib0.8001
ip link set ib0.8001 up
cat /sys/class/net/ib0.8001/pkey
# child interface 삭제
echo 0x8001 > /sys/class/net/ib0/delete_child
상위 프로토콜 (ULP)
| ULP | 프로토콜 계층 | 용도 | 커널 모듈(Kernel Module) | 특징 |
|---|---|---|---|---|
| SRP | SCSI over RDMA | 블록 스토리지 | ib_srp / ib_srpt |
SCSI 명령 + 데이터를 RDMA로 전송, iSCSI 대비 저지연 |
| iSER | iSCSI Extensions for RDMA | 블록 스토리지 | ib_iser / ib_isert |
기존 iSCSI 프로토콜을 RDMA로 가속, iSCSI 호환 |
| NVMe-oF/RDMA | NVMe over Fabrics | NVMe 스토리지 | nvme-rdma / nvmet-rdma |
NVMe 큐 모델을 RDMA에 직접 매핑, 초저지연 스토리지 |
| NFS/RDMA | NFS over RDMA (RPCRDMA) | 파일 시스템 | xprtrdma / svcrdma |
RPC 데이터를 RDMA Write/Read로 전송 |
NVMe-oF/RDMA 설정 예제
### NVMe-oF/RDMA Target 설정 ###
# 커널 모듈 로드
modprobe nvmet
modprobe nvmet-rdma
# NVMe-oF 서브시스템 생성
mkdir -p /sys/kernel/config/nvmet/subsystems/nqn.2024-01.io.example:nvme-rdma
cd /sys/kernel/config/nvmet/subsystems/nqn.2024-01.io.example:nvme-rdma
echo 1 > attr_allow_any_host
# 네임스페이스에 NVMe 디바이스 연결
mkdir namespaces/1
echo /dev/nvme0n1 > namespaces/1/device_path
echo 1 > namespaces/1/enable
# RDMA 포트 생성 및 서브시스템 바인드
mkdir -p /sys/kernel/config/nvmet/ports/1
echo rdma > /sys/kernel/config/nvmet/ports/1/addr_trtype
echo 10.0.0.1 > /sys/kernel/config/nvmet/ports/1/addr_traddr
echo 4420 > /sys/kernel/config/nvmet/ports/1/addr_trsvcid
echo ipv4 > /sys/kernel/config/nvmet/ports/1/addr_adrfam
ln -s /sys/kernel/config/nvmet/subsystems/nqn.2024-01.io.example:nvme-rdma \
/sys/kernel/config/nvmet/ports/1/subsystems/
### NVMe-oF/RDMA Initiator (클라이언트) 설정 ###
# 커널 모듈 로드
modprobe nvme-rdma
# Target 디스커버리
nvme discover -t rdma -a 10.0.0.1 -s 4420
# Target 연결
nvme connect -t rdma -n nqn.2024-01.io.example:nvme-rdma \
-a 10.0.0.1 -s 4420
# 연결된 NVMe 디바이스 확인
nvme list
lsblk
관리 및 진단 도구
opensm (Subnet Manager)
opensm은 Linux에서 가장 많이 사용되는 오픈소스 Subnet Manager입니다.
서브넷 토폴로지를 탐색하고 LID를 할당하며, 라우팅 테이블(Routing Table)을 계산하여 스위치에 배포합니다.
실제로는 SA 기능도 함께 제공하므로, Path Record 질의와 파티션 정책 배포까지 운영의 중심에 놓입니다.
# opensm 시작 (기본 설정)
opensm
# 포그라운드 + 디버그 레벨 지정
opensm -F -D 0x02
# 특정 포트 GUID로 SM 바인딩
opensm -g 0x0002c90300000001
# 라우팅 알고리즘 지정 (minhop, updn, ftree, dor 등)
opensm --routing_engine ftree
LinkUp인데도 State: Init에 머무르면, 케이블 문제보다 먼저
SM 부재, SM 충돌, 잘못된 GUID 바인딩, IsSM capability bit 충돌을 의심하는 편이 빠릅니다.
진단 도구
# HCA 상태 확인
ibstat
## 출력 예시:
## CA 'mlx5_0'
## CA type: MT4123
## Number of ports: 1
## Port 1:
## State: Active
## Physical state: LinkUp
## Rate: 100 (EDR)
## Base lid: 1
## SM lid: 1
# 디바이스 상세 정보
ibv_devinfo
# 디바이스 속성 (최대 QP 수, MR 크기 등)
ibv_devinfo -v
# SM / SA 상태
sminfo
saquery -s
# 파티션 / 멀티캐스트 / 경로 조회
saquery -p
saquery -m
# 서브넷 노드 목록
ibnetdiscover
# 링크 상태와 포트 정보 요약
iblinkinfo
# 스위치 라우팅 및 경로 추적
ibroute <switch_lid>
# 특정 노드 경로 추적
ibtracert <src_lid> <dst_lid>
# 포트 카운터 확인
perfquery
# fabric 전반 오류 요약
ibqueryerrors
# 포트 에러 카운터 초기화
perfquery -x -r <lid> <port>
# fabric 감사: 토폴로지/라우팅/카운터/케이블 검증
ibdiagnet
# 핑 (GRH 없이 LID 기반)
ibping -S # 서버 모드
ibping <lid> # 클라이언트
ibdiagnet는 단순 카운터 조회를 넘어, 토폴로지 검증, 라우팅 검증, 케이블/BER 확인, 혼잡 제어 설정 점검까지 수행하는
fabric audit 도구입니다. 대규모 클러스터에서는 ibnetdiscover보다 먼저 실행되는 경우도 많습니다.
성능 도구
# RDMA Write 대역폭 측정
ib_write_bw -d mlx5_0 # 서버
ib_write_bw -d mlx5_0 <server_ip> # 클라이언트
# RDMA Write 지연시간 측정
ib_write_lat -d mlx5_0 # 서버
ib_write_lat -d mlx5_0 <server_ip> # 클라이언트
# Send/Recv 대역폭/지연
ib_send_bw, ib_send_lat
# RDMA Read 대역폭/지연
ib_read_bw, ib_read_lat
# Atomic 연산 지연
ib_atomic_lat -d mlx5_0 <server_ip>
rdma (iproute2 rdma tool)
# RDMA 디바이스 목록
rdma dev
# RDMA 링크 상태
rdma link
# RDMA 통계 (상세)
rdma statistic show
# RDMA 리소스 (QP, CQ, MR 등) 목록
rdma resource show
# 특정 디바이스의 QP 목록
rdma resource show qp -d mlx5_0
# 포트별 GID / 링크 계층 / netdev 매핑 확인
rdma link show mlx5_0/1
# netns에서 RDMA 디바이스 사용 설정
rdma system set netns shared
성능 최적화
Adaptive Routing / SR-IOV
- Adaptive Routing (AR): HDR 이상의 IB 스위치가 실시간(Real-time) 네트워크 혼잡도를 감지하여 패킷 경로를 동적으로 변경합니다. 특히 All-to-All 통신 패턴(AI/ML 학습)에서 효과적입니다.
- 다중 레일(Multi-Rail): 노드당 2개 이상 HCA/포트를 두고 MPI/스토리지 트래픽을 레일별로 분산하면 단일 링크 병목과 장애 범위를 줄일 수 있습니다.
- SL/VL 기반 QoS: 대역폭만 늘리는 것보다, 지연 민감 제어 트래픽과 대용량 데이터 트래픽을 서로 다른 SL/VL 정책으로 분리하는 것이 더 큰 효과를 낼 때가 많습니다.
- SR-IOV: 하나의 물리 HCA를 여러 Virtual Function(VF)으로 분할하여 각 VM/컨테이너(Container)에 독립적인 RDMA 디바이스를 제공합니다. PCI/PCIe 서브시스템과 가상화 (KVM) 페이지도 참고하세요.
메모리 핀닝/등록 최적화
- 대용량 페이지(Huge Pages): 2MB/1GB 페이지를 사용하면 MR 등록 시 페이지 테이블(Page Table) 워크가 줄어들고, TLB 미스도 감소합니다.
- ODP (On-Demand Paging): 메모리를 미리 핀하지 않고 HCA 페이지 폴트 시 동적으로 매핑합니다. 등록 시간은 단축되지만 첫 접근 시 오버헤드가 있습니다.
- MR 풀링: 자주 사용하는 MR을 미리 등록하고 재사용합니다.
ib_mr_pool커널 API 또는 사용자 공간에서 직접 구현합니다. - NUMA 배치: HCA와 같은 NUMA 노드의 메모리를 사용하면 DMA 전송 성능이 향상됩니다. 메모리 및 NUMA 페이지를 참고하세요.
Completion Channel vs Polling
- Polling (ibv_poll_cq): CQ를 busy-wait으로 폴링합니다. 최저 지연이 필요할 때 사용하지만, CPU 코어를 100% 점유합니다.
- Completion Channel:
ibv_get_cq_event()로 이벤트를 대기합니다. CPU를 절약하지만 이벤트 전달 지연이 추가됩니다. - 하이브리드: 평소에는 polling으로 처리하다가 유휴 시 completion channel로 전환하는 적응형 방식이 실무에서 많이 사용됩니다.
QP / CQ 튜닝
- 깊이 설계: QP depth와 CQE 수가 너무 작으면
RNR, CQ overrun, 재시도 증가로 이어집니다. 반대로 과도하면 캐시(Cache)와 메모리 압력이 커집니다. - Unsignaled Completion: 모든 WR에 completion을 요구하면 CQ 트래픽이 폭증합니다. 벌크 전송은 주기적으로만 signaled WR을 넣는 방식이 일반적입니다.
- 배치 posting: 작은 WR을 여러 개 묶어 post하면 doorbell/MMIO 비용을 줄일 수 있습니다. 대규모 RPC보다 스트리밍 전송에서 특히 중요합니다.
- Completion vector / IRQ affinity: CQ와 poller 스레드(Thread)를 HCA와 같은 NUMA 노드에 배치하고 인터럽트(Interrupt) affinity를 맞추면 tail latency가 크게 줄어듭니다.
- CQE compression / moderation: provider가 지원하면 CQE 압축과 moderation으로 PCIe 및 CPU 부담을 줄일 수 있지만, 초저지연 단건 RPC에는 오히려 손해일 수 있습니다.
GPUDirect RDMA
NVIDIA GPUDirect RDMA는 GPU 메모리를 RDMA MR로 직접 등록하여, CPU 메모리를 거치지 않고 GPU ↔ HCA 간 직접 P2P DMA 전송을 수행합니다. AI/ML 분산 학습에서 GPU 간 gradient 교환 성능을 극적으로 향상시킵니다.
- 전통 경로: peer-memory 드라이버가 GPU 페이지를 핀하고 HCA가 이를 MR처럼 접근합니다.
- 현대 경로: 사용자 공간에서 dma-buf 기반 MR 등록을 사용해 GPU/가속기 메모리를 직접 연결하는 방식이 점점 중요해지고 있습니다.
- 검증 도구:
perftest는--use_cuda,--use_cuda_dmabuf같은 옵션으로 GPU 메모리 경로를 직접 검증할 수 있습니다. - 주의점: 동일 PCIe root complex, IOMMU 설정, peer-memory 모듈, CUDA/OFED/rdma-core 버전 호환성이 모두 맞아야 합니다.
DMA 페이지의 P2P DMA / GPUDirect 섹션도 참고하세요.
ulimit -l로 프로세스별 메모리 잠금(Lock) 제한을 확인하고, /etc/security/limits.conf에서
RDMA 사용자의 memlock 값을 적절히 설정해야 합니다.
컨테이너 환경에서는 cgroups의 memory.max도 함께 고려해야 합니다.
격리와 멀티테넌시
InfiniBand는 본질적으로 강한 성능 지향 기술이지만, 클라우드·공유 HPC·AI 플랫폼에서는 누가 어느 HCA 자원을 얼마나 쓰는지, 어느 파티션까지 접근 가능한지를 함께 설계해야 합니다. 단순히 디바이스 파일 권한만 나눠 주는 것으로는 충분하지 않습니다.
패브릭 격리: P_Key, SL, SR-IOV
- P_Key: 패브릭 수준 논리 분할입니다. IPoIB child interface, RC/UD 통신, 멀티캐스트 구성 모두 P_Key membership의 영향을 받습니다.
- SL / QoS 정책: 테넌트별 또는 워크로드별 우선순위(Priority) 분리를 구현할 때 쓰입니다. 대역폭 제어가 아니라 지연 격리에 더 가깝습니다.
- SR-IOV VF: VM/컨테이너에 VF를 직접 넘기면 성능은 좋지만, 펌웨어/하이퍼바이저(Hypervisor)/게스트 드라이버 조합이 모두 맞아야 하며 PF 수준 기능 일부는 노출되지 않을 수 있습니다.
RDMA cgroup controller
현재 커널 문서는 RDMA controller가 프로세스 집합별로 RDMA/IB 자원 사용량을 제한한다고 설명합니다.
인터페이스는 rdma.max, rdma.current 파일을 통해 노출되며, 장치별로 hca_handle, hca_object 한도를 설정합니다.
| 제어 항목 | 의미 | 실무 해석 |
|---|---|---|
hca_handle |
RDMA 장치 핸들 수 제한 | 프로세스군이 열 수 있는 HCA 컨텍스트 수를 제한합니다. |
hca_object |
RDMA 객체 수 제한 | QP, CQ, MR, AH 등 하드웨어/커널 객체 총량을 묶어서 제어합니다. |
# cgroup v2 예시: mlx5_0의 RDMA 자원 제한
mkdir -p /sys/fs/cgroup/tenant-a
echo '+rdma' > /sys/fs/cgroup/cgroup.subtree_control
echo 'mlx5_0 hca_handle=4 hca_object=2000' > /sys/fs/cgroup/tenant-a/rdma.max
# 현재 사용량 확인
cat /sys/fs/cgroup/tenant-a/rdma.current
cat /sys/fs/cgroup/tenant-a/rdma.max
/dev/infiniband/uverbs0만 넘겨도, memlock, cgroup RDMA 제한,
IOMMU 정책, VF/PF 매핑, GPU dma-buf 권한이 맞지 않으면 성능과 안정성이 바로 깨집니다.
RDMA 자원 격리는 반드시 디바이스 파일 + cgroup + 패브릭 파티션을 같이 봐야 합니다.
네임스페이스(Namespace)와 capability 위임
- network namespace: netdev는 네임스페이스에 들어가도 RDMA 디바이스 자체는 별도 관리 경로를 가집니다. 운영 중
rdma system set netns shared같은 정책이 왜 필요한지 여기서 드러납니다. - UCAP (Userspace Capabilities): 최신 커널 문서는 일부 저수준 기능을 더 세분화된 권한 모델로 위임하는 방향을 설명합니다. DevX류 기능을 모두에게 열지 않으려는 흐름입니다.
- 장치 파일 권한:
/dev/infiniband/rdma_cm,uverbs,umad는 성격이 서로 다르므로, 단순히chmod 666로 풀어 버리면 관리 평면까지 과도하게 열릴 수 있습니다.
트러블슈팅 및 주의사항
- InfiniBand: SM/SA, LID, P_Key, SL/VL, credit-based flow control, Path Record를 먼저 확인
- RoCE: PFC, ECN/DCQCN, VLAN/DSCP, lossless 큐, GID/IP 라우팅을 먼저 확인
ibv_devinfo와 rdma link에서 link_layer가 무엇인지부터 보세요.
일반적인 트러블슈팅 순서:
ibstat,ibv_devinfo,rdma link으로 포트 상태와 link_layer를 확인합니다.sminfo,saquery -s,ibnetdiscover로 SM/SA와 fabric discovery가 정상인지 확인합니다.saquery -p,ibroute,ibtracert로 P_Key와 실제 경로를 점검합니다.perfquery,ibqueryerrors,ibdiagnet로 포트 카운터와 fabric 오류를 수집합니다.rdma resource show,dmesg | grep -i rdma로 QP/CQ/MR과 커널 로그를 확인합니다.ib_write_bw,ib_send_bw, 필요하면 GPU 옵션이 붙은 perftest로 재현 가능한 성능 기준선을 만듭니다.
| 증상 | 먼저 볼 것 | 흔한 원인 |
|---|---|---|
| 포트가 INIT에서 올라오지 않음 | ibstat, sminfo, opensm 로그 |
SM 부재, 잘못된 GUID 바인딩, 케이블/모듈 문제, 포트 speed negotiation 실패 |
| QP가 RTR/RTS로 전이되지 않음 | QPN, LID/GID, PSN, MTU, P_Key | out-of-band 교환 값 오류, 경로 해석 실패, 파티션 불일치 |
IB_WC_REM_ACCESS_ERR, REM_INV_REQ_ERR |
MR access flags, rkey, remote_addr | 잘못된 rkey 전달, 원격 버퍼 길이 초과, MW/MR 무효화 이후 접근 |
RNR retry exceeded |
RQ depth, SRQ, 수신 버퍼 게시 타이밍 | 수신 측이 Receive WR을 충분히 post하지 않음, CQ poller 지연 |
| 대역폭이 선형으로 안 나옴 | NUMA 배치, CQE 수, signaled 비율, MTU, multi-rail | HCA와 메모리의 NUMA 불일치, 너무 잦은 completion, 작은 큐 깊이, 혼잡/경로 편중 |
| IPoIB child interface에 트래픽이 없음 | /sys/class/net/*/pkey, SA 파티션 정보 |
파티션 membership 누락, 잘못된 P_Key index, connected/datagram mode 혼선 |
자주 보는 Work Completion 상태
실패한 WR은 결국 CQ의 ibv_wc.status로 드러납니다. rdma-core의 enum ibv_wc_status를 기준으로,
현장에서 가장 자주 보는 상태는 다음 정도입니다.
ibv_wc_status | 의미 | 우선 확인할 것 |
|---|---|---|
IBV_WC_SUCCESS |
정상 완료 | 지연/처리량 문제면 큐 깊이, polling, NUMA, MTU 쪽으로 이동 |
IBV_WC_LOC_LEN_ERR |
로컬 SGE 길이/버퍼 범위 오류 | SGE length, MR 길이, receive buffer 크기 |
IBV_WC_LOC_PROT_ERR |
로컬 보호 오류 | lkey, access flag, 잘못된 MR 재사용 여부 |
IBV_WC_WR_FLUSH_ERR |
QP가 error 상태로 가며 미완료 WR이 flush됨 | 그 전에 발생한 최초 에러, QP 이벤트 핸들러(Handler) 로그 |
IBV_WC_REM_ACCESS_ERR |
원격 접근 권한 오류 | 원격 MR access flag, rkey, remote_addr 범위 |
IBV_WC_RETRY_EXC_ERR |
재시도 횟수 초과 | 상대 QP 상태, 경로 단절, PSN/MTU 불일치, 혼잡/링크 오류 |
IBV_WC_RNR_RETRY_EXC_ERR |
RNR 재시도 초과 | 수신 측 Receive WR 게시 누락, SRQ 고갈, poller 지연 |
IBV_WC_WR_FLUSH_ERR는 대개 2차 증상입니다. flush가 보이면 그 직전에 발생한
RETRY_EXC, REM_ACCESS, 링크 down 이벤트를 먼저 찾는 편이 훨씬 빠릅니다.
커널 모듈 관련 이슈는 네트워크 스택(Network Stack) 페이지를, PCIe/SR-IOV 관련 문제는 PCI/PCIe 서브시스템 페이지를 참고하세요.
프로덕션 배포 체크리스트
InfiniBand는 단일 설정값 하나로 안정화되는 기술이 아닙니다. 하드웨어, 패브릭, 메모리, 애플리케이션이 모두 같은 방향으로 맞아야 하므로 배포 전 체크 순서를 명확히 고정하는 것이 중요합니다.
- PCIe / 펌웨어 정합성
HCA가 기대한 링크 폭/속도로 올라왔는지, 펌웨어와 드라이버 조합이 지원 매트릭스 안에 있는지 확인합니다. - 패브릭 활성화
ibstat가Active,sminfo가 정상, LID와 P_Key가 의도한 값으로 배포됐는지 확인합니다. - 단일 흐름 기준선
ib_write_bw,ib_write_lat,ib_send_bw로 단일 QP/단일 코어 기준선을 먼저 만듭니다. - NUMA / IRQ / poller 배치
HCA와 같은 NUMA 노드에 메모리와 스레드를 묶고 CQ completion vector, IRQ affinity를 정렬합니다. - 애플리케이션 모델 선택
제어 메시지는 Send/Recv, 대용량 데이터는 RDMA Write/Read, 대규모 fan-in은 SRQ/XRC/DC로 분리 설계합니다. - 상위 프로토콜 검증
IPoIB, NFS/RDMA, NVMe-oF/RDMA, GPU 메모리 경로를 각각 별도 기준선으로 확인합니다. 기본 RDMA가 빠르다고 ULP도 자동으로 빠른 것은 아닙니다. - 지속 관측 체계
perfquery,ibqueryerrors,ibdiagnet,rdma statistic show를 정기 수집에 포함합니다. - 장애 시 격리 순서
애플리케이션 → Verbs → QP/CQ/MR → 포트 상태 → SM/SA → 스위치 경로 → PCIe/IOMMU 순으로 아래로 내려가며 원인을 좁힙니다.
2노드 Bring-up 시나리오
새 장비를 랙에 넣었을 때 가장 안전한 방식은, 복잡한 스토리지나 MPI 스택을 바로 올리지 말고 두 노드만 연결한 최소 구성에서 패브릭, Verbs, IPoIB, 성능 기준선을 순서대로 확인하는 것입니다.
- 드라이버와 관리 도구 로드
mlx5_ib,ib_umad, 필요하면ib_ipoib가 올라왔는지 확인합니다. - SM 확보
스위치 내장 SM이 없다면 한 노드에서opensm을 띄우고 다른 노드 포트가INIT → ACTIVE로 전이되는지 봅니다. - 기본 관측
ibstat,ibv_devinfo,sminfo,saquery -s로 포트 상태와 SM/SA를 점검합니다. - Verbs 기준선
ib_write_bw,ib_write_lat,ib_send_bw만으로 HCA-to-HCA 경로를 먼저 검증합니다. - IP 계층 확인
필요하면 IPoIB를 올리고ip addr,ping, 애플리케이션 포트 바인딩을 점검합니다. - 애플리케이션 계층 확장
그 다음에야 UCX/Open MPI, NVMe-oF, NFS/RDMA, GPU direct를 하나씩 붙입니다.
# 노드 A: SM이 필요하면 시작
modprobe mlx5_ib ib_umad
opensm -F
# 양 노드: 포트 상태 확인
ibstat
ibv_devinfo
sminfo
# 양 노드: Verbs 기준선 측정
# 노드 A (server)
ib_write_bw -d mlx5_0
# 노드 B (client)
ib_write_bw -d mlx5_0 <server_name_or_ip>
# 필요 시 IPoIB 확인
modprobe ib_ipoib
ip link set ib0 up
ip addr add 10.10.10.1/24 dev ib0
RDMA 데이터 경로 (Zero-copy)
RDMA의 핵심 가치는 커널 바이패스 + 제로카피입니다. 전통적인 TCP/IP 소켓 모델에서는 애플리케이션 버퍼 → 커널 소켓 버퍼 → NIC DMA 버퍼로 최소 2회 복사가 발생하지만, RDMA에서는 사용자 공간 버퍼를 MR로 등록하면 HCA가 해당 물리 페이지에 직접 DMA를 수행합니다.
제로카피 메커니즘 상세
- 메모리 등록 (MR):
ibv_reg_mr()로 사용자 버퍼를 등록하면, 커널은 해당 가상 주소(Virtual Address) 범위의 물리 페이지를 핀(pin)하고 HCA에 매핑 테이블을 설치합니다. 이후 HCA는 물리 주소(Physical Address)로 직접 DMA합니다. - 커널 바이패스: 데이터 경로에서
ibv_post_send()/ibv_post_recv()는 사용자 공간에서 직접 HCA의 MMIO doorbell을 두드려 WR을 제출합니다. 시스템 콜(System Call)이 발생하지 않습니다. - Completion 경로:
ibv_poll_cq()역시 커널 개입 없이 CQ 메모리를 직접 읽습니다. 인터럽트 방식(ibv_get_cq_event())만 커널을 거칩니다. - RDMA Write: 송신 측이 원격 MR에 직접 쓰기를 수행합니다. 수신 측 CPU는 전혀 관여하지 않으며, 심지어 Receive WR을 post할 필요도 없습니다.
- RDMA Read: 송신 측이 원격 MR에서 데이터를 가져옵니다. 마찬가지로 수신 측 CPU 개입이 없습니다.
QP 상태 전이 다이어그램
Queue Pair는 명시적인 상태 머신을 따릅니다. QP를 생성하면 RESET 상태에서 시작하며,
ibv_modify_qp()를 통해 순서대로 전이시켜야 데이터 전송이 가능합니다.
상태 전이를 건너뛰거나 잘못된 속성을 설정하면 QP가 ERROR 상태로 빠집니다.
상태별 필수 속성
| 전이 | 필수 속성 | 설명 |
|---|---|---|
RESET → INIT |
qp_access_flags, pkey_index, port_num |
QP가 소속될 포트와 파티션, 원격 접근 권한을 설정. 이 단계에서 Receive WR을 post할 수 있음 |
INIT → RTR |
dest_qp_num, rq_psn, max_dest_rd_atomic, min_rnr_timer, ah_attr(LID/GID), path_mtu |
원격 QP 정보와 경로를 설정. 이 단계부터 수신 가능 |
RTR → RTS |
sq_psn, timeout, retry_cnt, rnr_retry, max_rd_atomic |
송신 측 파라미터 설정. 이 단계부터 Send/RDMA Write/Read 가능 |
RTS → SQD |
없음 | Send Queue를 drain하여 진행 중인 WR 완료 후 일시 중지 |
SQD → RTS |
변경할 속성만 | 경로, 타이머(Timer) 등을 변경한 뒤 재개 |
INIT → RTR 전이에서 path_mtu를 양쪽 포트/스위치가 지원하지 않는 값으로 설정하면
ibv_modify_qp()가 EINVAL을 반환합니다. 반드시 ibv_query_port()로 active_mtu를 먼저 확인하세요.
RDMA CM 연결 설정 및 데이터 전송 예제
librdmacm을 사용하면 QP 상태 전이와 주소 교환을 자동화할 수 있습니다.
아래는 RDMA CM을 이용한 기본적인 RC 연결 설정과 Send/Recv 데이터 전송의 흐름입니다.
서버 측 흐름
/* RDMA CM 서버 예제 — 핵심 흐름 */
#include <rdma/rdma_cma.h>
#include <infiniband/verbs.h>
#define BUFFER_SIZE 4096
#define PORT 20000
int main(void) {
struct rdma_event_channel *ec;
struct rdma_cm_id *listener, *conn_id;
struct rdma_cm_event *event;
struct ibv_pd *pd;
struct ibv_cq *cq;
struct ibv_mr *mr;
struct ibv_qp_init_attr qp_attr = {};
struct rdma_conn_param conn_param = {};
char *buf;
struct sockaddr_in addr = {};
/* 1. 이벤트 채널 생성 */
ec = rdma_create_event_channel();
/* 2. CM ID 생성 및 바인드 */
rdma_create_id(ec, &listener, NULL, RDMA_PS_TCP);
addr.sin_family = AF_INET;
addr.sin_port = htons(PORT);
rdma_bind_addr(listener, (struct sockaddr *)&addr);
/* 3. 리스닝 시작 */
rdma_listen(listener, 1);
/* 4. 연결 요청 대기 */
rdma_get_cm_event(ec, &event);
/* event->event == RDMA_CM_EVENT_CONNECT_REQUEST */
conn_id = event->id;
rdma_ack_cm_event(event);
/* 5. PD, CQ, MR 생성 */
pd = ibv_alloc_pd(conn_id->verbs);
cq = ibv_create_cq(conn_id->verbs, 16, NULL, NULL, 0);
buf = malloc(BUFFER_SIZE);
mr = ibv_reg_mr(pd, buf, BUFFER_SIZE,
IBV_ACCESS_LOCAL_WRITE |
IBV_ACCESS_REMOTE_WRITE);
/* 6. QP 생성 (rdma_create_qp가 INIT까지 자동 전이) */
qp_attr.send_cq = cq;
qp_attr.recv_cq = cq;
qp_attr.qp_type = IBV_QPT_RC;
qp_attr.cap.max_send_wr = 16;
qp_attr.cap.max_recv_wr = 16;
qp_attr.cap.max_send_sge = 1;
qp_attr.cap.max_recv_sge = 1;
rdma_create_qp(conn_id, pd, &qp_attr);
/* 7. Receive WR 사전 게시 */
struct ibv_sge sge = { .addr = (uintptr_t)buf,
.length = BUFFER_SIZE,
.lkey = mr->lkey };
struct ibv_recv_wr recv_wr = { .sg_list = &sge,
.num_sge = 1 };
struct ibv_recv_wr *bad_wr;
ibv_post_recv(conn_id->qp, &recv_wr, &bad_wr);
/* 8. 연결 수락 (RTR → RTS 자동 전이) */
rdma_accept(conn_id, &conn_param);
rdma_get_cm_event(ec, &event);
/* event->event == RDMA_CM_EVENT_ESTABLISHED */
rdma_ack_cm_event(event);
/* 9. CQ 폴링하여 수신 데이터 확인 */
struct ibv_wc wc;
while (ibv_poll_cq(cq, 1, &wc) == 0)
; /* busy-wait */
printf("수신: %s\n", buf);
/* 10. 정리 */
rdma_disconnect(conn_id);
ibv_dereg_mr(mr);
ibv_destroy_qp(conn_id->qp);
ibv_destroy_cq(cq);
ibv_dealloc_pd(pd);
free(buf);
rdma_destroy_id(conn_id);
rdma_destroy_id(listener);
rdma_destroy_event_channel(ec);
return 0;
}
클라이언트 측 흐름
/* RDMA CM 클라이언트 예제 — 핵심 흐름 */
int main(void) {
struct rdma_event_channel *ec;
struct rdma_cm_id *conn_id;
struct rdma_cm_event *event;
struct ibv_pd *pd;
struct ibv_cq *cq;
struct ibv_mr *mr;
char *buf;
struct rdma_addrinfo hints = {}, *res;
struct ibv_qp_init_attr qp_attr = {};
struct rdma_conn_param conn_param = {};
/* 1. 이벤트 채널 + CM ID 생성 */
ec = rdma_create_event_channel();
rdma_create_id(ec, &conn_id, NULL, RDMA_PS_TCP);
/* 2. 주소 해석 */
hints.ai_port_space = RDMA_PS_TCP;
rdma_getaddrinfo("server_host", "20000", &hints, &res);
rdma_resolve_addr(conn_id, NULL, res->ai_dst_addr, 2000);
rdma_get_cm_event(ec, &event); /* ADDR_RESOLVED */
rdma_ack_cm_event(event);
/* 3. 경로 해석 */
rdma_resolve_route(conn_id, 2000);
rdma_get_cm_event(ec, &event); /* ROUTE_RESOLVED */
rdma_ack_cm_event(event);
/* 4. PD, CQ, MR 생성 + QP 생성 */
pd = ibv_alloc_pd(conn_id->verbs);
cq = ibv_create_cq(conn_id->verbs, 16, NULL, NULL, 0);
buf = malloc(BUFFER_SIZE);
memcpy(buf, "Hello RDMA!", 12);
mr = ibv_reg_mr(pd, buf, BUFFER_SIZE,
IBV_ACCESS_LOCAL_WRITE);
qp_attr.send_cq = cq;
qp_attr.recv_cq = cq;
qp_attr.qp_type = IBV_QPT_RC;
qp_attr.cap.max_send_wr = 16;
qp_attr.cap.max_recv_wr = 16;
qp_attr.cap.max_send_sge = 1;
qp_attr.cap.max_recv_sge = 1;
rdma_create_qp(conn_id, pd, &qp_attr);
/* 5. 연결 요청 → 서버 수락 후 ESTABLISHED */
rdma_connect(conn_id, &conn_param);
rdma_get_cm_event(ec, &event); /* ESTABLISHED */
rdma_ack_cm_event(event);
/* 6. Send WR 게시 */
struct ibv_sge sge = { .addr = (uintptr_t)buf,
.length = 12,
.lkey = mr->lkey };
struct ibv_send_wr send_wr = { .sg_list = &sge,
.num_sge = 1,
.opcode = IBV_WR_SEND,
.send_flags = IBV_SEND_SIGNALED };
struct ibv_send_wr *bad_wr;
ibv_post_send(conn_id->qp, &send_wr, &bad_wr);
/* 7. 완료 확인 */
struct ibv_wc wc;
while (ibv_poll_cq(cq, 1, &wc) == 0)
;
if (wc.status == IBV_WC_SUCCESS)
printf("전송 완료\n");
/* 8. 정리 */
rdma_disconnect(conn_id);
ibv_dereg_mr(mr);
ibv_destroy_qp(conn_id->qp);
ibv_destroy_cq(cq);
ibv_dealloc_pd(pd);
free(buf);
rdma_destroy_id(conn_id);
rdma_destroy_event_channel(ec);
return 0;
}
rdma_create_qp()가 QP를 INIT까지,
rdma_connect() / rdma_accept()가 RTR → RTS까지 자동 전이시킵니다.
수동으로 ibv_modify_qp()를 호출하고 QPN/PSN/LID를 교환하는 복잡한 과정이 모두 생략됩니다.
ibverbs를 이용한 Send/Recv 프로그래밍
RDMA CM 없이 libibverbs만으로 직접 QP를 관리하는 경우, QP 상태 전이와
원격 QP 정보 교환을 직접 수행해야 합니다. 이 방식은 더 복잡하지만 세밀한 제어가 가능합니다.
리소스 생성 순서
/* ibverbs 직접 사용 — 리소스 생성 흐름 */
struct ibv_device **dev_list;
struct ibv_context *ctx;
struct ibv_pd *pd;
struct ibv_cq *cq;
struct ibv_qp *qp;
struct ibv_mr *mr;
/* 1. 디바이스 열기 */
dev_list = ibv_get_device_list(NULL);
ctx = ibv_open_device(dev_list[0]);
/* 2. Protection Domain */
pd = ibv_alloc_pd(ctx);
/* 3. Completion Queue */
cq = ibv_create_cq(ctx, 256, NULL, NULL, 0);
/* 4. Queue Pair */
struct ibv_qp_init_attr qp_init = {
.send_cq = cq,
.recv_cq = cq,
.qp_type = IBV_QPT_RC,
.cap = { .max_send_wr = 128,
.max_recv_wr = 128,
.max_send_sge = 1,
.max_recv_sge = 1 }
};
qp = ibv_create_qp(pd, &qp_init);
/* 5. Memory Region 등록 */
char *buf = malloc(4096);
mr = ibv_reg_mr(pd, buf, 4096,
IBV_ACCESS_LOCAL_WRITE |
IBV_ACCESS_REMOTE_WRITE |
IBV_ACCESS_REMOTE_READ);
수동 QP 상태 전이
/* RESET → INIT */
struct ibv_qp_attr attr = {};
attr.qp_state = IBV_QPS_INIT;
attr.pkey_index = 0;
attr.port_num = 1;
attr.qp_access_flags = IBV_ACCESS_REMOTE_WRITE |
IBV_ACCESS_REMOTE_READ |
IBV_ACCESS_LOCAL_WRITE;
ibv_modify_qp(qp, &attr,
IBV_QP_STATE | IBV_QP_PKEY_INDEX |
IBV_QP_PORT | IBV_QP_ACCESS_FLAGS);
/* INIT → RTR (원격 QP 정보 필요) */
attr.qp_state = IBV_QPS_RTR;
attr.path_mtu = IBV_MTU_4096;
attr.dest_qp_num = remote_qpn; /* 상대에게서 받은 값 */
attr.rq_psn = remote_psn;
attr.max_dest_rd_atomic = 4;
attr.min_rnr_timer = 12;
attr.ah_attr.dlid = remote_lid;
attr.ah_attr.sl = 0;
attr.ah_attr.port_num = 1;
attr.ah_attr.is_global = 0; /* RoCE면 1 + GRH 설정 */
ibv_modify_qp(qp, &attr,
IBV_QP_STATE | IBV_QP_AV | IBV_QP_PATH_MTU |
IBV_QP_DEST_QPN | IBV_QP_RQ_PSN |
IBV_QP_MAX_DEST_RD_ATOMIC | IBV_QP_MIN_RNR_TIMER);
/* RTR → RTS */
attr.qp_state = IBV_QPS_RTS;
attr.sq_psn = local_psn;
attr.timeout = 14; /* ~67 sec */
attr.retry_cnt = 7;
attr.rnr_retry = 7; /* 무한 재시도 */
attr.max_rd_atomic = 4;
ibv_modify_qp(qp, &attr,
IBV_QP_STATE | IBV_QP_SQ_PSN | IBV_QP_TIMEOUT |
IBV_QP_RETRY_CNT | IBV_QP_RNR_RETRY |
IBV_QP_MAX_QP_RD_ATOMIC);
qp_num, psn, lid, gid 값을 교환해야 합니다.
이 교환 과정에서 값이 하나라도 잘못되면 QP가 ERROR 상태로 빠지거나 RETRY_EXC_ERR이 발생합니다.
RoCE v2 설정 및 운영
RDMA over Converged Ethernet (RoCE) v2는 UDP/IP 위에서 RDMA를 수행합니다. 네이티브 InfiniBand와 같은 Verbs API를 사용하지만, 손실 처리, 혼잡 제어, 주소 해석 방식이 완전히 다릅니다.
Lossless 네트워크 구성
RoCE v2는 UDP 기반이므로, 패킷 손실 시 RDMA 전송 계층이 직접 재전송을 처리해야 합니다. 이는 극심한 성능 저하로 이어지므로, 실무에서는 반드시 Lossless 네트워크를 구성합니다.
# PFC (Priority Flow Control) 설정 — 특정 우선순위에 대해 무손실 보장
# 예: 우선순위 3번을 RDMA 트래픽용 무손실로 설정
mlnx_qos -i eth0 --pfc 0,0,0,1,0,0,0,0
# ECN (Explicit Congestion Notification) 활성화
# 스위치에서 혼잡 감지 시 CE(Congestion Experienced) 비트를 마킹
echo 1 > /sys/class/net/eth0/ecn/roce_np/enable/3
echo 1 > /sys/class/net/eth0/ecn/roce_rp/enable/3
# DCQCN (Data Center QCN) 파라미터 — Mellanox 기준
# 혼잡 감지 임계값 및 반응 속도 조절
cma_roce_mode -d mlx5_0 -p 1 -m 2 # RoCE v2 모드 강제
# GID 테이블 확인 — RoCE v2는 GID가 IP 주소에 매핑됨
rdma link show mlx5_0/1
ibv_devinfo -v | grep GID
# DSCP → 우선순위 매핑
mlnx_qos -i eth0 --dscp2prio set,26,3
| 구성 요소 | 역할 | 미설정 시 증상 |
|---|---|---|
| PFC | 링크 수준 흐름 제어, 특정 우선순위의 패킷 손실 방지 | 패킷 드롭 → RDMA 재전송 → 성능 급락, QP ERROR 전이 |
| ECN/DCQCN | 엔드-투-엔드 혼잡 제어, 송신 속도 조절 | PFC 폭풍 발생, head-of-line blocking, 패브릭 전체 성능 저하 |
| DSCP 매핑 | IP 레벨 QoS 마킹으로 스위치별 큐 분류 | RDMA 트래픽이 일반 트래픽과 같은 큐 → PFC 효과 없음 |
| VLAN 태깅 | 802.1Q 우선순위 비트로 PFC 대상 식별 | 스위치가 우선순위를 구분하지 못함 |
GPUDirect RDMA 아키텍처
AI/ML 분산 학습에서 GPU 간 gradient 교환은 가장 빈번하고 대역폭 집약적인 통신입니다. GPUDirect RDMA는 CPU 메모리를 거치지 않고 GPU VRAM ↔ HCA 간 직접 P2P DMA를 수행하여 복사 오버헤드를 제거하고 GPU-to-GPU 지연시간을 최소화합니다.
GPUDirect RDMA 설정
# 1. nvidia-peermem 모듈 로드 (CUDA 11.4+ / OFED 5.4+)
modprobe nvidia-peermem
# 확인
lsmod | grep nvidia_peermem
dmesg | grep -i peermem
# 2. PCIe 토폴로지 확인 — GPU와 HCA가 같은 PCIe switch 아래에 있어야 최적
nvidia-smi topo -m
# GPU0 ↔ mlx5_0 : PIX (같은 PCIe switch)
# GPU0 ↔ mlx5_2 : SYS (다른 NUMA, 성능 저하)
# 3. dma-buf 기반 MR 등록 (최신 방식, rdma-core 39+)
# 커널 5.12+에서 ib_umem_dmabuf_get() 경로 사용
# perftest로 검증:
ib_write_bw -d mlx5_0 --use_cuda=0 # 전통 peer-memory
ib_write_bw -d mlx5_0 --use_cuda_dmabuf=0 # dma-buf 방식
# 4. IOMMU 설정 확인
# GPUDirect P2P는 IOMMU passthrough 또는 ACS 비활성화가 필요할 수 있음
dmesg | grep -i iommu
cat /proc/cmdline | grep iommu
| 구성 요소 | 요구 사항 | 확인 명령 |
|---|---|---|
| NVIDIA 드라이버 | 470+ (peer-memory 지원) | nvidia-smi |
| nvidia-peermem | 모듈 로드 필수 | lsmod | grep nvidia_peermem |
| OFED / rdma-core | MLNX_OFED 5.4+ 또는 inbox rdma-core 39+ | ofed_info 또는 rpm -q rdma-core |
| PCIe 토폴로지 | GPU와 HCA가 같은 PCIe switch (PIX/PHB) | nvidia-smi topo -m |
| IOMMU | passthrough 또는 ACS override | dmesg | grep iommu |
SR-IOV와 InfiniBand 가상화
클라우드와 공유 HPC 환경에서는 하나의 물리 HCA를 여러 VM/컨테이너에 분할해야 합니다. InfiniBand SR-IOV는 물리 기능(PF)에서 가상 기능(VF)을 생성하여 각각 독립적인 RDMA 디바이스를 제공합니다.
SR-IOV 설정
# 1. VF 생성 (PF에서)
echo 4 > /sys/class/infiniband/mlx5_0/device/sriov_numvfs
# 2. VF 확인
lspci | grep -i mellanox
ibdev2netdev
# 3. VF의 P_Key 설정 (SM이 관리)
# opensm 설정에서 VF GUID에 P_Key membership을 부여
# 4. VF GUID 설정 (하이퍼바이저에서)
ip link set dev enp1s0f0 vf 0 node_guid 00:11:22:33:44:55:66:77
ip link set dev enp1s0f0 vf 0 port_guid 00:11:22:33:44:55:66:78
# 5. VFIO로 VM에 패스스루
echo 0000:04:00.2 > /sys/bus/pci/devices/0000:04:00.2/driver/unbind
echo mlx5_core > /sys/bus/pci/devices/0000:04:00.2/driver_override
# 또는 VFIO 바인딩 후 QEMU에 -device vfio-pci 옵션으로 전달
# 6. K8s 환경 — RDMA device plugin
# Mellanox RDMA Shared Device Plugin으로 VF를 Pod에 자동 할당
kubectl get node -o json | jq '.items[].status.allocatable' | grep rdma
| 가상화 모드 | 격리 수준 | 성능 | 사용 사례 |
|---|---|---|---|
| SR-IOV VF (VFIO) | 하드웨어 격리, 독립 GID/P_Key | 네이티브에 근접 | VM에 전용 RDMA 디바이스 제공 |
| vHCA (Mellanox) | 펌웨어 수준 격리, 더 많은 VF 생성 가능 | 네이티브에 근접 | 대규모 멀티테넌트 클라우드 |
| SoftRoCE (rxe) | 소프트웨어, 낮은 격리 | 낮음 (소프트웨어 에뮬레이션) | 개발/테스트 환경 |
| SMC-R (z/Linux) | 프로토콜 수준 | 중간 | IBM 메인프레임 환경 |
RDMA 성능 튜닝
기본적인 QP/CQ 설정 이후에도 성능이 기대에 미치지 못할 때, 다음 영역을 순서대로 점검합니다.
인터럽트 모더레이션 (Interrupt Coalescing)
CQ 이벤트가 너무 빈번하면 인터럽트 오버헤드로 CPU가 포화됩니다. 반대로 너무 느리면 tail latency가 증가합니다.
# Mellanox HCA의 인터럽트 모더레이션 확인/설정
ethtool -c eth0
ethtool -C eth0 rx-usecs 8 rx-frames 64 adaptive-rx on
# RDMA 전용 CQ moderation (rdma-core API)
# ibv_modify_cq() 또는 mlx5dv_modify_cq()로 설정
# cq_period: 이벤트 간 최소 대기 시간 (마이크로초)
# cq_count: 이벤트 발생 전 최소 CQE 누적 수
# IRQ affinity 확인 — HCA와 같은 NUMA 노드에 바인딩
cat /proc/interrupts | grep mlx5
# IRQ를 특정 CPU에 고정
echo 4 > /proc/irq/123/smp_affinity_list
Adaptive Routing / ECMP
- Adaptive Routing (AR): HDR/NDR 스위치가 포트별 혼잡도를 실시간 모니터링하여 패킷 경로를 동적으로 변경합니다. All-to-All 통신(AI 학습의 AllReduce)에서 특히 효과적입니다.
- 주의점: AR은 패킷 순서를 보장하지 않을 수 있으므로, RC QP에서는 HCA가 PSN 기반 재정렬을 수행합니다. UD QP는 애플리케이션이 직접 처리해야 합니다.
- ECMP (Equal-Cost Multi-Path): RoCE v2 환경에서 IP 라우팅의 ECMP를 활용하여 여러 경로로 트래픽을 분산합니다. 해시(Hash) 기반이므로 elephant flow 문제가 발생할 수 있습니다.
ECN 기반 혼잡 제어
| 알고리즘 | 적용 환경 | 동작 원리 | 파라미터 |
|---|---|---|---|
| DCQCN | RoCE v2 (Mellanox) | ECN CE 마킹 → CNP 전송 → 송신 속도 감소 | clamp_tgt_rate, rpg_time_reset |
| HCQCN | RoCE v2 (ConnectX-6+) | DCQCN 개선, 하드웨어 가속 | 펌웨어 내장 |
| IB CC | 네이티브 InfiniBand | FECN/BECN 비트 기반 혼잡 알림 | SM QoS 정책 |
NVMe-oF RDMA
NVMe over Fabrics(NVMe-oF)는 NVMe 프로토콜을 네트워크로 확장하여 원격 NVMe 장치에 로컬과 유사한 지연시간으로 접근합니다. RDMA 전송을 사용하면 커널 바이패스와 제로카피의 이점을 스토리지 I/O에도 적용할 수 있습니다.
NVMe-oF/RDMA 설정
# === Target (스토리지 서버) ===
modprobe nvmet
modprobe nvmet-rdma
# Subsystem 생성
mkdir -p /sys/kernel/config/nvmet/subsystems/nvme-subsys1
echo 1 > /sys/kernel/config/nvmet/subsystems/nvme-subsys1/attr_allow_any_host
# Namespace 연결 (로컬 NVMe 장치)
mkdir -p /sys/kernel/config/nvmet/subsystems/nvme-subsys1/namespaces/1
echo /dev/nvme0n1 > /sys/kernel/config/nvmet/subsystems/nvme-subsys1/namespaces/1/device_path
echo 1 > /sys/kernel/config/nvmet/subsystems/nvme-subsys1/namespaces/1/enable
# RDMA 포트 생성
mkdir -p /sys/kernel/config/nvmet/ports/1
echo rdma > /sys/kernel/config/nvmet/ports/1/addr_trtype
echo ipv4 > /sys/kernel/config/nvmet/ports/1/addr_adrfam
echo 10.10.10.1 > /sys/kernel/config/nvmet/ports/1/addr_traddr
echo 4420 > /sys/kernel/config/nvmet/ports/1/addr_trsvcid
# Subsystem을 포트에 연결
ln -s /sys/kernel/config/nvmet/subsystems/nvme-subsys1 \
/sys/kernel/config/nvmet/ports/1/subsystems/
# === Initiator (클라이언트) ===
modprobe nvme-rdma
# Discovery
nvme discover -t rdma -a 10.10.10.1 -s 4420
# 연결
nvme connect -t rdma -n nvme-subsys1 -a 10.10.10.1 -s 4420
# 확인
nvme list
lsblk
nvme connect의 --nr-io-queues 옵션으로 큐 수를 조절할 수 있습니다.
흔한 실수와 안티패턴
RDMA 프로그래밍과 InfiniBand 운영에서 자주 발생하는 실수를 정리합니다. 이러한 패턴을 미리 인지하면 디버깅 시간을 크게 줄일 수 있습니다.
안티패턴 상세
| 실수 | 증상 | 원인 | 해결 |
|---|---|---|---|
| QP 상태 전이 건너뛰기 | ibv_modify_qp() EINVAL |
RESET→RTR 직접 전이 시도 | 반드시 RESET→INIT→RTR→RTS 순서 준수 |
| Recv WR 미게시 | RNR_RETRY_EXC_ERR |
상대가 Send를 보냈는데 수신 버퍼가 없음 | QP를 RTR로 전이하기 전에 Recv WR을 post |
| path_mtu 초과 | QP 전이 실패 또는 패킷 드롭 | 포트/스위치가 지원하지 않는 MTU 설정 | ibv_query_port()의 active_mtu 확인 후 설정 |
| rkey 전달 오류 | REM_ACCESS_ERR |
잘못된 rkey 또는 만료된 MR의 rkey 사용 | MR 등록 후 즉시 rkey를 상대에게 전달, MR 해제 전 알림 |
| SRQ depth 부족 | 다수 연결에서 동시 RNR | SRQ의 max_wr이 동시 활성 연결 수 대비 부족 | srq_limit 이벤트로 동적 WR 보충, 초기 depth 계산 공식 적용 |
| memlock ulimit | ibv_reg_mr() ENOMEM |
프로세스별 메모리 잠금 한도 초과 | /etc/security/limits.conf에서 memlock 증가 또는 unlimited |
| signaled WR 과다 | CQE 폭증, CPU 과부하 | 모든 WR에 IBV_SEND_SIGNALED 설정 | 벌크 전송은 N개마다 1개만 signaled로 설정 |
rkey와 remote_addr를 상대에게 전달하고,
상대가 RDMA Write를 완료하기 전에 MR을 해제(ibv_dereg_mr())하면
상대 QP가 ERROR 상태로 빠지고 해당 연결의 모든 미완료 WR이 flush됩니다.
MR 수명 관리는 반드시 양쪽의 합의된 프로토콜로 제어해야 합니다.
커널 6.x 최신 변경사항
리눅스 커널 6.x 시리즈에서 RDMA 서브시스템에 가해진 주요 변경사항을 정리합니다.
| 커널 버전 | 변경 사항 | 영향 |
|---|---|---|
| 6.0 | ib_umem 리팩토링, DMABUF MR 개선 | GPU 메모리 등록 경로 안정화 |
| 6.1 | mlx5 ODP 성능 최적화, RoCE LAG 지원 개선 | On-Demand Paging 페이지 폴트 처리 속도 향상 |
| 6.2 | rdma-netlink 통계 인터페이스 확장 | rdma statistic 명령으로 더 세밀한 성능 수집 가능 |
| 6.3 | rxe/siw 소프트웨어 RDMA 안정화 | 개발/테스트용 SoftRoCE, SoftiWARP 품질 향상 |
| 6.4~6.5 | mlx5 Crypto offload, TLS-over-RDMA 기초 | RDMA 경로에서 하드웨어 암호화(Encryption) 가속 |
| 6.6 LTS | RDMA restrack 개선, mlx5 devlink health reporter 확장 | 운영 중 자원 추적과 장애 진단 도구 강화 |
| 6.7~6.8 | ib_core 모듈 분리 리팩토링, MR cache 개선 | 커널 모듈 의존성 정리, 대규모 MR 풀 성능 향상 |
| 6.9~6.12 | rdma-cgroup v2 인터페이스 안정화, mlx5 multi-port 개선 | 컨테이너 환경 자원 제한 강화, 다중 포트 HCA 운영성 향상 |
RDMA 성능 벤치마크 해석
perftest 도구의 출력을 올바르게 해석하는 것은 RDMA 성능 분석의 기본입니다.
# 대역폭 측정 (RDMA Write, 다양한 메시지 크기)
ib_write_bw -d mlx5_0 -s 65536 -n 10000 --report_gbits
# 지연시간 측정
ib_write_lat -d mlx5_0 -s 2 -n 100000
# Send/Recv 대역폭 (양방향)
ib_send_bw -d mlx5_0 -s 4096 -n 5000 --bidirectional
# 다중 QP 성능
ib_write_bw -d mlx5_0 -q 8 -s 65536 -n 5000
# GPU 메모리 대역폭 (GPUDirect 검증)
ib_write_bw -d mlx5_0 --use_cuda=0 -s 1048576 -n 5000
핵심 메트릭 해석
| 메트릭 | 의미 | 정상 범위 (NDR 400G) | 이상 시 확인 |
|---|---|---|---|
| BW peak | 최대 대역폭 (Gb/s) | ~380 Gb/s (4X NDR) | PCIe 링크 폭, NUMA, path_mtu |
| BW average | 평균 대역폭 | peak의 95%+ | 혼잡, 큐 깊이, signaled 비율 |
| t_typical (lat) | 중간값 지연시간 | ~0.6 us (2B msg) | polling vs event, NUMA, 인터럽트 affinity |
| t_99 | 99th percentile 지연 | typical의 2~3배 이내 | 컨텍스트 스위칭(Context Switching), IRQ, 전력 관리 (C-state) |
| MsgRate | 초당 메시지 수 | ~100M msg/s (소형) | doorbell batching, CQE 압축 |
tuned-adm profile latency-performance 또는
echo 0 > /dev/cpu_dma_latency로 깊은 C-state를 비활성화한 뒤 측정하세요.
참고자료
표준 및 사양
- InfiniBand Architecture Specification — IBTA 공식 사양서
- InfiniBand Trade Association (IBTA) — 공식 표준 기관
- RFC 5040: RDMA Protocol Verbs — iWARP RDMA 프로토콜 사양
- RFC 4391: Transmission of IP Datagrams over InfiniBand — IPoIB 표준
- RFC 7306: Remote Direct Memory Access (RDMA) Protocol Extensions — RDMA 확장
커널 공식 문서
- InfiniBand 커널 문서 — RDMA 서브시스템 개요
- User Verbs (uverbs) — 유저스페이스 RDMA API
- InfiniBand sysfs 인터페이스 — 포트/디바이스 상태 조회
- SoftRoCE (rxe) 드라이버 — 소프트웨어 RoCE 에뮬레이션
- mlx5 드라이버 문서 — ConnectX HCA 드라이버
라이브러리 및 도구
- rdma-core (GitHub) — libibverbs, librdmacm, rdma-ndd 등 유저스페이스 라이브러리
- perftest — ib_read_bw, ib_write_lat 등 RDMA 벤치마크 도구
- OpenSM — InfiniBand Subnet Manager (SM)
- OpenFabrics Alliance (OFA) — OFED 스택 및 커뮤니티
- libfabric (OFI) — 고수준 패브릭 통신 라이브러리
- UCX (Unified Communication X) — MPI/RDMA 통합 프레임워크
주요 참고 글
- Redesigning the RDMA subsystem (LWN) — 커널 RDMA 서브시스템 리팩터링
- Shared RDMA memory regions (LWN) — 공유 MR 설계
- NVIDIA RDMA Core Documentation — MLNX_OFED RDMA 가이드
- UFM (Unified Fabric Manager) — InfiniBand 패브릭 관리 도구
- GPUDirect RDMA Whitepaper — GPU↔HCA 직접 전송
벤치마크 및 튜닝
- NVIDIA: 성능 튜닝 가이드 — IRQ affinity, 링 크기, MTU 최적화
- MLNX_OFED 성능 튜닝 — 어댑터별 최적 설정
커널 소스 경로
drivers/infiniband/core/— RDMA 코어 (verbs, cma, uverbs)drivers/infiniband/hw/mlx5/— mlx5 IB 드라이버 (ConnectX)drivers/infiniband/sw/rxe/— SoftRoCE (RXE) 드라이버drivers/infiniband/sw/siw/— Soft-iWARP 드라이버drivers/infiniband/ulp/ipoib/— IPoIB (IP over InfiniBand)drivers/infiniband/ulp/srp/— SRP (SCSI RDMA Protocol)drivers/infiniband/ulp/iser/— iSER (iSCSI Extensions for RDMA)include/rdma/— RDMA UAPI 및 내부 헤더include/uapi/rdma/— RDMA 유저스페이스 ABI 정의
관련 문서
이 주제와 관련된 다른 문서를 더 깊이 이해하고 싶다면 다음을 참고하세요.