GPU 컴퓨팅 (GPGPU)

Linux GPU 컴퓨팅(GPGPU) 프레임워크와 런타임을 심층 분석합니다. GPU 컴퓨트 개요, CUDA/NVIDIA 아키텍처, OpenCL 크로스 플랫폼 컴퓨트, Vulkan Compute 파이프라인, ROCm/HIP AMD GPU 컴퓨트, Intel oneAPI/Level Zero 플랫폼의 구조와 Linux 커널 드라이버 연동을 다룹니다.

전제 조건: GPU 서브시스템 개요 페이지에서 DRM/KMS 전체 구조를 먼저 파악하세요. GPU 메모리 관리와 스케줄링은 GPU 메모리 관리 및 스케줄러 페이지를 참고하세요.

GPU 컴퓨트 (GPGPU)

GPU는 렌더링 외에도 대규모 병렬 연산(GPGPU)에 사용됩니다. Linux 커널은 render node(/dev/dri/renderD128)를 통해 비특권 GPU 컴퓨트 접근을 제공하며, AMD는 KFD(Kernel Fusion Driver)로 HSA 컴퓨트를 추가 지원합니다.

프레임워크커널 인터페이스유저 공간GPU
OpenCL (Mesa) DRM render node Mesa Clover/Rusticl AMD, Intel, 일부 ARM
ROCm (AMD) KFD (/dev/kfd) ROCm runtime, HIP AMD GCN/RDNA/CDNA
oneAPI (Intel) DRM render node (xe/i915) Level Zero, SYCL Intel Xe/Arc
Vulkan Compute DRM render node Vulkan compute shader 모든 Vulkan 지원 GPU
CUDA (NVIDIA) nvidia.ko (독점) CUDA runtime NVIDIA (nouveau 미지원)

KFD (Kernel Fusion Driver) — AMD HSA

KFD (AMD HSA) 아키텍처 ROCm Runtime / HIP (User Space) `libhsakmt`, HSA Runtime → `/dev/kfd` ioctl 호출 KFD Kernel (`drivers/gpu/drm/amd/amdkfd/`) 프로세스별 compute queue(AQL) 관리 SVM(Shared Virtual Memory) 범위/속성 관리 GPU 이벤트/시그널 동기화 및 스케줄링 /dev/kfd는 compute 전용 인터페이스 amdgpu DRM Driver 하드웨어 큐 제출, 메모리 매핑, 인터럽트 처리 주요 KFD ioctl `KFD_IOC_CREATE_QUEUE` / `KFD_IOC_DESTROY_QUEUE` `KFD_IOC_ALLOC_MEMORY_OF_GPU` / `KFD_IOC_MAP_MEMORY_TO_GPU` `KFD_IOC_CREATE_EVENT` / `KFD_IOC_WAIT_EVENTS` `KFD_IOC_SVM` (SVM 등록/속성 설정) ROCm 런타임이 위 ioctl 조합으로 compute 워크플로우 구성

SVM (Shared Virtual Memory)

SVM은 CPU와 GPU가 동일한 가상 주소 공간을 공유하는 기술입니다. malloc()으로 할당한 메모리를 GPU가 동일한 포인터로 접근할 수 있습니다.

CPU Process VA Space 0x7fff_xxxx Stack 0x0040_0000 Code 0x1000_0000 malloc buf CPU가 이 주소로 접근 동일한 가상 주소 GPU VA Space 0x1000_0000 malloc buf GPU도 이 주소로 접근 구현: GPU 페이지 테이블 ↔ CPU 페이지 테이블 동기화 On-demand 페이징 (GPU 페이지 폴트) HMM 프레임워크 활용 MMU notifier로 CPU 페이지 테이블 변경 추적
/* 커널 SVM 지원 (amdgpu/KFD) */
#include <linux/hmm.h>

/* GPU 페이지 폴트 핸들러 */
/* 1. GPU가 매핑되지 않은 주소 접근 → 인터럽트 */
/* 2. 커널이 CPU 페이지 테이블에서 물리 주소 조회 */
/*    (페이지 없으면 CPU 페이지 폴트도 처리) */
/* 3. GPU 페이지 테이블에 매핑 추가 */
/* 4. GPU 작업 재개 */
Render Node 보안: /dev/dri/renderD128은 DRM Master 없이 접근 가능하므로, 일반 사용자도 GPU 컴퓨트를 사용할 수 있습니다. 그러나 GPU 가상 메모리 격리가 제대로 구현되어야 다른 프로세스의 GPU 데이터가 유출되지 않습니다. per-process GPU page tablecommand validation이 보안의 핵심입니다.
# GPU 컴퓨트 관련 확인 명령

# Render node 확인
ls -la /dev/dri/renderD*

# KFD 디바이스 확인 (AMD)
ls -la /dev/kfd

# GPU 토폴로지 (AMD ROCm)
cat /sys/class/kfd/kfd/topology/nodes/0/properties

# GPU 메모리 사용량 (amdgpu)
cat /sys/class/drm/card0/device/mem_info_vram_used
cat /sys/class/drm/card0/device/mem_info_gtt_used

# clinfo (OpenCL 디바이스 정보)
clinfo

# vulkaninfo (Vulkan 컴퓨트 능력)
vulkaninfo --summary

DRM Accel 서브시스템 (AI/NPU 가속기)

최근 커널은 AI/ML/NPU 같은 비그래픽 가속기를 위해 DRM Accel 서브시스템을 제공합니다. 핵심 아이디어는 GPU DRM이 이미 갖고 있는 파일 디스크립터별 세션 상태, 버퍼 객체, 동기화, ioctl 디스패치를 재사용하되, 디스플레이와 렌더 노드 개념은 제거하고 /dev/accel/accel0 같은 전용 노드에 compute UAPI만 싣는 것입니다.

항목DRM GPUDRM Accel
디바이스 노드 /dev/dri/card0, /dev/dri/renderD128 /dev/accel/accel0
용도 그래픽 렌더링 + 디스플레이 + GPGPU AI 추론/훈련, 신호 처리 등 비그래픽 가속
KMS 지원 (디스플레이 파이프라인) 미지원 (컴퓨트 전용)
GEM/DMA-BUF 지원 지원 (GPU와 버퍼 공유 가능)
권한 모델 primary는 특권, render는 비특권 display 관련 권한 없이 compute job과 buffer mapping만 노출
드라이버 예시 amdgpu, i915, xe, panfrost amdxdna, qaic, rocket 같은 전용 accel 드라이버 계열
활성화 플래그 DRIVER_RENDER DRIVER_COMPUTE_ACCEL
/* DRM Accel 드라이버 등록 (최소 골격) */
static const struct drm_driver my_accel_driver = {
    .driver_features = DRIVER_GEM | DRIVER_COMPUTE_ACCEL,
    .fops            = &my_accel_fops,
    .ioctls          = my_accel_ioctls,
    .num_ioctls      = ARRAY_SIZE(my_accel_ioctls),
    .name  = "my-npu",
    .desc  = "My NPU Accelerator",
    .date  = "20260101",
    .major = 1, .minor = 0,
};
/* drm_dev_register() 시 /dev/accel/accel0 자동 생성 */
/* (DRIVER_COMPUTE_ACCEL 플래그에 의해 accel 네임스페이스 사용) */
DRIVER_COMPUTE_ACCEL 제약: 최신 drm_drv.h 기준으로 이 플래그는 DRIVER_RENDER, DRIVER_MODESET와 상호 배타적입니다. 즉, 하나의 디바이스가 그래픽과 compute를 모두 지원하더라도 UAPI 계약상 “한 드라이버가 render node와 accel node를 동시에 제공”하는 방식은 권장되지 않으며, 메인라인 문서는 보조 버스(auxiliary bus)로 연결된 두 드라이버로 분리하는 설계를 권합니다.
참고: NPU/AI 가속기의 DRM Accel 드라이버 구조, 메모리 관리, 커맨드 서브미션 모델은 NPU (Neural Processing Unit) 페이지에서 자세히 다룹니다.

CUDA / NVIDIA — GPU 컴퓨트

CUDA(Compute Unified Device Architecture)는 NVIDIA가 2006년에 도입한 GPU 범용 컴퓨팅 플랫폼으로, GPU의 수천 개 코어를 C/C++ 확장 문법으로 프로그래밍할 수 있게 합니다. Linux에서 CUDA는 독점 커널 모듈(nvidia.ko)과 사용자 공간 런타임(libcuda.so, libcudart.so)으로 구성되며, 딥러닝(cuDNN, TensorRT), 과학 계산(cuBLAS, cuFFT), 고성능 컴퓨팅(NCCL, GPUDirect RDMA) 생태계가 핵심 경쟁력입니다.

CUDA 프로그래밍 모델의 핵심은 이종 컴퓨팅(Heterogeneous Computing)입니다. CPU(호스트)가 프로그램 흐름을 제어하고 GPU(디바이스)에 병렬 작업을 위임하는 구조로, 호스트 코드는 표준 C/C++ 컴파일러(gcc/clang)가, 디바이스 코드는 NVIDIA의 nvcc가 처리합니다. CUDA Runtime API(libcudart.so)는 고수준 추상화를, Driver API(libcuda.so)는 컨텍스트·모듈·함수 수준의 세밀한 제어를 제공합니다. 대부분의 애플리케이션은 Runtime API를 사용하며, 멀티 GPU·JIT 컴파일 등 고급 시나리오에서 Driver API를 활용합니다.

메인라인 vs 독점: NVIDIA의 독점 커널 모듈은 Linux 메인라인에 포함되지 않습니다. 메인라인에는 nouveau 오픈소스 드라이버(DRM/KMS)가 있으나 CUDA 지원이 제한적입니다. 2022년부터 NVIDIA는 오픈 GPU 커널 모듈을 별도 공개하고 있으며, 이 역시 메인라인에 포함되지는 않지만 GPL-2.0 / MIT 듀얼 라이선스로 소스가 공개되어 있습니다. 한편, NVIDIA는 2024년부터 오픈 커널 모듈을 기본 권장으로 전환하기 시작했으며, Turing(GTX 16xx) 이후 GPU에서는 독점 모듈 대신 오픈 모듈을 설치하는 것이 공식 권장 사항입니다.

NVIDIA GPU 아키텍처 진화

NVIDIA GPU 아키텍처는 2006년 Tesla(CUDA 최초 지원)부터 시작하여 세대마다 SM(Streaming Multiprocessor) 구조, 메모리 계층, 인터커넥트, 전용 하드웨어 유닛을 혁신해 왔습니다. 각 세대의 Compute Capability는 지원하는 CUDA 기능 집합을 결정하며, nvcc-arch=sm_XX 옵션으로 대상 아키텍처를 지정합니다.

NVIDIA GPU 아키텍처 진화 (2006–2025) Tesla (2006) — sm_10 CUDA 첫 도입, 공유메모리 16KB G80: 128 CUDA 코어, GDDR3 Fermi (2010) — sm_20 L1/L2 캐시 도입, ECC, 64-bit 주소 GF100: 512 코어, 6GB GDDR5 Kepler (2012) — sm_30 Dynamic Parallelism, Hyper-Q, GPUDirect GK110: 2880 코어, 최초 HPC 대규모 채택 Maxwell (2014) — sm_50 에너지 효율 2× 향상, SM 재설계 GM200: 3072 코어, 공유메모리 96KB Pascal (2016) — sm_60 NVLink 1.0, HBM2, Unified Memory, FP16 GP100: 3840 코어, 딥러닝 급부상 Volta (2017) — sm_70 ★ Tensor Core 최초 도입 (1세대) V100: 5120 코어, 독립 스레드 스케줄링 Turing (2018) — sm_75 RT Core (레이트레이싱), Tensor Core 2세대 오픈 커널 모듈 지원 시작점, INT8/INT4 Ampere (2020) — sm_80 TF32, 구조적 희소성 2:4, MIG, 3세대 NVLink A100: 6912 코어, 80GB HBM2e, 최초 MIG Hopper (2022) — sm_90 ★ FP8, DPX, TMA, Thread Block Cluster H100: 16896 코어, 80GB HBM3, NVLink 4.0 Blackwell (2024) — sm_100 ★ FP4, 2세대 TMA, 5세대 NVLink B200: 208B 트랜지스터, 192GB HBM3e
NVIDIA GPU 아키텍처 세대별 핵심 스펙 비교
아키텍처CC대표 GPUCUDA 코어Tensor Core메모리NVLink핵심 혁신
Tesla1.0G80128768MB GDDR3CUDA 최초 도입
Fermi2.0GF1005126GB GDDR5L1/L2 캐시, ECC
Kepler3.5GK110288012GB GDDR5Dynamic Parallelism
Maxwell5.2GM200307212GB GDDR5에너지 효율 2×
Pascal6.0GP100384016GB HBM21.0 (160GB/s)NVLink, FP16
Volta7.0V1005120640 (1세대)32GB HBM22.0 (300GB/s)Tensor Core 도입
Turing7.5T42560320 (2세대)16GB GDDR6RT Core, INT8/INT4
Ampere8.0A1006912432 (3세대)80GB HBM2e3.0 (600GB/s)TF32, MIG, 희소성 2:4
Hopper9.0H10016896528 (4세대)80GB HBM34.0 (900GB/s)FP8, TMA, DPX
Blackwell10.0B20018432576 (5세대)192GB HBM3e5.0 (1800GB/s)FP4, 2세대 TMA
Compute Capability(CC)와 -arch 관계: nvcc -arch=sm_80은 Ampere(CC 8.0) 대상으로 컴파일합니다. CC가 높을수록 더 많은 명령어(FP8, TMA 등)와 하드웨어 기능을 사용할 수 있습니다. 현재 GPU의 CC는 nvidia-smi --query-gpu=compute_cap --format=csv,noheader로 확인합니다. 하위 호환성: sm_80 바이너리는 sm_90 GPU에서 실행 가능하지만, sm_90 전용 기능(FP8 등)은 사용하지 못합니다. 반대로 sm_90 바이너리는 sm_80에서 실행 불가합니다.
Tensor Core 세대별 지원 정밀도: 1세대(Volta): FP16, 2세대(Turing): FP16 + INT8/INT4, 3세대(Ampere): FP16 + BF16 + TF32 + INT8 + 구조적 희소성 2:4, 4세대(Hopper): FP16 + BF16 + TF32 + FP8(E4M3/E5M2) + INT8, 5세대(Blackwell): FP16 + BF16 + TF32 + FP8 + FP4 + INT8. 각 세대마다 TFLOPS가 약 2~3× 증가하며, cuBLAS와 cuDNN은 자동으로 최적 정밀도를 선택합니다.

NVIDIA Linux 드라이버 스택

NVIDIA GPU를 CUDA로 활용하려면 커널 모듈 + 사용자 라이브러리가 함께 설치되어야 합니다. 아래 다이어그램은 CUDA 애플리케이션에서 GPU 하드웨어까지의 전체 소프트웨어 스택을 보여줍니다.

NVIDIA CUDA Linux 드라이버 스택 사용자 공간 (User Space) CUDA 애플리케이션 (.cu) libcudart.so (Runtime API) libcuda.so (Driver API) cuBLAS · cuDNN · cuFFT · NCCL · TensorRT 커널 공간 (Kernel Space) nvidia.ko nvidia-uvm.ko nvidia-modeset.ko nvidia-drm.ko nvidia-peermem.ko ioctl /dev/nvidia* NVIDIA GPU 하드웨어 (PCIe / NVLink) MMIO · DMA · 인터럽트 (MSI-X)
NVIDIA Linux 디바이스 노드
디바이스 노드제공 모듈용도
/dev/nvidia0..Nnvidia.koGPU별 컨트롤 채널 (컴퓨트, 메모리 할당)
/dev/nvidiactlnvidia.ko전역 컨트롤 (디바이스 열거, 초기화)
/dev/nvidia-uvmnvidia-uvm.koUnified Virtual Memory 관리
/dev/nvidia-uvm-toolsnvidia-uvm.koUVM 프로파일링 / 디버깅
/dev/nvidia-modesetnvidia-modeset.ko디스플레이 모드 설정
/dev/dri/card*nvidia-drm.koDRM primary 노드 (Wayland/X11 연동)
/dev/dri/renderD*nvidia-drm.koDRM render 노드 (비특권 GPU 접근)
# NVIDIA 커널 모듈 확인
lsmod | grep nvidia
# nvidia               61440000  5 nvidia_uvm,nvidia_modeset
# nvidia_uvm            3280896  0
# nvidia_modeset        1282048  1 nvidia_drm
# nvidia_drm              94208  3
# nvidia_peermem         16384  0

# 디바이스 노드 확인
ls -la /dev/nvidia*
# crw-rw-rw- 1 root root 195,   0 ... /dev/nvidia0
# crw-rw-rw- 1 root root 195, 255 ... /dev/nvidiactl
# crw-rw-rw- 1 root root 511,   0 ... /dev/nvidia-uvm

CUDA는 두 가지 API 레벨을 제공합니다. Runtime API(libcudart.so)는 cudaMalloc(), cudaMemcpy() 등 간결한 함수로 대부분의 사용 사례를 커버합니다. Driver API(libcuda.so)는 cuCtxCreate(), cuModuleLoad() 등 더 세밀한 제어를 제공하며, PTX JIT 컴파일이나 멀티 컨텍스트 관리에 필수적입니다.

CUDA Runtime API vs Driver API 비교
항목Runtime API (cudart)Driver API (cuda)
헤더cuda_runtime.hcuda.h
라이브러리libcudart.solibcuda.so (드라이버와 함께 설치)
초기화암묵적 (첫 API 호출 시)명시적 (cuInit(0))
컨텍스트기본 컨텍스트 자동 생성수동 생성/파괴 (cuCtxCreate)
커널 실행<<<...>>> 구문cuLaunchKernel()
PTX JIT 로드불가cuModuleLoadDataEx()
디바이스 관리cudaSetDevice()cuDeviceGet() + cuCtxCreate()
혼용가능 — 동일 프로세스에서 두 API를 함께 사용할 수 있음
드라이버 버전 호환성: CUDA Toolkit 버전과 NVIDIA 드라이버 버전은 호환성 매트릭스를 따릅니다. 예를 들어 CUDA 12.4는 최소 드라이버 550.54 이상이 필요합니다. nvidia-smi 출력 상단의 "CUDA Version"은 해당 드라이버가 지원하는 최대 CUDA 버전이며, 실제 설치된 Toolkit 버전과 다를 수 있습니다. Toolkit 버전은 nvcc --version으로 확인합니다.

Linux CUDA 설치 및 환경 설정

Linux에서 CUDA 환경을 구성하는 방법은 크게 세 가지입니다: 배포판 패키지 매니저(apt/dnf), NVIDIA CUDA 저장소(cuda-keyring), runfile 직접 설치. 프로덕션 환경에서는 NVIDIA 공식 저장소를 통한 설치가 버전 관리와 업데이트 측면에서 권장됩니다.

# === 방법 1: NVIDIA 공식 저장소 (Ubuntu/Debian) ===
# 저장소 키링 패키지 설치
wget https://developer.download.nvidia.com/compute/cuda/repos/ubuntu2204/x86_64/cuda-keyring_1.1-1_all.deb
sudo dpkg -i cuda-keyring_1.1-1_all.deb
sudo apt-get update

# CUDA Toolkit + 드라이버 설치 (메타 패키지)
sudo apt-get install cuda-toolkit-12-4
sudo apt-get install nvidia-open  # 오픈 커널 모듈 (Turing+)
# 또는: sudo apt-get install cuda-drivers  (독점 모듈)

# === 방법 2: RHEL/Rocky/AlmaLinux ===
sudo dnf config-manager --add-repo \
  https://developer.download.nvidia.com/compute/cuda/repos/rhel9/x86_64/cuda-rhel9.repo
sudo dnf install cuda-toolkit-12-4 nvidia-open

# === 환경 변수 설정 (~/.bashrc) ===
export PATH=/usr/local/cuda-12.4/bin:$PATH
export LD_LIBRARY_PATH=/usr/local/cuda-12.4/lib64:$LD_LIBRARY_PATH

# === 설치 검증 ===
nvcc --version        # CUDA 컴파일러 버전
nvidia-smi            # 드라이버 및 GPU 상태
cuda-install-samples-12.4.sh ~/cuda-samples  # 샘플 코드 설치
cd ~/cuda-samples/Samples/1_Utilities/deviceQuery
make && ./deviceQuery  # GPU 정보 상세 출력
CUDA 설치 방법 비교
방법장점단점적합한 환경
NVIDIA 저장소 (cuda-keyring)자동 업데이트, 의존성 해결시스템 전역 설치프로덕션, CI/CD
runfile 직접 설치설치 경로 지정, 다중 버전 공존의존성 수동 관리개발, HPC 클러스터
Conda (conda-forge)가상환경 격리, 크로스 플랫폼드라이버는 별도 설치데이터 과학, ML
컨테이너 (nvidia/cuda)완전 격리, 재현성nvidia-container-toolkit 필요클라우드, K8s
다중 CUDA 버전 관리: /usr/local/cuda는 심볼릭 링크이며 update-alternatives --config cuda로 활성 버전을 전환할 수 있습니다. ls /usr/local/cuda-*/로 설치된 모든 버전을 확인하고, 프로젝트별로 PATHLD_LIBRARY_PATH를 조정하세요. CUDA_HOME 환경 변수를 설정하면 CMake의 FindCUDA 모듈이 자동으로 인식합니다.

CUDA 프로그래밍 모델

CUDA는 SIMT(Single Instruction Multiple Thread) 실행 모델을 사용합니다. 프로그래머는 __global__ 함수(커널)를 정의하고, 호스트에서 <<<gridDim, blockDim>>> 구문으로 수천~수백만 스레드(Thread)를 동시에 실행합니다.

CUDA 스레드 계층 (Thread Hierarchy) Grid (커널 1회 실행 단위) gridDim = (2, 2) → 4개 블록 Block (0,0) — blockDim = (8,8) → 64 스레드 __shared__ 메모리 공유, __syncthreads() 동기화 Warp 0 (32 스레드 — SIMT 실행 단위) T0 T1 T2 · · · T30 T31 Warp 1 (T32–T63) ⋮ (블록 내 워프 수 = ⌈blockDim / 32⌉) SM (Streaming Multiprocessor) CUDA 코어 128개 (Hopper 기준) 최대 동시 워프 64개 (2048 스레드) 레지스터 파일 256 KB, 공유메모리 228 KB Block (1,0) Warp 0 · · · Warp N 독립 SM에 매핑 (서로 다른 블록은 동기화 불가) 블록 간 통신 → Global Memory 또는 Cooperative Groups Block (0,1) Block (1,1) OpenCL 대응: Grid↔NDRange, Block↔Work-group, Thread↔Work-item, Warp↔Sub-group
CUDA ↔ OpenCL 용어 대응표
CUDAOpenCL설명
GridNDRange전체 문제 공간 (커널 1회 실행)
BlockWork-groupSM에 매핑, 공유 메모리/배리어 범위
ThreadWork-item개별 실행 단위
Warp (32)Sub-groupSIMT 동시 실행 단위, 하드웨어 결정
__shared____local블록/그룹 내 공유 메모리
__syncthreads()barrier()블록/그룹 내 동기화
threadIdx.xget_local_id(0)블록/그룹 내 인덱스
blockIdx.xget_group_id(0)블록/그룹 ID
/* 벡터 덧셈 — CUDA 커널 기본 예제 */
__global__ void vecAdd(const float *A, const float *B, float *C, int N) {
    int i = blockIdx.x * blockDim.x + threadIdx.x;
    if (i < N)
        C[i] = A[i] + B[i];
}

int main(void) {
    int N = 1 << 20;  /* 1M 원소 */
    float *d_A, *d_B, *d_C;
    cudaMalloc(&d_A, N * sizeof(float));
    cudaMalloc(&d_B, N * sizeof(float));
    cudaMalloc(&d_C, N * sizeof(float));

    /* 호스트→디바이스 전송 (생략) */

    int threadsPerBlock = 256;
    int blocksPerGrid = (N + threadsPerBlock - 1) / threadsPerBlock;
    vecAdd<<<blocksPerGrid, threadsPerBlock>>>(d_A, d_B, d_C, N);

    cudaDeviceSynchronize();
    /* 디바이스→호스트 전송 (생략) */
    cudaFree(d_A); cudaFree(d_B); cudaFree(d_C);
    return 0;
}

CUDA 커널 실행의 핵심 개념을 정리하면 다음과 같습니다:

CUDA 커널 실행 핵심 개념
개념설명제약 조건
__global__호스트에서 호출, 디바이스에서 실행되는 커널 함수반환형 void, 재귀 불가(CC < 3.5), 가변 인자 불가
__device__디바이스에서만 호출/실행되는 함수호스트에서 직접 호출 불가
__host__호스트에서만 호출/실행 (기본값)__host__ __device__ 조합으로 양쪽 컴파일 가능
blockDim블록당 스레드 수 (1D/2D/3D)최대 1024 스레드/블록 (SM 아키텍처별 상이)
gridDim그리드당 블록 수 (1D/2D/3D)최대 2³¹-1 × 65535 × 65535
__syncthreads()블록 내 모든 스레드 배리어조건 분기 내에서 호출 시 데드락 위험
Cooperative Groups워프/블록/그리드/멀티 GPU 수준 동기화CC 6.0+, 그리드 동기화는 cudaLaunchCooperativeKernel
Dynamic Parallelism커널 내에서 새 커널 실행CC 3.5+, 중첩 깊이 24, 동기화 오버헤드 있음
/* Cooperative Groups — 워프 수준 리덕션 (CC 7.0+) */
#include <cooperative_groups.h>
#include <cooperative_groups/reduce.h>
namespace cg = cooperative_groups;

__global__ void warpReduce(const float *input, float *output, int N) {
    cg::thread_block block = cg::this_thread_block();
    cg::thread_block_tile<32> warp = cg::tiled_partition<32>(block);

    int idx = blockIdx.x * blockDim.x + threadIdx.x;
    float val = (idx < N) ? input[idx] : 0.0f;

    /* 워프 내 셔플 리덕션 — 레지스터 수준, 공유메모리 불필요 */
    float sum = cg::reduce(warp, val, cg::plus<float>());

    if (warp.thread_rank() == 0)
        atomicAdd(output, sum);
}

워프 실행과 분기 다이버전스

GPU의 SIMT(Single Instruction, Multiple Thread) 실행 모델에서 32개 스레드로 구성된 워프(Warp)는 동일한 프로그램 카운터(PC)를 공유합니다. 워프 내 모든 스레드가 같은 분기 경로를 따르면 최대 효율이지만, 서로 다른 경로를 택하면 분기 다이버전스(Branch Divergence)가 발생하여 각 경로를 순차적으로 실행해야 합니다.

워프 분기 다이버전스 (Branch Divergence) 균일 실행 (Uniform): if (threadIdx.x > -1) 32개 스레드 모두 동일 경로 → 1 패스, 100% 효율 T0 T1 T2 T3 · · · T30 T31 — 모두 활성 다이버전트 실행: if (threadIdx.x < 16) 패스 1 — then 분기 실행 T0–T15 활성 T16–T31 비활성 (마스크됨, 유휴 상태) 50% SM 활용률 — 절반의 ALU가 유휴 패스 2 — else 분기 실행 T0–T15 비활성 T16–T31 활성 총 실행 시간 = then 시간 + else 시간 (직렬화) 재수렴 (Reconvergence) — 32 스레드 다시 동기화 Volta+ (CC 7.0): 독립 스레드 스케줄링 (Independent Thread Scheduling) 각 스레드가 고유 PC 보유 → __syncwarp()로 명시적 재수렴 필요

분기 다이버전스를 최소화하는 것은 CUDA 최적화의 기본입니다. 워프 내 스레드들이 서로 다른 경로를 따를 때 SM의 실행 유닛 활용률이 떨어지며, 최악의 경우(32개 스레드 모두 다른 경로) 성능이 1/32로 저하될 수 있습니다.

분기 다이버전스 회피 전략
전략설명예제
워프 정렬 분기조건을 워프 경계(32 배수)로 정렬if (threadIdx.x / 32 < threshold)
프레디케이션짧은 분기는 컴파일러가 predicated 명령으로 변환val = (cond) ? a : b; (2~3 명령어)
데이터 재배치(Relocation)분기 패턴이 같은 데이터를 워프 단위로 그룹CSR 행렬의 행 길이별 정렬
셔플 기반 리덕션조건 분기 대신 __shfl_down_sync()워프 리덕션, 프리픽스 합
선택 함수__any_sync(), __all_sync() 투표워프 전체가 특정 조건을 만족하는지 확인
/* 워프 셔플 리덕션 — 분기 없이 워프 합 계산 */
__device__ float warpReduceSum(float val) {
    for (int offset = warpSize / 2; offset > 0; offset /= 2)
        val += __shfl_down_sync(0xFFFFFFFF, val, offset);
    return val;  /* lane 0에 합 결과 */
}

/* 워프 투표 함수 활용 */
__global__ void earlyExit(const int *data, int *result) {
    int val = data[blockIdx.x * blockDim.x + threadIdx.x];
    /* 워프 전체가 0이면 조기 종료 — 분기 다이버전스 없음 */
    if (__all_sync(0xFFFFFFFF, val == 0))
        return;
    /* ... 실제 연산 ... */
}
Volta 이후 독립 스레드 스케줄링: CC 7.0부터 각 스레드가 독립적인 프로그램 카운터를 가집니다. 이는 다이버전트 코드의 스케줄링 유연성을 높이지만, 이전 아키텍처에서 암묵적으로 보장되던 워프 내 lock-step 실행을 가정하면 안 됩니다. __syncwarp(mask)로 명시적 재수렴을 보장하고, __shfl_sync(mask, ...)에서 항상 유효한 마스크를 전달하세요.

CUDA 스트림과 비동기 실행

CUDA 스트림(Stream)은 순서가 보장되는 GPU 명령 큐입니다. 서로 다른 스트림의 명령은 하드웨어가 허용하는 한 동시에 실행될 수 있으며, 이를 통해 커널 실행, 메모리 전송, 호스트 연산을 오버랩하여 GPU 파이프라인 활용률을 극대화합니다.

CUDA 스트림 비동기 실행 타임라인 시간 → 순차 실행 (기본 스트림) H→D 전송 커널 실행 D→H 전송 총 420 단위 파이프라인 (3 스트림) S1 H→D 커널 D→H S2 H→D 커널 D→H S3 H→D 커널 D→H 총 290 단위 (31% 단축) GPU 하드웨어 엔진: Compute Engine (SM) Copy Engine (H→D) Copy Engine (D→H) 3개 엔진이 독립적으로 동작 → 커널과 메모리 전송 동시 실행 가능 CUDA 이벤트: cudaEvent_t — 스트림에 타임스탬프 마커 삽입 cudaEventRecord() / cudaEventSynchronize() / cudaEventElapsedTime() 스트림 간 의존성: cudaStreamWaitEvent(streamB, eventFromA) → A 완료 후 B 진행
/* 3-스트림 파이프라인 — 전송과 커널 오버랩 */
const int nStreams = 3;
cudaStream_t streams[nStreams];
for (int i = 0; i < nStreams; i++)
    cudaStreamCreate(&streams[i]);

int chunkSize = N / nStreams;
for (int i = 0; i < nStreams; i++) {
    int offset = i * chunkSize;
    /* 비동기 H→D 전송 (핀드 메모리 필수) */
    cudaMemcpyAsync(d_in + offset, h_in + offset,
        chunkSize * sizeof(float), cudaMemcpyHostToDevice, streams[i]);

    /* 커널 실행 — 같은 스트림이므로 전송 완료 후 자동 실행 */
    myKernel<<<chunkSize/256, 256, 0, streams[i]>>>(d_in + offset, d_out + offset);

    /* 비동기 D→H 전송 */
    cudaMemcpyAsync(h_out + offset, d_out + offset,
        chunkSize * sizeof(float), cudaMemcpyDeviceToHost, streams[i]);
}

/* 모든 스트림 완료 대기 */
cudaDeviceSynchronize();

/* 이벤트로 경과 시간 측정 */
cudaEvent_t start, stop;
cudaEventCreate(&start); cudaEventCreate(&stop);
cudaEventRecord(start, streams[0]);
myKernel<<<grid, block, 0, streams[0]>>>(d_in, d_out);
cudaEventRecord(stop, streams[0]);
cudaEventSynchronize(stop);
float ms;
cudaEventElapsedTime(&ms, start, stop);
/* ms = 커널 실행 시간 (밀리초) */
CUDA 동시성 메커니즘 비교
메커니즘범위동기화사용 사례
기본 스트림 (stream 0)디바이스 전역암묵적 직렬화단순 순차 실행
비기본 스트림스트림 단위스트림 내 순서 보장(Ordering)파이프라인, 다중 커널
CUDA 이벤트스트림 간cudaStreamWaitEvent()스트림 간 의존성, 타이밍
CUDA 그래프전체 워크플로그래프 구조로 정의반복 실행 최적화, 실행 오버헤드 최소화
동적 병렬처리커널 내부커널 내 cudaDeviceSynchronize()적응적 알고리즘, 재귀 분할
/* CUDA 그래프 — 반복 실행 워크플로 최적화 (CC 7.0+) */
cudaGraph_t graph;
cudaGraphExec_t graphExec;

/* 1. 스트림 캡처로 그래프 기록 */
cudaStreamBeginCapture(stream, cudaStreamCaptureModeGlobal);
cudaMemcpyAsync(d_in, h_in, size, cudaMemcpyHostToDevice, stream);
myKernel<<<grid, block, 0, stream>>>(d_in, d_out);
cudaMemcpyAsync(h_out, d_out, size, cudaMemcpyDeviceToHost, stream);
cudaStreamEndCapture(stream, &graph);

/* 2. 그래프 인스턴스화 (1회) */
cudaGraphInstantiate(&graphExec, graph, 0);

/* 3. 반복 실행 — 실행 오버헤드 대폭 감소 */
for (int iter = 0; iter < 1000; iter++)
    cudaGraphLaunch(graphExec, stream);

cudaGraphExecDestroy(graphExec);
cudaGraphDestroy(graph);
CUDA 그래프 vs 스트림: CUDA 그래프는 전체 워크플로(전송→커널→전송)를 하나의 실행 단위로 캡처하여 커널 실행 오버헤드를 1회로 줄입니다. 매 프레임/이터레이션마다 동일한 패턴을 반복하는 AI 추론, 시뮬레이션 루프에 특히 효과적입니다. 그래프 내부 노드(커널 파라미터, 메모리 주소)는 cudaGraphExecUpdate()로 재인스턴스화 없이 갱신할 수 있습니다.

CUDA 메모리 계층

GPU 성능 최적화의 핵심은 메모리 계층을 이해하고 활용하는 것입니다. CUDA 메모리는 크게 레지스터 → 공유 메모리(Shared) → L1/L2 캐시 → 글로벌 메모리(VRAM) 순으로 용량이 커지고 지연(Latency) 시간이 증가합니다.

CUDA 메모리 계층 SM (Streaming Multiprocessor) 레지스터 (256 KB) 스레드 전용, ~1 사이클 로컬 메모리 레지스터 스필오버 공유 메모리 (__shared__) — 최대 228 KB 블록 내 스레드 공유, ~5 사이클, 뱅크 충돌 주의 L1 캐시 (공유메모리와 자원 공유) 상수 메모리 (64 KB) 텍스처 메모리 L2 캐시 (전체 SM 공유) — H100: 50 MB 글로벌 메모리 (HBM3 VRAM) — H100: 80 GB, ~2 TB/s cudaMalloc(), ~400 사이클 지연, 코얼레싱 접근 필수 UVM (Unified Virtual Memory) nvidia-uvm.ko — 페이지 단위 마이그레이션 cudaMallocManaged() → 동일 포인터 CPU↔GPU 자동 페이지 폴트 처리 프리페치: cudaMemPrefetchAsync() 호스트 메모리 (시스템 RAM) cudaHostAlloc() — 핀드 메모리 (DMA 직접 전송) PCIe 4.0 x16: ~32 GB/s 양방향 PCIe DMA ⬆ 용량 증가 · 지연 증가 ⬆ | ⬇ 속도 증가 · 용량 감소 ⬇ 최적화 핵심: 데이터 재사용(공유메모리 타일링), 코얼레싱 접근, 오큐펀시 극대화
CUDA 메모리 유형 상세 비교
메모리선언범위수명지연Hopper (H100) 기준 용량
레지스터자동 변수스레드스레드~1 사이클SM당 256 KB (65536 × 32b)
로컬자동 (스필)스레드스레드L1/L2 캐시글로벌에 배치
공유__shared__블록블록~5 사이클SM당 최대 228 KB
상수__constant__그리드호스트 할당캐시 히트 ~4 사이클64 KB (전용 캐시)
글로벌cudaMalloc그리드+호스트호스트 할당~400 사이클80 GB HBM3
/* 공유 메모리 타일링 — 행렬 곱셈 최적화 */
#define TILE 16

__global__ void matMul(const float *A, const float *B, float *C, int N) {
    __shared__ float sA[TILE][TILE], sB[TILE][TILE];
    int row = blockIdx.y * TILE + threadIdx.y;
    int col = blockIdx.x * TILE + threadIdx.x;
    float sum = 0.0f;

    for (int t = 0; t < N / TILE; t++) {
        sA[threadIdx.y][threadIdx.x] = A[row * N + t * TILE + threadIdx.x];
        sB[threadIdx.y][threadIdx.x] = B[(t * TILE + threadIdx.y) * N + col];
        __syncthreads();  /* 블록 내 동기화 */

        for (int k = 0; k < TILE; k++)
            sum += sA[threadIdx.y][k] * sB[k][threadIdx.x];
        __syncthreads();
    }
    C[row * N + col] = sum;
}
코얼레싱(Coalescing): 같은 워프의 32개 스레드가 연속 메모리 주소에 접근하면 하드웨어가 하나의 넓은 트랜잭션(128바이트)으로 합쳐줍니다. 비연속 접근은 다중 트랜잭션으로 분해되어 글로벌 메모리 대역폭 활용률이 급격히 떨어집니다. nvidia-smi dmonmem_util이 낮다면 접근 패턴 최적화를 먼저 확인하세요.
글로벌 메모리 접근 패턴과 성능 영향
접근 패턴트랜잭션 수대역폭 활용설명
연속 정렬 (Aligned Coalesced)1× (128B)100%T0→addr[0], T1→addr[1], ..., T31→addr[31]
연속 비정렬 (Misaligned)~50%시작 주소가 128B 경계에 비정렬
스트라이드 (Strided)최대 32×~3%T0→addr[0], T1→addr[stride], ... (열 우선 접근)
랜덤 (Scattered)최대 32×~3%각 스레드가 무관한 주소 접근
/* 공유 메모리 뱅크 충돌 회피 — 패딩 기법 */
/* 뱅크 충돌: 같은 뱅크를 동시 접근하면 순차 처리 */
/* 32개 뱅크, 4B 인터리빙: bank = (addr / 4) % 32 */

/* ❌ 뱅크 충돌 발생 — 열 접근 */
__shared__ float tile[32][32];  /* tile[0][0], tile[1][0]은 같은 뱅크 */
float val = tile[threadIdx.x][0];  /* 32-way 뱅크 충돌! */

/* ✅ 패딩으로 해결 */
__shared__ float tile[32][33];  /* +1 패딩 → 뱅크 오프셋 이동 */
float val = tile[threadIdx.x][0];  /* 충돌 없음 */

/* ✅ swizzle 기법 (CUTLASS 스타일) */
int swizzled_col = col ^ (row & 0x1F);
float val = tile[row][swizzled_col];

UVM (Unified Virtual Memory) 심층 분석

Unified Virtual Memory(UVM)는 CPU와 GPU가 동일한 가상 주소 공간을 공유하는 메커니즘입니다. nvidia-uvm.ko 커널 모듈이 페이지 폴트(Page Fault) 기반 마이그레이션을 처리하여, 프로그래머가 명시적 cudaMemcpy() 없이도 양쪽에서 데이터에 접근할 수 있습니다.

UVM 페이지 폴트 기반 마이그레이션 통합 가상 주소 공간 (cudaMallocManaged) CPU (호스트) 시스템 RAM (호스트 페이지 테이블) Page A Page B (비활성) MMU 페이지 폴트 → nvidia-uvm.ko 처리 ① CPU 접근 시 GPU→CPU 마이그레이션 ② GPU 매핑 무효화 + 페이지 복사 GPU (디바이스) GPU VRAM (GPU 페이지 테이블) (비활성) Page C Page D GPU 리플레이 가능 페이지 폴트 ③ GPU 접근 시 CPU→GPU 마이그레이션 ④ CPU 매핑 무효화 + PCIe DMA 전송 CPU→GPU GPU→CPU ⚠ 페이지 폴트 비용: ~20μs/fault (PCIe) — cudaMemPrefetchAsync()로 선제적 마이그레이션 권장
UVM 메모리 관리 API
API설명성능 힌트
cudaMallocManaged()통합 메모리 할당초기: CPU 상주, GPU 접근 시 마이그레이션
cudaMemPrefetchAsync()선제적 페이지 마이그레이션폴트 오버헤드 제거, 대량 전송 최적화
cudaMemAdvise()접근 패턴 힌트 제공ReadMostly: 양쪽 복제본 유지, 무효화(Invalidation) 최소화
cudaMemAdvise(SetPreferredLocation)기본 상주 디바이스 지정마이그레이션 대신 원격 매핑(Access Counter 기반)
cudaMemAdvise(SetAccessedBy)접근 디바이스 알림직접 매핑 생성으로 폴트 회피
/* UVM 최적화 패턴 — 프리페치 + 힌트 */
float *data;
cudaMallocManaged(&data, size);

/* CPU에서 데이터 초기화 */
initDataOnCPU(data, N);

/* GPU로 선제적 마이그레이션 (폴트 없이 전송) */
cudaMemPrefetchAsync(data, size, deviceId, stream);

/* 읽기 전용 데이터: 양쪽에 복제본 유지 */
cudaMemAdvise(readOnlyData, size, cudaMemAdviseSetReadMostly, deviceId);

/* GPU 커널 실행 */
myKernel<<<grid, block, 0, stream>>>(data);

/* CPU로 다시 마이그레이션 */
cudaMemPrefetchAsync(data, size, cudaCpuDeviceId, stream);
cudaStreamSynchronize(stream);
processOnCPU(data, N);
UVM 성능 함정: UVM은 프로그래밍 편의성은 뛰어나지만, 무분별하게 사용하면 빈번한 페이지 폴트와 스래싱(thrashing)으로 성능이 급격히 떨어집니다. CPU와 GPU가 번갈아 같은 페이지를 접근하면 핑퐁 마이그레이션이 발생합니다. 프로덕션 코드에서는 cudaMemPrefetchAsync()로 명시적 프리페치하거나, 접근 패턴이 명확한 경우 cudaMalloc() + cudaMemcpy()가 더 효율적입니다. HMM(Heterogeneous Memory Management) 통합 시 nvidia-uvm.ko는 Linux 커널의 hmm_range_fault()와 연동하여 시스템 통합 메모리 관리를 구현합니다. 자세한 내용은 HMM 페이지를 참조하세요.

Tensor Core 연산

Tensor Core는 NVIDIA GPU에 내장된 행렬 연산 전용 하드웨어 유닛으로, Volta(V100) 세대에서 처음 도입되었습니다. 단일 사이클에 작은 행렬의 FMA(Fused Multiply-Add) 연산 D = A × B + C를 수행하며, 일반 CUDA 코어 대비 4~16× 높은 연산 처리량(Throughput)을 달성합니다.

Tensor Core — MMA (Matrix Multiply-Accumulate) A (m×k) FP16/BF16/TF32/FP8 × B (k×n) FP16/BF16/TF32/FP8 + C (m×n) FP32/FP16 (누적) = D (m×n) FP32/FP16 (결과) 세대별 MMA 타일 크기 Volta (1세대) 4×4×4 FP16→FP32 125 TFLOPS (V100) Ampere (3세대) m16n8k16 다중 정밀도 312 TFLOPS TF32 (A100) Hopper (4세대) m16n8k32 FP8 지원 989 TFLOPS FP16 (H100) Blackwell (5세대) FP4 마이크로스케일링 2500+ TFLOPS (B200) 프로그래밍 인터페이스 계층 cuBLAS / cuDNN CUTLASS WMMA API MMA PTX (인라인) 가장 쉬움 가장 세밀함 추상화 수준 ↔ 제어 수준
Tensor Core 지원 정밀도 및 성능 (세대별)
정밀도입력→출력VoltaAmpereHopperBlackwell주요 사용처
FP16FP16→FP32125 T312 T989 T2250 T딥러닝 학습/추론
BF16BF16→FP32312 T989 T2250 TLLM 학습 (높은 동적 범위)
TF32TF32→FP32156 T495 T1125 TFP32 드롭인 대체 (cuBLAS 자동)
FP8 (E4M3)FP8→FP16/321979 T4500 TLLM 추론, 양자화 학습
FP4FP4→FP16/329000 T초저정밀도 추론
INT8INT8→INT32624 T1979 T4500 TINT8 양자화 추론
2:4 희소구조적 희소 × 입력2× 위 수치2× 위 수치2× 위 수치프루닝된 모델 가속
/* WMMA API — Tensor Core 프로그래밍 (CC 7.0+) */
#include <mma.h>
using namespace nvcuda::wmma;

__global__ void tensorGemm(const half *A, const half *B, float *C) {
    /* 16×16×16 타일 단위 MMA */
    fragment<matrix_a, 16, 16, 16, half, row_major> a_frag;
    fragment<matrix_b, 16, 16, 16, half, col_major> b_frag;
    fragment<accumulator, 16, 16, 16, float> c_frag;

    fill_fragment(c_frag, 0.0f);

    int warpM = (blockIdx.x * blockDim.x + threadIdx.x) / warpSize;
    int warpN = blockIdx.y;

    /* A와 B 타일 로드 */
    load_matrix_sync(a_frag, A + warpM * 16 * K, K);
    load_matrix_sync(b_frag, B + warpN * 16, N);

    /* Tensor Core MMA: D = A × B + C */
    mma_sync(c_frag, a_frag, b_frag, c_frag);

    /* 결과 저장 */
    store_matrix_sync(C + warpM * 16 * N + warpN * 16, c_frag, N, mem_row_major);
}
TF32 자동 가속: Ampere 이후 GPU에서 cublasSgemm()(FP32 GEMM)을 호출하면, cuBLAS가 자동으로 TF32 Tensor Core를 활용합니다. TF32는 FP32와 동일한 지수 범위(8비트)에 축소된 가수(10비트)를 사용하여, FP32 정밀도에 근접하면서 Tensor Core 속도를 얻습니다. 비활성화: cublasSetMathMode(handle, CUBLAS_DEFAULT_MATH). 구조적 희소성 2:4: 4개 원소 중 2개가 0인 패턴에서 Tensor Core가 자동으로 2× 가속합니다. cusparseLt 라이브러리나 PyTorch의 to_sparse_semi_structured()로 활용합니다.

오큐펀시 최적화

오큐펀시(Occupancy)는 SM이 동시에 유지할 수 있는 활성 워프 수 대비 실제 실행 중인 워프의 비율입니다. 높은 오큐펀시는 메모리 지연 시간을 워프 스위칭으로 효과적으로 숨길 수 있게 합니다. 오큐펀시를 결정하는 3대 제약 요소는 레지스터 사용량, 공유 메모리 사용량, 블록당 스레드 수입니다.

오큐펀시 제약 요소 분석 (SM_80 기준) SM 리소스 (Ampere SM_80) 최대 워프: 64 워프 (2048 스레드) 레지스터: 65536 × 32bit 공유 메모리: 164 KB (설정 가능) 최대 블록: 32 블록/SM 예제 커널 분석: blockDim = 256 (8 워프) 레지스터/스레드 = 40 → 10240/블록 공유메모리/블록 = 8 KB → 레지스터 제약: 65536/10240 = 6 블록 → 48워프/64워프 = 75% 오큐펀시 오큐펀시 최적화 전략 ① 레지스터 제한 __launch_bounds__(256, 8) 또는 -maxrregcount=32 ② 블록 크기 조정 cudaOccupancyMaxPotentialBlockSize() 자동 결정 ③ 공유 메모리 카빙 cudaFuncSetAttribute(MaxDynamicSharedMemory) ④ 스필 최소화 레지스터→로컬메모리 스필 = 글로벌 지연 발생 ⚠ 높은 오큐펀시 ≠ 최고 성능 캐시 활용, ILP, 메모리 대역폭도 중요 50~75% 오큐펀시에서 최적인 경우 많음
/* 오큐펀시 최적화 — 최적 블록 크기 자동 결정 */
int minGridSize, blockSize;
cudaOccupancyMaxPotentialBlockSize(
    &minGridSize, &blockSize,
    myKernel,         /* 대상 커널 */
    0,                /* 동적 공유메모리 크기 */
    0                 /* 블록 크기 제한 (0 = 제한 없음) */
);
/* blockSize = SM 리소스를 최대 활용하는 블록 크기 */

int gridSize = (N + blockSize - 1) / blockSize;
myKernel<<<gridSize, blockSize>>>(data, N);

/* 커널 레지스터 제한 — 스필과 오큐펀시 트레이드오프 */
__global__ void
__launch_bounds__(256, 8)  /* 최대 256 스레드/블록, 최소 8 블록/SM */
myOptimizedKernel(float *data) {
    /* 컴파일러가 레지스터를 256*8 블록에 맞게 할당 */
    /* → 레지스터/스레드 = 65536 / (256*8) = 32 */
}
오큐펀시 함정: 100% 오큐펀시가 항상 최고 성능은 아닙니다. 레지스터를 억지로 줄이면 스필이 발생하여 글로벌 메모리 접근이 증가하고, 오히려 성능이 떨어질 수 있습니다. 또한 오큐펀시가 높으면 워프당 가용 레지스터와 공유 메모리가 줄어 데이터 재사용(타일링)이 제한됩니다. Nsight Compute(ncu)의 "Occupancy" 섹션에서 실측 오큐펀시와 병목(Bottleneck) 요인(레지스터/공유메모리/블록 수)을 확인하고, 성능 프로파일링 결과에 기반하여 조정하세요.

NVIDIA 커널 모듈 상세

NVIDIA 독점 드라이버는 5개의 커널 모듈로 구성됩니다. 각 모듈은 역할이 명확히 분리되어 있으며, sysfsprocfs를 통해 런타임 상태를 조회하거나 파라미터를 변경할 수 있습니다.

NVIDIA 커널 모듈 역할 분담
모듈역할주요 인터페이스
nvidia.ko GPU 하드웨어 제어 핵심 (MMIO, 인터럽트, DMA, 전원 관리) /dev/nvidia0..N, /dev/nvidiactl
nvidia-modeset.ko 디스플레이 엔진 제어 (모드 설정, HDMI/DP 출력) /dev/nvidia-modeset
nvidia-uvm.ko Unified Virtual Memory — CPU↔GPU 페이지 마이그레이션, 폴트 처리 /dev/nvidia-uvm, /dev/nvidia-uvm-tools
nvidia-drm.ko DRM/KMS 브릿지 — Wayland/X11 compositing, GBM 버퍼 할당 /dev/dri/card*, /dev/dri/renderD*
nvidia-peermem.ko GPUDirect RDMA — InfiniBand/RoCE NIC↔GPU 직접 DMA peer_memory_client 커널 API
# 주요 모듈 파라미터 확인
cat /proc/driver/nvidia/params
# NVreg_EnablePCIeGen3=1
# NVreg_MemoryPoolSize=256  (MB, nvidia-uvm 내부 풀)
# NVreg_PreserveVideoMemoryAllocations=0

# GPU 정보 조회
cat /proc/driver/nvidia/gpus/0000:01:00.0/information
# Model:           NVIDIA H100 80GB HBM3
# IRQ:             153
# GPU UUID:        GPU-xxxx-xxxx-xxxx-xxxx

# sysfs 전원 관리
cat /sys/bus/pci/devices/0000:01:00.0/power_state
# D0 (활성) / D3hot (절전)
커널 ABI 잠금(Lock): NVIDIA 독점 모듈은 빌드 시점의 커널 버전에 종속됩니다. 커널을 업그레이드하면 반드시 NVIDIA 드라이버도 재빌드해야 합니다. DKMS(Dynamic Kernel Module Support)를 활용하면 커널 업데이트 시 자동으로 모듈을 재컴파일합니다: dkms status nvidia로 현재 빌드 상태를 확인하세요.
NVIDIA 커널 모듈 주요 파라미터 (NVreg_*)
파라미터기본값설명조정 시나리오
NVreg_EnablePCIeGen31PCIe Gen3 모드 활성화호환성 문제 시 0으로 비활성화
NVreg_MemoryPoolSize256UVM 내부 메모리 풀 (MB)대규모 UVM 사용 시 증가
NVreg_PreserveVideoMemoryAllocations0서스펜드 시 VRAM 보존절전/하이버네이트 사용 시 1
NVreg_RegistryDwords레지스터 레벨 설정 주입NVIDIA 지원팀 지시에 따라
NVreg_EnableGpuFirmware0GSP 펌웨어 강제 활성화오픈 커널 모듈 전환 시 1
NVreg_OpenRmEnableUnsupportedGpus0미지원 GPU에서 오픈 모듈 허용실험적 하드웨어 테스트
# modprobe 옵션으로 파라미터 설정 (/etc/modprobe.d/nvidia.conf)
options nvidia NVreg_PreserveVideoMemoryAllocations=1
options nvidia NVreg_MemoryPoolSize=512

# DKMS 빌드 상태 확인
dkms status nvidia
# nvidia/550.127.05, 6.1.0-26-amd64, x86_64: installed

# nvidia-persistenced — GPU 컨텍스트 지속 (초기화 지연 제거)
sudo systemctl enable nvidia-persistenced
sudo systemctl start nvidia-persistenced
# GPU 초기화가 첫 CUDA 호출 시 ~0.5s 걸리는 문제 해소
# HPC/ML 서버에서 필수 — 잦은 CUDA 프로세스 시작/종료 시

# Fabricmanager — NVSwitch 기반 멀티 GPU 시스템 (DGX)
sudo systemctl enable nvidia-fabricmanager
# NVSwitch 토폴로지 관리, GPU 간 NVLink 풀 메시 구성

# /proc/driver/nvidia/ 전체 구조 확인
ls /proc/driver/nvidia/
# gpus/  params  patches  registry  version
cat /proc/driver/nvidia/version
# NVRM version: NVIDIA UNIX x86_64 Kernel Module  550.127.05
nvidia-persistenced vs Persistence Mode: nvidia-smi -pm 1로 설정하는 Persistence Mode는 GPU 초기화 상태를 유지하지만, nvidia-persistenced 데몬이 더 안정적입니다. 데몬은 GPU당 최소 컨텍스트를 유지하여 마지막 사용자 프로세스 종료 후에도 드라이버가 언로드되지 않게 합니다. HPC 클러스터에서는 두 방법 모두 활성화하는 것이 일반적입니다.

GPUDirect / RDMA

GPUDirect 기술은 GPU 메모리와 외부 디바이스(다른 GPU, NIC, NVMe) 간의 직접 DMA 경로를 제공하여 CPU 메모리 복사를 제거합니다. HPC와 대규모 AI 학습에서 노드 간 통신 병목을 해소하는 핵심 기술입니다.

GPUDirect RDMA 토폴로지 노드 1 GPU 0 HBM 80 GB GPU 1 HBM 80 GB NVLink 900 GB/s (H100) CPU + 시스템 RAM PCIe InfiniBand NIC GPUDirect RDMA (CPU 바이패스) 네트워크 패브릭 노드 2 GPU 2 HBM 80 GB GPU 3 HBM 80 GB NVLink CPU + 시스템 RAM InfiniBand NIC 네트워크 패브릭 InfiniBand HDR: 200 Gb/s NCCL AllReduce: GPU VRAM ↔ NIC ↔ 원격 NIC ↔ GPU VRAM (CPU 바이패스)
GPUDirect 기술 비교
기술경로커널 모듈대역폭 (예시)
GPUDirect P2PGPU↔GPU (동일 노드, PCIe)nvidia.koPCIe 4.0: ~32 GB/s
NVLinkGPU↔GPU (전용 인터커넥트)nvidia.koNVLink 4.0: 900 GB/s (H100)
GPUDirect RDMAGPU↔NIC (CPU 바이패스)nvidia-peermem.koIB HDR: ~25 GB/s
GPUDirect StorageGPU↔NVMe (CPU 바이패스)nvidia-fs.koPCIe: ~7 GB/s
nvidia-peermem 동작 원리: nvidia-peermem.ko는 Linux 커널의 peer_memory_client API에 등록하여 InfiniBand 서브시스템(mlx5_ib 등)이 GPU 메모리의 물리 주소를 직접 얻을 수 있게 합니다. NCCL은 이를 활용해 AllReduce 등 집합 통신 시 시스템 RAM을 거치지 않는 제로카피 전송을 수행합니다.
# GPUDirect P2P 토폴로지 확인
nvidia-smi topo -m
#         GPU0  GPU1  GPU2  GPU3  NIC0  CPU
# GPU0      X   NV12  NV12  NV12  SYS   SYS
# GPU1    NV12    X   NV12  NV12  SYS   SYS
# NV12 = NVLink 12 hops, SYS = PCIe through CPU

# GPU 간 P2P 접근 가능 여부 확인
nvidia-smi topo -p2p r
# GPU0 GPU1: OK (NVLink P2P 가능)

# nvidia-peermem 로드 확인
lsmod | grep nvidia_peermem
# nvidia_peermem  16384  0
cat /sys/kernel/mm/memory_peers/nvidia-peermem/version
# 1.3

# NCCL 환경 변수로 GPUDirect 제어
export NCCL_NET_GDR_LEVEL=5       # GPUDirect RDMA 활성화 수준
export NCCL_IB_HCA=mlx5_0:1       # InfiniBand HCA 지정
export NCCL_P2P_LEVEL=NVL          # NVLink P2P 사용
export NCCL_DEBUG=INFO             # 디버그 로그

NCCL 집합 통신

NCCL(NVIDIA Collective Communications Library)은 다중 GPU 간 집합 통신을 최적화하는 라이브러리로, 분산 딥러닝 학습의 핵심 인프라입니다. NVLink, PCIe, InfiniBand 등 사용 가능한 모든 인터커넥트를 자동으로 감지하여 최적의 통신 토폴로지를 구성합니다.

NCCL 주요 집합 통신 패턴 AllReduce (합산) GPU0: [1] GPU1: [2] GPU2: [3] GPU3: [4] [10] [10] [10] [10] 모든 GPU가 합산 결과를 보유 (분산 학습 그래디언트 동기화) AllGather (수집) GPU0: [A] GPU1: [B] GPU2: [C] GPU3: [D] 각 GPU: [A, B, C, D] 모든 GPU가 전체 데이터 보유 (Tensor 병렬처리) ReduceScatter [1,2,3,4] [5,6,7,8] [9,A,B,C] [D,E,F,G] Σcol0 Σcol1 Σcol2 Σcol3 합산 + 분산 (ZeRO 옵티마이저) Broadcast GPU0: [X] 각 GPU: [X] 루트에서 전체 배포 (모델 가중치 초기 배포) NCCL Ring AllReduce 알고리즘 GPU0 GPU1 GPU2 GPU3 2(N-1) 단계, 대역폭 최적
NCCL 집합 통신 연산과 분산 학습 사용 사례
연산입력→출력통신량대표 사용 사례
ncclAllReduce각 GPU 텐서 → 합산 결과 전체 복제2(N-1)/N × size데이터 병렬 그래디언트 동기화
ncclAllGather각 GPU 조각 → 전체 텐서 복제(N-1)/N × totalTensor Parallelism 출력 수집
ncclReduceScatter합산 + 분산(N-1)/N × sizeZeRO Stage 2/3 옵티마이저
ncclBroadcast루트 → 전체size모델 가중치 초기 배포
ncclReduce전체 → 루트 합산(N-1) × size메트릭 수집, 체크포인팅
ncclSend/Recv점대점sizePipeline Parallelism 스테이지 간 전송
/* NCCL AllReduce — 4-GPU 그래디언트 동기화 */
#include <nccl.h>

ncclComm_t comms[4];
ncclCommInitAll(comms, 4, devs);  /* 4 GPU 커뮤니케이터 생성 */

/* 각 GPU에서 비동기 AllReduce 실행 */
ncclGroupStart();
for (int i = 0; i < 4; i++) {
    cudaSetDevice(i);
    ncclAllReduce(
        sendbuff[i], recvbuff[i],
        count, ncclFloat, ncclSum,
        comms[i], streams[i]
    );
}
ncclGroupEnd();

/* 모든 스트림 동기화 */
for (int i = 0; i < 4; i++) {
    cudaSetDevice(i);
    cudaStreamSynchronize(streams[i]);
}

/* 정리 */
for (int i = 0; i < 4; i++)
    ncclCommDestroy(comms[i]);
NCCL 알고리즘 선택: NCCL은 토폴로지에 따라 자동으로 최적 알고리즘을 선택합니다. Ring: 대역폭 최적(N GPU에서 2(N-1) 단계), Tree: 지연 최적(log₂N 단계), NVLink SHARP: 네트워크 내 리덕션(InfiniBand 스위치에서 연산). NCCL_ALGO=Ring 또는 Tree로 강제 지정할 수 있으나, 대부분의 경우 자동 선택이 최적입니다. NCCL_DEBUG=INFO로 선택된 알고리즘과 대역폭을 확인하세요.

NVIDIA 오픈 GPU 커널 모듈

2022년 5월, NVIDIA는 open-gpu-kernel-modules 저장소를 통해 GPU 커널 드라이버의 소스 코드를 공개했습니다(GPL-2.0 / MIT 듀얼 라이선스). Turing(GTX 16xx) 이후 아키텍처에서 사용할 수 있으며, 장기적으로 독점 모듈을 대체할 계획입니다.

오픈 vs 독점 커널 모듈 비교
항목오픈 커널 모듈독점 커널 모듈nouveau (메인라인)
라이선스GPL-2.0 / MIT독점GPL-2.0
소스 공개전체 (커널 부분)비공개전체 (리버스 엔지니어링)
지원 GPUTuring 이후 (530+)Kepler 이후Tesla~Ampere (제한적)
CUDA 지원완전 (독점 libcuda.so 필요)완전제한적 (Volta+ GSP-RM 기반)
메인라인 포함아니오 (out-of-tree)아니오
GSP 펌웨어필수 (GPU System Processor)내장가능 (Turing+)
버그 리포트GitHub IssuesNVIDIA 포럼freedesktop GitLab
GSP-RM(GPU System Processor — Resource Manager): Turing 이후 GPU에는 RISC-V 기반 마이크로컨트롤러(GSP)가 내장되어 있으며, GPU 초기화·전원 관리·메모리 관리의 상당 부분을 펌웨어에서 처리합니다. 오픈 커널 모듈은 이 GSP와 통신하는 "얇은 커널 셸" 역할을 하고, 실제 하드웨어 제어 로직은 GSP 펌웨어(비공개)에 있습니다. nouveau도 동일한 GSP 펌웨어를 활용하여 Turing+ 하드웨어 지원을 개선하고 있습니다.

NVIDIA 컨테이너 / 가상화(Virtualization)

클라우드 및 AI 인프라에서 GPU를 컨테이너·가상 머신에 안전하게 공유하려면 별도 런타임이 필요합니다. NVIDIA는 Container Toolkit, MIG, vGPU, K8s Device Plugin을 통해 GPU 격리와 스케줄링을 제공합니다.

NVIDIA GPU 공유 / 격리 기술
기술격리 수준대상 GPU사용 사례
nvidia-container-toolkit 소프트웨어 (OCI 런타임 훅) 전체 Docker/Podman에서 --gpus 플래그로 GPU 할당
MIG (Multi-Instance GPU) 하드웨어 (SM/메모리 파티셔닝) A100, H100, H200 하나의 GPU를 최대 7개 독립 인스턴스로 분할
vGPU 하이퍼바이저 (SR-IOV / 메디에이티드) 데이터센터 GPU (라이선스 필요) VM에 가상 GPU 할당, VDI/원격 데스크톱
K8s Device Plugin 스케줄러 수준 전체 nvidia.com/gpu: 1 리소스 요청, 노드 선택
Time-Slicing 시간 분할 (컨텍스트 스위칭(Context Switching)) 전체 MIG 미지원 GPU에서 다중 워크로드 공유
# Docker에서 CUDA 컨테이너 실행
docker run --gpus all nvidia/cuda:12.4.0-runtime-ubuntu22.04 nvidia-smi

# MIG 인스턴스 생성 (A100/H100)
sudo nvidia-smi mig -cgi 19,19,19 -C  # 3x 3g.40gb 프로파일
nvidia-smi mig -lgi  # GPU 인스턴스 목록
nvidia-smi mig -lci  # 컴퓨트 인스턴스 목록

# Kubernetes GPU 리소스 요청 (Pod spec)
# resources:
#   limits:
#     nvidia.com/gpu: 1
MIG vs Time-Slicing: MIG는 SM과 메모리를 하드웨어 수준에서 완전히 격리하므로 QoS가 보장되지만, A100/H100 같은 고급 GPU에서만 지원됩니다. Time-Slicing은 모든 GPU에서 사용할 수 있지만 컨텍스트 스위칭 오버헤드가 있고 메모리 격리가 없어 OOM(Out-of-Memory) 간섭이 발생할 수 있습니다.
MIG (Multi-Instance GPU) 파티셔닝 — A100 80GB A100 80GB — 108 SM, 80GB HBM2e, 8× Memory Slice 프로파일 A: 7× 1g.10gb 1g.10gb (14SM) 1g.10gb 1g.10gb 1g.10gb 1g.10gb 1g.10gb 1g.10gb 프로파일 B: 3× 2g.20gb + 1× 1g.10gb 2g.20gb (28 SM, 20GB) 2g.20gb (28 SM, 20GB) 2g.20gb (28 SM, 20GB) 1g.10gb 프로파일 C: 1× 7g.80gb (전체 GPU) 7g.80gb — 전체 108 SM, 80GB HBM2e (MIG 비활성화와 동일 성능) 각 인스턴스: 독립 CUDA 컨텍스트, 격리된 메모리/SM/L2 캐시/디코더, /dev/nvidia-caps/ 노드
A100 MIG 프로파일 상세
프로파일SM 수메모리L2 캐시최대 인스턴스 수사용 사례
1g.10gb1410 GB5 MB7소형 추론, 개발/테스트
1g.20gb1420 GB10 MB4메모리 집약 추론
2g.20gb2820 GB10 MB3중형 학습/추론
3g.40gb4240 GB20 MB2대형 모델 학습
4g.40gb5640 GB20 MB1대형 학습 (단독)
7g.80gb10880 GB40 MB1전체 GPU 활용
# nvidia-container-toolkit 설치 (Ubuntu)
distribution=$(. /etc/os-release; echo $ID$VERSION_ID)
curl -fsSL https://nvidia.github.io/libnvidia-container/gpgkey | \
  sudo gpg --dearmor -o /usr/share/keyrings/nvidia-container-toolkit-keyring.gpg
sudo apt-get update && sudo apt-get install nvidia-container-toolkit

# Docker 런타임 설정
sudo nvidia-ctk runtime configure --runtime=docker
sudo systemctl restart docker

# MIG + 컨테이너 — 특정 MIG 인스턴스에서 컨테이너 실행
# MIG 디바이스 UUID 확인
nvidia-smi -L
# GPU 0: A100-SXM4-80GB (UUID: GPU-xxxxx)
#   MIG 1g.10gb Device 0: (UUID: MIG-yyyyy)

# MIG UUID로 컨테이너 실행
docker run --gpus '"device=MIG-yyyyy"' nvidia/cuda:12.4.0-base-ubuntu22.04 nvidia-smi

# Kubernetes MIG 리소스 — Device Plugin 설정
# helm install --set migStrategy=single nvidia-device-plugin ...
# Pod spec: nvidia.com/mig-1g.10gb: 1
nvidia-container-toolkit 동작 원리: 컨테이너 시작 시 OCI prestart 훅이 호출되어, nvidia-container-cli가 GPU 디바이스 노드(/dev/nvidia*)와 드라이버 라이브러리(libnvidia-*.so)를 컨테이너 네임스페이스에 바인드 마운트(Bind Mount)합니다. 컨테이너 이미지에는 CUDA 런타임만 포함하면 되며, 드라이버 버전은 호스트에서 자동으로 주입됩니다. 이를 통해 동일 이미지가 다양한 드라이버 버전의 호스트에서 동작합니다.

CUDA 컴파일 파이프라인

CUDA 소스(.cu)는 nvcc 컴파일러 드라이버를 통해 여러 단계를 거칩니다. 호스트 코드는 gcc/clang에 위임되고, 디바이스 코드는 PTX(가상 ISA) → SASS(실제 기계어(Machine Code))로 변환됩니다.

CUDA 컴파일 단계
단계입력출력도구설명
1. 전처리.cu.cu.cpp.ii / .cu.gpunvcc (cudafe++)호스트/디바이스 코드 분리
2. PTX 컴파일디바이스 코드.ptxcicc가상 ISA (중간 표현)
3. 어셈블.ptx.cubin (SASS)ptxas대상 SM 아키텍처용 기계어
4. Fatbinary.ptx + .cubin.fatbinfatbinary다중 아키텍처 번들
5. 호스트 컴파일호스트 코드 + .fatbin.ogcc / clangfatbin을 ELF에 임베드
6. 링크.o + libcudart실행 파일ldCUDA 런타임 라이브러리 링크
# 기본 컴파일 (sm_80 = Ampere, sm_90 = Hopper)
nvcc -arch=sm_80 -o matmul matmul.cu

# Fatbinary: 여러 아키텍처 동시 타겟
nvcc -gencode arch=compute_80,code=sm_80 \
     -gencode arch=compute_90,code=sm_90 \
     -gencode arch=compute_90,code=compute_90 \
     -o matmul matmul.cu
# compute_90,code=compute_90 → PTX 포함 (미래 GPU JIT 호환)

# PTX 어셈블리 확인
nvcc -arch=sm_80 --ptx -o matmul.ptx matmul.cu
# SASS 디스어셈블
cuobjdump -sass matmul
JIT 컴파일: 실행 파일에 PTX가 포함되어 있으면, 실행 시점에 드라이버가 현재 GPU 아키텍처에 맞는 SASS로 JIT 컴파일합니다. 이를 통해 컴파일 시점에 존재하지 않던 미래 GPU에서도 (최적은 아니지만) 동작할 수 있습니다. JIT 결과는 ~/.nv/ComputeCache에 캐시됩니다.
CUDA 컴파일 파이프라인 (nvcc) .cu 소스 cudafe++ 호스트/디바이스 분리 cicc 디바이스 컴파일 .ptx ptxas 어셈블 .cubin gcc / clang 호스트 컴파일 fatbinary PTX+SASS 번들 실행 파일 (ELF) fatbin 임베드 실행 시점 (Runtime) ELF 실행 libcuda.so 로드 SASS 매칭 검사 현재 GPU CC와 비교 매칭 ✓ SASS 직접 실행 PTX만 ✓ JIT 컴파일 (ptxas) SASS 생성 → 실행 ~/.nv/ComputeCache/ 다음 실행 시 캐시 사용 둘 다 ✗ cudaErrorNoKernelImageForDevice
# NVRTC — 런타임 CUDA 컴파일 (Driver API)
# PTX를 프로그램 실행 중에 동적 생성하는 시나리오
# AI 프레임워크(PyTorch, JAX)의 커널 퓨전에 사용

# Compute Capability별 아키텍처 코드 매핑
# sm_70 = Volta (V100)
# sm_75 = Turing (T4, RTX 20xx)
# sm_80 = Ampere (A100, A10)
# sm_86 = Ampere (RTX 30xx, A40)
# sm_89 = Ada Lovelace (RTX 40xx, L4, L40)
# sm_90 = Hopper (H100, H200)
# sm_90a = Hopper (SM 전용 기능, GH200)
# sm_100 = Blackwell (B200, GB200)

# 현재 GPU의 Compute Capability 확인
nvidia-smi --query-gpu=compute_cap --format=csv,noheader
# 9.0

# NVCC 상세 컴파일 과정 보기
nvcc -v -arch=sm_80 -o test test.cu 2>&1 | head -50
# cudafe++ → cicc → ptxas → fatbinary → gcc 순서 확인 가능

CUDA 디버깅 및 프로파일링

CUDA 프로그램의 성능 분석과 디버깅에는 NVIDIA가 제공하는 전용 도구체인과 Linux 커널의 /proc/driver/nvidia/ 인터페이스를 활용합니다.

CUDA 디버깅 / 프로파일링 도구
도구용도핵심 기능
nvidia-smiGPU 모니터링온도, 전력, 메모리, 프로세스, MIG, 클럭
nvtop실시간(Real-time) 모니터링 (htop 스타일)GPU/메모리 사용률 그래프, 프로세스 목록
nsys (Nsight Systems)시스템 프로파일링타임라인 뷰, CPU-GPU 상호작용, CUDA API 추적
ncu (Nsight Compute)커널 프로파일링오큐펀시, 메모리 대역폭, 워프 스톨 분석
cuda-gdbGPU 디버거커널 내 브레이크포인트, 워프/스레드 단위 검사
compute-sanitizer메모리 검사범위 초과 접근, 레이스 컨디션, 리크 탐지
/proc/driver/nvidia/커널 모듈 상태GPU 정보, 파라미터, 메모리 할당, 에러 로그
# GPU 상태 모니터링
nvidia-smi
# +-------------------------+------+------+
# | GPU  Name        Temp   | Util | MIG  |
# | Fan  Perf  Pwr:Usage/Cap|  GPU | Mode |
# |=========================+======+======|
# |   0  NVIDIA H100    38C |  85% |  On  |
# |  N/A  P0    310W / 350W |      |      |

# 시스템 프로파일링 (Nsight Systems)
nsys profile --stats=true ./my_cuda_app
# → report.nsys-rep 생성 (GUI에서 타임라인 분석)

# 커널 단위 프로파일링 (Nsight Compute)
ncu --set full --target-processes all ./my_cuda_app
# → 오큐펀시, SM 활용률, 메모리 throughput 상세 리포트

# 메모리 오류 탐지
compute-sanitizer --tool memcheck ./my_cuda_app
# → 범위 초과 접근, 초기화되지 않은 읽기 탐지

# 레이스 컨디션 탐지
compute-sanitizer --tool racecheck ./my_cuda_app
# → 공유 메모리 WAR/WAW/RAW 레이스 탐지

# 동기화 오류 탐지
compute-sanitizer --tool synccheck ./my_cuda_app
# → __syncthreads() 누락, 불균형 배리어 탐지

# cuda-gdb 디버깅 세션
cuda-gdb ./my_cuda_app
# (cuda-gdb) set cuda break_on_launch all
# (cuda-gdb) run
# (cuda-gdb) cuda thread (0,0,0)   ← 특정 스레드 선택
# (cuda-gdb) info cuda threads     ← 워프/블록 상태
# (cuda-gdb) print threadIdx       ← 현재 스레드 인덱스
# (cuda-gdb) cuda kernel            ← 활성 커널 정보
Nsight Compute (ncu) 주요 메트릭 해석
메트릭의미최적 범위낮을 때 원인
SM Occupancy (%)활성 워프 / 최대 워프50~100%레지스터/공유메모리 과다, 작은 블록
Compute Throughput (%)SM 파이프라인 활용률>60%메모리 바운드, 명령어 레벨 의존성
Memory Throughput (%)메모리 대역폭 활용률>60%비코얼레싱 접근, 낮은 오큐펀시
Warp Stall (사이클)워프 대기 원인별 사이클낮을수록 좋음long_scoreboard: 글로벌 메모리 대기
L1 Hit Rate (%)L1 캐시 적중률>80%랜덤 접근, 작업 세트 > L1 크기
Achieved Bandwidth (GB/s)실제 메모리 대역폭이론 대비 >70%비코얼레싱, 낮은 IPC
성능 최적화 워크플로:nsys profile로 전체 타임라인 확인 → CPU-GPU 동기화 병목, 유휴 시간 식별. ② 병목 커널을 ncu --set full로 상세 분석 → Compute vs Memory 바운드 판별. ③ Memory 바운드면: 코얼레싱 접근, 공유메모리 타일링, L2 지역성 최적화. ④ Compute 바운드면: ILP(Instruction-Level Parallelism) 증가, Tensor Core 활용, 알고리즘 개선. ⑤ Latency 바운드(오큐펀시 낮음)면: 블록 크기/레지스터 조정. 이 과정을 반복하여 루프라인 모델(Roofline Model) 상에서 이론적 한계에 근접시킵니다.

멀티 GPU 프로그래밍

단일 노드에 여러 GPU가 장착된 환경에서 CUDA는 cudaSetDevice()로 활성 GPU를 전환하고, P2P(Peer-to-Peer) 접근과 비동기 전송으로 GPU 간 데이터를 교환합니다. 대규모 AI 학습에서는 NCCL과 결합하여 데이터/모델/파이프라인 병렬처리를 구현합니다.

분산 학습 병렬화 전략 비교
전략분할 대상통신 패턴GPU당 메모리확장성프레임워크 지원
데이터 병렬 (DP)배치 (데이터)AllReduce (그래디언트)전체 모델 복제높음 (수백 GPU)DDP, FSDP, Horovod
텐서 병렬 (TP)레이어 내 텐서AllReduce/AllGather텐서 1/N노드 내 (NVLink 필요)Megatron-LM, DeepSpeed
파이프라인 병렬 (PP)레이어 그룹Send/Recv (스테이지 간)레이어 1/N중간 (버블 오버헤드)Megatron-LM, GPipe
ZeRO Stage 1옵티마이저 상태AllGather (업데이트 시)옵티마이저 1/N높음DeepSpeed
ZeRO Stage 2옵티마이저 + 그래디언트ReduceScatter + AllGather옵티마이저+그래디언트 1/N높음DeepSpeed, FSDP
ZeRO Stage 3옵티마이저+그래디언트+파라미터AllGather (순전파/역전파)파라미터 1/N가장 높음DeepSpeed, FSDP
/* 멀티 GPU — P2P 메모리 접근 + 비동기 전송 */
int deviceCount;
cudaGetDeviceCount(&deviceCount);

/* P2P 접근 활성화 (NVLink/PCIe P2P) */
for (int i = 0; i < deviceCount; i++) {
    cudaSetDevice(i);
    for (int j = 0; j < deviceCount; j++) {
        if (i != j) {
            int canAccess;
            cudaDeviceCanAccessPeer(&canAccess, i, j);
            if (canAccess)
                cudaDeviceEnablePeerAccess(j, 0);
        }
    }
}

/* GPU 0의 메모리를 GPU 1에서 직접 접근 (UVA) */
cudaSetDevice(0);
float *d_gpu0;
cudaMalloc(&d_gpu0, size);

cudaSetDevice(1);
/* P2P 활성화 시 GPU1 커널에서 d_gpu0 직접 읽기 가능 */
readFromGpu0Kernel<<<grid, block>>>(d_gpu0);

/* 비동기 GPU간 복사 (Copy Engine 사용) */
cudaMemcpyPeerAsync(d_gpu1, 1,   /* dst GPU 1 */
                     d_gpu0, 0,   /* src GPU 0 */
                     size, stream);
UVA(Unified Virtual Addressing): 64비트 시스템에서 CUDA는 모든 GPU와 CPU 메모리를 단일 가상 주소 공간에 매핑합니다. cudaPointerGetAttributes()로 포인터가 어느 디바이스에 속하는지 조회할 수 있으며, P2P가 활성화되면 GPU 0의 포인터를 GPU 1의 커널에서 직접 역참조(Dereference)할 수 있습니다(NVLink 시 ~900 GB/s, PCIe 시 ~32 GB/s).

CUDA 에러 처리 패턴

CUDA API 호출은 cudaError_t를 반환하며, 커널 실행 오류는 비동기적으로 발생합니다. 프로덕션 코드에서는 모든 CUDA 호출을 검사하는 매크로(Macro)를 사용하고, 커널 실행 후 cudaGetLastError()cudaDeviceSynchronize()로 에러를 포착합니다.

/* CUDA 에러 검사 매크로 — 프로덕션 필수 패턴 */
#define CUDA_CHECK(call) do { \
    cudaError_t err = (call); \
    if (err != cudaSuccess) { \
        fprintf(stderr, "CUDA error at %s:%d: %s\n", \
                __FILE__, __LINE__, cudaGetErrorString(err)); \
        exit(EXIT_FAILURE); \
    } \
} while(0)

/* 사용 예 */
CUDA_CHECK(cudaMalloc(&d_ptr, size));
CUDA_CHECK(cudaMemcpy(d_ptr, h_ptr, size, cudaMemcpyHostToDevice));

/* 커널 실행 에러 검사 — 2단계 필수 */
myKernel<<<grid, block>>>(d_ptr);
CUDA_CHECK(cudaGetLastError());          /* 실행 설정 오류 (즉시) */
CUDA_CHECK(cudaDeviceSynchronize());     /* 실행 중 오류 (비동기) */

/* 스트림 콜백으로 비동기 에러 처리 */
cudaLaunchHostFunc(stream, [](void* data) {
    cudaError_t err = cudaGetLastError();
    if (err != cudaSuccess)
        handle_gpu_error(err);
}, nullptr);
주요 CUDA 에러 코드와 원인
에러 코드원인해결 방법
cudaErrorMemoryAllocationGPU 메모리 부족할당 크기 줄이기, 메모리 풀 사용, nvidia-smi 확인
cudaErrorInvalidConfiguration잘못된 블록/그리드 크기블록 크기 ≤ 1024, CC별 제약 확인
cudaErrorIllegalAddress잘못된 메모리 접근 (SEGFAULT)compute-sanitizer --tool memcheck
cudaErrorLaunchTimeout커널 실행 시간 초과 (TDR)디스플레이 GPU에서 긴 커널 회피, TDR 타임아웃 증가
cudaErrorNoKernelImageForDevice현재 GPU CC 미지원 바이너리적절한 -arch=sm_XX로 재컴파일
cudaErrorAssert커널 내 assert() 실패디바이스 코드 로직 디버깅
cudaErrorECCUncorrectableGPU 메모리 ECC 오류 (하드웨어)nvidia-smi --query-gpu=ecc.errors.uncorrected.total
Sticky Error와 컨텍스트 복구: cudaErrorIllegalAddress 같은 치명적 에러가 발생하면 CUDA 컨텍스트가 sticky error 상태가 되어 이후 모든 CUDA 호출이 실패합니다. 복구하려면 cudaDeviceReset()으로 컨텍스트를 완전히 재초기화해야 합니다. 프로덕션에서는 GPU 워커 프로세스를 분리하여, 오류 시 프로세스를 재시작(Reboot)하는 구조가 권장됩니다. Xid 에러: 커널 로그(dmesg)에 나타나는 "NVRM: Xid" 메시지는 GPU 하드웨어/드라이버 오류입니다. Xid 79(GPU 폴트), Xid 48(더블 비트 ECC)이 반복되면 GPU 교체를 검토하세요.

CUDA 라이브러리 에코시스템

CUDA 생태계의 진정한 강점은 수십 년간 최적화된 도메인별 라이브러리에 있습니다. 대부분의 AI/HPC 워크로드는 커널을 직접 작성하지 않고 이 라이브러리들을 조합합니다.

주요 CUDA 라이브러리와 ROCm 대응
CUDA 라이브러리도메인ROCm 대응설명
cuBLAS선형대수rocBLASGEMM, 행렬 분해, Tensor Core 활용
cuDNN딥러닝MIOpenConv, RNN, Attention, BN 등 DNN 프리미티브
cuFFTFFTrocFFT1D/2D/3D FFT, 배치 처리
cuSPARSE희소 행렬rocSPARSESpMV, SpMM, 희소 행렬 연산
cuRAND난수 생성rocRAND의사/준난수, 병렬 RNG 스트림
NCCL집합 통신RCCLAllReduce, AllGather, 다중 GPU/노드
TensorRT추론 최적화그래프 최적화, INT8/FP8 양자화, 레이어 퓨전
Thrust병렬 알고리즘rocThrustsort, reduce, scan (C++ STL 스타일)
cuDSS직접 희소 솔버rocSOLVERLU, Cholesky, QR 분해 (희소)
CUTLASSGEMM 템플릿composable_kernelTensor Core GEMM 커스터마이징
/* cuBLAS GEMM — 행렬 곱셈 C = α·A·B + β·C */
cublasHandle_t handle;
cublasCreate(&handle);

float alpha = 1.0f, beta = 0.0f;
cublasSgemm(handle,
    CUBLAS_OP_N, CUBLAS_OP_N,
    M, N, K,
    &alpha,
    d_A, M,     /* A: M×K */
    d_B, K,     /* B: K×N */
    &beta,
    d_C, M);    /* C: M×N */

cublasDestroy(handle);
/* Tensor Core 자동 활용 (FP16/TF32/FP8 입력 시) */
ROCm/HIP 이식성: AMD의 hipify-perl / hipify-clang 도구는 CUDA 소스를 HIP 코드로 자동 변환합니다. cudaMallochipMalloc, cublasSgemmrocblas_sgemm 등 API가 1:1 대응되어, 대부분의 CUDA 코드를 AMD GPU에서도 실행할 수 있습니다. 자세한 내용은 ROCm/HIP 섹션을 참조하세요.
/* cuDNN — 합성곱(Convolution) 연산 예제 */
cudnnHandle_t cudnn;
cudnnCreate(&cudnn);

cudnnTensorDescriptor_t inputDesc, outputDesc;
cudnnFilterDescriptor_t filterDesc;
cudnnConvolutionDescriptor_t convDesc;

/* 텐서/필터 디스크립터 설정 (NCHW 포맷) */
cudnnCreateTensorDescriptor(&inputDesc);
cudnnSetTensor4dDescriptor(inputDesc, CUDNN_TENSOR_NCHW,
    CUDNN_DATA_FLOAT, batch, channels, height, width);

/* 최적 합성곱 알고리즘 자동 선택 */
cudnnConvolutionFwdAlgo_t algo;
cudnnGetConvolutionForwardAlgorithm_v7(cudnn,
    inputDesc, filterDesc, convDesc, outputDesc,
    1, &returnedCount, &perfResults);
algo = perfResults.algo;  /* 가장 빠른 알고리즘 선택 */

/* 합성곱 실행 — Tensor Core 자동 활용 */
cudnnSetConvolutionMathType(convDesc, CUDNN_TENSOR_OP_MATH);
float alpha = 1.0f, beta = 0.0f;
cudnnConvolutionForward(cudnn, &alpha,
    inputDesc, d_input, filterDesc, d_filter,
    convDesc, algo, d_workspace, workspaceSize,
    &beta, outputDesc, d_output);

cudnnDestroy(cudnn);
cuDNN 합성곱 알고리즘 비교
알고리즘워크스페이스속도정밀도사용 시나리오
IMPLICIT_GEMM0기본정확메모리 제약 환경
IMPLICIT_PRECOMP_GEMM소량빠름정확일반적 사용
GEMM대량 (im2col)빠름정확큰 배치
FFT대량매우 빠름근사큰 필터 크기
FFT_TILING중간빠름근사중간 필터
WINOGRAD소량매우 빠름근사3×3, 5×5 필터
WINOGRAD_NONFUSED중간매우 빠름근사3×3 필터 최적

딥러닝 프레임워크 통합

PyTorch, TensorFlow, JAX 등 주요 딥러닝 프레임워크는 CUDA를 통해 GPU 가속을 구현합니다. 프레임워크는 내부적으로 cuBLAS, cuDNN, NCCL, cuFFT 등 CUDA 라이브러리를 호출하며, 사용자는 Python API만으로 Tensor Core, 멀티 GPU, 혼합 정밀도 학습을 활용할 수 있습니다.

딥러닝 프레임워크 CUDA 통합 비교
프레임워크CUDA 백엔드커널 생성분산 학습혼합 정밀도
PyTorch ATen + cuDNN + cuBLAS torch.compile (Triton/CUDA) DDP, FSDP (NCCL) torch.amp (FP16/BF16/FP8)
TensorFlow XLA + cuDNN + cuBLAS XLA HLO → LLVM → PTX tf.distribute (NCCL) tf.keras.mixed_precision
JAX XLA (GPU) XLA HLO → LLVM → PTX pjit (NCCL) jnp.bfloat16 / jnp.float8
ONNX Runtime CUDA EP / TensorRT EP 사전 컴파일 커널 FP16/INT8 양자화
TensorRT 직접 CUDA 레이어 퓨전 + 커널 자동 선택 FP16/INT8/FP8
# PyTorch CUDA 사용 확인
python3 -c "import torch; print(torch.cuda.is_available())"
# True
python3 -c "import torch; print(torch.cuda.get_device_name(0))"
# NVIDIA H100 80GB HBM3

# PyTorch 혼합 정밀도 학습 (AMP)
# scaler = torch.amp.GradScaler()
# with torch.amp.autocast(device_type='cuda', dtype=torch.bfloat16):
#     output = model(input)
#     loss = criterion(output, target)
# scaler.scale(loss).backward()
# scaler.step(optimizer)

# TensorRT 모델 최적화 (FP16 추론)
trtexec --onnx=model.onnx \
  --fp16 \
  --workspace=4096 \
  --saveEngine=model_fp16.trt \
  --verbose
# → 레이어 퓨전, 텐서 레이아웃 최적화, Tensor Core 활용

# torch.compile — Triton 커널 자동 생성 (PyTorch 2.0+)
# model = torch.compile(model, mode='max-autotune')
# → 커널 퓨전, 메모리 접근 패턴 최적화, Triton→PTX 컴파일
CUDA 메모리 관리 주의점 (PyTorch): PyTorch는 torch.cuda.caching_allocator로 GPU 메모리를 풀링합니다. nvidia-smi에 표시되는 메모리 사용량은 실제 텐서 크기보다 클 수 있습니다. torch.cuda.memory_summary()로 실제 할당/캐시 상태를 확인하세요. OOM 발생 시 torch.cuda.empty_cache()로 캐시를 반환하거나, PYTORCH_CUDA_ALLOC_CONF=expandable_segments:True로 메모리 단편화(Fragmentation)를 줄일 수 있습니다.
Linux 커널과 CUDA 통합 포인트 요약: CUDA 애플리케이션은 Linux 커널의 여러 서브시스템과 밀접하게 연동됩니다. PCIe: GPU 디바이스 열거/BAR 매핑(lspci -vvv), DMA: dma_map_sg()를 통한 scatter-gather DMA, IOMMU: VFIO를 통한 GPU 패스스루(가상화), cgroups: devices 컨트롤러로 GPU 접근 제어(Access Control), udev: /dev/nvidia* 디바이스 노드 자동 생성, eBPF: GPU 사용량 추적(bpftrace -e 'kprobe:nvidia_ioctl { ... }'), NUMA: GPU 어피니티 설정(nvidia-smi topo -m으로 NUMA 노드 확인). 최적 성능을 위해 GPU와 같은 NUMA 노드의 CPU·메모리를 사용하는 것이 중요합니다.

OpenCL — 크로스 플랫폼 GPU 컴퓨트

OpenCL(Open Computing Language)은 Khronos Group이 표준화한 범용 병렬 컴퓨팅 API입니다. NVIDIA·AMD·Intel GPU, ARM Mali, Qualcomm Adreno 등 다양한 가속기를 동일한 코드로 활용할 수 있습니다. Linux에서는 Mesa Rusticl(신규 Rust 구현)과 Clover(레거시 C++ 구현)가 오픈소스 드라이버를 제공합니다.

실행 모델: Work-item / Work-group / NDRange

OpenCL NDRange 실행 계층 NDRange (전체 문제 공간) 예: 행렬 1024×1024 → NDRange (1024, 1024) Work-group (0,0) 공유 로컬 메모리 (Local Memory) 배리어 동기화 가능 범위 WI (0,0) WI (1,0) WI (2,0) WI (0,1) WI (1,1) WI (2,1) ... (local_size × local_size work-items) Work-group (1,0) 독립 실행, 서로 다른 CU에서 동시 실행 가능 ... (총 (1024/local_size)² 개 work-group) 메모리: Global(VRAM) → Local(SRAM, ~48KB) → Private(레지스터) → Constant(캐시)
/* OpenCL 메모리 계층 */
/*
 * Global Memory  (~수 GB VRAM): 모든 work-item 공유, 높은 지연
 * Local Memory   (~16~64 KB):   한 work-group 내 공유, 빠름
 * Private Memory (레지스터):    각 work-item 전용, 최고속
 * Constant Memory (캐시):       읽기 전용, GPU가 캐싱 최적화
 */

/* GEMM OpenCL 커널 예제 (타일링 최적화) */
__kernel void gemm_tiled(
    __global const float *A,     /* Global memory 입력 */
    __global const float *B,
    __global       float *C,
    const int N)
{
    __local float tileA[16][16];   /* Local memory 타일 */
    __local float tileB[16][16];

    int row = get_global_id(0);    /* 전체 좌표 */
    int col = get_global_id(1);
    int lrow = get_local_id(0);   /* work-group 내 좌표 */
    int lcol = get_local_id(1);

    float sum = 0.0f;

    for (int t = 0; t < N / 16; t++) {
        /* 타일을 Local memory로 협력 로딩 */
        tileA[lrow][lcol] = A[row * N + (t * 16 + lcol)];
        tileB[lrow][lcol] = B[(t * 16 + lrow) * N + col];
        barrier(CLK_LOCAL_MEM_FENCE);  /* work-group 동기화 */

        for (int k = 0; k < 16; k++)
            sum += tileA[lrow][k] * tileB[k][lcol];
        barrier(CLK_LOCAL_MEM_FENCE);
    }
    C[row * N + col] = sum;
}

Mesa Rusticl vs Clover

Mesa OpenCL 구현 비교
항목Rusticl (신규)Clover (레거시)
언어Rust + 안전성 보장C++
OpenCL 버전3.0 지원1.1~1.2
SPIR-V네이티브 지원제한적
GPU 지원AMD (RadeonSI), Intel (iris/crocus)AMD Radeon (레거시)
상태활발한 개발 중 (기본값 전환 중)유지보수 모드
# OpenCL 컴파일 파이프라인
# OpenCL C → LLVM IR → SPIR-V → GPU ISA

# clinfo로 OpenCL 드라이버 확인
clinfo | head -40
# Platform: Mesa/Rusticl or AMD ROCm or Intel OpenCL

# Rusticl 강제 활성화 (환경 변수)
RUSTICL_ENABLE=radeonsi clinfo

# 오프라인 OpenCL 커널 컴파일 (SPIR-V 생성)
clang -cl-std=CL3.0 -target spir64 -emit-llvm -c gemm.cl -o gemm.bc
llvm-spirv gemm.bc -o gemm.spv

OpenCL 호스트 워크플로우

OpenCL 호스트 프로그램은 플랫폼 탐색부터 결과 수집까지 정해진 API 순서를 따릅니다. 아래 다이어그램은 완전한 호스트 측 워크플로우를 보여 줍니다.

① 플랫폼 탐색 clGetPlatformIDs() ② 디바이스 선택 clGetDeviceIDs(CL_DEVICE_TYPE_GPU) ③ 컨텍스트 생성 clCreateContext() ④ 커맨드 큐 clCreateCommandQueueWithProperties() ⑤ 프로그램 빌드 clCreateProgramWithSource() + clBuildProgram() ⑥ 커널 생성 clCreateKernel(program, "kernel_name") ⑦ 버퍼 할당 clCreateBuffer(CL_MEM_READ_WRITE, ...) ⑧ 인수 설정 clSetKernelArg(kernel, 0, ...) ⑨ 인큐 & 실행 clEnqueueNDRangeKernel() ⑩ 결과 읽기 clEnqueueReadBuffer() + clFinish() ⑪ 리소스 해제 clReleaseKernel / clReleaseContext / ... 호스트 측 API 호출 순서 — 모든 cl* 함수는 CL_SUCCESS(0) 반환 시 성공

다음은 위 워크플로우를 완전하게 구현한 C 예제입니다. 오류 처리와 리소스 해제까지 포함한 실제 사용 가능한 코드입니다.

#include <CL/cl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define CHECK_CL(err, msg) \
    do { if ((err) != CL_SUCCESS) { \
        fprintf(stderr, "%s: %d\n", (msg), (err)); exit(1); } } while(0)

static const char *kernel_src =
    "__kernel void vadd(__global const float *a,\n"
    "                   __global const float *b,\n"
    "                   __global       float *c) {\n"
    "    int i = get_global_id(0);\n"
    "    c[i] = a[i] + b[i];\n"
    "}\n";

int main(void) {
    const int N = 1024;
    const size_t sz = N * sizeof(float);
    cl_int err;

    /* ① 플랫폼 탐색 */
    cl_platform_id platform;
    err = clGetPlatformIDs(1, &platform, NULL);
    CHECK_CL(err, "clGetPlatformIDs");

    /* ② 디바이스 선택 (GPU 우선) */
    cl_device_id device;
    err = clGetDeviceIDs(platform, CL_DEVICE_TYPE_GPU, 1, &device, NULL);
    if (err != CL_SUCCESS)
        err = clGetDeviceIDs(platform, CL_DEVICE_TYPE_CPU, 1, &device, NULL);
    CHECK_CL(err, "clGetDeviceIDs");

    /* ③ 컨텍스트 생성 */
    cl_context ctx = clCreateContext(NULL, 1, &device, NULL, NULL, &err);
    CHECK_CL(err, "clCreateContext");

    /* ④ 커맨드 큐 생성 (OpenCL 2.0+ 스타일) */
    cl_command_queue queue = clCreateCommandQueueWithProperties(
        ctx, device, NULL, &err);
    CHECK_CL(err, "clCreateCommandQueueWithProperties");

    /* ⑤ 프로그램 생성 & 빌드 */
    cl_program prog = clCreateProgramWithSource(
        ctx, 1, &kernel_src, NULL, &err);
    CHECK_CL(err, "clCreateProgramWithSource");
    err = clBuildProgram(prog, 1, &device,
        "-cl-std=CL3.0 -cl-fast-relaxed-math", NULL, NULL);
    if (err != CL_SUCCESS) {
        char log[4096];
        clGetProgramBuildInfo(prog, device, CL_PROGRAM_BUILD_LOG,
            sizeof(log), log, NULL);
        fprintf(stderr, "Build error:\n%s\n", log);
        exit(1);
    }

    /* ⑥ 커널 생성 */
    cl_kernel kernel = clCreateKernel(prog, "vadd", &err);
    CHECK_CL(err, "clCreateKernel");

    /* ⑦ 버퍼 할당 */
    cl_mem buf_a = clCreateBuffer(ctx, CL_MEM_READ_ONLY,  sz, NULL, &err);
    cl_mem buf_b = clCreateBuffer(ctx, CL_MEM_READ_ONLY,  sz, NULL, &err);
    cl_mem buf_c = clCreateBuffer(ctx, CL_MEM_WRITE_ONLY, sz, NULL, &err);

    /* 호스트 데이터 초기화 */
    float *ha = malloc(sz), *hb = malloc(sz), *hc = malloc(sz);
    for (int i = 0; i < N; i++) { ha[i] = (float)i; hb[i] = (float)(N - i); }

    /* 버퍼 쓰기 (호스트 → 디바이스) */
    err = clEnqueueWriteBuffer(queue, buf_a, CL_TRUE, 0, sz, ha, 0, NULL, NULL);
    err = clEnqueueWriteBuffer(queue, buf_b, CL_TRUE, 0, sz, hb, 0, NULL, NULL);

    /* ⑧ 커널 인수 설정 */
    clSetKernelArg(kernel, 0, sizeof(cl_mem), &buf_a);
    clSetKernelArg(kernel, 1, sizeof(cl_mem), &buf_b);
    clSetKernelArg(kernel, 2, sizeof(cl_mem), &buf_c);

    /* ⑨ 커널 실행 */
    size_t global = N, local = 64;
    err = clEnqueueNDRangeKernel(queue, kernel, 1, NULL,
        &global, &local, 0, NULL, NULL);
    CHECK_CL(err, "clEnqueueNDRangeKernel");

    /* ⑩ 결과 읽기 (디바이스 → 호스트) */
    err = clEnqueueReadBuffer(queue, buf_c, CL_TRUE, 0, sz, hc, 0, NULL, NULL);
    clFinish(queue);
    printf("c[0]=%.1f c[N-1]=%.1f\n", hc[0], hc[N - 1]);

    /* ⑪ 리소스 해제 */
    free(ha); free(hb); free(hc);
    clReleaseMemObject(buf_a); clReleaseMemObject(buf_b); clReleaseMemObject(buf_c);
    clReleaseKernel(kernel);
    clReleaseProgram(prog);
    clReleaseCommandQueue(queue);
    clReleaseContext(ctx);
    return 0;
}
주요 OpenCL 오류 코드
코드상수명원인해결 방법
0CL_SUCCESS정상
-1CL_DEVICE_NOT_FOUND지정 타입의 디바이스 없음CL_DEVICE_TYPE_ALL로 재시도
-5CL_OUT_OF_RESOURCES디바이스 메모리 부족버퍼 크기 축소 또는 분할
-6CL_OUT_OF_HOST_MEMORY호스트 메모리 부족호스트 할당량 확인
-11CL_BUILD_PROGRAM_FAILURE커널 컴파일 실패clGetProgramBuildInfo로 로그 확인
-30CL_INVALID_VALUE인수 값 잘못됨API 문서의 유효 범위 확인
-34CL_INVALID_CONTEXT컨텍스트 객체 무효클린업 순서 역전 여부 확인
-36CL_INVALID_COMMAND_QUEUE큐 객체 무효큐 생성 결과 코드 확인
-44CL_INVALID_PROGRAM_EXECUTABLE빌드 안 된 프로그램clBuildProgram 선행 호출 확인
-54CL_INVALID_WORK_GROUP_SIZElocal_size 초과CL_KERNEL_WORK_GROUP_SIZE 쿼리 후 조정

OpenCL 메모리 모델

OpenCL은 4단계 메모리 계층을 명시적으로 관리합니다. 글로벌 메모리(Global Memory)와 로컬 메모리(Local Memory) 간의 데이터 이동을 수동으로 제어하여 메모리 대역폭을 최적화합니다.

Host Memory (시스템 RAM) 호스트 배열/버퍼 malloc / new SVM 영역 (2.0+) clSVMAlloc() 지연: 최소 PCIe/DMA Write/Read Global Memory (VRAM, 수 GB) cl_mem 버퍼 clCreateBuffer() 이미지 (cl_image) clCreateImage() Constant Memory CL_MEM_READ_ONLY + __constant 지연: 높음 (~수백 ns) async_work_group _copy() Local Memory (SRAM, 16~64 KB/work-group) Work-group 0 공유 __local float tile[16][16]; barrier(CLK_LOCAL_MEM_FENCE); 동기화 필수 Work-group 1 공유 별도 독립 인스턴스 지연: 낮음 (~수십 ns) Private Memory (레지스터 파일) Work-item 전용 int i, float sum; 스필 발생 시 → Global Memory 강등 지연: 최소 (레지스터)
OpenCL 메모리 타입 비교
메모리 타입OpenCL 수식어범위 수명지연크기
Global__global모든 work-item 커널 실행 간 유지높음 (~수백 ns)수 GB (VRAM)
Local__local같은 work-group work-group 수명과 동일낮음16~64 KB
Private(기본값)단일 work-item 커널 실행 중최소 (레지스터)수십~수백 byte
Constant__constant모든 work-item (읽기 전용) 커널 실행 중낮음 (캐시)64 KB 이하

다음 예제는 명시적 버퍼 관리와 서브 버퍼(Sub-buffer) 활용법을 보여 줍니다.

/* 명시적 버퍼 관리 및 서브 버퍼 예제 */

/* 큰 버퍼 한 번 할당 후 분할하여 재사용 */
const size_t TOTAL = 64 * 1024 * 1024;  /* 64 MB */
cl_mem big_buf = clCreateBuffer(ctx,
    CL_MEM_READ_WRITE | CL_MEM_ALLOC_HOST_PTR,
    TOTAL, NULL, &err);

/* 서브 버퍼: 오프셋+크기로 큰 버퍼의 일부를 참조 */
cl_buffer_region region = {
    .origin = 0,
    .size   = TOTAL / 2
};
cl_mem sub_a = clCreateSubBuffer(big_buf,
    CL_MEM_READ_ONLY, CL_BUFFER_CREATE_TYPE_REGION,
    &region, &err);

region.origin = TOTAL / 2;
cl_mem sub_b = clCreateSubBuffer(big_buf,
    CL_MEM_READ_ONLY, CL_BUFFER_CREATE_TYPE_REGION,
    &region, &err);

/* 비동기 전송: 이벤트로 완료 대기 */
cl_event write_ev;
clEnqueueWriteBuffer(queue, sub_a, CL_FALSE, 0,
    TOTAL / 2, host_data, 0, NULL, &write_ev);

/* 커널 실행을 write_ev 완료 후로 의존성 설정 */
clEnqueueNDRangeKernel(queue, kernel, 1, NULL,
    &global, &local, 1, &write_ev, NULL);

/* 맵(Map)을 통한 제로 카피: PCIe 전송 없이 호스트에서 직접 접근 */
void *ptr = clEnqueueMapBuffer(queue, big_buf,
    CL_TRUE, CL_MAP_READ,
    0, TOTAL, 0, NULL, NULL, &err);
memcpy(host_result, ptr, TOTAL);
clEnqueueUnmapMemObject(queue, big_buf, ptr, 0, NULL, NULL);

clReleaseEvent(write_ev);
clReleaseMemObject(sub_a);
clReleaseMemObject(sub_b);
clReleaseMemObject(big_buf);
SVM (Shared Virtual Memory) — OpenCL 2.0+
OpenCL 2.0부터 도입된 공유 가상 메모리(SVM)는 호스트와 디바이스가 동일한 가상 주소 공간을 공유합니다. clSVMAlloc()으로 할당한 메모리는 호스트 포인터를 커널에 직접 전달할 수 있어 명시적 버퍼 전송이 불필요합니다. 세 가지 SVM 레벨이 있습니다: Coarse-Grained Buffer SVM(수동 동기화), Fine-Grained Buffer SVM(자동 일관성), Fine-Grained System SVM(malloc 포인터도 공유). AMD ROCm HIP의 통합 메모리(Unified Memory)와 유사한 개념입니다.

OpenCL 3.0 주요 변경사항

OpenCL 3.0(2020년 공개)은 기존 2.x 기능을 모두 선택적(Optional)으로 변경하고, OpenCL 1.2 기능만 필수(Mandatory)로 지정하여 임베디드·모바일 구현의 진입 장벽을 낮추었습니다. 런타임에서 기능 쿼리를 통해 지원 여부를 확인한 뒤 코드 경로를 선택하는 방식이 필요합니다.

OpenCL 3.0 필수 vs 선택 기능
기능3.0 상태2.0 상태쿼리 방법
OpenCL C 1.2필수(Mandatory)필수
OpenCL C 3.0 (선택적 기능 포함)선택 CL_DEVICE_OPENCL_C_FEATURES
SVM (Shared Virtual Memory)선택필수 CL_DEVICE_SVM_CAPABILITIES
파이프(Pipe) 객체선택필수 CL_DEVICE_MAX_PIPE_ARGS
디바이스 측 enqueue선택필수 CL_DEVICE_DEVICE_ENQUEUE_CAPABILITIES
원자 연산 (64비트)선택선택 CL_DEVICE_ATOMIC_MEMORY_CAPABILITIES
Work-group collective 함수선택 CL_DEVICE_WORK_GROUP_COLLECTIVE_FUNCTIONS_SUPPORT
SPIR-V 1.0 수집선택선택 CL_DEVICE_IL_VERSION
프로그램 범위 전역 변수선택필수 CL_DEVICE_GLOBAL_VARIABLE_PREFERRED_TOTAL_SIZE
비균일 Work-group선택 CL_DEVICE_NON_UNIFORM_WORK_GROUP_SUPPORT

런타임에서 OpenCL 3.0 기능 지원 여부를 쿼리하는 방법은 다음과 같습니다.

/* OpenCL 3.0 기능 쿼리 예제 */
#include <CL/cl.h>
#include <stdio.h>

void query_opencl30_features(cl_device_id device) {
    /* OpenCL C 버전 확인 */
    char version[128];
    clGetDeviceInfo(device, CL_DEVICE_OPENCL_C_VERSION,
        sizeof(version), version, NULL);
    printf("OpenCL C Version: %s\n", version);

    /* SVM 지원 여부 */
    cl_device_svm_capabilities svm_caps;
    clGetDeviceInfo(device, CL_DEVICE_SVM_CAPABILITIES,
        sizeof(svm_caps), &svm_caps, NULL);
    if (svm_caps & CL_DEVICE_SVM_COARSE_GRAIN_BUFFER)
        printf("  SVM: Coarse-Grain Buffer 지원\n");
    if (svm_caps & CL_DEVICE_SVM_FINE_GRAIN_BUFFER)
        printf("  SVM: Fine-Grain Buffer 지원\n");
    if (svm_caps & CL_DEVICE_SVM_FINE_GRAIN_SYSTEM)
        printf("  SVM: Fine-Grain System 지원\n");

    /* 원자 연산 메모리 모델 */
    cl_device_atomic_capabilities atomic_caps;
    clGetDeviceInfo(device, CL_DEVICE_ATOMIC_MEMORY_CAPABILITIES,
        sizeof(atomic_caps), &atomic_caps, NULL);
    printf("Atomic caps: 0x%lx\n", (unsigned long)atomic_caps);

    /* Work-group collective 함수 (reduce, scan) */
    cl_bool wg_collective;
    clGetDeviceInfo(device,
        CL_DEVICE_WORK_GROUP_COLLECTIVE_FUNCTIONS_SUPPORT,
        sizeof(wg_collective), &wg_collective, NULL);
    printf("Work-group collective: %s\n",
        wg_collective ? "지원" : "미지원");

    /* 디바이스 측 enqueue */
    cl_device_device_enqueue_capabilities enqueue_caps;
    clGetDeviceInfo(device,
        CL_DEVICE_DEVICE_ENQUEUE_CAPABILITIES,
        sizeof(enqueue_caps), &enqueue_caps, NULL);
    printf("Device-side enqueue: %s\n",
        enqueue_caps ? "지원" : "미지원");

    /* OpenCL C 3.0 세부 기능 목록 */
    size_t feat_size;
    clGetDeviceInfo(device, CL_DEVICE_OPENCL_C_FEATURES,
        0, NULL, &feat_size);
    size_t n = feat_size / sizeof(cl_name_version);
    cl_name_version *feats = malloc(feat_size);
    clGetDeviceInfo(device, CL_DEVICE_OPENCL_C_FEATURES,
        feat_size, feats, NULL);
    printf("OpenCL C 3.0 기능 (%zu개):\n", n);
    for (size_t i = 0; i < n; i++)
        printf("  %s (v%u.%u)\n", feats[i].name,
            CL_VERSION_MAJOR(feats[i].version),
            CL_VERSION_MINOR(feats[i].version));
    free(feats);
}
# clinfo로 OpenCL 3.0 기능 일괄 확인
clinfo --human | grep -E "Version|SVM|Atomic|Enqueue|collective"

# Mesa Rusticl OpenCL 3.0 지원 확인
RUSTICL_ENABLE=radeonsi clinfo | grep "Device Version"

# OpenCL C 3.0으로 컴파일 (선택적 기능 활성화)
clang -cl-std=CL3.0 \
      -cl-ext=+__opencl_c_int64,+__opencl_c_atomic_order_acq_rel \
      -target spir64 -emit-llvm -c kernel.cl -o kernel.bc
llvm-spirv kernel.bc -o kernel.spv
OpenCL vs Vulkan Compute — GPGPU 활용 시나리오 비교
두 API는 모두 GPU 범용 연산(GPGPU)을 지원하지만 설계 철학이 다릅니다. OpenCL은 CPU·GPU·FPGA·DSP를 아우르는 이기종 컴퓨팅에 적합하며, 런타임 커널 컴파일(온라인 컴파일)을 지원하여 호환성 범위가 넓습니다. Vulkan Compute는 그래픽 파이프라인과 동일한 커맨드 버퍼·동기화 메커니즘을 사용하여 그래픽·컴퓨트 혼합 작업에 유리하며, SPIR-V 사전 컴파일로 드라이버 컴파일 오버헤드가 없습니다. 순수 GPGPU 워크로드(ML 추론, 과학 계산)에는 OpenCL, 게임 엔진이나 렌더링 파이프라인과 연동하는 컴퓨트에는 Vulkan Compute를 선택하는 것이 일반적입니다. ROCm 환경에서는 HIP이 사실상 표준으로 자리잡고 있습니다.

Vulkan Compute 파이프라인

Vulkan은 Khronos Group이 표준화한 저수준 크로스 플랫폼 그래픽/컴퓨트 API입니다. Compute Pipeline을 통해 Graphics Pipeline 없이 GPGPU 연산을 수행할 수 있으며, 래스터라이저·프래그먼트 셰이더 대신 Compute Shader만으로 구성됩니다. Vulkan 컴퓨트는 CUDA와 달리 벤더 중립적이어서 NVIDIA, AMD, Intel, Qualcomm, ARM Mali 등 모든 Vulkan 호환 GPU에서 동일한 SPIR-V 셰이더를 실행할 수 있습니다.

Linux에서 Vulkan 드라이버는 크게 두 계열로 나뉩니다: Mesa 오픈소스 드라이버(RadV, ANV/Hasvk, PanVK, Turnip 등)와 벤더 독점 드라이버(NVIDIA, AMDGPU-PRO). 모든 Vulkan 드라이버는 DRM 서브시스템의 /dev/dri/renderD* 노드를 통해 GPU에 접근하므로, root 권한 없이도 컴퓨트 작업을 수행할 수 있습니다(render node 그룹 소속 필요).

Vulkan Compute vs CUDA/OpenCL: Vulkan Compute는 그래픽 API에 포함된 컴퓨트 기능이지만, OpenCL과 동등한 GPGPU 능력을 제공합니다. 장점: 크로스 벤더(NVIDIA+AMD+Intel+모바일), 명시적 메모리/동기화 제어, 그래픽과 컴퓨트 인터리빙. 단점: 보일러플레이트 코드가 많음(초기화 ~200줄), Tensor Core 같은 벤더 전용 가속 부재, CUDA 수준의 디버깅 도구 미비. ML 추론 엔진(ONNX Runtime, ncnn, MNN)에서 모바일/임베디드 GPU 백엔드로 널리 사용됩니다.

Vulkan Linux 드라이버 스택

Vulkan 애플리케이션이 GPU에 접근하는 전체 소프트웨어 스택은 다음과 같습니다. Vulkan 로더(libvulkan.so)가 ICD(Installable Client Driver)를 검색하여 적절한 드라이버를 동적 로드하고, 드라이버는 DRM 렌더 노드 ioctl로 GPU와 통신합니다.

Vulkan Linux 드라이버 스택 Vulkan 애플리케이션 (Compute/Graphics) Validation Layers (VK_LAYER_KHRONOS_validation) — 디버그 시에만 활성화 Vulkan 로더 (libvulkan.so.1) ICD 검색: /usr/share/vulkan/icd.d/*.json Mesa RadV (AMD) libvulkan_radeon.so Mesa ANV (Intel) libvulkan_intel.so NVIDIA (독점) libGLX_nvidia.so PanVK (Mali) Turnip (Adreno) V3DV (RPi) NVK (NVIDIA 오픈) lavapipe (SW) 사용자 공간 ↑ | ↓ 커널 공간 amdgpu (DRM) i915 / xe (DRM) nvidia-drm (독점) /dev/dri/renderD128 /dev/dri/renderD128 /dev/dri/renderD128 AMD GPU Intel GPU NVIDIA GPU SPIR-V 바이트코드 → 각 드라이버가 대상 GPU ISA(GCN/RDNA/Xe/SASS)로 최종 컴파일
Linux Vulkan 드라이버 비교
드라이버GPU출처Vulkan 버전Compute 지원DRM 드라이버
RadVAMD GCN 이후Mesa (오픈소스)1.3완전amdgpu
ANVIntel Gen8 이후Mesa (오픈소스)1.3완전i915 / xe
NVKNVIDIA Turing 이후Mesa (오픈소스)1.3완전nouveau (GSP)
PanVKARM Mali (Valhall)Mesa (오픈소스)1.0~1.1부분적panfrost
TurnipQualcomm AdrenoMesa (오픈소스)1.3완전msm
V3DVBroadcom VideoCore VIMesa (오픈소스)1.2부분적v3d
lavapipeCPU (소프트웨어)Mesa (오픈소스)1.3완전— (CPU 실행)
NVIDIA 독점NVIDIA Kepler 이후독점1.3완전nvidia-drm
AMDGPU-PROAMD (프로)반독점1.3완전amdgpu
# Vulkan 드라이버 정보 확인
vulkaninfo --summary
# GPU0: AMD Radeon RX 7900 XTX (RADV NAVI31)
#   apiVersion    = 1.3.274
#   driverVersion = 24.0.99
#   driverID      = DRIVER_ID_MESA_RADV

# ICD 파일 확인 (로더가 검색하는 JSON 매니페스트)
ls /usr/share/vulkan/icd.d/
# radeon_icd.x86_64.json  intel_icd.x86_64.json  nvidia_icd.json

# 특정 드라이버 강제 선택 (다중 GPU 시스템)
VK_ICD_FILENAMES=/usr/share/vulkan/icd.d/radeon_icd.x86_64.json ./my_app

# Vulkan 레이어 확인
vulkaninfo --show-layer-list 2>&1 | head -20

# Mesa 디버그: NIR→ISA 컴파일 덤프
RADV_DEBUG=preoptir,shaders ./my_vulkan_app 2>shader_dump.txt
NVK — Mesa의 NVIDIA Vulkan 드라이버: NVK는 Mesa에 포함된 오픈소스 NVIDIA Vulkan 드라이버로, Turing 이후 GPU에서 GSP 펌웨어와 nouveau 커널 드라이버를 사용합니다. 2024년에 Vulkan 1.3 적합성을 획득했으며, NVIDIA 독점 드라이버 없이도 Vulkan Compute를 실행할 수 있습니다. MESA_VK_DEVICE_SELECT=nouveau로 NVK를 명시적으로 선택합니다.

Compute Shader와 파이프라인 구성

Vulkan Compute Pipeline의 구성 과정은 크게 6단계로 나뉩니다: GLSL/HLSL 작성 → SPIR-V 컴파일 → VkDevice/VkQueue 생성 → 리소스 할당(버퍼, 디스크립터) → 파이프라인 생성 → 커맨드 기록 및 제출.

Vulkan Compute Pipeline — 단계별 흐름 ① GLSL Compute Shader 작성 layout(local_size_x=256) in; void main() { ... } ② SPIR-V 컴파일 glslc shader.comp -o shader.spv ③ VkInstance → VkPhysicalDevice → VkDevice + VkQueue(Compute) Compute 가능 큐 패밀리 선택: VK_QUEUE_COMPUTE_BIT (그래픽 큐와 독립 가능) ④ 리소스 할당 VkBuffer + VkDeviceMemory 할당 vkMapMemory → CPU에서 데이터 기록 DescriptorSet에 버퍼 바인딩 ⑤ 파이프라인 생성 VkShaderModule (SPIR-V 로드) VkPipelineLayout (Push Const + DS) vkCreateComputePipelines() ⑥ Command Buffer 기록 및 제출 vkBeginCommandBuffer() vkCmdBindPipeline(COMPUTE) vkCmdBindDescriptorSets() vkCmdPushConstants() vkCmdDispatch(gx, gy, gz) vkCmdPipelineBarrier() vkEndCommandBuffer() → vkQueueSubmit() → vkWaitForFences() DRM render node ioctl → GPU 하드웨어 실행 드라이버: SPIR-V → NIR → GPU ISA 최종 컴파일, 링 버퍼 삽입 dma_fence 시그널 → VkFence 해제 → CPU에서 결과 읽기
/* GLSL Compute Shader — 행렬 곱셈 (공유 메모리 타일링) */
/* matmul.comp → glslc matmul.comp -o matmul.spv */

#version 450

#define TILE_SIZE 16
layout(local_size_x = TILE_SIZE, local_size_y = TILE_SIZE) in;

layout(set = 0, binding = 0) readonly buffer MatA { float A[]; };
layout(set = 0, binding = 1) readonly buffer MatB { float B[]; };
layout(set = 0, binding = 2)          buffer MatC { float C[]; };

layout(push_constant) uniform PushConst { uint N; } pc;

/* 공유 메모리 — 워크그룹 내 invocation 간 공유 */
shared float tileA[TILE_SIZE][TILE_SIZE];
shared float tileB[TILE_SIZE][TILE_SIZE];

void main() {
    uint row = gl_GlobalInvocationID.y;
    uint col = gl_GlobalInvocationID.x;
    uint lr  = gl_LocalInvocationID.y;
    uint lc  = gl_LocalInvocationID.x;

    float sum = 0.0;
    uint numTiles = (pc.N + TILE_SIZE - 1) / TILE_SIZE;

    for (uint t = 0; t < numTiles; t++) {
        tileA[lr][lc] = A[row * pc.N + t * TILE_SIZE + lc];
        tileB[lr][lc] = B[(t * TILE_SIZE + lr) * pc.N + col];
        barrier();  /* 워크그룹 동기화 (CUDA __syncthreads 대응) */

        for (uint k = 0; k < TILE_SIZE; k++)
            sum += tileA[lr][k] * tileB[k][lc];
        barrier();
    }
    C[row * pc.N + col] = sum;
}

/* vkCmdDispatch(N/TILE_SIZE, N/TILE_SIZE, 1) 으로 실행
 * → work-group 수: (N/16)² 개
 * → work-group 내 invocation: 16×16 = 256개 */
CUDA ↔ Vulkan Compute 용어 대응
CUDAVulkan / GLSL설명
GridDispatch (vkCmdDispatch)전체 실행 범위
BlockWork-group (local_size)공유 메모리/배리어 범위
ThreadInvocation개별 실행 단위
blockIdxgl_WorkGroupID워크그룹 ID
threadIdxgl_LocalInvocationID로컬 인덱스
blockIdx*blockDim+threadIdxgl_GlobalInvocationID글로벌 인덱스
__shared__shared워크그룹 내 공유 메모리
__syncthreads()barrier()워크그룹 동기화
cudaMallocvkAllocateMemory + vkBindBufferMemory디바이스 메모리 할당
cudaMemcpyvkMapMemory / vkCmdCopyBuffer호스트↔디바이스 전송
CUDA StreamVkQueue + VkFence/VkSemaphore비동기 실행/동기화

SPIR-V 셰이더 컴파일

SPIR-V(Standard Portable Intermediate Representation)는 Vulkan의 셰이더 바이트코드 형식입니다. GLSL·HLSL·Slang 등 고급 셰이딩 언어에서 SPIR-V로 컴파일한 뒤 Vulkan 드라이버에 전달하면, 드라이버가 대상 GPU의 네이티브 ISA(GCN/RDNA, Xe, SASS 등)로 최종 번역합니다.

SPIR-V 셰이더 컴파일 파이프라인 GLSL HLSL Slang Rust (rust-gpu) glslc/glslang DXC slangc spirv-builder SPIR-V (.spv) 표준 중간 표현 바이트코드 Mesa NIR SPIR-V → NIR → ACO/ISA Intel 컴파일러 SPIR-V → NIR → Xe ISA NVIDIA 독점 SPIR-V → PTX → SASS RDNA/GCN ISA Xe/Gen EU ISA SASS
# GLSL → SPIR-V 컴파일
glslc -fshader-stage=compute shader.comp -o shader.spv

# 최적화 옵션
glslc -O shader.comp -o shader.spv                    # 기본 최적화
glslc --target-env=vulkan1.3 shader.comp -o shader.spv # Vulkan 1.3 타겟

# SPIR-V 디스어셈블 (spirv-tools)
spirv-dis shader.spv
# OpCapability Shader
# OpMemoryModel Logical GLSL450
# OpEntryPoint GLCompute %main "main" %gl_GlobalInvocationID
# OpExecutionMode %main LocalSize 256 1 1

# SPIR-V 유효성 검사
spirv-val shader.spv

# SPIR-V 최적화 (size/performance)
spirv-opt -O shader.spv -o shader_opt.spv

# HLSL → SPIR-V (DirectX Shader Compiler)
dxc -spirv -T cs_6_0 -E CSMain shader.hlsl -Fo shader.spv

# spirv-cross: SPIR-V → GLSL/HLSL/MSL 역변환
spirv-cross --output shader_back.glsl shader.spv
SPIR-V 컴파일러 비교
컴파일러입력출력특징
glslc (shaderc)GLSLSPIR-VGoogle 개발, Vulkan SDK 포함, glslang 기반
glslangValidatorGLSLSPIR-VKhronos 공식 참조 컴파일러
DXCHLSLSPIR-V / DXILMicrosoft 개발, SM 6.x 지원, HLSL 2021
slangcSlangSPIR-V / DXIL / PTXNVIDIA 개발, 자동 미분, 제네릭
nagaWGSL / GLSL / SPIR-VSPIR-V / MSL / GLSL / HLSLRust wgpu 생태계, 다중 백엔드

Vulkan 메모리 관리

Vulkan은 명시적 메모리 관리를 요구합니다. OpenGL이 드라이버에 위임하던 메모리 할당/바인딩을 애플리케이션이 직접 제어하며, 이를 통해 메모리 레이아웃과 전송을 최적화할 수 있습니다. GPU 메모리는 VkPhysicalDeviceMemoryProperties로 조회한 메모리 타입힙(Heap) 정보를 기반으로 할당합니다.

Vulkan 메모리 타입과 힙 아키텍처 Heap 0: DEVICE_LOCAL (VRAM) GPU 전용 고속 메모리 (8~24 GB) Type 0: DEVICE_LOCAL GPU만 접근, 최고 대역폭 — Storage Buffer, Image Type 1: DEVICE_LOCAL | HOST_VISIBLE GPU+CPU 접근 (BAR, 256MB) — Uniform, 작은 전송 Type 2: DEVICE_LOCAL | HOST_VISIBLE | HOST_COHERENT Heap 1: HOST_VISIBLE (시스템 RAM) CPU 메모리, GPU DMA 접근 (~32 GB) Type 3: HOST_VISIBLE | HOST_COHERENT Staging Buffer (H→D 전송용, vkMapMemory 가능) Type 4: HOST_VISIBLE | HOST_CACHED D→H 읽기 최적화 (CPU 캐시), GPU 쓰기 후 읽기 vkCmdCopyBuffer 일반적 메모리 사용 패턴 패턴 1: Staging 전송 HOST→Staging→vkCmdCopyBuffer→DEVICE 패턴 2: BAR 직접 쓰기 vkMapMemory → DEVICE_LOCAL|HOST_VISIBLE 패턴 3: Readback DEVICE→vkCmdCopyBuffer→HOST_CACHED→CPU읽기 VMA (Vulkan Memory Allocator): 서브할당 풀링으로 할당 오버헤드 최소화 — 프로덕션 필수 Resizable BAR (SAM): DEVICE_LOCAL | HOST_VISIBLE 힙 크기를 VRAM 전체로 확장 (BIOS 설정)
Vulkan 메모리 속성 플래그
플래그의미성능 특성
DEVICE_LOCALGPU 로컬 메모리 (VRAM)GPU 접근 최고 대역폭, CPU 직접 접근 불가(일반적)
HOST_VISIBLECPU에서 vkMapMemory 가능CPU 쓰기 가능, GPU 대역폭은 PCIe 제한
HOST_COHERENTCPU 쓰기 즉시 GPU에 가시적vkFlushMappedMemoryRanges 불필요
HOST_CACHEDCPU 읽기 캐시CPU→GPU 쓰기는 느림, GPU→CPU 읽기 최적화
LAZILY_ALLOCATED지연 할당 (타일 기반 GPU)모바일 GPU의 on-chip 메모리 (실제 VRAM 미사용)
메모리 할당 제한: Vulkan 명세는 디바이스별 maxMemoryAllocationCount를 보장하며, 일반적으로 4096개입니다. 작은 버퍼마다 vkAllocateMemory를 호출하면 이 제한에 빠르게 도달합니다. VMA(Vulkan Memory Allocator) 같은 서브할당자로 하나의 큰 할당에서 여러 버퍼를 서브할당하세요. VMA는 AMD가 개발한 오픈소스 라이브러리로, vmaCreateBuffer() 한 줄로 버퍼 생성+메모리 할당+바인딩을 처리합니다.

Vulkan 동기화 모델

Vulkan의 동기화는 완전히 명시적입니다. 드라이버가 자동으로 동기화하는 OpenGL과 달리, Vulkan에서는 모든 리소스 의존성을 개발자가 직접 선언해야 합니다. 잘못된 동기화는 데이터 레이스, 렌더링 아티팩트, 크래시의 주요 원인입니다.

Vulkan 동기화 프리미티브 비교
프리미티브범위세분화사용 사례
Pipeline Barrier 커맨드 버퍼 내부 파이프라인 스테이지 + 메모리 접근 Compute→Compute, Compute→Transfer 의존성
VkEvent 커맨드 버퍼 내부 (분할 배리어) set/wait 분리 더 세밀한 의존성 (두 지점 사이)
VkSemaphore 큐 간 (GPU↔GPU) 바이너리 또는 타임라인 Compute Queue→Graphics Queue, 멀티 큐
VkFence CPU↔GPU 제출 단위 CPU에서 GPU 작업 완료 대기
Timeline Semaphore 큐 간 / CPU↔GPU 단조 증가 카운터 파이프라인 스케줄링, CPU/GPU 혼합 의존성
/* Vulkan Pipeline Barrier — Compute 셰이더 결과 읽기 전 동기화 */
VkBufferMemoryBarrier barrier = {
    .sType               = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER,
    .srcAccessMask       = VK_ACCESS_SHADER_WRITE_BIT,   /* Compute 셰이더 쓰기 */
    .dstAccessMask       = VK_ACCESS_HOST_READ_BIT,      /* CPU 읽기 */
    .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
    .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
    .buffer              = outputBuffer,
    .offset              = 0,
    .size                = VK_WHOLE_SIZE,
};

vkCmdPipelineBarrier(cmdBuf,
    VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,  /* src: Compute 완료 후 */
    VK_PIPELINE_STAGE_HOST_BIT,            /* dst: CPU 접근 전 */
    0,                                     /* 플래그 */
    0, NULL,                               /* 메모리 배리어 */
    1, &barrier,                            /* 버퍼 배리어 */
    0, NULL                                /* 이미지 배리어 */
);

/* Vulkan 1.3 Synchronization2 — 더 직관적인 API */
VkBufferMemoryBarrier2 barrier2 = {
    .sType         = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER_2,
    .srcStageMask  = VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT,
    .srcAccessMask = VK_ACCESS_2_SHADER_STORAGE_WRITE_BIT,
    .dstStageMask  = VK_PIPELINE_STAGE_2_HOST_BIT,
    .dstAccessMask = VK_ACCESS_2_HOST_READ_BIT,
    .buffer        = outputBuffer,
    .size          = VK_WHOLE_SIZE,
};
VkDependencyInfo depInfo = {
    .sType                    = VK_STRUCTURE_TYPE_DEPENDENCY_INFO,
    .bufferMemoryBarrierCount = 1,
    .pBufferMemoryBarriers    = &barrier2,
};
vkCmdPipelineBarrier2(cmdBuf, &depInfo);
Synchronization2 (Vulkan 1.3 코어): 기존 vkCmdPipelineBarrier는 src/dst 스테이지와 접근 마스크를 배리어 호출과 개별 배리어 구조체에 분산시켜 혼란스러웠습니다. VK_KHR_synchronization2(1.3 코어)는 각 배리어 구조체에 스테이지+접근 마스크를 함께 포함하여 가독성이 크게 향상됩니다. 신규 코드에서는 항상 Synchronization2를 사용하세요.

Vulkan Compute 호스트 코드 작성

Vulkan Compute의 전체 호스트 코드(C)는 초기화 → 리소스 할당 → 파이프라인 생성 → 디스패치 → 정리 순서로 구성됩니다. 아래는 GLSL Compute Shader로 벡터 덧셈을 수행하는 최소 완전 예제입니다.

/* Vulkan Compute 최소 예제 — 벡터 덧셈 (핵심 부분만 발췌) */
/* 전체 코드는 ~300줄이지만, 핵심 흐름만 표시 */

/* 1. Instance + Physical Device + Logical Device */
VkInstance instance;
vkCreateInstance(&instInfo, NULL, &instance);
VkPhysicalDevice physDev;
vkEnumeratePhysicalDevices(instance, &count, &physDev);

/* Compute 큐 패밀리 검색 */
uint32_t computeQueueFamily = UINT32_MAX;
for (uint32_t i = 0; i < queueFamilyCount; i++) {
    if (queueFamilies[i].queueFlags & VK_QUEUE_COMPUTE_BIT)
        computeQueueFamily = i;
}

VkDevice device;
vkCreateDevice(physDev, &devInfo, NULL, &device);
VkQueue computeQueue;
vkGetDeviceQueue(device, computeQueueFamily, 0, &computeQueue);

/* 2. 버퍼 생성 + 메모리 할당 (입력 A, B / 출력 C) */
VkBuffer bufA, bufB, bufC;
VkDeviceMemory memA, memB, memC;
createBuffer(device, size, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, &bufA, &memA);
createBuffer(device, size, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, &bufB, &memB);
createBuffer(device, size, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, &bufC, &memC);

/* 호스트 데이터 업로드 (HOST_VISIBLE 메모리의 경우) */
float *mapped;
vkMapMemory(device, memA, 0, size, 0, (void**)&mapped);
memcpy(mapped, hostDataA, size);
vkUnmapMemory(device, memA);

/* 3. Descriptor Set Layout + Pipeline Layout */
VkDescriptorSetLayoutBinding bindings[3] = {
    { 0, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_COMPUTE_BIT },
    { 1, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_COMPUTE_BIT },
    { 2, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_COMPUTE_BIT },
};
/* ... DescriptorSetLayout, PipelineLayout 생성 생략 ... */

/* 4. Compute Pipeline 생성 */
VkShaderModule shaderModule;
/* shader.spv 파일 로드 → vkCreateShaderModule() */
VkComputePipelineCreateInfo pipelineInfo = {
    .sType  = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO,
    .stage  = {
        .sType  = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
        .stage  = VK_SHADER_STAGE_COMPUTE_BIT,
        .module = shaderModule,
        .pName  = "main",
    },
    .layout = pipelineLayout,
};
VkPipeline pipeline;
vkCreateComputePipelines(device, NULL, 1, &pipelineInfo, NULL, &pipeline);

/* 5. Command Buffer 기록 */
VkCommandBuffer cmdBuf;
vkBeginCommandBuffer(cmdBuf, &beginInfo);
vkCmdBindPipeline(cmdBuf, VK_PIPELINE_BIND_POINT_COMPUTE, pipeline);
vkCmdBindDescriptorSets(cmdBuf, VK_PIPELINE_BIND_POINT_COMPUTE,
    pipelineLayout, 0, 1, &descriptorSet, 0, NULL);
vkCmdDispatch(cmdBuf, (N + 255) / 256, 1, 1);  /* 디스패치! */
vkEndCommandBuffer(cmdBuf);

/* 6. 제출 + 완료 대기 */
VkFence fence;
vkCreateFence(device, &fenceInfo, NULL, &fence);
VkSubmitInfo submitInfo = { .commandBufferCount = 1, .pCommandBuffers = &cmdBuf };
vkQueueSubmit(computeQueue, 1, &submitInfo, fence);
vkWaitForFences(device, 1, &fence, VK_TRUE, UINT64_MAX);

/* 7. 결과 읽기 */
vkMapMemory(device, memC, 0, size, 0, (void**)&mapped);
memcpy(hostResult, mapped, size);
vkUnmapMemory(device, memC);

Subgroup 연산과 고급 Compute 기능

Vulkan 1.1에서 도입된 Subgroup은 CUDA의 Warp에 대응하는 개념으로, GPU 하드웨어가 동시에 SIMD 실행하는 invocation 그룹입니다. Subgroup 크기는 GPU마다 다르며(NVIDIA: 32, AMD: 64/32, Intel: 8~32), gl_SubgroupSizesubgroupBallot() 등 내장 함수로 워프 수준 최적화를 수행합니다.

Vulkan Compute 고급 기능
기능Vulkan 버전/확장CUDA 대응설명
Subgroup Operations1.1 코어Warp Intrinsics셔플, 투표, 리덕션 — 공유메모리 없이 워프 내 통신
Push Constants1.0 코어커널 인자커맨드 버퍼에 인라인 상수 (최대 128~256B)
Specialization Constants1.0 코어템플릿 파라미터파이프라인 생성 시 SPIR-V 상수 주입 (JIT 최적화)
Descriptor Indexing1.2 코어런타임 배열 인덱싱으로 바인딩리스 리소스 접근
Buffer Device Address1.2 코어GPU 포인터GPU 메모리 주소를 정수로 전달 (포인터 산술)
Timeline Semaphore1.2 코어CUDA Event단조 증가 카운터로 세밀한 CPU/GPU 동기화
Cooperative MatrixVK_KHR_cooperative_matrixWMMA / MMATensor Core/Matrix Core 접근 (행렬 FMA)
Mesh ShadersVK_EXT_mesh_shaderCompute-like 메시 처리 (그래픽 파이프라인)
Shader Int8/Float161.2 코어half/char저정밀도 연산, ML 추론 가속
/* Subgroup 리덕션 — 공유 메모리 없이 워크그룹 합 계산 */
#version 450
#extension GL_KHR_shader_subgroup_arithmetic : enable

layout(local_size_x = 256) in;

layout(set = 0, binding = 0) readonly buffer Input { float data[]; };
layout(set = 0, binding = 1)          buffer Output { float result[]; };

shared float partialSums[8];  /* 256/32 = 8 subgroups */

void main() {
    uint idx = gl_GlobalInvocationID.x;
    float val = data[idx];

    /* 1단계: Subgroup 내 합 (Warp 리덕션, 레지스터 수준) */
    float subgroupSum = subgroupAdd(val);

    /* 2단계: 각 Subgroup의 lane 0이 부분합 저장 */
    if (subgroupElect())
        partialSums[gl_SubgroupID] = subgroupSum;

    barrier();

    /* 3단계: 첫 Subgroup이 최종 합 계산 */
    if (gl_SubgroupID == 0) {
        float v = (gl_SubgroupInvocationID < gl_NumSubgroups)
                  ? partialSums[gl_SubgroupInvocationID] : 0.0;
        float total = subgroupAdd(v);
        if (subgroupElect())
            result[gl_WorkGroupID.x] = total;
    }
}
/* Specialization Constants — 파이프라인 생성 시 상수 주입 */
#version 450

/* 컴파일 시 값이 결정되지 않고, VkSpecializationInfo로 주입 */
layout(constant_id = 0) const uint BLOCK_SIZE = 256;
layout(constant_id = 1) const uint ALGORITHM  = 0;

layout(local_size_x_id = 0) in;  /* local_size를 상수로 지정 */

void main() {
    if (ALGORITHM == 0) {
        /* 알고리즘 A — 상수 접기로 데드 코드 제거됨 */
    } else {
        /* 알고리즘 B */
    }
}
VK_KHR_cooperative_matrix — Tensor Core/Matrix Core 접근: 이 확장은 CUDA의 WMMA API에 대응하며, Vulkan 셰이더에서 하드웨어 행렬 연산 유닛에 접근합니다. coopMatLoad, coopMatMulAdd, coopMatStore로 MMA(Matrix Multiply-Accumulate)를 수행합니다. NVIDIA(Volta+), AMD(RDNA3/CDNA), Intel(Xe-HPC)에서 지원되며, 크로스 벤더 ML 추론 가속에 유용합니다. 다만 아직 확정 확장(KHR)은 2024년 확정되었으며, 드라이버 지원 범위는 vulkaninfo로 확인하세요.

Vulkan Compute 디버깅

Vulkan은 드라이버가 에러 검증을 하지 않는 저수준 API이므로, Validation Layers를 활성화하여 API 오용을 검출하는 것이 필수적입니다.

Vulkan 디버깅/프로파일링 도구
도구용도핵심 기능
VK_LAYER_KHRONOS_validationAPI 유효성 검사잘못된 파라미터, 동기화 오류, 메모리 누수 탐지
VK_LAYER_KHRONOS_synchronization2동기화 검증배리어 누락, 레이스 컨디션 경고
RenderDocGPU 캡처/리플레이Compute Dispatch 상태 검사, 버퍼 내용 확인
Nsight GraphicsNVIDIA GPU 프로파일링SM 활용률, 메모리 대역폭, 워프 분석
Radeon GPU Profiler (RGP)AMD GPU 프로파일링파이프라인 타임라인, 웨이브 오큐펀시
GPA (Intel)Intel GPU 프로파일링EU 활용률, 메모리 대역폭
vulkaninfoGPU 기능 조회확장, 제한, 메모리 타입, 큐 패밀리
spirv-valSPIR-V 검증셰이더 바이트코드 유효성 검사
# Validation Layer 활성화 (환경 변수)
VK_INSTANCE_LAYERS=VK_LAYER_KHRONOS_validation ./my_vulkan_app

# GPU 선택 (다중 GPU 시스템)
VK_ICD_FILENAMES=/usr/share/vulkan/icd.d/radeon_icd.x86_64.json ./my_app

# Mesa 드라이버 디버그 (RadV)
RADV_DEBUG=info,preoptir ./my_vulkan_app  # 드라이버 정보 + 셰이더 IR 덤프
RADV_PERFTEST=nosam ./my_vulkan_app       # 성능 실험 플래그

# Mesa 드라이버 디버그 (ANV — Intel)
INTEL_DEBUG=cs ./my_vulkan_app            # Compute Shader 컴파일 로그

# RenderDoc CLI 캡처
renderdoccmd capture --wait-for-exit ./my_vulkan_app
# → .rdc 파일 생성 → GUI에서 Compute Dispatch 분석

# vulkaninfo — 디바이스 Compute 제한 확인
vulkaninfo 2>/dev/null | grep -A5 "maxComputeWorkGroupSize"
# maxComputeWorkGroupSize[0]       = 1024
# maxComputeWorkGroupSize[1]       = 1024
# maxComputeWorkGroupSize[2]       = 64
# maxComputeWorkGroupInvocations   = 1024
# maxComputeSharedMemorySize       = 49152  (48 KB)
Vulkan Compute와 DRM render node: Vulkan compute 제출은 내부적으로 /dev/dri/renderD128의 ioctl로 변환됩니다. DRM 드라이버가 GPU 커맨드를 검증(command validation)한 뒤 하드웨어 링 버퍼(ring buffer)에 삽입합니다. GPU fence 완료 시 dma_fence가 시그널되어 VkFence가 해제됩니다. Mesa 드라이버의 SPIR-V 처리 흐름: SPIR-V → spirv_to_nir() → NIR 최적화 패스 → 백엔드 ISA 생성(AMD: ACO 컴파일러 → GCN/RDNA ISA, Intel: Brw 컴파일러 → EU ISA).

Vulkan Compute 실전 사용 사례

Vulkan Compute는 크로스 플랫폼 GPU 가속이 필요하면서 벤더 종속성을 피하고 싶은 시나리오에서 사용됩니다. 특히 모바일/임베디드 ML 추론, 영상 처리, 게임 엔진 물리/파티클, 그래픽 후처리 분야에서 활발합니다.

Vulkan Compute 주요 사용 사례와 라이브러리
분야대표 프로젝트왜 Vulkan Compute?
ML 추론 (모바일)ncnn, MNN, ONNX RuntimeARM Mali/Adreno에서 CUDA 불가, OpenCL 제한적
ML 추론 (데스크톱)llama.cpp (ggml), KomputeNVIDIA/AMD/Intel 모든 GPU에서 LLM 추론
영상/이미지 처리FFmpeg (Vulkan 필터), darktable하드웨어 디코더(VkVideoDecodeKHR)와 통합
과학 계산VkFFT, VkCV크로스 벤더 FFT, 이미지 처리
게임 엔진Godot, Unreal Engine 5그래픽과 컴퓨트 동일 API, 큐 오버랩
UI 렌더링Zed (GPU UI), FlutterGPU 가속 텍스트/레이아웃 연산
블록체인GPU 마이너크로스 벤더 해시(Hash) 연산
GPU 컴퓨트 API 종합 비교
항목CUDAVulkan ComputeOpenCLROCm/HIPoneAPI/SYCL
벤더NVIDIA 전용크로스 벤더크로스 벤더AMD (+ NVIDIA via HIP)Intel (+ 크로스)
추상화 수준중간매우 낮음중간중간 (CUDA 호환)높음 (C++17)
셰이더/커널CUDA C (.cu)SPIR-V (GLSL/HLSL)OpenCL C / SPIR-VHIP C++ (.hip)SYCL/DPC++
Tensor CoreWMMA, MMA PTXVK_KHR_cooperative_matrixMatrix Core (MFMA)XMX
디버깅 도구cuda-gdb, NSightValidation Layer, RenderDocrocgdb, rocprofoneAPI debugger
AI 생태계cuDNN, TensorRT, NCCLncnn, KomputeMIOpen, RCCLoneDNN
보일러플레이트~20줄~300줄~100줄~20줄~30줄
모바일 GPU불가Mali, Adreno, PowerVR제한적불가불가
Kompute — 경량 Vulkan Compute 프레임워크: Vulkan Compute의 ~300줄 보일러플레이트를 ~10줄로 줄여주는 C++/Python 라이브러리입니다. kp::Manager가 디바이스 초기화, 메모리 할당, 파이프라인 생성을 자동화하여, CUDA 수준의 간결함으로 크로스 벤더 GPU 컴퓨트를 작성할 수 있습니다. llama.cpp의 Vulkan 백엔드(ggml-vulkan)도 유사한 추상화를 내부적으로 구현하여, NVIDIA/AMD/Intel GPU에서 LLM 추론을 수행합니다.

ROCm / HIP — AMD GPU 컴퓨트

ROCm(Radeon Open Compute)은 AMD의 오픈소스 GPU 컴퓨트 플랫폼입니다. HIP(Heterogeneous Interface for Portability)은 CUDA와 호환되는 API를 제공해 CUDA 코드를 AMD GPU용으로 이식하기 용이합니다. 커널 레벨에서는 /dev/kfd(KFD — Kernel Fusion Driver)를 통해 GPU와 통신하며, amdgpu DRM 드라이버가 GFX/SDMA/VCN 등 IP 블록을 관리합니다.

ROCm 소프트웨어 스택 개요

ROCm 스택은 사용자 애플리케이션부터 GPU 하드웨어까지 여러 계층으로 구성됩니다. HIP 경로와 OpenCL 경로가 공통 런타임(ROCr/HSA) 위에서 수렴하며, 최하단에서 /dev/kfd ioctl을 통해 KFD 커널 드라이버와 통신합니다.

ROCm 전체 소프트웨어 스택 HIP 경로 PyTorch / TensorFlow / MIOpen OpenCL 경로 ROCm OpenCL Runtime 컴파일러 / 툴체인 hipcc / ROCm LLVM / ROCclr HIP Runtime (amdhip64) hipMalloc / hipMemcpy / hipLaunchKernelGGL / hipStream ROCr — HSA Runtime (libhsa-runtime64) HSA Queue / Signal / Memory / Agent 추상화 Thunk — libhsakmt /dev/kfd ioctl 래퍼 (사용자 공간 ↔ 커널 인터페이스) KFD — Kernel Fusion Driver (/dev/kfd) 컴퓨트 큐 스케줄링 / 메모리 매핑 / 이벤트 처리 amdgpu DRM 드라이버 → GPU 하드웨어 (GFX / SDMA / VCN) ROCm 개발 도구 rocprof — 성능 프로파일링 roctracer — API 추적 rocgdb — GPU 디버거 rocm-smi — GPU 상태 모니터 hipify — CUDA → HIP 변환 MIOpen — DNN 라이브러리 rocBLAS / rocFFT RCCL — 집합 통신 ── 커널 공간 ── KFD (amdgpu 서브시스템) TTM 메모리 관리자 GEM 버퍼 오브젝트 ── 하드웨어 ── RDNA / CDNA GPU XGMI / Infinity Fabric

각 계층의 역할은 다음과 같습니다. HIP Runtimeamdhip64 라이브러리로 제공되며, CUDA와 유사한 API를 AMD GPU에서 실행할 수 있도록 변환합니다. ROCr(HSA Runtime)은 Heterogeneous System Architecture(HSA) 표준을 구현하여 큐 관리, 시그널, 에이전트 추상화를 담당합니다. Thunk(libhsakmt)/dev/kfd ioctl을 래핑하는 사용자 공간 라이브러리로, 커널 드라이버와의 바이너리 인터페이스를 제공합니다.

HIP 프로그래밍 — CUDA 이식성

HIP(Heterogeneous Interface for Portability)의 핵심 설계 목표는 CUDA 코드베이스를 최소한의 수정으로 AMD GPU에서 실행하는 것입니다. 대부분의 cuda* 함수는 hip* 함수로 1:1 대응되며, hipify-perl 또는 hipify-clang 도구로 자동 변환이 가능합니다.

CUDA API vs HIP API 대응표

CUDA API HIP API 설명
cudaMallochipMalloc디바이스 메모리 할당
cudaFreehipFree디바이스 메모리 해제
cudaMemcpyhipMemcpy호스트↔디바이스 데이터 복사
cudaMemcpyAsynchipMemcpyAsync비동기 메모리 복사
cudaDeviceSynchronizehipDeviceSynchronize디바이스 동기화
cudaStreamCreatehipStreamCreate스트림 생성
cudaEventCreatehipEventCreate이벤트 생성
cudaGetDevicePropertieshipGetDeviceProperties디바이스 속성 조회
cudaError_thipError_t오류 타입
cudaSuccesshipSuccess성공 코드
threadIdx / blockIdxthreadIdx / blockIdx스레드·블록 인덱스 (동일)
__global____global__커널 함수 한정자 (동일)
__shared____shared__공유 메모리 (동일)
__syncthreads()__syncthreads()블록 내 동기화 배리어 (동일)
atomicAddatomicAdd원자적 덧셈 (동일)
kernel<<<grid,block>>>()hipLaunchKernelGGL(...) 또는 <<<>>>커널 실행
cublasCreaterocblas_create_handleBLAS 핸들 생성
cudnnCreatemiopenCreateDNN 라이브러리 초기화
curandCreateGeneratorhiprandCreateGenerator난수 생성기
cufftPlan1drocfft_plan_createFFT 플랜 생성

HIP 벡터 덧셈 예제

#include <hip/hip_runtime.h>
#include <stdio.h>

// GPU 커널: 두 벡터를 더해 결과를 c에 저장합니다
__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 = 1 << 20;  // 1M 요소
    size_t size = N * sizeof(float);

    // 호스트 메모리 할당 및 초기화
    float *h_a = (float*)malloc(size);
    float *h_b = (float*)malloc(size);
    float *h_c = (float*)malloc(size);
    for (int i = 0; i < N; i++) { h_a[i] = i; h_b[i] = i * 2; }

    // 디바이스 메모리 할당
    float *d_a, *d_b, *d_c;
    hipMalloc(&d_a, size);
    hipMalloc(&d_b, size);
    hipMalloc(&d_c, size);

    // 호스트 → 디바이스 복사
    hipMemcpy(d_a, h_a, size, hipMemcpyHostToDevice);
    hipMemcpy(d_b, h_b, size, hipMemcpyHostToDevice);

    // 커널 실행: 그리드 크기와 블록 크기 설정
    dim3 block(256);
    dim3 grid((N + block.x - 1) / block.x);
    vectorAdd<<<grid, block>>>(d_a, d_b, d_c, N);

    // 커널 완료 대기 및 결과 복사
    hipDeviceSynchronize();
    hipMemcpy(h_c, d_c, size, hipMemcpyDeviceToHost);

    printf("h_c[0]=%.0f, h_c[1]=%.0f\n", h_c[0], h_c[1]);

    // 메모리 해제
    hipFree(d_a); hipFree(d_b); hipFree(d_c);
    free(h_a); free(h_b); free(h_c);
    return 0;
}

hipify 도구로 CUDA 코드 변환

hipify-perl은 CUDA 소스 파일을 파싱하여 HIP 등가 API로 텍스트 치환합니다. 더 정확한 AST 기반 변환이 필요할 경우 hipify-clang을 사용합니다.

# hipify-perl: 정규식 기반 빠른 변환 (간단한 프로젝트에 적합)
hipify-perl cuda_vector_add.cu > hip_vector_add.hip

# hipify-clang: Clang AST 기반 정밀 변환 (복잡한 템플릿/매크로에 권장)
hipify-clang --cuda-path=/usr/local/cuda \
    cuda_vector_add.cu -o hip_vector_add.hip

# 변환 통계 확인: 변환 성공/실패 항목 목록 출력
hipify-perl --print-stats cuda_project.cu

# 대규모 프로젝트: 디렉터리 일괄 변환
find . -name "*.cu" | xargs -I{} hipify-perl {} -o {}.hip
HIP 컴파일 — hipcc 컴파일러: hipcc는 AMD ROCm LLVM을 기반으로 하는 래퍼 컴파일러로, --offload-arch 플래그로 타겟 GPU 아키텍처를 지정합니다. 동일 소스를 AMD와 NVIDIA 백엔드 모두로 컴파일할 수 있습니다.
# AMD GPU 타겟 (CDNA2 — MI250X)
hipcc --offload-arch=gfx90a -O3 -o vadd hip_vector_add.hip

# AMD GPU 타겟 (RDNA3 — RX 7900 XT)
hipcc --offload-arch=gfx1100 -O3 -o vadd hip_vector_add.hip

# NVIDIA 백엔드 (CUDA 경유): HIP_PLATFORM=nvidia 환경 변수 필요
HIP_PLATFORM=nvidia hipcc --offload-arch=sm_80 -O3 -o vadd hip_vector_add.hip

# 복수 아키텍처 동시 빌드 (fat binary)
hipcc --offload-arch=gfx90a --offload-arch=gfx1100 -O3 -o vadd vadd.hip

RDNA vs CDNA 아키텍처 비교

AMD GPU 아키텍처는 크게 소비자용 게이밍 GPU인 RDNA 계열과 데이터센터·AI 컴퓨트 전용 CDNA 계열로 분류됩니다. 두 아키텍처 모두 64스레드 웨이브프론트(Wavefront)를 기본 실행 단위로 사용하지만, CDNA는 디스플레이 엔진을 제거하고 행렬 연산 가속기(Matrix Core)를 추가하였습니다.

항목 RDNA 3 (소비자용) CDNA 2 (데이터센터) CDNA 3 (데이터센터)
대표 제품 Radeon RX 7900 XTX Instinct MI250X Instinct MI300X
아키텍처 코드명 GFX1100 (Navi 31) GFX90A GFX940 / GFX941
Compute Unit 수 96 CU 220 CU (×2 GCD) 304 CU
웨이브프론트 크기 32 또는 64 (Wave32/64 선택) 64 (Wave64 고정) 64 (Wave64 고정)
메모리 타입 GDDR6 24 GB HBM2e 128 GB HBM3 192 GB
메모리 대역폭 960 GB/s 3.2 TB/s 5.3 TB/s
Matrix Core (행렬 가속) 없음 (AI 가속 미지원) MFMA (FP64/FP32/BF16/FP16) MFMA + XF32
FP64 성능 ~1.7 TFLOPS ~47.9 TFLOPS ~95.7 TFLOPS
FP16/BF16 (행렬) ~123 TFLOPS ~383 TFLOPS ~1,307 TFLOPS
디스플레이 출력 있음 (DisplayPort 2.1) 없음 없음
GPU 간 연결 없음 XGMI / Infinity Fabric XGMI / Infinity Fabric
주요 용도 게임 / 렌더링 / 소비자 컴퓨트 AI 학습 / HPC / 과학 계산 AI 학습·추론 / LLM / HPC
ROCm 지원 등급 2등급 (커뮤니티) 1등급 (공식 지원) 1등급 (공식 지원)

AMD Compute Unit 내부 구조

AMD의 CU(Compute Unit)는 NVIDIA의 SM(Streaming Multiprocessor)에 대응하는 기본 컴퓨트 블록입니다. 각 CU는 4개의 SIMD 유닛(SIMD16)을 포함하며, 이들이 함께 64스레드 웨이브프론트를 처리합니다. CU 내부에는 LDS(Local Data Share — 공유 메모리), 스칼라 유닛, 스케줄러가 포함됩니다.

AMD Compute Unit (CU) 내부 구조 웨이브프론트 스케줄러 (Wavefront Scheduler) 최대 40개 웨이브프론트 동시 관리 — 지연 은폐(Latency Hiding)를 위해 라운드로빈 발행 SIMD16 유닛 #0 16 ALU 레인 FP32 / INT32 VGPR 512개 × 256 레인 (Wave64) SIMD16 유닛 #1 16 ALU 레인 FP32 / INT32 VGPR 512개 × 256 레인 (Wave64) SIMD16 유닛 #2 16 ALU 레인 FP32 / INT32 VGPR 512개 × 256 레인 (Wave64) SIMD16 유닛 #3 16 ALU 레인 FP32 / INT32 VGPR 512개 × 256 레인 (Wave64) 4 × SIMD16 = Wave64 (64 스레드 동시 처리) 스칼라 유닛 (SCA) 분기 / 조건부 실행 SGPR 102개 (웨이브프론트 공유) LDS — Local Data Share 공유 메모리 64 KB 같은 워크그룹 내 스레드 간 공유 L1 캐시 / Texture Cache 32 KB 읽기 전용 캐시 텍스처·샘플러 공유 L2 캐시 (CU 전체 공유) — RDNA3: 6 MB / CDNA2: 8 MB 글로벌 메모리(GDDR6/HBM) 접근 전 최종 캐시 계층
웨이브프론트 크기 64 vs NVIDIA 워프 32: AMD GPU는 기본 실행 단위인 웨이브프론트(Wavefront)가 64 스레드인 반면, NVIDIA GPU의 워프(Warp)는 32 스레드입니다. RDNA 3부터는 Wave32 모드도 지원하지만, CDNA 계열은 Wave64 고정입니다. 이 차이는 분기(Divergence) 처리 비용에 영향을 미칩니다. CUDA 코드를 HIP으로 이식할 때, 웨이브프론트 크기를 가정한 warpSize·__ballot_sync 등의 코드는 별도 조정이 필요합니다. HIP에서는 warpSize가 런타임에 64 또는 32로 반환되므로, 하드코딩 대신 warpSize 변수를 사용하는 것이 이식성에 유리합니다.

ROCm 개발 도구

ROCm은 GPU 프로파일링, 디버깅, 시스템 관리, 코드 변환을 위한 다양한 도구를 제공합니다. 이 도구들은 /opt/rocm/bin/에 설치됩니다.

도구 패키지 주요 기능 용도
rocminfo rocminfo GPU 에이전트 정보 출력 (HSA 속성) 시스템 확인 / 설치 검증
rocm-smi rocm-smi-lib GPU 클럭·온도·전력·VRAM 사용량 모니터링 런타임 모니터링 / 오버클럭
rocprof rocprofiler HW 카운터·커널 실행 시간·메모리 대역폭 측정 성능 분석 / 병목 탐지
roctracer roctracer HIP/HSA API 호출 타임라인 추적 API 레벨 프로파일링
rocgdb rocgdb GDB 기반 GPU 커널 디버거 (웨이브프론트 중단점) 커널 코드 디버깅
hipify-perl hipify-clang CUDA→HIP 정규식 기반 변환 CUDA 코드 이식
hipify-clang hipify-clang CUDA→HIP Clang AST 기반 정밀 변환 복잡한 CUDA 코드 이식
hipcc hip-devel HIP 소스 컴파일 (AMD/NVIDIA 백엔드 선택) HIP 코드 빌드
roc-obj-ls rocm-dev HIP fat binary에서 GPU 코드 오브젝트 추출·검사 바이너리 분석
amdgpu-install amdgpu-install ROCm 및 amdgpu 드라이버 통합 설치 스크립트 ROCm 설치·관리

ROCm 진단 및 운영 명령

# ── 시스템 정보 확인 ──────────────────────────────────────

# ROCm 버전 및 설치 경로 확인
cat /opt/rocm/.info/version
rocminfo | head -40

# GPU 에이전트 목록 및 속성 (VRAM, CU 수, 클럭 등)
rocminfo

# GPU 상태 모니터링: 클럭·온도·전력·VRAM 사용량
rocm-smi
rocm-smi --showclocks       # 현재 클럭 속도
rocm-smi --showmeminfo vram # VRAM 사용량
rocm-smi --showtemp         # 온도
rocm-smi --showpower        # 전력 소비

# ── 컴파일 및 실행 ───────────────────────────────────────

# hipcc 버전 및 타겟 아키텍처 확인
hipcc --version
rocminfo | grep -A 5 "gfx"

# HIP 프로그램 컴파일 (MI250X 타겟)
hipcc --offload-arch=gfx90a -O3 -o vadd vector_add.hip

# ── 성능 프로파일링 ──────────────────────────────────────

# rocprof: 커널별 실행 시간 측정
rocprof --stats ./vadd

# rocprof: HIP/HSA API 추적
rocprof --hsa-trace ./vadd
rocprof --hip-trace ./vadd

# 특정 HW 카운터 수집 (metrics.xml 파일로 지정)
rocprof -i metrics.xml -o output.csv ./vadd

# ── 커널 디버깅 ─────────────────────────────────────────

# rocgdb: GPU 커널 디버깅 (디버그 빌드 필요: -g -O0)
hipcc -g -O0 --offload-arch=gfx90a -o vadd_dbg vector_add.hip
rocgdb ./vadd_dbg
# (gdb 프롬프트 내 명령 예시)
# break vectorAdd     -- GPU 커널 중단점 설정
# info threads        -- 웨이브프론트 목록
# thread 2            -- 특정 웨이브프론트로 전환
# print idx           -- 변수 출력

# ── KFD 커널 드라이버 확인 ───────────────────────────────

# KFD 디바이스 노드 확인
ls -la /dev/kfd /dev/dri/renderD*

# amdgpu 드라이버 로드 상태
lsmod | grep amdgpu
dmesg | grep -i amdgpu | tail -20

# sysfs를 통한 GPU 정보 확인
cat /sys/class/drm/card0/device/gpu_busy_percent
cat /sys/class/drm/card0/device/mem_info_vram_used
ROCm 기술 문서: ROCm 소프트웨어 스택, KFD 커널 드라이버, HIP 프로그래밍 모델, RDNA/CDNA 아키텍처 비교, GPU 메모리 관리, 컴퓨트 큐 스케줄링, rocProfiler/rocTracer, Multi-GPU(XGMI/RCCL), 컨테이너 배포, AI/ML 프레임워크 통합, 디버깅, 성능 최적화, 커널 빌드 설정까지 — ROCm / HIP 기술 문서에서 14개 SVG 다이어그램과 함께 상세히 다룹니다.

Intel oneAPI / Level Zero

Intel oneAPI는 CPU·GPU·FPGA를 통합하는 오픈 표준 프로그래밍 플랫폼입니다. Level Zero는 그 중 GPU와 직접 통신하는 저수준 API로, DRM render node(xe/i915 드라이버) 위에서 동작합니다. SYCL/DPC++ 컴파일러가 Level Zero를 백엔드로 사용합니다.

Level Zero 아키텍처

Intel oneAPI / Level Zero 계층 구조 SYCL / DPC++ 고수준 C++ 추상화 OpenCL 크로스 플랫폼 API Vulkan Compute 그래픽/컴퓨트 통합 Level Zero Runtime (libze_loader.so) ze_command_list 생성 · 커널 로딩 · 메모리 관리 · 동기화 Intel GPU 유저 공간 드라이버 (libze_intel_gpu.so) i915 / xe DRM ioctl 변환 · ISA 컴파일 · 하드웨어 제출 Linux 커널: i915 / xe DRM 드라이버 /dev/dri/renderD128 → GEM BO 관리 · GuC/HuC 펌웨어 · GPU 리셋

Level Zero API 핵심 객체

/* Level Zero 핵심 API 예제 (행렬 곱) */
#include <level_zero/ze_api.h>

/* 1. 초기화 및 디바이스 선택 */
zeInit(ZE_INIT_FLAG_GPU_ONLY);

ze_driver_handle_t  hDriver;
ze_device_handle_t  hDevice;
ze_context_handle_t hContext;
zeDriverGet(&driverCount, &hDriver);
zeDeviceGet(hDriver, &deviceCount, &hDevice);
zeContextCreate(hDriver, &ctxDesc, &hContext);

/* 2. GPU 메모리 할당 */
ze_device_mem_alloc_desc_t memDesc = {
    .stype = ZE_STRUCTURE_TYPE_DEVICE_MEM_ALLOC_DESC,
    .flags = ZE_DEVICE_MEM_ALLOC_FLAG_BIAS_CACHED,
};
void *d_A, *d_B, *d_C;
zeMemAllocDevice(hContext, &memDesc, N*N*sizeof(float), 64, hDevice, &d_A);

/* 3. 커맨드 리스트 생성 및 커널 제출 */
ze_command_list_handle_t hCmdList;
zeCommandListCreate(hContext, hDevice, &cmdListDesc, &hCmdList);

zeCommandListAppendLaunchKernel(
    hCmdList, hKernel,
    &groupCount,      /* dispatch 크기 */
    hSignalEvent,     /* 완료 시그널 (ze_event) */
    0, NULL           /* 대기 이벤트 없음 */
);
zeCommandListClose(hCmdList);
zeCommandQueueExecuteCommandLists(hCmdQueue, 1, &hCmdList, hFence);
zeFenceHostSynchronize(hFence, UINT64_MAX); /* CPU 대기 */

Intel XMX — AI 행렬 가속 유닛

Intel Arc GPU(Xe-HPG 마이크로아키텍처)부터 탑재된 XMX(Xe Matrix eXtensions)는 행렬 곱을 하드웨어에서 가속하는 전용 유닛입니다. 딥러닝 추론 성능을 크게 향상시킵니다.

Intel Arc GPU 메모리 계층
레벨크기접근 범위지연
레지스터 파일256KB/EU단일 Execution Unit1 사이클
L1 캐시 (SLM)128KB/서브슬라이스서브슬라이스 내 공유~10 사이클
L2 캐시16MB (Arc A770)칩 전체 공유~100 사이클
VRAM (GDDR6/LPDDR5)8~16GB전체 GPU~500 사이클
# Intel GPU 컴퓨트 환경 확인
# xe 드라이버 (Linux 6.2+, Intel Arc/Meteor Lake/Lunar Lake)
ls /dev/dri/renderD*
dmesg | grep -i "xe\|i915"

# Level Zero 디바이스 정보
zello_world     # Level Zero 기본 테스트 도구

# Intel GPU top (GPU 사용률 모니터링)
intel_gpu_top

# DPC++ 컴파일 (SYCL → SPIR-V → Intel GPU ISA)
icpx -fsycl -o matmul matmul.cpp

# OpenCL + Rusticl 또는 Intel NEO 드라이버로 Intel GPU 사용
OCL_ICD_FILENAMES=/usr/lib/intel-opencl/libigdrcl.so clinfo
SYCL / DPC++ 컴파일 파이프라인:
  1. DPC++ (Intel LLVM 기반) — SYCL C++ 소스 파싱
  2. SPIR-V 중간 표현 생성 (-fsycl-targets=spir64)
  3. Intel GPU OpenCL 드라이버 (NEO/ocloc) — SPIR-V → GPU ISA 컴파일
  4. Level Zero / OpenCL runtime — ISA를 GPU에 로딩 및 실행
  5. /dev/dri/renderD128 ioctl → DRM xe/i915 드라이버 → 하드웨어

SYCL / DPC++ 프로그래밍

SYCL은 Khronos Group이 표준화한 단일 소스 C++ 이기종 프로그래밍 모델입니다. 같은 코드가 CPU와 GPU에서 모두 실행되며, Intel의 DPC++(Data Parallel C++)는 SYCL 표준을 구현하면서 Level Zero 또는 OpenCL을 백엔드로 사용합니다.

// SYCL 벡터 덧셈 예제 (DPC++ 컴파일러)
#include <sycl/sycl.hpp>
using namespace sycl;

int main() {
    constexpr size_t N = 1024;

    // 1. 큐(Queue) 생성 — GPU 선택기 사용
    queue q{ gpu_selector_v };

    // 2. 호스트 데이터 초기화
    std::vector<float> a(N, 1.0f), b(N, 2.0f), c(N);

    {
        // 3. 버퍼(Buffer) 생성 — 호스트↔장치 데이터 소유권 관리
        buffer<float> buf_a(a.data(), range<1>{N});
        buffer<float> buf_b(b.data(), range<1>{N});
        buffer<float> buf_c(c.data(), range<1>{N});

        // 4. 커맨드 그룹(CGH) 제출
        q.submit([&](handler& cgh) {
            // 5. 접근자(Accessor) 획득 — 읽기/쓰기 권한 선언
            auto acc_a = buf_a.get_access<access::mode::read>(cgh);
            auto acc_b = buf_b.get_access<access::mode::read>(cgh);
            auto acc_c = buf_c.get_access<access::mode::write>(cgh);

            // 6. parallel_for — GPU에서 N개 워크아이템 병렬 실행
            cgh.parallel_for(range<1>{N}, [=](id<1> idx) {
                acc_c[idx] = acc_a[idx] + acc_b[idx];
            });
        });
    } // 버퍼 소멸 시 GPU→호스트 자동 전송

    // 7. 결과 검증 (큐 완료 보장 후)
    for (size_t i = 0; i < N; ++i)
        assert(c[i] == 3.0f);
    return 0;
}
SYCL / CUDA / OpenCL API 패턴 비교
개념 SYCL / DPC++ CUDA OpenCL
장치 선택 gpu_selector_v cudaSetDevice() clGetDeviceIDs()
실행 컨텍스트 queue cudaStream_t cl_command_queue
커널 함수 람다 / SYCL_EXTERNAL __global__ 함수 __kernel 함수 (문자열)
메모리 할당 buffer 또는 USM cudaMalloc() clCreateBuffer()
커널 실행 parallel_for() kernel<<<grid,block>>>() clEnqueueNDRangeKernel()
데이터 전송 접근자 자동 관리 cudaMemcpy() clEnqueueReadBuffer()
동기화 q.wait() cudaStreamSynchronize() clFinish()
이기종 CPU+GPU 단일 소스 표준 C++ 별도 소스 파일 필요 런타임 문자열 커널
USM(Unified Shared Memory) — 포인터 기반 메모리 모델:

버퍼/접근자 방식 외에 SYCL은 CUDA의 통합 메모리와 유사한 USM을 제공합니다.

  • malloc_device() — GPU 전용 메모리, 명시적 복사 필요
  • malloc_shared() — CPU·GPU 양쪽에서 접근 가능 (마이그레이션 자동)
  • malloc_host() — 호스트 고정 메모리, GPU DMA 직접 접근

USM을 사용하면 기존 C++ 포인터 코드를 최소 수정으로 GPU에 포팅할 수 있습니다.

xe vs i915 커널 드라이버 비교

Intel GPU는 리눅스 커널에서 두 가지 DRM 드라이버로 지원됩니다. i915는 2004년부터 유지된 레거시 드라이버이고, xe는 Linux 6.2부터 병합된 차세대 드라이버로 Xe 마이크로아키텍처 이후 GPU를 주 대상으로 합니다.

xe vs i915 DRM 드라이버 비교
항목 xe (신형) i915 (레거시)
커널 병합 버전 Linux 6.2 (스테이징), 6.8 (메인라인) Linux 2.6.x 시대부터
지원 GPU 세대 Xe-HP 이후 (Arc, Meteor Lake, Lunar Lake, Battlemage) Gen3(i915)~Raptor Lake, 일부 Arc 초기 지원
GuC/HuC 펌웨어 GuC 제출 필수, HuC 자동 인증 선택적 GuC 제출, 별도 i915.enable_guc 파라미터
컴퓨트 큐 네이티브 컴퓨트 엔진 지원 (xe_engine) 렌더 링 기반 에뮬레이션
메모리 관리 TTM 기반 (VRAM + 시스템 메모리 통합) GEM 기반 (Legacy GTT)
렌더 노드 /dev/dri/renderD* 표준 지원 동일, 단 일부 Gen 에서 제한
GPU 리셋 엔진별 독립 리셋 (per-GT) 전체 장치 리셋 (wedged 상태 복구)
드라이버 소스 drivers/gpu/drm/xe/ drivers/gpu/drm/i915/
xe 드라이버 내부 아키텍처 xe_device PCI 장치 초기화 · MMIO 매핑 · TTM 메모리 관리자 xe_gt[0] — Graphics/Compute EU 클러스터 · GuC · 렌더/컴퓨트 엔진 xe_gt[1] — Media (선택) VCS · VECS · SFC 엔진 (미디어 전용) xe_engine 컴퓨트 엔진 (CCS0~3) xe_engine 렌더 엔진 (RCS) xe_engine 복사 엔진 (BCS0~8) xe_engine 비디오 엔진 (VCS/VECS) xe_exec_queue GuC 제출 컨텍스트 · 스케줄링 우선순위 · 펜스 동기화
i915에서 xe로 마이그레이션 시 주의사항:
  • 동일 시스템에서 i915와 xe를 동시 사용할 수 없습니다. 부트 파라미터 i915.force_probe=!XXXX xe.force_probe=XXXX로 전환합니다.
  • xe 드라이버는 GuC 제출 모드가 기본값이므로, /lib/firmware/xe/ 경로에 GuC 펌웨어가 반드시 있어야 합니다.
  • 기존 i915 기반 Level Zero 환경은 xe 전환 후 libze_intel_gpu.so를 최신 버전으로 업데이트해야 합니다.
  • Arc GPU(DG2/A-series)는 Linux 6.2 이상의 xe 드라이버 또는 6.5+ i915 드라이버 모두 지원합니다.

Intel GPU 컴퓨트 세대별 스펙

Intel GPU는 Gen(Generation) 번호에서 Xe(크세) 브랜드 체계로 전환하였으며, 실행 유닛(EU, Execution Unit)의 명칭도 XVE(Xe Vector Engine)로 변경되었습니다. 아래 표는 컴퓨트 관련 세대별 주요 스펙을 비교합니다.

Intel GPU 컴퓨트 세대별 주요 스펙
세대 / 제품 EU/XVE 수 XMX 지원 최대 컴퓨트 유닛 메모리 유형 FP32 성능(참고) 커널 드라이버
Gen9 (Skylake GT2) 24 EU 없음 3 슬라이스 DDR4 공유 ~0.4 TFLOPS i915
Gen11 (Ice Lake) 64 EU 없음 8 서브슬라이스 LPDDR4X 공유 ~1.0 TFLOPS i915
Gen12 / Xe-LP (Tiger Lake / DG1) 96 EU 없음 6 서브슬라이스 × 2 슬라이스 LPDDR5 / GDDR6(DG1) ~2.0 TFLOPS i915
Xe-HPG (Arc A770 / DG2) 512 XVE 있음 (XMX8) 32 DSS GDDR6 16GB ~17.2 TFLOPS i915 / xe
Xe-HPC (Ponte Vecchio / Data Center GPU Max) 4,096 XVE 있음 (XMX8) 128 DSS × 2 타일 HBM2e 128GB ~52 TFLOPS (FP32) xe
Xe2 (Battlemage / Lunar Lake) 1,024 XVE (Arc B580) 있음 (XMX16) 20 DSS (Xe-core) GDDR6 12GB / LPDDR5X ~14.6 TFLOPS xe
Intel GPU 명칭 체계 변화 (EU → XVE, 서브슬라이스 → DSS):
  • EU(Execution Unit)XVE(Xe Vector Engine): Gen12 이후 Xe 브랜드 도입과 함께 개념적으로 동일한 하드웨어 유닛의 명칭이 변경되었습니다.
  • 서브슬라이스(Subslice)DSS(Dual Sub-Slice): Xe-HPG부터 두 개의 서브슬라이스를 묶어 DSS로 명명합니다. 1 DSS = 16 XVE.
  • 슬라이스(Slice)Xe-core: Xe2부터 Xe-core가 기본 클러스터 단위가 되었으며, 1 Xe-core는 8 XVE + XMX16 유닛 + 로컬 캐시로 구성됩니다.
  • 커널 코드 및 OpenCL/Level Zero API에서는 여전히 eu_count, subslice_mask 등 구형 명칭이 혼용됩니다.

참고 사항

커널 소스 참고 경로:
  • drivers/gpu/drm/ — DRM 코어 + GPU 드라이버
  • drivers/accel/ — compute accelerator 드라이버
  • drivers/gpu/drm/amd/amdkfd/ — AMD KFD (HSA 컴퓨트)
  • include/uapi/drm/ — 유저 공간 API
외부 참고 링크:

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