지난 글에서는 B300의 구조적 변화와 ECI의 가상화 구성을 살펴보았습니다.[1] 이번 글에서는 그중 노드 간 GPU 통신을 다룹니다. GPUDirect RDMA가 데이터를 전달하는 경로를 먼저 살펴보고, 이 경로를 가상 머신에서 활용하기 위해 ATS를 적용하던 중 발생한 장애와 해결 과정을 소개합니다.
-> B300의 구조적 변화와 ECI 가상화 구성 살펴보기
B300에서 달라진 부분 중 하나는 GPU와 NIC를 연결하는 구조입니다. 800 Gb/s 네트워크를 지원하는 ConnectX-8은 PCIe Gen6 스위치를 내장하며, HGX B300 보드에 직접 실장됩니다. GPU는 이 스위치에 PCIe Gen6 x16으로 연결됩니다. 단방향 이론 대역폭은 약 128 GB/s로, 800 Gb/s NIC의 전송량을 감당할 수 있는 경로입니다.[1]
같은 노드 안에서는 GPU들이 NVSwitch를 통해 NVLink로 연결됩니다. 5세대 NVLink의 GPU당 대역폭은 양방향 합계 1.8 TB/s입니다. 반면 다른 노드의 GPU와 통신하려면 PCIe 경로를 따라 NIC로 이동한 뒤 외부 네트워크를 거쳐야 합니다. GPU당 800 Gb/s 링크를 사용하더라도 단방향 대역폭은 이론상 100 GB/s로, NVLink의 단방향 대역폭인 900 GB/s의 약 1/9입니다. 프로토콜 오버헤드를 제외한 비교지만, 노드 내부와 외부의 통신 대역폭에 상당한 차이가 있음을 보여줍니다.
여러 노드에 걸친 분산 학습에서는 이 차이가 중요해집니다. 예를 들어 데이터 병렬 학습에서는 각 GPU가 계산한 그래디언트를 다른 GPU와 동기화해야 합니다. 연산이 빨라져도 통신이 이를 따라가지 못하면 GPU가 데이터를 기다리는 시간이 길어집니다. 따라서 네트워크 대역폭을 확보하는 것과 함께, GPU 메모리에서 NIC까지 이어지는 경로를 효율적으로 구성해야 합니다.
ECI는 이 경로를 가상화 환경에서도 활용하기 위해 VFIO, ACS, ATS를 함께 구성합니다. 각 기술의 역할을 이해하려면 먼저 GPU 데이터가 네트워크를 통해 전달되는 과정을 살펴볼 필요가 있습니다.
GPUDirect RDMA
GPUDirect RDMA는 NIC가 GPU 메모리에 직접 접근해 데이터를 주고받는 기술입니다.[2] 호스트 메모리에 데이터를 임시로 복사하는 과정을 없애 전송 경로를 줄입니다. TCP 소켓, 호스트 메모리를 경유하는 RDMA, GPUDirect RDMA를 비교하면 차이가 분명해집니다.
| 적용 기술 | TCP 소켓 | RDMA (호스트 메모리 경유) | GPUDirect RDMA |
|---|---|---|---|
| 데이터 전송에 RDMA 기능 사용 | 사용하지 않음 | 사용 | 사용 |
| 데이터 전송 시 커널 네트워크 스택 경유 | 경유 | 우회 | 우회 |
| 사용자 버퍼와 커널 소켓 버퍼 사이의 CPU 복사 | 있음 | 없음 | 없음 |
| GPU 데이터의 호스트 메모리 경유 | 경유 | 경유 | 우회 |
| NIC의 GPU 메모리 직접 접근 | 사용하지 않음 | 사용하지 않음 | 사용 |
| 로컬 데이터 이동 단계 | 6단계 | 4단계 | 2단계 |
여기서 데이터 이동 단계는 CPU의 메모리 복사와 장치의 DMA 전송을 구분하지 않고, 송신 측과 수신 측의 로컬 경로를 합산한 것입니다. NIC 사이의 네트워크 전송은 별도로 봅니다. 실제 복사 횟수는 구현과 최적화에 따라 달라질 수 있으므로, 아래에서는 일반적인 버퍼 기반 전송을 기준으로 비교합니다.
TCP 소켓: 호스트 메모리와 커널 버퍼를 경유

▲ 그림 1: TCP 소켓을 이용한 GPU 데이터 전송 경로
서버 A의 GPU 데이터를 서버 B의 GPU로 전송한다고 가정하겠습니다. 일반적인 TCP 소켓을 사용하면 다음 경로를 거칩니다.
- 송신 GPU의 데이터를 호스트의 사용자 공간 버퍼로 복사합니다. GPU와의 전송에는 보통 pinned memory를 사용합니다.
- 사용자 공간 버퍼의 데이터를 커널 소켓 버퍼로 복사합니다.
- 송신 NIC가 DMA로 커널 버퍼의 데이터를 읽어 네트워크로 전송합니다.
- 수신 NIC가 DMA로 데이터를 호스트 메모리의 수신 버퍼에 기록하고, 커널 네트워크 스택이 이를 처리합니다.
- 수신 데이터를 커널 버퍼에서 사용자 공간 버퍼로 복사합니다.
- 사용자 공간 버퍼의 데이터를 수신 GPU 메모리로 복사합니다.
이 과정에는 GPU와 호스트 메모리 사이의 전송뿐 아니라 사용자 공간과 커널 공간 사이의 복사도 포함됩니다. 데이터가 여러 버퍼를 거치면서 CPU와 호스트 메모리 대역폭을 사용하므로, GPU 연산과 별개로 전송 경로가 병목이 될 수 있습니다.
RDMA: 커널을 우회해 호스트 메모리에 직접 접근

▲ 그림 2: 호스트 메모리를 경유하는 RDMA 전송 경로
RDMA(Remote Direct Memory Access)는 NIC가 등록된 메모리에 직접 접근해 데이터를 전송하는 방식입니다. InfiniBand나 RoCE를 사용하는 RDMA NIC는 프로토콜 처리와 데이터 이동을 하드웨어에서 수행합니다. 연결 설정과 메모리 등록에는 OS가 관여하지만, 이후 데이터 전송은 일반적인 커널 네트워크 스택을 거치지 않습니다.
호스트 메모리를 경유하는 RDMA 구성에서는 GPU 데이터를 다음 순서로 전달합니다.
- 송신 GPU 메모리 → 송신 호스트 메모리: GPU DMA
- 송신 호스트 메모리 → 송신 NIC: NIC DMA
- 수신 NIC → 수신 호스트 메모리: NIC DMA
- 수신 호스트 메모리 → 수신 GPU 메모리: GPU DMA
TCP 소켓 예시의 2번과 5번에 해당하는 사용자 버퍼와 커널 버퍼 사이의 CPU 복사가 사라집니다. NIC가 등록된 사용자 버퍼에 직접 접근할 수 있기 때문입니다.
다만 이 구성에서는 데이터가 여전히 양쪽 호스트 메모리를 거칩니다. GPU 데이터를 호스트에 임시로 복사하는 과정을 호스트 스테이징(host staging)이라고 합니다. RDMA로 CPU의 복사 부담을 줄였더라도, 스테이징에 필요한 메모리 대역폭과 전송 시간은 남아 있습니다.
GPUDirect RDMA: NIC와 GPU 메모리를 직접 연결

▲ 그림 3: 호스트 메모리를 우회하는 GPUDirect RDMA 전송 경로
GPUDirect RDMA는 GPU 메모리를 NIC가 직접 접근할 수 있도록 등록하고 매핑합니다. 이를 통해 호스트 스테이징을 제거합니다.[2]
송신 측에서는 NIC가 GPU 메모리의 데이터를 직접 읽고, 수신 측에서는 NIC가 GPU 메모리에 데이터를 직접 기록합니다. 앞서 네 단계였던 로컬 데이터 이동이 두 단계로 줄어듭니다.
- 송신 GPU 메모리 → 송신 NIC
- 수신 NIC → 수신 GPU 메모리
GPU와 NIC가 같은 PCIe 스위치 아래에 있고 P2P(Peer-to-Peer) 전송이 허용되면, 데이터는 CPU의 루트 컴플렉스나 호스트 메모리로 우회하지 않고 스위치 내부에서 전달될 수 있습니다. 메모리 등록과 전송 제어에는 CPU와 드라이버가 관여하지만, 실제 GPU 데이터는 이 짧은 경로를 사용합니다.
ECI의 B300 구성에서는 ConnectX-8에 내장된 PCIe 스위치가 이 경로를 연결합니다. 다만 가상화 환경에서는 장치 간 직접 접근을 허용하면서도 테넌트별 접근 권한을 유지해야 합니다. 여기서 ACS와 ATS가 필요합니다.
가상화 환경에서 GPUDirect RDMA를 구성하는 방법

▲ 그림 4: IOMMU, ACS, ATS를 이용한 GPUDirect RDMA 경로 구성
ACS: 장치 간 접근 경로 제어
PCIe 스위치는 하위 포트에 연결된 장치끼리 직접 요청을 주고받게 할 수 있습니다. 그러나 여러 테넌트가 장치를 나누어 사용하는 환경에서 이러한 접근을 무조건 허용하면, 한 테넌트의 장치가 다른 테넌트에 할당된 메모리나 장치에 접근할 수 있습니다.
ACS(Access Control Services)는 이런 장치 간 요청의 경로를 제어하는 PCIe 기능입니다. P2P Request Redirection 등의 설정을 사용하면 요청을 상위 루트 컴플렉스 방향으로 보내 IOMMU의 주소 변환과 접근 권한 검사 경로를 거치게 할 수 있습니다. 다만 GPU와 NIC가 같은 스위치 아래에 있어도 트래픽이 상위 경로로 우회하면 대역폭이 줄거나 지연시간이 늘어날 수 있습니다.
NVIDIA는 GPUDirect 성능 문제를 다루는 문서에서 베어메탈 환경의 ACS 비활성화를 안내합니다. 반면 장치 격리가 필요한 VM 환경에서는 ACS를 유지하고 NIC의 ATS를 활성화하도록 안내합니다.[3]
ATS: 변환된 주소로 직접 전송
ATS(Address Translation Services)는 장치가 IOMMU에 주소 변환을 요청하고, 그 결과를 장치 내부의 ATC(Address Translation Cache)에 저장해 재사용할 수 있게 하는 기능입니다.
ECI는 ACS의 격리 관련 설정을 유지하면서 Direct Translated P2P를 허용하도록 구성합니다.[4] NIC는 IOMMU로부터 받은 변환 결과를 이용해 요청을 보내고, PCIe 스위치는 이렇게 변환된 요청을 GPU로 직접 전달합니다. 변환되지 않은 요청은 설정된 리다이렉션 경로를 따라 IOMMU의 변환과 검사를 받습니다.
이 구성은 호스트가 관리하는 IOMMU 매핑과 ATS 지원 장치의 올바른 동작을 전제로 합니다. ECI는 VFIO를 통한 장치 할당, IOMMU 매핑, NIC의 ATS, PCIe 스위치의 ACS 설정을 함께 검증해 VM에서도 GPU와 NIC 사이의 직접 전송 경로를 사용하도록 구성했습니다.
이때 주소 변환 결과를 장치에 캐시한다는 점은 새로운 관리 작업을 요구합니다. 호스트가 DMA 매핑을 해제하거나 바꾸면, 장치에 남아 있는 이전 변환 결과도 함께 무효화해야 합니다. B300 도입 과정에서 발생한 문제는 바로 이 단계에 있었습니다.
ATS를 활성화하자 호스트가 멈췄다
ECI에 B300을 도입하면서 GPU와 주변 장치의 가상화 설정을 하나씩 적용하고 검증했습니다. GPU 쪽 구성을 고정한 뒤 ConnectX-8의 VFIO와 ATS, 상위 PCIe 스위치의 ACS 설정을 확인했습니다. VFIO와 ACS까지는 문제가 없었고, 마지막으로 ATS를 활성화해 전체 초기화 과정을 실행했습니다.
호스트 초기화 프로그램은 NIC를 인식하고 VF(Virtual Function)를 생성한 뒤, 해당 VF를 vfio-pci 드라이버에 바인딩합니다. 그런데 이 단계에서 프로그램과 터미널이 함께 멈췄습니다. SSH를 포함해 호스트가 거의 응답하지 않았고, 결국 하드웨어 재부팅이 필요했습니다.
당시에는 B300에 맞춰 초기화 프로그램을 수정하며 E2E 테스트를 진행하고 있었습니다. 장치 인식에 실패하거나 프로그램이 종료되는 경우는 있었지만, 호스트 전체가 멈춘 것은 처음이었습니다. 일시적인 네트워크 문제를 의심했으나, 재부팅 후 같은 과정을 반복하자 동일한 증상이 재현됐습니다.
로그에서 찾은 단서
커널 로그에는 CPU 0의 hard lockup이 먼저 기록됐습니다.
watchdog: Watchdog detected hard LOCKUP on cpu #0
이후 다른 CPU에서도 soft lockup이 연속으로 발생했습니다.
watchdog: BUG: soft lockup - CPU#213 stuck for 26s!
hard lockup 당시 수집한 콜스택의 호출 흐름은 다음과 같았습니다.
vfio_iommu_type1_ioctl
vfio_iommu_type1_unmap_dma
__iommu_unmap
intel_iommu_unmap
intel_iommu_tlb_sync
iommu_flush_iotlb_psi
iommu_flush_dev_iotlb
qi_flush_dev_iotlb
qi_submit_sync
qi_check_fault
멈춘 위치는 NIC의 일반적인 패킷 송수신 경로가 아니었습니다. VFIO가 DMA 매핑을 해제한 뒤, Intel IOMMU가 장치의 주소 변환 캐시를 무효화하는 경로였습니다. Intel VT-d에서는 이 장치 측 캐시를 Device-TLB라고 부릅니다.
이 단서를 바탕으로 네트워크 송수신 자체보다 VFIO, IOMMU, ATS가 함께 동작하는 과정에 집중했습니다.
수백 개의 초기화 작업을 세 단계로 줄이기
호스트 초기화 프로그램은 수백 개의 설정과 장치 초기화 작업을 수행합니다. 프로그램 전체를 반복 실행하는 것만으로는 어느 장치의 어떤 설정이 문제를 일으키는지 알기 어려웠습니다. 서버 벤더나 NVIDIA에 분석을 요청하더라도, 전체 ECI 환경을 재구성해야 한다면 원인을 찾는 데 시간이 오래 걸릴 수밖에 없었습니다.
초기화 프로그램이 실행하는 명령을 분리하고, 대상 장치와 설정을 하나씩 바꾸며 재현 조건을 좁혔습니다. 최종적으로 다음 세 단계만으로 문제를 재현할 수 있었습니다.
- ConnectX-8 NIC에서 ATS를 활성화합니다.
- 해당 NIC에 VF를 생성합니다.
- 생성한 VF를
vfio-pci에 바인딩합니다.
세 조건이 모두 충족되면 장애가 반복됐고, ATS를 비활성화하면 같은 절차에서도 문제가 발생하지 않았습니다.
커널 부팅 옵션에 `iommu=pt`를 추가한 결과도 단서가 됐습니다. 문제가 사라지지는 않았지만, 발생 시점이 VF를 VM에 실제로 연결하는 단계로 옮겨갔습니다. 호스트가 멈추는 양상도 상대적으로 완화돼 콜스택을 수집하기 쉬워졌습니다.
| 설정 | 관찰 결과 |
|---|---|
| ATS 비활성화 | 동일 절차에서 장애가 발생하지 않음 |
| ATS 활성화 | VF를 vfio-pci에 바인딩하는 단계에서 장애 발생 |
ATS 활성화 + iommu=pt | VF를 VM에 연결하는 단계로 장애 발생 시점이 이동 |
이로써 분석 범위를 ATS가 활성화된 ConnectX-8 VF와 VFIO/IOMMU의 상호작용으로 좁힐 수 있었습니다.
Device-TLB 무효화가 끝나지 않은 이유
ATS를 사용하는 장치는 IOMMU가 제공한 주소 변환 결과를 ATC에 보관합니다. DMA 매핑을 해제할 때 IOMMU의 페이지 테이블만 바꾸고 장치의 캐시를 남겨 두면, 장치는 이미 해제된 주소로 계속 접근할 수 있습니다. 따라서 매핑을 변경한 뒤에는 관련 변환 캐시를 함께 무효화해야 합니다.

이 과정을 개념적으로 정리하면 다음과 같습니다.
- VFIO가 DMA 매핑 해제를 요청합니다.
- IOMMU 드라이버가 페이지 테이블을 변경하고 관련 IOTLB 항목을 무효화합니다.
- IOMMU가 ATS를 사용하는 장치에 Device-TLB Invalidate Request를 보냅니다.
- 장치가 ATC의 해당 항목을 무효화하고 Invalidate Completion을 반환합니다.
- 무효화 완료를 확인한 뒤 커널이 다음 작업을 진행합니다.
수집한 콜스택도 이 흐름과 일치했습니다. 주요 함수의 역할은 다음과 같습니다.[5]
vfio_iommu_type1_ioctl
vfio_iommu_type1_unmap_dma # VFIO DMA 매핑 해제 처리
__iommu_unmap # IOMMU 공통 계층과 Intel 드라이버를 통한 매핑 제거
intel_iommu_unmap
intel_iommu_tlb_sync # 변경된 매핑에 대한 변환 캐시 동기화와 IOTLB 무효화 처리
iommu_flush_iotlb_psi
iommu_flush_dev_iotlb # ATS를 사용하는 장치의 Device-TLB 무효화 처리
qi_flush_dev_iotlb # Device-TLB 무효화 디스크립터 구성 및 제출 요청
qi_submit_sync # 무효화 큐에 디스크립터를 제출하고 Wait Descriptor의 완료 상태 확인
qi_check_fault # 완료 대기 중 VT-d 오류 상태 확인
핵심은 qi_submit_sync의 완료 대기였습니다. 이 함수는 무효화 명령과 Wait Descriptor를 IOMMU의 큐에 제출하고, 완료 상태가 갱신될 때까지 확인합니다. 장치의 Device-TLB 무효화가 정상적으로 끝나지 않으면 후속 완료 처리도 진행되지 않을 수 있습니다.
iommu=pt에서 장애 시점이 달라진 것도 이 분석과 부합했습니다. 이 옵션은 호스트 장치의 기본 DMA 도메인을 passthrough로 구성하지만, VM에 장치를 할당하는 VFIO 경로의 IOMMU 매핑까지 없애지는 않습니다. 이번 환경에서는 호스트 초기화 단계에서 드러나던 문제가 VFIO를 통해 VM용 매핑을 다루는 단계에서 다시 나타났습니다. 따라서 이 옵션은 해결책이 아니라, 문제를 일으키는 경로를 구분하는 단서였습니다.
당시 분석한 커널의 qi_submit_sync는 완료를 기다리는 동안 해당 CPU의 인터럽트를 비활성화한 상태로 폴링합니다. 이 대기가 끝나지 않으면 CPU가 일반적인 인터럽트 처리를 재개하지 못해 hard lockup으로 검출될 수 있습니다. 다른 CPU도 해당 CPU의 응답이나 공유 자원을 기다리면서 진행이 막힐 수 있습니다. 관찰된 연쇄적인 soft lockup 역시 이런 장애 전파로 해석했습니다.
ATS를 끄면 증상이 사라졌고, iommu=pt에서는 발생 시점이 바뀌었으며, 콜스택은 Device-TLB 무효화 완료 대기 경로에 머물러 있었습니다. 이 결과를 종합해 ConnectX-8의 ATS 무효화 완료 처리에 문제가 있을 가능성이 높다고 판단했습니다. 다만 이 단계에서 호스트 로그만으로 NIC 내부의 동작까지 확정할 수는 없었습니다.
NVIDIA와 펌웨어 동작을 확인하다
NIC가 Invalidate Request를 받은 뒤 내부에서 어떻게 처리하는지는 호스트에서 직접 확인하기 어렵습니다. 최소 재현 절차와 콜스택, IOMMU 및 ATS 설정, iommu=pt 적용 전후의 차이, 펌웨어별 재현 결과를 정리해 서버 벤더와 NVIDIA에 공유했습니다.
OS와 드라이버, 부팅 옵션, 펌웨어를 바꿔 가며 재현 여부를 확인했습니다. ConnectX-8 펌웨어 40.46.5500과 40.47.2526에서는 모두 같은 증상이 발생했습니다.
이후 ECI에서는 설정을 통제한 채 최소 재현 절차를 반복했고, NVIDIA에서는 해당 결과를 기존 이슈 및 펌웨어 내부 동작과 대조했습니다. 이 과정에서 Device-TLB Invalidate Request 이후 Completion이 정상적으로 반환되지 않는 문제가 확인됐습니다. 호스트에서 관찰한 완료 대기와 NIC 내부의 처리 문제가 연결된 것입니다.
NVIDIA는 해당 경로의 수정 사항이 포함된 ConnectX-8 펌웨어 40.50.0464를 검증용으로 제공했습니다. 당시에는 공식 릴리스 전인 버전이었습니다. 다른 조건을 유지하고 동일한 최소 재현 절차를 반복한 결과, 기존 두 버전에서 발생하던 hard lockup이 수정 펌웨어에서는 재현되지 않았습니다.
| ConnectX-8 펌웨어 | 동일한 최소 재현 절차의 결과 |
|---|---|
| 40.46.5500 | hard lockup 재현 |
| 40.47.2526 | hard lockup 재현 |
| 40.50.0464 | hard lockup 미재현 |
최소 재현 조건으로 문제의 범위를 줄이고, 커널 경로와 펌웨어 동작을 대조한 뒤, 수정 펌웨어로 같은 조건을 다시 검증했습니다. 이를 통해 ATS Device-TLB 무효화 경로의 문제와 해당 환경에서의 수정 효과를 확인할 수 있었습니다.
마무리
GPU 클러스터의 통신 성능은 NIC의 대역폭만으로 결정되지 않습니다. GPU 메모리에서 NIC까지 데이터가 어떤 경로로 이동하는지, 그 경로가 가상화 환경에서도 의도대로 동작하는지 함께 확인해야 합니다.
GPUDirect RDMA는 호스트 메모리 스테이징을 없애 GPU와 NIC가 직접 데이터를 주고받게 합니다. ECI는 이 경로를 VM에서도 활용하기 위해 VFIO와 IOMMU, ACS, ATS를 함께 구성했습니다. 그 과정에서 ConnectX-8의 ATS를 활성화하면 호스트가 멈추는 문제를 만났고, Device-TLB 무효화 완료 대기까지 원인을 좁힌 뒤 NVIDIA와 펌웨어 수정 사항을 검증했습니다.
이번 분석에서 특히 도움이 된 것은 복잡한 초기화 과정을 세 단계의 재현 절차로 줄인 일이었습니다. 재현 조건이 명확해지자 ECI의 커널 분석 결과와 NVIDIA의 펌웨어 분석 결과를 같은 동작에 놓고 비교할 수 있었습니다. 수정 후에도 같은 절차를 사용해 문제가 해소됐는지 확인했습니다.
이 검증을 거쳐 ECI는 B300 VM에서도 ATS를 활성화하고 GPU와 NIC 사이의 직접 전송 경로를 사용할 수 있도록 구성했습니다. 다음 글에서는 멀티노드 클러스터에서 ATS 적용 여부에 따른 RDMA 성능 측정 결과를 소개하겠습니다.
🚀 AI 클라우드 인프라를 함께 만들어갈 동료를 찾습니다!
참고
[1] https://elice.io/ko/resources/blog/eci-b300-gpu-virtualization - 1편: NVIDIA B300 도입기: ECI에서 만나는 베어메탈급 성능
[2] https://docs.nvidia.com/cuda/gpudirect-rdma/index.html - NVIDIA GPUDirect RDMA 문서
[3] https://docs.nvidia.com/deeplearning/nccl/user-guide/docs/troubleshooting/gpu_troubleshooting.html#pci-access-control-services-acs - NVIDIA pci-access-control-services-acs
[4] https://docs.nvidia.com/ai-enterprise/planning-resource/optimizing-vm-configuration-ai-inference/latest/appendix.html#configuring-acs - NVIDIA AI Enterprise, Configuring ACS
[5] Linux 6.8 drivers/iommu/intel/iommu.c (iommu_flush_dev_iotlb), drivers/iommu/intel/dmar.c (qi_submit_sync, qi_check_fault)
