ROCm / HIP — AMD GPU 컴퓨트 플랫폼

AMD ROCm(Radeon Open Compute)은 GPU 범용 컴퓨팅을 위한 완전 오픈소스 소프트웨어 스택입니다. Linux 커널의 KFD(Kernel Fusion Driver)와 amdgpu 드라이버 위에서 동작하며, HIP(Heterogeneous-compute Interface for Portability) 언어 런타임, rocBLAS/MIOpen 등의 수학 라이브러리, rocProfiler/rocTracer 프로파일링 인프라, RCCL 집합 통신 라이브러리를 포함합니다. RDNA(그래픽+컴퓨트 겸용)와 CDNA(순수 HPC 컴퓨트 전용) 아키텍처를 지원하며, MI300X 같은 최신 APU 칩렛 구조까지 다룹니다. PyTorch, TensorFlow, JAX 등 주요 AI 프레임워크가 ROCm 백엔드를 공식 지원합니다.

전제 조건: GPU 서브시스템 (DRM/KMS) 문서를 먼저 읽으세요. ROCm은 amdgpu 드라이버의 DRM/KMS 인프라 위에서 동작하므로, GEM/TTM 메모리 관리, DRM 파일 오퍼레이션, DMA-BUF 버퍼 공유 개념을 먼저 파악해야 KFD와 HIP 런타임의 동작 방식이 자연스럽게 이해됩니다.
일상 비유: ROCm 스택은 공장 자동화 시스템과 비슷합니다. 커널 KFD는 공장 관리자(작업 배분·자원 할당), amdgpu는 공장 기계(실제 하드웨어 제어), HIP 런타임은 작업 지시서(프로그래밍 모델), 수학 라이브러리는 전문 부품(최적화 루틴), AI 프레임워크는 완성품 조립 라인(고수준 추상화)입니다.
버전 기준: 이 문서는 ROCm 6.x (2024년) 기준으로 작성되었습니다. ROCm 5.x와의 주요 차이점으로 HIP 런타임 통합, hipBLASLt 추가, MI300X 지원, WMMA(Wavefront Matrix Multiply-Accumulate) 명령어 확장이 있습니다. 커널 측에서는 Linux 6.7+ amdgpu에서 GFX11(RDNA3)/GFX9x(CDNA3) IP가 mainline에 포함되었습니다.

핵심 요약

  • KFD (/dev/kfd) — Kernel Fusion Driver. amdgpu 위에 올라타는 컴퓨트 전용 커널 인터페이스. 프로세스별 GPU 메모리 공간 격리, 큐 관리, IOMMU 지원을 제공합니다.
  • HIP ↔ CUDA 호환 — HIP API는 CUDA API와 거의 1:1 대응합니다. hipify-perl / hipify-clang으로 CUDA 소스를 자동 변환하며, AMD GPU에서는 amdgpu 백엔드로, NVIDIA GPU에서는 CUDA 백엔드로 투명하게 컴파일됩니다.
  • RDNA vs CDNA — RDNA(RX 7000 시리즈)는 그래픽+컴퓨트 겸용 아키텍처이며, CDNA(MI200/MI300 시리즈)는 HPC/AI 전용으로 ROPs와 디스플레이 엔진이 없는 대신 더 많은 CU와 HBM3 메모리를 탑재합니다.
  • ROCm 오픈소스 — 스택 전체가 MIT/Apache-2.0 라이선스로 GitHub(github.com/ROCm)에 공개됩니다. 커널 드라이버(amdgpu/KFD)는 GPL-2.0이며 Linux mainline에 포함되어 있습니다.
  • MI300X 특성 — CPU Die 없이 GPU XCD(Compute Die) 8개 + IOD 4개 + HBM3 192 GB를 단일 패키지에 집적한 APU 형태입니다. LLM 추론에서 단일 노드 최대 메모리 용량으로 주목받습니다.

단계별 이해

  1. 커널 드라이버 레이어 파악
    amdgpu 드라이버가 로드되면 KFD 모듈이 /dev/kfd를 생성합니다. ROCm 런타임은 이 노드를 통해 GPU 자원을 요청하고, 렌더링과 무관한 컴퓨트 전용 경로로 GPU에 직접 접근합니다.
  2. HIP 프로그래밍 모델 이해
    커널 함수는 __global__로 선언하고 hipLaunchKernelGGL 또는 <<<grid, block>>> 문법으로 GPU에서 실행합니다. 스레드는 Grid → Block → Wavefront(64 threads) → Thread 계층으로 구성됩니다.
  3. 메모리 계층 구조 습득
    GPU 전용 VRAM(HBM/GDDR6), CPU-GPU 공유 GTT 영역, Unified Shared Memory, on-chip LDS(Local Data Share), 레지스터 파일(VGPR/SGPR)의 용량·대역폭·레이턴시 차이를 파악합니다.
  4. 라이브러리 생태계 활용
    직접 커널을 작성하기 전에 rocBLAS(GEMM), rocFFT, MIOpen(딥러닝 연산자), rocRAND, rocSPARSE가 이미 최적화된 구현을 제공하는지 확인합니다.
  5. 프로파일링과 최적화
    rocprof / omniperf로 PMC(Performance Monitor Counter) 데이터를 수집하고, CU occupancy, 메모리 대역폭 활용률, wavefront 지연 원인을 분석하여 병목을 제거합니다.
  6. Multi-GPU 확장
    XGMI(Infinity Fabric) 링크로 GPU 간 직접 통신하고 RCCL로 collective 연산을 분산합니다. 컨테이너/쿠버네티스 환경에서는 ROCm Device Plugin으로 GPU 할당을 자동화합니다.

ROCm 발전 역사와 로드맵

ROCm(Radeon Open Compute)의 역사는 AMD가 2016년 오픈소스 GPU 컴퓨팅 생태계를 구축하겠다는 선언과 함께 시작됩니다. 당시 NVIDIA CUDA가 GPU 컴퓨팅 시장을 독점하는 상황에서, AMD는 Linux Foundation과의 협력을 통해 HSA(Heterogeneous System Architecture) 표준을 기반으로 한 완전 오픈소스 대안을 제시했습니다. 초기에는 Fiji(R9 Fury) 아키텍처를 지원하는 단일 드라이버로 출발했지만, 이후 7년간 CDNA 서버 GPU, AI 프레임워크 통합, WMMA 연산, MI300X APU 지원까지 급속히 발전했습니다.

ROCm의 근간에는 HSA(Heterogeneous System Architecture) 철학이 있습니다. HSA Consortium(AMD, ARM, Qualcomm, TI 등이 참여)이 정의한 이 표준은 CPU와 GPU가 동일한 가상 주소 공간에서 효율적으로 협력하는 모델을 규정합니다. AQL(Architected Queuing Language) 패킷 형식, 신호(Signal) 객체, 통합 메모리(SVM) 모두 HSA 1.1 사양에서 유래했습니다. ROCm 1.0이 이 사양을 최초로 오픈소스로 구현한 프로덕션 스택입니다.

컴파일러 측면에서의 주요 전환점은 HCC(Heterogeneous Compute Compiler)에서 HIP/hipcc로의 이행입니다. ROCm 3.x까지 AMD는 HCC를 주 GPU 컴파일러로 사용했으나, HCC는 CUDA 코드 이식성이 부족하고 LLVM 업스트림과 괴리가 있었습니다. ROCm 3.3부터 hipcc(LLVM Clang 기반)가 기본 컴파일러가 되면서 CUDA 코드의 직접 이식이 크게 쉬워졌고, LLVM 업스트림 AMDGPU 백엔드와의 통합도 강화되었습니다. ROCm 5.0에서 HCC는 공식 deprecated되었습니다.

버전별 주요 이정표

ROCm 주요 버전별 이력표 (1.x ~ 6.x)
버전출시 연도핵심 기능신규 GPU 지원컴파일러
1.0 ~ 1.9 2016 ~ 2017 HSA 1.1 오픈소스 구현, KFD 초기 릴리즈, ROCr 런타임, hcc 컴파일러, OpenCL 2.0 지원 Fiji (R9 Fury), Polaris (RX 480/580) HCC (Heterogeneous Compute Compiler)
2.0 ~ 2.10 2018 ~ 2019 MIOpen 1.0 출시, rocBLAS 초기 릴리즈, rocFFT, HIP-on-CUDA 실험적 지원, ROCm SMI 도구 Vega10 (RX Vega 56/64, GFX9), Vega20 (Radeon VII, GFX906) HCC + hipcc (실험적)
3.0 ~ 3.10 2020 ~ 2021 hipcc 기본 컴파일러 전환, RDNA1 지원 추가, rocDecode 초기, rocTracer API 추적 도구, SVM 초기 지원 RDNA1 GFX10 (RX 5700 XT), Arcturus (MI100, GFX908) hipcc (LLVM Clang 기반)
4.0 ~ 4.5 2021 ~ 2022 MI100 최적화, RCCL(AllReduce/AllGather), rocALUTION, rocPRIM, rocThrust, PyTorch-ROCm 공식 지원, hipify-clang 강화 Aldebaran (MI200/MI210, GFX90a, CDNA2) hipcc + 통합 LLVM
5.0 ~ 5.7 2022 ~ 2023 HCC 공식 deprecated, HIP 런타임 통합, RDNA3 초기 지원, omniperf 성능 분석 도구, TF-ROCm 안정화, JAX-ROCm 공식화 RDNA2 (RX 6000, GFX1030), RDNA3 (RX 7900, GFX1100) hipcc (LLVM 15/16 기반)
6.0 ~ 6.x 2024 ~ 현재 MI300X (CDNA3, GFX942) 완전 지원, hipBLASLt (cuBLASLt 대응), WMMA 명령어, 통합 HIP 런타임 v2, rocDecode GA, 192 GB HBM3 지원 MI300X (GFX942, CDNA3), MI300A (APU), RDNA3.5 예정 hipcc (LLVM 17/18 기반)
# ROCm 설치 버전 및 컴포넌트 확인
cat /opt/rocm/.info/version
# 출력: 6.1.0-1

# 설치된 모든 ROCm 패키지 나열
dpkg -l | grep rocm
# 또는 RPM 기반 시스템:
rpm -qa | grep rocm

# 컴파일러 버전 확인
/opt/rocm/bin/hipcc --version
# 출력: HIP version: 6.1.40091-5e38b8620
#        clang version 17.0.0 (amd-mainline-open ...)

# 주요 라이브러리 버전 확인
apt show rocblas 2>/dev/null | grep Version
apt show miopen-hip 2>/dev/null | grep Version
apt show rccl 2>/dev/null | grep Version

# rocm-smi로 현재 드라이버 버전 확인
rocm-smi --showdriverversion
# Driver Version: 6.7.0
# ROCm Version: 6.1.0

# KFD ioctl 버전 (ROCm ABI 호환성 확인)
cat /sys/class/kfd/kfd/version
# 출력: 1.14

ROCm vs CUDA 생태계 비교

ROCm은 CUDA와의 호환성을 명시적인 설계 목표로 삼습니다. HIP API는 CUDA Runtime API와 90% 이상 일치하며, hipify-perlhipify-clang 도구를 사용하면 대부분의 CUDA 소스 코드를 자동 변환할 수 있습니다. 단, CUDA device-specific intrinsics나 PTX 인라인 어셈블리는 수동 변환이 필요하고, CUDA 고유 기능(cooperative groups 일부, CUDA graphs 완전 지원 등)은 ROCm에서 구현 범위가 다를 수 있습니다.

ROCm vs CUDA 생태계 구성 요소 대응표
범주CUDA 생태계ROCm 생태계호환성 수준
프로그래밍 모델 CUDA C++ (nvcc) HIP C++ (hipcc) API 90% 이상 호환, hipify 자동 변환
런타임 라이브러리 libcudart.so libamdhip64.so API 1:1 대응 (접두사 cuda→hip)
드라이버 API libcuda.so (CUDA Driver) librocmdrv.so / ROCr HSA 하위 레이어 — HIP가 추상화
BLAS 라이브러리 cuBLAS / cuBLASLt rocBLAS / hipBLASLt API 호환, 성능 동등 수준
딥러닝 프리미티브 cuDNN MIOpen 기능 동등, API는 별개
FFT 라이브러리 cuFFT rocFFT / hipFFT hipFFT는 cuFFT 호환 API
희소 행렬 cuSPARSE rocSPARSE / hipSPARSE hipSPARSE는 cuSPARSE 호환 API
난수 생성 cuRAND rocRAND / hipRAND hipRAND는 cuRAND 호환 API
집합 통신 NCCL RCCL API 호환 (NCCL 포크)
프로파일러 Nsight Systems / Nsight Compute rocProfiler / omniperf 기능 유사, 워크플로우 다름
디버거 cuda-gdb rocgdb (GDB 확장) 명령어 동일, GPU 스레드 디버깅 지원
컨테이너 지원 nvidia-docker / NVIDIA Container Toolkit ROCm Docker (--device /dev/kfd --device /dev/dri) Docker 지원 동등, k8s 플러그인 있음
코드 변환 도구 없음 (독점) hipify-perl, hipify-clang CUDA→HIP 자동 변환 (90% 자동화)
ISA / 어셈블리 PTX (가상 ISA) / SASS AMDGCN ISA (진짜 하드웨어 ISA) 별개 — 직접 이식 불가

향후 로드맵과 전략 방향

2024년 이후 ROCm의 핵심 전략은 크게 세 방향으로 정리됩니다. 첫째, AI/LLM 추론 최적화입니다. MI300X의 192 GB HBM3 용량을 활용한 대형 언어 모델 단일 노드 추론 시나리오에서 vLLM, SGLang, llama.cpp 등이 ROCm 백엔드를 공식 지원하기 시작했습니다. hipBLASLt의 Epilogue Fusion과 Custom Kernel 지원이 Transformer attention 계층 최적화를 크게 개선했습니다.

둘째, LLVM 업스트림 통합 강화입니다. ROCm 팀은 자체 LLVM 포크에 의존하는 대신 AMDGPU 백엔드 코드를 지속적으로 LLVM 메인라인에 기여하고 있습니다. 이를 통해 Clang/LLVM 최신 버전을 사용하는 일반 개발자도 ROCm 없이 AMDGPU 코드를 컴파일할 수 있게 됩니다.

셋째, PyTorch/JAX 우선 지원입니다. AMD는 Meta와의 협력을 통해 PyTorch ROCm 빌드의 성능 격차를 CUDA 대비 5% 이내로 좁히는 것을 목표로 합니다. FlashAttention-ROCm, Triton-ROCm 등의 핵심 커널 라이브러리가 공식 AMD 팀에 의해 유지 관리되고 있습니다.

hipify 사용법: CUDA 프로젝트를 HIP로 변환할 때는 hipify-clang을 권장합니다. hipify-perl은 텍스트 치환 기반이라 문법적으로 잘못된 변환이 생길 수 있는 반면, hipify-clang은 AST를 분석하여 정확한 변환을 수행합니다. hipify-clang --cuda-path=/usr/local/cuda --print-stats my_cuda.cu로 변환 통계를 먼저 확인하세요.
GFX 버전 체계: amdgpu는 GPU를 gfxNNN 형태의 코드로 구분합니다. GFX9 계열(Vega/CDNA): gfx900(Vega10), gfx906(Vega20), gfx908(MI100), gfx90a(MI200), gfx942(MI300X). GFX10 계열(RDNA1/2): gfx1010(Navi10), gfx1030(Navi21). GFX11 계열(RDNA3): gfx1100(Navi31). rocm_agent_enumerator 또는 hipcc --amdgpu-target=gfxNNN에서 사용합니다.
ROCm 지원 중단 정책: ROCm은 각 메이저 버전에서 일부 이전 GPU 지원을 중단합니다. 예를 들어 ROCm 6.0은 GFX8(Fiji/Polaris) 지원을 공식 종료했습니다. 장기 지원이 필요한 경우 AMD 공식 지원 매트릭스(rocm.docs.amd.com/projects/install-on-linux/en/latest/reference/system-requirements.html)를 반드시 확인하세요.

ROCm 소프트웨어 스택

ROCm은 단일 라이브러리가 아니라 여러 레이어로 구성된 소프트웨어 스택입니다. 맨 아래의 Linux 커널 드라이버부터 최상위 AI 프레임워크까지 각 레이어가 명확한 인터페이스 계약을 통해 연결됩니다. 이 계층 구조를 이해하면 성능 문제나 API 오류가 발생했을 때 어느 레이어를 조사해야 하는지 즉시 판단할 수 있습니다.

스택의 핵심 철학은 레이어 간 명확한 계약입니다. HIP API는 AMD GPU와 NVIDIA GPU 양쪽에서 동작하는 이식성 레이어이며, ROCr(HSA Runtime)은 AQL(Architected Queuing Language) 패킷을 통해 GPU에 커맨드를 제출합니다. 커널 레이어에서는 KFD가 사용자 공간 런타임의 ioctl 요청을 받아 amdgpu 드라이버의 컴퓨트 큐를 설정합니다.

# ROCm 버전 및 GPU 상태 확인
rocm-smi --showdriverversion
# 출력 예: ROCm version: 6.1.0

rocm-smi --showproductname
# 출력 예: GPU[0]: Instinct MI300X

rocm-smi --showmeminfo vram
# VRAM 사용량 조회 (Used / Total in MB)

# ROCm 전체 컴포넌트 버전 확인
cat /opt/rocm/.info/version
# 출력: 6.1.0-1

# amdgpu 드라이버 로드 확인
lsmod | grep amdgpu
dmesg | grep -i kfd | head -20
ROCm 스택 레이어별 역할과 코드 위치
레이어구성 요소역할소스/패키지
Hardware AMD GPU (RDNA/CDNA) 실제 연산 수행, SH(Shader Hardware), CU(Compute Unit) 하드웨어
Kernel Driver amdgpu + KFD DRM 프레임워크, IP 블록 초기화, /dev/kfd 노출 drivers/gpu/drm/amd/ (Linux mainline)
HSA Runtime ROCr (libhsa-runtime64) AQL 패킷 큐 관리, 신호/이벤트, 메모리 할당 github.com/ROCm/ROCR-Runtime
HIP Runtime libamdhip64 CUDA 호환 API, 스트림, 이벤트, 통합 메모리 github.com/ROCm/HIP
컴파일러 hipcc / clang-hip HIP C++ → AMDGCN ISA 바이너리 생성 LLVM upstream + ROCm fork
수학 라이브러리 rocBLAS, MIOpen, rocFFT 등 GEMM, 합성곱, FFT 등 고성능 수치 연산 github.com/ROCm/rocBLAS 등 각 레포
프로파일링 rocProfiler, rocTracer, omniperf PMC 카운터, API 추적, 성능 분석 리포트 github.com/ROCm/rocprofiler
AI 프레임워크 PyTorch, TF, JAX 고수준 딥러닝 연산, 자동 미분 각 프레임워크 공식 ROCm 빌드
amdgpu vs KFD 역할 분리: amdgpu는 DRM 기반으로 KMS 디스플레이, GEM 메모리 관리, 렌더 노드(/dev/dri/renderD128)를 처리합니다. KFD는 amdgpu 위에 올라타서 컴퓨트 전용 인터페이스(/dev/kfd)를 제공합니다. ROCm 런타임은 주로 KFD를 통해 동작하며, OpenGL/Vulkan 같은 그래픽 API는 렌더 노드를 사용합니다.
HSA(Heterogeneous System Architecture): AMD가 주도하는 이종 컴퓨팅 표준으로, CPU와 GPU가 동일한 가상 주소 공간을 공유하고 효율적으로 협력하는 모델을 정의합니다. ROCr은 HSA 1.1 사양을 구현하며, KFD는 HSA 커널 인터페이스 역할을 합니다. HSA의 AQL(Architected Queuing Language) 패킷 형식은 GPU 커맨드 큐에 직접 쓰여 오버헤드를 최소화합니다.
ROCm 지원 GPU 확인 필수: 모든 AMD GPU가 ROCm을 지원하지는 않습니다. 공식 지원 목록은 rocm.docs.amd.com의 "Supported GPUs" 페이지에서 확인하세요. 일반적으로 Vega10(GFX9) 이상, 소비자용은 RX 5700(RDNA1/GFX10) 이상이 지원되며, 일부 기능(예: WMMA)은 RDNA3(GFX11)/CDNA3(GFX9x)에서만 동작합니다.

HSA/AQL 패킷 구조 심층 분석

AQL(Architected Queuing Language)은 HSA 사양이 정의하는 GPU 커맨드 제출 형식입니다. CUDA의 경우 CPU가 GPU 커맨드 링 버퍼에 드라이버 내부 포맷으로 패킷을 써야 했지만, AQL은 이 포맷을 공개 사양으로 표준화하여 사용자 공간에서 직접 GPU 큐에 패킷을 쓸 수 있게 했습니다. 이 설계 덕분에 커널 진입 없이 GPU 작업을 제출할 수 있어 레이턴시가 크게 줄어듭니다.

AQL 큐는 mmap된 메모리 링 버퍼입니다. 사용자 프로세스가 패킷을 채운 후 도어벨(doorbell) MMIO 레지스터에 새 쓰기 포인터(wptr)를 기록하면, GPU의 Command Processor(CP)가 이를 감지하여 패킷을 소비합니다. 이 과정에서 시스템 콜이나 드라이버 개입이 전혀 없습니다. ROCr HSA 런타임은 이 AQL 큐 관리를 캡슐화하고, HIP 런타임은 ROCr 위에서 hipLaunchKernel을 구현합니다.

AQL 패킷은 64바이트 고정 크기이며, 4가지 타입이 존재합니다. Kernel Dispatch Packet은 GPU 커널을 실행할 때 사용하고, Agent Dispatch Packet은 특수 에이전트 작업 요청에, Barrier-AND/OR Packet은 의존성 동기화에 사용됩니다. 커널 디스패치 패킷 하나가 hipLaunchKernel 호출 하나에 해당합니다.

AQL 패킷 구조 상세

도어벨 메커니즘

/* AQL Kernel Dispatch Packet 직접 구성 (C 로우레벨 예제) */
#include <hsa/hsa.h>
#include <stdint.h>
#include <string.h>

/* HSA 1.1 사양 4.2절: Kernel Dispatch Packet */
typedef struct __attribute__((packed)) {
    uint16_t     header;               /* [7:0]=type [8]=barrier [12:9]=fence */
    uint16_t     setup;
    uint16_t     workgroup_size_x;
    uint16_t     workgroup_size_y;
    uint16_t     workgroup_size_z;
    uint16_t     reserved0;
    uint32_t     grid_size_x;
    uint32_t     grid_size_y;
    uint32_t     grid_size_z;
    uint32_t     private_segment_size;
    uint32_t     group_segment_size;
    uint64_t     kernel_object;
    uint64_t     kernarg_address;
    uint64_t     reserved2;
    hsa_signal_t completion_signal;
} aql_kdp_t;
_Static_assert(sizeof(aql_kdp_t) == 64, "must be 64 bytes");

void submit_kernel(hsa_queue_t *q, uint64_t code, void *args,
                   uint32_t gx, uint32_t bx, hsa_signal_t done)
{
    /* 1. 원자적으로 wptr 인덱스 획득 */
    uint64_t wi = hsa_queue_add_write_index_relaxed(q, 1);
    while (wi - hsa_queue_load_read_index_acquire(q) >= q->size)
        ;

    /* 2. 슬롯 포인터 계산 */
    aql_kdp_t *p = (aql_kdp_t *)q->base_address + (wi & (q->size - 1));

    /* 3. 패킷 필드 작성 (header는 반드시 마지막에!) */
    memset(p, 0, 64);
    p->setup             = 1;
    p->workgroup_size_x  = (uint16_t)bx;
    p->workgroup_size_y  = p->workgroup_size_z = 1;
    p->grid_size_x       = gx;
    p->grid_size_y       = p->grid_size_z = 1;
    p->kernel_object     = code;
    p->kernarg_address   = (uint64_t)args;
    p->completion_signal = done;

    /* 4. header 원자적 쓰기 */
    uint16_t hdr =
        (uint16_t)HSA_PACKET_TYPE_KERNEL_DISPATCH
        | (1u << HSA_PACKET_HEADER_BARRIER)
        | (HSA_FENCE_SCOPE_SYSTEM << HSA_PACKET_HEADER_SCACQUIRE_FENCE_SCOPE)
        | (HSA_FENCE_SCOPE_SYSTEM << HSA_PACKET_HEADER_SCRELEASE_FENCE_SCOPE);
    __atomic_store_n(&p->header, hdr, __ATOMIC_RELEASE);

    /* 5. 도어벨 MMIO 쓰기 (syscall 없음!) */
    hsa_signal_store_relaxed(q->doorbell_signal, (hsa_signal_value_t)wi);
}

/* 완료 대기 */
hsa_signal_wait_scacquire(done, HSA_SIGNAL_CONDITION_LT, 1,
                          UINT64_MAX, HSA_WAIT_STATE_BLOCKED);
/* ROCr 런타임 큐 생성 및 완료 대기 패턴 */
hsa_init();

hsa_agent_t gpu;
hsa_iterate_agents(pick_gpu_callback, &gpu);

/* AQL 큐 생성: KFD_IOC_CREATE_QUEUE -> MQD 할당 -> 도어벨 mmap */
hsa_queue_t *q;
hsa_queue_create(gpu, 256, HSA_QUEUE_TYPE_SINGLE,
                 NULL, NULL, UINT32_MAX, UINT32_MAX, &q);

/* 완료 신호 (초기값=1, GPU 완료 후 0이 됨) */
hsa_signal_t done;
hsa_signal_create(1, 0, NULL, &done);

/* 커널 인자 버퍼: GPU 접근 가능 메모리에 할당 */
struct { float *a, *b, *c; int n; } kargs = {d_a, d_b, d_c, N};
void *kernarg;
hsa_memory_allocate(kernarg_region, sizeof(kargs), &kernarg);
memcpy(kernarg, &kargs, sizeof(kargs));

submit_kernel(q, kernel_code_ptr, kernarg, N, 256, done);
hsa_signal_wait_scacquire(done, HSA_SIGNAL_CONDITION_LT, 1,
                          UINT64_MAX, HSA_WAIT_STATE_BLOCKED);

hsa_signal_destroy(done);
hsa_queue_destroy(q);
hsa_shut_down();
AQL 패킷 타입 비교
패킷 타입type 값핵심 필드사용 시나리오
Kernel Dispatch 2 kernel_object, kernarg_address, grid/workgroup_size GPU 컴퓨트 커널 실행 (hipLaunchKernel 구현)
Agent Dispatch 4 type, return_address, arg[0~3] CPU 에이전트 서비스 요청 (GPU to CPU)
Barrier-AND 5 dep_signal[0~4] (5개 신호 모두 0이 되면 완료) 여러 이전 커널 완료 후 다음 커널 실행 허용
Barrier-OR 6 dep_signal[0~4] (5개 중 하나라도 0이면 완료) 경쟁 조건 중 가장 먼저 완료된 것 대기
HSA Signal 주요 연산
함수원자 연산메모리 순서설명
hsa_signal_create(val, ...) 초기화 신호 생성, 초기값 설정 (완료 대기용: 초기값 1)
hsa_signal_store_screlease(s, v) atomic store release 신호 값 설정 (store-release 보장)
hsa_signal_load_scacquire(s) atomic load acquire 신호 값 읽기
hsa_signal_add_screlease(s, v) fetch-add release 신호 값에 v 더하기
hsa_signal_cas_scacq_screl(s, e, v) CAS acq_rel Compare-And-Swap
hsa_signal_wait_scacquire(s, cond, cmp, timeout, mode) 블록/폴링 acquire 조건 충족까지 대기 (BLOCKED=슬립, ACTIVE=스핀)
hsa_signal_destroy(s) 해제 신호 자원 해제
제로-카피 제출의 의미: 전통 GPU 드라이버는 커맨드를 커널 공간으로 복사하고 검증했습니다. AQL은 사용자 공간 링 버퍼에 직접 패킷을 쓰고 MMIO 도어벨 쓰기 하나로 GPU를 깨웁니다. MI300X에서 빈 커널 launch-to-execution 레이턴시는 약 2~5 μs로, 커널 디스패치 경로에 syscall이 없어 오버헤드가 최소화됩니다.
header를 마지막에 써야 하는 이유: GPU CP는 각 슬롯의 header를 폴링합니다. header = 0(Invalid 타입)이면 CP는 해당 슬롯을 건너뜁니다. 모든 필드를 채운 후 header를 __atomic_store_n(..., __ATOMIC_RELEASE)로 써야 CP가 불완전한 패킷을 실행하는 경쟁 조건을 방지합니다.
큐 타입 선택: HSA_QUEUE_TYPE_SINGLE은 단일 CPU 스레드만 패킷을 씁니다. 여러 스레드가 동일 큐에 패킷을 제출하면 HSA_QUEUE_TYPE_MULTI를 사용하고 hsa_queue_add_write_index_scacq_screl()의 CAS 기반 인덱스 획득을 활용해야 합니다. HIP는 기본적으로 스트림당 큐 하나를 사용하므로 스트림을 스레드 간 공유하지 않으면 안전합니다.
Barrier 패킷 실무 활용: hipStreamSynchronize()는 내부적으로 completion_signal이 있는 Barrier-AND 패킷을 큐에 삽입하고 그 신호가 0이 될 때까지 CPU를 대기시킵니다. Barrier-AND의 dep_signal[0~4]를 활용하면 최대 5개 이전 커널 완료를 하나의 패킷으로 대기해 다단계 파이프라인에서 큐 슬롯을 절약할 수 있습니다.

KFD (Kernel Fusion Driver)

KFD(Kernel Fusion Driver)는 Linux 커널의 amdgpu 드라이버 내부에 통합된 컴퓨트 전용 커널 모듈입니다. drivers/gpu/drm/amd/amdkfd/에 위치하며, /dev/kfd 문자 디바이스를 통해 사용자 공간 ROCm 런타임에 GPU 컴퓨트 자원을 노출합니다. KFD의 핵심 설계 목표는 다중 프로세스 안전한 GPU 접근HSA 표준 준수입니다.

KFD는 프로세스별로 독립적인 GPU 주소 공간을 관리합니다. IOMMU를 활용한 PASID(Process Address Space ID) 메커니즘으로 한 프로세스의 GPU 메모리 접근이 다른 프로세스의 메모리를 침범할 수 없도록 격리합니다. 또한 Compute Queue(MQD/HQD)를 프로세스별로 할당하여 GPU 스케줄러가 공정하게 작업을 처리하도록 합니다.

# /dev/kfd 존재 및 권한 확인
ls -la /dev/kfd
# crw-rw---- 1 root render 234, 0 /dev/kfd
# render 그룹 멤버십 필요: sudo usermod -aG render $USER

# KFD topology: 연결된 GPU 정보 조회
ls /sys/bus/event_source/devices/ | grep kfd
cat /sys/class/kfd/kfd/topology/nodes/1/name
# 출력 예: gfx942 (MI300X의 GFX IP 버전)

cat /sys/class/kfd/kfd/topology/nodes/1/mem_banks/0/size_in_bytes
# HBM3 총 바이트 크기

# KFD ioctl 버전 확인 (ROCr이 호출하는 방식)
# GET_VERSION ioctl → struct kfd_ioctl_get_version_args
python3 -c "
import fcntl, struct, os
fd = os.open('/dev/kfd', os.O_RDWR | os.O_CLOEXEC)
KFD_IOC_GET_VERSION = 0x4008AE01  # _IOWR(0xAE, 0x01, ...)
buf = struct.pack('II', 0, 0)
res = fcntl.ioctl(fd, KFD_IOC_GET_VERSION, bytearray(buf))
maj, minor = struct.unpack('II', res)
print(f'KFD version: {maj}.{minor}')
os.close(fd)
"
# KFD topology sysfs: GPU 노드 상세 정보
for node in /sys/class/kfd/kfd/topology/nodes/*/; do
    echo "=== Node: $node ==="
    cat "$node/name" 2>/dev/null
    cat "$node/properties" 2>/dev/null | grep -E "simd_count|max_waves|mem_banks"
done

# 출력 예 (MI300X, 8 XCD):
# simd_count 1792      (CU 수 × 4 SIMD)
# max_waves_per_simd 10
# max_flat_address_watch 4
주요 KFD ioctl 목록 (include/uapi/linux/kfd_ioctl.h)
ioctl 이름기능주요 인자
KFD_IOC_GET_VERSION KFD 인터페이스 버전 조회 major, minor 버전 반환
KFD_IOC_CREATE_QUEUE 컴퓨트 큐 생성 (MQD 할당) queue_type, ring_base_address, ring_size
KFD_IOC_DESTROY_QUEUE 컴퓨트 큐 해제 queue_id
KFD_IOC_SET_MEMORY_POLICY 메모리 정책 설정 (캐시/UC) default_policy, alternate_policy
KFD_IOC_GET_CLOCK_COUNTERS GPU/CPU 클럭 카운터 읽기 gpu_clock_counter, cpu_clock_counter
KFD_IOC_GET_PROCESS_APERTURES 프로세스 메모리 어퍼처 조회 GPUVM, LDS, scratch base/limit
KFD_IOC_ALLOC_MEMORY_OF_GPU GPU 메모리 할당 size, flags (VRAM/GTT/userptr)
KFD_IOC_FREE_MEMORY_OF_GPU GPU 메모리 해제 handle
KFD_IOC_MAP_MEMORY_TO_GPU GPU 주소 공간에 메모리 매핑 handle, device_ids_array_ptr
KFD_IOC_SVM SVM(Shared Virtual Memory) 영역 속성 설정 svm_op, addr, size, nattrs
KFD topology 노드 속성 (sysfs properties 파일)
속성설명MI300X 예시
simd_count 총 SIMD 유닛 수 (CU × 4) 1792
max_waves_per_simd SIMD당 최대 동시 wavefront 10
lds_size_in_kb CU당 LDS 크기 (KB) 64
gds_size_in_kb Global Data Share 크기 (KB) 0 (CDNA3에서 제거)
wave_front_size Wavefront 스레드 수 64
array_count Shader Array 수 32 (MI300X XCD당 4, 8 XCD)
simd_arrays_per_engine 엔진당 SIMD 어레이 4
cu_per_simd_array 어레이당 CU 수 7
PASID (Process Address Space ID): IOMMU가 각 프로세스에 발급하는 고유 식별자입니다. GPU DMA 요청에 PASID를 태그하면 IOMMU가 해당 프로세스의 페이지 테이블로 주소를 변환합니다. 이로써 여러 프로세스가 동시에 GPU를 사용해도 메모리 격리가 보장됩니다. CONFIG_IOMMU_SVA 커널 옵션과 IOMMU 하드웨어 SVA(Shared Virtual Addressing) 지원이 필요합니다.
MQD (Memory Queue Descriptor): GPU 컴퓨트 큐를 기술하는 메모리 구조체입니다. ring buffer 기본 주소, 크기, 읽기/쓰기 포인터, VMID, 우선순위 등의 정보를 담고 있습니다. KFD가 MQD를 생성하면 amdgpu CP(Command Processor)가 이를 HQD(Hardware Queue Descriptor)에 등록하여 실제 GPU가 AQL 패킷을 소비하기 시작합니다.
render 그룹 멤버십: /dev/kfdrender 그룹 소유입니다. ROCm 애플리케이션을 실행하는 사용자는 sudo usermod -aG render,video $USER로 두 그룹 모두에 추가해야 합니다(video는 amdgpu 렌더 노드 접근용). 변경 후 로그아웃/재로그인이 필요합니다.
ATS/PRI (Address Translation Service / Page Request Interface): PCIe ATS를 사용하면 GPU가 IOMMU에 직접 주소 변환을 요청할 수 있습니다. PRI는 GPU가 아직 매핑되지 않은 페이지에 접근할 때 커널에 페이지 폴트를 전달하는 메커니즘입니다. 이 두 기능이 결합되면 CPU와 GPU 간 진정한 shared virtual memory(SVM)가 가능해집니다. KFD의 SVM ioctl은 이 인프라 위에서 동작합니다.

KFD ioctl 내부 동작 상세

KFD의 핵심 인터페이스는 /dev/kfd에 대한 ioctl 호출입니다. 유저스페이스 HSA 런타임은 이 ioctl을 통해 큐 생성, 메모리 할당, 이벤트 등록 등 모든 GPU 자원 관리 작업을 수행합니다. 커널 내부에서는 kfd_ioctl.c가 ioctl을 수신하고 각 명령에 해당하는 핸들러를 호출하며, 핸들러는 결국 amdgpu 백엔드를 통해 실제 하드웨어 자원을 조작합니다.

/* KFD CREATE_QUEUE ioctl 처리 흐름 (커널 내부 단순화) */
/* drivers/gpu/drm/amd/amdkfd/kfd_ioctl.c */

static long kfd_ioctl(struct file *filep, unsigned int cmd, unsigned long arg)
{
    const struct kfd_ioctl_desc *ioctl = NULL;
    kfd_ioctl_t *func;
    void *kdata = NULL;
    unsigned int nr = _IOC_NR(cmd);

    /* ioctl 번호로 핸들러 테이블에서 함수 포인터 조회 */
    if (nr < ARRAY_SIZE(kfd_ioctl_funcs))
        ioctl = &kfd_ioctl_funcs[nr];

    func = ioctl->func;
    /* 유저스페이스 인자를 커널 스택으로 복사 */
    copy_from_user(kdata, (void __user *)arg, ioctl->usize);

    return func(filep, kdata);   /* 예: kfd_ioctl_create_queue() */
}

/* CREATE_QUEUE 핸들러 - 큐 생성 주요 단계 */
static int kfd_ioctl_create_queue(struct file *filep, void *data)
{
    struct kfd_ioctl_create_queue_args *args = data;
    struct kfd_process *p;
    struct kfd_dev *dev;
    int err, queue_id;

    /* filep에서 현재 kfd_process 조회 (프로세스 격리 단위) */
    p = kfd_get_process(current());
    /* gpu_id로 대상 GPU 장치 조회 */
    dev = kfd_device_by_id(args->gpu_id);

    /* MQD 메모리 할당 및 큐 링 버퍼 매핑 */
    err = pqm_create_queue(&p->pqm, dev, filep, &args->queue_properties,
                            &queue_id, NULL, NULL, NULL, NULL);

    args->queue_id = queue_id;
    /* HQD에 MQD 등록 → CP가 AQL 패킷 소비 시작 */
    return err;
}
/* KFD GPU 메모리 할당 경로 */
/* ALLOC_MEMORY_OF_GPU ioctl → amdgpu_amdkfd_gpuvm_alloc_memory */

static int kfd_ioctl_alloc_memory_of_gpu(struct file *filep, void *data)
{
    struct kfd_ioctl_alloc_memory_of_gpu_args *args = data;
    struct kfd_process *p = kfd_get_process(current());
    struct kfd_dev *dev = kfd_device_by_id(args->gpu_id);
    void *mem;
    uint64_t offset;

    /* amdgpu TTM을 통한 VRAM/GTT 버퍼 할당 */
    int err = amdgpu_amdkfd_gpuvm_alloc_memory_of_gpu(
        dev->adev,        /* amdgpu_device 포인터 */
        args->va_addr,    /* 가상 주소 힌트 */
        args->size,       /* 할당 크기 (바이트) */
        p->mm,            /* 프로세스 VM */
        &mem,             /* 출력: BO 핸들 */
        &offset,          /* 출력: mmap offset */
        args->flags       /* ALLOC_MEM_FLAGS_VRAM 등 */
    );
    if (err) return err;

    /* kfd_process_device의 BO 목록에 등록 (eviction 추적용) */
    err = kfd_process_device_create_obj_handle(
        kfd_get_process_device_data(dev, p), mem);

    args->handle = err;
    args->mmap_offset = offset;
    return 0;
}

/* 할당 플래그 비트 필드 */
#define KFD_IOC_ALLOC_MEM_FLAGS_VRAM        (1 << 0)  /* GPU VRAM에 할당 */
#define KFD_IOC_ALLOC_MEM_FLAGS_GTT         (1 << 1)  /* CPU 접근 가능 GTT */
#define KFD_IOC_ALLOC_MEM_FLAGS_USERPTR     (1 << 2)  /* 기존 CPU 메모리 핀닝 */
#define KFD_IOC_ALLOC_MEM_FLAGS_DOORBELL    (1 << 3)  /* doorbell 페이지 */
#define KFD_IOC_ALLOC_MEM_FLAGS_WRITABLE    (1 << 31) /* GPU 쓰기 가능 */
#define KFD_IOC_ALLOC_MEM_FLAGS_EXECUTABLE  (1 << 30) /* 셰이더 실행 가능 */
KFD 프로세스 상태 (kfd_process 생명주기)
상태설명트리거복구 경로
KFD_PROC_STATUS_RUNNING정상 실행 중. GPU 큐가 활성화되어 AQL 패킷을 소비함큐 생성 완료
KFD_PROC_STATUS_EVICTED메모리 압박으로 VRAM이 시스템 RAM으로 스왑아웃됨. 큐 일시 중단TTM 메모리 회수 요청restore 이벤트 대기
KFD_PROC_STATUS_RESTORING메모리 복구 중. VRAM 재할당 및 페이지 테이블 재구성GPU 메모리 여유 발생복구 완료 시 RUNNING으로 전환
KFD_PROC_STATUS_SUSPEND시스템 절전 중. 모든 GPU 작업 동결PM suspend 이벤트resume 후 RUNNING 복귀
KFD_PROC_STATUS_UNSET초기화 전 또는 종료 후 상태프로세스 시작/종료
kfd_process vs kfd_process_device: kfd_process는 Linux 프로세스(PID) 단위의 최상위 컨텍스트입니다. 시스템에 GPU가 여러 개 있을 때 각 GPU와의 관계는 kfd_process_device 구조체로 표현됩니다. 즉, 프로세스 하나가 N개 GPU에 접근하면 kfd_process 1개 + kfd_process_device N개가 생성됩니다. 이 구조가 다중 GPU 컨텍스트 격리와 eviction 추적의 기본 단위입니다.
ioctl 인터페이스 안정성: KFD ioctl 인터페이스는 UAPI(User-space API)이므로 커널 버전 간 하위 호환성이 보장됩니다. 구조체 크기를 _IOC_SIZE(cmd)로 확인하여 미래 버전에서 필드가 추가되어도 이전 바이너리가 동작할 수 있도록 설계되어 있습니다. 새 필드를 추가할 때는 항상 구조체 끝에 붙이고 나머지를 0으로 초기화해야 합니다.

KFD 프로세스 Eviction과 복구

KFD 프로세스 eviction은 시스템 메모리 압박 시 GPU VRAM을 회수하기 위해 실행 중인 GPU 프로세스를 일시적으로 중단하고 그 메모리를 시스템 RAM으로 이동하는 메커니즘입니다. ROCm 런타임은 이 과정에서 애플리케이션이 크래시하지 않도록 투명하게 처리합니다. amdgpu TTM의 메모리 회수 콜백이 KFD에 eviction을 요청하며, KFD는 해당 프로세스의 모든 큐를 중단하고 BO(Buffer Object)를 GTT(CPU 접근 가능 영역)로 이동시킵니다.

/* KFD Eviction 흐름 (간략화) */
/* drivers/gpu/drm/amd/amdkfd/kfd_process.c */

/* TTM이 메모리 회수 필요 시 호출하는 진입점 */
int kgd2kfd_schedule_evict_and_restore_process(
    struct mm_struct *mm, struct dma_fence *fence)
{
    struct kfd_process *p = kfd_lookup_process_by_mm(mm);
    if (!p) return -ESRCH;

    if (fence)
        dma_fence_add_callback(fence, &p->eviction_fence_cb, evict_callback);
    else
        queue_delayed_work(kfd_wq, &p->eviction_work, 0);
    return 0;
}

/* 실제 eviction 워크큐 함수 */
static void evict_process_worker(struct work_struct *work)
{
    struct kfd_process *p =
        container_of(work, struct kfd_process, eviction_work.work);

    /* 1단계: 모든 큐를 HQD에서 unmap → GPU 실행 중단 */
    kfd_process_evict_queues(p, KFD_QUEUE_EVICTION_TRIGGER_TTM);

    /* 2단계: 모든 BO를 VRAM → GTT로 이동 */
    amdgpu_amdkfd_evict_userptr(p->mm, p->priv);

    /* 3단계: restore 워크 스케줄 (메모리 여유 생기면 복구) */
    queue_delayed_work(kfd_restore_wq, &p->restore_work, KFD_RESTORE_DELAY_MS);
    p->process_status = KFD_PROC_STATUS_EVICTED;
}

/* 복구 워크큐 함수 */
static void restore_process_worker(struct work_struct *work)
{
    struct kfd_process *p =
        container_of(work, struct kfd_process, restore_work.work);
    int ret;

    p->process_status = KFD_PROC_STATUS_RESTORING;

    /* 1단계: GTT → VRAM으로 BO 다시 이동 */
    ret = amdgpu_amdkfd_restore_userptr(p->mm, p->priv);
    if (ret) {
        /* VRAM 여전히 부족: 재시도 */
        queue_delayed_work(kfd_restore_wq, &p->restore_work, KFD_RESTORE_RETRY_MS);
        return;
    }

    /* 2단계: GPU 페이지 테이블 재구성 및 큐 복구 */
    kfd_process_restore_queues(p);
    p->process_status = KFD_PROC_STATUS_RUNNING;
}
KFD Eviction 트리거 유형
트리거열거값원인처리 방식
TTM 메모리 회수KFD_QUEUE_EVICTION_TRIGGER_TTM시스템 VRAM 부족, 다른 프로세스 메모리 요구BO를 GTT로 이동 후 복구 대기
GPU 리셋KFD_QUEUE_EVICTION_TRIGGER_GPU_RESETGPU hang, 펌웨어 오류큐 재생성 필요, 일부 BO 재할당
절전 모드KFD_QUEUE_EVICTION_TRIGGER_SUSPEND시스템 suspend/hibernation완전 동결, resume 시 전체 복구
디버거 개입KFD_QUEUE_EVICTION_TRIGGER_DEBUGGERROCgdb 디버깅 세션큐만 중단, 메모리 유지
SVM 압박KFD_QUEUE_EVICTION_TRIGGER_SVMhipMallocManaged 메모리 페이지 마이그레이션SVM migrator가 페이지 이동
Eviction 중 애플리케이션 동작: 프로세스 eviction 동안 유저스페이스 HIP 커널 호출은 블로킹 상태로 대기합니다. HSA 런타임이 kfd_wait_on_events 내에서 signal 기반으로 복구 완료를 기다립니다. 하지만 타임아웃(기본 30초)을 초과하면 HSA_STATUS_ERROR_OUT_OF_RESOURCES로 실패합니다. 메모리 압박이 심한 환경에서는 컨테이너의 메모리 상한을 GPU VRAM보다 작게 설정하거나 amdgpu.vm_size 파라미터를 조정하세요.
Eviction 모니터링: 실시간 eviction 이벤트는 echo 1 > /sys/module/amdgpu/parameters/dc_log_leveldmesg | grep -i evict로 추적합니다. rocm-smi --showmeminfo vram으로 현재 VRAM 사용량을 모니터링하고, /sys/class/kfd/kfd/topology/nodes/N/properties에서 노드 속성도 확인하세요.

amdgpu 커널 드라이버

amdgpu는 Linux mainline에 포함된 AMD GPU의 통합 DRM 드라이버입니다. drivers/gpu/drm/amd/ 하위에 위치하며, IP 블록 기반 설계로 GFX, SDMA, VCN, DCN, PSP, SMU, NBIO 등 각 하드웨어 IP를 독립적으로 초기화하고 관리합니다. 각 IP 블록은 amdgpu_ip_block_version 구조체를 통해 드라이버에 등록되며, GPU 세대(GFX9/GFX10/GFX11/GFX12)에 따라 다른 IP 구현을 자동으로 선택합니다.

# amdgpu 모듈 파라미터 확인
modinfo amdgpu | grep -E "^parm:"

# 주요 파라미터 설정 예 (/etc/modprobe.d/amdgpu.conf)
# options amdgpu ip_block_mask=0xff  # 특정 IP 블록 비트마스크로 활성화
# options amdgpu gpu_recovery=1     # GPU hang 시 자동 복구
# options amdgpu gttsize=8192       # GTT 최대 크기 (MB)
# options amdgpu exp_hw_support=1   # 실험적 GPU 지원 활성화
# options amdgpu ppfeaturemask=0xffffffff  # 전원 관리 기능 비트마스크
# options amdgpu runpm=1            # runtime PM 활성화

# 현재 로드된 파라미터 확인
cat /sys/module/amdgpu/parameters/gpu_recovery
cat /sys/module/amdgpu/parameters/gttsize
# dmesg에서 amdgpu 초기화 로그 확인
dmesg | grep -E "amdgpu|kfd" | head -50

# 정상 초기화 예시 출력:
# [    5.234] amdgpu 0000:03:00.0: amdgpu: Trusted Memory Zone (TMZ) enabled
# [    5.310] amdgpu 0000:03:00.0: amdgpu: VRAM: 49152M 0x0000008000000000 - 0x000000BFFFFFFFFF (49152M used)
# [    5.311] amdgpu 0000:03:00.0: amdgpu: GART: 512M 0x0000000000000000 - 0x000000001FFFFFFF
# [    5.312] amdgpu 0000:03:00.0: amdgpu: PSP is initialized, GFX version: 9.4.2
# [    5.890] kfd kfd: Initialized module
# [    5.891] kfd kfd: Added apertures for GPU 0000:03:00.0

# amdgpu debugfs 활용
ls /sys/kernel/debug/dri/0/
# amdgpu_fence_info  amdgpu_vm_info  amdgpu_gtt_mm  amdgpu_eviction_stats ...
cat /sys/kernel/debug/dri/0/amdgpu_fence_info
amdgpu IP 블록 역할 요약
IP 블록풀네임역할컴퓨트 관련성
GFX Graphics & Compute Engine 셰이더 실행, CP(Command Processor), 렌더 파이프라인 핵심 — 컴퓨트 큐 처리
SDMA System DMA Engine GPU ↔ CPU 메모리 DMA, memcpy/memset/scatter-gather hipMemcpy 백엔드
PSP Platform Security Processor 펌웨어 서명 검증, TEE, TMZ(Trusted Memory Zone) 펌웨어 로딩 필수
SMU System Management Unit 클럭/전압/온도/TDP 관리, DVFS, throttling 성능 상한 결정
NBIO North Bridge I/O PCIe 링크, XGMI/Infinity Fabric 인터커넥트 Multi-GPU 통신
UMC Unified Memory Controller HBM3/GDDR6 컨트롤러, ECC 관리 메모리 대역폭 결정
VCN Video Codec Engine H.264/H.265/AV1 하드웨어 인코딩/디코딩 컴퓨트와 무관
DCN Display Core Next KMS 디스플레이 파이프라인, CRTC/plane CDNA에는 없음
IP 버전 번호 읽기: amdgpu는 IP 블록을 버전으로 구분합니다. 예를 들어 gfx942는 GFX IP major=9, minor=4, revision=2를 의미하며 MI300X를 나타냅니다. gfx1100은 RDNA3(RX 7900 시리즈)입니다. 커널 소스의 drivers/gpu/drm/amd/include/amd_shared.h에서 IP enum을 확인할 수 있습니다.
펌웨어 파일: amdgpu는 GPU 초기화 시 PSP를 통해 여러 펌웨어를 로드합니다. /lib/firmware/amdgpu/ 디렉터리에 있으며, 예를 들어 MI300X는 gc_9_4_3_mec.bin(GFX MEC 펌웨어), sdma_6_1_0.bin(SDMA 펌웨어), psp_14_0_0_sos.bin(PSP SOS 펌웨어) 등을 필요로 합니다. 패키지 매니저로 설치: apt install firmware-amdgpu (Debian/Ubuntu) 또는 linux-firmware 패키지.
CDNA GPU에서 DCN 없음: MI200(CDNA2)/MI300X(CDNA3) 같은 서버용 GPU는 디스플레이 엔진(DCN)이 없습니다. 이 GPU로는 화면 출력이 불가하며, 서버 환경에서는 amdgpu.dc=0 또는 nomodeset으로 부팅하거나 별도의 그래픽 카드를 디스플레이 전용으로 사용합니다. rocm-smi와 ROCm 라이브러리는 정상 동작합니다.

AMDGCN ISA 개요

AMDGCN(AMD Graphics Core Next)은 AMD GPU의 실제 하드웨어 ISA(Instruction Set Architecture)입니다. CUDA의 PTX가 가상 ISA인 것과 달리, AMDGCN은 진짜 하드웨어 ISA로 hipcc가 생성하는 최종 바이너리가 바로 이 명령어 집합으로 구성됩니다. 커널 개발자가 AMDGCN ISA를 이해하면 컴파일러가 생성한 코드를 직접 분석하고, 레지스터 압박이나 메모리 접근 패턴을 최적화하거나, 인라인 어셈블리로 특수 명령어를 활용할 수 있습니다.

AMDGCN ISA의 핵심 특징은 스칼라(SALU)와 벡터(VALU) 파이프라인의 분리입니다. 스칼라 연산은 Wavefront 내 모든 스레드가 동일하게 수행하는 제어 흐름과 상수 계산에 사용되고, 벡터 연산은 각 스레드가 독립적으로 다른 데이터에 대해 수행하는 SIMD 연산입니다. SGPR(스칼라 레지스터)은 Wavefront당 공유되며, VGPR(벡터 레지스터)은 스레드별 독립 레인을 가집니다.

또 하나의 중요한 특징은 Wave32 vs Wave64 모드입니다. GFX9(Vega/CDNA) 계열은 64개 스레드 Wavefront(Wave64)만 지원하지만, GFX10(RDNA)/GFX11(RDNA3)은 32개 스레드 Wavefront(Wave32)를 기본으로 사용합니다. Wave32는 분기 비용이 낮고 점유율 관리가 유리하지만, 64개 float 연산당 레지스터 오버헤드가 증가합니다.

명령어 범주

AMDGCN 명령어 범주 및 인코딩 형식
범주약어인코딩대표 명령어특징
Vector ALU VALU VOP1/VOP2/VOP3 v_add_f32, v_fma_f32, v_mad_f32 스레드별 독립 연산, VGPR 사용, Wave64: 64 FP32/사이클
Scalar ALU SALU SOP1/SOP2/SOPC s_add_u32, s_mul_i32, s_cmp_eq_u32 Wavefront 공유, SGPR 사용, 제어 흐름 및 상수 계산
Vector Memory VMEM MUBUF/MTBUF/MIMG buffer_load_dword, global_load_b32 전역 메모리 로드/스토어, L1/L2 캐시 경유, 고레이턴시
Local Data Share LDS DS ds_read_b32, ds_write_b32, ds_add_u32 CU 내 공유 메모리, 저레이턴시, 블록 내 스레드 간 공유
Scalar Memory SMEM SMEM s_buffer_load_dword, s_load_dword 상수 버퍼 로드, SGPR에 저장, L2 캐시 경유
Flat FLAT FLAT flat_load_dword, flat_store_dword 통합 주소 공간 (VRAM/LDS/private 모두 접근)
Export EXP EXP exp (그래픽 파이프라인 출력) 그래픽 셰이더 전용 (컴퓨트에서는 미사용)
Branch/Flow SOPP SOPP s_branch, s_cbranch_vccnz, s_waitcnt 제어 흐름, 메모리 완료 대기, 장벽(barrier)

레지스터 파일과 점유율

AMDGCN에서 성능 최적화의 핵심은 레지스터 압박(register pressure)과 점유율(occupancy) 간의 균형입니다. 각 CU는 VGPR 풀을 여러 Wavefront가 공유합니다. CDNA2(MI200) 기준으로 CU당 512개의 AGPR(Accumulation VGPR)을 포함한 총 512 VGPR을 보유합니다. Wavefront당 사용하는 VGPR 수가 적을수록 더 많은 Wavefront가 CU에 동시에 상주할 수 있어 메모리 레이턴시를 숨길 수 있습니다.

# hipcc로 컴파일 시 임시 어셈블리 파일 저장
hipcc -save-temps -O3 --offload-arch=gfx942 vectoradd.hip -o vectoradd

# 생성된 .s 파일 (ISA 어셈블리) 확인
ls /tmp/vectoradd-*.s
cat /tmp/vectoradd-hip-amdgcn-amd-amdhsa-gfx942.s

# llvm-objdump으로 오브젝트 파일 역어셈블
/opt/rocm/llvm/bin/llvm-objdump -d vectoradd | grep -A 50 "vectorAdd:"

# 커널의 VGPR/SGPR 사용량 확인 (metadata에서)
/opt/rocm/llvm/bin/llvm-objdump --amdgpu-kernel-metadata vectoradd | grep -E "vgpr|sgpr|lds"
# 출력 예:
#   .vgpr_count:     12    (Wavefront당 VGPR 12개 사용)
#   .sgpr_count:     20    (SGPR 20개 사용)
#   .lds_size:       0     (LDS 미사용)

# 점유율 계산 (gfx942, 512 VGPR pool)
# VGPR_per_wf=12 → 할당 단위 4로 올림 → 12 → 512/12=42 wavefront/CU 가능
python3 -c "
vgpr_used = 12
vgpr_granule = 4  # GFX9 할당 단위
vgpr_alloc = ((vgpr_used + vgpr_granule - 1) // vgpr_granule) * vgpr_granule
max_wf_per_cu = 512 // vgpr_alloc
print(f'VGPR 사용: {vgpr_used} -> 할당: {vgpr_alloc} -> 최대 WF/CU: {max_wf_per_cu}')
"
AMDGCN 레지스터 파일 사양 (GFX 세대별)
항목GFX9 (Vega/CDNA2)GFX10 (RDNA1/2)GFX11 (RDNA3)GFX942 (CDNA3/MI300X)
Wavefront 크기 64 threads 32 threads (기본) 32 threads 64 threads
VGPR/CU 총량 256 512 (RDNA2) 1536 512 VGPR + 512 AGPR
최대 VGPR/Wavefront 256 256 256 256 VGPR + 256 AGPR
VGPR 할당 단위 4개씩 8개씩 8개씩 4개씩
최대 SGPR/Wavefront 104 108 108 104
SGPR 할당 단위 8개씩 8개씩 8개씩 8개씩
LDS/CU 64 KB 128 KB 128 KB 64 KB
최대 Wavefront/CU 40 20 (Wave32 기준) 16 8 Wavefront/SIMD (CU당 4 SIMD)
AGPR 지원 없음 없음 없음 있음 (Matrix core 입출력용)

인라인 어셈블리 활용

# AMDGCN 어셈블리 예: 벡터 덧셈 커널 (gfx942)
# 다음은 hipcc -save-temps 로 생성된 .s 파일 구조 예시
#
# .amdgpu_metadata 섹션: 커널 메타데이터
#   .group_segment_fixed_size: 0   (LDS 미사용)
#   .private_segment_fixed_size: 0  (scratch 미사용)
#   .vgpr_count: 10
#   .sgpr_count: 22
#
# 커널 진입점 (v_add_f32 예시):
#   s_load_dwordx4  s[0:3], s[4:5], 0x0    ; 커널 인자 로드 (SGPR)
#   s_load_dwordx2  s[4:5], s[6:7], 0x18   ; N 값 로드
#   v_lshlrev_b32   v0, 2, v0              ; threadIdx * 4 (byte offset)
#   s_waitcnt       lgkmcnt(0)             ; 스칼라 메모리 로드 완료 대기
#   global_load_dword v1, v[0:1], s[0:1]  ; A[idx] 로드
#   global_load_dword v2, v[0:1], s[2:3]  ; B[idx] 로드
#   s_waitcnt       vmcnt(0)              ; 벡터 메모리 로드 완료 대기
#   v_add_f32       v1, v1, v2            ; C = A + B
#   global_store_dword v[0:1], v1, s[4:5] ; C[idx] 저장
#   s_endpgm                              ; 커널 종료

# s_waitcnt 의미: 특정 메모리 연산 완료까지 대기
#   vmcnt(N): N개의 벡터 메모리 연산이 완료될 때까지 대기
#   lgkmcnt(N): N개의 LDS/GDS/스칼라 메모리 연산이 완료될 때까지 대기
#   expcnt(N): N개의 export 연산이 완료될 때까지 대기
#   s_waitcnt 0: 모든 종류의 메모리 연산 완료 대기 (완전 동기화)
/* HIP 커널 내 AMDGCN 인라인 어셈블리 활용 예제 */
#include <hip/hip_runtime.h>

/* 예제 1: EXEC 마스크 읽기 (활성화된 레인 수 확인) */
__device__ uint64_t get_exec_mask()
{
    uint64_t exec;
    __asm__ volatile("s_mov_b64 %0, exec" : "=s"(exec));
    return exec;
}

/* 예제 2: s_barrier 명령어로 Wavefront 간 동기화 */
__device__ void gpu_barrier()
{
    __asm__ volatile("s_barrier" ::: "memory");
    /* __syncthreads()와 동일 — LDS 데이터 일관성 보장 */
}

/* 예제 3: v_cvt_f16_f32 — FP32를 FP16으로 변환 (WMMA 입력용) */
__device__ __half fp32_to_fp16(float x)
{
    uint32_t result;
    __asm__ volatile(
        "v_cvt_f16_f32 %0, %1"
        : "=v"(result)
        : "v"(x)
    );
    return (__half)(result & 0xFFFF);
}

/* 예제 4: ds_bpermute — LDS 배열을 통한 레인 간 데이터 교환 */
__device__ float lane_shuffle(float val, int src_lane)
{
    float result;
    __asm__ volatile(
        "ds_bpermute_b32 %0, %1, %2"
        : "=v"(result)
        : "v"(src_lane * 4), "v"(val)
    );
    return result;
}

/* 예제 5: v_mad_f32 — FMA(Fused Multiply-Add) 강제 사용 */
__device__ float fma_explicit(float a, float b, float c)
{
    float result;
    __asm__ volatile(
        "v_fma_f32 %0, %1, %2, %3"
        : "=v"(result)
        : "v"(a), "v"(b), "v"(c)
    );
    return result;
}

/* 예제 6: s_waitcnt vmcnt(0) — 메모리 펜스 */
__device__ void vmem_fence()
{
    __asm__ volatile("s_waitcnt vmcnt(0)" ::: "memory");
    /* 모든 벡터 메모리 연산 완료 보장 — __threadfence()의 저수준 구현 */
}
# AMDGCN 커널 분석 실무 워크플로우

# 1. 커널 컴파일 및 임시 파일 저장
hipcc -save-temps -O3 --offload-arch=gfx942 -o kernel kernel.hip

# 2. GFX 코드 오브젝트 추출
/opt/rocm/llvm/bin/clang-offload-bundler --unbundle     --type=o --targets=hip-amdgcn-amd-amdhsa-gfx942     --inputs=kernel --outputs=kernel_gfx942.o

# 3. 역어셈블 (상세 주석 포함)
/opt/rocm/llvm/bin/llvm-objdump -d --disassemble-all kernel_gfx942.o | head -100

# 4. 커널 메타데이터 확인 (VGPR/SGPR/LDS 사용량)
/opt/rocm/llvm/bin/llvm-objdump --amdgpu-kernel-metadata kernel_gfx942.o

# 5. rocprof로 실제 레지스터 사용 통계 수집
rocprof --stats -i rocprof_input.txt ./kernel
# rocprof_input.txt 내용 예:
# pmc: VFetchInsts VWriteInsts LDSBankConflict

# 6. omniperf 전체 분석 (레지스터 압박 + 점유율 + 메모리 접근 패턴)
omniperf profile -n vectoradd -- ./vectoradd
omniperf analyze -p workloads/vectoradd/ --roof-only
Wave32 vs Wave64 선택: RDNA(GFX10/GFX11) GPU에서는 컴파일러가 기본적으로 Wave32를 사용합니다. Wave32는 분기 발산 비용이 낮고, 같은 수의 스레드에 대해 Wavefront 수가 두 배이므로 레이턴시 숨기기(latency hiding)가 유리합니다. 반면 CDNA(GFX9/GFX942)는 Wave64를 사용하며, 동일한 VGPR 예산에서 두 배의 데이터를 처리합니다. hipcc --offload-arch=gfx1100 -mwavefrontsize64로 Wave64 강제 설정도 가능합니다.
VGPR 할당 단위와 점유율: GFX9는 VGPR을 4개 단위로 할당합니다. 커널이 VGPR 5개를 사용해도 8개가 할당됩니다. CU당 VGPR 풀이 256개이면, VGPR 8개 커널은 32 Wavefront/CU 동시 실행이 가능하지만 VGPR 64개 커널은 4 Wavefront/CU만 가능합니다. __launch_bounds__(blockDim, minWavesPerEU)로 컴파일러에 목표 점유율을 힌트로 줄 수 있습니다.
인라인 어셈블리 사용 주의: HIP 인라인 어셈블리는 GCC/Clang Extended Asm 문법을 사용합니다. constraint "v"는 VGPR, "s"는 SGPR, "a"는 AGPR을 의미합니다. 잘못된 constraint 사용은 컴파일 오류 또는 런타임 크래시를 유발합니다. 특히 s_waitcnt를 빠뜨리면 메모리 순서 오류로 데이터 레이스가 발생할 수 있습니다.
AGPR (Accumulation VGPR): CDNA2(MI200) 이상에서 도입된 특수 레지스터입니다. Matrix FMA 명령어(v_mfma_f32_32x32x2f32 등)의 누산기(accumulator)로 사용되며, AGPR는 일반 VGPR 연산에 직접 접근할 수 없어 v_accvgpr_write/read로 VGPR과 교환합니다. hipBLASLt와 MIOpen의 GEMM 커널은 AGPR를 사용하여 고성능 행렬 곱 연산을 수행합니다.

HIP 프로그래밍 모델

HIP(Heterogeneous-compute Interface for Portability)는 AMD가 개발한 GPU 프로그래밍 모델로, CUDA와 거의 동일한 문법과 API를 제공합니다. hipcc 컴파일러로 AMD GPU에서는 amdgpu 백엔드를, NVIDIA GPU에서는 CUDA 백엔드를 투명하게 사용합니다. 이로써 단일 소스 코드베이스로 두 플랫폼을 동시에 지원할 수 있습니다.

HIP의 실행 모델은 CUDA와 동일한 계층 구조를 따릅니다. 최상위는 Grid(전체 커널 실행 단위)이며, Grid는 여러 Block으로, Block은 여러 Thread로 구성됩니다. AMD GPU에서는 32개 또는 64개의 Thread가 하나의 Wavefront(NVIDIA의 Warp에 해당)를 이루어 SIMD 방식으로 동시에 실행됩니다.

/* HIP 벡터 덧셈 예제: CUDA 문법과 99% 동일 */
#include <hip/hip_runtime.h>
#include <cstdio>

/* GPU 커널 함수: __global__ 키워드로 GPU에서 실행됨을 표시 */
__global__ void vectorAdd(const float *A, const float *B,
                           float *C, int N)
{
    /* 각 스레드가 처리할 전역 인덱스 계산 */
    int idx = blockIdx.x * blockDim.x + threadIdx.x;
    if (idx < N) {
        C[idx] = A[idx] + B[idx];
    }
}

int main()
{
    const int N = 1024 * 1024;
    const size_t bytes = N * sizeof(float);

    /* GPU 메모리 할당 */
    float *d_A, *d_B, *d_C;
    hipMalloc(&d_A, bytes);
    hipMalloc(&d_B, bytes);
    hipMalloc(&d_C, bytes);

    /* 호스트→GPU 데이터 복사 (초기화 코드 생략) */
    /* hipMemcpy(d_A, h_A, bytes, hipMemcpyHostToDevice); */

    /* 커널 실행: <<>> 문법 */
    int blockSize = 256;
    int gridSize  = (N + blockSize - 1) / blockSize;
    vectorAdd<<<gridSize, blockSize>>>(d_A, d_B, d_C, N);

    /* 커널 완료 대기 */
    hipDeviceSynchronize();

    /* 메모리 해제 */
    hipFree(d_A); hipFree(d_B); hipFree(d_C);
    return 0;
}
/* 공유 메모리(LDS) 활용 예: 타일 행렬 곱셈 */
#define TILE 16

__global__ void tiledMatMul(const float *A, const float *B,
                              float *C, int N)
{
    /* __shared__: LDS(Local Data Share) 할당 — CU 내 모든 스레드 공유 */
    __shared__ float sA[TILE][TILE];
    __shared__ float sB[TILE][TILE];

    int tx = threadIdx.x, ty = threadIdx.y;
    int row = blockIdx.y * TILE + ty;
    int col = blockIdx.x * TILE + tx;
    float sum = 0.0f;

    for (int t = 0; t < N / TILE; ++t) {
        /* 타일 로드 */
        sA[ty][tx] = A[row * N + t * TILE + tx];
        sB[ty][tx] = B[(t * TILE + ty) * N + col];
        /* 블록 내 스레드 동기화 (LDS 쓰기 완료 보장) */
        __syncthreads();

        for (int k = 0; k < TILE; ++k)
            sum += sA[ty][k] * sB[k][tx];
        __syncthreads();
    }
    C[row * N + col] = sum;
}
/* 스트림(비동기 실행)과 이벤트 사용 예 */
hipStream_t stream0, stream1;
hipStreamCreate(&stream0);
hipStreamCreate(&stream1);

hipEvent_t startEvent, stopEvent;
hipEventCreate(&startEvent);
hipEventCreate(&stopEvent);

/* 스트림 0: 비동기 복사 후 커널 실행 */
hipMemcpyAsync(d_A, h_A, bytes, hipMemcpyHostToDevice, stream0);
hipEventRecord(startEvent, stream0);
myKernel<<<grid, block, 0, stream0>>>(d_A, d_C, N);
hipEventRecord(stopEvent, stream0);

/* 스트림 1: 독립적 작업 동시 실행 */
hipMemcpyAsync(d_B, h_B, bytes, hipMemcpyHostToDevice, stream1);
myKernel<<<grid, block, 0, stream1>>>(d_B, d_D, N);

/* 두 스트림 모두 완료 대기 */
hipStreamSynchronize(stream0);
hipStreamSynchronize(stream1);

/* 실행 시간 측정 */
float ms;
hipEventElapsedTime(&ms, startEvent, stopEvent);
printf("Kernel time: %.3f ms\n", ms);

hipStreamDestroy(stream0);
hipStreamDestroy(stream1);
/* HIP 에러 처리 — 모든 HIP API 호출에 적용 권장 */
#define HIP_CHECK(call)                                              \
    do {                                                              \
        hipError_t err = (call);                                      \
        if (err != hipSuccess) {                                      \
            fprintf(stderr, "HIP error %s:%d: %s\n",               \
                    __FILE__, __LINE__, hipGetErrorString(err));      \
            exit(1);                                                  \
        }                                                             \
    } while(0)

/* 디바이스 속성 조회 */
hipDeviceProp_t prop;
HIP_CHECK(hipGetDeviceProperties(&prop, 0));
printf("GPU: %s\n", prop.name);
printf("VRAM: %zu MB\n", prop.totalGlobalMem / (1024*1024));
printf("CU count: %d\n", prop.multiProcessorCount);
printf("Max threads/block: %d\n", prop.maxThreadsPerBlock);
printf("Warp size: %d\n", prop.warpSize);  /* AMD: 64 */
printf("L2 cache: %d KB\n", prop.l2CacheSize / 1024);
printf("Memory BW: %.1f GB/s\n",
       2.0 * prop.memoryClockRate * (prop.memoryBusWidth / 8) / 1e6);
CUDA ↔ HIP 주요 API 대응표
CUDA APIHIP API설명
cudaMallochipMallocGPU 메모리 할당
cudaFreehipFreeGPU 메모리 해제
cudaMemcpyhipMemcpy메모리 복사
cudaMemcpyAsynchipMemcpyAsync비동기 메모리 복사
cudaMallocManagedhipMallocManaged통합 메모리 할당
cudaStream_thipStream_t비동기 실행 스트림
cudaEvent_thipEvent_t타이밍/동기화 이벤트
cudaDeviceSynchronizehipDeviceSynchronize디바이스 완료 대기
cudaGetDevicePropertieshipGetDeviceProperties디바이스 속성 조회
cudaSetDevicehipSetDevice활성 GPU 선택
cudaGetErrorStringhipGetErrorString에러 문자열 변환
__shared____shared__공유 메모리(LDS) 선언
__syncthreads()__syncthreads()블록 내 스레드 동기화
threadIdx, blockIdxthreadIdx, blockIdx스레드/블록 인덱스
cublasHandle_trocblas_handleBLAS 핸들 (라이브러리 차이)
cudnnHandle_tmiopenHandle_t딥러닝 라이브러리 핸들
hipcc 컴파일 명령: hipcc -O3 -o vectorAdd vectorAdd.cpp로 컴파일합니다. AMD GPU 타겟 아키텍처를 명시하려면 --offload-arch=gfx942(MI300X)처럼 지정합니다. 여러 아키텍처를 동시에 지원하는 팻 바이너리도 가능합니다: hipcc --offload-arch=gfx1100 --offload-arch=gfx942 -o myapp myapp.cpp
Wavefront 크기 주의: AMD GPU의 기본 wavefront 크기는 64입니다. RDNA1/2(GFX10)는 wave32 모드(32 threads)도 지원하며, __attribute__((amdgpu_waves_per_eu(n)))로 조정할 수 있습니다. NVIDIA의 warp 크기인 32와 다르므로, CUDA에서 warp size를 32로 가정한 코드는 AMD에서 성능 문제가 생길 수 있습니다. hipDeviceProp_t::warpSize를 항상 동적으로 조회하세요.
__syncthreads() 범위: HIP/CUDA의 __syncthreads()는 블록 내 스레드만 동기화합니다. 블록 간 동기화는 커널 종료 후 hipDeviceSynchronize() 또는 cooperative groups를 사용해야 합니다. AMD GPU에서는 블록이 같은 CU에 배치될 때만 LDS 공유가 의미 있으며, 서로 다른 CU에 배치된 블록 간에는 L2 캐시를 통해서만 데이터를 교환할 수 있습니다.
HIP Math 함수: hip/hip_math_constants.hhip/hip_fp16.h에서 half-precision(FP16) 및 BF16 타입을 제공합니다. AI 학습에 중요한 __half, hip_bfloat16 타입과 연산 함수가 포함됩니다. ROCm 6.x에서는 FP8(E4M3/E5M2) 타입도 지원이 추가되었습니다.

hipGraph API

CUDA Graph의 AMD 동등 구현인 hipGraph는 GPU 작업(커널 실행, 메모리 복사, 이벤트 등)을 유향 비순환 그래프(DAG)로 정의하여 반복 실행 비용을 대폭 낮추는 API입니다. 일반 스트림 실행에서는 매 반복마다 CPU가 커널 실행·동기화·메모리 복사 명령을 각각 드라이버에 제출하고, 드라이버는 이를 AQL 패킷으로 변환하여 GPU 큐에 기록합니다. 이 경로의 CPU 오버헤드는 커널 하나당 수 마이크로초 수준이며, 짧은 커널이 수백 개 이어지는 딥러닝 레이어별 실행 구간에서 병목이 됩니다.

hipGraph를 사용하면 첫 번째 반복에서 캡처(capture) 단계를 통해 작업 그래프를 기록하고, 인스턴스화(instantiate)로 실행 가능한 그래프 오브젝트를 생성한 뒤, 이후 반복에서는 단일 hipGraphLaunch() 호출만으로 전체 그래프를 GPU에 제출합니다. CPU 제출 오버헤드가 그래프당 O(1)로 고정되어, 반복 횟수가 많을수록 이득이 커집니다.

스트림 캡처 방식

스트림 캡처는 hipStreamBeginCapture()hipStreamEndCapture() 사이에 발생하는 모든 GPU 작업을 자동으로 기록하는 편리한 방법입니다. 기존 스트림 기반 코드를 최소한의 수정으로 그래프로 전환할 수 있습니다. 캡처 모드는 hipStreamCaptureModeGlobal(기본, 가장 안전), hipStreamCaptureModeThreadLocal(스레드별 격리), hipStreamCaptureModeRelaxed(이벤트 혼용 허용) 세 가지가 있으며, 멀티 스트림 캡처 시 hipEventRecord()로 스트림 간 의존 관계를 표현합니다.

// hipGraph 스트림 캡처 예제
#include <hip/hip_runtime.h>

__global__ void kernelA(float* data, int n) {
    int idx = blockIdx.x * blockDim.x + threadIdx.x;
    if (idx < n) data[idx] *= 2.0f;
}

__global__ void kernelB(float* a, float* b, int n) {
    int idx = blockIdx.x * blockDim.x + threadIdx.x;
    if (idx < n) b[idx] = a[idx] + b[idx];
}

int main() {
    const int N = 1 << 20;
    float *dA, *dB, *hA;
    hipMalloc(&dA, N * sizeof(float));
    hipMalloc(&dB, N * sizeof(float));
    hA = new float[N];
    for (int i = 0; i < N; i++) hA[i] = (float)i;

    hipStream_t stream;
    hipStreamCreate(&stream);

    // --- 캡처 단계 (최초 1회) ---
    hipGraph_t graph;
    hipStreamBeginCapture(stream, hipStreamCaptureModeGlobal);

    hipMemcpyAsync(dA, hA, N * sizeof(float),
                   hipMemcpyHostToDevice, stream);
    hipMemsetAsync(dB, 0, N * sizeof(float), stream);

    dim3 block(256), grid((N + 255) / 256);
    kernelA<<<grid, block, 0, stream>>>(dA, N);
    kernelB<<<grid, block, 0, stream>>>(dA, dB, N);

    hipStreamEndCapture(stream, &graph);

    // --- 인스턴스화 ---
    hipGraphExec_t graphExec;
    hipGraphInstantiate(&graphExec, graph,
                        nullptr, nullptr, 0);

    // --- 반복 실행 (각 반복마다 O(1) CPU 오버헤드) ---
    for (int iter = 0; iter < 1000; iter++) {
        hipGraphLaunch(graphExec, stream);
    }
    hipStreamSynchronize(stream);

    hipGraphExecDestroy(graphExec);
    hipGraphDestroy(graph);
    hipStreamDestroy(stream);
    hipFree(dA); hipFree(dB);
    delete[] hA;
}

수동 그래프 구성

스트림 캡처가 불가능한 경우(조건부 분기, 루프 종속 구조 등) 노드와 엣지를 직접 추가하는 수동 구성 방식을 사용합니다. hipGraphCreate()로 빈 그래프를 생성한 뒤 각 노드 타입별 Add 함수로 노드를 추가하고, hipGraphAddDependencies()로 의존 관계를 선언합니다. 수동 구성의 장점은 노드 간 의존 관계를 세밀하게 제어할 수 있고, 스트림 캡처로는 표현하기 어려운 복잡한 DAG 위상을 직접 정의할 수 있다는 점입니다.

// 수동 그래프 구성: GEMM → Bias Add → Activation 파이프라인
hipGraph_t graph;
hipGraphCreate(&graph, 0);

// GEMM 커널 노드 파라미터
hipKernelNodeParams gemmParams = {};
gemmParams.func = (void*)gemmKernel;
gemmParams.gridDim  = dim3(gridX, gridY, 1);
gemmParams.blockDim = dim3(16, 16, 1);
gemmParams.sharedMemBytes = 0;
void* gemmArgs[] = { &dA, &dB, &dC, &M, &N, &K };
gemmParams.kernelParams = gemmArgs;
gemmParams.extra = nullptr;

hipGraphNode_t gemmNode, biasNode, actNode, cpNode;

// 루트 노드 (의존 없음)
hipGraphAddKernelNode(&gemmNode, graph,
                      nullptr, 0, &gemmParams);

// Bias: gemmNode에 의존
hipKernelNodeParams biasParams = {};
biasParams.func = (void*)biasKernel;
biasParams.gridDim = dim3(gridX, 1, 1);
biasParams.blockDim = dim3(256, 1, 1);
void* biasArgs[] = { &dC, &dBias, &N };
biasParams.kernelParams = biasArgs;
hipGraphAddKernelNode(&biasNode, graph,
                      &gemmNode, 1, &biasParams);

// Activation: biasNode에 의존
hipKernelNodeParams actParams = {};
actParams.func = (void*)reluKernel;
actParams.gridDim = dim3(gridX, 1, 1);
actParams.blockDim = dim3(256, 1, 1);
void* actArgs[] = { &dC, &N };
actParams.kernelParams = actArgs;
hipGraphAddKernelNode(&actNode, graph,
                      &biasNode, 1, &actParams);

// Memcpy D→H: actNode 이후
hipMemcpy3DParms cpParams = {};
cpParams.srcPtr = make_hipPitchedPtr(
    dC, M*sizeof(float), M, N);
cpParams.dstPtr = make_hipPitchedPtr(
    hC, M*sizeof(float), M, N);
cpParams.extent = make_hipExtent(
    M*sizeof(float), N, 1);
cpParams.kind = hipMemcpyDeviceToHost;
hipGraphAddMemcpyNode(&cpNode, graph,
                     &actNode, 1, &cpParams);

// 인스턴스화 및 실행
hipGraphExec_t exec;
hipGraphInstantiate(&exec, graph, nullptr, nullptr, 0);
hipGraphLaunch(exec, stream);
hipStreamSynchronize(stream);

그래프 업데이트 및 재사용

ROCm 5.x 이후 hipGraphExecKernelNodeSetParams()hipGraphExecUpdate()를 통해 이미 인스턴스화된 그래프의 파라미터(커널 인자, grid/block 크기, memcpy 포인터 등)를 재인스턴스화 없이 수정할 수 있습니다. 학습 루프처럼 입력 데이터 포인터만 바뀌는 경우에 특히 유용하며, 그래프의 위상(노드 수·의존 관계)이 동일하면 재인스턴스화 비용(수 밀리초)을 절약할 수 있습니다.

// 그래프 파라미터 업데이트: 재인스턴스화 없이 커널 인자 변경

// 1) 특정 노드 파라미터만 직접 변경
hipKernelNodeParams newParams = gemmParams;
void* newArgs[] = {
    &dA2, &dB2, &dC2, &M, &N, &K
}; // 새 배치 포인터
newParams.kernelParams = newArgs;
hipGraphExecKernelNodeSetParams(exec, gemmNode, &newParams);

// 2) 그래프 오브젝트 변경 후 Exec에 반영
//    파라미터만 변경이면 SUCCESS, 위상 변경이면 FAIL → 재인스턴스화
hipGraphExecUpdateResult updateResult;
hipGraphNode_t errNode;
hipError_t ret = hipGraphExecUpdate(
    exec, graph, &errNode, &updateResult);
if (updateResult != hipGraphExecUpdateSuccess) {
    hipGraphExecDestroy(exec);
    hipGraphInstantiate(&exec, graph,
                        nullptr, nullptr, 0);
}

// 3) 학습 루프에서의 전형적 사용 패턴
for (int epoch = 0; epoch < epochs; epoch++) {
    for (int step = 0; step < stepsPerEpoch; step++) {
        // 현재 배치로 입력 포인터 업데이트
        updateInputPointers(exec, batchPtr[step]);
        hipGraphLaunch(exec, stream);
        hipStreamSynchronize(stream);
    }
}

// 4) 디버깅: 그래프 DOT 파일로 시각화
hipGraphDebugDotPrint(graph, "graph.dot",
    hipGraphDebugDotFlagsVerbose);
hipGraph 노드 타입 요약
노드 타입 Add 함수 용도 주요 파라미터 구조체
KernelNode hipGraphAddKernelNode() GPU 커널 실행 hipKernelNodeParams
MemcpyNode hipGraphAddMemcpyNode() H↔D / D↔D 비동기 메모리 복사 hipMemcpy3DParms
MemsetNode hipGraphAddMemsetNode() GPU 메모리 초기화 hipMemsetParams
HostNode hipGraphAddHostNode() CPU 콜백 실행 (그래프 내 CPU 작업) hipHostNodeParams
EventRecordNode hipGraphAddEventRecordNode() 이벤트 기록 (타이밍·스트림 간 동기화) hipEvent_t
EventWaitNode hipGraphAddEventWaitNode() 이벤트 대기 hipEvent_t
EmptyNode hipGraphAddEmptyNode() 의존 관계 정리용 배리어 없음
ChildGraphNode hipGraphAddChildGraphNode() 서브그래프 내포 (계층 DAG) hipGraph_t
그래프가 유리한 상황 짧은 커널이 수십 개 이상 반복되는 AI 추론 서빙, 물리 시뮬레이션, 실시간 신호처리처럼 동일한 GPU 작업 패턴이 수백~수천 번 반복될 때 hipGraph는 CPU 제출 오버헤드를 사실상 0으로 만들어 GPU 점유율을 높입니다. 그래프 내 커널 수가 20개 이상이고 반복 횟수가 100회를 넘으면 스트림 방식 대비 5~30% 처리량 향상을 기대할 수 있습니다.
그래프 vs. 스트림 성능 비교 기준 스트림 방식의 CPU-GPU 제출 오버헤드는 커널당 약 2~8 μs(드라이버 유형·OS에 따라 다름)입니다. hipGraph는 이 오버헤드가 hipGraphLaunch() 1회(약 10~30 μs)로 고정됩니다. 커널 평균 실행 시간이 수십 μs 이상이고 커널 수가 적다면 그래프 이점이 줄어들며, 커널당 실행 시간이 1 ms 이상이면 스트림 방식과 성능 차이가 거의 없습니다.
ROCm hipGraph 제한 사항 (ROCm 6.x 기준) (1) hipMalloc() / hipFree()는 캡처 중에 호출할 수 없습니다. (2) 동기식 API(hipMemcpy(), hipDeviceSynchronize() 등)도 캡처 불가합니다. (3) 조건부 그래프(hipGraphConditional)는 ROCm 6.2 이후 실험적 지원으로 CUDA 대비 기능이 제한됩니다. (4) 멀티 GPU 그래프(peer memcpy 포함)는 인스턴스화 시 장치 토폴로지를 검증하며, XGMI 연결이 없는 장치 조합에서는 예외를 발생시킬 수 있습니다.

HIP Cooperative Groups

Cooperative Groups는 HIP에서 스레드 그룹을 유연하게 정의하고 동기화하는 API입니다. 기존 __syncthreads()가 블록 내 모든 스레드를 동기화하는 것과 달리, Cooperative Groups는 서브셋 타일, 블록 전체, 그리드 전체 등 다양한 범위의 동기화를 지원합니다. 그리드 레벨 동기화를 위해서는 hipLaunchCooperativeKernel로 커널을 시작해야 하며, GPU가 cooperative launch를 지원해야 합니다.

/* HIP Cooperative Groups: 그리드 레벨 협동 실행 */
#include <hip/hip_cooperative_groups.h>
namespace cg = cooperative_groups;

/* 그리드 전체 동기화를 활용한 reduce 커널 */
__global__ void grid_reduce_kernel(float *data, float *result, int n)
{
    /* 그리드 전체를 하나의 그룹으로 묶음 */
    cg::grid_group grid = cg::this_grid();

    int tid = grid.thread_rank();
    int stride = grid.size();

    /* 1단계: 각 스레드가 담당 구간을 합산 */
    float local_sum = 0.0f;
    for (int i = tid; i < n; i += stride)
        local_sum += data[i];

    /* 2단계: 블록 내 shared memory로 reduce */
    __shared__ float smem[256];
    cg::thread_block block = cg::this_thread_block();
    smem[block.thread_rank()] = local_sum;
    block.sync();   /* __syncthreads()와 동일하지만 명시적 그룹 기반 */

    for (int s = block.size() / 2; s > 0; s >>= 1) {
        if (block.thread_rank() < s)
            smem[block.thread_rank()] += smem[block.thread_rank() + s];
        block.sync();
    }

    /* 3단계: 그리드 전체 동기화 후 블록 결과를 원자적으로 합산 */
    if (block.thread_rank() == 0)
        atomicAdd(result, smem[0]);
    grid.sync();   /* 그리드 레벨 동기화 (모든 블록 완료 대기) */
}

/* Cooperative 커널 시작 방법 */
void launch_cooperative(float *d_data, float *d_result, int n)
{
    int block_size = 256;

    /* 동시 실행 가능한 최대 블록 수 조회 */
    int num_blocks;
    hipOccupancyMaxActiveBlocksPerMultiprocessor(
        &num_blocks, grid_reduce_kernel, block_size, 0);

    int device;
    hipGetDevice(&device);
    hipDeviceProp_t props;
    hipGetDeviceProperties(&props, device);
    int grid_size = num_blocks * props.multiProcessorCount;

    /* Cooperative 커널 시작: 일반 <<<...>>> 구문 대신 */
    void *args[] = { &d_data, &d_result, &n };
    hipLaunchCooperativeKernel(
        (void *)grid_reduce_kernel,
        dim3(grid_size), dim3(block_size),
        args, 0, 0
    );
}
/* Thread Block Tile: wavefront 내 sub-group 동기화 */

__global__ void warp_reduce_kernel(float *data, float *out)
{
    cg::thread_block block = cg::this_thread_block();

    /* 32개 스레드 타일 생성 (warp/wavefront 서브셋) */
    auto tile32 = cg::tiled_partition<32>(block);

    int tid = blockIdx.x * blockDim.x + threadIdx.x;
    float val = data[tid];

    /* 타일 내 butterfly reduce (lane XOR shuffle) */
    for (int offset = tile32.size() / 2; offset > 0; offset >>= 1)
        val += tile32.shfl_down(val, offset);

    /* tile의 lane 0이 블록 결과를 출력 */
    if (tile32.thread_rank() == 0)
        atomicAdd(out, val);

    /* 16개 타일도 지원: tiled_partition<16> */
    auto tile16 = cg::tiled_partition<16>(block);
    /* AMD에서는 wavefront 64개 스레드를 16개 x 4 타일로 분할 */
}

/* coalesced_threads: 조건부 활성 스레드 그룹 */
__global__ void conditional_sync_kernel(int *data, int *result, int n)
{
    int tid = blockIdx.x * blockDim.x + threadIdx.x;
    cg::thread_block block = cg::this_thread_block();

    if (tid < n && data[tid] > 0) {
        /* 조건을 만족하는 스레드만으로 그룹 구성 */
        auto active = cg::coalesced_threads();
        int sum = reduce(active, data[tid], cg::plus<int>());
        if (active.thread_rank() == 0)
            atomicAdd(result, sum);
    }
}
HIP Cooperative Groups 타입 비교
타입생성 방법크기동기화 범위주요 용도
thread_blockthis_thread_block()blockDim.x×y×z블록 내 전체__syncthreads() 대체, LDS 공유
grid_groupthis_grid()gridDim × blockDim그리드 전체단일 커널 내 전역 reduce, 2단계 알고리즘
thread_block_tile<N>tiled_partition<N>(block)N (2의 거듭제곱)N개 스레드 내warp-level reduce, shuffle 연산
coalesced_groupcoalesced_threads()조건부 활성 스레드 수활성 스레드 내조건 분기 후 활성 스레드 집합 연산
multi_grid_groupthis_multi_grid()전체 GPU 수 × 그리드다중 GPU 전체다중 GPU cooperative 실행 (실험적)
AMD wavefront와 tile 크기: AMD GPU의 기본 wavefront 크기는 64입니다. tiled_partition<64>가 1개 wavefront에 대응하며, tiled_partition<32>는 wavefront를 반으로 나눕니다. RDNA1/2에서 wave32 모드를 활성화하면 tiled_partition<32>가 1개 wavefront에 대응합니다. 타일 크기를 하드코딩하지 말고 props.warpSize를 참조하세요.
Cooperative Launch 지원 여부 확인: hipDeviceProp_t::cooperativeLaunch 필드가 1이어야 grid_group 동기화를 사용할 수 있습니다. cooperativeMultiDeviceLaunch는 다중 GPU cooperative 실행 지원 여부를 나타냅니다. MI100/MI200/MI300 시리즈는 모두 지원하며, RX 7000 시리즈도 지원합니다. 지원하지 않는 GPU에서 this_grid().sync()를 호출하면 정의되지 않은 동작이 발생합니다.

HIP Virtual Memory Management (VMM)

HIP VMM API는 CUDA Virtual Memory Management와 유사하게, 물리적 메모리 할당과 가상 주소 공간 매핑을 분리하는 세밀한 제어 기능을 제공합니다. 이를 통해 과도한 예약(overcommit), 동적 크기 조정, 여러 GPU에 동일한 물리 메모리 매핑 등 고급 메모리 관리 기법을 구현할 수 있습니다.

/* HIP VMM: 가상 주소 공간 예약 → 물리 메모리 할당 → 매핑 → 접근 권한 설정 */

#include <hip/hip_runtime.h>

void hip_vmm_example(size_t size)
{
    hipDeviceptr_t va_ptr;
    hipMemGenericAllocationHandle_t handle;

    /* 1단계: 가상 주소 공간 예약 (물리 메모리 소비 없음) */
    hipMemAddressReserve(&va_ptr, size,
                         2 * 1024 * 1024,  /* 2 MB 정렬 */
                         0,                 /* 주소 힌트 (0=자동) */
                         0);                /* flags */

    /* 2단계: 물리 메모리 할당 속성 설정 */
    hipMemAllocationProp prop = {};
    prop.type = hipMemAllocationTypePinned;
    prop.location.type = hipMemLocationTypeDevice;
    prop.location.id = 0;   /* GPU 0의 VRAM */

    /* 3단계: 물리 메모리 할당 (실제 VRAM 소비) */
    hipMemCreate(&handle, size, &prop, 0);

    /* 4단계: 가상 주소에 물리 메모리 매핑 */
    hipMemMap(va_ptr, size,
              0,      /* offset within physical allocation */
              handle,
              0);     /* flags */

    /* 5단계: 접근 권한 설정 (읽기/쓰기 활성화) */
    hipMemAccessDesc access_desc = {};
    access_desc.location.type = hipMemLocationTypeDevice;
    access_desc.location.id = 0;
    access_desc.flags = hipMemAccessFlagsProtReadWrite;
    hipMemSetAccess(va_ptr, size, &access_desc, 1);

    /* 이제 va_ptr를 GPU 커널에서 일반 포인터처럼 사용 가능 */
    /* ... GPU 커널 실행 ... */

    /* 해제 순서: 매핑 해제 → 가상 주소 해제 → 물리 메모리 해제 */
    hipMemUnmap(va_ptr, size);
    hipMemAddressFree(va_ptr, size);
    hipMemRelease(handle);
}

/* 동적 크기 조정 예: realloc 패턴 */
void vmm_dynamic_resize(hipDeviceptr_t *va_ptr, size_t old_size, size_t new_size)
{
    hipDeviceptr_t new_va;
    hipMemAllocationProp prop = {};
    prop.type = hipMemAllocationTypePinned;
    prop.location.type = hipMemLocationTypeDevice;
    prop.location.id = 0;

    /* 새 크기의 가상 주소 공간 예약 */
    hipMemAddressReserve(&new_va, new_size, 0, 0, 0);

    /* 기존 데이터를 새 영역에 매핑 (데이터 복사 없이 재매핑) */
    hipMemGenericAllocationHandle_t new_handle;
    hipMemCreate(&new_handle, new_size, &prop, 0);
    hipMemMap(new_va, new_size, 0, new_handle, 0);

    hipMemAccessDesc desc = {};
    desc.location.type = hipMemLocationTypeDevice;
    desc.location.id = 0;
    desc.flags = hipMemAccessFlagsProtReadWrite;
    hipMemSetAccess(new_va, new_size, &desc, 1);

    *va_ptr = new_va;
}
VMM vs hipMalloc 사용 지침: 일반 할당에는 hipMalloc이 훨씬 간단하므로 항상 우선 사용하세요. VMM API가 유리한 경우는 (1) 최대 크기를 미리 알 수 없어 동적으로 늘려야 할 때, (2) 여러 GPU가 동일한 물리 메모리 블록을 서로 다른 가상 주소로 접근해야 할 때, (3) 대규모 sparse 행렬처럼 일부만 실제 메모리가 필요한 구조를 다룰 때입니다. ROCm 5.3+ 부터 VMM API가 안정화되었으며, ROCm 6.x에서 XGMI 피어 매핑도 지원합니다.
VMM 정렬 요구사항: hipMemAddressReservealignment 파라미터는 플랫폼 최소 정렬(일반적으로 2 MB)의 배수여야 합니다. hipMemGetAllocationGranularity로 현재 장치의 최소 및 권장 정렬 크기를 조회하세요: hipMemGetAllocationGranularity(&granularity, &prop, hipMemAllocationGranularityMinimum). 정렬 요구사항 미준수 시 hipErrorInvalidValue가 반환됩니다.

hipify 마이그레이션

기존 CUDA 코드를 HIP로 전환할 때는 hipify-perlhipify-clang 두 가지 도구를 사용합니다. hipify-perl은 단순 텍스트 치환 방식으로 빠르고 의존성이 없지만 복잡한 코드에서 오변환이 생길 수 있습니다. hipify-clang은 Clang AST를 기반으로 의미론적으로 정확하게 변환하며 통계 리포트도 제공합니다.

# hipify-perl: 단순 텍스트 치환
hipify-perl cuda_vector_add.cu > hip_vector_add.hip

# 디렉터리 전체 변환
hipify-perl --inplace src/*.cu src/*.cuh

# 변환 통계 확인
hipify-perl --print-stats myapp.cu

# 출력 예:
# [HIPIFY] info: converted 42 CUDA API calls
# [HIPIFY] info: 3 CUDA API call(s) not converted (manual action required)
# hipify-clang: AST 기반 변환 (더 정확)
# compilation database (compile_commands.json) 필요
cmake -DCMAKE_EXPORT_COMPILE_COMMANDS=ON ..

hipify-clang cuda_app.cu \
    -p ./build \
    --cuda-path=/usr/local/cuda \
    -o hip_app.cpp

# 변환 통계 리포트 생성
hipify-clang --print-stats=1 cuda_app.cu -p ./build

# 여러 파일 일괄 변환
find src -name "*.cu" | xargs -I{} hipify-clang {} -p ./build
# CMakeLists.txt: HIP 통합 빌드 설정
cmake_minimum_required(VERSION 3.21)
project(MyROCmApp LANGUAGES CXX HIP)

# ROCm 경로 설정
find_package(HIP REQUIRED)
find_package(rocblas REQUIRED)

set(CMAKE_HIP_ARCHITECTURES "gfx942;gfx1100")  # 타겟 아키텍처

add_executable(myapp main.hip vector_add.hip)

target_link_libraries(myapp
    hip::host          # HIP 런타임
    roc::rocblas       # rocBLAS
)

set_source_files_properties(main.hip vector_add.hip
    PROPERTIES LANGUAGE HIP)

# 디버그 빌드 시 sanitizer 활성화
target_compile_options(myapp PRIVATE
    "$<$<CONFIG:Debug>:-fsanitize=address>")
hipify 마이그레이션 복잡도 분류
카테고리도구변환 난이도예시
런타임 API (cudaMemcpy 등) hipify-perl 자동 (쉬움) cuda* → hip* 1:1 치환
cuBLAS / cuFFT / cuDNN hipify-clang 반자동 (중간) rocBLAS/rocFFT/MIOpen API로 매핑
PTX 인라인 어셈블리 수동 어려움 AMDGCN 어셈블리로 재작성 필요
CUDA 고유 기능 (WarpShuffle) hipify-clang 중간 __shfl_* → __shfl_* (HIP에서 지원)
Cooperative Groups hipify-clang 중간 hip/hip_cooperative_groups.h
CUDA Graphs 수동 검토 어려움 hipGraph API (ROCm 5.x+에서 지원)
Driver API (cuCtx 등) hipify-clang 중간 hipCtx 등으로 변환
hipify 후 검증 절차: 변환 후에는 ① 컴파일 성공 여부, ② 수치 결과 정확성(참조 구현과 비교), ③ 성능 비교(원본 CUDA 대비 AMD GPU 성능) 순으로 검증합니다. hipify-clang --print-stats로 변환되지 않은 API 목록을 반드시 확인하고 수동으로 처리합니다.
CUDA Textures: CUDA texture 객체/레퍼런스는 HIP에서도 지원하지만, AMD GPU의 텍스처 하드웨어와 NVIDIA의 차이가 있습니다. 컴퓨트 코드에서 texture를 사용한다면 L1/L2 캐시로 대체하는 것이 AMD에서 성능상 더 유리한 경우가 많습니다.
warpSize 하드코딩 주의: CUDA 코드에 if (threadIdx.x % 32 == 0)처럼 warpSize를 32로 하드코딩한 경우, AMD GPU(wavefront=64)에서 논리적 오류가 발생합니다. 항상 warpSize 변수 또는 hipDeviceProp_t::warpSize를 동적으로 참조하세요.

RDNA vs CDNA 아키텍처

AMD GPU는 용도에 따라 두 가지 주요 아키텍처 계열로 나뉩니다. RDNA(Radeon DNA)는 게임과 컨슈머 그래픽을 위한 아키텍처로, 디스플레이 엔진(DCN), 레이 트레이싱 유닛(BVH), 미디어 인코더(VCN)를 포함합니다. CDNA(Compute DNA)는 HPC와 AI 연산을 위한 서버 전용 아키텍처로, 그래픽 관련 유닛을 제거하고 더 많은 CU(Compute Unit), 대용량 HBM 메모리, XGMI 인터커넥트, 행렬 연산 가속기(Matrix Core)에 집중합니다.

# rocm-smi로 GPU 토폴로지 및 아키텍처 확인
rocm-smi --showtopo
# GPU[0]  GPU[1]  ... GPU[7]
# GPU[0]:  0       XGMI    XGMI  ...
# GPU[1]:  XGMI    0       XGMI  ...

# 개별 GPU 정보 조회
rocm-smi -d 0 --showproductname --showmeminfo vram
rocm-smi -d 0 --showclkfreq       # 현재 클럭 주파수
rocm-smi -d 0 --showtemp           # GPU 온도
rocm-smi -d 0 --showpower          # 전력 소비

# HIP으로 아키텍처 버전 조회
rocminfo | grep -E "Name|gfx_target_triple"
# 출력 예: gfx942 (MI300X의 GFX IP)
/* HIP으로 아키텍처 정보 프로그래밍 방식 조회 */
#include <hip/hip_runtime.h>
#include <hip/hip_ext.h>

hipDeviceProp_t prop;
hipGetDeviceProperties(&prop, 0);

printf("Name: %s\n", prop.name);
printf("gcnArchName: %s\n", prop.gcnArchName);     /* gfx942 등 */
printf("CU count: %d\n", prop.multiProcessorCount);
printf("VRAM: %zu GB\n", prop.totalGlobalMem >> 30);
printf("L2 cache: %d MB\n", prop.l2CacheSize >> 20);
printf("Max shmem/block: %zu KB\n", prop.sharedMemPerBlock >> 10);
printf("XNACK: %d\n", prop.xnackEnabled);           /* SVM 폴트 지원 */
printf("Unified addr: %d\n", prop.unifiedAddressing);
RDNA3(RX 7900 XTX) vs CDNA3(MI300X) 주요 사양 비교
항목RDNA3 (GFX1100)CDNA3 (GFX942)
대표 제품Radeon RX 7900 XTXInstinct MI300X
Compute Unit 수96 CU448 CU (8 XCD × 56)
FP64 성능~2.6 TFLOPS~81.7 TFLOPS
FP16 성능~123 TFLOPS~1307 TFLOPS (FP8: ~2.6 PFLOPS)
메모리 타입GDDR6 (24 GB)HBM3 (192 GB)
메모리 대역폭~960 GB/s~5.3 TB/s
L2 캐시96 MB16 MB × 8 = 128 MB
TDP355W750W
Matrix Core제한적 (WMMA)완전 지원 (MFMA)
ECC미지원지원 (HBM3 + SRAM)
인터커넥트PCIe 4.0PCIe 5.0 + XGMI 3.0
디스플레이 엔진있음 (DCN 3.2)없음
AMD GPU 아키텍처 세대 일람
세대GFX IP대표 제품주요 특징
GCN1GFX6HD 7970GPGPU 기반, 최초 ROCm 지원 (제한적)
Vega (GCN5)GFX9RX Vega 64, MI60HBM2, ROCm 완전 지원 시작
CDNA1GFX908MI100Matrix Core FP16/BF16, 제1세대 서버 GPU
CDNA2GFX90aMI250XFP64 Matrix Core, xGMI3, HBM2e
RDNA1GFX10RX 5700 XTwave32 모드, 재설계된 CU
RDNA2GFX103RX 6900 XT레이 트레이싱, Infinity Cache
RDNA3GFX11RX 7900 XTX칩렛 설계, AI Accelerator
CDNA3GFX942MI300XCPU+GPU 통합 패키지, FP8, HBM3, 192 GB
Matrix Core (MFMA) vs WMMA: CDNA의 MFMA(Matrix Fused Multiply-Add) 명령어는 단일 명령으로 작은 행렬 곱(예: 16×16×16)을 수행합니다. RDNA3의 WMMA(Wavefront Matrix Multiply-Accumulate)는 더 제한된 형태입니다. HIP에서 rocwmma 라이브러리나 __builtin_amdgcn_mfma_f32_16x16x16f16 내장 함수로 접근합니다.
XNACK 모드: XNACK(eXpanded None-Acknowledged)은 GPU가 SVM 페이지 폴트를 처리할 수 있는 모드입니다. 활성화 시 GPU가 아직 매핑되지 않은 가상 주소에 접근해도 페이지 폴트로 복구할 수 있지만, 하드웨어 복잡성이 증가하여 성능이 약간 낮아집니다. ROCm에서는 HSA_XNACK=1 환경 변수로 제어합니다.
칩렛 간 지연 시간: MI300X의 8개 XCD는 물리적으로 별도의 다이이므로, 서로 다른 XCD의 CU 간 통신은 같은 XCD 내부보다 레이턴시가 높습니다. ROCm 런타임은 NUMA-aware하게 메모리를 배치하려 하지만, LDS처럼 같은 CU 내에서만 공유되는 자원은 XCD 경계를 넘을 수 없습니다.

Matrix Core / MFMA 프로그래밍 심화

Matrix Core(MFMA, Matrix Fused Multiply-Add)는 CDNA 아키텍처(MI100 이후)의 핵심 AI 가속 엔진입니다. 단일 명령어로 소형 행렬 곱셈-덧셈(D = A × B + C)을 실행하며, FP64/FP32/FP16/BF16/INT8/FP8 등 다양한 정밀도를 지원합니다. RDNA3(GFX11)에서는 WMMA(Wavefront Matrix Multiply-Accumulate)라는 유사하지만 범위가 더 제한된 형태로 제공됩니다. 프로그래밍 방법은 rocWMMA 라이브러리를 통한 고수준 API와, LLVM 내장 함수(intrinsic)를 직접 호출하는 저수준 방법이 있습니다.

/* rocWMMA를 사용한 MFMA 행렬 곱: C = A × B + C */
/* rocWMMA는 CUDA wmma API와 유사한 고수준 인터페이스 제공 */
#include <rocwmma/rocwmma.hpp>

using namespace rocwmma;

constexpr int WMMA_M = 16;
constexpr int WMMA_N = 16;
constexpr int WMMA_K = 16;

__global__ void rocwmma_gemm_kernel(
    half *A, half *B, float *C, float *D,
    int M, int N, int K)
{
    /* 블록 내 wavefront 위치 계산 */
    int wave_row = (blockIdx.y * blockDim.y + threadIdx.y) / warpSize;
    int wave_col = (blockIdx.x * blockDim.x + threadIdx.x) / warpSize;

    /* fragment 선언: 16×16 행렬 타일 */
    fragment<matrix_a, WMMA_M, WMMA_N, WMMA_K, half, row_major> a_frag;
    fragment<matrix_b, WMMA_M, WMMA_N, WMMA_K, half, col_major> b_frag;
    fragment<accumulator, WMMA_M, WMMA_N, WMMA_K, float> c_frag;
    fragment<accumulator, WMMA_M, WMMA_N, WMMA_K, float> d_frag;

    /* accumulator 초기화 */
    fill_fragment(d_frag, 0.0f);

    /* K 차원을 따라 WMMA_K 크기 타일로 반복 */
    for (int k = 0; k < K; k += WMMA_K) {
        int a_row = wave_row * WMMA_M;
        int a_col = k;
        int b_row = k;
        int b_col = wave_col * WMMA_N;

        /* 글로벌 메모리에서 fragment 로드 */
        load_matrix_sync(a_frag, A + a_row * K + a_col, K);
        load_matrix_sync(b_frag, B + b_row * N + b_col, N);

        /* MFMA 실행: d_frag += a_frag × b_frag */
        mma_sync(d_frag, a_frag, b_frag, d_frag);
    }

    /* C 행렬 로드 및 누산 */
    load_matrix_sync(c_frag, C + wave_row * WMMA_M * N + wave_col * WMMA_N, N,
                     mem_row_major);
    for (int i = 0; i < d_frag.num_elements; i++)
        d_frag.x[i] += c_frag.x[i];

    /* 결과를 글로벌 메모리에 저장 */
    store_matrix_sync(D + wave_row * WMMA_M * N + wave_col * WMMA_N,
                      d_frag, N, mem_row_major);
}
/* 저수준 MFMA intrinsic 직접 사용 */
/* __builtin_amdgcn_mfma_f32_16x16x16f16 */

__global__ void mfma_intrinsic_kernel(half *A, half *B, float *C)
{
    /* MFMA f32 16x16x16 f16 연산 */
    /* 각 wavefront(64 스레드)가 16x16 타일을 공동으로 처리 */

    /* 입력: A는 half×4 벡터 (각 스레드가 4개의 half 값 보유) */
    typedef float __attribute__((ext_vector_type(4))) float4v;
    typedef __fp16 __attribute__((ext_vector_type(4))) half4v;

    int lane = threadIdx.x % 64;  /* wavefront 내 lane 번호 */
    int row = lane / 4;           /* 담당 행 인덱스 */
    int col_base = (lane % 4) * 4; /* 담당 열 시작 인덱스 */

    /* A 행렬의 해당 스레드 담당 원소 로드 */
    half4v a_vec;
    a_vec[0] = A[row * 16 + col_base];
    a_vec[1] = A[row * 16 + col_base + 1];
    a_vec[2] = A[row * 16 + col_base + 2];
    a_vec[3] = A[row * 16 + col_base + 3];

    half4v b_vec;  /* B 행렬 원소 (유사하게 로드) */

    /* accumulator 초기화 (4개 float, 각 스레드가 accumulator 일부 소유) */
    float4v c_vec = {0.0f, 0.0f, 0.0f, 0.0f};

    /* MFMA 내장 함수 호출: D[16x16][float] = A[16x16][f16] × B[16x16][f16] + C */
    float4v result = __builtin_amdgcn_mfma_f32_16x16x16f16(
        a_vec,   /* A 타일 (각 스레드 담당 부분) */
        b_vec,   /* B 타일 */
        c_vec,   /* C accumulator (초기값) */
        0,       /* cbsz: broadcast 제어 */
        0,       /* abid: A 행렬 broadcast ID */
        0        /* blgp: B 행렬 layout 그룹 */
    );

    /* 결과 저장: 각 스레드가 accumulator 일부를 글로벌 메모리에 기록 */
    C[row * 16 + (lane % 4) * 4 + 0] = result[0];
    C[row * 16 + (lane % 4) * 4 + 1] = result[1];
    C[row * 16 + (lane % 4) * 4 + 2] = result[2];
    C[row * 16 + (lane % 4) * 4 + 3] = result[3];
}
MFMA 명령어 변형 (CDNA2/CDNA3 기준)
명령어타일 크기 (M×N×K)입력 타입출력 타입TFLOPS (MI300X)
mfma_f32_16x16x16f1616×16×16FP16FP32~383 TFLOPS
mfma_f32_32x32x8f1632×32×8FP16FP32~383 TFLOPS
mfma_f32_16x16x16bf1616×16×16BF16FP32~383 TFLOPS
mfma_i32_16x16x16i816×16×16INT8INT32~1307 TOPS
mfma_i32_32x32x8i832×32×8INT8INT32~1307 TOPS
mfma_f32_16x16x32_fp8_fp816×16×32FP8 (E4M3)FP32~2614 TFLOPS
mfma_f64_16x16x4f6416×16×4FP64FP64~95 TFLOPS
rocWMMA vs hipBLASLt 선택: 직접 MFMA를 프로그래밍할 필요가 있는 경우는 드뭅니다. 대부분의 경우 hipBLASLt(커스텀 GEMM 커널 선택 지원)나 rocBLAS를 사용하면 ROCm 팀이 최적화한 MFMA 커널을 자동으로 활용할 수 있습니다. rocWMMA는 비표준 타일 크기나 특수 레이아웃이 필요할 때 직접 사용합니다. MFMA intrinsic을 직접 호출하는 것은 마이크로아키텍처 수준의 최적화가 필요한 극한 경우에만 권장됩니다.
AGPR (Accumulator GPR): CDNA 아키텍처에는 VGPR과 별도로 AGPR이라는 전용 accumulator 레지스터 파일이 있습니다. 각 스레드는 최대 512개의 AGPR을 사용할 수 있습니다. MFMA 명령의 결과는 AGPR에 저장되며, 글로벌 메모리로 쓸 때는 AGPR → VGPR 이동 명령(v_accvgpr_read)이 필요합니다. RDNA3의 WMMA는 AGPR 없이 VGPR만 사용하므로 더 단순하지만 accumulator 용량이 제한됩니다.
FP8 지원 범위: mfma_f32_16x16x32_fp8_fp8 등의 FP8 MFMA 명령은 CDNA3(MI300 시리즈, GFX942) 이상에서만 지원합니다. MI200(GFX90a, CDNA2)는 FP8을 지원하지 않습니다. ROCm 6.x에서 hip/hip_fp8.h가 추가되었으며 __hip_fp8_e4m3 타입을 제공합니다. hipDeviceProp_t에서 props.gcnArchName으로 아키텍처를 확인한 후 사용하세요.

메모리 관리

ROCm GPU 프로그래밍에서 메모리는 성능의 핵심 결정 요소입니다. GPU는 여러 계층의 메모리를 제공하며 각 계층은 용량, 대역폭, 레이턴시, 가시성 범위가 서로 다릅니다. 최적의 성능을 얻으려면 알고리즘의 데이터 접근 패턴에 맞는 메모리 계층을 선택하고, 데이터 지역성을 최대화하며, 메모리 계층 간 전송을 최소화해야 합니다.

/* 기본 GPU 메모리 할당 및 복사 */
#include <hip/hip_runtime.h>

int N = 1024 * 1024;
float *h_data = new float[N];  /* 호스트 메모리 */
float *d_data;                   /* 디바이스 (GPU VRAM) 포인터 */

/* VRAM 할당 */
hipMalloc(&d_data, N * sizeof(float));

/* 호스트 → GPU 복사 (PCIe DMA) */
hipMemcpy(d_data, h_data, N * sizeof(float), hipMemcpyHostToDevice);

/* GPU 내부 복사 (SDMA 엔진 사용) */
float *d_dst;
hipMalloc(&d_dst, N * sizeof(float));
hipMemcpy(d_dst, d_data, N * sizeof(float), hipMemcpyDeviceToDevice);

/* 페이지 고정(Pinned) 호스트 메모리: PCIe DMA 성능 향상 */
float *h_pinned;
hipHostMalloc(&h_pinned, N * sizeof(float), hipHostMallocDefault);
/* 해제 */
hipHostFree(h_pinned);

/* 메모리 0 초기화 */
hipMemset(d_data, 0, N * sizeof(float));

hipFree(d_data);
/* Unified(Managed) Memory: CPU-GPU 자동 마이그레이션 */
float *um_data;
hipMallocManaged(&um_data, N * sizeof(float));

/* CPU에서 초기화 */
for (int i = 0; i < N; ++i) um_data[i] = (float)i;

/* GPU 커널 실행 — 런타임이 자동으로 GPU로 마이그레이션 */
myKernel<<<grid, block>>>(um_data, N);
hipDeviceSynchronize();

/* CPU에서 다시 접근 — 런타임이 자동으로 CPU로 마이그레이션 */
printf("result[0] = %f\n", um_data[0]);

/* 마이그레이션 힌트: prefetch로 성능 개선 */
hipMemPrefetchAsync(um_data, N * sizeof(float),
                     0,  /* GPU device id */
                     0); /* stream */

hipFree(um_data);
/* 메모리 풀 (hipMallocFromPoolAsync): ROCm 5.x+ */
hipMemPool_t pool;
hipMemPoolProps props = {};
props.allocType       = hipMemAllocationTypePinned;
props.location.type   = hipMemLocationTypeDevice;
props.location.id     = 0;
hipMemPoolCreate(&pool, &props);

/* 스트림에서 풀 메모리 비동기 할당 */
float *d_buf;
hipStream_t stream;
hipStreamCreate(&stream);
hipMallocFromPoolAsync(&d_buf, N * sizeof(float), pool, stream);

/* 사용 ... */
myKernel<<<grid, block, 0, stream>>>(d_buf, N);

/* 스트림 완료 후 풀에 반환 (즉시 OS에 반환하지 않음) */
hipFreeAsync(d_buf, stream);
hipStreamSynchronize(stream);
hipMemPoolDestroy(pool);
ROCm GPU 메모리 타입 및 특성
메모리 타입API위치CPU 접근GPU 접근용도
VRAM (디바이스) hipMalloc GPU VRAM 불가 (직접) 최고 속도 GPU 전용 데이터 버퍼
Pinned Host hipHostMalloc CPU RAM (고정) 가능 PCIe DMA DMA 전송 스테이징 버퍼
Managed (SVM) hipMallocManaged 마이그레이션 가능 가능 간편한 데이터 공유
GTT (GART) KFD ioctl CPU RAM (매핑) 가능 IOMMU 경유 커맨드 버퍼, 중간 데이터
LDS __shared__ On-chip (CU) 불가 초고속 (~10 TB/s) 블록 내 스레드 간 공유
VGPR/SGPR 자동 (컴파일러) On-chip (CU) 불가 레지스터 속도 스레드별 로컬 변수
메모리 대역폭 비교 (참고값, 실제 성능은 시스템 구성에 따라 다름)
메모리 경로대역폭레이턴시
VGPR/SGPR → ALU~수백 TB/s0~1 cycle
LDS → SIMD (128b)~10 TB/s4~6 cycles
L1 캐시 → SIMD~4 TB/s~20 cycles
L2 캐시 → L1~1 TB/s~100 cycles
HBM3 → L2 (MI300X)~5.3 TB/s~500 cycles
PCIe 5.0 ×16~128 GB/s (단방향)~수 µs
XGMI 3.0 링크~896 GB/s (MI300X 내부)~수십 cycles
LDS 활용 최적화: 전역 메모리(VRAM) 접근 횟수를 줄이는 가장 효과적인 방법은 LDS(공유 메모리)를 타일 버퍼로 활용하는 것입니다. 타일 GEMM 알고리즘에서 VRAM 접근을 O(N³)에서 O(N³/tile)로 줄일 수 있습니다. LDS 최대 64 KB/CU 중 커널이 실제로 사용하는 양이 많을수록 CU occupancy(동시 블록 수)가 낮아지는 트레이드오프가 있으므로 roofline 모델로 최적점을 찾아야 합니다.
MI300X Unified Memory: MI300X는 CPU와 GPU가 없는 APU 형태이지만, 호스트 CPU(별도 소켓)의 DDR5와 GPU HBM3 간에 통합 주소 공간을 구성할 수 있습니다. hipMallocManagedHSA_ENABLE_SDMA=0 설정으로 런타임이 메모리 힌트에 따라 최적 위치를 선택합니다.
VGPR spilling: 커널이 스레드당 너무 많은 레지스터(VGPR)를 사용하면 컴파일러가 일부를 스크래치(VRAM) 영역에 저장/불러오기(spill/reload)합니다. 이는 극심한 성능 저하를 유발합니다. hipcc -Rpass=regalloc으로 spilling 여부를 확인하고, __attribute__((amdgpu_flat_work_group_size(64, 256)))이나 __launch_bounds__(최대스레드수, 최소블록수)로 컴파일러에 힌트를 줍니다.
hipMemAdvise: hipMemAdvise(ptr, size, hipMemAdviseSetReadMostly, device)처럼 메모리 사용 패턴 힌트를 제공하면 런타임이 데이터 배치를 최적화합니다. SetPreferredLocation은 마이그레이션 우선 위치를, SetAccessedBy는 매핑만 해두고 마이그레이션 없이 직접 접근할 장치를 지정합니다.

SVM (Shared Virtual Memory) 심층 분석

AMD GPU의 SVM(Shared Virtual Memory)은 CPU와 GPU가 동일한 가상 주소 공간을 공유하여 포인터를 별도 복사 없이 두 장치 사이에서 직접 교환할 수 있게 하는 기술입니다. ROCm의 SVM 구현은 HSA 사양을 따르며 KFD(Kernel Fusion Driver)가 중심 역할을 합니다. hipMallocManaged()로 할당된 메모리는 처음에 CPU 또는 GPU 중 어느 쪽에도 물리적으로 고정되지 않고, 실제 접근이 발생하는 시점에 페이지 폴트 메커니즘을 통해 해당 장치의 메모리로 마이그레이션됩니다.

AMD GPU의 SVM은 IOMMU(Input-Output Memory Management Unit)와 깊게 연계됩니다. GPU가 아직 매핑되지 않은 페이지에 접근하면 IOMMU가 페이지 폴트를 발생시키고, KFD의 폴트 핸들러가 이를 처리합니다. 이 기능을 XNACK(eXtended Not Acknowledge)라 부르며, GPU가 메모리 접근 실패를 재시도할 수 있는 하드웨어 지원을 뜻합니다. XNACK 활성화 여부는 GFX9(Vega) 이후 아키텍처에서 지원하며, 운영 환경(bare-metal vs MES)과 커널 빌드 플래그에 따라 다릅니다.

SVM 페이지 폴트 메커니즘

XNACK 활성화 환경에서 GPU가 미매핑 페이지에 접근하면 다음 순서로 처리됩니다. (1) GPU 메모리 컨트롤러가 IOMMU로 XNACK 신호를 보냅니다. (2) IOMMU가 FAULT 인터럽트를 CPU에 전달합니다. (3) KFD의 kfd_iommu_device_init() 경로에서 등록된 폴트 핸들러가 호출됩니다. (4) 핸들러는 해당 가상 주소가 속한 SVM 범위를 조회하고 hipMemAdvise로 설정된 힌트를 참조합니다. (5) migrate_vma_pages() 또는 DMA를 통해 물리 페이지를 GPU VRAM으로 복사합니다. (6) GPU 페이지 테이블과 IOMMU TLB를 갱신합니다. (7) GPU가 메모리 접근을 재시도합니다.

// SVM hipMemAdvise 패턴 예제
#include <hip/hip_runtime.h>

void* ptr;
size_t size = 256 * 1024 * 1024;  // 256 MiB
hipMallocManaged(&ptr, size);

int gpuDevice = 0;

// 패턴 1: GPU 우선 배치 (GPU가 주로 읽기/쓰기)
hipMemAdvise(ptr, size,
    hipMemAdviseSetPreferredLocation, gpuDevice);
// 향후 GPU 접근 시 마이그레이션 없이 GPU VRAM에 배치 유지

// 패턴 2: CPU도 접근하지만 읽기 전용 (미러 페이지 생성)
hipMemAdvise(ptr, size,
    hipMemAdviseSetReadMostly, hipCpuDeviceId);
// CPU/GPU 모두 로컬 복사본 유지, 쓰기 시 미러 무효화

// 패턴 3: GPU가 매핑만 유지 (마이그레이션 없이 직접 접근)
hipMemAdvise(ptr, size,
    hipMemAdviseSetAccessedBy, gpuDevice);
// CPU DRAM에 두되 GPU 페이지 테이블에 직접 매핑
// PCIe/XGMI를 통한 원격 접근: 대역폭 낮지만 용량 활용 가능

// 패턴 4: GPU 실행 전 선제 프리페치 (폴트 방지)
hipStream_t stream;
hipStreamCreate(&stream);
hipMemPrefetchAsync(ptr, size, gpuDevice, stream);
// 비동기 DMA: 커널 실행 전에 GPU VRAM으로 이동 보장

// 사용 후 CPU로 복귀 (결과 수집)
hipMemPrefetchAsync(ptr, size, hipCpuDeviceId, stream);
hipStreamSynchronize(stream);
// 이제 CPU에서 ptr 직접 접근 가능

hipFree(ptr);

SVM 속성 타입 및 조회

hipMemRangeGetAttribute()를 통해 특정 메모리 범위의 현재 SVM 속성을 조회할 수 있습니다. 이는 런타임에 마이그레이션 결정 근거를 확인하거나 디버깅에 활용됩니다. KFD에서 관리하는 SVM 범위 정보는 /sys/kernel/debug/kfd/를 통해서도 부분적으로 확인할 수 있습니다.

// SVM 속성 조회 및 페이지 마이그레이션 상태 확인

// 현재 preferred location 조회
int location = -1;
hipMemRangeGetAttribute(
    &location, sizeof(int),
    hipMemRangeAttributePreferredLocation,
    ptr, size);
// location == hipCpuDeviceId(=-1) 또는 GPU device ID

// 페이지가 실제로 어디 있는지 확인 (디바이스 ID 배열 반환)
int locations[256];  // 페이지 수만큼
hipMemRangeGetAttribute(
    locations, size / (4096),  // 4KB 페이지 단위
    hipMemRangeAttributeAccessedBy,
    ptr, size);

// READ_MOSTLY 미러 페이지 생성 상태
int readMostly = 0;
hipMemRangeGetAttribute(
    &readMostly, sizeof(int),
    hipMemRangeAttributeReadMostly,
    ptr, size);

// KFD SVM ioctl을 통한 범위 속성 직접 설정 (고급)
#include <linux/kfd_ioctl.h>

struct kfd_ioctl_svm_args args = {};
args.start_addr = (uint64_t)ptr;
args.size        = size;
args.op          = KFD_IOCTL_SVM_OP_SET_ATTR;
args.nattr       = 2;

struct kfd_ioctl_svm_attribute attrs[2];
attrs[0].type  = KFD_IOCTL_SVM_ATTR_PREFERRED_LOC;
attrs[0].value = KFD_IOCTL_SVM_LOCATION_GPU(0);
attrs[1].type  = KFD_IOCTL_SVM_ATTR_SET_FLAGS;
attrs[1].value = KFD_IOCTL_SVM_FLAG_GPU_EXEC;
args.attrs = (uint64_t)attrs;

ioctl(kfd_fd, AMDKFD_IOC_SVM, &args);

XNACK 모드 설정과 성능 영향

XNACK 모드는 GPU 하드웨어 수준에서 활성화되며, AMD GPU 아키텍처별 지원 여부가 다릅니다. GFX9(Vega20/MI100) 이상에서 지원되며 RDNA(RX 5000-7000) 시리즈에서는 지원되지 않습니다. XNACK OFF 시 모든 SVM 메모리는 커널 실행 전에 사전 매핑되어야 하며, 이 경우 hipMallocManaged()는 실질적으로 항상 GPU VRAM 또는 시스템 메모리 중 하나에 고정(pinned) 방식으로 동작합니다.

## XNACK 모드 확인 및 설정

# 현재 XNACK 모드 확인
cat /sys/class/kfd/kfd/topology/nodes/*/properties | grep xnack
# xnack_enabled 1 → XNACK 지원 및 활성화

# 환경변수로 XNACK 활성화 (런타임 제어)
export HSA_XNACK=1
# GFX9 이상에서만 유효; RDNA에서는 무시됨

# 커널 부트 파라미터로 XNACK 강제 설정
# /etc/default/grub에서 GRUB_CMDLINE_LINUX에 추가:
# amdgpu.noretry=0   (XNACK 재시도 활성화)

# XNACK OFF 성능 프로파일: 폴트 핸들러 없음
#   장점: 커널 실행 중 예측 가능한 지연시간
#   단점: GPU VRAM 용량 초과 시 OOM 오류

# XNACK ON 성능 프로파일
#   장점: Oversubscription 가능 (VRAM > 물리 메모리처럼 사용)
#   단점: 첫 접근 페이지 폴트 → 마이그레이션 레이턴시 수십~수백 μs

# GPU 페이지 폴트 통계 확인
cat /sys/kernel/debug/kfd/proc/*/vm_fault_info 2>/dev/null
hipMemAdvise 속성 타입 요약
속성 상수 효과 권장 사용 패턴
선호 위치 hipMemAdviseSetPreferredLocation 지정 장치 메모리를 우선 배치 대상으로 설정 특정 GPU가 주 소유자일 때
읽기 전용 미러 hipMemAdviseSetReadMostly CPU/GPU 각자 복사본 유지, 쓰기 시 미러 무효화 모델 가중치처럼 불변 대용량 데이터
접근 장치 등록 hipMemAdviseSetAccessedBy 마이그레이션 없이 페이지 테이블 매핑만 생성 CPU와 GPU가 산발적으로 공유하는 데이터
선호 위치 해제 hipMemAdviseUnsetPreferredLocation 이전 선호 위치 힌트 제거 힌트 재설정 전 초기화
읽기 전용 해제 hipMemAdviseUnsetReadMostly 미러 페이지 제거, 표준 마이그레이션으로 복귀 데이터 변경 필요 시
접근 장치 해제 hipMemAdviseUnsetAccessedBy 직접 매핑 제거 장치 접근 패턴 변경 시
XNACK ON 활용 전략 MI300X처럼 192 GB HBM을 가진 GPU에서도 LLM 추론 시 KV 캐시가 VRAM을 가득 채울 수 있습니다. XNACK ON + SVM을 이용하면 초과분을 CPU DRAM(DDR5)으로 overflow시켜 OOM을 방지하고 더 긴 컨텍스트 길이를 지원할 수 있습니다. 단, 페이지 마이그레이션 레이턴시(수십~수백 μs/페이지)가 발생하므로 hipMemPrefetchAsync()로 선제 로드하는 것이 중요합니다.
코히런트 메모리 vs. 비코히런트 메모리 AMD GPU의 SVM에는 두 가지 일관성 모델이 있습니다. 코히런트(Coherent) 메모리는 CPU와 GPU 간 캐시 일관성을 하드웨어가 보장하지만 PCIe 경유 시 성능 오버헤드가 있습니다. 비코히런트(Non-coherent) 메모리는 소프트웨어 명시적 동기화 (hipStreamSynchronize(), __threadfence_system())가 필요하지만 성능이 높습니다. hipMalloc()은 비코히런트, hipHostMalloc(CL_MEM_SVM_FINE_GRAIN_BUFFER)는 코히런트입니다.
페이지 마이그레이션 비용 PCIe Gen4 기준 CPU↔GPU 페이지 마이그레이션은 4 KB 페이지당 약 10~50 μs가 소요됩니다. 대용량 데이터(수 GB)의 빈번한 마이그레이션은 성능을 크게 저하시킵니다. 프로파일러로 KMD_SVM_MIGRATION_FAULT 이벤트 빈도를 확인하고, 빈번하면 hipMemAdvise(SetPreferredLocation)hipMemPrefetchAsync()로 사전 배치 전략을 적용하십시오.

GPU 스케줄링과 컴퓨트 큐

ROCm의 GPU 작업 제출 경로는 사용자 공간에서 AQL 패킷을 큐 링 버퍼에 직접 쓰는 방식으로 동작합니다. 커널 경유 없이 사용자 공간에서 GPU 커맨드를 제출할 수 있으므로 제출 레이턴시가 매우 낮습니다. 이 설계는 HSA(Heterogeneous System Architecture)의 핵심 원칙 중 하나입니다.

/* 여러 스트림으로 병렬 GPU 작업 실행 */
const int NUM_STREAMS = 4;
hipStream_t streams[NUM_STREAMS];

for (int i = 0; i < NUM_STREAMS; ++i) {
    hipStreamCreateWithFlags(&streams[i],
        hipStreamNonBlocking);  /* 기본 스트림과 독립 */
}

/* 각 스트림에 독립적 작업 제출 */
for (int i = 0; i < NUM_STREAMS; ++i) {
    int offset = i * (N / NUM_STREAMS);
    hipMemcpyAsync(d_data + offset, h_data + offset,
                   (N / NUM_STREAMS) * sizeof(float),
                   hipMemcpyHostToDevice, streams[i]);
    myKernel<<<grid/NUM_STREAMS, block, 0, streams[i]>>>(
        d_data + offset, N / NUM_STREAMS);
}

/* 모든 스트림 완료 대기 */
for (int i = 0; i < NUM_STREAMS; ++i)
    hipStreamSynchronize(streams[i]);
/* 큐 우선순위 설정 (ROCm 확장) */
hipStream_t high_prio_stream, low_prio_stream;

/* 우선순위 범위 조회 */
int prio_low, prio_high;
hipDeviceGetStreamPriorityRange(&prio_low, &prio_high);
/* 일반적으로 prio_high=-1, prio_low=0 */

hipStreamCreateWithPriority(&high_prio_stream,
    hipStreamNonBlocking, prio_high);
hipStreamCreateWithPriority(&low_prio_stream,
    hipStreamNonBlocking, prio_low);

/* 우선순위 높은 스트림에 중요 작업 제출 */
criticalKernel<<<grid, block, 0, high_prio_stream>>>(d_data);
backgroundKernel<<<grid, block, 0, low_prio_stream>>>(d_buf);
ROCm 큐 타입 및 특성
큐 타입API용도특성
컴퓨트 큐 hipStreamCreate 커널 디스패치 비동기, 여러 스트림 병렬
SDMA 큐 hipMemcpyAsync DMA 전송 컴퓨트 큐와 독립 병렬 실행
기본 스트림 (NULL) hipLaunchKernel(..., 0) 단순 직렬 실행 동기적, 이전 작업 완료 후 시작
cooperative 스트림 hipLaunchCooperativeKernel Grid-wide 동기화 모든 블록 동시 상주 보장 필요
DMA와 컴퓨트 오버랩: SDMA 엔진과 GFX 컴퓨트 엔진은 독립적으로 동작합니다. 서로 다른 스트림에 hipMemcpyAsync와 커널 실행을 배치하면 데이터 전송과 컴퓨트가 동시에 진행됩니다(compute-transfer overlap). 이를 위해 핀드 호스트 메모리와 충분한 VRAM이 필요합니다.
hipEvent_t 동기화: hipEventRecord + hipStreamWaitEvent로 스트림 간 의존성을 표현합니다. 예를 들어 스트림 A의 memcpy 완료 후 스트림 B의 커널이 시작되도록 할 수 있습니다. 이는 CUDA graph와 유사하며, ROCm의 hipGraph API로 더 정교하게 표현할 수도 있습니다.
컨텍스트 스위치 비용: 여러 프로세스가 동시에 GPU를 사용할 때 KFD의 GPU 스케줄러가 HQD를 컨텍스트 스위치합니다. 컨텍스트 스위치는 VGPR/LDS 상태를 저장/복원해야 하므로 수십 µs의 오버헤드가 발생합니다. HPC 환경에서는 한 프로세스가 GPU를 독점하도록 스케줄링하는 것이 일반적입니다.

ROCm 라이브러리 생태계

ROCm은 수치 연산, 딥러닝, 신호 처리, 희소 행렬 등 다양한 도메인을 위한 고도로 최적화된 라이브러리를 제공합니다. 이 라이브러리들은 AMD GPU의 Matrix Core, LDS 계층, 메모리 대역폭을 최대한 활용하도록 수작업 튜닝된 커널을 포함합니다. 대부분의 경우 직접 커널을 작성하기보다 이 라이브러리를 활용하는 것이 더 높은 성능을 얻을 수 있습니다.

/* rocBLAS: CUDA cuBLAS 호환 GEMM 예제 */
#include <rocblas/rocblas.h>

rocblas_handle handle;
rocblas_create_handle(&handle);

/* C = alpha * A * B + beta * C  (행렬 곱) */
const int M = 4096, N = 4096, K = 4096;
const float alpha = 1.0f, beta = 0.0f;

/* FP32 GEMM */
rocblas_sgemm(handle,
    rocblas_operation_none, rocblas_operation_none,
    M, N, K,
    &alpha,
    d_A, M,   /* A: M×K */
    d_B, K,   /* B: K×N */
    &beta,
    d_C, M);  /* C: M×N */

/* FP16 GEMM (Tensor Core / Matrix Core 활용) */
rocblas_hgemm(handle,
    rocblas_operation_none, rocblas_operation_none,
    M, N, K,
    &alpha_h16, d_A_h16, M, d_B_h16, K,
    &beta_h16,  d_C_h16, M);

rocblas_destroy_handle(handle);
/* rocFFT: FFT 실행 예제 */
#include <rocfft/rocfft.h>

rocfft_setup();  /* 라이브러리 초기화 */

rocfft_plan plan;
size_t lengths[1] = { 1024 };  /* 1D FFT 크기 */

rocfft_plan_create(&plan,
    rocfft_placement_inplace,      /* in-place FFT */
    rocfft_transform_type_complex_forward,
    rocfft_precision_single,       /* FP32 복소수 */
    1,      /* 차원 */
    lengths,
    1,      /* 배치 수 */
    nullptr);

/* 작업 버퍼 크기 조회 */
rocfft_plan_description desc;
size_t work_buf_size;
rocfft_plan_get_work_buffer_size(plan, &work_buf_size);

void *work_buf = nullptr;
if (work_buf_size) hipMalloc(&work_buf, work_buf_size);

/* FFT 실행 */
rocfft_execution_info info;
rocfft_execution_info_create(&info);
rocfft_execution_info_set_work_buffer(info, work_buf, work_buf_size);

void *in_out[] = { d_complex_data };
rocfft_execute(plan, in_out, nullptr, info);

rocfft_plan_destroy(plan);
rocfft_teardown();
/* MIOpen: 딥러닝 합성곱 예제 (cuDNN 호환) */
#include <miopen/miopen.h>

miopenHandle_t handle;
miopenCreate(&handle);

/* 텐서 디스크립터 생성 */
miopenTensorDescriptor_t inputDesc, filterDesc, outputDesc;
miopenCreateTensorDescriptor(&inputDesc);
miopenSet4dTensorDescriptor(inputDesc, miopenFloat,
    1, 64, 224, 224);  /* N, C, H, W */

miopenCreateTensorDescriptor(&filterDesc);
miopenSet4dTensorDescriptor(filterDesc, miopenFloat,
    64, 64, 3, 3);     /* K, C, R, S */

/* 합성곱 디스크립터 */
miopenConvolutionDescriptor_t convDesc;
miopenCreateConvolutionDescriptor(&convDesc);
miopenInitConvolutionDescriptor(convDesc,
    miopenConvolution, 1, 1, 1, 1, 1, 1);

/* 출력 크기 계산 → 합성곱 실행 */
int n, c, h, w;
miopenGetConvolutionForwardOutputDim(convDesc, inputDesc,
    filterDesc, &n, &c, &h, &w);

miopenConvolutionForward(handle, &alpha,
    inputDesc, d_input, filterDesc, d_filter,
    convDesc, algo, &beta, outputDesc, d_output,
    workspace, workspace_size);

miopenDestroy(handle);
ROCm 라이브러리 생태계 일람
라이브러리CUDA 대응기능핵심 API
rocBLAScuBLAS기본 선형 대수 (GEMM, TRSM 등)rocblas_sgemm
hipBLASLtcublasLt경량 GEMM, 텐서 연산, epiloguehipblasltMatmul
rocFFTcuFFTFFT (1D/2D/3D, C2C/R2C)rocfft_execute
rocRANDcuRAND난수 생성 (Mersenne Twister, XORWOW)rocrand_generate
MIOpencuDNN딥러닝 연산자 (Conv, BN, Pool, LSTM)miopenConvolutionForward
RCCLNCCL집합 통신 (AllReduce, AllGather 등)ncclAllReduce
rocSPARSEcuSPARSE희소 행렬 연산 (SpMM, SpMV)rocsparse_spmm
rocALUTION-반복 선형 솔버 (CG, GMRES)C++ 객체 API
hipCUBCUBGPU 병렬 알고리즘 (sort, scan)DeviceRadixSort
rocThrustThrust고수준 STL 유사 GPU 알고리즘thrust::sort
hipSOLVERcuSOLVER밀집 선형 솔버 (LU, QR, SVD)hipsolverDgetrf
rocWMMAWMMA행렬 곱 가속기 직접 접근 APIrocwmma::mma_sync
hipBLASLt 선택: ROCm 5.5+에서 도입된 hipBLASLt는 epilogue fusion, 사용자 정의 행렬 레이아웃, 혼합 정밀도(FP8/BF16/FP16) GEMM을 지원합니다. PyTorch의 torch.nn.functional.linear는 내부적으로 hipBLASLt를 사용합니다.
MIOpen 자동 튜닝: MIOpen은 첫 실행 시 여러 커널 구현을 벤치마킹하여 최적 알고리즘을 선택하고 캐시에 저장합니다(~/.config/miopen/). 도커 컨테이너 환경에서는 이 캐시 디렉터리를 볼륨 마운트로 보존하여 컨테이너 재시작마다 튜닝을 반복하지 않도록 합니다.
RCCL (ROCm Collective Communication Library): NCCL API와 완전 호환되는 Multi-GPU 집합 통신 라이브러리입니다. XGMI 링크를 통한 직접 GPU 간 통신을 활용하여 AllReduce 등 연산의 대역폭을 최대화합니다. PyTorch DDP(Distributed Data Parallel)의 AMD 백엔드로 사용됩니다.

Composable Kernel (CK) 라이브러리

AMD의 Composable Kernel(CK)은 C++ 템플릿 메타프로그래밍을 기반으로 한 GPU 커널 빌딩 블록 라이브러리입니다. rocBLAS, MIOpen, Flash Attention 등 AMD의 주요 고성능 라이브러리들이 내부적으로 CK를 사용합니다. CK의 핵심 가치는 타일(tile) 수준의 추상화입니다. 각 연산을 타일 단위로 분해하고, 타일별 크기·레이아웃·로딩 방식을 컴파일 타임에 결정하여 레지스터 및 LDS(Local Data Share) 활용을 최대화합니다.

CK는 장치별 마이크로커널(예: Matrix Core MFMA 명령어 래핑)을 직접 구현하며, 이를 Tensor Operation 레이어에서 GEMM, Convolution, Softmax, LayerNorm, Attention 등으로 조합합니다. 최종 사용자는 Device API 레이어만 호출하면 되고, 내부의 타일 스케줄링과 메모리 파이프라인은 CK가 자동으로 처리합니다.

CK 아키텍처와 타일 프로그래밍

CK 타일 프로그래밍 모델의 핵심은 TensorDescriptor입니다. 다차원 텐서의 논리적 형태(shape)와 물리적 메모리 레이아웃(stride, padding, transpose)을 컴파일 타임 타입으로 인코딩합니다. 이를 통해 런타임 분기 없이 다양한 행렬 레이아웃(RowMajor/ColumnMajor)을 동일한 커널 코드로 처리합니다. BlockwiseGemmXdlops는 하나의 워크그룹이 담당하는 출력 타일을 MFMA 명령어로 계산하는 핵심 빌딩 블록이며, M/N/K 방향의 타일 크기를 템플릿 파라미터로 지정합니다.

// CK GEMM 타일 크기 구성 예제
// C = A * B  (M×K) × (K×N) = M×N
#include <ck/tensor_operation/gpu/device/device_gemm_xdl.hpp>
#include <ck/tensor_operation/gpu/element/element_wise_operation.hpp>

using namespace ck;
using namespace ck::tensor_operation;
using namespace ck::tensor_operation::device;

// 타입 정의
using ADataType    = ck::half_t;
using BDataType    = ck::half_t;
using CDataType    = ck::half_t;
using AccDataType  = float;

// 레이아웃 지정 (행우선/열우선)
using ALayout = Row;
using BLayout = Col;  // B 전치
using CLayout = Row;

// 요소별 연산 (에필로그)
using AElementOp = element_wise::PassThrough;
using BElementOp = element_wise::PassThrough;
using CEElementOp = element_wise::PassThrough;

// GEMM Device 인스턴스 (타일 크기 템플릿 파라미터)
using DeviceGemmInstance = DeviceGemmXdl<
    ALayout, BLayout, CLayout,
    ADataType, BDataType, CDataType, AccDataType,
    AElementOp, BElementOp, CEElementOp,
    256,   // BlockSize: 워크그룹 스레드 수
    256,   // MPerBlock: M 방향 타일 크기
    128,   // NPerBlock: N 방향 타일 크기
    32,    // KPerBlock: K 방향 타일 크기 (LDS 사용)
    8,     // AK1: A 행렬 벡터화 단위
    8,     // BK1: B 행렬 벡터화 단위
    32,    // MPerXDL: MFMA M 타일
    32,    // NPerXDL: MFMA N 타일
    1, 2,  // MXdlPerWave, NXdlPerWave
    S<4,64,1>,  // ABlockTransfer ThreadCluster
    S<4,64,1>,  // BBlockTransfer ThreadCluster
    1, 1, 1   // CBlockTransfer 파라미터
>;

int run_ck_gemm(
    int M, int N, int K,
    const ADataType* dA,
    const BDataType* dB,
    CDataType* dC)
{
    auto gemm = DeviceGemmInstance{};
    auto invoker  = gemm.MakeInvoker();
    auto argument = gemm.MakeArgument(
        dA, dB, dC,
        M, N, K,
        K,   // StrideA
        K,   // StrideB (B가 전치이므로 K)
        N,   // StrideC
        AElementOp{}, BElementOp{}, CEElementOp{});

    if (!gemm.IsSupportedArgument(argument)) {
        return -1;  // 타일 크기가 M/N/K로 나누어지지 않음
    }
    invoker.Run(argument, StreamConfig{nullptr, false});
    return 0;
}

CK GEMM 성능 튜닝

CK 기반 GEMM은 MPerBlock, NPerBlock, KPerBlock, BlockSize 네 가지 타일 파라미터가 성능에 가장 큰 영향을 미칩니다. MI300X(CDNA3)에서는 256×128×32 타일 구성이 FP16 GEMM에서 가장 넓은 범위의 행렬 크기에 대해 최고 성능을 내는 경향이 있습니다. CK는 PerfConfig 열거로 주어진 행렬 크기에 대해 최적 구성을 자동 선택하는 커널 선택(kernel selection) 메커니즘도 제공합니다.

// CK 최적 커널 자동 선택 (kernel selection API)
#include <ck/tensor_operation/gpu/device/device_gemm_multiple_d_xdl_cshuffle.hpp>

// 컴파일 타임에 등록된 모든 구성을 런타임에 프로파일링 후 최적 선택
auto gemms = DeviceOperationInstanceFactory<
    DeviceGemmInstance>::GetInstances();

float best_time = std::numeric_limits<float>::max();
int   best_idx  = -1;

for (int i = 0; i < (int)gemms.size(); i++) {
    auto& gemm = *gemms[i];
    auto arg = gemm.MakeArgument(
        dA, dB, dC, M, N, K, K, K, N,
        AElementOp{}, BElementOp{}, CEElementOp{});
    if (!gemm.IsSupportedArgument(arg)) continue;

    auto invoker = gemm.MakeInvoker();
    float t = invoker.Run(
        arg, StreamConfig{stream, true}); // time_kernel=true
    if (t < best_time) { best_time = t; best_idx = i; }
}
// best_idx 인스턴스로 이후 실행

CK 퓨전 연산

CK의 강점 중 하나는 에필로그 퓨전(epilogue fusion)입니다. GEMM 결과에 적용되는 Bias 덧셈, ReLU/GELU 활성화, LayerNorm, Softmax를 출력 타일을 레지스터에 유지한 채 연속 적용하여 Global Memory 왕복을 줄입니다. Transformer 모델의 Attention 연산은 QKᵀ GEMM → Softmax → V GEMM 세 단계가 하나의 CK 퓨전 커널로 처리됩니다(Flash Attention 방식).

// CK 퓨전 연산: GEMM + BiasAdd + GELU in one kernel
#include <ck/tensor_operation/gpu/device/device_gemm_multiple_d_xdl_cshuffle.hpp>

// 에필로그: C = GELU(A*B + Bias)
using CDEElementOp = ck::tensor_operation::element_wise::AddGelu;

using DeviceFusedGemm = DeviceGemmMultipleD_Xdl_CShuffle<
    ALayout, BLayout,
    Tuple<BiasLayout>,  // D 텐서 목록 (Bias)
    ELayout,
    ADataType, BDataType,
    Tuple<BiasDataType>,
    EDataType,
    AccDataType,
    AElementOp, BElementOp, CDEElementOp,
    GemmDefault,
    256, 256, 128, 32, 8, 8, 32, 32, 4, 2
>;

auto gemm_fused = DeviceFusedGemm{};
auto arg = gemm_fused.MakeArgument(
    dA, dB,
    std::array<const void*, 1>{dBias},  // D 텐서 배열
    dE,
    M, N, K,
    K, K,
    std::array<int, 1>{0},  // Bias stride (broadcast)
    N,
    AElementOp{}, BElementOp{},
    CDEElementOp{});

auto invoker = gemm_fused.MakeInvoker();
invoker.Run(arg, StreamConfig{stream, false});
CK 지원 연산 및 특성
연산 범주 Device API 클래스 지원 데이터 타입 퓨전 가능 에필로그
GEMM DeviceGemmXdl FP16, BF16, FP8, INT8 Bias, ReLU, GELU, Sigmoid
Batched GEMM DeviceBatchedGemmXdl FP16, BF16 Bias, Scale
Grouped GEMM DeviceGroupedGemmXdl FP16, BF16 각 그룹별 에필로그 설정
Convolution (Forward) DeviceConvFwdXdl FP16, BF16, INT8 Bias, ReLU, BN-Relu 퓨전
Convolution (BWD) DeviceConvBwdDataXdl FP16, BF16 없음 (순수 역전파)
Softmax DeviceSoftmax FP16, BF16, FP32 Scale (temperature 나눗셈)
LayerNorm DeviceLayernorm FP16, BF16, FP32 Gamma/Beta 적용
Flash Attention DeviceGroupedMultiheadAttentionForward FP16, BF16, FP8 QKᵀ+Softmax+V 완전 퓨전
MIOpen과 CK의 관계 MIOpen은 AMD의 딥러닝 원시 연산 라이브러리로 내부적으로 CK 기반 커널을 포함합니다. ROCm 5.3 이후 MIOpen은 특정 행렬 크기와 데이터 타입에 대해 CK 커널을 기존 OpenCL/HIP 커널 대신 자동으로 선택합니다. 직접 CK를 사용하면 MIOpen의 커널 선택 오버헤드 없이 특정 형상에 최적화된 커널을 고정하여 사용할 수 있어 서빙 환경에서 레이턴시 예측성이 높아집니다.
CK 설치 및 CMake 연동 ROCm 패키지 저장소에서 composablekernel-dev 패키지로 설치하거나 소스에서 빌드합니다(cmake -DGPU_TARGETS="gfx942;gfx90a"). CMake 프로젝트에서는 find_package(composable_kernel REQUIRED)target_link_libraries(myapp PRIVATE composable_kernel::device_gemm_operations)로 연결합니다.
CK 컴파일 시간 주의 CK는 C++ 템플릿을 대량으로 인스턴스화하므로 풀 빌드 시 GPU 아키텍처당 수십 분이 소요될 수 있습니다. 개발 중에는 -DGPU_TARGETS="gfx942"처럼 대상 아키텍처를 한 가지로 제한하고, -DCK_USE_CODEGEN=OFF로 Python 코드 생성을 비활성화하면 빌드 시간을 크게 줄일 수 있습니다.

rocProfiler / rocTracer

ROCm 프로파일링 인프라는 GPU의 하드웨어 성능 카운터(PMC)를 수집하는 rocProfiler와 HIP/ROCr API 호출을 추적하는 rocTracer로 구성됩니다. omniperf는 이들 위에 구축된 고수준 분석 도구로, roofline 분석, 캐시 효율, 메모리 대역폭 활용률 등 종합적인 성능 리포트를 생성합니다. rocProf v2(ROCm 6.x)는 rocProfiler SDK를 기반으로 한 새 세대 프로파일러입니다.

# rocprof: 커널 실행 시간 프로파일링
rocprof --stats ./my_hip_app arg1 arg2
# 출력: results.csv (커널별 실행 횟수, 총 시간, 평균 시간)

# 특정 PMC 카운터 수집
rocprof --input metrics.txt ./my_hip_app
# metrics.txt 내용:
# pmc: SQ_WAVES, SQ_INSTS_VALU, SQ_INSTS_VMEM_RD
# range: 0:10000  (커널 N번째 실행 범위)

# HIP API 타임라인 추적 (roctracer 사용)
rocprof --hip-trace ./my_hip_app
# 출력: results.json (Chrome Trace 포맷)
# chrome://tracing 에서 시각화 가능

# HSA API 레벨 추적
rocprof --hsa-trace ./my_hip_app
# rocprof 주요 PMC 메트릭 예시 (metrics.txt)
# -- VALU 활용률: VALU 명령 / 전체 클럭 사이클
pmc: SQ_WAVES SQ_INSTS_VALU SQ_INSTS_SALU SQ_INSTS_VMEM_RD SQ_INSTS_VMEM_WR

# -- L1/L2 캐시 히트율
pmc: TCP_TOTAL_CACHE_ACCESSES TCP_TCP_LATENCY TCP_TOTAL_HIT

# -- 메모리 컨트롤러 대역폭
pmc: TCC_EA_RDREQ_32B TCC_EA_WRREQ_32B TCC_WRREQ_64B

# -- Wavefront 점유율
pmc: SQ_WAIT_INST_ANY SQ_ACTIVE_INST_ANY
/* roctracer API를 애플리케이션 코드에서 직접 사용 */
#include <roctracer/roctracer_hip.h>

/* HIP API 활동 콜백 등록 */
roctracer_enable_domain_activity(ACTIVITY_DOMAIN_HIP_API);
roctracer_enable_domain_activity(ACTIVITY_DOMAIN_HIP_OPS);

/* 사용자 정의 영역 마킹 */
roctxRangePush("Forward Pass");
runForwardPass(...);
roctxRangePop();

roctxRangePush("Backward Pass");
runBackwardPass(...);
roctxRangePop();
# omniperf: 심층 성능 분석 (MI100/MI200/MI300 전용)
# 1단계: 프로파일 수집
omniperf profile -n my_profile -- ./my_hip_app

# 2단계: 분석 리포트 생성
omniperf analyze -p workloads/my_profile/mi300x_a1

# 특정 분석 모듈만 선택
omniperf analyze -p workloads/my_profile/mi300x_a1 \
    --list-metrics  # 가용 메트릭 목록

# Roofline 분석: 산술 강도 vs 실제 성능
omniperf analyze -p workloads/my_profile/mi300x_a1 \
    --roofline
ROCm 프로파일링 도구 비교
도구레벨출력 형식주요 용도
rocprof --stats커널 수준CSV커널별 실행 시간, 호출 횟수
rocprof --hip-traceAPI 수준JSON (Chrome)API 타임라인, overlap 확인
rocprof --pmc하드웨어CSVPMC 카운터 (VALU util, BW 등)
omniperf시스템 수준리포트/GUIRoofline, 메모리 효율, 병목 분석
Radeon GPU Profilerwavefront 수준GUI 파형wavefront 상태, 지연 원인 시각화
rocm-smi시스템 수준텍스트GPU 클럭, 온도, 전력, 메모리 사용량
roofline 모델: omniperf의 roofline 분석은 커널의 산술 강도(FLOP/Byte)를 계산하고 메모리 대역폭 지붕(roof)과 컴퓨트 지붕 중 어디에 병목이 있는지 시각화합니다. 산술 강도가 낮으면 메모리 바운드, 높으면 컴퓨트 바운드입니다. 최적화 방향: 메모리 바운드 → 데이터 재사용/캐시 활용, 컴퓨트 바운드 → VALU 유닛 포화도 향상.
ATT (AMD Thread Trace): rocProfiler의 ATT 기능은 wavefront 수준의 실행 추적을 수집합니다. 어떤 명령어가 실행됐는지, 어디서 대기가 발생했는지 명령어 레벨에서 분석할 수 있습니다. 매우 상세한 데이터를 생성하므로 짧은 구간에만 활성화하는 것이 좋습니다.
PMC 수집 시 주의사항: 일부 PMC 카운터는 동시에 수집할 수 없어 여러 번 실행해야 합니다. rocprof는 자동으로 패스를 분할하지만, 커널 실행 결과가 매번 달라서는 안 됩니다(idempotent 필요). 또한 PMC 수집 자체가 약간의 오버헤드를 유발하므로 프로덕션 환경에서는 비활성화해야 합니다.

omniperf 핵심 메트릭 심층 분석

omniperf는 단순한 실행 시간 측정을 넘어 GPU 하드웨어 카운터를 수집하고 Roofline 모델, 메모리 계층 효율, wavefront 스톨 원인까지 분석하는 종합 프로파일링 도구입니다. omniperf profile 단계에서 PMC를 수집하고 omniperf analyze에서 리포트를 생성합니다. GUI 모드(omniperf analyze --gui)는 웹 브라우저 기반 대화형 인터페이스를 제공합니다.

# omniperf 상세 메트릭 수집 및 분석

# 1단계: PMC 카운터 수집 (여러 패스 자동 분할)
omniperf profile   --name my_gemm_run   --hip-trace   --no-roof   -- ./my_gemm_app

# 2단계: 기본 분석 리포트 출력
omniperf analyze   --path workloads/my_gemm_run   --dispatch 0   --metric-list

# 3단계: 특정 메트릭 그룹만 조회
omniperf analyze   --path workloads/my_gemm_run   --dispatch 0   --block 2.1   # 2.1 = Compute Pipeline (VALU/MFMA 활용률)

# 4단계: Roofline 분석 (산술 강도 vs 대역폭 지붕)
omniperf profile --name roofline_run --roof-only -- ./my_app
omniperf analyze --path workloads/roofline_run --roof

# 5단계: 메모리 계층 분석 (L1/L2 히트율)
omniperf analyze   --path workloads/my_gemm_run   --dispatch 0   --block 5.1   # 5.1 = L1 캐시 통계
  --block 5.2   # 5.2 = L2 캐시 통계

# 6단계: CSV로 내보내기 (자동화 파이프라인 통합)
omniperf analyze   --path workloads/my_gemm_run   --dispatch 0   --output-format csv   --output my_metrics.csv
omniperf 핵심 메트릭 목록과 의미
메트릭 ID메트릭 이름의미이상적 값문제 징후
2.1.0VALU UtilizationVector ALU 유닛 사용률 (%)80% 이상낮으면 메모리 대기 또는 분기 발산
2.1.1MFMA UtilizationMatrix Core 사용률 (%)70% 이상 (AI 커널)낮으면 GEMM 타일 크기 부적합
2.1.2SALU UtilizationScalar ALU 사용률 (%)20% 이하높으면 스칼라 루프/인덱스 계산 과다
3.1.0Wavefront Occupancy활성 wavefront 수 / 최대 wavefront 수50~100%낮으면 VGPR 또는 LDS 점유율 초과
4.0.0Fetch Stall Cycles명령어 fetch 대기 사이클 비율5% 이하높으면 instruction cache miss
5.1.0L1 Hit RateL1 벡터 캐시 히트율 (%)80% 이상낮으면 비연속 메모리 접근 패턴
5.2.0L2 Hit RateL2 캐시 히트율 (%)60% 이상낮으면 working set이 L2 용량 초과
5.2.1L2→VRAM BandwidthL2 미스로 인한 VRAM 접근 대역폭HBM 피크의 80%피크 미달이면 코얼레싱 문제
6.0.0Wavefront LDS StallLDS 뱅크 충돌 또는 LDS 대역폭 포화로 인한 스톨2% 이하높으면 LDS 접근 패턴 재설계 필요
7.0.0Arithmetic IntensityFLOP / 메모리 바이트 비율알고리즘 의존Roofline 지붕 유형 판별에 사용
omniperf 워크플로우 자동화: CI/CD 파이프라인에서 omniperf를 통합하려면 --output-format csv로 메트릭을 내보낸 후 Python pandas로 분석하세요. 예를 들어 MFMA Utilization이 특정 임계값(예: 60%) 미만이면 빌드를 실패 처리하는 회귀 방지 테스트를 구성할 수 있습니다. omniperf profile --kernel-regex ".*gemm.*"으로 특정 커널만 프로파일링을 제한하면 수집 시간을 줄일 수 있습니다.
Roofline 분석 해석: omniperf Roofline 그래프에서 커널 점이 (1) 메모리 지붕(대각선) 왼쪽에 있으면 메모리 바운드: LDS 타일링, L1 재사용 늘리기, (2) 컴퓨트 지붕(수평선) 아래에 있으면 컴퓨트 바운드: MFMA 활용, FP8 정밀도, 루프 언롤링, (3) 두 지붕의 교점 근처에 있으면 이상적 균형 상태입니다. MI300X의 HBM3 대역폭 지붕은 약 5.3 TB/s, FP8 컴퓨트 지붕은 약 5.2 PFLOPS입니다.

rocProfiler v2 SDK (rocprofiler-sdk)

ROCm 6.x에서 새롭게 도입된 rocprofiler-sdk는 기존 rocProfiler v1의 후속입니다. 콜백 기반 API로 HIP API 호출, 커널 실행, 메모리 복사 등 다양한 이벤트를 프로그래밍 방식으로 추적할 수 있습니다. v1보다 멀티 에이전트(multi-GPU) 지원과 스레드 안전성이 크게 개선되었으며, Perfetto, OpenTelemetry와의 통합도 지원합니다.

/* rocprofiler-sdk 콜백 API 사용 예시 */
#include <rocprofiler-sdk/rocprofiler.h>

/* 커널 dispatch 콜백 함수 */
void kernel_dispatch_callback(
    rocprofiler_callback_tracing_record_t record,
    rocprofiler_user_data_t              *user_data,
    void                                 *callback_data)
{
    if (record.kind != ROCPROFILER_CALLBACK_TRACING_HIP_RUNTIME_API)
        return;

    auto *api_data = (const rocprofiler_callback_tracing_hip_api_data_t *)record.payload;

    if (record.phase == ROCPROFILER_CALLBACK_PHASE_ENTER) {
        /* API 호출 진입 시점 */
        printf("HIP API 진입: %s
",
               rocprofiler_get_hip_runtime_api_name(record.operation));
    } else if (record.phase == ROCPROFILER_CALLBACK_PHASE_EXIT) {
        /* API 호출 종료 시점 (반환값 등 확인 가능) */
        printf("HIP API 완료: %s, 시간=%lu ns
",
               rocprofiler_get_hip_runtime_api_name(record.operation),
               record.end_timestamp - record.start_timestamp);
    }
}

/* 커널 실행 완료 콜백 (하드웨어 타이밍 포함) */
void kernel_completion_callback(
    rocprofiler_callback_tracing_record_t record,
    rocprofiler_user_data_t              *user_data,
    void                                 *callback_data)
{
    if (record.kind != ROCPROFILER_CALLBACK_TRACING_KERNEL_DISPATCH)
        return;

    auto *kern = (const rocprofiler_callback_tracing_kernel_dispatch_data_t *)
                  record.payload;

    printf("커널: %s
", kern->kernel_name);
    printf("  GPU 실행 시간: %lu ns
", kern->end_timestamp - kern->start_timestamp);
    printf("  그리드: %u x %u x %u
",
           kern->dispatch_info.grid_size.x,
           kern->dispatch_info.grid_size.y,
           kern->dispatch_info.grid_size.z);
}

/* 초기화: 콜백 서비스 등록 */
void setup_profiling()
{
    rocprofiler_context_id_t ctx_id;
    rocprofiler_create_context(&ctx_id);

    /* HIP Runtime API 추적 활성화 */
    rocprofiler_configure_callback_tracing_service(
        ctx_id,
        ROCPROFILER_CALLBACK_TRACING_HIP_RUNTIME_API,
        nullptr, 0,           /* 모든 API 추적 (필터 없음) */
        kernel_dispatch_callback,
        nullptr
    );

    /* 커널 dispatch 완료 추적 활성화 */
    rocprofiler_configure_callback_tracing_service(
        ctx_id,
        ROCPROFILER_CALLBACK_TRACING_KERNEL_DISPATCH,
        nullptr, 0,
        kernel_completion_callback,
        nullptr
    );

    rocprofiler_start_context(ctx_id);
    /* ... 애플리케이션 실행 ... */
    rocprofiler_stop_context(ctx_id);
    rocprofiler_destroy_context(ctx_id);
}
rocprofv2 vs rocprofv1: 기존 rocprof(v1) 명령은 ROCm 6.x에서도 동작하지만 내부적으로 rocprofiler-sdk v2로 마이그레이션이 진행 중입니다. 새 도구인 rocprofv2 명령은 더 풍부한 필터링과 멀티 GPU 지원을 제공합니다: rocprofv2 --hip-trace --kernel-trace -o output.json ./myapp. Perfetto 형식으로 출력하면 Chrome 브라우저에서 ui.perfetto.dev로 시각화할 수 있습니다.
SDK 기반 커스텀 프로파일러 구축: rocprofiler-sdk를 링크하면 자체 프로파일링 라이브러리를 만들 수 있습니다. LD_PRELOAD=./libmy_profiler.so ./myapp으로 주입하거나 ROCPROFILER_PRELOAD=./libmy_profiler.so ./myapp으로 명시적으로 지정합니다. PyTorch의 torch.profiler ROCm 백엔드도 이 SDK를 사용합니다.

Multi-GPU 프로그래밍

ROCm은 여러 GPU를 연결하는 두 가지 인터커넥트를 지원합니다. XGMI(eXtended Global Memory Interface)는 AMD의 Infinity Fabric 기반 고대역폭 GPU 간 직접 링크로, 피어 GPU의 메모리를 원격 접근하거나 RDMA 방식으로 데이터를 전송합니다. PCIe는 표준 버스를 통한 연결로 대역폭이 낮지만 범용성이 높습니다. 서버 플랫폼에서 MI300X 8개는 XGMI 완전 연결(All-to-All) 토폴로지를 구성합니다.

/* P2P(Peer-to-Peer) 직접 GPU 간 메모리 접근 */
int canAccessPeer;

/* GPU 0 → GPU 1 P2P 가능 여부 확인 */
hipDeviceCanAccessPeer(&canAccessPeer, 0, 1);
if (canAccessPeer) {
    hipSetDevice(0);
    hipDeviceEnablePeerAccess(1, 0);  /* GPU0가 GPU1에 접근 허용 */
}

/* GPU 0의 메모리 할당 */
hipSetDevice(0);
float *d_src;
hipMalloc(&d_src, N * sizeof(float));

/* GPU 1의 메모리 할당 */
hipSetDevice(1);
float *d_dst;
hipMalloc(&d_dst, N * sizeof(float));

/* GPU 0에서 GPU 1로 직접 P2P 복사 (PCIe/XGMI DMA) */
hipMemcpyPeer(d_dst, 1, d_src, 0, N * sizeof(float));

/* GPU 1의 커널에서 GPU 0의 메모리 직접 접근 (XGMI 필요) */
hipSetDevice(1);
crossGpuKernel<<<grid, block>>>(d_src, d_dst, N);
/* d_src는 GPU 0의 메모리지만 XGMI로 직접 접근 가능 */
/* RCCL AllReduce: Multi-GPU 그래디언트 합산 예제 */
#include <rccl/rccl.h>

int nGPUs = 8;
ncclComm_t comms[8];
int devList[8] = {0,1,2,3,4,5,6,7};

/* RCCL 통신자 초기화 */
ncclCommInitAll(comms, nGPUs, devList);

/* 각 GPU에 스트림 생성 */
hipStream_t streams[8];
for (int i = 0; i < nGPUs; ++i) {
    hipSetDevice(i);
    hipStreamCreate(&streams[i]);
}

/* AllReduce: 모든 GPU의 그래디언트를 합산하여 모두에게 배포 */
ncclGroupStart();
for (int i = 0; i < nGPUs; ++i) {
    ncclAllReduce(
        d_grad[i],        /* 각 GPU의 그래디언트 버퍼 */
        d_grad[i],        /* 결과도 같은 위치에 */
        N,                /* 요소 수 */
        ncclFloat,        /* FP32 */
        ncclSum,          /* 합산 연산 */
        comms[i],
        streams[i]);
}
ncclGroupEnd();

for (int i = 0; i < nGPUs; ++i) {
    ncclCommDestroy(comms[i]);
}
# XGMI 토폴로지 및 링크 상태 확인
rocm-smi --showtopo
# 출력 예 (4 GPU, 완전 연결):
# GPU[0]: GPU[1]=XGMI GPU[2]=XGMI GPU[3]=XGMI
# GPU[1]: GPU[0]=XGMI GPU[2]=XGMI GPU[3]=XGMI
# ...

# XGMI 링크 대역폭 측정
rocm-smi --showxgmibw
# 또는 p2pBandwidthLatencyTest 도구 사용
p2pBandwidthLatencyTest

# GPU 토폴로지 상세 (Linux NUMA 노드 포함)
rocminfo | grep -A5 "Topology"
cat /sys/class/kfd/kfd/topology/nodes/*/io_links/*/type
인터커넥트 방식 비교
방식대역폭 (단방향)레이턴시전제조건사용 시나리오
XGMI 3.0 (×2 링크)~896 GB/s~수십 nsAMD 서버 플랫폼MI300X/MI250X 클러스터
PCIe 5.0 ×16~128 GB/s~수 µsPCIe 5.0 CPU소비자 GPU 멀티 카드
PCIe 4.0 ×16~64 GB/s~수 µsPCIe 4.0 CPU이전 세대 시스템
NVLink (NVIDIA)~900 GB/s~수십 nsNVIDIA NVLink-C2CH100 SXM 플랫폼
RCCL 환경 변수: RCCL 성능 튜닝에 유용한 환경 변수들: NCCL_DEBUG=INFO (디버그 로그), NCCL_ALGO=Ring 또는 Tree (집합 알고리즘 선택), NCCL_P2P_DISABLE=1 (P2P 비활성화 테스트), NCCL_SOCKET_IFNAME=eth0 (멀티 노드 NIC 지정).
Topology-aware 배치: 동일 XGMI 도메인의 GPU에 연관 데이터를 배치하면 통신 비용이 감소합니다. PyTorch의 torch.cuda.set_device(local_rank)ROCR_VISIBLE_DEVICES 환경 변수로 GPU 할당을 제어합니다.
Multi-GPU 메모리 모델: XGMI가 있어도 원격 GPU 메모리 접근은 로컬 메모리보다 느립니다. 커널 내에서 다른 GPU 메모리에 임의 접근하는 방식은 통신 패턴을 예측하기 어렵게 만들고 성능이 저하됩니다. RCCL의 명시적 집합 통신으로 통신을 계획적으로 관리하는 것이 좋습니다.

GPUDirect RDMA / ROCnRDMA

GPUDirect RDMA는 GPU VRAM과 네트워크 인터페이스 카드(NIC) 사이에서 CPU와 시스템 메모리를 경유하지 않고 직접 데이터를 전송하는 기술입니다. 전통적인 GPU↔네트워크 전송 경로는 GPU VRAM → CPU DRAM(복사) → NIC(DMA)였으나, GPUDirect RDMA를 사용하면 GPU VRAM → NIC(DMA) 경로로 단축되어 CPU 개입과 중간 복사 오버헤드가 제거됩니다. AMD에서는 이 기술의 구현을 ROCnRDMA라 부르며, amdgpu 커널 드라이버의 amdgpu_dmabuf_ops 및 PEER Direct 인터페이스를 통해 제공합니다.

HPC 클러스터에서 AllReduce 같은 집합 통신(collective)이 초대형 모델 학습의 병목이 됩니다. MI250X/MI300X + InfiniBand HDR(200 Gb/s)/NDR(400 Gb/s) 조합에서 ROCnRDMA는 노드 간 통신 지연시간을 크게 낮추고, RCCL이 이를 자동으로 활용합니다.

ROCnRDMA 개요와 원리

ROCnRDMA는 Linux 커널의 PEER Direct(또는 peer_mem) 인터페이스를 활용합니다. NIC 드라이버(예: Mellanox OFED의 mlx5_core)가 GPU 메모리 영역에 대한 DMA 매핑을 요청할 때, 표준 경로에서는 시스템 메모리만 허용됩니다. peer_mem 인터페이스는 서드파티 메모리 공급자(amdgpu)가 등록되면 NIC 드라이버가 GPU VRAM 영역을 직접 DMA 매핑할 수 있도록 합니다. ROCm 6.x 이후 DMA-BUF(Linux 5.12+)를 기반으로 한 방식으로 전환 중입니다.

## ROCnRDMA 환경 설정

# 커널 모듈 확인
lsmod | grep -E 'amdgpu|ib_core|mlx5'

# ROCm + OFED 설치 (ROCm 6.x → MLNX_OFED 5.8+)
apt-get install -y rocm-dev mlnx-ofed-all

# 구형: peer_mem 방식
modprobe amdgpu_peer_mem && dmesg | grep peer_mem

# 신형: DMA-BUF (ROCm 6.x 자동)
dmesg | grep -i "dmabuf\|p2p"

# BAR1 크기 확인 (VRAM 크기와 일치 필요)
lspci -vv -s <GPU_BDF> | grep "Memory at"

# 대역폭 테스트
ib_write_bw -d mlx5_0 --use_rocm=0

ROCnRDMA libibverbs 연동

GPU와 NIC가 동일한 NUMA 노드에 위치하는 것이 P2P DMA 성능에 중요합니다. ibv_reg_mr()에 GPU VRAM 포인터를 전달하면 ROCnRDMA/DMA-BUF가 내부적으로 처리합니다.

// ROCnRDMA: GPU 버퍼를 InfiniBand로 직접 전송
#include <infiniband/verbs.h>
#include <hip/hip_runtime.h>

ibv_mr* register_gpu_buf(
    ibv_pd* pd, void* gpu_ptr, size_t size)
{
    int flags = IBV_ACCESS_LOCAL_WRITE
              | IBV_ACCESS_REMOTE_WRITE
              | IBV_ACCESS_REMOTE_READ;
    return ibv_reg_mr(pd, gpu_ptr, size, flags);
}

void rdma_write_gpu(
    ibv_qp* qp, ibv_mr* src_mr,
    void* src, uint64_t dst_addr,
    uint32_t rkey, size_t size)
{
    ibv_sge sge = {
        .addr   = (uint64_t)src,
        .length = (uint32_t)size,
        .lkey   = src_mr->lkey,
    };
    ibv_send_wr wr = {
        .opcode     = IBV_WR_RDMA_WRITE,
        .send_flags = IBV_SEND_SIGNALED,
        .wr.rdma    = { .remote_addr = dst_addr, .rkey = rkey },
        .sg_list    = &sge,
        .num_sge    = 1,
    };
    ibv_send_wr* bad;
    ibv_post_send(qp, &wr, &bad);
}

MPI GPU-Aware 집합 통신

ROCm 환경에서 GPU-aware MPI는 통신 버퍼가 GPU VRAM에 있어도 MPI가 직접 처리합니다. Open MPI + UCX 조합이 ROCnRDMA를 자동으로 활용하며, CPU DRAM 복사 단계가 생략됩니다.

// GPU-Aware MPI AllReduce
#include <mpi.h>
#include <hip/hip_runtime.h>

int main(int argc, char** argv) {
    MPI_Init(&argc, &argv);
    int rank;
    MPI_Comm_rank(MPI_COMM_WORLD, &rank);
    hipSetDevice(rank % 8);

    const int N = 1 << 24;
    float* dGrad;
    hipMalloc(&dGrad, N * sizeof(float));

    computeGradients<<<grid, block>>>(dGrad, N);
    hipDeviceSynchronize();

    // GPU VRAM 포인터를 직접 MPI에 전달
    MPI_Allreduce(MPI_IN_PLACE, dGrad, N,
                  MPI_FLOAT, MPI_SUM, MPI_COMM_WORLD);

    hipFree(dGrad);
    MPI_Finalize();
}

## Open MPI + UCX 환경 설정
export OMPI_MCA_pml=ucx
export UCX_TLS=rc,rocm_copy,rocm_ipc,gdr_copy
mpirun -np 32 --map-by ppr:8:node ./my_app
ROCnRDMA 요구사항 및 지원 구성
구성 항목최소 요구사항권장 구성비고
Linux 커널 5.6+ (peer_mem) / 5.12+ (DMA-BUF) 6.1+ LTS DMA-BUF 방식 권장
ROCm 버전 4.5+ 6.x ROCm 6.x에서 DMA-BUF 기본 활성화
AMD GPU Vega20 (MI60) 이상 MI250X / MI300X BAR1 크기 ≥ VRAM 크기 필요
InfiniBand NIC Mellanox ConnectX-5 ConnectX-7 (NDR) Peer Direct 지원 드라이버 필요
OFED 버전 MLNX_OFED 5.4+ MLNX_OFED 5.8+ ROCm 버전과 호환 확인 필요
PCIe Gen3 x16 Gen4/Gen5 x16 GPU-NIC 동일 NUMA 노드 권장
MPI 구현 Open MPI 4.0+ Open MPI 5.0+ UCX 1.12+ 필요
ROCnRDMA BAR1 크기 설정 MI300X(192 GB)에서는 amdgpu.large_bar=1 커널 파라미터와 시스템 BIOS의 "Above 4G Decoding" / "Resizable BAR" 활성화가 필요합니다. lspci -vv -s <GPU_BDF> | grep "Memory at"로 BAR1 크기를 확인합니다.
RCCL + ROCnRDMA 자동 활용 RCCL은 UCX 백엔드 사용 시 ROCnRDMA를 자동으로 감지하여 AllReduce, AllGather, ReduceScatter 등에서 활용합니다. export NCCL_DEBUG=INFO로 선택된 전송 채널을 로그에서 확인할 수 있습니다.
ROCnRDMA 주요 장애 원인 (1) OFED-ROCm 버전 불일치, (2) BAR1 크기 부족(Resizable BAR 미설정), (3) iommu=pt 미설정으로 인한 충돌, (4) PCIe ACS 활성화로 P2P DMA 차단. dmesg | grep -i "p2p\|peer\|bar\|iommu"로 먼저 확인하십시오.

컨테이너와 가상화

ROCm은 Docker 컨테이너, Kubernetes(K8s), 그리고 제한적으로 가상화 환경에서도 동작합니다. AMD는 DockerHub에서 공식 ROCm 베이스 이미지를 제공하며, K8s에서는 AMD GPU Device Plugin을 통해 GPU 자원 할당을 자동화합니다. 가상화 환경에서는 SR-IOV(Single Root I/O Virtualization)나 MxGPU 기술로 GPU를 가상 기계에 노출합니다.

# Docker with ROCm: AMD 공식 베이스 이미지 사용
docker pull rocm/rocm-terminal:6.1
docker pull rocm/pytorch:latest

# ROCm 컨테이너 실행 (GPU 디바이스 전달)
docker run -it --rm \
    --device=/dev/kfd \           # KFD 디바이스 (필수)
    --device=/dev/dri \           # DRI 디바이스 (렌더 노드)
    --group-add video \           # video 그룹 추가
    --group-add render \          # render 그룹 추가
    --ipc=host \                  # RCCL 멀티 GPU를 위한 공유 메모리
    --cap-add=SYS_PTRACE \        # 디버거 사용 시
    --security-opt seccomp=unconfined \
    -v /home/user/data:/data \
    rocm/pytorch:latest \
    python3 train.py

# 특정 GPU만 컨테이너에 전달
# ROCR_VISIBLE_DEVICES=0,2 docker run ... (GPU 0, 2만 노출)
docker run --device=/dev/kfd --device=/dev/dri/renderD128 \
    -e ROCR_VISIBLE_DEVICES=0 \
    rocm/pytorch:latest python3 -c "import torch; print(torch.cuda.device_count())"
# Kubernetes: AMD GPU Device Plugin 배포
# 1. Device Plugin 설치
# kubectl apply -f https://raw.githubusercontent.com/RadeonOpenCompute/k8s-device-plugin/main/k8s-ds-amdgpu-dp.yaml

# 2. GPU를 요청하는 Pod 스펙
apiVersion: v1
kind: Pod
metadata:
  name: rocm-training-pod
spec:
  containers:
  - name: trainer
    image: rocm/pytorch:latest
    command: ["python3", "train.py"]
    resources:
      limits:
        amd.com/gpu: 4          # GPU 4개 요청
    env:
    - name: HIP_VISIBLE_DEVICES
      value: "0,1,2,3"
    volumeMounts:
    - name: dataset
      mountPath: /data
  volumes:
  - name: dataset
    persistentVolumeClaim:
      claimName: training-data-pvc
# SR-IOV 설정 (AMD MxGPU 가상화)
# amdgpu 드라이버에서 SR-IOV 활성화
modprobe amdgpu sriov_enabled=1

# VF(Virtual Function) 생성
echo 4 > /sys/bus/pci/devices/0000:03:00.0/sriov_numvfs
# 4개의 VF 생성 (각각 /dev/dri/renderD128~D131)

# VF를 VM에 passthrough (libvirt/QEMU)
virsh nodedev-detach pci_0000_03_08_0   # VF PCI 주소
# virsh XML에서 hostdev 항목으로 VM에 할당

# ROCm 환경에서 VF 확인
rocm-smi --showproductname
# VF에서는 물리 GPU 스펙의 1/N으로 표시됨
AMD GPU 가상화 옵션 비교
방식격리 수준성능ROCm 지원사용 사례
전체 passthrough (VFIO)완전 격리네이티브 ~100%완전 지원단일 VM 전용 GPU
SR-IOV (MxGPU)하드웨어 VF~90%제한적 지원VDI, 다중 VM 공유
Docker (bare metal)namespace네이티브 ~100%완전 지원ML/HPC 컨테이너
KVM Virtio-GPU소프트웨어낮음미지원디스플레이 전용
ROCm Docker 이미지 선택: AMD는 용도별로 여러 이미지를 제공합니다: rocm/dev-ubuntu-22.04:6.1-complete(전체 스택), rocm/pytorch:latest(PyTorch 통합), rocm/tensorflow:latest(TensorFlow), rocm/rocm-terminal:6.1(미니멀 터미널). 이미지 크기가 크므로 멀티 스테이지 빌드로 최종 이미지를 최소화하는 것을 권장합니다.
--ipc=host 옵션: RCCL을 단일 컨테이너에서 다중 GPU로 사용할 때 --ipc=host가 필요합니다. RCCL은 GPU 간 공유 메모리를 활용하므로 컨테이너의 /dev/shm이 충분히 커야 합니다. 또는 --shm-size=16g로 명시 설정합니다.
seccomp 정책: 기본 Docker seccomp 프로파일은 일부 ioctl을 차단할 수 있습니다. ROCm은 /dev/kfd에 다양한 ioctl을 사용하므로 --security-opt seccomp=unconfined가 필요한 경우가 있습니다. 프로덕션 환경에서는 ROCm에 필요한 최소 syscall만 허용하는 커스텀 seccomp 프로파일을 사용하세요.

AI/ML 프레임워크 통합

ROCm은 주요 AI/ML 프레임워크의 공식 백엔드로 지원됩니다. PyTorch는 torch.cuda API를 AMD GPU에서 그대로 사용할 수 있으며, TensorFlow-ROCm은 XLA-HLO 컴파일러가 ROCm 백엔드를 사용합니다. JAX는 XLA를 통해 ROCm을 지원합니다. 이 프레임워크들은 내부적으로 MIOpen, rocBLAS, RCCL을 호출하여 실제 GPU 연산을 수행합니다.

# PyTorch with ROCm: torch.cuda API는 변경 없이 AMD GPU에서 동작
import torch

# ROCm에서는 torch.cuda.is_available()이 True 반환
print(torch.cuda.is_available())      # True (ROCm 설치 시)
print(torch.cuda.get_device_name(0))  # AMD Instinct MI300X
print(torch.version.hip)              # ROCm HIP 버전

# 모델 학습 예제 (코드 변경 불필요)
device = torch.device("cuda")  # ROCm도 "cuda" 디바이스 명칭 사용
model = torch.nn.Linear(1024, 1024).to(device)
optimizer = torch.optim.Adam(model.parameters())

x = torch.randn(256, 1024, device=device)
y = model(x)
loss = y.sum()
loss.backward()
optimizer.step()

# Mixed Precision (FP16/BF16) with GradScaler
from torch.cuda.amp import autocast, GradScaler
scaler = GradScaler()

with autocast(dtype=torch.bfloat16):  # BF16 (MI300X 권장)
    output = model(x)
    loss = output.sum()
scaler.scale(loss).backward()
scaler.step(optimizer)
scaler.update()
# TensorFlow-ROCm 사용
import tensorflow as tf

# GPU 확인
gpus = tf.config.list_physical_devices('GPU')
print(f"ROCm GPUs: {gpus}")

# tf.data와 Keras로 학습
model = tf.keras.Sequential([
    tf.keras.layers.Dense(512, activation='relu'),
    tf.keras.layers.Dense(10, activation='softmax')
])
model.compile(optimizer='adam', loss='sparse_categorical_crossentropy')

# Mixed precision (BF16)
tf.keras.mixed_precision.set_global_policy('mixed_bfloat16')

# Multi-GPU 분산 학습
strategy = tf.distribute.MirroredStrategy()
with strategy.scope():
    dist_model = tf.keras.Sequential([...])
# JAX with ROCm backend
import jax
import jax.numpy as jnp

# ROCm 백엔드 활성화 (환경 변수 설정 필요)
# export JAX_BACKEND_TARGET=rocm
# 또는 xla_flags 설정

print(jax.devices())  # [RocmDevice(id=0), RocmDevice(id=1), ...]

# JIT 컴파일 함수
@jax.jit
def matmul(a, b):
    return jnp.dot(a, b)

a = jnp.ones((4096, 4096))
b = jnp.ones((4096, 4096))
c = matmul(a, b)

# pmap: 데이터 병렬화 (Multi-GPU)
@jax.pmap
def parallel_fn(x):
    return x * 2

xs = jnp.stack([a] * jax.device_count())
ys = parallel_fn(xs)
AI 프레임워크 ROCm 지원 현황
프레임워크지원 방식주요 제한설치 방법
PyTorch공식 ROCm 빌드 제공일부 CUDA 확장 미지원pip install torch --index-url .../rocm6.1/
TensorFlowtensorflow-rocm 패키지XLA 일부 연산 미지원pip install tensorflow-rocm
JAX실험적 ROCm 지원공식 바이너리 없음소스 빌드 필요
ONNX RuntimeROCm EP 지원일부 op 미지원pip install onnxruntime-rocm
vLLMROCm 공식 지원일부 커스텀 커널pip install vllm --extra-index-url .../rocm/
llama.cppHIP 백엔드성능 차이 있음cmake -DGGML_HIPBLAS=ON ..
PyTorch 설치: ROCm 버전에 맞는 PyTorch를 설치해야 합니다. https://pytorch.org/get-started/locally/에서 ROCm 버전을 선택하거나, AMD 공식 Docker 이미지 rocm/pytorch:latest를 사용하면 의존성 문제 없이 바로 실행할 수 있습니다. torch.version.hip으로 HIP 버전을 확인합니다.
CUDA 확장 모듈 호환성: PyTorch C++/CUDA 확장은 hipify 변환 후 재컴파일해야 합니다. Flash Attention 같은 CUDA 확장은 AMD용 flash-attention-rocm이나 composable_kernel 기반 구현으로 대체할 수 있습니다.
BF16 vs FP16: MI300X(CDNA3)는 BF16 연산에서 FP16보다 성능이 높고, 더 넓은 dynamic range로 학습 안정성이 좋습니다. PyTorch에서 torch.bfloat16, TF에서 mixed_bfloat16을 사용합니다. RDNA3(RX 7000)는 BF16 지원이 제한적이므로 FP16을 사용하는 것이 좋습니다.

LLM 추론 최적화

AMD MI300X(192 GB HBM3)는 현존하는 가속기 중 단일 카드 메모리 용량이 가장 크며, 이는 대형 언어 모델(LLM) 추론에 근본적인 이점을 제공합니다. NVIDIA H100 SXM5(80 GB HBM3e)와 비교하면 단일 카드 기준 2.4배 더 많은 모델 파라미터를 수용할 수 있어, 70B~405B 파라미터 모델을 더 적은 수의 카드로 서빙할 수 있습니다. ROCm 생태계에서 주요 LLM 추론 프레임워크들이 공식 AMD GPU 지원을 추가하고 있습니다.

LLM 추론의 성능은 크게 두 단계로 나뉩니다. 프리필(Prefill) 단계는 프롬프트 토큰 전체를 병렬 처리하여 KV 캐시를 생성하는 컴퓨트 집약 단계이고, 디코드(Decode) 단계는 토큰을 하나씩 자기회귀적으로 생성하는 메모리 대역폭 집약 단계입니다. MI300X의 HBM3는 5.2 TB/s 이상의 메모리 대역폭을 제공하여 디코드 단계의 병목을 완화합니다.

LLM 서빙 프레임워크

vLLM은 PagedAttention 알고리즘으로 KV 캐시 단편화를 제거하고 연속 배칭(continuous batching)을 구현한 고성능 서빙 엔진으로, ROCm 공식 지원이 vLLM 0.2.x 이후 추가되어 MI200/MI300 시리즈에서 작동합니다. llama.cpp는 GGML 양자화 포맷을 지원하며 HIP 백엔드로 ROCm GPU에서 실행됩니다.

## vLLM on ROCm 설치 및 구성

# 1. ROCm 지원 vLLM 설치
pip install vllm --extra-index-url \
    https://download.pytorch.org/whl/rocm6.0

# 2. 기본 서버 실행 (MI300X, Llama-3-70B BF16)
python -m vllm.entrypoints.openai.api_server \
    --model meta-llama/Meta-Llama-3-70B-Instruct \
    --dtype bfloat16 \
    --max-model-len 8192 \
    --tensor-parallel-size 1 \
    --gpu-memory-utilization 0.90

# 3. FP8 KV 캐시 + FP8 양자화 (MI300X 전용)
python -m vllm.entrypoints.openai.api_server \
    --model meta-llama/Meta-Llama-3-70B-Instruct \
    --dtype bfloat16 \
    --kv-cache-dtype fp8 \
    --quantization fp8 \
    --max-model-len 131072 \
    --tensor-parallel-size 1

# 4. 멀티 GPU 텐서 병렬 (8× MI300X, 405B 모델)
python -m vllm.entrypoints.openai.api_server \
    --model meta-llama/Meta-Llama-3.1-405B-Instruct \
    --dtype bfloat16 \
    --tensor-parallel-size 8 \
    --max-model-len 32768 \
    --gpu-memory-utilization 0.95

KV 캐시 메모리 관리

MI300X의 192 GB HBM은 LLM 추론에서 메모리 용량의 병목을 크게 완화합니다. Llama-3-70B(BF16)는 약 140 GB를 소비하여 단일 MI300X에서 완전히 수용할 수 있으며, 나머지 ~50 GB를 KV 캐시와 활성화 버퍼에 활용할 수 있습니다.

## Flash Attention ROCm 구성

# ROCm용 Flash Attention 설치
pip install flash-attn --no-build-isolation \
    --extra-index-url https://download.pytorch.org/whl/rocm6.0

# 소스 빌드 (ROCm)
export ROCM_HOME=/opt/rocm
export GPU_ARCHS="gfx942"
git clone https://github.com/ROCm/flash-attention
cd flash-attention && pip install -e . --no-build-isolation

## Flash Attention 사용 (BF16, causal)
import torch
from flash_attn import flash_attn_func

Q = torch.randn(2, 4096, 32, 128,
                dtype=torch.bfloat16, device='cuda')
K = torch.randn_like(Q)
V = torch.randn_like(Q)

out = flash_attn_func(Q, K, V, dropout_p=0.0, causal=True)
# HBM 왕복 최소화: softmax+matmul 온칩 퓨전

양자화 옵션

MI300X는 FP8(E4M3/E5M2)을 하드웨어 수준에서 지원합니다(CDNA3 아키텍처). FP8 양자화는 BF16 대비 모델 크기와 KV 캐시를 절반으로 줄이면서도 대부분의 모델에서 2% 이하의 정확도 손실을 보입니다. llama.cpp는 GGUF 포맷의 Q4_K_M, Q8_0 등 다양한 정밀도 옵션을 제공합니다.

## llama.cpp HIP 빌드 및 양자화

git clone https://github.com/ggerganov/llama.cpp
cd llama.cpp
mkdir build-hip && cd build-hip
cmake .. -DGGML_HIP=ON -DAMDGPU_TARGETS=gfx942 \
         -DCMAKE_BUILD_TYPE=Release
cmake --build . --config Release -j$(nproc)

# Q4_K_M: 4-bit 혼합 양자화 (품질 우선)
./quantize /path/to/llama3-70b-f16.gguf \
           llama3-70b-q4_k_m.gguf Q4_K_M

# GPU 추론 서버 (전 레이어 GPU 적재)
./llama-server \
    -m llama3-70b-q4_k_m.gguf \
    -ngl 80 --host 0.0.0.0 --port 8080 \
    -c 4096 --threads 16

성능 비교 및 벤치마크

MI300X는 메모리 대역폭 우위(5.2 TB/s vs H100의 3.35 TB/s)로 디코드 단계 처리량에서 유리하며, 메모리 용량 우위(192 GB vs 80 GB)로 대형 모델의 단일 카드 수용 및 긴 컨텍스트 서빙에서 두드러집니다.

ROCm 주요 LLM 추론 프레임워크 비교
프레임워크 ROCm 지원 주요 기능 권장 사용
vLLM 0.2.x+ PagedAttention, Continuous Batching, FP8 KV 프로덕션 서빙, OpenAI API 호환
Text Generation Inference (TGI) 2.0+ Flash Attention, Tensor Parallel, 동적 배칭 HuggingFace 모델 서빙
llama.cpp (HIP 백엔드) GGML HIP 전체 GGUF 양자화(Q4/Q5/Q8), CPU+GPU 혼합 엣지, 개발/테스트
SGLang 0.2+ RadixAttention, KV 캐시 공유, 병렬 디코딩 에이전트, Chain-of-Thought
MLC-LLM ROCm 지원 TVM 컴파일 최적화, 기기별 커널 자동 생성 특수 하드웨어 배포
MI300X vs H100 LLM 추론 성능 비교 (Llama-3-70B BF16)
항목 AMD MI300X (1×) NVIDIA H100 SXM5 (1×) 비고
HBM 용량 192 GB HBM3 80 GB HBM3e MI300X가 2.4× 우위
메모리 대역폭 5.2 TB/s 3.35 TB/s MI300X 약 1.55× 우위
FP16 TFLOPS 192 (sparse 383) 267 (sparse 534) H100이 약 1.4× 우위
Decode throughput (배치=1) ~1,400 tok/s ~900 tok/s HBM 대역폭에 비례
최대 컨텍스트 (BF16, 배치=1) ~800K 토큰 ~280K 토큰 KV 캐시 가용 공간 차이
405B BF16 단독 수용 카드 수 최소 5× MI300X 최소 11× H100 총 비용(TCO) 고려 필요
MI300X 메모리 용량 활용 전략 192 GB HBM3는 LLM 추론에서 다음 시나리오에 특히 유리합니다. (1) FP16/BF16 전정밀도 70B~120B 모델을 단일 카드에 올릴 때, (2) 긴 컨텍스트(128K~1M 토큰) 추론에서 KV 캐시 공간이 필요할 때, (3) 다수의 소형 모델(7B~13B)을 단일 카드에서 다중 로딩할 때.
PagedAttention과 ROCm vLLM의 PagedAttention은 KV 캐시를 고정 크기 블록(기본값 16토큰)으로 나누어 관리합니다. 이 방식은 시퀀스 길이가 다양한 배치에서 KV 캐시 단편화를 제거하고, 요청 간 KV 캐시 공유(prefix caching)를 가능하게 합니다. ROCm 환경에서 PagedAttention 커널은 CK 기반으로 구현되어 MI200/MI300에 최적화됩니다.
ROCm FP8 추론 지원 (MI300X) MI300X(CDNA3/gfx942)는 FP8(E4M3/E5M2) 행렬 연산을 MFMA 명령어 수준에서 지원합니다. vLLM에서 --kv-cache-dtype fp8으로 KV 캐시만 FP8로, --quantization fp8으로 모델 전체를 FP8로 활성화할 수 있습니다. FP8은 BF16 대비 메모리·대역폭을 50% 절약하고 처리량을 최대 2× 향상시킬 수 있습니다.
ROCm LLM 프레임워크 호환성 주의 ROCm 생태계에서 CUDA 대비 LLM 프레임워크 지원 시차가 있을 수 있습니다. NVIDIA Triton GPU 컴파일러 기반 커널이 ROCm에서 그대로 동작하지 않을 수 있으며, ROCm용 Triton 백엔드 또는 CK 기반 커널로 대체가 필요한 경우가 있습니다. TORCH_ROCM_AOTRITON_ENABLE_EXPERIMENTAL=1로 실험적 Triton 지원을 활성화할 수 있습니다.

디버깅과 트러블슈팅

ROCm 디버깅은 사용자 공간(HIP 런타임 오류, 메모리 접근 위반)부터 커널 공간(amdgpu/KFD 오류, GPU hang) 까지 여러 레벨에 걸쳐 있습니다. 주요 도구로는 rocgdb(GDB 기반 GPU 디버거), umr(amdgpu 레지스터 접근), dmesg(커널 GPU 에러 로그), 다양한 환경 변수를 통한 런타임 디버그 모드가 있습니다.

# rocgdb: HIP 애플리케이션 GPU 디버깅
# 컴파일 시 디버그 심볼 포함
hipcc -g -O0 -o myapp_debug myapp.cpp

# rocgdb 시작
rocgdb ./myapp_debug

# rocgdb 명령어 (GDB 호환)
# (rocgdb) break vectorAdd        # GPU 커널 브레이크포인트
# (rocgdb) run                    # 실행
# (rocgdb) info threads           # 모든 wavefront/스레드 표시
# (rocgdb) thread 1.1:0           # wavefront 1의 스레드 0 선택
# (rocgdb) print threadIdx.x      # GPU 빌트인 변수 출력
# (rocgdb) x/16f $s_p             # 스칼라 레지스터 메모리 덤프
# (rocgdb) info registers         # 레지스터 상태
# umr: amdgpu 레지스터 직접 접근 (디버그용)
# 설치: apt install umr 또는 소스 빌드

# GPU 레지스터 읽기 (예: GFX 상태)
umr -asic gfx942 -read 0x2100  # GRBM_STATUS 읽기

# 파워 레지스터 상태 확인
umr -asic gfx942 -pp          # 전원 상태 덤프

# 클럭 정보
umr -asic gfx942 -clocks
# GPU hang 분석: dmesg 로그 확인
dmesg -T | grep -E "amdgpu|kfd" | grep -i "error\|hang\|reset\|timeout"

# 정상 복구 로그 예:
# [Mon Mar 10 15:23:41] amdgpu 0000:03:00.0: amdgpu: GPU reset begin!
# [Mon Mar 10 15:23:42] amdgpu 0000:03:00.0: amdgpu: GPU reset succeeded

# GPU hang 상세 정보 (amdgpu_fence_info)
cat /sys/kernel/debug/dri/0/amdgpu_fence_info

# GPU 렌더링 진행 중인 작업 상태
cat /sys/kernel/debug/dri/0/amdgpu_ring_gfx_0

# GPU 메모리 누수 확인
cat /sys/kernel/debug/dri/0/amdgpu_gtt_mm
cat /sys/kernel/debug/dri/0/amdgpu_vram_mm
# ROCm 디버그 환경 변수

# HIP 런타임 오류 검사 활성화 (느리지만 상세 오류 정보)
export AMD_LOG_LEVEL=4         # 0=오류, 1=경고, 2=정보, 3=디버그, 4=상세
export HSA_ENABLE_SDMA=0       # SDMA 비활성화 (오류 격리)
export HIP_LAUNCH_BLOCKING=1   # 모든 커널을 동기적으로 실행 (오류 위치 특정)
export HIPCC_VERBOSE=1         # 컴파일러 상세 출력

# Address Sanitizer (메모리 오류 감지)
hipcc -fsanitize=address -o myapp myapp.cpp
export ASAN_OPTIONS=detect_leaks=1

# KFD 이벤트 추적
export ROCR_DEBUG_API=1

# GPU 코어 덤프 활성화 (amdgpu hang 시)
export AMD_DEBUG=vm_fault_info
ulimit -c unlimited
ROCm 디버깅 도구 목록
도구용도명령/경로
rocgdbGPU 커널 소스 레벨 디버깅rocgdb ./myapp
umramdgpu 하드웨어 레지스터 직접 접근umr -asic gfx942 -read ADDR
rocm-smiGPU 상태, 온도, 전력, 클럭 모니터링rocm-smi --showall
dmesg커널 수준 GPU 오류 로그dmesg | grep amdgpu
AMD_LOG_LEVELROCm 런타임 디버그 로그 레벨환경 변수 설정
HIP_LAUNCH_BLOCKING동기 커널 실행으로 오류 위치 특정환경 변수 설정
Address SanitizerGPU 메모리 경계 위반 감지hipcc -fsanitize=address
amdgpu debugfs커널 내부 GPU 상태 파일 시스템/sys/kernel/debug/dri/0/
HIP_LAUNCH_BLOCKING 활용: 비동기 커널 실행 중 오류가 발생하면 어떤 커널에서 문제가 생겼는지 특정하기 어렵습니다. HIP_LAUNCH_BLOCKING=1을 설정하면 모든 커널 런치가 동기적으로 실행되어 오류 직전 커널을 정확히 식별할 수 있습니다. 디버깅 후 반드시 제거하세요.
GPU hang 자동 복구: amdgpu는 GPU hang이 감지되면 자동으로 GPU 리셋을 시도합니다. options amdgpu gpu_recovery=1이 기본값입니다. 리셋 후 실행 중이던 프로세스는 SIGABRTSIGKILL을 받습니다. dmesg에 "GPU reset succeeded" 메시지가 있으면 자동 복구된 것입니다.
TDR (Timeout Detection and Recovery): Windows와 달리 Linux에서는 TDR 타임아웃이 기본으로 설정되어 있지 않지만, amdgpu의 amdgpu_fence_timeout 파라미터로 fence 타임아웃을 설정할 수 있습니다. 장시간 실행되는 컴퓨트 커널이 타임아웃으로 강제 종료될 수 있으므로 HPC 워크로드에서는 타임아웃을 충분히 크게 설정하거나 0(무한)으로 설정합니다.
rocgdb 멀티 wavefront 디버깅: GPU에서는 수십~수백 개의 wavefront가 동시에 실행됩니다. rocgdb는 info threads로 모든 활성 wavefront를 나열하고, thread <gpu_id>.<wavefront_id>:<lane> 형식으로 특정 스레드를 선택합니다. 조건부 브레이크포인트 break kernel.cpp:42 if threadIdx.x == 0을 활용하면 특정 스레드에서만 멈출 수 있습니다.

성능 최적화

ROCm GPU 성능 최적화는 roofline 모델에서 시작합니다. 커널이 메모리 바운드인지 컴퓨트 바운드인지 먼저 파악하고, 각 경우에 맞는 최적화 전략을 적용합니다. AMD GPU에서의 핵심 최적화 요소는 CU occupancy(동시 실행 wavefront 수), VGPR 사용량, LDS 활용, 메모리 접근 패턴, 분기 발산(divergence)입니다.

/* occupancy 계산 및 최적 블록 크기 선택 */
#include <hip/hip_runtime.h>

/* 특정 커널의 최적 블록 크기 자동 계산 */
int minGridSize, blockSize;
hipOccupancyMaxPotentialBlockSize(
    &minGridSize,
    &blockSize,
    myKernel,         /* 커널 함수 포인터 */
    0,                 /* 동적 LDS 크기 */
    0);                /* 블록 크기 제한 없음 */
printf("Optimal block size: %d\n", blockSize);

/* occupancy 수치 조회 */
int numBlocks;
hipOccupancyMaxActiveBlocksPerMultiprocessor(
    &numBlocks, myKernel, blockSize, 0);
printf("Blocks per CU: %d\n", numBlocks);
printf("Wavefronts per CU: %d\n",
       numBlocks * (blockSize / 64));
/* __launch_bounds__: 컴파일러에 실행 바운드 힌트 제공 */
/* 이로써 컴파일러가 더 공격적인 레지스터 할당 최적화 가능 */
__global__
__attribute__((amdgpu_flat_work_group_size(64, 256)))  /* min, max 블록 크기 */
void optimizedKernel(float *data, int N)
{
    int idx = blockIdx.x * blockDim.x + threadIdx.x;
    if (idx < N) {
        /* ... */
    }
}

/* CUDA 스타일 launch_bounds (HIP 호환) */
__global__ __launch_bounds__(256, 2)  /* maxThreadsPerBlock, minBlocksPerMP */
void anotherKernel(float *data) { /* ... */ }
/* 메모리 접근 최적화: 합체 접근(Coalescing) 패턴 */

/* 나쁜 패턴: 열 우선 접근 → VRAM 접근이 산발적 */
__global__ void bad_access(float *matrix, int rows, int cols)
{
    int col = blockIdx.x * blockDim.x + threadIdx.x;
    for (int row = 0; row < rows; ++row)
        matrix[row * cols + col] += 1.0f;  /* stride=cols, 비합체 */
}

/* 좋은 패턴: 행 우선 접근 → VRAM 합체 접근 */
__global__ void good_access(float *matrix, int rows, int cols)
{
    int row = blockIdx.y * blockDim.y + threadIdx.y;
    int col = blockIdx.x * blockDim.x + threadIdx.x;
    if (row < rows && col < cols)
        matrix[row * cols + col] += 1.0f;  /* 연속 접근, 합체 */
}
# AMDGCN 어셈블리 확인 (컴파일러 출력 검사)
hipcc -O3 --offload-arch=gfx942 \
    -save-temps \          # 중간 파일 보존
    -o myapp myapp.cpp

# 생성된 어셈블리 확인
ls -la *.s *.ll           # .s=어셈블리, .ll=LLVM IR

# 커널 레지스터 사용량 확인
hipcc -O3 --offload-arch=gfx942 \
    -Rpass-analysis=regalloc \
    myapp.cpp 2>&1 | grep "spilled\|VGPR"

# 어셈블리에서 VGPR 수 확인
grep "NumVgprs" *.s
ROCm GPU 성능 최적화 체크리스트
최적화 항목방법효과
메모리 합체 접근연속 주소 패턴, 스트라이드 1VRAM 대역폭 극대화
LDS 타일링행렬 타일을 LDS에 로드 후 재사용VRAM 접근 횟수 감소
VGPR 최소화__launch_bounds__, 루프 분할occupancy 향상
분기 발산 최소화조건부 코드를 블록 수준으로 이동SIMD 효율 향상
FP8/BF16 사용낮은 정밀도 타입 선택Matrix Core TFLOPS 극대화
비동기 메모리 전송hipMemcpyAsync + 스트림 오버랩PCIe ↔ 컴퓨트 오버랩
hipBLASLt 사용커스텀 GEMM 대신 라이브러리 활용최적화된 Matrix Core 커널
클럭 고정rocm-smi --setperfdeterminism재현 가능한 성능 측정
Roofline 분석 워크플로우: 1. omniperf profile로 커널 PMC 수집, 2. omniperf analyze --roofline으로 시각화, 3. 커널이 메모리 지붕 아래에 있으면 메모리 접근 최적화, 4. 컴퓨트 지붕 아래에 있으면 VALU 활용률 향상 (언롤링, SIMD 벡터화), 5. 지붕 위에 도달하면 더 빠른 메모리(LDS/레지스터)로 계층 이동.
클럭 최대화 설정: 기본적으로 amdgpu는 동적 클럭 주파수 스케일링(DVFS)을 사용합니다. 벤치마킹 시에는 rocm-smi --setperflevel highrocm-smi --setperfdeterminism 1900(클럭 고정 MHz)으로 일관된 클럭을 설정해야 재현 가능한 결과를 얻을 수 있습니다.
occupancy vs 지연 숨김 트레이드오프: 높은 occupancy가 항상 좋은 것은 아닙니다. wavefront 수가 많아지면 레지스터 파일 압박으로 캐시 적중률이 낮아질 수 있습니다. 실제로는 occupancy를 높이면서 동시에 VGPR을 줄이는 것이 최적입니다. 항상 실측 데이터(rocprof PMC)로 검증하세요.

메모리 코얼레싱 심층 분석

메모리 코얼레싱(coalescing)은 wavefront 내 64개 스레드의 메모리 접근을 가능한 한 적은 수의 VRAM 트랜잭션으로 병합하는 최적화입니다. AMD GPU의 최소 메모리 트랜잭션 단위는 64 바이트 또는 128 바이트입니다. 64개 스레드가 각각 4 바이트(float)에 접근할 때 완전 코얼레싱되면 256 바이트 = 4 트랜잭션으로 처리되지만, 완전 비코얼레싱(각 스레드가 임의 주소 접근)이면 최대 64 트랜잭션이 발생합니다.

/* 코얼레싱 분석 코드 패턴과 rocprof 메트릭 수집 */

/* 패턴 1: 완전 코얼레싱 (최적) - 연속 주소 접근 */
__global__ void coalesced_access(float *data, float *result, int n)
{
    int tid = blockIdx.x * blockDim.x + threadIdx.x;
    if (tid < n) {
        /* lane i가 data[tid]에 접근: 연속 → 완전 코얼레싱 */
        result[tid] = data[tid] * 2.0f;
    }
}

/* 패턴 2: 스트라이드 접근 (부분 코얼레싱) */
__global__ void strided_access(float *data, float *result, int n, int stride)
{
    int tid = blockIdx.x * blockDim.x + threadIdx.x;
    if (tid < n) {
        /* lane i가 data[tid * stride] 접근: stride=2이면 50% 효율 */
        result[tid] = data[tid * stride];  /* stride > 1: 비효율적 */
    }
}

/* 패턴 3: 전치 행렬 접근 최적화 예 (LDS를 통한 코얼레싱) */
#define TILE_DIM 32
#define PAD 1  /* LDS 뱅크 충돌 방지 패딩 */

__global__ void transpose_coalesced(float *out, const float *in, int width, int height)
{
    __shared__ float tile[TILE_DIM][TILE_DIM + PAD];  /* PAD로 뱅크 충돌 방지 */

    int xin  = blockIdx.x * TILE_DIM + threadIdx.x;
    int yin  = blockIdx.y * TILE_DIM + threadIdx.y;
    int xout = blockIdx.y * TILE_DIM + threadIdx.x;
    int yout = blockIdx.x * TILE_DIM + threadIdx.y;

    /* 입력: 행 방향 코얼레싱 (연속 읽기) */
    if (xin < width && yin < height)
        tile[threadIdx.y][threadIdx.x] = in[yin * width + xin];

    __syncthreads();

    /* 출력: LDS에서 열 방향으로 읽고 행 방향으로 쓰기 (코얼레싱 유지) */
    if (xout < height && yout < width)
        out[yout * height + xout] = tile[threadIdx.x][threadIdx.y];
}

/* rocprof로 코얼레싱 효율 측정 */
/* input.txt 카운터 파일: */
/*   pmc: SQ_INSTS_VMEM_WR SQ_INSTS_VMEM_RD               */
/*        TCP_TOTAL_CACHE_ACCESSES_sum TCP_TOTAL_READ_sum  */

# bash 명령으로 수집:
# rocprof --pmc -i coalescing_counters.txt ./my_kernel

/* 코얼레싱 비율 계산: */
/* coalesce_ratio = (이론적 트랜잭션 수) / (실제 트랜잭션 수) */
/* SQ_INSTS_VMEM_RD: 발행된 벡터 메모리 읽기 명령 수            */
/* TCP_TOTAL_CACHE_ACCESSES: L1 캐시 라인 접근 수               */
/* 비율이 낮을수록(1에 가까울수록) 코얼레싱이 잘 됨              */
메모리 접근 패턴별 효율 비교 (wavefront 64 스레드 기준)
접근 패턴스트라이드VRAM 트랜잭션대역폭 효율권장 해결책
완전 코얼레싱1 (연속)4 (256 bytes)100%
2-스트라이드28 (512 bytes 범위)50%SoA 레이아웃 변환
4-스트라이드41625%타일링 + 공유 메모리
행렬 전치 (비최적)M (행 크기)64 (각 1 cache line)~1/MLDS 타일 전치
랜덤 접근임의최대 64<10%scatter/gather 최소화, 간접 접근 제거
LDS 경유 재배치4 (VRAM 측)100% (VRAM 측)비연속 접근을 LDS로 흡수 후 연속 쓰기
AoS vs SoA 변환: 구조체 배열(Array of Structures) 대신 배열의 구조체(Structure of Arrays)를 사용하면 SIMD 코얼레싱을 자연스럽게 달성합니다. 예: struct Particle { float x, y, z, w; } 배열은 스레드 i가 p[i].x에 접근 시 스트라이드 4가 발생합니다. 반면 float *xs, *ys, *zs, *ws로 분리하면 xs[i] 접근이 연속적이어서 완전 코얼레싱됩니다.
LDS 뱅크 충돌: LDS는 32개 뱅크로 나뉘며 같은 wavefront 내 2개 이상의 스레드가 같은 뱅크에 접근하면 직렬화됩니다(뱅크 충돌). __shared__ float smem[32][32]에서 smem[threadIdx.y][threadIdx.x]로 접근하면 뱅크 충돌이 없지만, smem[threadIdx.x][threadIdx.y] 패턴은 열 방향 접근으로 전체 뱅크 충돌이 발생할 수 있습니다. 배열 크기에 패딩(float smem[32][33])을 추가하면 충돌을 방지할 수 있습니다.

커널 융합 (Kernel Fusion)

커널 융합은 여러 개의 GPU 커널 호출을 하나로 합치는 최적화 기법입니다. 각 커널 실행에는 런치 오버헤드(~수 μs)와 중간 결과를 VRAM에 쓰고 다시 읽는 메모리 대역폭 낭비가 발생합니다. 융합 커널은 중간 결과를 VRAM 대신 레지스터나 LDS에 유지하므로 메모리 트래픽을 크게 줄입니다. 특히 element-wise 연산 + reduction, activation + normalization 같은 패턴에서 효과적입니다.

/* 커널 융합 예: Element-wise 연산 + Reduction 합산 */
/* 비융합: (1) scale 커널 → VRAM → (2) reduce 커널 = 2× VRAM BW 소모 */
/* 융합: scale + reduce 동시 처리, 중간 결과 레지스터 유지 */

constexpr int BLOCK_SIZE = 256;

__global__ void fused_scale_reduce(
    const float *__restrict__ input,
    float       *__restrict__ output,
    float                    scale,
    int                      n)
{
    __shared__ float smem[BLOCK_SIZE];

    int tid = blockIdx.x * blockDim.x + threadIdx.x;
    int lid = threadIdx.x;

    /* 1단계 (융합): 로드 + 스케일 적용 → 중간 결과를 레지스터에 유지 */
    float val = 0.0f;
    for (int i = tid; i < n; i += gridDim.x * blockDim.x) {
        /* element-wise: ReLU + scale (비융합이면 별도 커널 필요) */
        float x = input[i] * scale;
        x = (x > 0.0f) ? x : 0.0f;  /* ReLU */
        val += x;                       /* 로컬 reduce: 레지스터 내 누산 */
    }

    /* 2단계: 블록 내 shared memory reduce */
    smem[lid] = val;
    __syncthreads();

    for (int s = BLOCK_SIZE / 2; s > 0; s >>= 1) {
        if (lid < s) smem[lid] += smem[lid + s];
        __syncthreads();
    }

    /* 3단계: 블록 결과를 원자적으로 글로벌 accumulator에 추가 */
    if (lid == 0) atomicAdd(output, smem[0]);
}

/* Flash Attention 스타일의 Online Softmax + Attention 융합 */
/* 비융합: QK^T → softmax → @ V = 3개 커널, O(N²) VRAM 트래픽  */
/* 융합:   타일 단위 온라인 softmax로 O(N) VRAM 트래픽            */
__global__ void online_softmax_attn(
    const float *__restrict__ Q,  /* [seq, dim] */
    const float *__restrict__ K,
    const float *__restrict__ V,
    float       *__restrict__ O,
    int seq_len, int dim)
{
    int row = blockIdx.x;  /* 이 블록이 담당하는 쿼리 행 */

    float m_i = -INFINITY;  /* 온라인 max (수치 안정성) */
    float l_i = 0.0f;       /* 누적 exp 합 */
    float acc[64] = {};    /* 출력 accumulator (레지스터 내 유지) */

    /* 키/값 타일을 순회하며 온라인으로 attention 계산 */
    for (int j = 0; j < seq_len; j++) {
        float score = 0.0f;
        /* Q[row] · K[j] 내적 계산 */
        for (int d = threadIdx.x; d < dim; d += blockDim.x)
            score += Q[row * dim + d] * K[j * dim + d];
        /* reduce score (warp shuffle) ... */

        float m_new = fmaxf(m_i, score);
        float alpha = expf(m_i - m_new);
        /* 기존 acc 재조정 + 현재 V 타일 가중치 누산 */
        l_i = l_i * alpha + expf(score - m_new);
        m_i = m_new;
        /* acc 업데이트 (V[j] * exp(score - m_new)) ... */
    }
    /* 최종 정규화: acc / l_i → O[row] 저장 */
}
커널 융합의 트레이드오프: 융합 커널은 VRAM 대역폭을 절약하지만 코드 복잡성이 높아집니다. ROCm에서는 torch.compile(PyTorch 2.0+) 또는 hipBLASLt 커스텀 epilog를 통해 자동 융합을 활용하는 것을 우선 검토하세요. 직접 융합 커널을 작성할 때는 VGPR 사용량이 급격히 늘어 occupancy가 낮아질 수 있으므로 --launch_bounds나 레지스터 스필을 모니터링해야 합니다.
HIP Graph와 커널 융합의 차이: HIP Graph는 여러 커널의 시작/의존성 관리 오버헤드를 줄이지만 커널 자체는 별도로 실행됩니다. 커널 융합은 하나의 커널 내에서 여러 연산을 수행하므로 중간 VRAM 트래픽까지 제거합니다. 두 기법은 상호 보완적으로 사용할 수 있습니다. 예: hipGraph로 여러 융합 커널의 실행 순서를 관리하면 런치 오버헤드도 추가로 감소합니다.

커널 설정과 빌드

ROCm이 제대로 동작하려면 Linux 커널이 amdgpu, KFD, IOMMU 등 필수 기능과 함께 빌드되어야 합니다. 대부분의 배포판 커널에는 이미 amdgpu가 모듈로 포함되어 있지만, 서버 환경이나 커스텀 커널을 빌드하는 경우 아래 설정을 확인해야 합니다. DKMS를 사용하면 커널 업그레이드 시 amdgpu 모듈을 자동으로 재빌드할 수 있습니다.

# 필수 커널 Kconfig 옵션 (menuconfig 또는 .config 파일)

# DRM/GPU 기본
CONFIG_DRM=m
CONFIG_DRM_AMDGPU=m
CONFIG_DRM_AMDGPU_SI=y          # GFX6/GFX7 Southern Islands 지원
CONFIG_DRM_AMDGPU_CIK=y         # GFX7/8 Sea Islands 지원

# KFD (ROCm 컴퓨트 인터페이스)
CONFIG_HSA_AMD=m                 # KFD 모듈 (필수)
CONFIG_HSA_AMD_SVM=y             # SVM(Shared Virtual Memory) 지원
CONFIG_HSA_AMD_P2P=y             # GPU P2P 접근 지원

# IOMMU (GPU 메모리 격리)
CONFIG_AMD_IOMMU=y
CONFIG_AMD_IOMMU_V2=y            # IOMMUv2 (PASID/PRI 지원)
CONFIG_IOMMU_SVA=y               # SVA (PASID 기반 공유 가상 주소)

# 메모리 관리
CONFIG_HMM_MIRROR=y              # HMM (Heterogeneous Memory Management)
CONFIG_MEMORY_HOTPLUG=y          # CXL/HBM 메모리 핫플러그
CONFIG_DEVICE_PRIVATE=y          # 디바이스 전용 메모리 (SVM)

# 네트워킹 (ROCm RDMA)
CONFIG_INFINIBAND=m
CONFIG_INFINIBAND_USER_ACCESS=m  # uverbs (GPUDirect RDMA)
# DKMS를 통한 amdgpu 모듈 관리
# ROCm 설치 후 DKMS로 모듈 상태 확인
dkms status

# 수동으로 amdgpu-dkms 설치 (Ubuntu)
apt install amdgpu-dkms linux-headers-$(uname -r)
# amdgpu-dkms는 ROCm 리포지터리에서 제공

# DKMS 빌드 강제 실행
dkms build amdgpu/6.7.8 -k $(uname -r)
dkms install amdgpu/6.7.8 -k $(uname -r)

# 모듈 로드 확인
modprobe amdgpu
lsmod | grep amdgpu
# udev 규칙: /dev/kfd와 /dev/dri/renderD* 권한 설정
# /etc/udev/rules.d/70-amdgpu.rules

KERNEL=="kfd", GROUP="render", MODE="0660"
SUBSYSTEM=="drm", KERNEL=="renderD*", GROUP="render", MODE="0660"
SUBSYSTEM=="drm", KERNEL=="card*", GROUP="video", MODE="0660"

# udev 규칙 즉시 적용
udevadm control --reload-rules
udevadm trigger --type=subsystems --action=change
udevadm trigger --type=devices --subsystem-match=drm
# GRUB 부팅 파라미터 설정 (/etc/default/grub)

# IOMMU 활성화 (AMD IOMMU)
GRUB_CMDLINE_LINUX="amd_iommu=on iommu=pt"
# iommu=pt: passthrough 모드 (KVM VM에 GPU passthrough 시)

# amdgpu 특정 파라미터
GRUB_CMDLINE_LINUX="amdgpu.ppfeaturemask=0xffffffff"
# 전력 관리 모든 기능 활성화

# 디스플레이 없는 서버 GPU (CDNA)
GRUB_CMDLINE_LINUX="amdgpu.dc=0 nomodeset"
# KMS/디스플레이 코드 비활성화

# 적용
update-grub
reboot
ROCm을 위한 필수 커널 설정
Kconfig 옵션기능필요 여부
CONFIG_DRM_AMDGPUamdgpu DRM 드라이버필수
CONFIG_HSA_AMDKFD 컴퓨트 드라이버 (/dev/kfd)필수
CONFIG_HSA_AMD_SVMSVM (hipMallocManaged)권장
CONFIG_AMD_IOMMUAMD IOMMU (메모리 격리)필수 (다중 프로세스)
CONFIG_AMD_IOMMU_V2PASID 기반 격리권장
CONFIG_HMM_MIRRORCPU-GPU 페이지 테이블 미러링SVM 사용 시 필수
CONFIG_DEVICE_PRIVATEGPU 전용 메모리 페이지SVM 사용 시 필수
CONFIG_INFINIBAND_USER_ACCESSGPUDirect RDMARDMA 사용 시
배포판별 ROCm 설치: Ubuntu/Debian: AMD 공식 리포지터리 amdgpu.repos.amd.com에서 amdgpu-install 스크립트 사용. RHEL/CentOS: dnf install rocm-hip-sdk. Arch Linux: AUR의 rocm-hip-sdk 패키지. 설치 스크립트는 커널 드라이버(amdgpu-dkms), ROCm 런타임, 라이브러리를 한 번에 설치합니다.
amdgpu vs amdgpu-pro: amdgpu는 오픈소스 mainline 드라이버, amdgpu-pro는 AMD가 배포하는 클로즈드 소스 OpenGL/Vulkan 구성요소를 포함한 하이브리드 드라이버입니다. ROCm 컴퓨트 용도에서는 오픈소스 amdgpu로 충분하며, 배포판 커널에 포함된 것을 사용하는 것이 권장됩니다.
커널 버전 호환성: ROCm 6.x는 Linux 5.15+ 커널을 지원하며, Linux 6.1+ 권장입니다. 너무 오래된 커널(5.4 미만)에서는 KFD 기능이 제한됩니다. 특히 SVM, HSA_AMD_P2P, XNACK 지원은 최신 커널에서 더 안정적입니다. rocminfo 명령으로 현재 시스템의 ROCm 지원 상태를 확인하세요.

ROCm 완전 설치 가이드

ROCm을 처음 설치할 때는 운영체제별로 AMD 공식 리포지터리를 설정하고 커널 드라이버(amdgpu-dkms), ROCm 런타임, 라이브러리를 순서대로 설치해야 합니다. 설치 후에는 사용자 그룹 설정, 환경 변수 설정, 기능 검증까지 완료해야 HIP 프로그램과 AI 프레임워크가 정상적으로 GPU를 인식합니다.

##############################################################
# Ubuntu 22.04 LTS (Jammy) - ROCm 6.x 완전 설치 절차
##############################################################

# 1. 사전 조건: 커널 헤더 및 빌드 도구 설치
sudo apt update
sudo apt install -y linux-headers-$(uname -r) build-essential

# 2. AMD GPU 드라이버 설치 스크립트 다운로드
wget https://repo.radeon.com/amdgpu-install/6.3/ubuntu/jammy/amdgpu-install_6.3.60300-1_all.deb
sudo dpkg -i amdgpu-install_6.3.60300-1_all.deb
sudo apt update

# 3. ROCm 전체 스택 설치 (드라이버 + 런타임 + 라이브러리)
#    --usecase=rocm: 컴퓨트 전용 (그래픽 불필요 시)
#    --usecase=graphics: 그래픽 + 컴퓨트
#    --usecase=rocm,graphics,opencl: 다목적
sudo amdgpu-install --usecase=rocm --no-dkms-upgrade
# 또는 선택적 설치:
# sudo amdgpu-install --usecase=hiplibsdk  # HIP SDK만
# sudo amdgpu-install --usecase=mlsdk      # ML 프레임워크 포함

# 4. 사용자 그룹 추가 (필수! 로그아웃 후 재로그인 필요)
sudo usermod -aG render,video $USER
# 변경 즉시 적용 (재로그인 없이):
newgrp render

# 5. 환경 변수 설정 (~/.bashrc 또는 /etc/environment에 추가)
echo 'export PATH=$PATH:/opt/rocm/bin:/opt/rocm/hip/bin' >> ~/.bashrc
echo 'export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/opt/rocm/lib' >> ~/.bashrc
echo 'export ROCM_PATH=/opt/rocm' >> ~/.bashrc
source ~/.bashrc

# 6. DKMS 상태 확인 (커널 모듈 빌드 완료 여부)
dkms status
# 예상 출력: amdgpu/6.7.0, 6.5.0-45-generic/x86_64: installed

# 7. 설치 검증
rocminfo           # GPU 목록, 아키텍처, 메모리 용량 확인
rocm-smi           # 실시간 GPU 상태 (클럭, 온도, 메모리)
hipcc --version    # HIP 컴파일러 버전 확인
clinfo             # OpenCL 장치 목록 (rocm-opencl 설치 시)
##############################################################
# RHEL 9 / Rocky Linux 9 / AlmaLinux 9 - ROCm 6.x 설치
##############################################################

# 1. 사전 조건
sudo dnf install -y kernel-headers kernel-devel gcc make
sudo dnf install -y epel-release
sudo crb enable  # CodeReady Builder (RHEL9) 또는 PowerTools (Rocky8)

# 2. AMD ROCm 리포지터리 추가
sudo tee /etc/yum.repos.d/rocm.repo << 'EOF'
[ROCm-6.3]
name=ROCm 6.3
baseurl=https://repo.radeon.com/rocm/rhel9/6.3/main
enabled=1
gpgcheck=1
gpgkey=https://repo.radeon.com/rocm/rocm.gpg.key
EOF

# amdgpu 드라이버 리포지터리
sudo tee /etc/yum.repos.d/amdgpu.repo << 'EOF'
[amdgpu]
name=amdgpu
baseurl=https://repo.radeon.com/amdgpu/6.3/rhel/9.4/main/x86_64/
enabled=1
gpgcheck=1
gpgkey=https://repo.radeon.com/rocm/rocm.gpg.key
EOF

# 3. 드라이버 + ROCm 설치
sudo dnf install -y amdgpu-dkms
sudo dnf install -y rocm-hip-sdk rocm-opencl-sdk

# 4. 사용자 그룹 추가
sudo usermod -aG render,video $USER

# 5. SELinux 정책 (필요 시)
# ROCm이 SELinux enforcing 모드에서 동작하려면 avcstat로 거부 확인:
# sudo ausearch -m avc -ts recent | audit2allow -M rocm_policy
# sudo semodule -i rocm_policy.pp

# 6. 재부팅 후 검증
sudo reboot
rocminfo
rocm-smi --showproductname
##############################################################
# 설치 후 검증 체크리스트
##############################################################

# [1] GPU 인식 여부
rocminfo | grep -A3 "Name:"
# 예: Name: gfx942 (MI300X), gfx1100 (RX 7900 XTX)

# [2] /dev/kfd 존재 및 권한
ls -la /dev/kfd /dev/dri/render*
# crw-rw---- 1 root render 0 Mar 11 /dev/kfd
# crw-rw---- 1 root render /dev/dri/renderD128

# [3] 현재 사용자 그룹 확인
groups | grep -E "render|video"

# [4] HIP Hello World 컴파일 및 실행
cat > /tmp/hip_test.cpp << 'EOF'
#include <hip/hip_runtime.h>
#include <cstdio>
__global__ void hello() { printf("GPU thread %d
", threadIdx.x); }
int main() {
  hello<<<1, 4>>>();
  hipDeviceSynchronize();
}
EOF
hipcc -o /tmp/hip_test /tmp/hip_test.cpp
/tmp/hip_test  # 예상 출력: GPU thread 0~3

# [5] rocBLAS 동작 확인
rocblas-bench -f gemm --transpA N --transpB N -m 4096 -n 4096 -k 4096   --a_type f16_r --b_type f16_r --c_type f32_r --d_type f32_r   --compute_type f32_r
# 성능 수치(TFLOPS) 출력되면 정상

# [6] PyTorch ROCm 동작 확인
python3 -c "
import torch
print('PyTorch 버전:', torch.__version__)
print('CUDA(ROCm) 사용 가능:', torch.cuda.is_available())
print('GPU 장치 이름:', torch.cuda.get_device_name(0))
x = torch.randn(1000, 1000, device='cuda')
y = torch.mm(x, x)
print('행렬 곱 성공:', y.shape)
"

# [7] 메모리 대역폭 벤치마크
rocm-bandwidth-test  # H2D, D2H, D2D 대역폭 측정

# [8] GPU 상태 종합 확인
rocm-smi --showallinfo
ROCm 설치 문제 해결 가이드
증상원인해결책
rocminfo 실행 시 "No GPU agents found"/dev/kfd 없음 또는 amdgpu 모듈 미로드lsmod | grep amdgpu, IOMMU 활성화 확인, modprobe amdgpu
hipcc 컴파일 오류: "cannot find -lamdhip64"LD_LIBRARY_PATH에 /opt/rocm/lib 미포함export LD_LIBRARY_PATH=/opt/rocm/lib:$LD_LIBRARY_PATH
GPU 인식되나 HIP 커널 실행 시 SIGSEGVrender/video 그룹 미포함sudo usermod -aG render,video $USER 후 재로그인
dmesg에 "amdgpu: [KFD] No supported devices found"GPU가 ROCm 지원 목록에 없음rocminfo로 GFX 버전 확인, ROCm 지원 GPU 목록 검토
PyTorch torch.cuda.is_available() == FalseCPU 빌드 PyTorch 설치됨pip install torch --index-url https://download.pytorch.org/whl/rocm6.2
커널 업그레이드 후 amdgpu 모듈 사라짐DKMS 재빌드 실패sudo dkms autoinstall, linux-headers-$(uname -r) 설치 확인
컨테이너 내 GPU 인식 불가/dev/kfd, /dev/dri 미마운트docker run --device /dev/kfd --device /dev/dri --group-add render
RDNA2/3 카드에서 compute queue 오류amdgpu.mes=1 FW 미설치linux-firmware 업데이트 또는 amdgpu.mes=0 부트 파라미터 추가
Docker ROCm 이미지 활용: AMD 공식 Docker 이미지를 사용하면 설치 복잡성을 크게 줄일 수 있습니다. docker pull rocm/pytorch:latest 또는 rocm/tensorflow:latest를 사용하고 컨테이너 실행 시 --device /dev/kfd --device /dev/dri --group-add render --group-add video 옵션을 추가하세요. ROCm 컨테이너 툴킷(rocm-docker)을 사용하면 이 옵션들이 자동으로 처리됩니다.
멀티 GPU 설치 확인: 서버에 GPU가 여러 개 있는 경우 rocm-smi --showserial로 각 GPU의 시리얼 번호를 확인하고, rocminfo | grep "Agent "이라는 접두사로 각 에이전트(GPU)의 수를 확인합니다. XGMI 연결 여부는 rocm-smi --showtopology로 확인할 수 있습니다. MI300X 시스템에서 8개 GPU가 모두 인식되지 않으면 BIOS의 PCIe/ARI 설정을 확인하세요.
배포판 커널 amdgpu와 amdgpu-dkms 충돌: Ubuntu 22.04+는 이미 mainline amdgpu를 포함합니다. amdgpu-dkms를 추가 설치하면 두 버전이 충돌할 수 있습니다. sudo apt remove amdgpu-dkms 후 배포판 기본 커널의 amdgpu를 사용하거나, 반대로 배포판 amdgpu 모듈을 블랙리스트에 추가하고 DKMS 버전만 사용해야 합니다. dpkg -l | grep amdgpu로 설치된 패키지를 확인하세요.

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