InfiniBand / RDMA

InfiniBand 서브넷 아키텍처, RDMA 핵심 개념(QP, CQ, MR), 커널 Verbs API, 사용자 공간(User Space) 라이브러리(libibverbs, librdmacm), IPoIB, 상위 프로토콜(SRP, iSER, NVMe-oF RDMA), GPUDirect RDMA, 성능 최적화 및 트러블슈팅 종합 가이드. 커널 내부 데이터 경로, 핵심 자료구조/API, 운영 환경 튜닝 포인트와 장애 디버깅(Debugging) 절차까지 실무 관점으로 다룹니다.

전제 조건: PCI/PCIe, DMA, NUMA 문서를 먼저 읽으세요. InfiniBand 성능 문제의 상당수는 프로토콜보다도 PCIe 대역폭(Bandwidth), 메모리 핀닝, NUMA 배치에서 먼저 발생하므로 하드웨어 경로를 먼저 고정해야 합니다.
일상 비유: 이 개념은 자동 창고의 전용 컨베이어 + 출입증과 비슷합니다. 버퍼(MR)를 미리 등록해 두고 출입증(rkey)을 받은 상대만 직접 물건을 내려놓을 수 있다는 점이 RDMA Write/Read 모델과 닮아 있습니다.

핵심 요약

  • HCA — RDMA 엔진, DMA, QP/CQ를 가진 InfiniBand NIC입니다.
  • QP — 송신/수신 작업이 실제로 적재되는 하드웨어 큐 쌍입니다.
  • CQ — 완료 이벤트를 회수하는 큐이며 지연(Latency)과 CPU 사용률을 좌우합니다.
  • MR / rkey — 원격 접근 가능한 메모리와 그 접근 권한입니다.
  • SM / SA — 패브릭에 LID와 경로를 배포하고 파티션 정책을 관리합니다.

단계별 이해

  1. 패브릭 준비
    포트가 Active이고 SM이 살아 있으며 LID/GID/P_Key가 정상 배포됐는지 확인합니다.
  2. 리소스 생성
    프로세스(Process)는 PD, MR, CQ, QP를 만들고 버퍼를 등록합니다.
  3. 경로 확정
    QPN/LID/GID/PSN을 교환하거나 librdmacm으로 경로를 해석해 QP를 RTS까지 올립니다.
  4. 데이터 전송
    WR을 post하고 CQ를 poll하여 Send, RDMA Write/Read, Atomic을 실행합니다.
  5. 스케일링
    SRQ/XRC/DC, NUMA 배치, AR/QoS, GPU 메모리 연동으로 대규모 성능을 끌어올립니다.
관련 표준: InfiniBand Architecture Specification, RDMA Consortium — 고속 네트워킹 하드웨어 및 프로토콜 표준입니다. 2025년 7월 31일에는 IBTA가 Volume 1 / Volume 2 Release 2.0을 공개했고, 공개 로드맵은 3.2 Tb/s급 링크까지 확장 방향을 제시하고 있습니다. 종합 목록은 참고자료 — 표준 & 규격 섹션을 참고하세요.

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 비교

특성InfiniBandEthernet (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 네트워크 호환, 높은 지연
RDMA 핵심 가치: RDMA는 세 가지 핵심 기술을 결합합니다: (1) 커널 바이패스 — 데이터 경로에서 커널 개입 없이 사용자 공간에서 직접 HW 큐에 접근, (2) 제로카피 — 애플리케이션 메모리와 NIC 간 중간 버퍼 복사 없이 직접 DMA 전송, (3) 프로토콜 오프로드 — 전송 프로토콜 처리(세그멘테이션, 신뢰성, 흐름 제어)를 HCA 하드웨어가 수행. 이를 통해 CPU 사용률을 극적으로 낮추면서 높은 처리량(Throughput)과 낮은 지연시간을 동시에 달성합니다.

InfiniBand 서브넷 아키텍처

서브넷 구성 요소

InfiniBand 서브넷은 다음 구성 요소로 이루어집니다:

물리 계층 및 속도

세대lane 신호 속도대표 포트 속도주요 링크 폭비고
SDR2.5 Gbaud4X 기준 8 Gb/s1X / 4X / 12X초기 세대, 8b/10b
DDR5 Gbaud4X 기준 16 Gb/s1X / 4X / 12X8b/10b
QDR10 Gbaud4X 기준 32 Gb/s1X / 4X / 12X오랫동안 HPC 표준
FDR14.0625 Gbaud4X 기준 54.54 Gb/s4X / 10X64b/66b 도입
EDR25 Gbaud4X 기준 100 Gb/s1X / 2X / 4X현대 세대의 출발점
HDR50 GbaudHDR100(2X), HDR200(4X)2X / 4XPAM4
NDR100 GbaudNDR200(2X), NDR400(4X)2X / 4XPAM4
XDR200 GbaudXDR800(4X), 스위치 간 1.6 Tb/s급 링크 지원4X / 8X최신 공개 세대
속도 표 해석: 최신 실배치 패브릭은 대체로 4X 링크를 중심으로 설계되며, HDR/NDR 세대에는 2X 포트(HDR100, NDR200)도 흔합니다. 과거 문헌에 자주 보이는 12X 표기는 주로 구세대 백플레인/스위치 문맥이며, 최신 패브릭 설계에서는 4X와 8X를 우선적으로 보는 편이 실무에 맞습니다.

주소 체계

InfiniBand는 단순히 "빠른 NIC"가 아니라, 패브릭 수준에서 경로와 서비스 클래스를 제어하는 링크 계층을 내장합니다. 애플리케이션은 QP를 만들고 WR을 post하지만, 실제 패킷은 SA가 돌려준 Path Record와 SM이 배포한 SL/VL 정책에 따라 흐릅니다.

실무 포인트: 네트워크 성능이 이상할 때 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, 멀티캐스트 그룹, 파티션 정보를 질의합니다.

제어 평면과 데이터 평면 분리 Host A Application / MPI / Storage libibverbs + librdmacm Local HCA QP0/QP1 + RC/UD QP uverbs / umad / rdma_cm opensm / SA LID 할당, Path Record, P_Key, 라우팅 IB Switch / Fabric LFT, SL→VL, Credit-based FC Adaptive Routing / Partition 정책 Host B Application / MPI / Storage libibverbs + librdmacm Remote HCA QP0/QP1 + RC/UD QP CQ / MR / Doorbell SA 조회 / rdma_resolve_route / 파티션 정보 SMP / VL 기반 정책 배포 실제 데이터 경로: RC / UD / XRC / DC QP InfiniBand Subnet Host A HCA (Port 1) QP / CQ / MR Host B HCA (Port 1) QP / CQ / MR IB Switch LID-based Forwarding Subnet Manager opensm / Switch SM LID 할당 / 경로 계산 IB Router GID-based L3 Routing SMP (관리)

핵심 자료구조 및 개념

Queue Pair (QP)

QP는 RDMA 통신의 기본 단위로, Send Queue (SQ)Receive Queue (RQ) 한 쌍으로 구성됩니다. 각 QP는 고유한 QP Number (QPN)를 가지며, 원격 QP와 1:1(RC), 1:N(UD) 등의 연결 관계를 맺습니다.

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) 테이블이 생성됩니다.

고급 메모리 등록: ODP, MW, dma-buf

실무에서는 단순한 ibv_reg_mr()만으로는 부족한 경우가 많습니다. 대용량 메모리, GPU 메모리, 고빈도 권한 부여/회수 패턴을 처리하려면 메모리 등록 전략 자체를 따로 설계해야 합니다.

보안 관점: rkey는 사실상 "원격 DMA 접근 토큰"입니다. 데이터 버퍼 주소와 rkey를 애플리케이션 프로토콜에 평문으로 흘려보내면 그 자체가 capability 누출이 되므로, 제어 채널과 데이터 채널을 분리하고 무효화(Invalidation) 시점을 설계해야 합니다.

Protection Domain (PD) / Address Handle (AH)

Work Request (WR) / Work Completion (WC)

QP 상태 머신

QP는 엄격한 상태 전이 순서를 따라야 합니다:

RESET INIT RTR RTS SQD ERROR ibv_modify_qp P_Key, Port Remote QPN Drain SQ Resume 에러 발생 시 Reset 데이터 전송 가능 수신 가능
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을 반환합니다.
MR 등록 비용과 ODP: 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)

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, XRC는 비교적 표준적인 축에 속하지만, DC(DCT/DCI), UMR, CQE compression, DevX 같은 기능은 주로 mlx5 provider 확장에 걸쳐 있습니다. 성능은 좋아도 애플리케이션 이식성은 낮아질 수 있습니다.
Host A Application Buffer (MR) SQ RQ CQ HCA (RDMA Engine) IB Fabric (Switch / Router) Credit-based Flow Control Host B Application Buffer (MR) SQ RQ CQ HCA (RDMA Engine) RDMA Write (Zero-Copy) DMA Read DMA Write RDMA Read (reverse direction)
RDMA Write vs Send 선택 기준: RDMA Write는 수신 측 CPU 개입이 없어 처리량이 높고 지연이 낮지만, 수신 측에 데이터 도착을 알리려면 별도 메커니즘(polling, RDMA Write with Immediate)이 필요합니다. Send/Recv는 수신 측이 데이터 도착을 즉시 인지할 수 있어 제어 메시지, 작은 RPC에 적합합니다. 대용량 데이터 전송에는 RDMA Write, 제어/시그널(Signal)링에는 Send를 사용하는 하이브리드 패턴이 일반적입니다.

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 구조 레이아웃

mlx5 Send WQE 구조 offset 0 +16 +32 +48 +64 +80 +96 +112 Control Segment (mlx5_wqe_ctrl_seg) — 16 bytes (1 DS) opmod_idx_opcode | qpn_ds | signature | fm_ce_se | imm Ethernet Segment (mlx5_wqe_eth_seg) — 16+ bytes mss | swp_flags | inline_hdr_sz | inline_hdr_start[] RDMA Segment (mlx5_wqe_raddr_seg) — 16 bytes (1 DS) raddr (64-bit remote address) | rkey (32-bit) | reserved Data Segment 0 (mlx5_wqe_data_seg) — 16 bytes (1 DS) byte_count | lkey | addr (64-bit virtual address) Data Segment 1 — 16 bytes (1 DS) byte_count | lkey | addr ... 추가 Data Segment (SGE 수에 따라) ... [대안] Inline Data Segment byte_count | inline_data[] (WQE에 직접 패킷 데이터 삽입) NOP 패딩 (64-byte BB 경계 정렬용) 1 DS = 16 bytes 1 BB = 64 bytes (4 DS) WQE 크기 단위 Control Segment (필수) Ethernet Segment (ETH용) RDMA Segment (RDMA 동작용) Data Segment (SGE 포인터) Inline Data (선택적) DS = Doubleword Segment (16B) BB = Basic Block (64B) qpn_ds 필드에 WQE 크기 (DS 수) 저장 WQE는 BB 경계에 정렬 필수 * Ethernet Segment와 RDMA Segment는 동작 유형에 따라 선택적으로 포함됩니다 * Send WQE = Ctrl + [ETH/RDMA] + Data Segments, Receive WQE = Data Segments만

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 등 동작별 용도
Fence Mode 상세: 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의 lkeyaddr를 사용하여 등록된 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 역할을 합니다.

Send Queue 링 버퍼 관리 WQE 0 WQE 1 WQE 2 HW 처리중 WQE 3 제출됨 WQE 4 제출됨 WQE 5 비어있음 WQE 6 비어있음 WQE 7 비어있음 CI (Consumer Index) HCA가 다음에 처리할 위치 PI (Producer Index) 드라이버가 다음에 기록할 위치 Wrap-around (순환) 핵심 자료구조 mlx5_wq_cyc (순환 워크 큐) buf : WQE 버퍼 시작 주소 (DMA 매핑) frag_buf : 페이지 조각 관리 구조 sz_m1 : 슬롯 수 - 1 (마스크, 2의 거듭제곱) log_stride : 각 WQE 슬롯의 크기 (log2) 인덱스 계산: real_ix = (pc & wq->sz_m1) << wq->log_stride wqe_ptr = wq->buf + real_ix sz_m1 마스크로 자동 wrap-around 처리 mlx5e_txqsq (Ethernet Send Queue) pc : Producer Counter (SW가 증가) cc : Consumer Counter (CQE로 갱신) sqn : Send Queue Number (HW 식별자) mkey_be : MR lkey (빅엔디안) wq : mlx5_wq_cyc 구조체 가용 슬롯 확인: avail = sq->wq.sz_m1 - (sq->pc - sq->cc) pc - cc = 제출 후 미완료 WQE 수
/* 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);
}
WQE Wrap-around 주의: WQE가 링 버퍼 끝에서 시작하여 처음으로 걸쳐질(wrapping) 수 있습니다. mlx5 드라이버는 남은 공간이 WQE 크기보다 작으면 MLX5_OPCODE_NOP WQE로 패딩하여 분할을 방지합니다. 이는 HCA가 연속 메모리에서 WQE를 읽는 것을 보장합니다.

WQE 포스팅 메커니즘: Doorbell과 BlueFlame

드라이버가 WQE를 SQ 버퍼에 기록한 후, HCA에 새 WQE가 있음을 알려야 합니다. 이를 Doorbell이라 하며, mlx5에서는 두 가지 방식을 지원합니다.

WQE 포스팅: 일반 Doorbell vs BlueFlame 일반 Doorbell (Non-Cached UAR) CPU: WQE를 SQ 버퍼 (시스템 메모리)에 기록 CPU: Doorbell Record (DB)에 PI 기록 CPU: UAR에 MMIO 쓰기 (doorbell ring) HCA: PCIe DMA Read 로 WQE 가져옴 HCA: WQE 파싱 & 패킷 전송 PCIe 왕복 2회: 1. MMIO Write (doorbell) 2. DMA Read (WQE fetch) 지연: ~1.0 usec (PCIe RTT x2) BlueFlame (Write-Combining UAR) CPU: WQE를 SQ 버퍼에 기록 + BlueFlame UAR에도 WQE 데이터 MMIO 기록 HCA: WQE를 doorbell과 함께 직접 수신 HCA: 즉시 파싱 & 패킷 전송 PCIe 왕복 1회: 1. MMIO Write (WQE + doorbell 동시) DMA Read 불필요! 지연: ~0.5 usec (PCIe RTT x1) 요구 사항: Write-Combining 매핑된 UAR 영역 WQE 크기 64B (1 BB) 이내 권장
/* 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);
}
UAR (User Access Region): UAR는 HCA의 MMIO 공간에 매핑된 4KB 페이지입니다. 사용자 공간에서 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를 폴링하여 전송 완료를 확인하고 리소스를 회수합니다.

WQE → CQE 완료 흐름 Send Queue (SQ) WQE 0 WQE 1 WQE 2 ... 완료됨 완료됨 처리중 HCA Engine WQE 파싱 패킷 생성/전송 Completion Queue (CQ) CQE 0 WQE 0 완료 CQE 1 WQE 1 완료 ... wqe_counter: 완료된 WQE 인덱스 포함 DMA Read DMA Write 완료 처리 경로 (NAPI Poll) ibv_poll_cq() 또는 NAPI poll 콜백 CQE에서 wqe_counter 확인 → CC 갱신 DMA unmap + SKB 해제 (TX 완료) SQ 슬롯 회수 → netif_wake_queue() CQ DB 갱신 (arm 또는 poll)
/* 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 Compression (mlx5 확장): ConnectX-5 이상에서는 CQE 압축을 지원합니다. 연속된 유사한 완료 이벤트를 하나의 CQE로 압축하여 PCIe 대역폭을 절약합니다. 타임스탬프 차이(cqe_ts_to_ns)만 기록하는 미니 CQE(8바이트) 배열로 압축되며, 드라이버가 압축을 풀어 개별 완료를 처리합니다. 이 기능은 소형 패킷이 대량으로 들어오는 워크로드에서 CQ 메모리 대역폭을 50% 이상 절감합니다.

WQE 성능 최적화 기법

1. Inline Data

패킷 데이터(또는 일부)를 WQE 안에 직접 삽입하는 기법입니다. HCA가 별도의 DMA Read 없이 WQE 자체에서 데이터를 읽을 수 있어 소형 패킷의 지연시간을 줄입니다.

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 처리량 증가
Signaled vs Unsignaled Completion: Control Segment의 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));
}
Receive WQE 고갈(Starvation) 주의: RQ에 Receive WQE가 부족하면 수신 패킷이 드롭됩니다. mlx5에서는 delay_drop 기능으로 WQE 고갈 시 일정 시간 동안 패킷을 보류할 수 있지만, 근본적으로는 RQ 크기를 충분히 설정하고 NAPI 폴링이 적시에 WQE를 재게시해야 합니다. ethtool -Srx_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 세그먼트 구성 RC (Reliable Connected) UD (Unreliable Datagram) Raw Ethernet Control Segment RDMA Segment [Atomic Segment] Data Segment[] RDMA Read/Write/Atomic 지원 재전송 보장, ACK 기반 Control Segment UD/AV Segment dest QPN, Q_Key, AH Data Segment[] 1:N 통신, MTU 이내 RDMA 불가, Send 전용 Control Segment Ethernet Segment mss, cs_flags, inline_hdr Data Segment[] TSO/체크섬 오프로드 mlx5e NIC 드라이버 사용 Receive WQE 비교 Data Segment[] (SGE) RC/UC: 페이로드만 수신 GRH (40 bytes) Data Segment[] UD: 버퍼 앞 40B는 GRH 영역 Data Segment (Strided) Strided RQ: 1 WQE = N stride Control (필수) RDMA/Atomic UD AV / ETH Data (SGE) 선택적 * DC(Dynamically Connected): RC와 유사하나 AV Segment 추가. XRC: RC + XRCSRQ Segment 추가
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];
};
UD Receive WQE의 GRH: UD QP에서 수신된 패킷의 Receive WQE 버퍼 앞쪽 40바이트에는 GRH(Global Routing Header)가 포함됩니다. 응용 프로그램이 버퍼를 읽을 때 실제 페이로드 시작은 buf + 40입니다. ibv_wcwc_flagsIBV_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의 메모리가 필요합니다.

개별 RQ: QP당 독립 링 버퍼 QP 0 RQ: QP 1 RQ: QP N RQ: 메모리: N × depth × WQE 크기 N=10,000 QP × 256 WQE × 64B = 160 MB SRQ: 공유 Linked List QP 0 QP 1 QP N WQE 0 next→3 WQE 3 next→7 WQE 7 next→… head ↑ tail ↑ next_wqe_index로 연결된 free list 메모리: 1 × depth × WQE 크기 1 × 4096 WQE × 64B = 256 KB (625배 절감) SRQ Limit Warning 메커니즘 SRQ WQE 사용 (head 이동) 잔여 WQE ≤ srq_limit 임계값 HCA 비동기 이벤트: SRQ_LIMIT_REACHED 드라이버 핸들러: WQE 대량 재게시 SRQ 보충됨 SRQ WQE 내부 구조 Next Segment (16B) Data Segment 0 (16B) Data Segment 1 (16B) ... next_wqe_index + signature byte_count, lkey, addr (수신 버퍼 포인터)
/* 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);
}
비교 항목개별 RQSRQ
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 Warning: SRQ에서 사용 가능한 WQE가 사전 설정한 임계값 이하로 떨어지면, HCA는 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 메타데이터를 변경합니다.

UMR WQE 구조와 Indirect MR UMR WQE 세그먼트 구성 Control Segment (opcode=0x25) UMR Control Segment mkey Context (mkc) KLM 0 (bcount, key, va) KLM 1 (bcount, key, va) ... KLM N ... mkey_mask, xlt_octowords access flags, 크기, 상태 Indirect MR: KLM으로 비연속 매핑 Parent mkey (Indirect MR) RDMA에서 이 mkey 하나로 접근 KLM 0 4KB, page_A KLM 1 4KB, page_C KLM 2 8KB, page_F 물리 page A 물리 page C 물리 page F-G 비연속 물리 페이지를 가상 연속 공간으로 매핑 UMR vs 일반 MR 등록 경로 비교 ibv_reg_mr() 시스템 콜 (ioctl) 커널 명령 인터페이스 ~수십 μs (제어 경로) UMR WQE 작성 SQ 제출 + Doorbell ~수 μs (데이터 경로, 커널 우회)
/* 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/OpmodAtomic 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) 구현 가능
Masked Atomic의 활용: Masked CAS/FAA는 잠금 없는(lock-free) 분산 데이터 구조에서 핵심적입니다. 예를 들어, 64비트 워드를 8비트 × 8개의 독립 카운터로 사용하면, 하나의 RDMA Atomic 연산으로 특정 카운터만 원자적으로 증가시킬 수 있습니다. 이는 분산 해시 테이블, 동시성 비트맵, 원격 스핀락 등에 활용됩니다.

WQE 메모리 순서 보장과 배리어

WQE는 CPU와 HCA 사이의 공유 메모리 인터페이스이므로, 메모리 순서(Memory Ordering)가 정확히 보장되어야 합니다. x86은 TSO(Total Store Ordering)로 비교적 강한 순서를 제공하지만, ARM64/RISC-V 등 약한 순서 모델에서는 명시적 배리어가 필수입니다.

WQE 포스팅 — 메모리 배리어 위치와 역할 CPU (드라이버) ① WQE 세그먼트 기록 Ctrl, Data, RDMA → 시스템 메모리 (store) wmb() — WQE 완료 보장 ② Doorbell Record에 PI 기록 *wq->db = cpu_to_be32(sq->pc) wmb() — DB 완료 보장 ③ UAR MMIO 쓰기 (Doorbell Ring) mlx5_write64(ctrl, uar_map) ④ CQE 읽기 시: dma_rmb() HCA (하드웨어) DB Record DMA Read 새 PI 확인 → WQE 존재 인지 UAR Doorbell 수신 즉시 WQE DMA Read 시작 CQE DMA Write → 시스템 메모리 누락 시: HCA가 불완전한 WQE 읽음 → LOCAL_LENGTH_ERR syndrome 누락 시: HCA가 이전 PI 참조 → WQE 미인식, 전송 누락
배리어 위치리눅스 커널 APIx86ARM64목적
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 필드가 에러 원인을 나타냅니다.

WQE 에러 발생 시 QP 상태 전이와 복구 RTS 정상 동작 Error CQE SQE / ERR 에러 상태 미처리 WQE 전부 WR_FLUSH_ERR CQE 생성 복구 RESET QP 초기화 INIT 속성 설정 RTR 수신 준비 → RTS (송수신 준비 완료) 에러 유형 분류 로컬 에러 LOCAL_LENGTH_ERR (0x01) LOCAL_QP_OP_ERR (0x02) LOCAL_PROT_ERR (0x04) 원격 에러 REMOTE_INV_REQ_ERR (0x12) REMOTE_ACCESS_ERR (0x13) BAD_RESP_ERR (0x10) 재시도 실패 TRANSPORT_RETRY (0x15) RNR_RETRY (0x16) 네트워크/원격 RQ 문제 부수 효과 WR_FLUSH_ERR (0x05) 에러 이후 남은 WQE의 자동 flush 결과
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     — 상세 로그 활성화
 */
QP Error 상태와 WQE Flush: QP가 Error 상태로 전이되면 SQ에 남아 있는 모든 미처리 WQE에 대해 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 지역성이 모두 중요합니다.

WQE 메모리 레이아웃: Hot/Cold 분리와 캐시 정렬 SQ 버퍼 (DMA coherent 메모리) WQE 0 (BB 0) Ctrl+Data = 64B = 1 캐시 라인 WQE 1 (BB 1-2) Ctrl+ETH+Data = 2 캐시 라인 WQE 2 (BB 3) NOP 패딩 = 1 캐시 라인 ... offset 0x000 0x040 0x0C0 wqe_info[] (CPU 전용 메타데이터) info[0] skb, num_dma num_bytes info[1] skb, num_dma num_bytes info[2] skb, num_dma ... 분리 HCA DMA 영역 (coherent) CPU L1 캐시에 상주 HCA DMA 접근 없음 NUMA 지역성과 DMA 매핑 NUMA Node 0 (HCA 연결 노드) SQ 버퍼 (DMA) wqe_info[] DB Record HCA가 이 NUMA 노드의 메모리에 로컬 DMA 접근 지연: ~80ns (로컬) NUMA Node 1 (원격 노드) 여기에 할당하면 성능 저하! HCA → QPI/UPI → 원격 메모리 컨트롤러 지연: ~150ns (원격, ~2배 느림) QPI/UPI
최적화 영역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 크기와 SQ 용량의 트레이드오프: SQ는 고정 크기 메모리이므로, WQE가 크면(SGE 많음, Inline 많음) 수용 가능한 WQE 수가 줄어듭니다. 예를 들어, SQ가 64KB이고 WQE가 4 BB(256B)라면 256개의 WQE만 수용 가능합니다. 동일 SQ에서 WQE가 1 BB(64B)라면 1024개까지 수용 가능합니다. 파이프라인 깊이(outstanding request 수)가 중요한 워크로드에서는 SGE 수를 최소화하여 더 많은 WQE를 SQ에 넣는 것이 유리합니다.

WQE 보안 관점

WQE는 하드웨어에 직접 명령을 전달하므로, 보안 경계에서 중요한 역할을 합니다. RDMA의 커널 우회(kernel bypass) 특성상 사용자 공간에서 WQE를 직접 구성하므로, 하드웨어 수준의 보호 메커니즘이 필수적입니다.

WQE 보안 검증 경계 사용자 공간 (프로세스 A) WQE 구성 UAR mmap (전용 4KB 페이지) SQ 버퍼 (DMA 매핑 메모리) lkey, addr, rkey 포함 ibv_post_send() (libmlx5) 커널 우회: 시스템 콜 없이 WQE → SQ → Doorbell (MMIO) 프로세스 B의 UAR/SQ 접근 불가 별도 mmap → 가상주소 격리 다른 QPN의 doorbell 불가 HCA 하드웨어 검증 엔진 ① QPN 소유권 검증 UAR → QPN 매핑 확인 ② lkey 검증 MR 테이블: VA/길이/권한 매칭 ③ rkey 검증 (RDMA) 원격 MR 접근 권한 + 범위 ④ PD 격리 QP와 MR이 같은 PD 소속 통과 → 패킷 전송 실패 → Error CQE 생성 rkey 보안 상위 비트: 랜덤 생성 추측 공격 실질적 불가 Send w/ Invalidate로 회수 WQE DMA
보안 메커니즘보호 대상구현 위치
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 마스크로 자동 범위 제한. 오버플로우 불가
RDMA 보안 주의사항: RDMA Write/Read는 원격 CPU를 거치지 않고 메모리에 직접 접근하므로, rkey가 노출되면 원격 메모리를 무단으로 읽거나 쓸 수 있습니다. 실제 운영 환경에서는 (1) rkey의 수명을 최소화하고 (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/
IB Verbs 추상화와 WQE: 사용자 공간의 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 사용 방식을 정리합니다.

NVMe-oF RDMA: 단일 I/O의 WQE 흐름 호스트 (Initiator) UMR WQE SGL 페이지들을 Fast Registration MR로 등록 SEND WQE NVMe 커맨드 (64B) + rkey/raddr 전달 (Inline + BlueFlame) CQE 대기: 타겟이 RDMA로 데이터 전송 중... (Read: 호스트 MR에서 읽기 / Write: 호스트 MR에 쓰기) Recv WQE (응답 수신) SEND_INVAL 수신 → rkey 자동 무효화 + CQE 생성 완료 처리 MR 이미 무효화됨 → I/O 완료 콜백 호출 타겟 (Controller) Recv WQE: NVMe 커맨드 수신 CQE에서 호스트의 rkey/raddr 추출 RDMA Read/Write WQE 호스트 MR에 직접 데이터 DMA (zero-copy) SEND_INVAL WQE: 응답 + rkey 무효화 NVMe 응답 전송 + 호스트 MR의 rkey를 원격 무효화 SEND RDMA SEND_INVAL 전체 I/O에 4~5개 WQE 사용. UMR + Inline + BlueFlame 최적화로 지연시간 최소화
통신 패턴송신 측 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 관련 참고 자료

표준 및 공식 사양

리눅스 커널 소스 코드

사용자 공간 라이브러리 (rdma-core)

기술 문서 및 블로그

학술 논문 및 기술 보고서

도구 및 디버깅

리눅스 커널 InfiniBand 서브시스템

drivers/infiniband/ 소스 구조

drivers/infiniband/ 구조 drivers/infiniband/ core/ (ib_core) device.c, verbs.c, cq.c, mr_pool.c uverbs_main/cmd, umad_main sa_query.c, cm.c, mad.c addr.c, multicast.c, rdma_core.c Verbs API, uverbs, MAD/CM 공통 계층 hw/ (HCA 벤더 드라이버) mlx4/, mlx5/, hfi1/ bnxt_re/, irdma/ rxe/, siw/ (software RDMA) 실제 NIC/HCA 하드웨어 연동 ulp/ (Upper Layer Protocol) ipoib/, srp/, iser/, isert/ rtrs/ 등 RDMA 기반 상위 프로토콜 네트워크/스토리지 사용자 기능 제공 데이터/제어 흐름 1) 벤더 드라이버(hw/)가 `ib_register_device()`로 디바이스 등록 2) core/가 Verbs 객체(QP/CQ/MR)와 uverbs/umad 인터페이스 제공 3) 사용자 공간(libibverbs, opensm)은 `/dev/infiniband/*`를 통해 접근 4) ulp/가 IPoIB, iSER 등 기능을 조합해 서비스 제공 5) 공통 추상화 덕분에 IB/RoCE/iWARP를 동일한 Verbs 모델로 다룸

ib_core 모듈

ib_core는 InfiniBand 서브시스템의 핵심 프레임워크입니다. HCA 드라이버가 ib_register_device()로 디바이스를 등록하면, ib_core가 sysfs 항목 생성, 이벤트 알림, Verbs API 디스패치(Dispatch) 등을 처리합니다. 모든 RDMA 디바이스(IB, RoCE, iWARP)가 동일한 Verbs 추상화를 통해 접근됩니다.

ib_uverbs / ib_umad

대표 모듈과 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 수 등) 조회
PDib_alloc_pd()Protection Domain 생성
ib_dealloc_pd()PD 해제
QPib_create_qp()QP 생성 (타입, CQ, SRQ 지정)
ib_modify_qp()QP 상태 전이 (RESET→INIT→RTR→RTS)
ib_destroy_qp()QP 파괴
CQib_alloc_cq() / ib_create_cq()CQ 생성
ib_poll_cq()CQ에서 완료 이벤트 폴링(Polling)
MRib_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 함수:

librdmacm (rdma_* API)

librdmacm은 RDMA Connection Manager 라이브러리로, TCP 소켓과 유사한 연결 설정/해제 인터페이스를 제공합니다. QP 상태 전이를 자동으로 처리하므로 개발이 훨씬 간편합니다.

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
사용자 공간 진입점(Entry Point) 정리:
  • /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 확장을 선택합니다.

애플리케이션이 실제로 타는 사용자 공간 스택 Application MPI / NCCL / RPC / Storage Collective / Tag matching Open MPI / MPICH UCX PML / MTL / Collective 계층 UCX UCP / UCT / multi-rail / GPU memory libfabric / OFI rc_x / dc / ud_x UCX transport alias verbs;ofi_rxm RDM endpoint layering Verbs mlx5dv
계층역할InfiniBand 문맥에서 중요한 점
UCX 전송 선택, 프로토콜 분할, multi-rail, GPU 메모리 연동 UCX_TLSrc, 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_rxesiw를 사용해 기존 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
중요: rxesiw는 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 매핑, 관리 디바이스 정보를 꽤 풍부하게 노출합니다.

사용자 공간, 문자 디바이스, sysfs의 연결 Applications MPI / Storage / RPC perf tools / opensm /dev/infiniband/uverbs0 PD / CQ / QP / MR 생성 /dev/infiniband/rdma_cm 주소/경로 해석, 연결 이벤트 /dev/infiniband/umad0 MAD / SM / SA / 진단 도구 /sys/class/infiniband node_guid, fw_ver ports/*/state, lid, rate /sys/class/infiniband_mad umadN ↔ ibdev/port 매핑 /sys/class/infiniband_verbs uverbsN ↔ ABI / ibdev 매핑 HCA mlx5_0 Port 1
경로의미언제 보는가
/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
최신 커널 방향: 2026년 3월 5일자 최신 커널 문서(next 계열)에는 사용자 capability(UCAP) 문서가 추가되어, 일부 펌웨어 기능을 세분화된 문자 디바이스 권한으로 위임하는 방향이 설명됩니다. 아직 모든 배포판에서 일반화된 기능은 아니지만, DevX 같은 저수준 접근을 더 안전하게 노출하려는 흐름으로 이해하면 됩니다.

IPoIB (IP over InfiniBand)

IPoIB는 InfiniBand 패브릭 위에서 기존 IP/TCP/UDP 프로토콜을 투명하게 사용할 수 있게 합니다. 커널의 drivers/infiniband/ulp/ipoib/에 구현되어 있으며, ib0, ib1 등의 네트워크 인터페이스로 나타납니다.

Connected Mode vs Datagram Mode

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    # 디버그 메시지 (선택)
IPoIB Connected Mode MTU: Connected Mode에서 MTU를 65520으로 설정하면 IP 계층 프래그먼테이션 없이 대용량 전송이 가능하여 TCP/IP 성능이 크게 향상됩니다. 다만, ARP/DHCP 등 브로드캐스트가 필요한 프로토콜은 여전히 UD(Datagram) QP를 사용합니다. 현재 모드는 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
운영 팁: 일부 mlx5 스택은 UD 기반의 Enhanced IPoIB를 제공해 RSS/TSS, multi-queue, interrupt moderation, stateless offload를 결합합니다. 따라서 "항상 connected mode가 더 빠르다"라고 단정하면 안 되며, 작은 패킷 다중 흐름에서는 강화된 datagram 모드가 더 좋은 균형을 줄 수 있습니다.

상위 프로토콜 (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
NVMe-oF/RDMA 성능: NVMe-oF/RDMA는 NVMe의 멀티큐 모델을 RDMA Send/RDMA Write에 직접 매핑하여 원격 NVMe SSD에 로컬과 유사한 지연시간(~10μs 추가)으로 접근할 수 있습니다. iSCSI 대비 CPU 사용률이 크게 낮고, 4KB random read에서 수백만 IOPS를 달성할 수 있습니다. Block I/O 서브시스템 페이지의 blk-mq 섹션도 참고하세요.

관리 및 진단 도구

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

메모리 핀닝/등록 최적화

Completion Channel vs Polling

QP / CQ 튜닝

GPUDirect RDMA

NVIDIA GPUDirect RDMA는 GPU 메모리를 RDMA MR로 직접 등록하여, CPU 메모리를 거치지 않고 GPU ↔ HCA 간 직접 P2P DMA 전송을 수행합니다. AI/ML 분산 학습에서 GPU 간 gradient 교환 성능을 극적으로 향상시킵니다.

DMA 페이지의 P2P DMA / GPUDirect 섹션도 참고하세요.

메모리 핀닝 주의사항: RDMA MR 등록은 물리 페이지를 핀하여 스왑(Swap) 아웃을 방지합니다. 대량의 MR을 등록하면 시스템 가용 메모리가 급격히 줄어들어 OOM이 발생할 수 있습니다. ulimit -l로 프로세스별 메모리 잠금(Lock) 제한을 확인하고, /etc/security/limits.conf에서 RDMA 사용자의 memlock 값을 적절히 설정해야 합니다. 컨테이너 환경에서는 cgroups의 memory.max도 함께 고려해야 합니다.

격리와 멀티테넌시

InfiniBand는 본질적으로 강한 성능 지향 기술이지만, 클라우드·공유 HPC·AI 플랫폼에서는 누가 어느 HCA 자원을 얼마나 쓰는지, 어느 파티션까지 접근 가능한지를 함께 설계해야 합니다. 단순히 디바이스 파일 권한만 나눠 주는 것으로는 충분하지 않습니다.

패브릭 격리: P_Key, SL, SR-IOV

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 위임

트러블슈팅 및 주의사항

가장 먼저 확인할 것: 이 연결이 정말 네이티브 InfiniBand인지, 아니면 Ethernet 포트의 RoCE인지 구분하세요. 같은 Verbs API를 써도 문제를 보는 순서가 완전히 다릅니다.
  • InfiniBand: SM/SA, LID, P_Key, SL/VL, credit-based flow control, Path Record를 먼저 확인
  • RoCE: PFC, ECN/DCQCN, VLAN/DSCP, lossless 큐, GID/IP 라우팅을 먼저 확인
ibv_devinfordma link에서 link_layer가 무엇인지부터 보세요.

일반적인 트러블슈팅 순서:

  1. ibstat, ibv_devinfo, rdma link으로 포트 상태와 link_layer를 확인합니다.
  2. sminfo, saquery -s, ibnetdiscover로 SM/SA와 fabric discovery가 정상인지 확인합니다.
  3. saquery -p, ibroute, ibtracert로 P_Key와 실제 경로를 점검합니다.
  4. perfquery, ibqueryerrors, ibdiagnet로 포트 카운터와 fabric 오류를 수집합니다.
  5. rdma resource show, dmesg | grep -i rdma로 QP/CQ/MR과 커널 로그를 확인합니다.
  6. 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는 단일 설정값 하나로 안정화되는 기술이 아닙니다. 하드웨어, 패브릭, 메모리, 애플리케이션이 모두 같은 방향으로 맞아야 하므로 배포 전 체크 순서를 명확히 고정하는 것이 중요합니다.

  1. PCIe / 펌웨어 정합성
    HCA가 기대한 링크 폭/속도로 올라왔는지, 펌웨어와 드라이버 조합이 지원 매트릭스 안에 있는지 확인합니다.
  2. 패브릭 활성화
    ibstatActive, sminfo가 정상, LID와 P_Key가 의도한 값으로 배포됐는지 확인합니다.
  3. 단일 흐름 기준선
    ib_write_bw, ib_write_lat, ib_send_bw로 단일 QP/단일 코어 기준선을 먼저 만듭니다.
  4. NUMA / IRQ / poller 배치
    HCA와 같은 NUMA 노드에 메모리와 스레드를 묶고 CQ completion vector, IRQ affinity를 정렬합니다.
  5. 애플리케이션 모델 선택
    제어 메시지는 Send/Recv, 대용량 데이터는 RDMA Write/Read, 대규모 fan-in은 SRQ/XRC/DC로 분리 설계합니다.
  6. 상위 프로토콜 검증
    IPoIB, NFS/RDMA, NVMe-oF/RDMA, GPU 메모리 경로를 각각 별도 기준선으로 확인합니다. 기본 RDMA가 빠르다고 ULP도 자동으로 빠른 것은 아닙니다.
  7. 지속 관측 체계
    perfquery, ibqueryerrors, ibdiagnet, rdma statistic show를 정기 수집에 포함합니다.
  8. 장애 시 격리 순서
    애플리케이션 → Verbs → QP/CQ/MR → 포트 상태 → SM/SA → 스위치 경로 → PCIe/IOMMU 순으로 아래로 내려가며 원인을 좁힙니다.
운영 원칙: 처음부터 multi-rail, SR-IOV, GPU direct, 컨테이너 RDMA, 스토리지 ULP를 한 번에 붙이지 마세요. 반드시 네이티브 RC QP 단일 노드 쌍 기준선을 먼저 확보한 뒤 기능을 하나씩 쌓아야, 성능 회귀와 장애 원인을 추적할 수 있습니다.

2노드 Bring-up 시나리오

새 장비를 랙에 넣었을 때 가장 안전한 방식은, 복잡한 스토리지나 MPI 스택을 바로 올리지 말고 두 노드만 연결한 최소 구성에서 패브릭, Verbs, IPoIB, 성능 기준선을 순서대로 확인하는 것입니다.

  1. 드라이버와 관리 도구 로드
    mlx5_ib, ib_umad, 필요하면 ib_ipoib가 올라왔는지 확인합니다.
  2. SM 확보
    스위치 내장 SM이 없다면 한 노드에서 opensm을 띄우고 다른 노드 포트가 INIT → ACTIVE로 전이되는지 봅니다.
  3. 기본 관측
    ibstat, ibv_devinfo, sminfo, saquery -s로 포트 상태와 SM/SA를 점검합니다.
  4. Verbs 기준선
    ib_write_bw, ib_write_lat, ib_send_bw만으로 HCA-to-HCA 경로를 먼저 검증합니다.
  5. IP 계층 확인
    필요하면 IPoIB를 올리고 ip addr, ping, 애플리케이션 포트 바인딩을 점검합니다.
  6. 애플리케이션 계층 확장
    그 다음에야 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
기준선 해석: 이 단계에서 원하는 대역폭이 나오지 않으면, 원인을 상위 스택에서 찾지 말고 포트 속도, Path MTU, NUMA 배치, CQ signaled 비율, PCIe 링크 폭, 케이블 품질 쪽으로 먼저 내려가야 합니다.

RDMA 데이터 경로 (Zero-copy)

RDMA의 핵심 가치는 커널 바이패스 + 제로카피입니다. 전통적인 TCP/IP 소켓 모델에서는 애플리케이션 버퍼 → 커널 소켓 버퍼 → NIC DMA 버퍼로 최소 2회 복사가 발생하지만, RDMA에서는 사용자 공간 버퍼를 MR로 등록하면 HCA가 해당 물리 페이지에 직접 DMA를 수행합니다.

RDMA Zero-copy 데이터 경로 vs 전통 소켓 경로 전통 TCP/IP 소켓 경로 App Buffer Socket Buffer (커널) NIC DMA Buffer NIC Hardware copy_from_user() memcpy → DMA RDMA Zero-copy 경로 App Buffer (MR 등록) HCA (RDMA Engine) Direct DMA 커널 바이패스 복사 0회 커널 영역 (우회됨)

제로카피 메커니즘 상세

성능 효과: 제로카피 경로는 64KB 이상의 메시지에서 TCP 대비 50~80% CPU 사용률 감소를 보이며, 8B 이하 소형 메시지에서도 지연시간이 1/5~1/10 수준입니다. 단, MR 등록 비용이 높으므로 자주 변경되는 버퍼에는 ODP(On-Demand Paging) 또는 MR 풀링이 필요합니다.

QP 상태 전이 다이어그램

Queue Pair는 명시적인 상태 머신을 따릅니다. QP를 생성하면 RESET 상태에서 시작하며, ibv_modify_qp()를 통해 순서대로 전이시켜야 데이터 전송이 가능합니다. 상태 전이를 건너뛰거나 잘못된 속성을 설정하면 QP가 ERROR 상태로 빠집니다.

QP State Machine (RC/UC) RESET INIT RTR RTS SQD SQE ERROR IBV_QPS_INIT IBV_QPS_RTR IBV_QPS_RTS SQD RTS Send 오류 RESET으로 복귀 모든 상태에서 ERROR로 전이 가능 (치명적 오류 시). ERROR에서는 RESET으로만 복귀 가능.

상태별 필수 속성

전이필수 속성설명
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 CM의 핵심 가치: 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);
QPN/PSN/LID 교환: RDMA CM 없이 직접 QP를 관리할 때는, 양쪽 노드가 out-of-band 채널(TCP 소켓 등)로 qp_num, psn, lid, gid 값을 교환해야 합니다. 이 교환 과정에서 값이 하나라도 잘못되면 QP가 ERROR 상태로 빠지거나 RETRY_EXC_ERR이 발생합니다.

RoCE v2 설정 및 운영

RDMA over Converged Ethernet (RoCE) v2는 UDP/IP 위에서 RDMA를 수행합니다. 네이티브 InfiniBand와 같은 Verbs API를 사용하지만, 손실 처리, 혼잡 제어, 주소 해석 방식이 완전히 다릅니다.

RoCE v2 패킷 구조 Ethernet DA + SA + Type IPv4/IPv6 DSCP + TTL UDP dst: 4791 BTH (IB Transport) QPN + PSN + OpCode Payload RDMA 데이터 ICRC 무결성 14B 20/40B 8B 12B 가변 4B RoCE v1은 IP/UDP 없이 Ethernet 위에 직접 BTH를 실음 → L2 전용, VLAN 내에서만 동작 DSCP: QoS 분류 ECN: 혼잡 알림 (CE 비트)

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 대상 식별 스위치가 우선순위를 구분하지 못함
RoCE v2 vs IB: 네이티브 InfiniBand는 Credit-based Flow Control이 내장되어 PFC/ECN이 필요 없습니다. RoCE v2로 전환할 때 가장 큰 운영 부담은 Lossless 네트워크 구성과 유지입니다. 스위치, NIC, OS 드라이버 설정이 모두 일관되어야 하며, 한 곳이라도 빠지면 패킷 드롭이 발생합니다.

GPUDirect RDMA 아키텍처

AI/ML 분산 학습에서 GPU 간 gradient 교환은 가장 빈번하고 대역폭 집약적인 통신입니다. GPUDirect RDMA는 CPU 메모리를 거치지 않고 GPU VRAM ↔ HCA 간 직접 P2P DMA를 수행하여 복사 오버헤드를 제거하고 GPU-to-GPU 지연시간을 최소화합니다.

GPUDirect RDMA 아키텍처 Node A CPU + DRAM GPU VRAM (Gradient) HCA (RDMA) DMA Engine PCIe Root Complex / Switch P2P DMA Node B CPU + DRAM GPU VRAM (Gradient) HCA (RDMA) DMA Engine PCIe Root Complex / Switch P2P DMA IB / RoCE 전통 경로: GPU→CPU DRAM→HCA→Network→HCA→CPU DRAM→GPU (4회 복사) GPUDirect: GPU→HCA→Network→HCA→GPU (0회 CPU 복사)

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
GPUDirect Storage: GPUDirect RDMA의 확장인 GPUDirect Storage(GDS)는 NVMe 장치에서 GPU VRAM으로 직접 DMA를 수행합니다. AI 학습 데이터 로딩 시 CPU 메모리 복사를 건너뛰어 I/O 파이프라인(Pipeline)의 병목을 제거합니다. NVMe-oF/RDMA와 결합하면 원격 스토리지에서 GPU로 직접 전송이 가능합니다.

SR-IOV와 InfiniBand 가상화

클라우드와 공유 HPC 환경에서는 하나의 물리 HCA를 여러 VM/컨테이너에 분할해야 합니다. InfiniBand SR-IOV는 물리 기능(PF)에서 가상 기능(VF)을 생성하여 각각 독립적인 RDMA 디바이스를 제공합니다.

InfiniBand SR-IOV: PF / VF / vHCA 구조 물리 HCA (ConnectX-7) PF: mlx5_0 — VF 0~15 생성 가능, 각 VF에 독립 QP/CQ/MR 공간 PF (Host) mlx5_0 (전체 제어) VF 0 vHCA (독립 GID) VF 1 vHCA (독립 GID) VF N vHCA (독립 GID) Host OS Hypervisor opensm / 관리 VM 1 VFIO passthrough 독립 RDMA 스택 VM 2 VFIO passthrough 독립 RDMA 스택 Container K8s RDMA device plugin 배치 각 VF는 독립 GID, P_Key, QP 네임스페이스를 가짐. SM은 VF를 별도 HCA 포트로 인식.

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

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-oF/RDMA는 기본적으로 io_queue 수가 CPU 코어 수와 같습니다. HCA와 NVMe 장치가 같은 NUMA 노드에 있어야 최적 성능을 얻을 수 있으며, nvme connect--nr-io-queues 옵션으로 큐 수를 조절할 수 있습니다.

흔한 실수와 안티패턴

RDMA 프로그래밍과 InfiniBand 운영에서 자주 발생하는 실수를 정리합니다. 이러한 패턴을 미리 인지하면 디버깅 시간을 크게 줄일 수 있습니다.

RDMA 프로그래밍 흔한 실수 Top 6 1. QP 상태 전이 누락 INIT→RTR→RTS 순서 건너뛰기 → QP ERROR, 무응답 2. Receive WR 미게시 RTR 전이 전 recv post 누락 → RNR_RETRY_EXC_ERR 3. MTU 불일치 양쪽 path_mtu가 다름 → EINVAL 또는 성능 저하 4. MR access flag 오류 REMOTE_WRITE 없이 RDMA Write → REM_ACCESS_ERR 5. CQ overrun CQ 크기 < 총 WR 수 → CQ 오버플로, 데이터 손실 6. NUMA 불일치 HCA와 다른 NUMA 노드의 메모리 → 대역폭 30~50% 감소 예방 원칙: 단일 QP 기준선 먼저 → 속성 하나씩 변경 → 각 단계에서 perftest 검증 ibv_query_qp()로 QP 상태 확인, ibv_query_port()로 active_mtu 확인, numactl --hardware로 토폴로지 확인

안티패턴 상세

실수증상원인해결
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로 설정
가장 위험한 패턴: MR을 등록한 뒤 rkeyremote_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 운영성 향상
LTS 선택: 프로덕션 InfiniBand 환경에서는 커널 6.1 LTS 또는 6.6 LTS를 권장합니다. 최신 기능이 필요하면 MLNX_OFED가 제공하는 out-of-tree 드라이버를 사용하는 방식이 inbox 드라이버 대비 더 빠른 버그 수정과 기능 추가를 받을 수 있습니다.

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 압축
C-state 주의: 벤치마크 시 CPU가 깊은 절전 상태(C6 등)에 들어가면 tail latency가 급증합니다. tuned-adm profile latency-performance 또는 echo 0 > /dev/cpu_dma_latency로 깊은 C-state를 비활성화한 뒤 측정하세요.

참고자료

표준 및 사양

커널 공식 문서

라이브러리 및 도구

주요 참고 글

벤치마크 및 튜닝

커널 소스 경로

이 주제와 관련된 다른 문서를 더 깊이 이해하고 싶다면 다음을 참고하세요.